From ffb1e926911720bef3d8c4eff2f3db1e9267054d Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 11 Oct 2025 20:13:41 +0530 Subject: [PATCH 001/203] ddp client basic --- app/lib/services/connect.ts | 22 +- app/lib/services/sdk.ts | 18 +- package.json | 2 + yarn.lock | 550 +++++++++++++++++++++++++++++++++++- 4 files changed, 565 insertions(+), 27 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 2c292873522..36758c5fd90 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -1,4 +1,5 @@ import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; +import { DDPSDK } from '@rocket.chat/ddp-client'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { InteractionManager } from 'react-native'; import { Q } from '@nozbe/watermelondb'; @@ -95,20 +96,15 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr sdk.initialize(server); getSettings(); - sdk.current - .connect() - .then(() => { - console.log('connected'); - }) - .catch((err: unknown) => { - console.log('connect error', err); - }); + sdk.current.connection.on('connected', () => { + console.log('connected'); + }); - connectingListener = sdk.current.onStreamData('connecting', () => { + connectingListener = sdk.current.connection.on('connecting', () => { store.dispatch(connectRequest()); }); - connectedListener = sdk.current.onStreamData('connected', () => { + connectedListener = sdk.current.connection.on('connected', () => { const { connected } = store.getState().meteor; if (connected) { return; @@ -120,16 +116,16 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr } }); - closeListener = sdk.current.onStreamData('close', () => { + closeListener = sdk.current.connection.on('close', () => { store.dispatch(disconnectAction()); }); - usersListener = sdk.current.onStreamData( + usersListener = sdk.current.client.on( 'users', protectedFunction((ddpMessage: any) => _setUser(ddpMessage)) ); - notifyAllListener = sdk.current.onStreamData( + notifyAllListener = sdk.current.stream( 'stream-notify-all', protectedFunction(async (ddpMessage: { fields: { args?: any; eventName: string } }) => { const { eventName } = ddpMessage.fields; diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 10965b48cf6..ad612fec5e5 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -1,4 +1,5 @@ import { Rocketchat } from '@rocket.chat/sdk'; +import { DDPSDK } from '@rocket.chat/ddp-client'; import EJSON from 'ejson'; import isEmpty from 'lodash/isEmpty'; @@ -15,18 +16,18 @@ import { import { compareServerVersion, random } from '../methods/helpers'; class Sdk { - private sdk: typeof Rocketchat; + private sdk: DDPSDK; private code: any; - private initializeSdk(server: string): typeof Rocketchat { + private initializeSdk(server: string): Promise { // The app can't reconnect if reopen interval is 5s while in development - return new Rocketchat({ host: server, protocol: 'ddp', useSsl: isSsl(server), reopen: __DEV__ ? 20000 : 5000 }); + return DDPSDK.createAndConnect(server); } // TODO: We need to stop returning the SDK after all methods are dehydrated - initialize(server: string) { + async initialize(server: string) { this.code = null; - this.sdk = this.initializeSdk(server); + this.sdk = await this.initializeSdk(server); return this.sdk; } @@ -40,8 +41,7 @@ class Sdk { */ disconnect() { if (this.sdk) { - this.sdk.disconnect(); - this.sdk = null; + this.sdk.connection.close(); } return null; } @@ -57,7 +57,7 @@ class Sdk { ? void : Serialized>> ): Promise>>> { - return this.current.get(endpoint, params); + return this.current.rest.get(`/v1/${endpoint}`, params); } post>( @@ -74,7 +74,7 @@ class Sdk { return new Promise(async (resolve, reject) => { const isMethodCall = endpoint?.startsWith('method.call/'); try { - const result = await this.current.post(endpoint, params); + const result = await this.current.rest.post(`/v1/${endpoint}`, params); /** * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, diff --git a/package.json b/package.json index d441c006c7a..790924803c3 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,8 @@ "@react-navigation/elements": "^2.6.1", "@react-navigation/native": "^7.1.16", "@react-navigation/native-stack": "^7.3.23", + "@rocket.chat/ddp-client": "^0.3.38", + "@rocket.chat/emitter": "^0.31.25", "@rocket.chat/message-parser": "^0.31.31", "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto", "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", diff --git a/yarn.lock b/yarn.lock index 66e8c2a16b3..2db385ab968 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2493,86 +2493,171 @@ dependencies: tslib "^2.4.0" +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + "@esbuild/aix-ppc64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz#4e0f91776c2b340e75558f60552195f6fad09f18" integrity sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA== +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + "@esbuild/android-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz#bc766407f1718923f6b8079c8c61bf86ac3a6a4f" integrity sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg== +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + "@esbuild/android-arm@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.5.tgz#4290d6d3407bae3883ad2cded1081a234473ce26" integrity sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA== +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + "@esbuild/android-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.5.tgz#40c11d9cbca4f2406548c8a9895d321bc3b35eff" integrity sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw== +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + "@esbuild/darwin-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz#49d8bf8b1df95f759ac81eb1d0736018006d7e34" integrity sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ== +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + "@esbuild/darwin-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz#e27a5d92a14886ef1d492fd50fc61a2d4d87e418" integrity sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ== +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + "@esbuild/freebsd-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz#97cede59d638840ca104e605cdb9f1b118ba0b1c" integrity sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw== +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + "@esbuild/freebsd-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz#71c77812042a1a8190c3d581e140d15b876b9c6f" integrity sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw== +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + "@esbuild/linux-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz#f7b7c8f97eff8ffd2e47f6c67eb5c9765f2181b8" integrity sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg== +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + "@esbuild/linux-arm@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz#2a0be71b6cd8201fa559aea45598dffabc05d911" integrity sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw== +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + "@esbuild/linux-ia32@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz#763414463cd9ea6fa1f96555d2762f9f84c61783" integrity sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA== +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + "@esbuild/linux-loong64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz#428cf2213ff786a502a52c96cf29d1fcf1eb8506" integrity sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg== +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + "@esbuild/linux-mips64el@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz#5cbcc7fd841b4cd53358afd33527cd394e325d96" integrity sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg== +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + "@esbuild/linux-ppc64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz#0d954ab39ce4f5e50f00c4f8c4fd38f976c13ad9" integrity sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ== +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + "@esbuild/linux-riscv64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz#0e7dd30730505abd8088321e8497e94b547bfb1e" integrity sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA== +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + "@esbuild/linux-s390x@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz#5669af81327a398a336d7e40e320b5bbd6e6e72d" integrity sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ== +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + "@esbuild/linux-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz#b2357dd153aa49038967ddc1ffd90c68a9d2a0d4" @@ -2583,6 +2668,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz#53b4dfb8fe1cee93777c9e366893bd3daa6ba63d" integrity sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw== +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + "@esbuild/netbsd-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz#a0206f6314ce7dc8713b7732703d0f58de1d1e79" @@ -2593,26 +2683,51 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz#2a796c87c44e8de78001d808c77d948a21ec22fd" integrity sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw== +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + "@esbuild/openbsd-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz#28d0cd8909b7fa3953af998f2b2ed34f576728f0" integrity sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg== +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + "@esbuild/sunos-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz#a28164f5b997e8247d407e36c90d3fd5ddbe0dc5" integrity sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA== +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + "@esbuild/win32-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz#6eadbead38e8bd12f633a5190e45eff80e24007e" integrity sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw== +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + "@esbuild/win32-ia32@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz#bab6288005482f9ed2adb9ded7e88eba9a62cc0d" integrity sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ== +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + "@esbuild/win32-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz#7fc114af5f6563f19f73324b5d5ff36ece0803d1" @@ -3428,6 +3543,14 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@inquirer/external-editor@^1.0.0": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@inquirer/external-editor/-/external-editor-1.0.2.tgz#dc16e7064c46c53be09918db639ff780718c071a" + integrity sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ== + dependencies: + chardet "^2.1.0" + iconv-lite "^0.7.0" + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -3783,6 +3906,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@msgpack/msgpack@3.0.0-beta2": + version "3.0.0-beta2" + resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz#5bccee30f84df220b33905e3d8249ba96deca0b7" + integrity sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw== + "@napi-rs/wasm-runtime@^0.2.11": version "0.2.12" resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" @@ -4904,6 +5032,61 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.2.1.tgz#9403f51c17cae37edf870c6bc0c81c1ece5ccef8" integrity sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA== +"@rocket.chat/api-client@^0.2.38": + version "0.2.38" + resolved "https://registry.yarnpkg.com/@rocket.chat/api-client/-/api-client-0.2.38.tgz#7d50f69db48d89f67ecf09c404f75ff9abb7d3fe" + integrity sha512-yC4wEW1LzNa1S1636RxBCysaFmzCyrjhTUVTBoJaZcSJLL9wKx2e05NYFQFPA54I+7zt5GQDRL8zRtjAjQkEOQ== + dependencies: + "@rocket.chat/core-typings" "^7.10.1" + "@rocket.chat/rest-typings" "^7.10.1" + filter-obj "^3.0.0" + query-string "^7.1.3" + split-on-first "^3.0.0" + strict-uri-encode "^2.0.0" + +"@rocket.chat/apps-engine@1.46.0": + version "1.46.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/apps-engine/-/apps-engine-1.46.0.tgz#d40b6818803e9d9bceb387c3972ad6eb1e154944" + integrity sha512-9ZQD/0xibyGRtk+OuUXTCV5onja2alL1irpw1J5ru3YeUWwe+BFwdHdRYITFsfj23LTYSydfGzCOvuNTPBQi1Q== + dependencies: + "@msgpack/msgpack" "3.0.0-beta2" + adm-zip "^0.5.9" + cryptiles "^4.1.3" + debug "^4.3.4" + deno-bin "1.37.1" + esbuild "^0.20.2" + jose "^4.11.1" + jsonrpc-lite "^2.2.0" + lodash.clonedeep "^4.5.0" + semver "^5.7.1" + stack-trace "0.0.10" + uuid "~8.3.2" + +"@rocket.chat/core-typings@^7.10.1", "@rocket.chat/core-typings@~7.10.1": + version "6.13.1" + resolved "https://registry.yarnpkg.com/@rocket.chat/core-typings/-/core-typings-6.13.1.tgz#50d759233683998f76529f0af4d36a6c6bf61429" + integrity sha512-braIc3mvjju01HSc7jK3I3rbBtjVwL9yvUy1rSb4OBMOoSxE0UqQRR01q2djhgxYqCeIrTWOVgQyO9qPxW38Zw== + dependencies: + "@rocket.chat/apps-engine" "1.46.0" + "@rocket.chat/icons" "~0.38.0" + "@rocket.chat/message-parser" "^0.31.31" + "@rocket.chat/ui-kit" "~0.36.1" + "@types/express" "^4.17.21" + +"@rocket.chat/ddp-client@^0.3.38": + version "0.3.38" + resolved "https://registry.yarnpkg.com/@rocket.chat/ddp-client/-/ddp-client-0.3.38.tgz#5fad1e47f0186f0243c0bc3623e69c3e7760c20c" + integrity sha512-w/kqTsWhrT/gR4NNWQFlL22He6E0zPauZVvqfbRGL3ulfxsWCopYqPH4k2qU/nGqDeuB2rERvNNtv45cWi/r4Q== + dependencies: + "@rocket.chat/api-client" "^0.2.38" + "@rocket.chat/core-typings" "~7.10.1" + "@rocket.chat/rest-typings" "^7.10.1" + +"@rocket.chat/emitter@^0.31.25": + version "0.31.25" + resolved "https://registry.yarnpkg.com/@rocket.chat/emitter/-/emitter-0.31.25.tgz#17dd7838dc988ebc5fcd96343c4431d5cef45845" + integrity sha512-hw5BpDlNwpYSb+K5X3DNMNUVEVXxmXugUPetGZGCWvntSVFsOjYuVEypoKW6vBBXSfqCBb0kN1npYcKEb4NFBw== + "@rocket.chat/eslint-config@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@rocket.chat/eslint-config/-/eslint-config-0.4.0.tgz#d648decd02ae739eac17a32e1630332a75318ea1" @@ -4911,6 +5094,11 @@ dependencies: eslint-plugin-import "^2.17.2" +"@rocket.chat/icons@~0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/icons/-/icons-0.38.0.tgz#12dab5d3aec57680b63ee72181ab7679fb895427" + integrity sha512-T0p6dIxyQSoYTQ9CQnq11vXMkadOXFuT8vYW2SG/JEieCJeZMTRF9hVBRC31pQB/4KF5qAtfXY6gdDC0iTBzCw== + "@rocket.chat/message-parser@^0.31.31": version "0.31.31" resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.31.31.tgz#9a3eea7602ac37387c6384577623865c8d536003" @@ -4918,10 +5106,28 @@ dependencies: tldts "~5.7.112" +"@rocket.chat/message-parser@^0.31.32": + version "0.31.32" + resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.31.32.tgz#100ba61165e960303695e50a51ba0427058ea95a" + integrity sha512-n13B5NnZxoTIMUn9n5fEhXFAsze9EKWmI0szIv1ALoGEb5RXs9wCiFuOMajCwm+gw597hQL5ZTDoQFyrns/s2w== + dependencies: + tldts "~5.7.112" + "@rocket.chat/mobile-crypto@RocketChat/rocket.chat-mobile-crypto": version "0.1.0" resolved "https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/269d60a8d34ef74678af6c2a3c36a2c1c1205cb5" +"@rocket.chat/rest-typings@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@rocket.chat/rest-typings/-/rest-typings-7.10.1.tgz#d5493ce9efc53d1bd4bc2745acae649dcab54edf" + integrity sha512-jNttcWuUYIFBkwoyK7aPi8oS/crZU5UEmIxVxzuG6pGpV04vLkRLyG+ZrQlAhhlk6fQWRG8M0rBxSyPATPfGiA== + dependencies: + "@rocket.chat/core-typings" "^7.10.1" + "@rocket.chat/message-parser" "^0.31.32" + "@rocket.chat/ui-kit" "~0.37.0" + ajv "^8.17.1" + ajv-formats "^3.0.1" + "@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": version "1.3.3-mobile" resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/b6d2b3f25b0ff8283dd71faf6dae720c47fecd8f" @@ -4937,6 +5143,25 @@ resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.31.19.tgz#737103123bc7e635382217eef75965b7e0f44703" integrity sha512-8zRKQ5CoC4hIuYHVheO0d7etX9oizmM18fu99r5s/deciL/0MRWocdb4H/QsmbsNrkKCO6Z6wr7f9zzJCNTRHg== +"@rocket.chat/ui-kit@~0.36.1": + version "0.36.1" + resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.36.1.tgz#0364c9dd92500aee561f5d550631a7f375a7f9e1" + integrity sha512-3gqNOc5LAmRlwGmre2fjSd0ey364qwA+NvId0D8bCObwhJCnq2u547p/knPa7uOBaRgKZFeMhX0wiK9mvfNHiA== + dependencies: + typia "~5.3.3" + +"@rocket.chat/ui-kit@~0.37.0": + version "0.37.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.37.0.tgz#aa5fc6c1ce1adce3f0ab76b265164a5b37c67a5d" + integrity sha512-NoWdmyKSJys9T7nqJBJvxNi8o6lcVZwWKelCbpCP6YCSHrUUfzqCU4bg7F5nrkVBH8C9MKd5T/r3kzyicL+GHg== + dependencies: + typia "~6.9.0" + +"@samchon/openapi@^0.4.6": + version "0.4.9" + resolved "https://registry.yarnpkg.com/@samchon/openapi/-/openapi-0.4.9.tgz#7dbfa20b415d39a7f8733f43d1ce219e2447b364" + integrity sha512-mvCHjm6WmfB7ObRukAM7djBTNgOtLrT5M++t530txQg+UaqsaGtLgoZ2WNkULgLtqRWxncjty3tfik4DGveOMQ== + "@sideway/address@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" @@ -5139,6 +5364,14 @@ dependencies: "@babel/types" "^7.20.7" +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/bytebuffer@^5.0.44": version "5.0.48" resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.48.tgz#0e6b0e8b8dbafde3314148ed378b8aed6eaac4f1" @@ -5147,11 +5380,38 @@ "@types/long" "^3.0.0" "@types/node" "*" +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + "@types/ejson@^2.1.3": version "2.2.2" resolved "https://registry.yarnpkg.com/@types/ejson/-/ejson-2.2.2.tgz#54e8f33ae02ac5854ce3a3cd721ba75cbcdfbd44" integrity sha512-4DEXy6yKJVr1DQx4xy+y7NV9mK6p1vgikvtbOITWgp2Prs6yhffqZ8XOjEtojX2pzC7B1KF9kcQe9Bv52E9K2A== +"@types/express-serve-static-core@^4.17.33": + version "4.19.7" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" + integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.21": + version "4.17.23" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" + integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -5179,6 +5439,11 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + "@types/i18n-js@3.8.3": version "3.8.3" resolved "https://registry.yarnpkg.com/@types/i18n-js/-/i18n-js-3.8.3.tgz#9aac0ac81c0b5f16d864b395607442eebb382528" @@ -5255,6 +5520,11 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-3.0.32.tgz#f4e5af31e9e9b196d8e5fca8a5e2e20aa3d60b69" integrity sha512-ZXyOOm83p7X8p3s0IYM3VeueNmHpkk/yMlP8CLeOnEcu6hIwPH7YjZBvhQkR0ZFS2DqZAxKtJ/M5fcuv3OU5BA== +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + "@types/node@*": version "20.11.28" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.28.tgz#4fd5b2daff2e580c12316e457473d68f15ee6f66" @@ -5284,6 +5554,16 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== +"@types/qs@*": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + "@types/react-native-background-timer@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/react-native-background-timer/-/react-native-background-timer-2.0.2.tgz#22f9126124068a0ee6a812e16f98e270c2b9b2c7" @@ -5357,6 +5637,30 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== +"@types/send@*": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.0.tgz#ae9dfa0e3ab0306d3c566182324a54c4be2fb45a" + integrity sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ== + dependencies: + "@types/node" "*" + +"@types/send@<1": + version "0.17.5" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" + integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.9" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.9.tgz#f9b08ab7dd8bbb076f06f5f983b683654fe0a025" + integrity sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "<1" + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -5840,6 +6144,11 @@ acorn@^8.11.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== +adm-zip@^0.5.4, adm-zip@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" + integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -5867,6 +6176,13 @@ ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv-keywords@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" @@ -5894,6 +6210,16 @@ ajv@^8.0.0, ajv@^8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" +ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + anser@^1.4.9: version "1.4.10" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" @@ -6046,6 +6372,11 @@ array-includes@^3.1.4, array-includes@^3.1.6, array-includes@^3.1.7: get-intrinsic "^1.2.4" is-string "^1.0.7" +array-timsort@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-timsort/-/array-timsort-1.0.3.tgz#3c9e4199e54fb2b9c3fe5976396a21614ef0d926" + integrity sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ== + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -6502,6 +6833,13 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boom@7.x.x: + version "7.3.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-7.3.0.tgz#733a6d956d33b0b1999da3fe6c12996950d017b9" + integrity sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A== + dependencies: + hoek "6.x.x" + bplist-creator@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" @@ -6729,6 +7067,11 @@ char-regex@^2.0.0: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e" integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw== +chardet@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-2.1.0.tgz#1007f441a1ae9f9199a4a67f6e978fb0aa9aa3fe" + integrity sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA== + check-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" @@ -6825,6 +7168,11 @@ cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -6973,6 +7321,11 @@ commander@9.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== +commander@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" + integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== + commander@^12.0.0: version "12.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" @@ -7008,6 +7361,15 @@ commander@^9.4.1: resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== +comment-json@^4.2.3: + version "4.4.1" + resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-4.4.1.tgz#0757e3ba31a9e56f3f6e00bdaae114384ac8bcf3" + integrity sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg== + dependencies: + array-timsort "^1.0.3" + core-util-is "^1.0.3" + esprima "^4.0.1" + common-path-prefix@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" @@ -7115,7 +7477,7 @@ core-js-pure@^3.30.2: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.36.0.tgz#ffb34330b14e594d6a9835cf5843b4123f1d95db" integrity sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ== -core-util-is@~1.0.0: +core-util-is@^1.0.3, core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== @@ -7173,6 +7535,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +cryptiles@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.3.tgz#2461d3390ea0b82c643a6ba79f0ed491b0934c25" + integrity sha512-gT9nyTMSUC1JnziQpPbxKGBbUg8VL7Zn2NB4E1cJYvuXdElHrwxrV9bmltZGDzet45zSDGyYceueke1TjynGzw== + dependencies: + boom "7.x.x" + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -7421,6 +7790,14 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +deno-bin@1.37.1: + version "1.37.1" + resolved "https://registry.yarnpkg.com/deno-bin/-/deno-bin-1.37.1.tgz#b5a75292c53497e68dbf9e30d2394ff449e9a996" + integrity sha512-RORiEtlUPKJqXVYO8P/B1sdOkYLGfSYi2kiRzXb1WUHzkq14yAQr73gXrRU3jcM7do77Rwq+ZR+XZ2Adq8Uymg== + dependencies: + adm-zip "^0.5.4" + follow-redirects "^1.10.0" + denodeify@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" @@ -7564,6 +7941,11 @@ dotenv@^16.4.5, dotenv@~16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== +drange@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/drange/-/drange-1.1.1.tgz#b2aecec2aab82fcef11dbbd7b9e32b83f8f6c0b8" + integrity sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA== + dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -7928,6 +8310,35 @@ esbuild-register@^3.5.0: "@esbuild/win32-ia32" "0.25.5" "@esbuild/win32-x64" "0.25.5" +esbuild@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + escalade@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" @@ -8549,6 +8960,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: version "4.4.1" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" @@ -8589,6 +9005,13 @@ fdir@^6.5.0: resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -8608,6 +9031,11 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== +filter-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-3.0.0.tgz#dba2f9e64e921758ed9a51028ca2ed02d4cccbcf" + integrity sha512-oQZM+QmVni8MsYzcq9lgTHD/qeLqaG8XaOPOW7dzuSafVxSUlH1+1ZDefj2OD9f2XsmG5lFl2Euc9NI4jgwFWg== + finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -8746,6 +9174,11 @@ flow-parser@^0.206.0: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.206.0.tgz#f4f794f8026535278393308e01ea72f31000bfef" integrity sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w== +follow-redirects@^1.10.0: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + follow-redirects@^1.15.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -9228,6 +9661,11 @@ hoek@4.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.3.1.tgz#1d2ea831857e4ecdce7fd5ffe07acdf8eb368cca" integrity sha512-v7E+yIjcHECn973i0xHm4kJkEpv3C8sbYS4344WXbzYqRyiDD7rjnnKo4hsJkejQBAFdRMUGNHySeSPKSH9Rqw== +hoek@6.x.x: + version "6.1.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" + integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== + hoist-non-react-statics@3.3.2, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -9324,6 +9762,13 @@ iconv-lite@0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.0.tgz#c50cd80e6746ca8115eb98743afa81aa0e147a3e" + integrity sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + idb@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" @@ -9423,6 +9868,27 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +inquirer@^8.2.5: + version "8.2.7" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.7.tgz#62f6b931a9b7f8735dc42db927316d8fb6f71de8" + integrity sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA== + dependencies: + "@inquirer/external-editor" "^1.0.0" + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^6.0.1" + internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" @@ -10297,6 +10763,11 @@ joi@^7.0.1: moment "2.x.x" topo "2.x.x" +jose@^4.11.1: + version "4.15.9" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" + integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== + jpeg-js@0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" @@ -10497,6 +10968,11 @@ jsonify@^0.0.1: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== +jsonrpc-lite@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jsonrpc-lite/-/jsonrpc-lite-2.2.0.tgz#fb3aa9d292c8970eb7f83c6040c6554767bbc6a6" + integrity sha512-/cbbSxtZWs1O7R4tWqabrCM/t3N8qKUZMAg9IUqpPvUs6UyRvm6pCNYkskyKN/XU0UgffW+NY2ZRr8t0AknX7g== + jsrsasign@11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-11.0.0.tgz#766570c21f87d68075a142f5188f7e583cee9d70" @@ -10737,6 +11213,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -11499,6 +11980,11 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + mz@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -12397,6 +12883,14 @@ queue@6.0.2: dependencies: inherits "~2.0.3" +randexp@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.5.3.tgz#f31c2de3148b30bdeb84b7c3f59b0ebb9fec3738" + integrity sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w== + dependencies: + drange "^1.0.2" + ret "^0.2.0" + random-bytes@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" @@ -13263,6 +13757,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +ret@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.2.2.tgz#b6861782a1f4762dce43402a71eb7a283f44573c" + integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -13294,6 +13793,11 @@ rimraf@~2.6.2: dependencies: glob "^7.1.3" +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -13308,7 +13812,7 @@ rxjs@^7.5.1: dependencies: tslib "^2.1.0" -rxjs@^7.8.1: +rxjs@^7.5.5, rxjs@^7.8.1: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== @@ -13404,7 +13908,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== -"semver@2 || 3 || 4 || 5", semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.6.0, semver@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -13798,6 +14302,11 @@ split-on-first@^1.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== +split-on-first@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" + integrity sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -13820,6 +14329,11 @@ stack-generator@^2.0.3, stack-generator@^2.0.5: dependencies: stackframe "^1.3.4" +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -14329,7 +14843,7 @@ through2@^2.0.1: readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.8: +through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== @@ -14634,6 +15148,27 @@ typescript@5.4.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +typia@~5.3.3: + version "5.3.12" + resolved "https://registry.yarnpkg.com/typia/-/typia-5.3.12.tgz#848b07d28b783772322518053c9c250e90357ef6" + integrity sha512-PzgbFt08j7Cb3NKjB6KX7FTWD8dX0MicQ3zXqhDx0cMAeEd+i/omO9B+X/tvtuZ1e2w+2jz1QE3qXMF0/o/LDA== + dependencies: + commander "^10.0.0" + comment-json "^4.2.3" + inquirer "^8.2.5" + randexp "^0.5.3" + +typia@~6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/typia/-/typia-6.9.0.tgz#60da0f18884510f983c1e1b206aa0244a58c4dc3" + integrity sha512-Vgo3CmE9XZhb/zsrW6Q4IQk7JeJk+ZVGxu/T4QZdI/PEokOpn00mGFMk9hhXNA27jrYC0NCgalO+YcZYsR5nGw== + dependencies: + "@samchon/openapi" "^0.4.6" + commander "^10.0.0" + comment-json "^4.2.3" + inquirer "^8.2.5" + randexp "^0.5.3" + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" @@ -14871,6 +15406,11 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@~8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-to-istanbul@^9.0.1: version "9.2.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" @@ -15112,7 +15652,7 @@ wordwrapjs@^4.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^6.2.0: +wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== From 28cd8e747e5cdeb1b1dee876a61a3633657c53d1 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:37:46 +0530 Subject: [PATCH 002/203] Some more changes --- app/lib/methods/getSettings.ts | 6 +- app/lib/services/connect.ts | 266 +++++++++++++-------------------- app/lib/services/sdk.ts | 257 +++++++++++++++++-------------- app/lib/services/twoFactor.ts | 7 - 4 files changed, 249 insertions(+), 287 deletions(-) diff --git a/app/lib/methods/getSettings.ts b/app/lib/methods/getSettings.ts index e1f3a7d733b..603a944a27f 100644 --- a/app/lib/methods/getSettings.ts +++ b/app/lib/methods/getSettings.ts @@ -143,7 +143,7 @@ export async function setSettings(): Promise { reduxStore.dispatch(addSettings(parseSettings(parsed.slice(0, parsed.length)))); } -export function subscribeSettings(): void { +export function subscribeSettings() { return sdk.subscribe('stream-notify-all', 'public-settings-changed'); } @@ -159,8 +159,8 @@ export async function getSettings(): Promise { let settings: IData[] = []; const serverVersion = reduxStore.getState().server.version; const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') - ? `${sdk.current.client.host}/api/v1/settings.public?_id=${settingsParams.join(',')}` - : `${sdk.current.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}`; + ? `${sdk.current?.connection.url}/api/v1/settings.public?_id=${settingsParams.join(',')}` + : `${sdk.current?.connection.url}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}`; // Iterate over paginated results to retrieve all settings do { // TODO: why is no-await-in-loop enforced in the first place? diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 36758c5fd90..a47a5f16bad 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -1,8 +1,7 @@ -import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; -import { DDPSDK } from '@rocket.chat/ddp-client'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { InteractionManager } from 'react-native'; import { Q } from '@nozbe/watermelondb'; +import { DDPSDK } from '@rocket.chat/ddp-client'; import log from '../methods/helpers/log'; import { setActiveUsers } from '../../actions/activeUsers'; @@ -13,21 +12,19 @@ import { store } from '../store/auxStore'; import { loginRequest, logout, setLoginServices, setUser } from '../../actions/login'; import sdk from './sdk'; import I18n from '../../i18n'; -import { type ICredentials, type ILoggedUser, STATUSES } from '../../definitions'; +import { ICredentials, ILoggedUser, STATUSES } from '../../definitions'; import { connectRequest, connectSuccess, disconnect as disconnectAction } from '../../actions/connect'; import { updatePermission } from '../../actions/permissions'; import EventEmitter from '../methods/helpers/events'; import { updateSettings } from '../../actions/settings'; import { defaultSettings } from '../constants/defaultSettings'; -import { unsubscribeRooms } from '../methods/subscribeRooms'; -import { getSettings } from '../methods/getSettings'; -import { onRolesChanged } from '../methods/getRoles'; -import { setPresenceCap } from '../methods/getUsersPresence'; -import { _setUser, type IActiveUsers, _setUserTimer, _activeUsers } from '../methods/setUser'; -import { compareServerVersion } from '../methods/helpers/compareServerVersion'; -import { isIOS } from '../methods/helpers/deviceInfo'; -import { isSsl } from '../methods/helpers/isSsl'; -import fetch from '../methods/helpers/fetch'; +import { compareServerVersion, isIOS } from '../methods/helpers'; +import { onRolesChanged } from 'lib/methods/getRoles'; +import { getSettings } from 'lib/methods/getSettings'; +import { setPresenceCap } from 'lib/methods/getUsersPresence'; +import { _setUser, _activeUsers, _setUserTimer } from 'lib/methods/setUser'; +import { unsubscribeRooms } from 'lib/methods/subscribeRooms'; +import { IActiveUsers } from 'reducers/activeUsers'; interface IServices { [index: string]: string | boolean; @@ -38,94 +35,47 @@ interface IServices { service: string; } -let connectingListener: any; -let connectedListener: any; -let closeListener: any; -let usersListener: any; -let notifyAllListener: any; -let rolesListener: any; -let notifyLoggedListener: any; -let logoutListener: any; - function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise { - return new Promise(resolve => { - // Check for running requests and abort them before connecting to the server - abort(); + return new Promise(async resolve => { + if (sdk.current?.connection.url === server) { + return resolve(); + } disconnect(); database.setActiveDB(server); - store.dispatch(connectRequest()); - - if (connectingListener) { - connectingListener.then(stopListener); - } - - if (connectedListener) { - connectedListener.then(stopListener); - } - - if (closeListener) { - closeListener.then(stopListener); - } - - if (usersListener) { - usersListener.then(stopListener); - } - - if (notifyAllListener) { - notifyAllListener.then(stopListener); - } - - if (rolesListener) { - rolesListener.then(stopListener); - } - - if (notifyLoggedListener) { - notifyLoggedListener.then(stopListener); - } - - if (logoutListener) { - logoutListener.then(stopListener); - } - unsubscribeRooms(); EventEmitter.emit('INQUIRY_UNSUBSCRIBE'); - sdk.initialize(server); + await sdk.initialize(server); + resolve(); getSettings(); - sdk.current.connection.on('connected', () => { - console.log('connected'); - }); - - connectingListener = sdk.current.connection.on('connecting', () => { - store.dispatch(connectRequest()); - }); - - connectedListener = sdk.current.connection.on('connected', () => { - const { connected } = store.getState().meteor; - if (connected) { - return; + sdk.current?.connection.on('connection', status => { + if (['connecting', 'reconnecting'].includes(status)) { + store.dispatch(connectRequest()); } - store.dispatch(connectSuccess()); - const { user } = store.getState().login; - if (user?.token) { - store.dispatch(loginRequest({ resume: user.token }, logoutOnError)); + if (status === 'connected') { + const { connected } = store.getState().meteor; + if (connected) { + return; + } + store.dispatch(connectSuccess()); + const { user } = store.getState().login; + if (user?.token) { + store.dispatch(loginRequest({ resume: user.token }, logoutOnError)); + } + } + if (['disconnected', 'closed'].includes(status)) { + store.dispatch(disconnectAction()); } }); + await sdk.current?.connection.connect(); - closeListener = sdk.current.connection.on('close', () => { - store.dispatch(disconnectAction()); - }); - - usersListener = sdk.current.client.on( - 'users', - protectedFunction((ddpMessage: any) => _setUser(ddpMessage)) - ); + sdk.onCollection('users', (ddpMessage: any) => _setUser(ddpMessage)); - notifyAllListener = sdk.current.stream( + sdk.onCollection( 'stream-notify-all', protectedFunction(async (ddpMessage: { fields: { args?: any; eventName: string } }) => { const { eventName } = ddpMessage.fields; @@ -163,28 +113,31 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr }) ); - rolesListener = sdk.current.onStreamData( + sdk.onCollection( 'stream-roles', protectedFunction((ddpMessage: any) => onRolesChanged(ddpMessage)) ); // RC 4.1 - sdk.current.onStreamData('stream-user-presence', (ddpMessage: { fields: { args?: any; uid?: any } }) => { - const userStatus = ddpMessage.fields.args[0]; - const { uid } = ddpMessage.fields; - const [, status, statusText] = userStatus; - const newStatus = { status: STATUSES[status], statusText }; - // @ts-ignore - store.dispatch(setActiveUsers({ [uid]: newStatus })); - - const { user: loggedUser } = store.getState().login; - if (loggedUser && loggedUser.id === uid) { - // @ts-ignore - store.dispatch(setUser(newStatus)); + sdk.onCollection('stream-user-presence', ddpMessage => { + if (ddpMessage.msg === 'added' || ddpMessage.msg === 'changed') { + if (!ddpMessage.fields) { + return; + } + const userStatus = ddpMessage.fields.args[0]; + const { uid } = ddpMessage.fields; + const [, status, statusText] = userStatus; + const newStatus = { status: STATUSES[status], statusText }; + store.dispatch(setActiveUsers({ [uid]: newStatus })); + + const { user: loggedUser } = store.getState().login; + if (loggedUser && loggedUser.id === uid) { + store.dispatch(setUser(newStatus)); + } } }); - notifyLoggedListener = sdk.current.onStreamData( + sdk.onCollection( 'stream-notify-logged', protectedFunction(async (ddpMessage: { fields: { args?: any; eventName?: any } }) => { const { eventName } = ddpMessage.fields; @@ -200,7 +153,8 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr store.dispatch(setActiveUsers(activeUsersBatch)); }); _setUserTimer.setUserTimer = null; - _activeUsers.activeUsers = {} as IActiveUsers; + //@ts-ignore - fix me + _activeUsers.activeUsers = {} return null; }, 10000); } @@ -265,7 +219,7 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr }) ); - logoutListener = sdk.current.onStreamData('stream-force_logout', () => store.dispatch(logout(true))); + sdk.onCollection('stream-force_logout', () => store.dispatch(logout(true))); resolve(); }); @@ -275,60 +229,62 @@ function stopListener(listener: any): boolean { return listener && listener.stop(); } -async function login(credentials: ICredentials): Promise { - // RC 0.64.0 - await sdk.current.login(credentials); +async function login(credentials: ICredentials, isFromWebView = false): Promise { + // TODO: other login methods: ldap, saml, cas, apple, oauth, oauth_custom + const result = await sdk.login(credentials); + const { me } = result; const serverVersion = store.getState().server.version; - const result = sdk.current.currentLogin?.result; + const loginUser = sdk.current?.account.user; + + if (!me) { + throw new Error("Couldn't fetch user data"); + } let enableMessageParserEarlyAdoption = true; let showMessageInMainThread = false; if (compareServerVersion(serverVersion, 'lowerThan', '5.0.0')) { - enableMessageParserEarlyAdoption = result.me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true; - showMessageInMainThread = result.me.settings?.preferences?.showMessageInMainThread ?? true; + enableMessageParserEarlyAdoption = me.settings?.preferences?.enableMessageParserEarlyAdoption ?? true; + showMessageInMainThread = me.settings?.preferences?.showMessageInMainThread ?? true; } - if (result) { + if (loginUser) { + // TODO: review type const user: ILoggedUser = { - id: result.userId, - token: result.authToken, - username: result.me.username, - name: result.me.name, - language: result.me.language, - status: result.me.status, - statusText: result.me.statusText, - customFields: result.me.customFields, - statusLivechat: result.me.statusLivechat, - emails: result.me.emails, - roles: result.me.roles, - avatarETag: result.me.avatarETag, + id: loginUser.id, + token: loginUser.token as string, + username: me.username as string, + name: me.name, + language: me.language, + status: me.status as ILoggedUser['status'], + statusText: me.statusText, + customFields: me.customFields, + statusLivechat: me.statusLivechat, + emails: me.emails, + roles: me.roles, + avatarETag: me.avatarETag, showMessageInMainThread, enableMessageParserEarlyAdoption, - alsoSendThreadToChannel: result.me.settings?.preferences?.alsoSendThreadToChannel, - bio: result.me.bio, - nickname: result.me.nickname, - requirePasswordChange: result.me.requirePasswordChange + alsoSendThreadToChannel: me.settings?.preferences?.alsoSendThreadToChannel, + bio: me.bio, + nickname: me.nickname, + requirePasswordChange: me.requirePasswordChange }; return user; } } -function loginTOTP(params: ICredentials, loginEmailPassword?: boolean): Promise { +function loginTOTP(params: ICredentials, loginEmailPassword?: boolean, isFromWebView = false): Promise { return new Promise(async (resolve, reject) => { try { - const result = await login(params); + const result = await login(params, isFromWebView); if (result) { return resolve(result); } } catch (e: any) { if (e.data?.error && (e.data.error === 'totp-required' || e.data.error === 'totp-invalid')) { - const { details, error } = e.data; + const { details } = e.data; try { - const code = await twoFactor({ - params, - method: details?.method || 'totp', - invalid: (details.error || error) === 'totp-invalid' - }); + const code = await twoFactor({ method: details?.method || 'totp', invalid: details?.error === 'totp-invalid' }); if (loginEmailPassword) { store.dispatch(setUser({ username: params.user || params.username })); @@ -387,19 +343,13 @@ function loginWithPassword({ user, password }: { user: string; password: string return loginTOTP(params, true); } -async function loginOAuthOrSso(params: ICredentials) { - const result = await loginTOTP(params, false); - store.dispatch(loginRequest({ resume: result.token }, false)); -} - -function abort() { - if (sdk.current) { - return sdk.current.abort(); - } +async function loginOAuthOrSso(params: ICredentials, isFromWebView = true) { + const result = await loginTOTP(params, false, isFromWebView); + store.dispatch(loginRequest({ resume: result.token }, false, isFromWebView)); } function checkAndReopen() { - return sdk.current.checkAndReopen(); + console.log('TODO: CHECK AND REOPEN: do we need to hurry connection on app foreground?'); } function disconnect() { @@ -411,24 +361,19 @@ async function getWebsocketInfo({ }: { server: string; }): Promise<{ success: true } | { success: false; message: string }> { - const websocketSdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) }); - try { - await websocketSdk.connect(); - } catch (err: any) { - if (err.message && err.message.includes('400')) { - return { - success: false, - message: I18n.t('Websocket_disabled', { contact: I18n.t('Contact_your_server_admin') }) - }; - } - } - - websocketSdk.disconnect(); + const sdk = await DDPSDK.createAndConnect(server); + sdk.connection.close(); - return { - success: true - }; + return { + success: true + }; + } catch { + return { + success: false, + message: I18n.t('Websocket_disabled', { contact: I18n.t('Contact_your_server_admin') }) + }; + } } async function getLoginServices(server: string) { @@ -461,11 +406,11 @@ async function getLoginServices(server: string) { } function determineAuthType(services: IServices) { - const { name, custom, showButton, service } = services; + const { name, custom, showButton = true, service } = services; const authName = name || service; - if (custom && showButton !== false) { + if (custom && showButton) { return 'oauth_custom'; } @@ -492,11 +437,10 @@ export { loginWithPassword, loginOAuthOrSso, checkAndReopen, - abort, connect, disconnect, getWebsocketInfo, stopListener, getLoginServices, determineAuthType -}; +}; \ No newline at end of file diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index ad612fec5e5..8f38f82925b 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -1,117 +1,130 @@ -import { Rocketchat } from '@rocket.chat/sdk'; -import { DDPSDK } from '@rocket.chat/ddp-client'; import EJSON from 'ejson'; import isEmpty from 'lodash/isEmpty'; +import { ClientStream, DDPSDK } from '@rocket.chat/ddp-client'; +import { Platform } from 'react-native'; +import DeviceInfo from 'react-native-device-info'; import { twoFactor } from './twoFactor'; -import { isSsl } from '../methods/helpers/isSsl'; import { store as reduxStore } from '../store/auxStore'; -import { - type Serialized, - type MatchPathPattern, - type OperationParams, - type PathFor, - type ResultFor -} from '../../definitions/rest/helpers'; -import { compareServerVersion, random } from '../methods/helpers'; +import { random } from '../methods/helpers'; +import UserPreferences from '../methods/userPreferences'; +import { BASIC_AUTH_KEY } from 'lib/methods/helpers/fetch'; class Sdk { - private sdk: DDPSDK; - private code: any; - - private initializeSdk(server: string): Promise { - // The app can't reconnect if reopen interval is 5s while in development - return DDPSDK.createAndConnect(server); + private sdk: DDPSDK | undefined; + private code: any = null; + private headers: Record = { + 'User-Agent': `RC Mobile; ${ + Platform.OS + } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` + }; + + async initialize(server: string): Promise { + this.sdk = await DDPSDK.create(server); + this.setBasicAuth(); + this.sdk.rest.handleTwoFactorChallenge(this.twoFactorHandler); + return this.sdk; } - // TODO: We need to stop returning the SDK after all methods are dehydrated - async initialize(server: string) { - this.code = null; - this.sdk = await this.initializeSdk(server); - return this.sdk; + private ensureInitialized(): DDPSDK { + if (!this.current) { + throw new Error('SDK not initialized'); + } + return this.current; } get current() { return this.sdk; } - /** - * TODO: evaluate the need for assigning "null" to this.sdk - * I'm returning "null" because we need to remove both instances of this.sdk here and on rocketchat.js - */ - disconnect() { + disconnect(): null { if (this.sdk) { this.sdk.connection.close(); + this.sdk = undefined; } return null; } - get>( - endpoint: TPath, - params: void extends OperationParams<'GET', MatchPathPattern> - ? void - : Serialized>> = undefined as void extends OperationParams< - 'GET', - MatchPathPattern - > - ? void - : Serialized>> - ): Promise>>> { - return this.current.rest.get(`/v1/${endpoint}`, params); + private setBasicAuth(): void { + const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${this.sdk?.connection.url}`); + if (basicAuth) { + this.setHeaders({ Authorization: `Basic ${basicAuth}` }); + } } - post>( - endpoint: TPath, - params: void extends OperationParams<'POST', MatchPathPattern> - ? void - : Serialized>> = undefined as void extends OperationParams< - 'POST', - MatchPathPattern - > - ? void - : Serialized>> - ): Promise>> { - return new Promise(async (resolve, reject) => { - const isMethodCall = endpoint?.startsWith('method.call/'); - try { - const result = await this.current.rest.post(`/v1/${endpoint}`, params); - - /** - * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, - * responses have a different object structure - */ - if (isMethodCall) { - const response = JSON.parse(result.message); - if (response?.error) { - throw response.error; - } - return resolve(response.result); - } - return resolve(result); - } catch (e: any) { - const errorType = isMethodCall ? e?.error : e?.data?.errorType; - const totpInvalid = 'totp-invalid'; - const totpRequired = 'totp-required'; - if ([totpInvalid, totpRequired].includes(errorType)) { - const { details } = isMethodCall ? e : e?.data; - try { - await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); - return resolve(this.post(endpoint, params)); - } catch { - // twoFactor was canceled - return resolve({} as any); - } - } else { - reject(e); - } + private setHeaders(headers: Record): void { + this.headers = { ...this.headers, ...headers }; + } + + getHeaders(): Record { + return this.headers; + } + + get: DDPSDK['rest']['get'] = (...args: Parameters) => { + const [endpoint, params, options] = args; + const sdk = this.ensureInitialized(); + return sdk.rest.get(endpoint, params, { + ...options, + headers: { + ...this.headers, + ...options?.headers + } + }); + }; + + post: DDPSDK['rest']['post'] = (...args: Parameters) => { + const [endpoint, params, options] = args; + const sdk = this.ensureInitialized(); + return sdk.rest.post(endpoint, params, { + ...options, + headers: { + ...this.headers, + ...options?.headers + } + }); + }; + + delete: DDPSDK['rest']['delete'] = (...args: Parameters) => { + const [endpoint, params, options] = args; + const sdk = this.ensureInitialized(); + return sdk.rest.delete(endpoint, params, { + ...options, + headers: { + ...this.headers, + ...options?.headers } }); + }; + + async twoFactorHandler({ + method, + // emailOrUsername, TODO: what is this for? + invalidAttempt + }: { + method: 'totp' | 'email' | 'password'; + invalidAttempt?: boolean; + }): Promise { + const result = await twoFactor({ method, invalid: !!invalidAttempt }); + return result.twoFactorCode; + } + + async login(credentials: any): Promise { + try { + const loginResult = await this.post('/v1/login', credentials); + // TODO: get/set headers from SDK instead? + this.setHeaders({ 'X-Auth-Token': loginResult.data.authToken, 'X-User-Id': loginResult.data.userId }); + await this.current?.account.loginWithToken(loginResult.data.authToken); + return loginResult.data; + } catch (e) { + return Promise.reject(e); + } } methodCall(...args: any[]): Promise { return new Promise(async (resolve, reject) => { try { - const result = await this.current.methodCall(...args, this.code || ''); + // @ts-ignore + const result = await this.current?.client.callAsyncWithOptions(...args, this.code || ''); return resolve(result); } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { @@ -130,15 +143,19 @@ class Sdk { }); } - methodCallWrapper(method: string, ...params: any[]): Promise { + async methodCallWrapper(method: string, ...params: any[]): Promise { const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings; const { user } = reduxStore.getState().login; if (API_Use_REST_For_DDP_Calls) { const url = isEmpty(user) ? 'method.callAnon' : 'method.call'; - // @ts-ignore - return this.post(`${url}/${method}`, { + const result = (await this.post(`/v1/${url}/${method}`, { message: EJSON.stringify({ msg: 'method', id: random(10), method, params }) - }); + })) as any; + const response = JSON.parse(result.message) as any; + if (response?.error) { + throw response.error; + } + return response.result; } const parsedParams = params.map(param => { if (param instanceof Date) { @@ -146,47 +163,55 @@ class Sdk { } return param; }); + // @ts-ignore return this.methodCall(method, ...parsedParams); } - subscribe(...args: any[]) { - return this.current.subscribe(...args); + subscribe(...args: Parameters) { + return this.current?.client.subscribe(...args); } - subscribeRaw(...args: any[]) { - return this.current.subscribeRaw(...args); + onCollection(...args: Parameters) { + return this.current?.client.onCollection(...args); } - subscribeRoom(...args: any[]) { - const { server } = reduxStore.getState(); - const { version: serverVersion } = server; - const topic = 'stream-notify-room'; - let eventUserTyping; - if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.0.0')) { - eventUserTyping = this.subscribe(topic, `${args[0]}/user-activity`, ...args); - } else { - eventUserTyping = this.subscribe(topic, `${args[0]}/typing`, ...args); + stream(...args: Parameters) { + return this.current?.stream(...args); + } + + _stream(name: string, data: unknown, cb: (...data: any) => void) { + const [key, args] = Array.isArray(data) ? data : [data]; + + if (!this.current) { + return { stop: () => {} }; } - // Taken from https://github.com/RocketChat/Rocket.Chat.js.SDK/blob/454b4ba784095057b8de862eb99340311b672e15/lib/drivers/ddp.ts#L555 - return Promise.all([ - this.subscribe('stream-room-messages', args[0], ...args), - eventUserTyping, - this.subscribe(topic, `${args[0]}/deleteMessage`, ...args), - this.subscribe(topic, `${args[0]}/deleteMessageBulk`, ...args), - this.subscribe(topic, `${args[0]}/messagesRead`, ...args) - ]); - } + const subscription = this.current.client.subscribe(`stream-${name}`, key, { useCollection: false, args: [args] }); - unsubscribe(subscription: any[]) { - return this.current.unsubscribe(subscription); - } + const stop = subscription.stop.bind(subscription); + const cancel = [ + () => stop(), + this.current?.client.onCollection(`stream-${name}`, (data: any) => { + if (data.collection !== `stream-${name}`) { + return; + } + if (data.msg === 'added') { + return; + } + if (data.fields.eventName === key) { + cb(data); + } + }) + ]; - onStreamData(...args: any[]) { - return this.current.onStreamData(...args); + return Object.assign(subscription, { + stop: () => { + cancel.forEach(fn => fn()); + } + }); } } const sdk = new Sdk(); -export default sdk; +export default sdk; \ No newline at end of file diff --git a/app/lib/services/twoFactor.ts b/app/lib/services/twoFactor.ts index f8b47609b46..1da5cc3f325 100644 --- a/app/lib/services/twoFactor.ts +++ b/app/lib/services/twoFactor.ts @@ -1,5 +1,3 @@ -import { settings } from '@rocket.chat/sdk'; - import { TWO_FACTOR } from '../../containers/TwoFactor'; import EventEmitter from '../methods/helpers/events'; import { type ICredentials } from '../../definitions'; @@ -18,11 +16,6 @@ export const twoFactor = ({ method, invalid, params }: ITwoFactor): Promise<{ tw params, cancel: () => reject(), submit: (code: string) => { - settings.customHeaders = { - ...settings.customHeaders, - 'x-2fa-code': code, - 'x-2fa-method': method - }; resolve({ twoFactorCode: code, twoFactorMethod: method }); } }); From 41760c614987ff4d1dc11161daec42d090de4f7b Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:52:40 +0530 Subject: [PATCH 003/203] Some rest api changes --- app/lib/services/restApi.ts | 236 +++++++++++++++++------------------- 1 file changed, 114 insertions(+), 122 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 9d1a63ab12c..e98cfde7827 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -54,12 +54,12 @@ export const createChannel = ({ ...(teamId && { teamId }) } }; - return sdk.post(type ? 'groups.create' : 'channels.create', params); + return sdk.post(type ? '/v1/groups.create' : '/v1/channels.create', params); }; export const e2eSetUserPublicAndPrivateKeys = (public_key: string, private_key: string, force: boolean = false) => // RC 2.2.0 - sdk.post('e2e.setUserPublicAndPrivateKeys', { public_key, private_key, ...(force && { force: true }) }); + sdk.post('/v1/e2e.setUserPublicAndPrivateKeys', { public_key, private_key, ...(force && { force: true }) }); export const e2eRequestSubscriptionKeys = (): Promise => // RC 0.72.0 @@ -67,44 +67,42 @@ export const e2eRequestSubscriptionKeys = (): Promise => export const e2eGetUsersOfRoomWithoutKey = (rid: string) => // RC 0.70.0 - sdk.get('e2e.getUsersOfRoomWithoutKey', { rid }); + sdk.get('/v1/e2e.getUsersOfRoomWithoutKey', { rid }); export const e2eSetRoomKeyID = (rid: string, keyID: string) => // RC 0.70.0 - sdk.post('e2e.setRoomKeyID', { rid, keyID }); + sdk.post('/v1/e2e.setRoomKeyID', { rid, keyID }); export const e2eUpdateGroupKey = (uid: string, rid: string, key: string): any => // RC 0.70.0 - sdk.post('e2e.updateGroupKey', { uid, rid, key }); + sdk.post('/v1/e2e.updateGroupKey', { uid, rid, key }); export const e2eRequestRoomKey = (rid: string, e2eKeyId: string): Promise<{ message: { msg?: string }; success: boolean }> => // RC 0.70.0 sdk.methodCall('stream-notify-room-users', `${rid}/e2ekeyRequest`, rid, e2eKeyId); -export const e2eAcceptSuggestedGroupKey = (rid: string): Promise<{ success: boolean }> => - // RC 5.5 - sdk.post('e2e.acceptSuggestedGroupKey', { rid }); +// RC 5.5 +export const e2eAcceptSuggestedGroupKey = (rid: string)=> sdk.post('/v1/e2e.acceptSuggestedGroupKey', { rid }); -export const e2eRejectSuggestedGroupKey = (rid: string): Promise<{ success: boolean }> => - // RC 5.5 - sdk.post('e2e.rejectSuggestedGroupKey', { rid }); +// RC 5.5 +export const e2eRejectSuggestedGroupKey = (rid: string) => sdk.post('/v1/e2e.rejectSuggestedGroupKey', { rid }); -export const fetchUsersWaitingForGroupKey = (roomIds: string[]) => sdk.get('e2e.fetchUsersWaitingForGroupKey', { roomIds }); +export const fetchUsersWaitingForGroupKey = (roomIds: string[]) => sdk.get('/v1/e2e.fetchUsersWaitingForGroupKey', { roomIds }); export const provideUsersSuggestedGroupKeys = (usersSuggestedGroupKeys: any) => - sdk.post('e2e.provideUsersSuggestedGroupKeys', { usersSuggestedGroupKeys }); + sdk.post('/v1/e2e.provideUsersSuggestedGroupKeys', { usersSuggestedGroupKeys }); export const updateJitsiTimeout = (roomId: string) => // RC 0.74.0 - sdk.post('video-conference/jitsi.update-timeout', { roomId }); + sdk.post('/v1/video-conference/jitsi.update-timeout', { roomId }); export const register = (credentials: { name: string; email: string; pass: string; username: string }) => // RC 0.50.0 - sdk.post('users.register', credentials); + sdk.post('/v1/users.register', credentials); export const forgotPassword = (email: string) => // RC 0.64.0 - sdk.post('users.forgotPassword', { email }); + sdk.post('/v1/users.forgotPassword', { email }); export const sendConfirmationEmail = (email: string): Promise<{ message: string; success: boolean }> => sdk.methodCallWrapper('sendConfirmationEmail', email); @@ -122,7 +120,7 @@ export const spotlight = ( export const createDirectMessage = (username: string) => // RC 0.59.0 - sdk.post('im.create', { username }); + sdk.post('/v1/im.create', { username }); export const createDiscussion = ({ prid, @@ -140,7 +138,7 @@ export const createDiscussion = ({ encrypted?: boolean; }) => // RC 1.0.0 - sdk.post('rooms.createDiscussion', { + sdk.post('/v1/rooms.createDiscussion', { prid, pmid, t_name, @@ -167,7 +165,7 @@ export const getDiscussions = ({ ...(text && { text }) }; // RC 2.4.0 - return sdk.get('chat.getDiscussions', params); + return sdk.get('/v1/chat.getDiscussions', params); }; export const createTeam = ({ @@ -198,15 +196,15 @@ export const createTeam = ({ } }; // RC 3.13.0 - return sdk.post('teams.create', params); + return sdk.post('/v1/teams.create', params); }; export const addRoomsToTeam = ({ teamId, rooms }: { teamId: string; rooms: string[] }) => // RC 3.13.0 - sdk.post('teams.addRooms', { teamId, rooms }); + sdk.post('/v1/teams.addRooms', { teamId, rooms }); export const removeTeamRoom = ({ roomId, teamId }: { roomId: string; teamId: string }) => // RC 3.13.0 - sdk.post('teams.removeRoom', { roomId, teamId }); + sdk.post('/v1/teams.removeRoom', { roomId, teamId }); export const leaveTeam = ({ teamId, rooms }: { teamId: string; rooms: string[] }): any => // RC 3.13.0 @@ -220,7 +218,7 @@ export const leaveTeam = ({ teamId, rooms }: { teamId: string; rooms: string[] } export const removeTeamMember = ({ teamId, userId, rooms }: { teamId: string; userId: string; rooms: string[] }) => // RC 3.13.0 - sdk.post('teams.removeMember', { + sdk.post('/v1/teams.removeMember', { teamId, userId, // RC 4.2.0 @@ -229,7 +227,7 @@ export const removeTeamMember = ({ teamId, userId, rooms }: { teamId: string; us export const updateTeamRoom = ({ roomId, isDefault }: { roomId: string; isDefault: boolean }) => // RC 3.13.0 - sdk.post('teams.updateRoom', { roomId, isDefault }); + sdk.post('/v1/teams.updateRoom', { roomId, isDefault }); export const deleteTeam = ({ teamId, roomsToRemove }: { teamId: string; roomsToRemove: string[] }): any => // RC 3.13.0 @@ -239,19 +237,14 @@ export const deleteTeam = ({ teamId, roomsToRemove }: { teamId: string; roomsToR export const teamListRoomsOfUser = ({ teamId, userId }: { teamId: string; userId: string }) => // RC 3.13.0 - sdk.get('teams.listRoomsOfUser', { teamId, userId }); + sdk.get('/v1/teams.listRoomsOfUser', { teamId, userId }); export const convertChannelToTeam = ({ rid, name, type }: { rid: string; name: string; type: 'c' | 'p' }) => { const serverVersion = reduxStore.getState().server.version; let params; if (type === 'c') { // https://github.com/RocketChat/Rocket.Chat/pull/25279 - params = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.8.0') - ? { channelId: rid } - : { - channelId: rid, - channelName: name - }; + params = { channelId: rid }; } else { params = { roomId: rid, @@ -259,7 +252,7 @@ export const convertChannelToTeam = ({ rid, name, type }: { rid: string; name: s }; } - return sdk.post(type === 'c' ? 'channels.convertToTeam' : 'groups.convertToTeam', params); + return sdk.post(type === 'c' ? '/v1/channels.convertToTeam' : '/v1/groups.convertToTeam', params); }; export const convertTeamToChannel = ({ teamId, selected }: { teamId: string; selected: string[] }) => { @@ -267,7 +260,7 @@ export const convertTeamToChannel = ({ teamId, selected }: { teamId: string; sel teamId, ...(selected.length && { roomsToRemove: selected }) }; - return sdk.post('teams.convertToChannel', params); + return sdk.post('/v1/teams.convertToChannel', params); }; export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p') => { @@ -275,46 +268,46 @@ export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p if (type === 'p') { return sdk.methodCallWrapper('joinRoom', roomId) as Promise; } - return sdk.post('channels.join', { roomId, joinCode }); + return sdk.post('/v1/channels.join', { roomId, joinCode }); }; export const deleteMessage = (messageId: string, rid: string) => // RC 0.48.0 - sdk.post('chat.delete', { msgId: messageId, roomId: rid }); + sdk.post('/v1/chat.delete', { msgId: messageId, roomId: rid }); export const markAsUnread = ({ messageId }: { messageId: string }) => // RC 0.65.0 - sdk.post('subscriptions.unread', { firstUnreadMessage: { _id: messageId } }); + sdk.post('/v1/subscriptions.unread', { firstUnreadMessage: { _id: messageId } }); export const toggleStarMessage = (messageId: string, starred?: boolean) => { if (starred) { // RC 0.59.0 - return sdk.post('chat.unStarMessage', { messageId }); + return sdk.post('/v1/chat.unStarMessage', { messageId }); } // RC 0.59.0 - return sdk.post('chat.starMessage', { messageId }); + return sdk.post('/v1/chat.starMessage', { messageId }); }; export const togglePinMessage = (messageId: string, pinned?: boolean) => { if (pinned) { // RC 0.59.0 - return sdk.post('chat.unPinMessage', { messageId }); + return sdk.post('/v1/chat.unPinMessage', { messageId }); } // RC 0.59.0 - return sdk.post('chat.pinMessage', { messageId }); + return sdk.post('/v1/chat.pinMessage', { messageId }); }; export const reportUser = (userId: string, description: string) => // RC 6.4.0 - sdk.post('moderation.reportUser', { userId, description }); + sdk.post('/v1/moderation.reportUser', { userId, description }); export const reportMessage = (messageId: string) => // RC 0.64.0 - sdk.post('chat.reportMessage', { messageId, description: 'Message reported by user' }); + sdk.post('/v1/chat.reportMessage', { messageId, description: 'Message reported by user' }); export const setUserPreferences = (userId: string, data: Partial) => // RC 0.62.0 - sdk.post('users.setPreferences', { userId, data }); + sdk.post('/v1/users.setPreferences', { userId, data }); export const setUserStatus = (status: string, message: string) => // RC 1.2.0 @@ -322,7 +315,7 @@ export const setUserStatus = (status: string, message: string) => export const setReaction = (emoji: string, messageId: string) => // RC 0.62.2 - sdk.post('chat.react', { emoji, messageId }); + sdk.post('/v1/chat.react', { emoji, messageId }); /** * Toggles the read status of a room. @@ -332,11 +325,7 @@ export const setReaction = (emoji: string, messageId: string) => * @param includeThreads - Optional flag to include threads when marking as read. * @returns A promise from the sdk post method. */ -export const toggleReadStatus = ( - isRead: boolean, - roomId: string, - includeThreads?: boolean -): Promise> => { +export const toggleReadStatus = (isRead: boolean, roomId: string, includeThreads?: boolean) => { let endpoint: keyof SubscriptionsEndpoints; let payload: OperationParams<'POST', keyof SubscriptionsEndpoints> = { roomId }; @@ -350,7 +339,7 @@ export const toggleReadStatus = ( } } - return sdk.post(endpoint, payload); + return sdk.post(`/v1/${endpoint}`, payload); }; export const getRoomCounters = ( @@ -358,26 +347,26 @@ export const getRoomCounters = ( t: SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL ) => // RC 0.65.0 - sdk.get(`${roomTypeToApiType(t)}.counters`, { roomId }); + sdk.get(`/v1/${roomTypeToApiType(t)}.counters`, { roomId }); export const getChannelInfo = (roomId: string) => // RC 0.48.0 - sdk.get('channels.info', { roomId }); + sdk.get('/v1/channels.info', { roomId }); export const getUserPreferences = (userId: string) => // RC 0.62.0 - sdk.get('users.getPreferences', { userId }); + sdk.get('/v1/users.getPreferences'); export const getRoomInfo = (roomId: string) => // RC 0.72.0 - sdk.get('rooms.info', { roomId }); + sdk.get('/v1/rooms.info', { roomId }); export const getRoomByTypeAndName = (roomType: RoomType, roomName: string): Promise => sdk.methodCallWrapper('getRoomByTypeAndName', roomType, roomName); export const getVisitorInfo = (visitorId: string) => // RC 2.3.0 - sdk.get('livechat/visitors.info', { visitorId }); + sdk.get('/v1/livechat/visitors.info', { visitorId }); export const setUserPresenceAway = () => sdk.methodCall('UserPresence:away'); @@ -407,7 +396,7 @@ export const getTeamListRoom = ({ params.filter = filter; } // RC 3.13.0 - return sdk.get('teams.listRooms', params); + return sdk.get('/v1/teams.listRooms', params); }; export const closeLivechat = (rid: string, comment?: string, tags?: string[]) => { @@ -427,22 +416,23 @@ export const editLivechat = (userData: TParams, roomData: TParams): Promise<{ er return sdk.methodCallWrapper('livechat:saveInfo', userData, roomData); } // RC 5.3.0 - return sdk.post('livechat/room.saveInfo', { guestData: userData, roomData }) as any; + return sdk.post('/v1/livechat/room.saveInfo', { guestData: userData, roomData }) as any; }; export const returnLivechat = (rid: string): Promise => // RC 0.72.0 sdk.methodCallWrapper('livechat:returnAsInquiry', rid); -export const onHoldLivechat = (roomId: string) => sdk.post('livechat/room.onHold', { roomId }); +export const onHoldLivechat = (roomId: string) => sdk.post('/v1/livechat/room.onHold', { roomId }); export const forwardLivechat = (transferData: any) => // RC 0.36.0 sdk.methodCallWrapper('livechat:transfer', transferData); +//fix me export const getDepartmentInfo = (departmentId: string) => // RC 2.2.0 - sdk.get(`livechat/department/${departmentId}?includeAgents=false`); + sdk.get(`/v1/livechat/department/${departmentId}?includeAgents=false`); export const getDepartments = (args?: { count: number; offset: number; text: string }) => { let params; @@ -454,12 +444,12 @@ export const getDepartments = (args?: { count: number; offset: number; text: str }; } // RC 2.2.0 - return sdk.get('livechat/department', params); + return sdk.get('/v1/livechat/department', params); }; export const usersAutoComplete = (selector: any) => // RC 2.4.0 - sdk.get('users.autocomplete', { selector }); + sdk.get('/v1/users.autocomplete', { selector }); export const getRoutingConfig = (): Promise<{ previewRoom: boolean; @@ -479,11 +469,11 @@ export const getTagsList = (): Promise => export const getAgentDepartments = (uid: string) => // RC 2.4.0 - sdk.get(`livechat/agents/${uid}/departments?enabledDepartmentsOnly=true`); + sdk.get(`/v1/livechat/agents/${uid}/departments?enabledDepartmentsOnly=true`); export const getCustomFields = () => // RC 2.2.0 - sdk.get('livechat/custom-fields'); + sdk.get('/v1/livechat/custom-fields'); export const getListCannedResponse = ({ scope = '', departmentId = '', offset = 0, count = 25, text = '' }) => { const params = { @@ -495,7 +485,7 @@ export const getListCannedResponse = ({ scope = '', departmentId = '', offset = }; // RC 3.17.0 - return sdk.get('canned-responses', params); + return sdk.get('/v1/canned-responses', params); }; export const toggleBlockUser = (rid: string, blocked: string, block: boolean): Promise => { @@ -509,11 +499,11 @@ export const toggleBlockUser = (rid: string, blocked: string, block: boolean): P export const leaveRoom = (roomId: string, t: RoomTypes) => // RC 0.48.0 - sdk.post(`${roomTypeToApiType(t)}.leave`, { roomId }); + sdk.post(`/v1/${roomTypeToApiType(t)}.leave`, { roomId }); export const deleteRoom = (roomId: string, t: RoomTypes) => // RC 0.49.0 - sdk.post(`${roomTypeToApiType(t)}.delete`, { roomId }); + sdk.post(`/v1/${roomTypeToApiType(t)}.delete`, { roomId }); export const toggleMuteUserInRoom = ( rid: string, @@ -542,10 +532,10 @@ export const toggleRoomOwner = ({ const type = t as SubscriptionType.CHANNEL; if (isOwner) { // RC 0.49.4 - return sdk.post(`${roomTypeToApiType(type)}.addOwner`, { roomId, userId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.addOwner`, { roomId, userId }); } // RC 0.49.4 - return sdk.post(`${roomTypeToApiType(type)}.removeOwner`, { roomId, userId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.removeOwner`, { roomId, userId }); }; export const toggleRoomLeader = ({ @@ -562,10 +552,10 @@ export const toggleRoomLeader = ({ const type = t as SubscriptionType.CHANNEL; if (isLeader) { // RC 0.58.0 - return sdk.post(`${roomTypeToApiType(type)}.addLeader`, { roomId, userId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.addLeader`, { roomId, userId }); } // RC 0.58.0 - return sdk.post(`${roomTypeToApiType(type)}.removeLeader`, { roomId, userId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.removeLeader`, { roomId, userId }); }; export const toggleRoomModerator = ({ @@ -582,33 +572,33 @@ export const toggleRoomModerator = ({ const type = t as SubscriptionType.CHANNEL; if (isModerator) { // RC 0.49.4 - return sdk.post(`${roomTypeToApiType(type)}.addModerator`, { roomId, userId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.addModerator`, { roomId, userId }); } // RC 0.49.4 - return sdk.post(`${roomTypeToApiType(type)}.removeModerator`, { roomId, userId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.removeModerator`, { roomId, userId }); }; export const removeUserFromRoom = ({ roomId, t, userId }: { roomId: string; t: RoomTypes; userId: string }) => // RC 0.48.0 - sdk.post(`${roomTypeToApiType(t)}.kick`, { roomId, userId }); + sdk.post(`/v1/${roomTypeToApiType(t)}.kick`, { roomId, userId }); export const ignoreUser = ({ rid, userId, ignore }: { rid: string; userId: string; ignore: boolean }) => // RC 0.64.0 - sdk.get('chat.ignoreUser', { rid, userId, ignore }); + sdk.get('/v1/chat.ignoreUser', { rid, userId, ignore }); export const toggleArchiveRoom = (roomId: string, t: SubscriptionType, archive: boolean) => { const type = t as SubscriptionType.CHANNEL | SubscriptionType.GROUP; if (archive) { // RC 0.48.0 - return sdk.post(`${roomTypeToApiType(type)}.archive`, { roomId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.archive`, { roomId }); } // RC 0.48.0 - return sdk.post(`${roomTypeToApiType(type)}.unarchive`, { roomId }); + return sdk.post(`/v1/${roomTypeToApiType(type)}.unarchive`, { roomId }); }; export const hideRoom = (roomId: string, t: RoomTypes) => // RC 0.48.0 - sdk.post(`${roomTypeToApiType(t)}.close`, { roomId }); + sdk.post(`/v1/${roomTypeToApiType(t)}.close`, { roomId }); export const saveRoomSettings = ( rid: string, @@ -644,26 +634,26 @@ export const saveUserProfile = ( customFields?: { [key: string | number]: string } ) => // RC 0.62.2 - sdk.post('users.updateOwnBasicInfo', { data, customFields }); + sdk.post('/v1/users.updateOwnBasicInfo', { data, customFields }); export const saveUserPreferences = (data: Partial) => // RC 0.62.0 - sdk.post('users.setPreferences', { data }); + sdk.post('/v1/users.setPreferences', { data }); export const saveNotificationSettings = (roomId: string, notifications: IRoomNotifications) => // RC 0.63.0 - sdk.post('rooms.saveNotification', { roomId, notifications }); + sdk.post('/v1/rooms.saveNotification', { roomId, notifications }); export const getSingleMessage = (msgId: string) => // RC 0.47.0 - sdk.get('chat.getMessage', { msgId }); + sdk.get('/v1/chat.getMessage', { msgId }); export const getRoomRoles = ( roomId: string, type: SubscriptionType.CHANNEL | SubscriptionType.GROUP | SubscriptionType.OMNICHANNEL ) => // RC 0.65.0 - sdk.get(`${roomTypeToApiType(type)}.roles`, { roomId }); + sdk.get(`/v1/${roomTypeToApiType(type)}.roles`, { roomId }); export const getAvatarSuggestion = (): Promise<{ [service: string]: IAvatarSuggestion }> => // RC 0.51.0 @@ -671,7 +661,7 @@ export const getAvatarSuggestion = (): Promise<{ [service: string]: IAvatarSugge export const resetAvatar = (userId: string) => // RC 0.55.0 - sdk.post('users.resetAvatar', { userId }); + sdk.post('/v1/users.resetAvatar', { userId }); export const setAvatarFromService = ({ data, @@ -687,12 +677,12 @@ export const setAvatarFromService = ({ export const getUsernameSuggestion = () => // RC 0.65.0 - sdk.get('users.getUsernameSuggestion'); + sdk.get('/v1/users.getUsernameSuggestion'); export const getFiles = (roomId: string, type: SubscriptionType, offset: number) => { const t = type as SubscriptionType.DIRECT | SubscriptionType.CHANNEL | SubscriptionType.GROUP; // RC 0.59.0 - return sdk.get(`${roomTypeToApiType(t)}.files`, { + return sdk.get(`/v1/${roomTypeToApiType(t)}.files`, { roomId, offset, sort: { uploadedAt: -1 } @@ -736,7 +726,7 @@ export const getMessages = ({ params.pinned = pinned; } - return sdk.get(`${roomTypeToApiType(t)}.messages`, params); + return sdk.get(`/v1/${roomTypeToApiType(t)}.messages`, params); } const params: any = { roomId, @@ -757,11 +747,11 @@ export const getMessages = ({ } // RC 0.59.0 - return sdk.get(`${roomTypeToApiType(t)}.messages`, params); + return sdk.get(`/v1/${roomTypeToApiType(t)}.messages`, params); }; export const getPinnedMessages = ({ roomId, offset, count }: { roomId: string; offset: number; count: number }) => - sdk.get('chat.getPinnedMessages', { + sdk.get('/v1/chat.getPinnedMessages', { roomId, offset, count @@ -769,13 +759,13 @@ export const getPinnedMessages = ({ roomId, offset, count }: { roomId: string; o export const getReadReceipts = (messageId: string) => // RC 0.63.0 - sdk.get('chat.getMessageReadReceipts', { + sdk.get('/v1/chat.getMessageReadReceipts', { messageId }); export const searchMessages = (roomId: string, searchText: string, count: number, offset: number) => // RC 0.60.0 - sdk.get('chat.search', { + sdk.get('/v1/chat.search', { roomId, searchText, count, @@ -785,9 +775,9 @@ export const searchMessages = (roomId: string, searchText: string, count: number export const toggleFollowMessage = (mid: string, follow: boolean) => { // RC 1.0 if (follow) { - return sdk.post('chat.followMessage', { mid }); + return sdk.post('/v1/chat.followMessage', { mid }); } - return sdk.post('chat.unfollowMessage', { mid }); + return sdk.post('/v1/chat.unfollowMessage', { mid }); }; export const getThreadsList = ({ rid, count, offset, text }: { rid: string; count: number; offset: number; text?: string }) => { @@ -801,19 +791,19 @@ export const getThreadsList = ({ rid, count, offset, text }: { rid: string; coun } // RC 1.0 - return sdk.get('chat.getThreadsList', params); + return sdk.get('/v1/chat.getThreadsList', params); }; export const getSyncThreadsList = ({ rid, updatedSince }: { rid: string; updatedSince: string }) => // RC 1.0 - sdk.get('chat.syncThreadsList', { + sdk.get('/v1/chat.syncThreadsList', { rid, updatedSince }); export const runSlashCommand = (command: string, roomId: string, params: string, triggerId?: string, tmid?: string) => // RC 0.60.2 - sdk.post('commands.run', { + sdk.post('/v1/commands.run', { command, roomId, params, @@ -823,7 +813,7 @@ export const runSlashCommand = (command: string, roomId: string, params: string, export const getCommandPreview = (command: string, roomId: string, params: string) => // RC 0.65.0 - sdk.get('commands.preview', { + sdk.get('/v1/commands.preview', { command, roomId, params @@ -838,7 +828,7 @@ export const executeCommandPreview = ( tmid?: string ) => // RC 0.65.0 - sdk.post('commands.preview', { + sdk.post('/v1/commands.preview', { command, params, roomId, @@ -875,7 +865,7 @@ export const getDirectory = ({ } else { params.query = { text, type, workspace }; } - return sdk.get('directory', params); + return sdk.get('/v1/directory', params); }; export const saveAutoTranslate = ({ @@ -894,7 +884,7 @@ export const getSupportedLanguagesAutoTranslate = (): Promise<{ language: string sdk.methodCallWrapper('autoTranslate.getSupportedLanguages', 'en'); export const translateMessage = (messageId: string, targetLanguage: string) => - sdk.post('autotranslate.translateMessage', { messageId, targetLanguage }); + sdk.post('/v1/autotranslate.translateMessage', { messageId, targetLanguage }); export const findOrCreateInvite = ({ rid, days, maxUses }: { rid: string; days: number; maxUses: number }): any => // RC 2.4.0 @@ -928,7 +918,7 @@ export const createGroupChat = () => { const usernames = users.map(u => u.name).join(','); // RC 3.1.0 - return sdk.post('im.create', { usernames }); + return sdk.post('/v1/im.create', { usernames }); }; export const addUsersToRoom = (rid: string): Promise => { @@ -950,23 +940,25 @@ export const emitTyping = (room: IRoom, typing = true) => { return sdk.methodCall('stream-notify-room', `${room}/typing`, name, typing); }; -export function e2eResetOwnKey(): Promise<{ success?: boolean }> { +export function e2eResetOwnKey(userId: string): Promise<{ success?: boolean }> { // {} when TOTP is enabled unsubscribeRooms(); // RC 3.6.0 - return sdk.post('users.resetE2EKey'); + sdk.post('/v1/users.resetE2EKey', { userId }); + + return Promise.resolve({ success: true }); } export function e2eResetRoomKey(rid: string, e2eKey: string, e2eKeyId: string): Promise { // RC ? - return sdk.post('e2e.resetRoomKey', { rid, e2eKey, e2eKeyId }); + return sdk.post('/v1/e2e.resetRoomKey', { rid, e2eKey, e2eKeyId }); } export const editMessage = async (message: Pick) => { const { rid, msg } = await Encryption.encryptMessage(message as IMessage); // RC 0.49.0 - return sdk.post('chat.update', { roomId: rid, msgId: message.id, text: msg }); + return sdk.post('/v1/chat.update', { roomId: rid, msgId: message.id, text: msg }); }; export const registerPushToken = () => @@ -993,19 +985,19 @@ export const removePushToken = (): Promise => { const token = getDeviceToken(); if (token) { // RC 0.60.0 - return sdk.current.del('push.token', { token }); + sdk.delete('/v1/push.token', { token }); } return Promise.resolve(); }; // RC 6.6.0 -export const pushTest = () => sdk.post('push.test'); +export const pushTest = () => sdk.post('/v1/push.test'); // RC 6.5.0 -export const pushInfo = () => sdk.get('push.info'); +export const pushInfo = () => sdk.get('/v1/push.info'); // RC 3.1.0 -export const sendEmailCode = (emailOrUsername: string) => sdk.post('users.2fa.sendEmailCode', { emailOrUsername }); +export const sendEmailCode = (emailOrUsername: string) => sdk.post('/v1/users.2fa.sendEmailCode', { emailOrUsername }); export const getRoomMembers = async ({ rid, @@ -1035,8 +1027,8 @@ export const getRoomMembers = async ({ ...(filter && { filter }) }; // RC 3.16.0 - const result = await sdk.get(`${roomTypeToApiType(t)}.members`, params); - if (result.success) { + const result = await sdk.get(`/v1/${roomTypeToApiType(t)}.members`, params); + if (result) { return result?.members; } } @@ -1047,11 +1039,11 @@ export const getRoomMembers = async ({ export const e2eFetchMyKeys = async () => { // RC 0.70.0 - const result = await sdk.get('e2e.fetchMyKeys'); + const result = await sdk.get('/v1/e2e.fetchMyKeys'); // snake_case -> camelCase - if (result.success) { + if (result) { return { - success: result.success, + success: true, publicKey: result.public_key, privateKey: result.private_key }; @@ -1061,30 +1053,30 @@ export const e2eFetchMyKeys = async () => { export const logoutOtherLocations = () => { const { id } = reduxStore.getState().login.user; - return sdk.post('users.removeOtherTokens', { userId: id as string }); + return sdk.post('/v1/users.removeOtherTokens', { userId: id as string }); }; export function getUserInfo(userId: string) { // RC 0.48.0 - return sdk.get('users.info', { userId }); + return sdk.get('/v1/users.info', { userId }); } export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite }); export const videoConferenceJoin = (callId: string, cam?: boolean, mic?: boolean) => - sdk.post('video-conference.join', { callId, state: { cam: !!cam, mic: mic === undefined ? true : mic } }); + sdk.post('/v1/video-conference.join', { callId, state: { cam: !!cam, mic: mic === undefined ? true : mic } }); -export const videoConferenceGetCapabilities = () => sdk.get('video-conference.capabilities'); +export const videoConferenceGetCapabilities = () => sdk.get('/v1/video-conference.capabilities'); -export const videoConferenceStart = (roomId: string) => sdk.post('video-conference.start', { roomId, allowRinging: true }); +export const videoConferenceStart = (roomId: string) => sdk.post('/v1/video-conference.start', { roomId, allowRinging: true }); -export const videoConferenceCancel = (callId: string) => sdk.post('video-conference.cancel', { callId }); +export const videoConferenceCancel = (callId: string) => sdk.post('/v1/video-conference.cancel', { callId }); export const deleteOwnAccount = (password: string, confirmRelinquish = false): any => // RC 0.67.0 - sdk.post('users.deleteOwnAccount', { password, confirmRelinquish }); + sdk.post('/v1/users.deleteOwnAccount', { password, confirmRelinquish }); -export const postMessage = (roomId: string, text: string) => sdk.post('chat.postMessage', { roomId, text }); +export const postMessage = (roomId: string, text: string) => sdk.post('/v1/chat.postMessage', { roomId, text }); export const notifyUser = (type: string, params: Record): Promise => sdk.methodCall('stream-notify-user', type, params); @@ -1093,7 +1085,7 @@ export const getUsersRoles = async (): Promise => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.10.0')) { // RC 7.10.0 - const response = await sdk.get('roles.getUsersInPublicRoles'); + const response = await sdk.get('/v1/roles.getUsersInPublicRoles'); if (response.success) { return response.users; } From ed2266146f3e93d2b9f9169422088838d0719f6c Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 15 Nov 2025 01:18:29 +0530 Subject: [PATCH 004/203] some fix --- app/containers/Avatar/useAvatarETag.ts | 4 ++-- app/containers/MessageActions/index.tsx | 4 ++-- .../MessageComposer/components/SendThreadToChannel.tsx | 3 ++- app/containers/MessageComposer/hooks/useAutocomplete.ts | 4 ++-- app/containers/TwoFactor/index.tsx | 4 +--- app/ee/omnichannel/lib/index.ts | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/containers/Avatar/useAvatarETag.ts b/app/containers/Avatar/useAvatarETag.ts index 4b8a2596c91..1795d3c53a6 100644 --- a/app/containers/Avatar/useAvatarETag.ts +++ b/app/containers/Avatar/useAvatarETag.ts @@ -48,8 +48,8 @@ export const useAvatarETag = ({ } if (record) { - const observable = record.observe() as Observable; - subscription = observable.subscribe(r => { + const observable = record.observe() as unknown as Observable; + subscription = observable.subscribe((r) => { setAvatarETag(r.avatarETag); }); } diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx index 90a516ce0c0..c74eb07908e 100644 --- a/app/containers/MessageActions/index.tsx +++ b/app/containers/MessageActions/index.tsx @@ -234,7 +234,7 @@ const MessageActions = React.memo( try { const db = database.active; const result = await markAsUnread({ messageId }); - if (result.success) { + if (result) { const subRecord = await getSubscriptionByRoomId(rid); if (!subRecord) { return; @@ -292,7 +292,7 @@ const MessageActions = React.memo( const handleReplyInDM = async (message: TAnyMessageModel) => { if (message?.u?.username) { const result = await createDirectMessage(message.u.username); - if (result.success) { + if (result) { const { room } = result; const params = { rid: room.rid, diff --git a/app/containers/MessageComposer/components/SendThreadToChannel.tsx b/app/containers/MessageComposer/components/SendThreadToChannel.tsx index e752e89c271..6bbbef8bfed 100644 --- a/app/containers/MessageComposer/components/SendThreadToChannel.tsx +++ b/app/containers/MessageComposer/components/SendThreadToChannel.tsx @@ -1,7 +1,7 @@ import { TouchableWithoutFeedback } from 'react-native-gesture-handler'; import { StyleSheet, Text } from 'react-native'; import React, { useEffect, useRef } from 'react'; -import { type Subscription } from 'rxjs'; +import { Subscription } from 'rxjs/internal/Subscription'; import { Q } from '@nozbe/watermelondb'; import { useRoomContext } from '../../../views/RoomView/context'; @@ -55,6 +55,7 @@ export const SendThreadToChannel = (): React.ReactElement | null => { if (alsoSendThreadToChannelUserPref === 'default') { const db = database.active; const observable = db.get('threads').query(Q.where('id', tmid)).observe(); + // @ts-ignore: fix me subscription.current = observable.subscribe(result => { setAlsoSendThreadToChannel(!result.length); }); diff --git a/app/containers/MessageComposer/hooks/useAutocomplete.ts b/app/containers/MessageComposer/hooks/useAutocomplete.ts index 94ab23f63cc..45afe72c529 100644 --- a/app/containers/MessageComposer/hooks/useAutocomplete.ts +++ b/app/containers/MessageComposer/hooks/useAutocomplete.ts @@ -159,7 +159,7 @@ export const useAutocomplete = ({ return; } const response = await getCommandPreview(text, rid, commandParams); - if (response.success) { + if (response.preview) { const previewItems = (response.preview?.items || []).map(item => ({ id: item.id, preview: item, @@ -176,7 +176,7 @@ export const useAutocomplete = ({ } if (type === '!') { const res = await getListCannedResponse({ text }); - if (res.success) { + if (res.cannedResponses) { if (res.cannedResponses.length === 0) { setItems([ { diff --git a/app/containers/TwoFactor/index.tsx b/app/containers/TwoFactor/index.tsx index 6520d453d60..d451e3789b9 100644 --- a/app/containers/TwoFactor/index.tsx +++ b/app/containers/TwoFactor/index.tsx @@ -95,9 +95,7 @@ const TwoFactor = React.memo(() => { clearErrors(); const response = await sendEmailCode(params?.user); - if (response.success) { - showToast(I18n.t('Two_Factor_Success_message')); - } + showToast(I18n.t('Two_Factor_Success_message')); } } catch (e) { log(e); diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 486749d005d..6a41ff4f5c1 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -12,8 +12,8 @@ export const changeLivechatStatus = () => sdk.methodCallWrapper('livechat:change // @ts-ignore export const getInquiriesQueued = (serverVersion: string) => { const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') - ? 'livechat/inquiries.queuedForUser' - : 'livechat/inquiries.queued'; + ? '/v1/livechat/inquiries.queuedForUser' + : '/v1/livechat/inquiries.queued'; return sdk.get(url); }; From b8457dfd5da574e8ffbacaee7966a21fe4223c44 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 15 Nov 2025 01:20:33 +0530 Subject: [PATCH 005/203] more changes --- app/lib/services/restApi.ts | 2 +- app/views/SetUsernameView.tsx | 2 +- app/views/UserNotificationPreferencesView/index.tsx | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index e98cfde7827..ca2261c954e 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -353,7 +353,7 @@ export const getChannelInfo = (roomId: string) => // RC 0.48.0 sdk.get('/v1/channels.info', { roomId }); -export const getUserPreferences = (userId: string) => +export const getUserPreferences = () => // RC 0.62.0 sdk.get('/v1/users.getPreferences'); diff --git a/app/views/SetUsernameView.tsx b/app/views/SetUsernameView.tsx index 6e36da60f30..877653380c7 100644 --- a/app/views/SetUsernameView.tsx +++ b/app/views/SetUsernameView.tsx @@ -63,7 +63,7 @@ const SetUsernameView = () => { useEffect(() => { const init = async () => { const suggestion = await getUsernameSuggestion(); - if (suggestion.success) { + if (suggestion) { setValue('username', suggestion.result, { shouldValidate: true }); } }; diff --git a/app/views/UserNotificationPreferencesView/index.tsx b/app/views/UserNotificationPreferencesView/index.tsx index a7d2ffb109b..f42f5388d7b 100644 --- a/app/views/UserNotificationPreferencesView/index.tsx +++ b/app/views/UserNotificationPreferencesView/index.tsx @@ -43,10 +43,10 @@ const UserNotificationPreferencesView = () => { useEffect(() => { async function getPreferences() { try { - const result = await getUserPreferences(userId); - if (result.success) { + const result = await getUserPreferences(); + if (result.preferences) { setLoading(false); - setPreferences(result.preferences); + setPreferences(result.preferences as INotificationPreferences); } } catch (error) { setLoading(false); @@ -61,7 +61,7 @@ const UserNotificationPreferencesView = () => { try { setPreferences({ ...previousPreferences, ...param }); const result = await setUserPreferences(userId, param); - if (!result.success) { + if (!result) { setPreferences(previousPreferences); } } catch (error) { From bb68c799b9aafc4078032b645eaa4263d01fec10 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 15 Nov 2025 01:22:54 +0530 Subject: [PATCH 006/203] some more fix --- app/sagas/selectServer.ts | 2 +- app/sagas/troubleshootingNotification.ts | 2 +- app/views/AddExistingChannelView/index.tsx | 2 +- app/views/ForgotPasswordView.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index cd4a8238466..f983abc782f 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -137,7 +137,7 @@ const getServerInfoSaga = function* getServerInfoSaga({ server, raiseError = tru const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }: ISelectServerAction) { try { - if (sdk.current?.client?.host === server) { + if (sdk.current?.connection?.url === server) { yield put(appStart({ root: RootEnum.ROOT_INSIDE })); yield put(selectServerCancel()); return; diff --git a/app/sagas/troubleshootingNotification.ts b/app/sagas/troubleshootingNotification.ts index 0717c083f19..99dd8fa7163 100644 --- a/app/sagas/troubleshootingNotification.ts +++ b/app/sagas/troubleshootingNotification.ts @@ -28,7 +28,7 @@ function* init() { try { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.5.0')) { const pushInfoResult = yield* call(pushInfo); - if (pushInfoResult.success) { + if (pushInfoResult) { pushGatewayEnabled = pushInfoResult.pushGatewayEnabled; defaultPushGateway = pushInfoResult.defaultPushGateway; } diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index f16fb14105d..bf71d11232c 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -137,7 +137,7 @@ const AddExistingChannelView = () => { try { logEvent(events.CT_ADD_ROOM_TO_TEAM); const result = await addRoomsToTeam({ rooms: selected, teamId }); - if (result.success) { + if (result) { sendLoadingEvent({ visible: false }); Navigation.resetTo(); } diff --git a/app/views/ForgotPasswordView.tsx b/app/views/ForgotPasswordView.tsx index 509701a1239..b26a80eaaea 100644 --- a/app/views/ForgotPasswordView.tsx +++ b/app/views/ForgotPasswordView.tsx @@ -52,7 +52,7 @@ const ForgotPasswordView = (): React.ReactElement => { logEvent(events.FP_FORGOT_PASSWORD); setIsFetching(true); const result = await forgotPassword(email); - if (result.success) { + if (result) { navigation.pop(); showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert')); } From 3fa1fd8889b941009891bb363972178668c6fdf0 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 15 Nov 2025 02:29:31 +0530 Subject: [PATCH 007/203] lock file fix --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2db385ab968..0e3e13a2af0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5037,7 +5037,7 @@ resolved "https://registry.yarnpkg.com/@rocket.chat/api-client/-/api-client-0.2.38.tgz#7d50f69db48d89f67ecf09c404f75ff9abb7d3fe" integrity sha512-yC4wEW1LzNa1S1636RxBCysaFmzCyrjhTUVTBoJaZcSJLL9wKx2e05NYFQFPA54I+7zt5GQDRL8zRtjAjQkEOQ== dependencies: - "@rocket.chat/core-typings" "^7.10.1" + "@rocket.chat/core-typings" "6.13.1" "@rocket.chat/rest-typings" "^7.10.1" filter-obj "^3.0.0" query-string "^7.1.3" @@ -5062,7 +5062,7 @@ stack-trace "0.0.10" uuid "~8.3.2" -"@rocket.chat/core-typings@^7.10.1", "@rocket.chat/core-typings@~7.10.1": +"@rocket.chat/core-typings@6.13.1", "@rocket.chat/core-typings@6.13.1": version "6.13.1" resolved "https://registry.yarnpkg.com/@rocket.chat/core-typings/-/core-typings-6.13.1.tgz#50d759233683998f76529f0af4d36a6c6bf61429" integrity sha512-braIc3mvjju01HSc7jK3I3rbBtjVwL9yvUy1rSb4OBMOoSxE0UqQRR01q2djhgxYqCeIrTWOVgQyO9qPxW38Zw== @@ -5079,7 +5079,7 @@ integrity sha512-w/kqTsWhrT/gR4NNWQFlL22He6E0zPauZVvqfbRGL3ulfxsWCopYqPH4k2qU/nGqDeuB2rERvNNtv45cWi/r4Q== dependencies: "@rocket.chat/api-client" "^0.2.38" - "@rocket.chat/core-typings" "~7.10.1" + "@rocket.chat/core-typings" "6.13.1" "@rocket.chat/rest-typings" "^7.10.1" "@rocket.chat/emitter@^0.31.25": @@ -5122,7 +5122,7 @@ resolved "https://registry.yarnpkg.com/@rocket.chat/rest-typings/-/rest-typings-7.10.1.tgz#d5493ce9efc53d1bd4bc2745acae649dcab54edf" integrity sha512-jNttcWuUYIFBkwoyK7aPi8oS/crZU5UEmIxVxzuG6pGpV04vLkRLyG+ZrQlAhhlk6fQWRG8M0rBxSyPATPfGiA== dependencies: - "@rocket.chat/core-typings" "^7.10.1" + "@rocket.chat/core-typings" "6.13.1" "@rocket.chat/message-parser" "^0.31.32" "@rocket.chat/ui-kit" "~0.37.0" ajv "^8.17.1" From 5a4149cab51d4c6e6fa1c3d3d30ac5e4658705e6 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 15 Nov 2025 02:33:23 +0530 Subject: [PATCH 008/203] some changes --- app/views/RegisterView/index.tsx | 2 +- app/views/RoomInfoView/index.tsx | 4 ++-- app/views/RoomMembersView/helpers.ts | 2 +- app/views/SearchMessagesView/index.tsx | 4 ++-- app/views/TeamChannelsView.tsx | 6 +++--- app/views/ThreadMessagesView/index.tsx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/RegisterView/index.tsx b/app/views/RegisterView/index.tsx index 0159ccd2040..1120fe96ef4 100644 --- a/app/views/RegisterView/index.tsx +++ b/app/views/RegisterView/index.tsx @@ -132,7 +132,7 @@ const RegisterView = ({ navigation, route }: IProps) => { try { const response = await register({ name, email, pass: password, username }); - if (response.success) { + if (response) { if (Accounts_EmailVerification) { showErrorAlert(I18n.t('Verify_email_desc'), I18n.t('Registration_Succeeded')); navigation.goBack(); diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index e0f9fa01159..db2f7026e21 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -127,7 +127,7 @@ const RoomInfoView = (): React.ReactElement => { try { if (room?.visitor?._id) { const result = await getVisitorInfo(room.visitor._id); - if (result.success) { + if (result) { const { visitor } = result; const params: { os?: string; browser?: string } = {}; if (visitor.userAgent) { @@ -166,7 +166,7 @@ const RoomInfoView = (): React.ReactElement => { try { const roomUserId = getUidDirectMessage({ ...(room || { rid, t }), itsMe }); const result = await getUserInfo(roomUserId); - if (result.success) { + if (result) { const { user } = result; const r = handleRoles(user); setRoomUser({ ...user, roles: r }); diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index 3f8b5aa072d..64b03eb3d85 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -39,7 +39,7 @@ export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, up try { const type = roomType; const result = await getRoomRoles(rid, type); - if (result?.success) { + if (result) { updateState({ roomRoles: result.roles }); } } catch (e) { diff --git a/app/views/SearchMessagesView/index.tsx b/app/views/SearchMessagesView/index.tsx index 165a0440148..3ae66c4aa6b 100644 --- a/app/views/SearchMessagesView/index.tsx +++ b/app/views/SearchMessagesView/index.tsx @@ -140,7 +140,7 @@ class SearchMessagesView extends React.Component => { + searchMessages = async (searchText: string): Promise<(TMessageModel)[]> => { if (!searchText) { return []; } @@ -160,7 +160,7 @@ class SearchMessagesView extends React.Component { if (message.urls && message.urls.length > 0) { message.urls = message.urls?.map((url, index) => { diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 1ea24a71887..1b0b3390b53 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -340,7 +340,7 @@ class TeamChannelsView extends React.Component { if (i._id === item._id) { i.teamDefault = !i.teamDefault; @@ -406,7 +406,7 @@ class TeamChannelsView extends React.Component result.room._id !== room._id); this.setState({ data: newData }); } diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 7e148423324..df055589d9b 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -341,7 +341,7 @@ class ThreadMessagesView extends React.Component Date: Tue, 13 Jan 2026 03:44:36 +0530 Subject: [PATCH 009/203] lock file fix --- yarn.lock | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index e50a2247636..b73d9324de8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3646,6 +3646,14 @@ resolved "https://registry.yarnpkg.com/@ide/backoff/-/backoff-1.0.0.tgz#466842c25bd4a4833e0642fab41ccff064010176" integrity sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g== +"@inquirer/external-editor@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@inquirer/external-editor/-/external-editor-1.0.3.tgz#c23988291ee676290fdab3fd306e64010a6d13b8" + integrity sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA== + dependencies: + chardet "^2.1.1" + iconv-lite "^0.7.0" + "@isaacs/balanced-match@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" @@ -5134,7 +5142,7 @@ resolved "https://registry.yarnpkg.com/@rocket.chat/api-client/-/api-client-0.2.38.tgz#7d50f69db48d89f67ecf09c404f75ff9abb7d3fe" integrity sha512-yC4wEW1LzNa1S1636RxBCysaFmzCyrjhTUVTBoJaZcSJLL9wKx2e05NYFQFPA54I+7zt5GQDRL8zRtjAjQkEOQ== dependencies: - "@rocket.chat/core-typings" "6.13.1" + "@rocket.chat/core-typings" "^7.10.1" "@rocket.chat/rest-typings" "^7.10.1" filter-obj "^3.0.0" query-string "^7.1.3" @@ -5159,7 +5167,7 @@ stack-trace "0.0.10" uuid "~8.3.2" -"@rocket.chat/core-typings@6.13.1", "@rocket.chat/core-typings@6.13.1": +"@rocket.chat/core-typings@6.13.1": version "6.13.1" resolved "https://registry.yarnpkg.com/@rocket.chat/core-typings/-/core-typings-6.13.1.tgz#50d759233683998f76529f0af4d36a6c6bf61429" integrity sha512-braIc3mvjju01HSc7jK3I3rbBtjVwL9yvUy1rSb4OBMOoSxE0UqQRR01q2djhgxYqCeIrTWOVgQyO9qPxW38Zw== @@ -5176,7 +5184,7 @@ integrity sha512-w/kqTsWhrT/gR4NNWQFlL22He6E0zPauZVvqfbRGL3ulfxsWCopYqPH4k2qU/nGqDeuB2rERvNNtv45cWi/r4Q== dependencies: "@rocket.chat/api-client" "^0.2.38" - "@rocket.chat/core-typings" "6.13.1" + "@rocket.chat/core-typings" "~7.10.1" "@rocket.chat/rest-typings" "^7.10.1" "@rocket.chat/emitter@^0.31.25": @@ -5219,7 +5227,7 @@ resolved "https://registry.yarnpkg.com/@rocket.chat/rest-typings/-/rest-typings-7.10.1.tgz#d5493ce9efc53d1bd4bc2745acae649dcab54edf" integrity sha512-jNttcWuUYIFBkwoyK7aPi8oS/crZU5UEmIxVxzuG6pGpV04vLkRLyG+ZrQlAhhlk6fQWRG8M0rBxSyPATPfGiA== dependencies: - "@rocket.chat/core-typings" "6.13.1" + "@rocket.chat/core-typings" "^7.10.1" "@rocket.chat/message-parser" "^0.31.32" "@rocket.chat/ui-kit" "~0.37.0" ajv "^8.17.1" @@ -7160,10 +7168,10 @@ char-regex@^2.0.0: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-2.0.1.tgz#6dafdb25f9d3349914079f010ba8d0e6ff9cd01e" integrity sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw== -chardet@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-2.1.0.tgz#1007f441a1ae9f9199a4a67f6e978fb0aa9aa3fe" - integrity sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA== +chardet@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-2.1.1.tgz#5c75593704a642f71ee53717df234031e65373c8" + integrity sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ== check-error@^2.1.1: version "2.1.1" @@ -9789,6 +9797,11 @@ hermes-profile-transformer@^0.0.6: dependencies: source-map "^0.7.3" +hoek@6.x.x: + version "6.1.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" + integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== + hoist-non-react-statics@3.3.2, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -10879,6 +10892,11 @@ joi@^17.2.1: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" +jose@^4.11.1: + version "4.15.9" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" + integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== + jpeg-js@0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" @@ -12987,6 +13005,14 @@ queue@6.0.2: dependencies: inherits "~2.0.3" +randexp@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.5.3.tgz#f31c2de3148b30bdeb84b7c3f59b0ebb9fec3738" + integrity sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w== + dependencies: + drange "^1.0.2" + ret "^0.2.0" + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" From d0c792c2565742d7ea3abc791d89e69025baf1cc Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 13 Jan 2026 03:48:22 +0530 Subject: [PATCH 010/203] import fix --- app/lib/services/connect.ts | 11 +++++------ app/lib/services/sdk.ts | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index a47a5f16bad..2d56a2534c5 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -19,12 +19,11 @@ import EventEmitter from '../methods/helpers/events'; import { updateSettings } from '../../actions/settings'; import { defaultSettings } from '../constants/defaultSettings'; import { compareServerVersion, isIOS } from '../methods/helpers'; -import { onRolesChanged } from 'lib/methods/getRoles'; -import { getSettings } from 'lib/methods/getSettings'; -import { setPresenceCap } from 'lib/methods/getUsersPresence'; -import { _setUser, _activeUsers, _setUserTimer } from 'lib/methods/setUser'; -import { unsubscribeRooms } from 'lib/methods/subscribeRooms'; -import { IActiveUsers } from 'reducers/activeUsers'; +import { onRolesChanged } from '../../lib/methods/getRoles'; +import { getSettings } from '../../lib/methods/getSettings'; +import { setPresenceCap } from '../../lib/methods/getUsersPresence'; +import { _setUser, _activeUsers, _setUserTimer } from '../../lib/methods/setUser'; +import { unsubscribeRooms } from '../../lib/methods/subscribeRooms'; interface IServices { [index: string]: string | boolean; diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 8f38f82925b..626e124362e 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -8,7 +8,7 @@ import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; import { random } from '../methods/helpers'; import UserPreferences from '../methods/userPreferences'; -import { BASIC_AUTH_KEY } from 'lib/methods/helpers/fetch'; +import { BASIC_AUTH_KEY } from '../methods/helpers/fetch'; class Sdk { private sdk: DDPSDK | undefined; From f5a4e27a0bd91fdd7e2e355f2b20e6bb789cecf5 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 13 Jan 2026 04:31:05 +0530 Subject: [PATCH 011/203] partially fixed --- app/lib/methods/getPermissions.ts | 2 +- app/lib/methods/getRooms.ts | 11 +++-------- app/lib/methods/getSettings.ts | 4 ++-- app/lib/methods/loadMessagesForRoom.ts | 5 +++-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/lib/methods/getPermissions.ts b/app/lib/methods/getPermissions.ts index 02082089ad1..9e1ce52fe08 100644 --- a/app/lib/methods/getPermissions.ts +++ b/app/lib/methods/getPermissions.ts @@ -186,7 +186,7 @@ export function getPermissions(): Promise { params.updatedSince = updatedSince; } // RC 0.73.0 - const result = await sdk.get('permissions.listAll', params); + const result = await sdk.methodCall('permissions/get', params); if (!result.success) { return resolve(); diff --git a/app/lib/methods/getRooms.ts b/app/lib/methods/getRooms.ts index 0c289953172..c17a6790282 100644 --- a/app/lib/methods/getRooms.ts +++ b/app/lib/methods/getRooms.ts @@ -5,15 +5,10 @@ export function getRooms(updatedSince: Date) { // rooms.get: Since RC 0.62.0 if (updatedSince) { const updatedDate = updatedSince.toISOString(); - // TODO: missing definitions from server return Promise.all([ - // @ts-ignore - sdk.get('subscriptions.get', { updatedSince: updatedDate }), - // @ts-ignore - sdk.get('rooms.get', { updatedSince: updatedDate }) + sdk.get('/v1/subscriptions.get', { updatedSince: updatedDate }), + sdk.get('/v1/rooms.get', { updatedSince: updatedDate }) ]); } - // TODO: missing definitions from server - // @ts-ignore - return Promise.all([sdk.get('subscriptions.get'), sdk.get('rooms.get')]); + return Promise.all([sdk.get('/v1/subscriptions.get', {}), sdk.get('/v1/rooms.get', {})]); } diff --git a/app/lib/methods/getSettings.ts b/app/lib/methods/getSettings.ts index 603a944a27f..93aa99e694b 100644 --- a/app/lib/methods/getSettings.ts +++ b/app/lib/methods/getSettings.ts @@ -155,7 +155,7 @@ export async function getSettings(): Promise { const settingsParams = Object.keys(defaultSettings).filter(key => !loginSettings.includes(key)); // RC 0.60.0 let offset = 0; - let remaining; + let remaining let settings: IData[] = []; const serverVersion = reduxStore.getState().server.version; const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') @@ -165,7 +165,7 @@ export async function getSettings(): Promise { do { // TODO: why is no-await-in-loop enforced in the first place? /* eslint-disable no-await-in-loop */ - const response = await fetch(`${url}&offset=${offset}`); + const response = await fetch(`https://${url}&offset=${offset}`); const result = await response.json(); if (!result.success) { diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts index 747b8b2aad2..280930ee906 100644 --- a/app/lib/methods/loadMessagesForRoom.ts +++ b/app/lib/methods/loadMessagesForRoom.ts @@ -7,6 +7,7 @@ import { type RoomTypes, roomTypeToApiType } from './roomTypeToApiType'; import sdk from '../services/sdk'; import updateMessages from './updateMessages'; import { generateLoadMoreId } from './helpers/generateLoadMoreId'; +import { type ChatGetMessage } from '@rocket.chat/rest-typings'; const COUNT = 50; const COUNT_LIMIT = COUNT * 10; @@ -25,12 +26,12 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t: return; } - const params = { roomId, count: COUNT, ...(lastTs && { latest: lastTs }) }; + const params: ChatGetMessage = { roomId, count: COUNT, ...(lastTs && { latest: lastTs }) }; let data; switch (apiType) { case 'channels': - data = await sdk.get('channels.history', params); + data = await sdk.get('/v1/chat.getMessage', params); break; case 'groups': data = await sdk.get('groups.history', params); From 73bcb05fa08b20bbb6fabc04ff4401c18113f5a3 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 9 Mar 2026 01:56:52 +0530 Subject: [PATCH 012/203] lock --- yarn.lock | 83 +++++++++++++++---------------------------------------- 1 file changed, 22 insertions(+), 61 deletions(-) diff --git a/yarn.lock b/yarn.lock index 62cd9fd3d33..bf2328c23fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4790,7 +4790,7 @@ stack-trace "0.0.10" uuid "~8.3.2" -"@rocket.chat/core-typings@6.13.1": +"@rocket.chat/core-typings@^7.10.1", "@rocket.chat/core-typings@~7.10.1": version "6.13.1" resolved "https://registry.yarnpkg.com/@rocket.chat/core-typings/-/core-typings-6.13.1.tgz#50d759233683998f76529f0af4d36a6c6bf61429" integrity sha512-braIc3mvjju01HSc7jK3I3rbBtjVwL9yvUy1rSb4OBMOoSxE0UqQRR01q2djhgxYqCeIrTWOVgQyO9qPxW38Zw== @@ -5092,14 +5092,22 @@ "@types/connect" "*" "@types/node" "*" -"@types/bytebuffer@^5.0.44": - version "5.0.48" - resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.48.tgz#0e6b0e8b8dbafde3314148ed378b8aed6eaac4f1" - integrity sha512-ormKm68NtTOtR8C/4jyRJEYbwKABXRkHHR/1fmkiuFbCQkltgtXSUGfldCSmJzvuyJvmBzWjBbOi79Ry/oJQug== +"@types/bytebuffer@~5.0.49": + version "5.0.49" + resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.49.tgz#04cb63de6c83ae2b6f33f397d2503c079bfc321a" + integrity sha512-lV4YLiolMdD4upDmr4vnfiwV/FN9Jg33eNTSFMkHqyMKqTIAn5TmFTYsARwXwUXeFU7yzRpECmWV1SOhIvdkRQ== dependencies: "@types/long" "^3.0.0" "@types/node" "*" +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" + "@types/connect@*": version "3.4.38" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" @@ -5107,6 +5115,11 @@ dependencies: "@types/node" "*" +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + "@types/ejson@^2.1.3": version "2.2.2" resolved "https://registry.yarnpkg.com/@types/ejson/-/ejson-2.2.2.tgz#54e8f33ae02ac5854ce3a3cd721ba75cbcdfbd44" @@ -6749,7 +6762,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -6843,14 +6856,6 @@ cli-spinners@^2.0.0, cli-spinners@^2.5.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== - dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" - cli-width@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" @@ -7141,12 +7146,12 @@ core-js-pure@^3.30.2: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.36.0.tgz#ffb34330b14e594d6a9835cf5843b4123f1d95db" integrity sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ== -core-util-is@^1.0.3, core-util-is@~1.0.0: +core-util-is@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: +cosmiconfig@^5.0.5: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== @@ -7462,11 +7467,6 @@ deno-bin@1.37.1: adm-zip "^0.5.4" follow-redirects "^1.10.0" -denodeify@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" - integrity sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg== - depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -8641,13 +8641,6 @@ fast-uri@^3.0.1: resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== -fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: - version "4.4.1" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" - integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== - dependencies: - strnum "^1.0.5" - fast-xml-parser@^4.4.1: version "4.5.3" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz#c54d6b35aa0f23dc1ea60b6c884340c006dc6efb" @@ -8845,11 +8838,6 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.231.0.tgz#13daa172b3c06ffacbb31025592dc0db41fe28f3" integrity sha512-WVzuqwq7ZnvBceCG0DGeTQebZE+iIU0mlk5PmJgYj9DDrt+0isGC2m1ezW9vxL4V+HERJJo9ExppOnwKH2op6Q== -flow-parser@^0.206.0: - version "0.206.0" - resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.206.0.tgz#f4f794f8026535278393308e01ea72f31000bfef" - integrity sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w== - follow-redirects@^1.10.0: version "1.15.11" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" @@ -9311,13 +9299,6 @@ hermes-parser@0.28.1: dependencies: hermes-estree "0.28.1" -hermes-profile-transformer@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz#bd0f5ecceda80dd0ddaae443469ab26fb38fc27b" - integrity sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ== - dependencies: - source-map "^0.7.3" - hoek@6.x.x: version "6.1.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" @@ -13031,13 +13012,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.5.1: - version "7.8.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" - integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== - dependencies: - tslib "^2.1.0" - rxjs@^7.5.5, rxjs@^7.8.1: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" @@ -13115,11 +13089,6 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== - "semver@2 || 3 || 4 || 5", semver@^5.6.0, semver@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" @@ -14001,15 +13970,7 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== -through2@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6, through@^2.3.8: +through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== From 348f2e08d91bbc883ecbb52b73e8c0e39b5d0e0e Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:05:36 +0530 Subject: [PATCH 013/203] more fix --- app/ee/omnichannel/lib/index.ts | 5 +- .../omnichannel/lib/subscriptions/inquiry.ts | 16 +++--- app/lib/methods/actions.ts | 4 +- app/lib/methods/getUsersPresence.ts | 2 +- app/lib/methods/logout.ts | 2 +- app/lib/methods/sendMessage.ts | 6 +- app/lib/methods/subscriptions/rooms.ts | 6 +- app/lib/services/sdk.ts | 57 ++++++++++++++++++- app/sagas/createChannel.js | 12 ++++ app/views/ThreadMessagesView/index.tsx | 2 +- 10 files changed, 89 insertions(+), 23 deletions(-) diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index e0ba31d510f..a727bc2d7ed 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -14,15 +14,16 @@ export const getInquiriesQueued = (serverVersion: string) => { const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') ? '/v1/livechat/inquiries.queuedForUser' : '/v1/livechat/inquiries.queued'; - return sdk.get(url); + return sdk.get(url as any); }; // this inquiry is added to the db by the subscriptions stream // and will be removed by the queue stream // RC 2.4.0 +// @ts-ignore export const takeInquiry = (inquiryId: string, serverVersion: string) => { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.11.0')) { - return sdk.post('livechat/inquiries.take', { inquiryId }); + return sdk.post('livechat/inquiries.take' as any, { inquiryId }); } // Method removed in 8.0.0 return sdk.methodCallWrapper('livechat:takeInquiry', inquiryId); diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index 7c34f5920f5..cc8d6103ebb 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -82,21 +82,21 @@ export default function subscribeInquiry() { throw new Error('inquiry: @subscribeInquiry user.id not found'); } - getAgentDepartments(user.id).then(result => { - if (result.success) { + getAgentDepartments(user.id).then((result: any) => { + if (result?.success) { const { departments } = result; - if (!departments.length || hasRole('livechat-manager')) { - sdk.subscribe(streamTopic, 'public').catch((e: unknown) => console.log(e)); + if (!departments?.length || hasRole('livechat-manager')) { + sdk.subscribe(streamTopic, 'public'); } - const departmentIds = departments.map(({ departmentId }) => departmentId); - departmentIds.forEach(departmentId => { + const departmentIds = departments?.map(({ departmentId }: { departmentId: string }) => departmentId) || []; + departmentIds.forEach((departmentId: string) => { // subscribe to all departments of the agent - sdk.subscribe(streamTopic, `department/${departmentId}`).catch((e: unknown) => console.log(e)); + sdk.subscribe(streamTopic, `department/${departmentId}`); }); } - }); + }).catch((e: unknown) => console.log(e)); return { stop: () => stop() diff --git a/app/lib/methods/actions.ts b/app/lib/methods/actions.ts index 06f98147847..6c3603465ef 100644 --- a/app/lib/methods/actions.ts +++ b/app/lib/methods/actions.ts @@ -88,8 +88,8 @@ export function triggerAction({ type, actionId, appId, rid, mid, viewId, contain const payload = rest.payload || rest; try { - const { userId, authToken } = sdk.current.currentLogin; - const { host } = sdk.current.client; + const { userId, authToken } = sdk.currentLogin; + const host = sdk.current?.connection.url; // we need to use fetch because this.sdk.post add /v1 to url const result = await fetch(`${host}/api/apps/ui.interaction/${appId}/`, { diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index 798fe37ebec..f15345782cb 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -64,7 +64,7 @@ export async function getUsersPresence(usersParams: string[]) { const result = (await sdk.get('users.presence' as any, params as any)) as any; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) { - sdk.subscribeRaw('stream-user-presence', ['', { added: usersParams }]); + sdk.subscribeRaw('stream-user-presence', '', { added: usersParams }); } if (result.success) { diff --git a/app/lib/methods/logout.ts b/app/lib/methods/logout.ts index 69a9643ec2d..6402a35af41 100644 --- a/app/lib/methods/logout.ts +++ b/app/lib/methods/logout.ts @@ -105,7 +105,7 @@ export async function logout({ server }: { server: string }): Promise { try { // RC 0.60.0 - await sdk.current.logout(); + await sdk.logout(); } catch (e) { log(e); } diff --git a/app/lib/methods/sendMessage.ts b/app/lib/methods/sendMessage.ts index c7f4942f0db..c3762bf77d9 100644 --- a/app/lib/methods/sendMessage.ts +++ b/app/lib/methods/sendMessage.ts @@ -51,10 +51,8 @@ const changeMessageStatus = async (id: string, status: number, tmid?: string, me async function sendMessageCall(message: any) { const { _id, tmid } = message; try { - // RC 0.60.0 - // @ts-ignore - const result = await sdk.post('chat.sendMessage', { message }); - if (result.success) { + const result = await sdk.post('/v1/chat.sendMessage', { message }); + if (result.message) { // @ts-ignore return changeMessageStatus(_id, messagesStatus.SENT, tmid, result.message); } diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 78aeb9ca674..5787d8363dd 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -302,7 +302,7 @@ export default function subscribeRooms() { const db = database.active; // check if the server from variable is the same as the js sdk client - if (sdk && sdk.current.client && sdk.current.client.host !== subServer) { + if (sdk && sdk.current && sdk.current.connection && sdk.current.connection.url !== subServer) { return; } if (ddpMessage.msg === 'added') { @@ -439,8 +439,8 @@ export default function subscribeRooms() { try { // set the server that started this task - subServer = sdk.current.client.host; - sdk.current.subscribeNotifyUser().catch((e: unknown) => console.log(e)); + subServer = sdk.current?.connection.url || ''; + sdk.subscribeRaw('stream-notify-user', 'user'); roomsSubscription = { stop: () => stop() }; return null; } catch (e) { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 626e124362e..ba9d68c6ebb 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -1,6 +1,6 @@ import EJSON from 'ejson'; import isEmpty from 'lodash/isEmpty'; -import { ClientStream, DDPSDK } from '@rocket.chat/ddp-client'; +import { type ClientStream, DDPSDK } from '@rocket.chat/ddp-client'; import { Platform } from 'react-native'; import DeviceInfo from 'react-native-device-info'; @@ -171,14 +171,69 @@ class Sdk { return this.current?.client.subscribe(...args); } + subscribeRoom(rid: string): Promise { + const subscriptions: any[] = []; + if (!this.current) { + return Promise.resolve(subscriptions); + } + try { + subscriptions.push(this.current.client.subscribe('stream-room-messages', rid)); + subscriptions.push(this.current.client.subscribe('stream-notify-room', rid, { useCollection: false })); + return Promise.resolve(subscriptions); + } catch (e) { + return Promise.resolve(subscriptions); + } + } + onCollection(...args: Parameters) { return this.current?.client.onCollection(...args); } + onStreamData(name: string, callback: (...data: any) => void): Promise<{ stop: () => void }> { + return new Promise((resolve) => { + if (!this.current) { + resolve({ stop: () => {} }); + return; + } + const listener = this.current.client.onCollection(name, (ddpMessage: any) => { + // DDP stream events come in the format: { msg, collection, id, fields: { eventName, args } } + if (ddpMessage && ddpMessage.fields) { + callback(ddpMessage); + } + }); + resolve({ stop: listener }); + }); + } + stream(...args: Parameters) { return this.current?.stream(...args); } + subscribeRaw(...args: Parameters) { + return this.current?.client.subscribe(...args); + } + + get currentLogin() { + const user = this.current?.account.user; + if (!user) { + return { userId: '', authToken: '' }; + } + return { + userId: user.id, + authToken: user.token || '' + }; + } + + get client() { + return this.current?.client; + } + + async logout(): Promise { + if (this.current?.account) { + await this.current.account.logout(); + } + } + _stream(name: string, data: unknown, cb: (...data: any) => void) { const [key, args] = Array.isArray(data) ? data : [data]; diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index caa54482011..c194275c4d0 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -63,6 +63,18 @@ const handleRequest = function* handleRequest({ data }) { yield db.write(async () => { await subCollection.create((s) => { s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); + s.id = sub.rid; + s.ts = sub.ts || new Date(); + s.ls = sub.ls || new Date(); + s.lr = sub.lr || new Date(); + s.lm = sub.lm || new Date(); + s.roomUpdatedAt = sub.roomUpdatedAt || new Date(); + s.open = sub.open ?? true; + s.alert = sub.alert ?? false; + s.unread = sub.unread ?? 0; + s.userMentions = sub.userMentions ?? 0; + s.groupMentions = sub.groupMentions ?? 0; + s.ro = sub.ro ?? false; Object.assign(s, sub); }); }); diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index df055589d9b..a5e62bc322b 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -345,7 +345,7 @@ class ThreadMessagesView extends React.Component Date: Mon, 9 Mar 2026 17:40:49 +0530 Subject: [PATCH 014/203] added resolutions --- package.json | 3 ++- yarn.lock | 30 +++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index f08bf550235..e71a82f16b1 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,8 @@ }, "resolutions": { "ua-parser-js": "1.0.2", - "jpeg-js": "0.4.4" + "jpeg-js": "0.4.4", + "@rocket.chat/core-typings": "6.13.1" }, "devDependencies": { "@babel/core": "~7.25.9", diff --git a/yarn.lock b/yarn.lock index bf2328c23fb..f1685b5c06c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4790,7 +4790,7 @@ stack-trace "0.0.10" uuid "~8.3.2" -"@rocket.chat/core-typings@^7.10.1", "@rocket.chat/core-typings@~7.10.1": +"@rocket.chat/core-typings@^7.10.1", "@rocket.chat/core-typings@^7.10.8", "@rocket.chat/core-typings@~7.10.1": version "6.13.1" resolved "https://registry.yarnpkg.com/@rocket.chat/core-typings/-/core-typings-6.13.1.tgz#50d759233683998f76529f0af4d36a6c6bf61429" integrity sha512-braIc3mvjju01HSc7jK3I3rbBtjVwL9yvUy1rSb4OBMOoSxE0UqQRR01q2djhgxYqCeIrTWOVgQyO9qPxW38Zw== @@ -4835,22 +4835,22 @@ tldts "~5.7.112" "@rocket.chat/message-parser@^0.31.32": - version "0.31.32" - resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.31.32.tgz#100ba61165e960303695e50a51ba0427058ea95a" - integrity sha512-n13B5NnZxoTIMUn9n5fEhXFAsze9EKWmI0szIv1ALoGEb5RXs9wCiFuOMajCwm+gw597hQL5ZTDoQFyrns/s2w== + version "0.31.34" + resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.31.34.tgz#cd3c0f46ec524411a3ac14b26eeb9607ef3c8a21" + integrity sha512-QWKPJPuKGAzPgKF1ouCPkGONQ1PLxTVUCn2t85pAOSJj2tqcjPK3dA1V8VNIeCYDPKwN2tjhYffrVCpjulMl/g== dependencies: - tldts "~5.7.112" + tldts "~6.1.86" "@rocket.chat/mobile-crypto@RocketChat/rocket.chat-mobile-crypto": version "0.2.1" resolved "https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/b75e261282bc0c25a3b8fde3230d1c1e01809e00" "@rocket.chat/rest-typings@^7.10.1": - version "7.10.1" - resolved "https://registry.yarnpkg.com/@rocket.chat/rest-typings/-/rest-typings-7.10.1.tgz#d5493ce9efc53d1bd4bc2745acae649dcab54edf" - integrity sha512-jNttcWuUYIFBkwoyK7aPi8oS/crZU5UEmIxVxzuG6pGpV04vLkRLyG+ZrQlAhhlk6fQWRG8M0rBxSyPATPfGiA== + version "7.10.8" + resolved "https://registry.yarnpkg.com/@rocket.chat/rest-typings/-/rest-typings-7.10.8.tgz#36f84194c95868fa929d47f4789260a291244f5b" + integrity sha512-M+4xqhwspj9++TPEboso0l4Kzs419z7BPrjsH1XwmJ+f6i+KkwvYAatHJbRvnryam/nsinsOcmMYUJTrOR0VIQ== dependencies: - "@rocket.chat/core-typings" "^7.10.1" + "@rocket.chat/core-typings" "^7.10.8" "@rocket.chat/message-parser" "^0.31.32" "@rocket.chat/ui-kit" "~0.37.0" ajv "^8.17.1" @@ -14008,6 +14008,11 @@ tldts-core@^5.7.112: resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-5.7.112.tgz#168459aa79495f5d46407a685a7a9f0cdc9a272b" integrity sha512-mutrEUgG2sp0e/MIAnv9TbSLR0IPbvmAImpzqul5O/HJ2XM1/I1sajchQ/fbj0fPdA31IiuWde8EUhfwyldY1Q== +tldts-core@^6.1.86: + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.86.tgz#a93e6ed9d505cb54c542ce43feb14c73913265d8" + integrity sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== + tldts@~5.7.112: version "5.7.112" resolved "https://registry.yarnpkg.com/tldts/-/tldts-5.7.112.tgz#f3d7a5ade3ee09a48a1ecb4f05f04335b0787c84" @@ -14015,6 +14020,13 @@ tldts@~5.7.112: dependencies: tldts-core "^5.7.112" +tldts@~6.1.86: + version "6.1.86" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.86.tgz#087e0555b31b9725ee48ca7e77edc56115cd82f7" + integrity sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== + dependencies: + tldts-core "^6.1.86" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" From 517a4cdfb6d65e69c76d09c56e0bff76e5e035d2 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:37:43 +0530 Subject: [PATCH 015/203] more changes --- app/lib/encryption/encryption.ts | 2 +- app/lib/methods/getCustomEmojis.ts | 4 ++-- app/lib/methods/getPermissions.ts | 5 ++--- app/lib/methods/helpers/helpers.ts | 1 + app/lib/methods/loadMissedMessages.ts | 3 +-- app/lib/methods/readMessages.ts | 2 +- app/lib/services/restApi.ts | 17 +++++++++++------ app/sagas/createChannel.js | 2 ++ app/views/RoomInfoView/index.tsx | 7 +++++-- app/views/RoomView/index.tsx | 2 +- 10 files changed, 27 insertions(+), 18 deletions(-) diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 8a1e50c5762..5518bee5fd1 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -490,7 +490,7 @@ class Encryption { } const result = await fetchUsersWaitingForGroupKey(sampleIds); - if (!result.success || !Object.keys(result.usersWaitingForE2EKeys).length) { + if (!Object.keys(result.usersWaitingForE2EKeys).length) { return; } diff --git a/app/lib/methods/getCustomEmojis.ts b/app/lib/methods/getCustomEmojis.ts index a3c986f3a7a..c3c04ad5f7a 100644 --- a/app/lib/methods/getCustomEmojis.ts +++ b/app/lib/methods/getCustomEmojis.ts @@ -130,9 +130,9 @@ export function getCustomEmojis() { } // RC 0.75.0 - const result = await sdk.get('emoji-custom.list', params); + const result = await sdk.get('/v1/emoji-custom.list', params); - if (!result.success) { + if (!result) { return resolve(); } diff --git a/app/lib/methods/getPermissions.ts b/app/lib/methods/getPermissions.ts index 9e1ce52fe08..4d65a84f988 100644 --- a/app/lib/methods/getPermissions.ts +++ b/app/lib/methods/getPermissions.ts @@ -186,9 +186,8 @@ export function getPermissions(): Promise { params.updatedSince = updatedSince; } // RC 0.73.0 - const result = await sdk.methodCall('permissions/get', params); - - if (!result.success) { + const result = await sdk.get('/v1/permissions.listAll', params); + if (!result) { return resolve(); } diff --git a/app/lib/methods/helpers/helpers.ts b/app/lib/methods/helpers/helpers.ts index 08fb78fedb2..7f58c0b8c8e 100644 --- a/app/lib/methods/helpers/helpers.ts +++ b/app/lib/methods/helpers/helpers.ts @@ -111,6 +111,7 @@ export async function hasPermission(permissions, rid?: any): Promise const loginUser = reduxStore.getState().login.user; const userRoles = loginUser?.roles || []; const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; + console.log('channel.edit-1', permissions, mergedRoles); return permissions.map(permission => permission?.some(r => mergedRoles.includes(r) ?? false)); } catch (e) { log(e); diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index ba2e6c23458..af75ffceeb3 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -8,8 +8,7 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; const count = 50; const syncMessages = async ({ roomId, next, type }: { roomId: string; next: number; type: 'UPDATED' | 'DELETED' }) => { - // @ts-ignore // this method dont have type - const { result } = await sdk.get('chat.syncMessages', { roomId, next, count, type }); + const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, next, count, type }); return result; }; diff --git a/app/lib/methods/readMessages.ts b/app/lib/methods/readMessages.ts index 790cafbe505..70cbed2a091 100644 --- a/app/lib/methods/readMessages.ts +++ b/app/lib/methods/readMessages.ts @@ -34,7 +34,7 @@ export async function readMessages(rid: string, ls: Date, updateLastOpen = false // RC 0.61.0 // @ts-ignore - await sdk.post('subscriptions.read', { rid }); + await sdk.post('/v1/subscriptions.read', { rid }); await db.write(async () => { try { diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 9fa111c5e69..fdf00214b55 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -16,7 +16,7 @@ import { type TParams } from '../../definitions/ILivechatEditView'; import { type ILivechatTag } from '../../definitions/ILivechatTag'; import { type ISpotlight } from '../../definitions/ISpotlight'; import { TEAM_TYPE } from '../../definitions/ITeam'; -import { type OperationParams, type ResultFor } from '../../definitions/rest/helpers'; +import { type OperationParams } from '../../definitions/rest/helpers'; import { type SubscriptionsEndpoints } from '../../definitions/rest/v1/subscriptions'; import { Encryption } from '../encryption'; import { type RoomTypes, roomTypeToApiType } from '../methods/roomTypeToApiType'; @@ -71,7 +71,7 @@ export const e2eGetUsersOfRoomWithoutKey = (rid: string) => export const e2eSetRoomKeyID = (rid: string, keyID: string) => // RC 0.70.0 - sdk.post('/v1/e2e.setRoomKeyID', { rid, keyID }); + sdk.methodCallWrapper('e2e.setRoomKeyID', rid, keyID); export const e2eUpdateGroupKey = (uid: string, rid: string, key: string): any => // RC 0.70.0 @@ -268,7 +268,8 @@ export const joinRoom = (roomId: string, joinCode: string | null, type: 'c' | 'p if (type === 'p') { return sdk.methodCallWrapper('joinRoom', roomId) as Promise; } - return sdk.post('/v1/channels.join', { roomId, joinCode }); + const params = { roomId, ...(joinCode && { joinCode }) }; + return sdk.post('/v1/channels.join', params); }; export const deleteMessage = (messageId: string, rid: string) => @@ -423,7 +424,7 @@ export const returnLivechat = (rid: string, departmentId?: string): Promise const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.12.0')) { - return sdk.post('livechat/inquiries.returnAsInquiry', { roomId: rid, departmentId }); + return sdk.post('/v1/livechat/room.returnAsInquiry', { roomId: rid, departmentId }); } // RC 0.72.0 @@ -517,9 +518,13 @@ export const leaveRoom = (roomId: string, t: RoomTypes) => // RC 0.48.0 sdk.post(`/v1/${roomTypeToApiType(t)}.leave`, { roomId }); -export const deleteRoom = (roomId: string, t: RoomTypes) => - // RC 0.49.0 +export const deleteRoom = (roomId: string, t: RoomTypes) => { + if (t === 'd') { + return sdk.post(`/v1/im.close`, { roomId }); + } + sdk.post(`/v1/${roomTypeToApiType(t)}.delete`, { roomId }); +} export const toggleMuteUserInRoom = (rid: string, username: string, userId: string, mute: boolean) => { const serverVersion = reduxStore.getState().server.version; diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index c194275c4d0..fd27840de63 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -57,6 +57,7 @@ const handleRequest = function* handleRequest({ data }) { ...result?.group }; } + console.log('new channel', sub) try { const db = database.active; const subCollection = db.get('subscriptions'); @@ -64,6 +65,7 @@ const handleRequest = function* handleRequest({ data }) { await subCollection.create((s) => { s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); s.id = sub.rid; + s._id = sub.rid; s.ts = sub.ts || new Date(); s.ls = sub.ls || new Date(); s.lr = sub.lr || new Date(); diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index 78a31286aaf..7c392f63e3a 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -183,6 +183,7 @@ const RoomInfoView = (): React.ReactElement => { const loadRoom = async () => { const permissionToEdit = isLivechat ? [editOmnichannelContact, editLivechatRoomCustomfields] : [editRoomPermission]; const permissions = await hasPermission(permissionToEdit, rid); + console.log('channel.edit-2', editRoomPermission, permissions) const canEdit = permissions.some(Boolean); const subRoom = await getSubscriptionByRoomId(rid); if (!subRoom && isDirect && fromRid) { @@ -197,13 +198,15 @@ const RoomInfoView = (): React.ReactElement => { const sub = subRoom.observe(); subscription.current = sub.subscribe(changes => { setRoom(changes.asPlain()); + console.log('channel.edit', roomType, canEdit) setHeader(roomType === SubscriptionType.DIRECT ? false : canEdit); }); } else { try { if (!isDirect) { const result = await getRoomInfo(rid); - if (result.success) setRoom({ ...room, ...(result.room as unknown as ISubscription) }); + console.log('room', result) + if (result) setRoom({ ...room, ...(result.room as unknown as ISubscription) }); } } catch (e) { log(e); @@ -219,7 +222,7 @@ const RoomInfoView = (): React.ReactElement => { if (!isEmpty(member)) return resolve(); try { const result = await createDirectMessage(roomUser.username); - if (result.success) return resolve({ ...roomUser, rid: result.room.rid }); + if (result?.room) return resolve({ ...roomUser, rid: result.room.rid }); } catch (e) { reject(e); } diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 89c851ab168..b205e6c4264 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -700,7 +700,7 @@ class RoomView extends React.Component { this.setState({ roomUserId }, () => this.setHeader()); const result = await getUserInfo(roomUserId); - if (result.success) { + if (result) { return result.user; } } catch (e) { From 80130ece352df57920b8f0187df6931e9f067a81 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:50:52 +0530 Subject: [PATCH 016/203] more type fix --- app/lib/services/restApi.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index fdf00214b55..cdb6a88e552 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1,3 +1,5 @@ +import type { IPushTokenTypes } from '@rocket.chat/core-typings'; + import { type IAvatarSuggestion, type IMessage, @@ -1007,7 +1009,7 @@ export const registerPushToken = () => new Promise(async resolve => { const token = getDeviceToken(); if (token) { - const type = isIOS ? 'apn' : 'gcm'; + const type: IPushTokenTypes = isIOS ? 'apn' : 'gcm'; const data = { value: token, type, @@ -1015,7 +1017,7 @@ export const registerPushToken = () => }; try { // RC 0.60.0 - await sdk.post('push.token', data); + await sdk.post('/v1/push.token', data); } catch (error) { console.log(error); } @@ -1103,9 +1105,9 @@ export function getUserInfo(userId: string) { return sdk.get('/v1/users.info', { userId }); } -export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('rooms.favorite', { roomId, favorite }); +export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('/v1/rooms.favorite', { roomId, favorite }); -export const sendInvitationReply = (roomId: string, action: 'accept' | 'reject') => sdk.post('rooms.invite', { roomId, action }); +export const sendInvitationReply = (roomId: string, action: 'accept' | 'reject') => sdk.post('/v1/rooms.invite', { roomId, action }); export const videoConferenceJoin = (callId: string, cam?: boolean, mic?: boolean) => sdk.post('/v1/video-conference.join', { callId, state: { cam: !!cam, mic: mic === undefined ? true : mic } }); @@ -1130,7 +1132,7 @@ export const getUsersRoles = async (): Promise => { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.10.0')) { // RC 7.10.0 const response = await sdk.get('/v1/roles.getUsersInPublicRoles'); - if (response.success) { + if (response.users) { return response.users; } return false; From 540a705f717be1727fcb7bb27da442114110763f Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:57:19 +0530 Subject: [PATCH 017/203] more ts fix --- app/lib/services/restApi.ts | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index cdb6a88e552..d1a55f3921d 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -210,9 +210,7 @@ export const removeTeamRoom = ({ roomId, teamId }: { roomId: string; teamId: str export const leaveTeam = ({ teamId, rooms }: { teamId: string; rooms: string[] }): any => // RC 3.13.0 - // TODO: missing definitions from server - // @ts-ignore - sdk.post('teams.leave', { + sdk.post('/v1/teams.leave', { teamId, // RC 4.2.0 ...(rooms?.length && { rooms }) @@ -233,16 +231,13 @@ export const updateTeamRoom = ({ roomId, isDefault }: { roomId: string; isDefaul export const deleteTeam = ({ teamId, roomsToRemove }: { teamId: string; roomsToRemove: string[] }): any => // RC 3.13.0 - // TODO: missing definitions from server - // @ts-ignore - sdk.post('teams.delete', { teamId, roomsToRemove }); + sdk.post('/v1/teams.delete', { teamId, roomsToRemove }); export const teamListRoomsOfUser = ({ teamId, userId }: { teamId: string; userId: string }) => // RC 3.13.0 sdk.get('/v1/teams.listRoomsOfUser', { teamId, userId }); export const convertChannelToTeam = ({ rid, name, type }: { rid: string; name: string; type: 'c' | 'p' }) => { - const serverVersion = reduxStore.getState().server.version; let params; if (type === 'c') { // https://github.com/RocketChat/Rocket.Chat/pull/25279 @@ -525,13 +520,13 @@ export const deleteRoom = (roomId: string, t: RoomTypes) => { return sdk.post(`/v1/im.close`, { roomId }); } - sdk.post(`/v1/${roomTypeToApiType(t)}.delete`, { roomId }); + return sdk.post(`/v1/${roomTypeToApiType(t)}.delete`, { roomId }); } export const toggleMuteUserInRoom = (rid: string, username: string, userId: string, mute: boolean) => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.8.0')) { - return sdk.post(mute ? 'rooms.muteUser' : 'rooms.unmuteUser', { roomId: rid, userId }); + return sdk.post(mute ? '/v1/rooms.muteUser' : '/v1/rooms.unmuteUser', { roomId: rid, userId }); } // RC 0.51.0 return sdk.methodCallWrapper(mute ? 'muteUserInRoom' : 'unmuteUserInRoom', { rid, username }); @@ -919,21 +914,15 @@ export const translateMessage = (messageId: string, targetLanguage: string) => export const findOrCreateInvite = ({ rid, days, maxUses }: { rid: string; days: number; maxUses: number }): any => // RC 2.4.0 - // TODO: missing definitions from server - // @ts-ignore - sdk.post('findOrCreateInvite', { rid, days, maxUses }); + sdk.post('/v1/findOrCreateInvite', { rid, days, maxUses }); export const validateInviteToken = (token: string): any => // RC 2.4.0 - // TODO: missing definitions from server - // @ts-ignore - sdk.post('validateInviteToken', { token }); + sdk.post('/v1/validateInviteToken', { token }); export const inviteToken = (token: string): any => // RC 2.4.0 - // TODO: missing definitions from server - // @ts-ignore - sdk.post('useInviteToken', { token }); + sdk.post('/v1/useInviteToken', { token }); export const readThreads = (tmid: string): Promise => { const serverVersion = reduxStore.getState().server.version; @@ -981,8 +970,7 @@ export function e2eResetOwnKey(userId: string): Promise<{ success?: boolean }> { return Promise.resolve({ success: true }); } -export function e2eResetRoomKey(rid: string, e2eKey: string, e2eKeyId: string): Promise { - // RC ? +export function e2eResetRoomKey(rid: string, e2eKey: string, e2eKeyId: string): Promise { return sdk.post('/v1/e2e.resetRoomKey', { rid, e2eKey, e2eKeyId }); } @@ -994,7 +982,7 @@ export const editMessage = async (message: Pick From f9c01e96457f5ab99995e662cce6e32a46b3f802 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 10 Mar 2026 00:39:31 +0530 Subject: [PATCH 018/203] more --- app/lib/encryption/room.ts | 2 +- app/lib/hooks/useUserData.ts | 6 +++--- app/lib/methods/canOpenRoom.ts | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/lib/encryption/room.ts b/app/lib/encryption/room.ts index 685ddb214eb..77be343015f 100644 --- a/app/lib/encryption/room.ts +++ b/app/lib/encryption/room.ts @@ -314,7 +314,7 @@ export default class EncryptionRoom { try { const decryptedOldGroupKeys = await this.exportOldRoomKeys(this.subscription?.oldRoomKeys); const result = await e2eGetUsersOfRoomWithoutKey(this.roomId); - if (result.success) { + if (result.users) { const { users } = result; if (!users.length) { return; diff --git a/app/lib/hooks/useUserData.ts b/app/lib/hooks/useUserData.ts index b2cdbb4c7ae..c1fc3e7b9a3 100644 --- a/app/lib/hooks/useUserData.ts +++ b/app/lib/hooks/useUserData.ts @@ -28,12 +28,12 @@ const useUserData = (rid: string) => { } else { try { const result = await getUserInfo(rid); - if (result.success) { + if (result.user) { const { user } = result; const username = useRealName && user.name ? user.name : user.username; setUser({ - username, - avatar: user.username, + username: username || '', + avatar: user.username || '', uid: user._id, type: SubscriptionType.DIRECT, direct: true diff --git a/app/lib/methods/canOpenRoom.ts b/app/lib/methods/canOpenRoom.ts index a1a964b04bb..5226029284c 100644 --- a/app/lib/methods/canOpenRoom.ts +++ b/app/lib/methods/canOpenRoom.ts @@ -17,7 +17,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: // if the dm already exists it'll return the existent if (type === ERoomTypes.DIRECT && !rid) { const result = await createDirectMessage(name); - if (result.success) { + if (result.room) { const { room } = result; return { ...room, @@ -30,8 +30,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: if (type === ERoomTypes.GROUP) { try { // RC 0.61.0 - // @ts-ignore - await sdk.post(`${restTypes[type]}.open`, params); + await sdk.post(`/v1/groups.open`, params); } catch (e: any) { if (!(e.data && /is already open/.test(e.data.error))) { return false; @@ -43,8 +42,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: // we'll get info from the room if ((type === ERoomTypes.CHANNEL || type === ERoomTypes.GROUP) && !rid) { // RC 0.72.0 - // @ts-ignore - const result: any = await sdk.get(`${restTypes[type]}.info`, params); + const result: any = await sdk.get(`/v1/${restTypes[type]}.info`, params); if (result.success) { const room = result[type]; room.rid = room._id; From 5f34fc0747674201952fb0a17830374e61860ae5 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:23:04 +0530 Subject: [PATCH 019/203] some more --- app/lib/methods/getRoles.ts | 2 +- app/lib/methods/loadMissedMessages.ts | 2 +- app/views/LivechatEditView.tsx | 2 +- app/views/RoomInfoView/index.tsx | 5 +---- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/lib/methods/getRoles.ts b/app/lib/methods/getRoles.ts index a266b91222c..aaaa7d71fbf 100644 --- a/app/lib/methods/getRoles.ts +++ b/app/lib/methods/getRoles.ts @@ -74,7 +74,7 @@ export function getRoles(): Promise { return new Promise(async resolve => { try { // RC 0.70.0 - const result = await sdk.get('roles.list'); + const result = await sdk.get('/v1/roles.list'); if (!result.success) { return resolve(); diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index af75ffceeb3..0c92a2a7d29 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -8,7 +8,7 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; const count = 50; const syncMessages = async ({ roomId, next, type }: { roomId: string; next: number; type: 'UPDATED' | 'DELETED' }) => { - const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, next, count, type }); + const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, next: next.toString(), count, type }); return result; }; diff --git a/app/views/LivechatEditView.tsx b/app/views/LivechatEditView.tsx index 64e55cdfa35..86b9b692e1c 100644 --- a/app/views/LivechatEditView.tsx +++ b/app/views/LivechatEditView.tsx @@ -79,7 +79,7 @@ const LivechatEditView = ({ user, navigation, route, theme }: ILivechatEditViewP const handleGetCustomFields = async () => { const result = await getCustomFields(); - if (result.success && result.customFields?.length) { + if (result.customFields?.length) { const visitorCustomFields = result.customFields .filter(field => field.visibility !== 'hidden' && field.scope === 'visitor') .map(field => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' })) diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index 7c392f63e3a..ac4364a17b3 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -166,7 +166,7 @@ const RoomInfoView = (): React.ReactElement => { try { const roomUserId = getUidDirectMessage({ ...(room || { rid, t }), itsMe }); const result = await getUserInfo(roomUserId); - if (result) { + if (result && result.user) { const { user } = result; const r = handleRoles(user); setRoomUser({ ...user, roles: r }); @@ -183,7 +183,6 @@ const RoomInfoView = (): React.ReactElement => { const loadRoom = async () => { const permissionToEdit = isLivechat ? [editOmnichannelContact, editLivechatRoomCustomfields] : [editRoomPermission]; const permissions = await hasPermission(permissionToEdit, rid); - console.log('channel.edit-2', editRoomPermission, permissions) const canEdit = permissions.some(Boolean); const subRoom = await getSubscriptionByRoomId(rid); if (!subRoom && isDirect && fromRid) { @@ -198,14 +197,12 @@ const RoomInfoView = (): React.ReactElement => { const sub = subRoom.observe(); subscription.current = sub.subscribe(changes => { setRoom(changes.asPlain()); - console.log('channel.edit', roomType, canEdit) setHeader(roomType === SubscriptionType.DIRECT ? false : canEdit); }); } else { try { if (!isDirect) { const result = await getRoomInfo(rid); - console.log('room', result) if (result) setRoom({ ...room, ...(result.room as unknown as ISubscription) }); } } catch (e) { From c222e1e3046d2dee9a65fc9453337041ebb9d265 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 12 Mar 2026 02:33:49 +0530 Subject: [PATCH 020/203] more --- app/lib/methods/getRoles.ts | 2 +- app/lib/methods/getSingleMessage.ts | 4 ++-- app/sagas/videoConf.ts | 2 +- app/views/ReadReceiptView/index.tsx | 2 +- app/views/RoomActionsView/index.tsx | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/lib/methods/getRoles.ts b/app/lib/methods/getRoles.ts index aaaa7d71fbf..fc2f4a54319 100644 --- a/app/lib/methods/getRoles.ts +++ b/app/lib/methods/getRoles.ts @@ -76,7 +76,7 @@ export function getRoles(): Promise { // RC 0.70.0 const result = await sdk.get('/v1/roles.list'); - if (!result.success) { + if (!result.roles) { return resolve(); } diff --git a/app/lib/methods/getSingleMessage.ts b/app/lib/methods/getSingleMessage.ts index 1cecd9be7d9..146239b7957 100644 --- a/app/lib/methods/getSingleMessage.ts +++ b/app/lib/methods/getSingleMessage.ts @@ -1,11 +1,11 @@ -import { type IMessage } from '../../definitions'; +import { type IMessage } from '@rocket.chat/core-typings'; import { getSingleMessage as getSingleMessageService } from '../services/restApi'; const getSingleMessage = (messageId: string): Promise => new Promise(async (resolve, reject) => { try { const result = await getSingleMessageService(messageId); - if (result.success) { + if (result.message) { return resolve(result.message); } return reject(); diff --git a/app/sagas/videoConf.ts b/app/sagas/videoConf.ts index d9ca57c4f2a..7838ac4e75f 100644 --- a/app/sagas/videoConf.ts +++ b/app/sagas/videoConf.ts @@ -175,7 +175,7 @@ function* initCall({ payload: { mic, cam, direct, rid } }: { payload: TCallProps if (isServer5OrNewer) { try { const videoConfResponse = yield* call(videoConferenceStart, rid); - if (videoConfResponse.success) { + if (videoConfResponse.data) { if (direct && videoConfResponse.data.type === 'direct') { yield call(callUser, { rid, uid: videoConfResponse.data.calleeId, callId: videoConfResponse.data.callId }); } else { diff --git a/app/views/ReadReceiptView/index.tsx b/app/views/ReadReceiptView/index.tsx index 6df57998f3c..a2f9f99c56a 100644 --- a/app/views/ReadReceiptView/index.tsx +++ b/app/views/ReadReceiptView/index.tsx @@ -85,7 +85,7 @@ class ReadReceiptView extends React.Component Date: Thu, 12 Mar 2026 22:14:54 +0530 Subject: [PATCH 021/203] local map --- package.json | 12 +- yarn.lock | 475 ++++++++++----------------------------------------- 2 files changed, 96 insertions(+), 391 deletions(-) diff --git a/package.json b/package.json index e71a82f16b1..e831d11cf86 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,13 @@ "@react-navigation/elements": "^2.6.1", "@react-navigation/native": "^7.1.16", "@react-navigation/native-stack": "^7.3.23", - "@rocket.chat/ddp-client": "^0.3.38", + "@rocket.chat/ddp-client": "file:../Rocket.Chat/packages/ddp-client", "@rocket.chat/emitter": "^0.31.25", - "@rocket.chat/message-parser": "^0.31.31", + "@rocket.chat/message-parser": "file:../Rocket.Chat/packages/message-parser", + "@rocket.chat/media-signaling": "file:../Rocket.Chat/packages/media-signaling", "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto", "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", - "@rocket.chat/ui-kit": "0.31.19", + "@rocket.chat/ui-kit": "file:../Rocket.Chat/packages/ui-kit", "axios": "~0.28.1", "bytebuffer": "~5.0.1", "color2k": "1.2.4", @@ -140,7 +141,10 @@ "resolutions": { "ua-parser-js": "1.0.2", "jpeg-js": "0.4.4", - "@rocket.chat/core-typings": "6.13.1" + "@rocket.chat/core-typings": "file:../Rocket.Chat/packages/core-typings", + "@rocket.chat/rest-typings": "file:../Rocket.Chat/packages/rest-typings", + "@rocket.chat/api-client": "file:../Rocket.Chat/packages/api-client", + "@rocket.chat/media-signaling": "file:../Rocket.Chat/packages/media-signaling" }, "devDependencies": { "@babel/core": "~7.25.9", diff --git a/yarn.lock b/yarn.lock index f1685b5c06c..b825f932796 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2412,171 +2412,86 @@ dependencies: tslib "^2.4.0" -"@esbuild/aix-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" - integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== - "@esbuild/aix-ppc64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz#4e0f91776c2b340e75558f60552195f6fad09f18" integrity sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA== -"@esbuild/android-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" - integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== - "@esbuild/android-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz#bc766407f1718923f6b8079c8c61bf86ac3a6a4f" integrity sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg== -"@esbuild/android-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" - integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== - "@esbuild/android-arm@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.5.tgz#4290d6d3407bae3883ad2cded1081a234473ce26" integrity sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA== -"@esbuild/android-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" - integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== - "@esbuild/android-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.5.tgz#40c11d9cbca4f2406548c8a9895d321bc3b35eff" integrity sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw== -"@esbuild/darwin-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" - integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== - "@esbuild/darwin-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz#49d8bf8b1df95f759ac81eb1d0736018006d7e34" integrity sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ== -"@esbuild/darwin-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" - integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== - "@esbuild/darwin-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz#e27a5d92a14886ef1d492fd50fc61a2d4d87e418" integrity sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ== -"@esbuild/freebsd-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" - integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== - "@esbuild/freebsd-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz#97cede59d638840ca104e605cdb9f1b118ba0b1c" integrity sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw== -"@esbuild/freebsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" - integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== - "@esbuild/freebsd-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz#71c77812042a1a8190c3d581e140d15b876b9c6f" integrity sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw== -"@esbuild/linux-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" - integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== - "@esbuild/linux-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz#f7b7c8f97eff8ffd2e47f6c67eb5c9765f2181b8" integrity sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg== -"@esbuild/linux-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" - integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== - "@esbuild/linux-arm@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz#2a0be71b6cd8201fa559aea45598dffabc05d911" integrity sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw== -"@esbuild/linux-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" - integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== - "@esbuild/linux-ia32@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz#763414463cd9ea6fa1f96555d2762f9f84c61783" integrity sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA== -"@esbuild/linux-loong64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" - integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== - "@esbuild/linux-loong64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz#428cf2213ff786a502a52c96cf29d1fcf1eb8506" integrity sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg== -"@esbuild/linux-mips64el@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" - integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== - "@esbuild/linux-mips64el@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz#5cbcc7fd841b4cd53358afd33527cd394e325d96" integrity sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg== -"@esbuild/linux-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" - integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== - "@esbuild/linux-ppc64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz#0d954ab39ce4f5e50f00c4f8c4fd38f976c13ad9" integrity sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ== -"@esbuild/linux-riscv64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" - integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== - "@esbuild/linux-riscv64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz#0e7dd30730505abd8088321e8497e94b547bfb1e" integrity sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA== -"@esbuild/linux-s390x@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" - integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== - "@esbuild/linux-s390x@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz#5669af81327a398a336d7e40e320b5bbd6e6e72d" integrity sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ== -"@esbuild/linux-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" - integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== - "@esbuild/linux-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz#b2357dd153aa49038967ddc1ffd90c68a9d2a0d4" @@ -2587,11 +2502,6 @@ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz#53b4dfb8fe1cee93777c9e366893bd3daa6ba63d" integrity sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw== -"@esbuild/netbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" - integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== - "@esbuild/netbsd-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz#a0206f6314ce7dc8713b7732703d0f58de1d1e79" @@ -2602,51 +2512,26 @@ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz#2a796c87c44e8de78001d808c77d948a21ec22fd" integrity sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw== -"@esbuild/openbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" - integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== - "@esbuild/openbsd-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz#28d0cd8909b7fa3953af998f2b2ed34f576728f0" integrity sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg== -"@esbuild/sunos-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" - integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== - "@esbuild/sunos-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz#a28164f5b997e8247d407e36c90d3fd5ddbe0dc5" integrity sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA== -"@esbuild/win32-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" - integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== - "@esbuild/win32-arm64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz#6eadbead38e8bd12f633a5190e45eff80e24007e" integrity sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw== -"@esbuild/win32-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" - integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== - "@esbuild/win32-ia32@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz#bab6288005482f9ed2adb9ded7e88eba9a62cc0d" integrity sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ== -"@esbuild/win32-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" - integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== - "@esbuild/win32-x64@0.25.5": version "0.25.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz#7fc114af5f6563f19f73324b5d5ff36ece0803d1" @@ -3940,11 +3825,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@msgpack/msgpack@3.0.0-beta2": - version "3.0.0-beta2" - resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz#5bccee30f84df220b33905e3d8249ba96deca0b7" - integrity sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw== - "@napi-rs/wasm-runtime@^0.2.11": version "0.2.12" resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" @@ -4760,61 +4640,43 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.2.1.tgz#9403f51c17cae37edf870c6bc0c81c1ece5ccef8" integrity sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA== -"@rocket.chat/api-client@^0.2.38": - version "0.2.38" - resolved "https://registry.yarnpkg.com/@rocket.chat/api-client/-/api-client-0.2.38.tgz#7d50f69db48d89f67ecf09c404f75ff9abb7d3fe" - integrity sha512-yC4wEW1LzNa1S1636RxBCysaFmzCyrjhTUVTBoJaZcSJLL9wKx2e05NYFQFPA54I+7zt5GQDRL8zRtjAjQkEOQ== +"@rocket.chat/api-client@file:../Rocket.Chat/packages/api-client", "@rocket.chat/api-client@workspace:^": + version "0.2.50" dependencies: - "@rocket.chat/core-typings" "^7.10.1" - "@rocket.chat/rest-typings" "^7.10.1" + "@rocket.chat/core-typings" "workspace:^" + "@rocket.chat/rest-typings" "workspace:^" filter-obj "^3.0.0" query-string "^7.1.3" split-on-first "^3.0.0" strict-uri-encode "^2.0.0" -"@rocket.chat/apps-engine@1.46.0": - version "1.46.0" - resolved "https://registry.yarnpkg.com/@rocket.chat/apps-engine/-/apps-engine-1.46.0.tgz#d40b6818803e9d9bceb387c3972ad6eb1e154944" - integrity sha512-9ZQD/0xibyGRtk+OuUXTCV5onja2alL1irpw1J5ru3YeUWwe+BFwdHdRYITFsfj23LTYSydfGzCOvuNTPBQi1Q== +"@rocket.chat/core-typings@file:../Rocket.Chat/packages/core-typings", "@rocket.chat/core-typings@workspace:^", "@rocket.chat/core-typings@workspace:~": + version "8.3.0-develop" dependencies: - "@msgpack/msgpack" "3.0.0-beta2" - adm-zip "^0.5.9" - cryptiles "^4.1.3" - debug "^4.3.4" - deno-bin "1.37.1" - esbuild "^0.20.2" - jose "^4.11.1" - jsonrpc-lite "^2.2.0" - lodash.clonedeep "^4.5.0" - semver "^5.7.1" - stack-trace "0.0.10" - uuid "~8.3.2" - -"@rocket.chat/core-typings@^7.10.1", "@rocket.chat/core-typings@^7.10.8", "@rocket.chat/core-typings@~7.10.1": - version "6.13.1" - resolved "https://registry.yarnpkg.com/@rocket.chat/core-typings/-/core-typings-6.13.1.tgz#50d759233683998f76529f0af4d36a6c6bf61429" - integrity sha512-braIc3mvjju01HSc7jK3I3rbBtjVwL9yvUy1rSb4OBMOoSxE0UqQRR01q2djhgxYqCeIrTWOVgQyO9qPxW38Zw== - dependencies: - "@rocket.chat/apps-engine" "1.46.0" - "@rocket.chat/icons" "~0.38.0" - "@rocket.chat/message-parser" "^0.31.31" - "@rocket.chat/ui-kit" "~0.36.1" - "@types/express" "^4.17.21" - -"@rocket.chat/ddp-client@^0.3.38": - version "0.3.38" - resolved "https://registry.yarnpkg.com/@rocket.chat/ddp-client/-/ddp-client-0.3.38.tgz#5fad1e47f0186f0243c0bc3623e69c3e7760c20c" - integrity sha512-w/kqTsWhrT/gR4NNWQFlL22He6E0zPauZVvqfbRGL3ulfxsWCopYqPH4k2qU/nGqDeuB2rERvNNtv45cWi/r4Q== - dependencies: - "@rocket.chat/api-client" "^0.2.38" - "@rocket.chat/core-typings" "~7.10.1" - "@rocket.chat/rest-typings" "^7.10.1" + "@rocket.chat/icons" "~0.46.0" + "@rocket.chat/message-parser" "workspace:^" + "@rocket.chat/ui-kit" "workspace:~" + typia "~9.7.2" + zod "~4.3.6" + +"@rocket.chat/ddp-client@file:../Rocket.Chat/packages/ddp-client": + version "1.0.3" + dependencies: + "@rocket.chat/api-client" "workspace:^" + "@rocket.chat/core-typings" "workspace:~" + "@rocket.chat/media-signaling" "workspace:^" + "@rocket.chat/rest-typings" "workspace:^" "@rocket.chat/emitter@^0.31.25": version "0.31.25" resolved "https://registry.yarnpkg.com/@rocket.chat/emitter/-/emitter-0.31.25.tgz#17dd7838dc988ebc5fcd96343c4431d5cef45845" integrity sha512-hw5BpDlNwpYSb+K5X3DNMNUVEVXxmXugUPetGZGCWvntSVFsOjYuVEypoKW6vBBXSfqCBb0kN1npYcKEb4NFBw== +"@rocket.chat/emitter@^0.32.0": + version "0.32.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/emitter/-/emitter-0.32.0.tgz#6fe39474684ad6c97109df160cdd9c18a6beebbc" + integrity sha512-QCXNGDm5xjJPePMeP9GknMAZne5xhNmLLD/ZVVqNgOyeWdfQKddwBUqtMiWDaFCRq1bC22kNf3WQLqt89CO6fg== + "@rocket.chat/eslint-config@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@rocket.chat/eslint-config/-/eslint-config-0.4.0.tgz#d648decd02ae739eac17a32e1630332a75318ea1" @@ -4822,37 +4684,39 @@ dependencies: eslint-plugin-import "^2.17.2" -"@rocket.chat/icons@~0.38.0": - version "0.38.0" - resolved "https://registry.yarnpkg.com/@rocket.chat/icons/-/icons-0.38.0.tgz#12dab5d3aec57680b63ee72181ab7679fb895427" - integrity sha512-T0p6dIxyQSoYTQ9CQnq11vXMkadOXFuT8vYW2SG/JEieCJeZMTRF9hVBRC31pQB/4KF5qAtfXY6gdDC0iTBzCw== +"@rocket.chat/icons@~0.46.0": + version "0.46.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/icons/-/icons-0.46.0.tgz#13ab207b0bdac9bb189987d234d6031cd9f6753a" + integrity sha512-SQ8V3bkmhZwCdyeXhLDo+G+8qMNZrZgURm8O/g05c2Njo4/ZG3vj2j1PEvlSJ4Ao8IYBmUO4q9/qkEGDr40ZLg== -"@rocket.chat/message-parser@^0.31.31": - version "0.31.31" - resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.31.31.tgz#9a3eea7602ac37387c6384577623865c8d536003" - integrity sha512-YWHdin2ejndwgQJXlHIzAmQB+xvZLeEOqOkPq8RxdyTjAaGo1DbBCrc0aU7u0LOUnGN+jm3H4grnYdI9SPwitA== +"@rocket.chat/media-signaling@file:../Rocket.Chat/packages/media-signaling", "@rocket.chat/media-signaling@workspace:^": + version "0.1.1" dependencies: - tldts "~5.7.112" + "@rocket.chat/emitter" "^0.32.0" + ajv "^8.17.1" -"@rocket.chat/message-parser@^0.31.32": - version "0.31.34" - resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.31.34.tgz#cd3c0f46ec524411a3ac14b26eeb9607ef3c8a21" - integrity sha512-QWKPJPuKGAzPgKF1ouCPkGONQ1PLxTVUCn2t85pAOSJj2tqcjPK3dA1V8VNIeCYDPKwN2tjhYffrVCpjulMl/g== +"@rocket.chat/message-parser@file:../Rocket.Chat/packages/message-parser": + version "0.31.33" dependencies: tldts "~6.1.86" +"@rocket.chat/message-parser@workspace:^": + version "0.32.0-dev.383" + resolved "https://registry.yarnpkg.com/@rocket.chat/message-parser/-/message-parser-0.32.0-dev.383.tgz#6928d0aa9de85cc9662b27d1fc5fbb21d94d5cd1" + integrity sha512-zTHOL3YBPPUyLydZWnwHtP3ZxAKQqInMEBJBLlWyu1V6E0JijPxiG7mqImXGzfZAfZI/cO8w1B1wGVzTWnwXQA== + dependencies: + tldts "~5.7.112" + "@rocket.chat/mobile-crypto@RocketChat/rocket.chat-mobile-crypto": version "0.2.1" resolved "https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/b75e261282bc0c25a3b8fde3230d1c1e01809e00" -"@rocket.chat/rest-typings@^7.10.1": - version "7.10.8" - resolved "https://registry.yarnpkg.com/@rocket.chat/rest-typings/-/rest-typings-7.10.8.tgz#36f84194c95868fa929d47f4789260a291244f5b" - integrity sha512-M+4xqhwspj9++TPEboso0l4Kzs419z7BPrjsH1XwmJ+f6i+KkwvYAatHJbRvnryam/nsinsOcmMYUJTrOR0VIQ== +"@rocket.chat/rest-typings@file:../Rocket.Chat/packages/rest-typings", "@rocket.chat/rest-typings@workspace:^": + version "8.3.0-develop" dependencies: - "@rocket.chat/core-typings" "^7.10.8" - "@rocket.chat/message-parser" "^0.31.32" - "@rocket.chat/ui-kit" "~0.37.0" + "@rocket.chat/core-typings" "workspace:^" + "@rocket.chat/message-parser" "workspace:^" + "@rocket.chat/ui-kit" "workspace:~" ajv "^8.17.1" ajv-formats "^3.0.1" @@ -4866,29 +4730,22 @@ tiny-events "^1.0.1" universal-websocket-client "^1.0.2" -"@rocket.chat/ui-kit@0.31.19": - version "0.31.19" - resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.31.19.tgz#737103123bc7e635382217eef75965b7e0f44703" - integrity sha512-8zRKQ5CoC4hIuYHVheO0d7etX9oizmM18fu99r5s/deciL/0MRWocdb4H/QsmbsNrkKCO6Z6wr7f9zzJCNTRHg== - -"@rocket.chat/ui-kit@~0.36.1": - version "0.36.1" - resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.36.1.tgz#0364c9dd92500aee561f5d550631a7f375a7f9e1" - integrity sha512-3gqNOc5LAmRlwGmre2fjSd0ey364qwA+NvId0D8bCObwhJCnq2u547p/knPa7uOBaRgKZFeMhX0wiK9mvfNHiA== +"@rocket.chat/ui-kit@file:../Rocket.Chat/packages/ui-kit": + version "0.39.0" dependencies: - typia "~5.3.3" + typia "~9.7.2" -"@rocket.chat/ui-kit@~0.37.0": - version "0.37.0" - resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.37.0.tgz#aa5fc6c1ce1adce3f0ab76b265164a5b37c67a5d" - integrity sha512-NoWdmyKSJys9T7nqJBJvxNi8o6lcVZwWKelCbpCP6YCSHrUUfzqCU4bg7F5nrkVBH8C9MKd5T/r3kzyicL+GHg== +"@rocket.chat/ui-kit@workspace:~": + version "0.39.1-rc.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.39.1-rc.0.tgz#584d4d8425ea5a028ebfd54cc4ffcf778904ae20" + integrity sha512-zpdcYrhwJW4/wKs6gZCfe5jkqhkWZ0Rym1dh91yKcedBHsEnkAHOqGPYg4WjF3/63CgOaMoy4XJhyn0X5uqNcQ== dependencies: - typia "~6.9.0" + typia "~9.7.2" -"@samchon/openapi@^0.4.6": - version "0.4.9" - resolved "https://registry.yarnpkg.com/@samchon/openapi/-/openapi-0.4.9.tgz#7dbfa20b415d39a7f8733f43d1ce219e2447b364" - integrity sha512-mvCHjm6WmfB7ObRukAM7djBTNgOtLrT5M++t530txQg+UaqsaGtLgoZ2WNkULgLtqRWxncjty3tfik4DGveOMQ== +"@samchon/openapi@^4.7.1": + version "4.7.2" + resolved "https://registry.yarnpkg.com/@samchon/openapi/-/openapi-4.7.2.tgz#b00c54b587b22e03e454ff58989c4a63144850b3" + integrity sha512-yj6kGWHtKK85wfJXrSmWqLWjfCd98SAvCTsVDlej2s7OfXXHqA4hmgPRNrAPIQRVi00xn26qL1PChjq1MhzlRQ== "@sideway/address@^4.1.5": version "4.1.5" @@ -4926,6 +4783,11 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@standard-schema/spec@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + "@storybook/global@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@storybook/global/-/global-5.0.0.tgz#b793d34b94f572c1d7d9e0f44fac4e0dbc9572ed" @@ -5084,14 +4946,6 @@ dependencies: "@babel/types" "^7.20.7" -"@types/body-parser@*": - version "1.19.6" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" - integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - "@types/bytebuffer@~5.0.49": version "5.0.49" resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.49.tgz#04cb63de6c83ae2b6f33f397d2503c079bfc321a" @@ -5108,13 +4962,6 @@ "@types/deep-eql" "*" assertion-error "^2.0.1" -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - "@types/deep-eql@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" @@ -5125,26 +4972,6 @@ resolved "https://registry.yarnpkg.com/@types/ejson/-/ejson-2.2.2.tgz#54e8f33ae02ac5854ce3a3cd721ba75cbcdfbd44" integrity sha512-4DEXy6yKJVr1DQx4xy+y7NV9mK6p1vgikvtbOITWgp2Prs6yhffqZ8XOjEtojX2pzC7B1KF9kcQe9Bv52E9K2A== -"@types/express-serve-static-core@^4.17.33": - version "4.19.7" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz#f1d306dcc03b1aafbfb6b4fe684cce8a31cffc10" - integrity sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@^4.17.21": - version "4.17.23" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.23.tgz#35af3193c640bfd4d7fe77191cd0ed411a433bef" - integrity sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -5172,11 +4999,6 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/http-errors@*": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - "@types/i18n-js@3.8.3": version "3.8.3" resolved "https://registry.yarnpkg.com/@types/i18n-js/-/i18n-js-3.8.3.tgz#9aac0ac81c0b5f16d864b395607442eebb382528" @@ -5253,11 +5075,6 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-3.0.32.tgz#f4e5af31e9e9b196d8e5fca8a5e2e20aa3d60b69" integrity sha512-ZXyOOm83p7X8p3s0IYM3VeueNmHpkk/yMlP8CLeOnEcu6hIwPH7YjZBvhQkR0ZFS2DqZAxKtJ/M5fcuv3OU5BA== -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - "@types/node@*": version "20.11.28" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.28.tgz#4fd5b2daff2e580c12316e457473d68f15ee6f66" @@ -5287,16 +5104,6 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== -"@types/qs@*": - version "6.14.0" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" - integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - "@types/react-native-background-timer@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/react-native-background-timer/-/react-native-background-timer-2.0.2.tgz#22f9126124068a0ee6a812e16f98e270c2b9b2c7" @@ -5347,30 +5154,6 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== -"@types/send@*": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.0.tgz#ae9dfa0e3ab0306d3c566182324a54c4be2fb45a" - integrity sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ== - dependencies: - "@types/node" "*" - -"@types/send@<1": - version "0.17.5" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.5.tgz#d991d4f2b16f2b1ef497131f00a9114290791e74" - integrity sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.9" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.9.tgz#f9b08ab7dd8bbb076f06f5f983b683654fe0a025" - integrity sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "<1" - "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -5855,11 +5638,6 @@ acorn@^8.11.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== -adm-zip@^0.5.4, adm-zip@^0.5.9: - version "0.5.16" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" - integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== - agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -6530,13 +6308,6 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -boom@7.x.x: - version "7.3.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-7.3.0.tgz#733a6d956d33b0b1999da3fe6c12996950d017b9" - integrity sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A== - dependencies: - hoek "6.x.x" - bplist-creator@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/bplist-creator/-/bplist-creator-0.1.0.tgz#018a2d1b587f769e379ef5519103730f8963ba1e" @@ -7204,13 +6975,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -cryptiles@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.3.tgz#2461d3390ea0b82c643a6ba79f0ed491b0934c25" - integrity sha512-gT9nyTMSUC1JnziQpPbxKGBbUg8VL7Zn2NB4E1cJYvuXdElHrwxrV9bmltZGDzet45zSDGyYceueke1TjynGzw== - dependencies: - boom "7.x.x" - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -7459,14 +7223,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -deno-bin@1.37.1: - version "1.37.1" - resolved "https://registry.yarnpkg.com/deno-bin/-/deno-bin-1.37.1.tgz#b5a75292c53497e68dbf9e30d2394ff449e9a996" - integrity sha512-RORiEtlUPKJqXVYO8P/B1sdOkYLGfSYi2kiRzXb1WUHzkq14yAQr73gXrRU3jcM7do77Rwq+ZR+XZ2Adq8Uymg== - dependencies: - adm-zip "^0.5.4" - follow-redirects "^1.10.0" - depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -7953,35 +7709,6 @@ esbuild-register@^3.5.0: "@esbuild/win32-ia32" "0.25.5" "@esbuild/win32-x64" "0.25.5" -esbuild@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" - integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== - optionalDependencies: - "@esbuild/aix-ppc64" "0.20.2" - "@esbuild/android-arm" "0.20.2" - "@esbuild/android-arm64" "0.20.2" - "@esbuild/android-x64" "0.20.2" - "@esbuild/darwin-arm64" "0.20.2" - "@esbuild/darwin-x64" "0.20.2" - "@esbuild/freebsd-arm64" "0.20.2" - "@esbuild/freebsd-x64" "0.20.2" - "@esbuild/linux-arm" "0.20.2" - "@esbuild/linux-arm64" "0.20.2" - "@esbuild/linux-ia32" "0.20.2" - "@esbuild/linux-loong64" "0.20.2" - "@esbuild/linux-mips64el" "0.20.2" - "@esbuild/linux-ppc64" "0.20.2" - "@esbuild/linux-riscv64" "0.20.2" - "@esbuild/linux-s390x" "0.20.2" - "@esbuild/linux-x64" "0.20.2" - "@esbuild/netbsd-x64" "0.20.2" - "@esbuild/openbsd-x64" "0.20.2" - "@esbuild/sunos-x64" "0.20.2" - "@esbuild/win32-arm64" "0.20.2" - "@esbuild/win32-ia32" "0.20.2" - "@esbuild/win32-x64" "0.20.2" - escalade@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" @@ -8838,11 +8565,6 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.231.0.tgz#13daa172b3c06ffacbb31025592dc0db41fe28f3" integrity sha512-WVzuqwq7ZnvBceCG0DGeTQebZE+iIU0mlk5PmJgYj9DDrt+0isGC2m1ezW9vxL4V+HERJJo9ExppOnwKH2op6Q== -follow-redirects@^1.10.0: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== - follow-redirects@^1.15.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -9299,11 +9021,6 @@ hermes-parser@0.28.1: dependencies: hermes-estree "0.28.1" -hoek@6.x.x: - version "6.1.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" - integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== - hoist-non-react-statics@3.3.2, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -10386,11 +10103,6 @@ joi@^17.2.1: "@sideway/formula" "^3.0.1" "@sideway/pinpoint" "^2.0.0" -jose@^4.11.1: - version "4.15.9" - resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100" - integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA== - jpeg-js@0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" @@ -10586,11 +10298,6 @@ jsonify@^0.0.1: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978" integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg== -jsonrpc-lite@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jsonrpc-lite/-/jsonrpc-lite-2.2.0.tgz#fb3aa9d292c8970eb7f83c6040c6554767bbc6a6" - integrity sha512-/cbbSxtZWs1O7R4tWqabrCM/t3N8qKUZMAg9IUqpPvUs6UyRvm6pCNYkskyKN/XU0UgffW+NY2ZRr8t0AknX7g== - jsrsasign@11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-11.0.0.tgz#766570c21f87d68075a142f5188f7e583cee9d70" @@ -10796,11 +10503,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== -lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== - lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -11814,6 +11516,13 @@ package-json-from-dist@^1.0.0: resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== +package-manager-detector@^0.2.0: + version "0.2.11" + resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-0.2.11.tgz#3af0b34f99d86d24af0a0620603d2e1180d05c9c" + integrity sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ== + dependencies: + quansync "^0.2.7" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -12197,6 +11906,11 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" +quansync@^0.2.7: + version "0.2.11" + resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.11.tgz#f9c3adda2e1272e4f8cf3f1457b04cbdb4ee692a" + integrity sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA== + query-string@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" @@ -13089,7 +12803,7 @@ schema-utils@^4.0.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" -"semver@2 || 3 || 4 || 5", semver@^5.6.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -13487,11 +13201,6 @@ stack-generator@^2.0.3, stack-generator@^2.0.5: dependencies: stackframe "^1.3.4" -stack-trace@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - stack-utils@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -14280,25 +13989,17 @@ typescript@~5.9.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== -typia@~5.3.3: - version "5.3.12" - resolved "https://registry.yarnpkg.com/typia/-/typia-5.3.12.tgz#848b07d28b783772322518053c9c250e90357ef6" - integrity sha512-PzgbFt08j7Cb3NKjB6KX7FTWD8dX0MicQ3zXqhDx0cMAeEd+i/omO9B+X/tvtuZ1e2w+2jz1QE3qXMF0/o/LDA== +typia@~9.7.2: + version "9.7.2" + resolved "https://registry.yarnpkg.com/typia/-/typia-9.7.2.tgz#e6770ae64225de25955f83a2b53ce37147d9e4d2" + integrity sha512-eLIKd0KHZtSvbsA+FYwX+Y0ZBt0BwVGz3GgODQX+6GfGL4DOzKW02LEx62oUZg6vCQX1BL5xyiPXAIdW+Hc51g== dependencies: + "@samchon/openapi" "^4.7.1" + "@standard-schema/spec" "^1.0.0" commander "^10.0.0" comment-json "^4.2.3" inquirer "^8.2.5" - randexp "^0.5.3" - -typia@~6.9.0: - version "6.9.0" - resolved "https://registry.yarnpkg.com/typia/-/typia-6.9.0.tgz#60da0f18884510f983c1e1b206aa0244a58c4dc3" - integrity sha512-Vgo3CmE9XZhb/zsrW6Q4IQk7JeJk+ZVGxu/T4QZdI/PEokOpn00mGFMk9hhXNA27jrYC0NCgalO+YcZYsR5nGw== - dependencies: - "@samchon/openapi" "^0.4.6" - commander "^10.0.0" - comment-json "^4.2.3" - inquirer "^8.2.5" + package-manager-detector "^0.2.0" randexp "^0.5.3" typical@^4.0.0: @@ -14534,11 +14235,6 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@~8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - v8-to-istanbul@^9.0.1: version "9.2.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" @@ -15032,3 +14728,8 @@ yup@0.32.11: version "4.1.12" resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.12.tgz#64f1ea53d00eab91853195653b5af9eee68970f0" integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ== + +zod@~4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a" + integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg== From 13fdf960214366417d128ab584f9d00d8d15e1ba Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Apr 2026 02:59:48 +0530 Subject: [PATCH 022/203] using live packages --- package.json | 8 +- .../rocket.chat-media-signaling-0.2.0.tgz | Bin 0 -> 74929 bytes yarn.lock | 117 +++++++++++++----- 3 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 packages/rocket.chat-media-signaling-0.2.0.tgz diff --git a/package.json b/package.json index 9600d64b67f..996bc68a543 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "@react-navigation/elements": "^2.6.1", "@react-navigation/native": "^7.1.16", "@react-navigation/native-stack": "^7.3.23", + "@rocket.chat/core-typings": "^8.3.2", + "@rocket.chat/ddp-client": "^0.3.50", "@rocket.chat/message-parser": "0.31.31", "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto", "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", @@ -139,10 +141,8 @@ "resolutions": { "ua-parser-js": "1.0.2", "jpeg-js": "0.4.4", - "@rocket.chat/core-typings": "file:../Rocket.Chat/packages/core-typings", - "@rocket.chat/rest-typings": "file:../Rocket.Chat/packages/rest-typings", - "@rocket.chat/api-client": "file:../Rocket.Chat/packages/api-client", - "@rocket.chat/media-signaling": "file:../Rocket.Chat/packages/media-signaling" + "@rocket.chat/core-typings": "8.3.2", + "@rocket.chat/media-signaling": "file:./packages/rocket.chat-media-signaling-0.2.0.tgz" }, "devDependencies": { "@babel/core": "~7.25.9", diff --git a/packages/rocket.chat-media-signaling-0.2.0.tgz b/packages/rocket.chat-media-signaling-0.2.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..4fa5387e0da5c440af6d5f2b18aa1b29e01026bd GIT binary patch literal 74929 zcmY(KQ*Xxj(ZI;j^M!uIheoj$Mne9EL4z+#*hD9P!QEY5lDo}-r^hCO z{_C%xKQvg$ay@o^ZC!1vd_#h!s(@|-SbY1stoY<#*+n)Q78;t`MuSJr^jkx$qant_ z1FDEfD%ns^R>?pP!r1+A${|LHd-z-g;3%lMKW}Pk%3MB|@%-iArurmnW_5>v-?zD~ z>FILq811>`-!C2og2-P7iEDnA>VceI-;`vTxGh9Ct`+5xAZ}mO1^WKp# zI*>vK0LGqNGh&~zj9H>%hbu_6FH*5Q=}?VJXMp29ki_mSKwMPe)~7X3C)EYWOciKa z6X*r(X?weD8v?bou>U5keFNEkCj9t_LYfM9?!oD=25naILw$|RogpK7iE`TieBbeE z_atZFuCx95}

rS3^2 z#CY%3%d^3GbfJ3#2j%^g6S05Un>2|Kx*{{Qu(P@SB31m zV(o9G`ZYZ~-_XAdnysE}YK3A18MB7c(Bb+VeLoj3U>%;lI)pmUuL6-(_*4)}6~S98 z2*^MgB}FrxSkcpgMx#R`vCBZZykb#wFyw6rNyfQ$#L#Q;F6-o;6G<#`H1xo~cAPWQ zMJO|2oKoUmdD-TQ$l{;@qz2lhvP?|HY^14~oGs}6au=W1DSPkIYXVhcO^RWz}kO-BZA(Zx59#w^ml+O8}2<_yq+%?cQZGBz907xD-+KO zDz)21j`1gxUNusIdACB{v>jYX0>fCBsi4&867O#e>L#(+d8Lqm0A26Py=?+Mbbt3; zh}W41R=72a4_IIq>HFXrRk$c-#MTi`1MBfH++lPe%7A(YG)Z0$ykGMNOkF5YuOVOy zDfd_3pWBxt1piRMbXIRn&G$4{stB|UErC}$Vm0gZ4BPmV|Z0DIU0gr&`3=Pf|gZwUz&Z~ z1B^v4`wjvs&A5Tk0Ju*pMZpiVt;0kUKz~V+GeVqoFK~m&T#jmb)@g8-UZM%ZJ0C4u z#B)L{jm9^-%t7WT8SrtTew@p>v9V!{zgGO+7Q%A32+tlNjK1ulU#Ks#kp9(DEqdGh z>|ZM6NtzQv2%-qGioysETE27xga8voR58+4q@|zvvrMw9nmA!_6yZ}m-9e@URfs#H z?7*llf~1dB+tY3PW7p_btAns~#2U?b%+~=V;%qJ=D?JFbSd(vuj$`nt{YMqxk1o0T zO49LI#bg;@bKx`b3hlhD6^keCCp;I=Fg5(K4VF{Bju?l84&M2A4y$~5(|WDymD@eX zLO;YrU8duTe$7M~a6Rbas<9xoPok;V{VFC`gd#v-*pn)OZeD?wVWn&X889LX8zjlg ziI*cjJ^g%mfN+T|xmJN2$+Lj)Av6(XjWiQfIwTO_MBt5oe=Ua+Av`O{R-N*>U>fcl zSP4C2f#h^opGGfnLS>w3JV6D}=&i#*5gSuHoc-bpm!`*$js*W>4w*Dvq#5>Q;uj;- zJiwKs3s+G?(xHP}9w?^jF3G3lRpsY^TlAN5V{a$ou8>4cb}@5+DsQOoXv1I}T+C@h zzo%iE=PkP!6SlypB~CWY_Gy9D^_)%P2+#jpv}btgk#N{#xPN5wI=akY&m@IJUIRFA z>s@D^P6Yl<-a6B(7q_sCOZ@A1W80963G{`!b!f8%NwLtH#OHub-FzPxMALwU(83Q& zE;`aK(+9-_GhLo@N&Q?~!ra|48$CSpzEc)$|1pPxB7D6R8jDDnhSz%uK99lnSPwzrjbJBT=S8F5 zUqw*i8|JjE7`MEVXeZV4SkDpM`ec*~{pUFn!>An!CGa=J^?Ngq>d5c~$o-REPRHs8y5A12RU=Ouog! zBw9uS3IkU>FnHtRJ_iy9>WBi&Uo5%gh02w~BU#*V4(BAwOEr?C7i=QY8aVfBB zCyQ8%&a2#qqR2ySU%(wzmLeR3{;2Fb{x_KIc3Q}-evYY0*F%{)!SeN9Kc#YFSMd7? zHOC3}2L9WR8Xaz8;w$`{EfQVe(aa>}BP7DZWY zo9$L8IZsSwVqmHNyk@*iYq75Dxd3V-tyJS5u%O;NMLm%*5s!>y6+uFtEIcIFhxa5>%9y{B!rUKY9Om>NoW10Xl<^#s>WMn?|`mIo?2 zl+9hdeB&`sRO$@~k1L1Sk>Yk$VWp-|Dhce7^A0_EU&x&Slk9Dh67i?}Zei+`-sQ6i z;Zv00Q@9l(n2?b(lw2WozX9C zlCPrT)PwHU_R@aSjnYg3&S-vAJG{C~DtQ6%n1gq+3X*v_+0ScZe`e(RRXyLjTCr*B zT;K11Q0>?2r4;V<`uT77yIS#6@J_MzY1J$-b|k20+h|~mTMBBiqk%+tspr?5P37g7mjskJJ$Nr6=>NLcmT+f86f~oe z6r=~H^~1_6T;*n;k6_2qF>^d6^quA3M~QNWu!lawPF=nWO00b_KHA!HpdSmFSPL4Z z6Mxun&A_{jOMXxh2Js8U>wJ@!o+Z*UL78Po8Y^$7EMl)RI1K88S$F3Ss{>JkZ(gz4rpNoqV(Z;4 zNF~qki#L83K!}65@zu4Ke?)pVKZ&7t8Bc@o4-WEq z$6=PK4~EI-Z@+iJg$Q@v#|9$6k`Lkq<#$Q^oXvJ%=%`x$RDJY0@&~z%oLn5H<)5cj z#yAgSJP{_2&Ba)jz_1sp@T*qDn{0-Vb&Ck}O*Slqz1|XZ+OE{g5}YeF_hQR5|tgw9I|bV~P~X6CgbK@|?yB zf_Ku5E`o4|1tY?*M7!FXOJVM29W?GAmEBF-^q;5{`?KswKi*TmvP+qe5|yN`md4A2 zVar`wwXahNze5eB5oEN5JX?7pV8~_kfT^5KNnCyaJr=0G3*uW2Duf|?Q?Tg z#Xq^84P*OO?yY=?MsKIg0x`;BKu3ka)%OZDO9}q=^1NW=8wb`Di$|0dMehKGV%vAE zE~*ThfAUQlGGb%SiQSg)xtVgY{K&`O@E5^=Ny%B1lFW zq@MQ4Q(9!LS9(LX(R->EHzH3&#S8_Z8{&gEd0=!RxOE|q)br;U1(f$7bdU|(^9Shq z@LYhqpn%(P{x~r)x=Cn=$%+Gp_6UQRxKUf0jwyBBDA3Wksy7gF_5#kFdO5ei{#vhH zn08g67e?y(K?eRlyXrn51=CG`BotG7vX$DrnvE%*%8y=E`@SN(?q!GcaK)SXT6>i` z44BfaT0DviJWSio=AI>m?Zq;V$2^Pna>-)3LQ#W4n#1Z!Rye4c+{#*enIpA^Edg#n zXB0hCRl&xbs2i(T0Q_M3-5seAlfX(!x7=5@)%xUf&MUGrKmu%Vde`iYHHxWQWzOV|ApQ1g0Vy+v z6Fh(3yea7utIlRt!~ICaByi}AkMg_w-W^%Cmm_EnbAE4Rt!ep3295{x+i&}h*u_?G zwk^izL269McYj+6U*`j*-oVy8LIDkt7SbPobB4aZ+)zyqdf<oJMu)c{E$_j<>CRrgJF)Iad)Ad^TV;YG&r_KT}=}P3P_3~oP>-Y%?Ooi$>wKOl(a@<{h7@9k_N0R4|Wmu}&t=zU-2kdM1LboHV>;OSB{g_W{2d1x0HUvRKoV%S$Nm zt-3bIJ0nAsmhLH2sr+kr?I@%NE+&CC8ebFmTxuwXV=2$Mn|FA0e(gtVo)5FfXI7hj zzRyHftPAmf=n~W{#t)U=dx=c4y8r=?b;Z+B9ZBMEa|O8>C(&1+%~_HK*#2_rYid%-~Z z9gs`|o}xPJX`N;H_(PkUGjpiw7eY?mJKti=37jKt^hvd=i~57q9mN)tJMB}nEB59O zR95esyx~udrGb?$V@<>BVUBt7_JFmOww`ws*)uei>xd{KkSH0YwuGNMJdZ-vdK+Ss zh9lYw9iG6FYnwyjtgNS`OJ&U%!KWKZ?Jrv~>GHXVEX&Jz7 zlI7j`=;iAiqt!U;8MrY^=(%B*nap_8g+0>0k;inEsT^5Wau>rs;^i#mRbaXO(Q<7yjGO#Fn`UA*MVjo{-C zOZ?SZNPV{D0gc4w?VQ8+@pYrWDax?A&A+O@sSI({3gpAlHAlbt=Y_fqG`qxn zlwI7{KoW1z~%{tl$#7|dNP zvAmkm>2zQgyyZ*ur;XU_xegd{T{k-9?m1;kV82F3D2IVwMy6sw#(;yPp08EVEV$(E^IOW?46unxuFS#eY<@G?d z+?dxB{^oX%U7Tv&861q9K3sr$K01rW(pHLN(URiP96Ubn^DlviYM>6sTd9ZsCo%2Hsk7HTFE#E9y{8h*ZlP#7q=~C# zus{_~pxO)p4Q|!P@tpK5&Y~fFHbo;N*X5Pw!{&FjJ%4G&AI>?6<`!8@t75n#sG~FS z;mJSxmKm?V+1MM?3^0bNcesUjolGG0Hgoo zGf?c1P|Py%7R3it00h>g!;=?p&~vTdJzhk)EmabK{YaL}uA0)G=x=i4B3(J_8;3U7 zCaP3-FaMdYmC4PsePq@<)t^A8b!2g+K9(E)_pzn4kx^~a@i361%Yz}+Pm{da`S*Yy zwKCOBdb!~h_)OuL<^;+VdL#EF3rXqbpU^^z*Y}jwaQ^BRru`yu5lO}_$Rk8$L@4UI z`)ut$y`7v~7>Y6kNL7fF@|&0y%}v6EQ`{(6=Sjc563N^go%o-m_2 zhzj*JD*Cc5FSj(5?yZNd>qY+KrA9tvClkLJoL88PBrKyH_^NINfvE1 zU2}+H^HL_(ty~$bmBcoL)WZHDM?i~~ELB*fO+S&8(XIAmyRAwbSLB?FAJS-NAsN)- zrmkw8A5>_yNKWGk7R)jNyy0s{!NHQElxS=KT4SU62YMTzQ7$~-{+#t`F#8I~_(l6D znFZgYpA@s4+g652Hd}O7rPI4(r^jf-&3={2Ku`r%qthFSo{^PWstuKJo=_S>dsA(6 zYI;TRb1%uLT0^seo4Q5=O`6=K4>@;wv!_6mW}U?N#Y)T;Ew3k^OoXN2qWUz$PK$N1 zMPvoK(F*I@T+<=KPDJ-NJeZdPIGa*^otN+5BO}IS2X{ zs#4@6ck9sAfa`cE+bOA#Hk+)03stiXFH!cC?Y)hz%5d4W$sw=br;<#c_F*`LkX*P^ z-Iw{LicMu*W1OAzaD47UdB5e14WVhEgJv#z^Ujvi*u`v$1sEJ>#EMUI;q`ypiv_DO z(9{|aq}+ec;F;u_#KCOKYI@~)>~Py+fh>V#Qu;=6uCoBib)__7dVbXjH*}kMnbBj= z@Fb0&9c)4qRo%#+-@VA1@?wKU&v*3>#y= z^LqKd*686%s)|&n-IWV_1ASq*OoBXGU7b##bd;1CTGUbXA8TC1wHZst;d`n>u@tm7 zWTi%tkcKn~21o=z;-gUr4<~vS^Zw_JkaGcD5xe0~ij(qcWPBnGE2W)f#&n|46qDbNhY%Al^KaJ%( z(vyb1l2}ow=b_@=9x9=Q4yBy={_Stq|N2RY5>VctTb23*w6|a$Ji&q-tBvEXvkkE3 zYwh?^&pmx|n^ZrutcSx+1^6d~qsZ=t5e6glV$XsX?WihUnTtCHzpJmqno1JdZ6Y{{b}DN>j>06Y{_6Jnh1qdyB_(I6a61cF zn(~aXcHF6=cPkf*8x2L*B2TI%4p}ZXL9zTp9+-H-P&Dcpl4(#4;xaiEgWL>yAjWXAN3yu_LJIE;#$^yz zw_?n$iYOLYXRd{3-=?G0V)QLaijAcvZbK{aL%6;&T1fB-6tKe8cf#w6$B9#6Cz@<>&J6mI)`m>SX&Uy3E*EWKu!N z-?Cu5!b0p`-lIUc%Li{C%+4tGLJ>R#?fGd;;x~kEbzApxW=$xWg>lj^&>sy_`$$`q z1=56l(Y19!Eh~IBFUPo%RQ~4h$2LYzGEtNMCqY`)%WY0o>05!H`}v_PI~QPw)}{8XWtqqO;dxby=8PkJe?5fX z<_Dp5h-R!@Im*H)0W)=ZyXfyjsPm6ZL8&L(+Qgfa3w3sRA->q&pbhScerF$Y2yU zC>jdId#JLNo?J&3#~2j{%6y@-wb9U6Cj1(DW=jN21S2h#JB{;uw)`j(BlB|>h-*a* z(}h?V0D?RCNqdjsiP;WR(wITmRBgO!!gA&*MZvJP?Lid-j!d(y(uLKvva5nD?q>pO zni`&H3LQ;#LPY`TG1reQryy7c1kzvVWFqMH6)%EnB1IM$WJ4dcUk~#F z94SBhY)-J$>cL$pCoANR@QKusN@#7`FoMjEOrdtT2T#}!1oCU7Ccyx|dyJYL->^Y; zexV{O_cWhP_8*eM{ohgw6Gn{b&H^jIsaB>*oXVRW-TUFI0}m0=An9I!hN0f8KN+T& zRDe(TrAiBxKs7VBvJHk-?TlZ#7b>Q3l+}S1I^;bD->hx$>KlK^&HC#SRX_Hjxk2d> zVksnh+RUAIfyHz!yDN^-#rXPV%ek>xbZE1r^6aG!RELAR!uJMQadxF-<=w6xI&RcY z{-;W|jcl*!VcD&UeZU`0Jv}5J^$fGV2|<*one;%D+f0U6etUja^pu z`}nb~Gk(-Bt>69ROtXd*fCr2^pLSVBBB79CrP{lUW*MG_WuHsf!ZwS};+k-1CyDnu z5{IIPdR)M$(uRHy`>gRoW%rXmw9*q>#4c-xZdJNf7i!q=PBNXkff8BHZJTXza*H0sm)>l7N@QwGxMx0%XkT4T!Q*d|j0N0vb?QPo;&e>{Q; zxEAencA^<%WLC}X__FYyMfsN84*^4E(jF_Kq`(=lO`$!uVjX+TW3wvUX{@4pj0lSu zQ;MQ9+GFM1RlW5lHiy;RHVf`cE!)jUoB}tLxyALIuUrR<2sGniL~s&^~6^ragc!xAYHh>!87a9&!CaQ8HF$p} zpm|@??Av%Zj|LAGA}HqQj0gAbcb^Rgd6GVV=k`FapF10{dTzi01!LMB++YX+t%6fl}xi3v>Emz=R>ah)eAS&_Wse7yp{qvyUw3B>*VR zqp<+gzX1}KELdjhWWdtOgXCM9bF0okdb2%30c>BOl2iTkOXpwEKMJbW$9?j1fh`2~ z-1nfyGWopfGZ4m^K9Vt-+fN?ApLKUuu7d^YPo2zQWfeB5G$@6Qhbm;aC1@{Nrq|yZ z>oDc+$V^13Nw+-1o$kek*8k8VMc3AWX4Wo%zWR&QN#aJ<|HXkdVqo;`?7u2F z3eAU2_;_9M)GM%MF|j$TKIM@^MAy zMA92NM|r;XXAR8@_pkI%{}B4VF*?w@s=zt^%6(bSay<}T^c}4pyWj|%ml5V7C=rr*V7y7zn&~5>Z zybpal-x}X~u_jCb19sHb3c7W{bF-`_=XpGW$fk`7m%WpUn&1jAK}-PGEyM8}Q-`Kk zq-mCkdCTMpoSxllDA2hZOK#a6=gmatJqF%WE}@9@xAhog`?Q-nE=BI@aGheqwe2Tb zR5x(z;$cBrUv-(ikgvUr|a7Rl<(0Qpa%Rz-W&q z8kY|jwjKSH*76hi7V8s`QY^+PICRm?d-nG~+}8{*fi`bIwE8d_IB);1}UwJ+0L(*ZAJlT|L_50F|gI$@{DQVSEoK_;mN}FcXL)n zNJWHa0f%3cHVbFM?64?Rlzw9qRa;1ZttIR!{pvRa$8hH?zAZ5@xe;YWsxf0Rj%=cu z_ude+u4pW&i@a&;HZ>#hdA?!@{@Q_p){X==DJ+?42!8*vx3$xL?CENH^SJ`KGkgNh z6%Bl)0V^;3pLr6V%sf8Wramfr;35SrN8qwRK1)sgjosDMlA5=tg-3q#&4EC0^Ir@bTJ3A$6i!uCw~V#|CcrQdo81ZEC|5% z#xErVJefKLvFEiv_e_mkyzY1lg3dQr&_S5Rph{$4@AsW`mjghaeeDyVMH0l{;Fv0> zX}){v!)-l$t6mJUCizl(?77+m$+NfQ{-f#SP61>V+i$vD!PRSh`e$DNUL6C^H_+Mf zLle-R4pa-;#Qu`LY5wHW=!JU(U5>4T++{%*{O;nQQV=MmRiFs8nL z%k=;^K!w_O-z%5#iKo9F#O#9Tydgc!DW4H})+|&Vrc~6M#Z7RHVE2*FnQu&ZErk^K z!IUqcc<+hD?41YlYs=^HXrBODi(Y!d@in5pUwjar@{fF8{PqH&{S&y8ndSGMrq3Rr zbH^hfM+)@(7}v%p^N$g1o(6e~zW`jJ;~P9(+A$Eiwco=&`G5C*$2kRjvhq#O2^Y1yoY!&`rVPoBL2+*`%}S4es1LwI1ZYR^-vzK=$|v2W?>*CB z3`<~PYSkOolc2_bgrqPC3$3mcka)g2ybE#ZW=~w7_uM&2FQ_S8vO#|xbZVIOpa@? zz#$h*FcT^s)arOTkHV0G*8-~+QM2A>keYCOV^8llpz@}+TrNA2M+4eb_1NRyR^QnF zNf45y$q}&5JbP4Js3KX<6hjPr%yQFC5WlM9T3%*-oRyYELc65W0p+QI z1l#=>rod8PKwF!B?a!bt@ke`nP$q)U%Ok@*$f^v0bzdAlJO?9MRpKFF{5eyq4RXE$ zdEcDw^!c;lzD}PK?t}3A^}DY1x!(c6&+;sgv$eG>sHGCboi1ijNteR>{~@*Fx@`x` zkPDBT*CW(>BUFg@*=;ZsDXg!j)5kR0O$*p)7 zIT0L|3$)sG%&f0bmqOj`NKgnZ=3zBTX znZu#@k5{!#CfK6^Kfa>MaBe*5e@$Z_B!Ur!yrpmY3~}Q?)9b8h?}t638TDYANp#ZJ zn&{m44qHG&d0O8EsH-^=0PF6?p(0$3ZN1lS6Pb$3H#bmdD>$IV^1TkM207<>Dwfa4 z$yILY;fqe5ar=hCWBJ6Q;#Y7DOLeDLMvN$;;^46WQq0)2tweuVQKeDHS^xGq-?%Lo zLpu(24J7&<*BchFm~k((fz_PK`{g0QL>%Ey2pt3-6YN{++mOCirpr*c(3qYkoOk^C z{KpUR{0B|uGjCO+SzBNP*o%d1w!agUtD1TENnDWcSEPkvV-Y)xHnN82c_YzI+}tvl z8^$0ZKR0!pCD_)nVRB~6bOV{VQvx41Hk(o_USB?B*zbAupuT+XW0(UfsI?MrVzYil zKa5z*BYPaCK2aljTjV1Bizoez1D9MXDRWFXqPwkwU3Hp9^cf>M#j9`s0!Zt_|Wx zu1tKLC+e6upL*GfITN%59H9`#&-H4~imk2DmX3sVd`^5hi2c-NKHplaJSYa%;&quj zg(Py=_2f~iA;3#P;mfr;Lri!5oE4iB$5RDXv1Ch-v>&)0)T!T^Xge#@0ER_v*ttr8 znKgJTT0I@S`6Dz6uw?^fB%kX--Ep91u4Iqi-Ii|m-JOm}-md#ujORy(q6fmuo@RWS z6W1-^Lr#a)>Wcaap}fX=8MLd~>UH-b_BGR3X7{!7&&sj#oabE*!Unz`e19h0n9=N| zpTBgD-)o9`fyT`-Jxgv}cq^Wv=g2zAhNL}obbwV)>l6$;Omr=M2z1BdKaosm+^40| z085)dudm&`BgZXs%+_=0oC^weJ&ss52eW>4q{{g>RPpk0_<#KR*>R~8375ym{qE7& z-o`-?5@;PT^}LGV^H5&k!jl#pl@-Fz&z#N!F-B}~z>yx#7S_4D#wz83C+dEiRQ3tUvG_|TAYQ@0W&xz))p+8(4brss={J%8?WEFfqIcvO}nMI_Qn%_ zvN@-Kt-D4rpceX=e}*>%|5xlq1p)R2<8x(s{mgviS%fvHI+ztH!Y=r`sB-Rflzt#t zbjYHT6TrD{pm=wU^WEi5JJyI`k+veoY;irevlnGJe3AEAL3cNfpbrwL(9H3_LQzB+Cj%k`wpD}Bju9vOsoXq7+hi8!xpBu* zqx{G0)6@G;PNFq=pZn**luZ2cC+l0d{#!6Cy5hGWj+UC;RbO{P+Yf2rR@w8S+Yd)8 zUnA$?4ubkkRTcG7BrE$!=4};-lU_(%yi<;IaQyJ>lTWEx(l8AI|3gMP!}N!Jgl&by z;vOCgX$K6z>W1+&GI|c(dE1Xz!wTk6L)bNfzU3mbz}AUlAnGkawTY+%S+^(C@b_Wz zzQyq7X;Iq99I*@R4FbT~@7d40Bfl1JyXR7q#QrvEbI8}^wqK>`062pHy{3>Q z>_}D@yq_`LeXKp8+Q&zGKajn>J)iIKJAg-X1h6$u=(Cj#5q}S62_6A^=tfNVhvAP0 z&P)vt4vHtigWmltg_E%0msCA z6d_+V7Xo71oeu#fYT!dFR_x)nDKJ*Z_d^@?p`_cOZDY74_&vLce z%ro^O7DS__k7NNd-pYu4S1y>j<<8BOkj%6z2Q@Y|!MwJly&_c673lr`c57_z4tSSP zBkQGAB*Xk9k-sBw125Gu}L$NW4sq=h|u`PUr zuuEA}e$!JeL~Ij{cxi~MB6beCX%92MSk$>*Jv1PdgBTU#+}qSMGIFV(()A*;0Ix=6 zUa-GJRKfExMyf6fxRTd{M1S_JQ4bzI-p}}f4EeAy-6d5PZ%h8RQieOlwd*w9n!@AB z#eiJMw;nKeUWWx3;lAqkGm72kTBpYkgcgZ>T)qpxZl3l@cs|>~F4dFfSye3Zf8PKk z{AY36JOA0bH2am|rnch3sf9W%Ct2}8l;us^*DWO}Z0-bWqUcY-Sm?spNWS9tje#`- zcnwc~k(g$Ok*9oAs;Z?>tv?+M(KgV2G7*dotk^eH-;3AeVZRFECdsKEDeU9ZH_BgVH1+Z?3&zEH0*N2X* zq!p=wROJH*HCxk`AR(4)2AL9?M$dotLu4X{^F2IiQVxW zw((Aj5J<&lEsu;bkof4W_e2dQbE@mva})HgNC|ZeI!Mr0hp|9FICGq_t+2& z_zZ+Xk`HJgFRjMM^AB6PAxBTQk1xt(v6f#9dBL30R$G%+Mkr96qqgSWMjc$7(9j={ zXZIU+EIuyA1}SiSso~?$HSN}jUc0P}z}-)nxahRaF1?V}Fi(74M{&I7I6?FQf2=S+ z_?$v+V(t94R|*P}1hu84S3ZWQ*&A+XrhXmrmAhw)|Lve>B5ESr!}ICWL@w&m=YWQ` zf-QGY8w>h{871g$qTo)wvH(9VCfE;5KM5&BnW{V?p`Px2;uwG6oGD+6+v)OY8Mrr? zTr(jsA%>>_r}~YJM2VN7GtD?tng0Br8wVF!akAZ~VL;4beV0CYvaHOgum1*N*uhU9`Q0W|t1%cbxi2Xe+*}ME*o@i*z09 z!6Ij}0V_O5`EvObOe^9}5vcpY;>v|>f}&XIZB;AubJl#xq%xnFL|I&2`Z3Y;kOz3f z_1d`wI{vU5t0sF~XmMKx7x3*oo0Qy|aJBy4*b;ek;S$GJZMIBwfk6htCe#T%UDpQr z!uNeX+sWV73mNTj>I2T}ZPp5F2ueW6F6M!|PNn;KEhzpCg?zxHM1?GjZmIeY#kTur z=_9J^DU}+n*SS+WBdQ#HtPBCbDR?Z1PVntqTArj9BqALnBrGCuR_`+KK$TW%omr}) zkWo7+e4bEJSp5eRQVjf0&2!cSxxxi_#NyMeyzaYlsZjp&xbOq-?vT~FCe0G{^lEtxSGK_%2#& z$a>uP|Li}CPH>K}xSC8Cmr8~0tM0U3hlp(;Uq7M#OI(>It6%lIis%9Z<9L_FQsAF9YEyJTKxo@7u+Y)h(Vs} z8&*Ueyd(d*<&n7O?7j&6mKR;*l8bKWl`yJKVZOTv=DNU~e5IK=nC360nb2tPV|e`z zPb@D&s`9pb!0}iU>JHtqzgC{)tm(z=!wzq28IU>wvN#5~JIgZcA!!hTTAGd)0jt|y zpFX;v3-*T|PgIMK#P}Uc?7A$PXIBLBuJGc^!&ghZ?yhpDyx)r2BYO6_u=t6ZRkd;U zQJ~7to^`{T^KacQz?*-zVfmG}o*Pl1{KM;t$miAxh!z$Um}fR*@%kKk2Do^`nC<)t zy7T$h_o+VMU%n%}c#Ts%;Ex{t5ffoajQz*A{7!&30bx6h8kchX(qx%)f_G5cA?wteC@kyetELQ;T#f69I=QpwLxLrTR2i5DPhpj)c zk#iVJrj|a9_^JgOO3eFem`{yMT;n@sjw-9wOEV@a(7Dx1eVk8jcO?74o~UlN7K;hdP%~%nMt}R zY90h1LLJ_8duEEHA}N38hBa_h)I-uk#e>?0{cs6A?JOFn6))k znfQ0t`d^|i_}pt9%~|OqJUd5&@t`WpdL)G9FKbnz{%C+q3GWtJFeaJ?eyYmZ z>PV+@4aYXxbBJA~Wl=(}Cp3g|Cx7$#>ep^%Xr3Ki2*qYyXdH$p5KdYE{n98aKBZ=Ml41F}O>}_#DGG zx>iDx2-?+z2X~D2f`q9uDc|d7(d3Nh=CW*0kFy|uB@NR1$z_8E)ChEyqpRL;3AK&u z76KmTefVV@P2BwIP->s!YN~WN8D~kV z%=XIsubW?$;}PVmfaEtSjPVlt23aP$MF~qY$O%H``Jr5zO2}J`sr>!l*%qZujbw5& zb)<^@_;t2AKSfS)4Vr2t0Gr6%F>njeuL7U3?jF4};k9M*B6GiR^oHlx3k=Mn70=V) zPcZywI8IIR6Ak{IT4qK~mzX@1U+DK^*Tqv4RBj-(0-YO3E_bm*?Wa%BLkU0gsM5#MRZNO1eELeCAeGoopA?1@(Rxjp%&hRb5YRu;C%~KH0nHC!oVumT+ zg1O^g(e8%EU3yN%8P4_sBkV_1VuH6RgBVPxzj%ffZ^Zs`hI#0MT)XDWta;E33vYs) zAR!|PZ@R0(H>k|kf)u@bEv_84Om@RmWv#5!+g-`2#Mj|p-uQvUxGq9gujwaN zbEvnIje1MWli0!AWr`$Y8YjlP+oODq&0FX;Jr1t@yonukYTPGZ;QaFpA+xf$?`6KL zr`7~Di77@ZPqMfC&*Cr}fAjNsopUhQ1)Q6n3QzJA7 zx-9&f8sNBH?+^xPEMPpe#BSFo#c8Dw%$J(71$^yyQEm20zhbBv?sOg{6ydH%f~x~Y zjkb(>;Vi3QYs{l*swWK=rt9X@jCI|oC?@x{(F`@#xy!YOLk7<~)Av=T|2nS(A?YWt zr({YqcFK9eq zIh1ivIQYc)^3i%p=RcMvounDjT5eIs$r+mj(H4_!%@izU+478ca?p!G9Hz+4`QLzT z^@m!<;yrrAT5SIowq07h%F$|)K-%>>$%~u3E58RZujQ@Vz||-~x3M5BckQll<2p#y zGDm7%g2Ecn)qek9J>R2EY${%|&3)@NU$lc8y`bAg&w&x?d6hf1QYXY)VISzqNFxx`TWxo0|Cw<>7^nxwXZM91?8L zXPwgWhu_blFyu*%Bjwd_>2Q*y5U3}898EzmoO6ILx*Fg*2OZk@;w{5hX@zPumsX5y z-G-X0OuXcAaM`e-X(Zkzvr-GV?7oIW_FE7YWvbdJnayQYBX-KiX{L$Pkh7#nS%8b3 zr3Ful2-D)81;VtPvW%bD66E2 z0z6BD+FM2Pm3V(e$wXs&`202LP+9dmSTIC9u4Lj)Fm&<+OR9>OjPXK_AiOi3PtIUB z^0mj(x$(k7CX`c^2~8zkT{3!G-}H}q!PtY3ht(4;NLVCak;YoZ9{c!;?c{8drlH!Q zFbG{rxICy?rlY25wOp~(8V&j#Su(sU>eza;7P@VJbXVzF0_u{aQ)o|XfVzH2mt3Kx zEUpv*i{?%{AS<31#3IS}P?^lYtE60EH&)pwf`(9GBFi>O3X+L41S0Z;g((0eOv@Bc z6gAZn`%)|a4d#@<1NIbX+!xK)L3|Nh=4_sG>GgBLE!9~R!sb6G>4n%|nDQL=g^jYPh(l3CHN3_kHhohV;g zBKV|ovu%z+se%;Np(r)*6mR_B1gV%~%!??_hfb@` z?gQ~gf4P6WfAZp&{kLxqUZ1?)|M}oG+hZMV<#y01F6TuM$ChWZ7J3TBRcq38<7X%4 zlgBk(;Hx)$(-XK^8=e_y$rGWlXZUVj_)=}-dVL>ZWu9-U{3iW18u1rF5=J4^e1)2P zMEK>2@T7k;#}wP-{9#Z8FGLmggPLsO`hEolsHoH#jKq>bR*ZU1T5?gR5IP)ykurBi zmewB?$LkPex17Hrq)Q*ouzQQ&RPA5d8^-UZbT##%kD>?WqMGx4dfP8t1dG5p7yXan zA71{-yFFzX=C=U|>AB$rgtgq#tAotkU8J+xV7SAQkh%%KsQ&d0`Ly{lM&BM4x?t>B zxGHKtv??>}4L$>%e;p_J{D!7%lim9!86^Kgk1HMpU%UT*@#I1NJWoa@lx$YPQJ zqUvAY2~yab^q_`zRS49me$Y?@i;CY5)FAFYW7V|?;+#CiPUbTZ5MD-^xW`_lS=B*u z+HON~R+0PFEH%7RJna`2jra$Sp5mryt3uY zOZ%(vuot5Zt`CdE9$r>bPz@$vHRTc=enuY{BSVU}C*n=}o&de-xV>q!Gx7a!#Jg=$ z*;V!1y?Kcp4*3S36DWm~DPcXw+mHjc6zjF;h$EswkqOKJ2uut?e~>_q@v!?)bg#;* zz(c?ma|GaF{jXA)Ahv7o?iYCYVCQSxn|zg2&!%N|hMlX83BcD3BQ{2lLqo(w;@_g; zES(nwE=nMwM0H!yaW&F#I&;Erc@lc))_MY(RahYCrATKY{6nkfAi%ndKZ*Ma!-rJL zd-0h~0|)^E4~SFHQK1XP=_LywWCUudpdVq%B5*#M&%mn+hR7jO2wX){geZ%O0ez!+(4UglGDl3mEO7K`5GA8{9`d}a3Ly1b;mEvt^5|K4 zH?UrMTM9r_@gV_`p-7cz+G{A$C5z?Hv$IK}(Hkbf_D)wE46gwQOHE=&wnUMWfF#&xlbg zUvk32Rv(DVIz=?)>AdK)iGbCYPgu7GdGtRJGXZ?F1FRc>z+~bq^>1o}mC3CEW}0zs z*Ow~-z?gS)9`jLg-BnQAb8jLRcO@3?V2ry%fApSUSijh2-gcUf`pCDB9ekdF-AooI zfWIaDLcd+S6xmyL%9t1MRMmw2w`p<2laQ?Re3b`-mQq9;0CfUdM>5fj)yg_R9cce0xE@D((cL;HGC*bB7ocT%wVf$Doy zfI{qAvA*HfQU?h0cC2f4sj4YAFQ-oGpJPpcx+a}N4P@;CJ32VYN}RV>>RM?RRUk8c zi_0`*>2wzJf_JW5Hu#1Rfq-SlUtzA6->&~!ZprpjU$C0Ww9azi*V>Ez@ z3#OV8Jc#91SvTiJQ?2==v1Dl~hep3P-Gle&gD`B`(~7GSBxsKfX(hmowJ`}Q5-YpfEj>}+Hl+O+C+^LoBV+XVv0%>tdE8y_<0I zAUX>ewA4+Rc^X6myVyb>qOEg|^%YoHJ_b$$Fwa3I!+FNii$p90O_xTzH}62uImgIz z&W>LGmkdhh>>@}CvV+%)quw@4ly)H1n&<`aV{s1V$ zey|^rKgj(~!CTaT?Yxld7e@9l0v#_V(undBGxI>?7@#qxp35rWFuMLF7~ zK3K-bpcTP2$aRa&xd|S^D3r+=#2v%Wc_xxH$fOR!+<%c~pZj>w21;OyB8z~9v5bfF zB!ndx`4)13XaeJ!;7QuYf=2^8n^If6?)b1;Fenlm+O|hen+7QxZ~fy0OS^$xPrk?fB!-* zbOCJn?_cbtE`G1Ckwb&j*)eSeXu?<;t7vrn7ErA%F0{qCMosp|60uz{>iYkhVR|>E z<3>boILUiO94HA`E$mG#2ES7#U=NkljkMb$A`E6QF!SN4a>r;%TZ3oOa^PDzo5h#uLobmk5P}cm-FK zbRqrC#hO)m->GYg*%>2}228_;WGB)WB$5ZG=jh(ZHS$H}odDpMWUZuvS)-NYQ;AbO*p9NoBQ>?kGM zWJ6_wYwaDAyKHqfSZX4&KwtjQVOW6Y!GBiJ|KNprB+-;W%`I`jyUMavsEy$tl1u&&6>#c z&`rE%VYsFkcN($!+jkx?*96SN0aH6@v~iwZk+C-oEimhkJWXDd*!91T-n|_N3XR5> zkS<+*a`FQi>j7|dwUM;O-l7%%0t&4;RN3)k2cp}-TYR3Vzi|Y4N2psiFv3|BM1%Qcp~h57uEErrjgxHa-1AL3I{nc{P?xmFejp0|jlBL(zhg)}+oATWeOY zz7-)A7@u=;dSiukUZKA~JbVxYCR0`v)LWwPAjzNZlpW|RBAI~-gYvLm5!W)MBU+?j zUuX{QKuyFocAD|vGZir-NvGA=$S;-|9H?=pS@@w}EsdiSMDE*$@=nAaN6ExR48|Wg}OQyG8U#AZ_aZFvj0kBWyzaF&v`mkHA&Id&Tz z`_k#cZbN$2_fje&O1f?I`v3QT_k%{if*KNpeLFA`wYqw{53Wx=#L26=M(jE$Yig;A zVLRfjFAIn5U?zZ#@)S3WO2;pG5kzsmW54+2LD{v`mMc?u9gaJN7v0ntREU^YwESk_M()SKC^Dc=%U9BKS?PBM zC*PLZfy&IpVnK@*yuVpUQQK(;r7E9}i%;}J=SF}e9vrf;ce^|`w}gQd5L5)8rNr0j zl259*cgm{KP%vWQ8`c~7PrOcF$hO^{SCQkR8z*XC^s@t6zk}3j=9X%b6p*=g1suwF zB|ALW7eY-3?S3cFY)TJLz*JL(+Mre5t#oWOS+c=Drqr(foTf1k5>LFfH?Llcz--d9 zhR{pbhN$p3=PQnFCN-{${r0hnJ2s9`i*oNVUr@TWLsu(y$Gh`*lGQkX$@ewk2G%b5 zg(?kwxAF7TQpvs{LO-N~_(j@3#?;#`hjEiJLSS#B1-vhI4V z(RHQSdr|*+6h}NMhRRH}QW4nt`p;Wi+naX%*XQf{@3&k3SsL!~utniLJpT{!*pEhM zd>d6fUxk$euOJ(izW+i03<|K_0Y-#bLN(vmP;q^dvPiaKYsO)*7V@ zH$G0Ip>E zMwkBy!Vt+ih~I0+qyblkFoiqpW2ZVZa0S6c-=F#qdna^O5G`9BX= zxB=?<|K{eiEsOte!>zF9|F_8hzci6I9{ifX|GM-qihOTCRLh3EuiCPLCi;K=#HRo4 zO=!QS|NB7y^ue!L!C#F2!8u?9%ADN7{lE46`QyiH`oACauO0lF=>NLgoUS=F{hn{J;Hpt^c`|KhadsKPL5f#e-s=@z+sau!YEh(HRVegTWBK4i8^c`XSKA zcXl5%sgDqApzZ1!q#%xOQ+xDnX{;)$q_tM?h5N=fpkI2llc!L((l#qDE$GR73Oc5F zF3Qv8(F~MJRMT3J5^4LX9tqe) z=x*UxX-VT963R{ys2zx8Z$+t&X)ee&%2n*MK*{*}KnGY{VM@-l;w(Orsk zmy`&Cq^UdlyNq1o4 z4wQZ2zHOER<09Ffw&JG5>>as-wi8qt$qWEAO(VDanO{oZLv$7$#Q0!?3dHSCuj<|= zGddZyJscY0*fzd<{M#I^diS@f!~c(XoTC#vk(0n;znUmk%dV900PYV}y= zRRJrQ&EiW6g|N)Xr(REhQ{9IU9kbYdP%5eqL^yY;p#IM+9ew5)ict(@tanFiZz-)S z#r9C@u*;U3<{I;i!_U8Ekg`3~dfA`!5hU{zZj+fv|6cWk5WzHooeqhHo!tlGl0Q1$ zKR!75aPa=!hvSpOx5oz`{GvI89HZn7`zp zjXT7=-10*fBw;!|LaF6d06V{QwwQ59(5@n2KXAZ|UxFlzc}8#LbCO;pfcFrT08H{t zFmpzYCP|v{P;Yj`3%6MrPD#&&$-<&!a;UeXfETLGlf$TLHua5Qt;gvlmqRuyeA)Q&l_ZJiL*P}RyV0~<>}aNJS1*MC42OZF49gF8F{ z6}X1AU6SjWY0vQ-x=k5qRN)fL=As25O?dGph!FW@7Qgn9NT>7svWIC}vGt=zkOtZH@P>DoT5!Q;YHc_>sKec?SxAj%Rfgm402ajACWB+j)ve-Wmh{NHNuhBka;uD z896-4W5hMW#vSh2q~H=cHu}Ll2Kg|;N-Qq~6wD34cUild{C))l#~kWa zhW1eX){b7(zZisNDs-%XMLNtteV0h`^Z+1MmsjL!=t*9W z@3I$IBLyb=`Lgctk^NR@p!iA=jiZrJxB!2VE#N;~3-BcKIaCAM&9&6s<-5*T=tZzP zs8=9*YuUccxHL4ILi9*@U^Q7z)tF5c>YUF(;nFXHfpNq-;sEDlEEQBY5RBAXPhow_OeYH%;V z&3UQx=_(b`&EK!KMoRs4*Us1+rcn6v3?6OKc*p*_H7F}2o(1Qe1<55tg&7lRxi5KP zciA4WsOaRP$|zW2#ndbc?Us)Rpp{}tikYCZNND(>VC7jfI#d0M6jZjJ!eg59?(0`p z?N?!st#yNXfz%<~8)-wsmb$!V?lZb32O1=%){^RZ5fvKZYezU*9$(J*$yQ%FUmu1i zTP@I5Go%riTMb+d+lN{#!4y=qUlzBYWzIbT1%^(wMqkP{N*PBbR?E^0RB#G!l;8Jd z9bGUe3fA&_3oiBrFEwC~$vDNF`2uje9&zKaEL|b$u^n@;?*tj@fDglNZ?{2C8I>YR zm(-SMtTC%XB)VUuJFCii-9%dYG?Cj& zsGa3M+)0j5s=(x6@~<^x%NA^pRsYy^>YzGj;8N7@fB#}#e2WeFQ=Lg=@w=&H*2UO* zeUPCagJfvNVaxHbjgAVpmNLsB&}uD`LJz|_(@&}LI}E9eaGH`u+k1~JSD_OJtwkd) zVXdLe6-TxeV{7m!e{dmGt5~cm)!F6_8+pC9Lw*63vACm&)C@#uPmu##dNmb|P$i-< zqLCcCk)1Zi$et+?D3}-N(Pc8~0rN1vq_NUrA7c50FLsI>Z{tO;*RzbXweQor!}#1@ zQmKXz7DK0K@I{bUjn&F*lQeA;kWeseaI{7`)0gvwiE`+O=sRk4+HZ!54Fx!3k)CwJ zM~>FJ>&_o>HFu+ z_9}p#&Wm?r*bu_nSpTAx|KEfc2Y*Ib&zySnIQo39HTLj4Q#)dwuHN0}2dg#lW~Od< zc9wPRUpTi?wUrn&*UkH|$8wLY9LRfE+Q_*Lp?0PtnOoPi)epIA0+KSV} zD`~WM$M>3v+yTZ>62$SP?-ks}W&W19i@TJ8Tzva;_t7^Ew{NVYmc6>kg!G-w+2J8fpUC_EDWS=ovuHj2*lismR@{77x zb;(XN%k1~YiLFg7$%fr<8Uws$k64C%sel^yW$Bw*l@qRK>-yLlUsa(|xP7b|rmS^e zgoFG}sk@$xaLpa;xfY48XHJ5jCHl4>sG>bXQBlv5O z@N}NDpeXosR^;$Vh7dk@WXoExX%b&5&qDk?ofqKLf_#iKc&&n7fHwNK6vx^mV2{s&0xJI^2)ixZ2XO0P?I>p}5f4uwr=H;tMXwqg#dR$&GUX|l_2cHJ zT~aFrE<px$>KSN&dQ6V;k`XS^XED{=^C~>@^ z${pR%D+mBD^kSnRL$5%D@i>TL9=5)g+qyQJkLwmSFa5}OcdC8gnmWB}(_b$kN3~He z)A)>Yh|NIN_u0IVCEV)?TjJCywHWJFO&cu?FMX$4gToVr3J_XK`@pe;?=#$_GNT^nV-jxxGUnrBiaEz6n~1JZiJrk#Du5 z0bV%EtyYL8gCu>at&Y?xTOPWdSJ+d%9OUNMl3$ci+ul`u3_x9kdJE?KT|v&>7tM|f z`NXs%B;5PTN1oRqvN*F9n*8BAI@cfn?p`+4S~`w@Qkk*HgpZ7kBM_ z>#0(DBkwr=$xbx@QIS{Nub6~fAKv}m_g0;j;Q~92!3AHxvZtGyo6blwIAF&YL4fJQ z%wDCpwCg@?wsVuqlaUq`g6-029DtEq8OLP8D&=zDw;TX$8@!WI$8wESqgWV^qNhxM zHb|3hN5+H*(`l!#r}Rm2xEFc@!OQB#%e4d=#Av#wapPE`Qpi#Lh%f9$&Eq07s2TRy z`rbSu=0nGvouNJ0TYgcplwQM0h>Q=;F4c#2_hm-GatG8f(si+UoUhkcvE@~>ti2zI^a}gLKJF4hyhdz>cwbQ#T)Cl!&{<9~$s0`U0?87gREE;pFn*3{;nRdgiP((# zWUr_;g6L%`Vt+nIGZE$~qpCzi6Y9RO=ot@1s`dcGaX7fi$>411CX?zuFft%oW-*~M zcs^8Mc278$eUxoc4MR0GQOWLsjyLKb1Mw)QM$u484PlM_=n<<%)sN2j(LKO&a?D5B zYm;-^PPT1i+17Py81t>~)PuPi)+SQgSIJ4M1sbZgg?A2pDRN5n@0w`?U5;%%cdIpCNi^_njBpFsb-EdQuQPuN!y&$195%Mo>8tn-=ao>D$^B#t_GA6cuoS<_d7)0T zraVdeBP-KN$hH0leKUFAxSnHX|IW7jv2J|@B)ZttpsJx$5n8=?Ye`qPE$TY*UoH#7 z57FMAUq>CthWg*nH=o<`-;*cLw$}3BZTuPjDEU&399{&ca9M*f0h*eQpkGRZlV6$~NUG5c}&z%Di%}q}4|y&-V7b+6-bR$OK<1h)H-V|nr7DQr3}^}JcB1)jRK5?Lj3<1O z77<2Udv-?_lU4M2C6-~(KUU*GRoGDEqsoWEQ)h@uGEo8k+UtKmfA-9I|2=v3bWQ)a zSpQr4uvLz0TJ5?q&yGfCd>WLFi84>AEU?}G|Ji%@<~D9+Uv&Sw_NPespLO=J5{aZJ zT2bA)7lRp6jG{P<;yCoKeX3|lmPAveOi>SO)jFU37J7i`Mx*CNJ)JPM%8Qx-FnBil z`D>SM;^KI|01v@`dA={k`HtQ7X?>gR+xNsXm%K@!tppKi!%7r`+l+r%i9xiJzu0`$avh($h=_IK4Xm@w@;2r*S$p zB|G!~)JtqHcbv9P#|0(%D zo=rSRfc`H@MQ-n*weJwrzB^s}&S|eg<$0E+h;V9w5*?cYwGsZwP0R3^@BSbAy&Z<) z|NATr49OxU7LZY+7HskT{c(cxM7#FSv{RvjH=Bw7Rg{l2%+C400QMVDd9z#sDa5}` zZ}RW|N^^G#X#d%gLL2}7uWZ8pi_dQwuZTe{5FeRlErFkUc67YwVlJNSnQsR+nKRe>*0#ozE#qSLyh)vDF8x3#~vIF`Os7gNkc|cfwc=KExwZIIln83dz|7ZXIhwNX_@Vj320Xl&pIwg@rU9dQH&2Yun*>3v+2ME)T!^{=)oUYLYWM+O%wIe19=NzCJs?F*VOWADzuj;U9ijH~+J* zOe=>M-+e#-F6|KWw-TrZWJKw|{7D|IkVw)3VuMpc&XO@T&cTDgK}TFgM+b z=-0RZr0gG@()tWQnf=$$dXN4$HySJb-xJY)B{w5PvkPd_{O$4VWO17&x-Jq)95a?ZR0J*76`NY1VeW9OnWhDerTG%cY9d_rrghBdq@Z2*@4oZ z(z+GKhR@8zn`ajh?;tj4>6L485|R353C+Wg*q@z~Na9PUoNJ{o;$rIg*X93=W(@vG zbD^!P>DLU<$SD8nu!1H0-)4u~f1AyX#)|)YKKd^%_R@Dj=~k-C6w+f32lpmY>L$Ar z^fci$K1WJ@rZp}jr7p7?$$Ct`R+lavc|72&u2>|cu@EPeiKu!1G$zquKl|3HiB~yf4-N10uPX)AKBK{u`}kaQ`=2&Bn_A_xa9$wg;-5dUM{hjZ^I^ z&jf{n8#z$!G$W;&^bRNYvAOjZ!L4tbgx{NyQk%3kh?SWtsq59%e>x^0QTjA;!kqrY zDQ5JaPKwaKIhjrWPTqReM@o&%&yS=MDXNt^(NI*cooS`c;E%CJ<&N$|;*i@BA9D6x zH_X?OQm^wT=_=x?GJhR(r!Ft0*JEDeYoydyTjM-Z>YUd85-IfsYqOQ`hO`k$>|XCW zSiDR=)#J#OPE>Lmt!+fmOrmrn66W_!q|_VSgbS-ufGJ9cX++9JfnE@m5p=TL`!=0! zyP@5?JMB%a-LUTMI3~IQPqR3Q!bv6HLwzJlyY6)>qN2Q@bsU>%IE!LfrI9%^Cy{CA zyr5H5(^u_ZZb3cpKJ6LE`?ils!!(e<$W9_o8laI(Q<@F8qseQh z4}NdnxbMIx;2+zW>^oS|&C<}kXxNI-8dQX-)+%WS72WvW(TeaID%yet6~Dlnx#l%t z^0i7%HLvOPx@paptqDX0Yl4IVe`>Yg(Mx@uwvk-hHrUd+0k)!|*YCX7IIs5gzFUwh zt<)84#cRzM&y~FkrqWJTx&|G)2C!4SF0S9V;$@=LWdbKY$?08kM+XYq#(TX@U*nhD z0oV?@XsMkx2bwN7TvzycSSq_qv*osbD@}B!RG76(3mVX*LBHxQLztMc7W)J5CiI=N zs=%A=h`fQ#>E5hI1xrw+HTULg6c7Ns5O3h+MJ&`iNndH{Mo5lV&O%@vRoe)r5K% z_Qcz1f*xed8T4U)nvj0Nj6o@Ck~0J%CZ&*QXUn@8wX{)t_s*z=Fk@ZydcbTXMsLpS zoxRkML2Zlq=U?sYr8{CvZ^t`3J8xAF?49o@D@U$lyc}@8Tw7+cZlc>{ZPrt22F+Y| z-9+naGN!b9sPA{jLYo)1w;l%r-89d1OXu(@DbwthReRkvAD8=k5}ihCUw4)Ix(kQN zQ|*!2&NxSpB|)PUPHMsATy;A=*Zb^h*y*c5J0e8gJ{&e8vPbO0{%ywLd5^cFl#FGk zW2dj0X4KitI>|%Y2L)aJ99?9L`aPPticY)E6zvUiqVvW>KMF15jKi|PGg~O@!%klv zbYgD}Yt_At$&BIZ(;Zj1`a6Afsn-+21=**Y(NXyD_=xey_c|Q36T?=_ZJ|B4W=((0 zvuGUWt%0Qh-inPi9TVL2i+s$=7jYpU2h8V zNVXw2<5-+m9KTz?>yQh9DZJ%u+)iJe>1oIunDY?bMT{}n>8p3woJZU_R438T-m%9C zO)pZI{JVrOxAaPeP9Lhn>nMkYTY?;)a<**R4GMo5%-Uk}yrqP$dJ9bZsc_2 z?F`lNC)>u(P|c!gdW-G!)$6D&Y_ZW)*hyRd<({c#82LzdifN=lAKS0@)nQ94{k9=K zK8w8PsWbrF)8m3ug_b#T9r2_4@<-o!jc0p*%Xpzx@8f&#@w=2u-*$kdt7>=i*A7=|CXd(P}V{j9x7UMcZWrb9EV+%`$LHt0!>Qzeb@D>$Fm> zcx4xQT>H<%hWQlPe;N$~_QLHy&CV+R_qpvqHg>WOS*Q096_`7{LrY*4^!p=}FLf0Imz0jrATsxOTq zNr#^>ny;;{Qd?cH+H`GmTB*-M>A|TT63l_4ye*=r-!Yzkr%KKR2lgXw@xi;Z*}W* zMAnJ*IQagY*Qm3gR+bypXiZY4H)SpD+`WCe5oy6Ph z=Xop>T3^IvBv)R+OkZz1$l8(iu}@9?wj0^cK}>b=ruDWweN<)VrmsT{%WnGq*@8p7gv zA5JDVBp{6eKqVdQH}mk6`Y7Nh;E0*`t%#^%e7*tRkLCBw>Vsz2%)k$|7qPcaeDzG| z!L`oz%`V$FW@hQ2{KYYE@2FMBKT78Hl;rms{GyY}?#vk)EQumOXRz zIssSXmo}TT5pf=!@%`re5huY+78wA=42W1*YFqxASH|lR?6t5=D-uWMmAPB~TNr9N z069%6g9>-=l31rLHklD6D|M^I8THC|*A?^#`{v)3ebceA0!McdbHBX46+-3UCTToi72W|m_X zQJ-G{X{?J^N0Bqc%*d$bUWz%d%|tT8R#=JNl>aT`!D6mRVSU@sjxkCf-k2+ha>YaN zOM$Y&4k9*AnNeactyjE>|t-M20AJ50tk1aL-upfR8C~scCBB z7n6C9zXu;VvrDs!p6vm@zYAfPCUtiy^><`?7x{q1FIIZgOt7Wo^U=u8)WqEjj} z?NvK=_g_D&QO;SvMj~G1tWC0;ABj3KZjES8r9+S1^NzpAxgZ@8HjgD1g&YL&U-U%n zaWFQctJ)KtM)059_3CFk%TV-7`Zjr_#I3I*cx3TKCswIBhJ^3n%;cMHgH$GB$4nhctqJ>j66k%YIy&p7Q}m}FVa>u3!^7r(fJ&-6H!$Mh7Gj$u%6`iVI%wtt*B`>0 zO@QLS)dS^~d3X_#r)ek|Ja1@@#E~|E1|kF_!VT~hyNNy8C0aRoCQYevpnW!D{vD^f zOJ~|T%A4mNaST}?qljw$q*U{#RM+@ZU9J$?h}X`$N}Z=0Xlgc)*12RAdVbA0Z+LnU z<(jwLu3!fc`pAeK>N{~4;4ix=^eNdpU6EKJXcpbeNFU5jBh>7VeBOAr1aEAboRHsf zNp)N;+U4Rp$c1KVa_#d`g&T8#J#>7Ya}uCxdx>;z>)bT1W#}`!=EqF7-Op@K z^K#>f_u2I_`=Vrek_a0yW=WQ|0b1H5v!!pWjL5*8ox~~ArKJu638_8Dd52iNNbc^G zmM$>1^x>+-rop>$fQP&Inj$sTn%soBS5~Zb^Q2j4!_+{$S%O`Bnam z%WufVY53x6U_^+RH9&4c%A?crgCcu~-7o8X-Iu%}mm@2_4>>O}Ey^dwtinU=7z>XdSc^^izSo+zzr-@b6Jf_3lbkcnS#N?4__c@Ohd>@``>uOrzR?^QIn2;I#(2c$IW-v-eR*O(WT zwZ59g@SEWtIi$i#S)x26RCSK^5!B@fN_{(&!x<07OVD-7$? z{v&up2^M#Gzq2PC-wS50yEfA(nvyO1o688U3(G$iQf8FNvVpZWn7<0wl@J#fA*-t` zuNw+dl>*~*Bg8AOwi~1medn_=hvNI&t9kGqLLfkYz_wD!GNNZo!aRgec zY_i{Mfvrf`##F|mK=o3JdFz?J(c{mUEZc)V?y(i-Q$BmDkJI0n`l5aj-{wa$JPpg*uUegsCJW1%&@bpI*v< zjvw_hGM@TQSdYpkRM@F*bR~)T@e;kHoigyMH;v9ga$i66Gop7u=EbKn1>`c$2ZXcb zYxb>o`D^Z6?BA7o1P1zeL!rPwg!#_*S}KQfgacPnvM&KrWl!Jn)PDkdur}eD|R-Aj>rm-`Cou zQ;@DXPL@=8fhq}Z)&`VuBM7sk!f4K!pWxfCuWLI7O`)1t5kv)APO?UZwC4rpMF&yI zK%ASK{tPN)YHEAT@FJ^6oPjc&&$}mFL*hMzWv(9XOTP|AVUjS}@psBX7VP>F?!- zrt118{NLP9MGEz{Kc5xyTVa1G;Cu)hQ88>dD3ve6Ho;kV2o^@2{5%qjC2F2L!7)eg zAf%rK!=G^IH(YvYW`0Y}ZzsB}N11x^gG!a^k;$MnPP<*pZ+oxps3w!W?i4Q?w`bZ% z{3~qww0^6jD0aI&#(x*i2u005QulnnmSttAsO2_auW;z8^I5f5QZ0;jp4-j0_f_uL z*y*M)DH#_l8Mk!EHczJ{@eip;r7N;MeKA@ypLXG6auOaSP6)|r)Li5i2NSQu20C!r zI^2|GJr2p25-q`?8)5RDNVd|`V-gNC_D>&kVYL=Zmf2CUf(n#E7%=sm!dh zvX@DE9U%Y8BvfEf^Ob~o;_&eoNz|)Z)e?h<_4ZM^z%E}EJjHeoS$HlLh_|EymVh?i7WB-o$c;aG}w+uz8@Fz z@!M?EeObmY@{yegme%3Z(w%DLUyppgt{!YLJ8dR*iP|4Tc2NBI>u)?)SXEA&%CZ<_ zMfBl@;A0R!o0>Qy+hI7tX8hQs2k!vK_tavgw9Npi%D7^>UY}q}ulrkOReYk8wvT1t zNZx|fmif^1%v=$(7Yfkiq8X-d(flkwy1s-UlN7fRri`;M($*_E7eL-L3D=PD@#-cC{Ny_vd7-?5^z47alH_%aw2D~qwBb6xj6Qi{PhdR`)`{6M{}7uEe- zmw4C+9vtGK_f6kRu92ahVhT69c3m$!c^=C6d)dYia*VoT)GN z94?)nv2#Q+jg?YEtE^PCDo=q-Wizn#<}GV-L_E!{ zJFW7&H8})@v@?qVj4btIsaKLhu8&zu;47#ZiFE0-`istYeu`pWn&hwEdkwg!q~7my zx}tSd3TQEvS4`An}mWJa$gjMRlZ2>%*k+ z{=cyyuxr7Rjnb_SB0()}bdBs6uEsP3&TqvuXG+q(889$(&xtmbXz@x>)cf1(@TlUP zV*9yUlsIioZZn3?QtxBZ>jwO=M=d{j z?(;ea0Xq|IQ4hZ9noDKE;x$aR+cvI8mX#?SdCDy2%8aBMd4b`lPZVvW9}uZ&(W%$* zmkgHV(A7_vlo%PbXi-J!kqMaSy5*5n>ihKD?u}!T@T9`Xi_WS|^}_WW_M{iJyypcRB(C2Xp?{m>*eh@+-Q&q(zS81*Vf?2d04tQ@ zn)6!`gi_DZXK^s}UCNiTqv*&t2dhhK(I?@TO@c4Qdv(}{Dmc21h~$n47E9gqXEhqM z3xs(wInzkJn~#e_&6k}AAs2>8A+Bh)Vi!9#-#G{y=3J7QU6Zx0q(jc)$rYgKaf} zVs{aAiNR~E7;xL}Yv(Bv7#hx|tG62~&~q1~@$<`+w$5c;Ki<&Vj3es2F>7j0M5EV*i#W1)Ow<+L@Ka5*&km-17zjCOX;Z!EePWTGMRP>VTw=qrExZ z1b7X`{_T}o%Rz5fE6A-8m)%k#=Z`ZOCeyO=wuW0UB6LG7pfN}4>=^**E6qYZs+drS z3X1IxgNImnnV<}8rL{1J{8ft?FUSh{!;|f@X zJ7YIw-yTy{(d?iKMWqQDo=1XhNvcv+HobYhr2mzPUMsgn#-8uRW_tb?uPX2Ke@dUgBLCT?{n&?u8@S&bMf4L$ zw9C1RcsaIvrSHSus?w`$ZngG=QnUP#cdDdwqgKXMFIFfPWu;;|uWn4{D;`dc9#p@&` zI!&dP&(f?2e-scM$-p?v9o;6x>#UNyPHev$))js|T1zg22f%Zit!#$fKloRUl?Y%5bG!@ZoB8=QdaiD+wa(3&vq4t{=rhppHbZhT*-z~bD$tAgO~ z^u!Rb&aM@`*t$5Mh?7>Qq`1tW-e75Qd3f=LJxcD7Ql89Mmzz!T>Mtl+mqQUbZ-w(b zXHfT1P$WAo%;PFV?#KN64m?fTkRVPQ)xZW(V|6I7z9P~8lF~udUpmwMmuasGF9l^u zVbR7#=2t$q{ci_VzMX44mf2M>W&R3+{g}ve%V_;<_%lN4DrSWUmC0>Hp`IA{&0ciV zusrpp@Sm?HBW}_Hxlu-bMFZ@(6zKS8f>nd05^wP@*z<3wBl!|at;WSI`!L6nfj|3j zskQq-TI9WTu{3y}W@g=~2K<_jgbf+x^cfZ8@$SYjh?R2xq7$o`>}bf%F^b` z<~uhFy)Bp2j&8l^N)NTRbKMr4cBkURn2vDzz_zWkQ8La41x;c3X&wfI&{Pm3+yN(t zeiCd6f!uP30ZP}Un*ra?W$^>tzt1i<>WTH21GQfZko^tOIHj;TOk9i=F0NPE|6I!A z>Y&<&u-eXTc_8MWja#XEl}{Mu)D2Oy79(wq<`Pi`0!N$25nPu+8%X}e5KKU=-FR?2yvmRHdlPx z{vq}N%ff}24COWTTcN8n0og*B77p(l{UjodD?Bk6BOY48mVA$}o+eJ!vsoOQ*Cai?ijjpp%i4$1hv@T^1_qI&l=P{_TE|gv#=viE4Eg zEa6QYEERiFy*-~i>fdJ`Bz4X_q|GibbfGxhg?g$q15u71jd6O1C-bwx`}4=Qw|~pA!25X^)yg8E zKZ70RHn;z88K~;ch?&XXfd2AR>FZ@FDzh2BKg*G@h5Zz#)t6o+SbITB!_(N%)c3TP zMH5X^m6`ax8CtTXM!)P*6Vo;4C01A=? zIA43`x7N^30dpH0Nx%^9ok|4u!4Nl+*aO03F}gV1z1CB40i^c-jA=!hWKWlT}?U3uU>h%Eit99@H#ThCaGRh zz2*-xulV!DpOSs3eFv1A=|(xvMP$gHac%hPbRMdjW7TZ1ZkZGR=A=I-X92hG%Am6s z2V2A^z}&*w6_Wc7j-@)guf2YA2nIy5vU6ukp3LDUu7PT$J|IZR5_-i2_@HAq=&iMS zX%Bu5DYk9I()asEtFt&87@?RO_LHDmQD%cY-zRXDup=FeZ{~m9ib_YGL*I(KR};gZ zu^LuM*9dt#*ht8YO&bicr?}MlfG4y6zk`w^(5m^mOY8_4jrDoe^y!=I2H0nnN(#q| z%}!QgiB57;j^Ux#P4__-t!fVe3qfGrT2m09L5*us)`!P=ji7;v==@0Lb;YWB&iZAA znz$PS&?m#dyo*6Tn}}#{`Xz0>U&_;7c#2s`5G44+456;LMlV7ZBmcuJGrI8v(VUI zt?b#8|Ho>KGC29w=XxOAKrtF>PXg?@&$&+y>^9Vr9q}9DaHnc1pensq4a0_kaw#b~yn86_!b$Kf6RAuW--jLZv-v#sGgC$sLqZkJ@lUh={Nlm8w?4v|?2|Alb3n9) z3?PslP52Jtd5x`nSJ4#R*oSxaw=$<1gjFgxV1`O3lkZ8wA~SP&_1tl|ddbGL*-(oG z8!cGs3?dlSe*}L=5y0)~!E%u|I9Md@U@#T5o1-%kFd#Q__-uTRtMgkrkij&a2JkFiYtnLn5`bJB4DjvxY&DwKtS<-z-P zk{Y4scMbT&zfVA-T;L2(t#c~cQI%g>){8KC~gso4Rz`#OMqj))tT_UzEtZ}iG0uSw6=RYWfXs`|c ze(I0S+<)YmyGyeAciuX`IZDD2P4Ho`>D;!*k4Z9eP^Y=Hu~tf)LOeJIRBxaeryr=T zXE_D6v}n8n79zkwsnC02ADof0m~#2_(jw%>0@mLGX*A2!f|-(x$Kk*1+(lk19jSl# zd#_3`YFMJK3*_R5Rw@XlneG@~vMr_`Ra-Y>clE~;h?1?@yQlynsDI|k=U z$hz|3nm5Zo{g|Aa`BTr|oxO!lPtWL^!>67;=3hnDLrj9ecY{FvVEXi!zJQhT?WgC- z3eL;qM2%9`j=(t)J62+G zBGR8Ofm*f$j2amqw@3}>2$Cjc`B{B%nHZvM>-KdOTI#w_-yF|P|9pBG`qL9mqQC*H z^RA^m9dq9(A{l?=1G3$d`EPVA2m~!?QP!YsCWqHOxHTpvA3iu!0UetWxvv57R>MVs zhi&tkvbyBlvuev>N%LaI+v*wBK;QsRL(Z3Cqg>hj_2e{OLPpE&16ds7Gp<53YpGYFxN_M7#I8M`JY`j z{SdsFU*(RAqHzz}f8lv9oF%@+6T}I7Jc} zU~&rL*$9MKINO*3zTd#WGip1)L4cv!OM)wY<>*mquJ;t-M$Xle7O#*Oul~*G*LP6# z=`^me6c7EdZkRy0F%4*$@uEFn<$`@1vu@=DaOF14qR7$zeNHo&d62TZe0EzC zk$s0&RW_8nOyzjdHfAQq=>D}$38Z_cNGExKJ>u0M>Z#{{qIJ4^E~fLQP-Jq?p#DmpYfZU>;{NU6kZU*-<7}Gt zX>HC0WD=;Rk#__c{iE~T{E@bvcDrl(^3URH7cBh2iEJGvtu!S5@?b@Tkx#9C6ypUS ziD5;V_F4U{{+o0O9SOz{izxWCqS(^dG36~vW|7qUI{iHtvf2updJC$S5(RXtlOZ=| z^Z#3e2?w9{XMnet)BXJ~&%JKn_W+H5yXbs?%Q{sgTpJHn7Vs%B=9?*VI0&l%X;8}p z+aZ*3*BnajNHZe+$3mtd&raSZ60p!Xzb$=zu+RnG=W^33`}saC?&n15YeTH* z`wGy5caKLRA-$}xB7SP;y-?U@8U(QQa`Acl?-{$=JRGR2Zyx}>d+uVR-i#e;@7L*6tU z`1=3WobO#BCBH2d8W-rz-6hFB<^ah2(3x7GQYd6JnhkL`{Q|od4XKaDm9KOsF zn>ECa6Sn{H%XE!>fm*SGUYWLxkD z3Cu3HL!j-1!5U7)vX$TOJCLVfudqN@>7L;&7#SiGU0wZn4guU|MK%S0$oeb3$oP9* zq96PE;-agG=mFZ%QWp%nW#1xSb-X0*+)yieoB%Y*kmXrWBh-k;8Ws!y2>)c4{EdIL z%l$qJOGMzNaucjQ+bg^EfWn=332{Jp1XhkGfRoI2K!2i@-_&3C>Yo{St^zyODp5#9YwvfG0cN|4jPYI}LT8#a1tfjR#O8(-$QKBml<=>Um25?-J0g`2y-Sd*7XS zd)oX9sXhk;{HC6J&AILY#LT^OFevj0@a+O=lJ`&Y13AF)!4@z*2b3=I@b@pj08n|_ ziJjg`698`p5dZjokp)!u0q)Krz}@F8Ao=OW@Elx7d=j+a-|*b4(Fe3kV)DMonsv&QwZ)!A({K(p_!9LxWMiERv{G>)oWAn)4CDH> z%fY_Iujk3M-`4cULtCz}QWE4)xcJ~+(_D327(^Ok^BF`%)Njgs5xTxN2e*W**Uq}D z%Wsx;O>{7i>}Rg4z>YLqhvKFUrTU>P5OR&jYvW2GF`;BY!!tmX3S?HV9G`=G^X{OBU-=QRcD*&~q9lrY z!ag774|!;+evtlic<$y=Jdh%LeLM8)23~4M(2}-+tG{4gXo;HY1sR=nd)3tIU(5zk z_HP64&tSNOwzGLtyjY^bbFV(A$5}N$zc{pL-1g3TLXL+0IR1mlfA44&{z$Y(1VEAD zA^u!t~u!sAMBky8U+ zeDowO)lDy6Yv%y0xdIP;oXpJrmtCzFpsNZRFfEI{9S^z5Kx^n0P@#``KazLF2{8ZFGm6D-nVff)URERIaVsdFlhH%M72Tx0)Cz;d~ywa zk<&q~{TUxoKa#9*j%K6h?ulu`9siZm7KuoI9S_ZoV`4<5??CuJQ`$GNO$FyUi{me` zo9E(51O=>Wv7ZgFdoXyYJ&<~t(M_LuE(21v-)Z+tIG;z^lF7Eq9~seXf7e`XhTkLX z9=6&^%U;<9lw%ldD4C*_QvPI6UbrUzP!dhrb4t4K>RU`KG%qI2C}e`{@KuDUCSLgr zXOLK$HW5|Qh+`+K8lTmE$4ILqF++D;q$ZbYT-wQYDXO+MAw-IpxN@RfbW#MYM9qS$n`N5s_j^JukgwmRiC>fI@d- zy97M(dQrnQ+FfmT!9Ds;OBj5GK37+KWgXz}v$A2?8MA0tXiR5KYs)^_kw- zJ&tsW7{*;gN`2NK1Op!k^o%Xw-Jx(B>iN*1e}f(lFjKEt>jzM!2_0&xyw%L*);BoX zslPW$lxxGu2ICEs;wTZWu}_qFWG$4E3-#tm1!oR&l=jFms*btfe?bo+sO?51>jfeMf1Hi=E`x`oLY&5tQG?D5`oD>pe8u*>amn_GH@$72Q5~< z117`pJgg)aEDwyy9i71=q|M(dRwkJL&4`|fL^PQHkJ-ZUJ=p9~@9q-cS!Hg5$eTg!*@1Ep$FIF8*g69n?9~% z5Mkw*Ke*d0@uK5w9$_=?3O<_WS|zDoaC3UmaCz28ghozV5pG<_PyflaG!jk05DASJ z)H#!F*te!UgYY7O!(sqi?#*dYpb=UMfqWm|M0Br^k74t+N0VXGz0)lI+Fj7Xv!U2E zobjeJ+0-_x)qd}iR-~7{aK5G=?evz5LD5>*9G8>`J;;=&ThRY#M|vpVRQy7f=oLzY zJbk1%>&KZNSmDq!$=W@@1iuI~QMRH!W4bgI0F2V1?qT>3B+vuPAOsg{jK~SoUU}zGRckn9iBY9y1Zjx1* z3*C5Xc!T#C0a#^=B#uH20rnG0`gPyz?srvQLhVI#BuJW+hOvJy8?B96XfILu8Kobn zPRjK8fLpBnoc99(SYcS4E(M%|meL+nK~0P2PG{+Zmn=I`t`corP0zf~Wr@tIP_u{1 zHD+pPdDG`peQvqt7ypQaW;|6pd6f`#Xn_CG75F(UWt#4YkOSlS&TGu~?;04tJUsM( zaCr8~-vYYdAi2jQP|!Kw4PbklDg$e7lZxiScgu6i5WFLQ3Q9hRSPoaAt@>Wu?IUzA zP^qI#rt>gRCO7^9!Xck)02}JULUzq9iUCguG^Z9V_9h(l^1Vn{EGC89IX>im+2xt+@YlggG*$K){%_iga%Sj7=9!haJA z3hsZ=_^;j&rNFVvM}inSG|9#O6GDuF0cD|G)!$)_I_vMKe`Z`z59E04LS~_mc7EOj zncb@t_ld=7_qy+?En;ZxVsy3dU9zj)Uc0C69W>8I*jjy4pvlu-u2Sbh>N@9Lc%h~z z>EjrOrn8n>A)~*X**)WUY=^S8h&zA~ef3p{P3)%~RLeAVIrhN3t!kO_>Oh+lT9gS)du8^^CvIkuMY-wI zqqAKE5_JEsJha6;zk2Hogpo+=GB*lC(EI7Rni`jb;&|WI?_-+m9;x*f-v(otI_+F! zdIE4beVVOk^+#pg|M1eZi1v3jJ2WKEJ*yq|hP43p!T$_1@ZF#rTo~vExAs2le5tZb z>tEsFKM7)at)#Wyh{&VvU>T0uLSxlh11b*TgGM%@zj*shT#LgE`VDS{$sGX=iu#Nz zvfX2er+BpNe)DDndfJl_i3dgj+IhSaOc;#;-_}04uFY>j!IU>kX6G2m;XQCmDW^vJ zWJ~=lvw@Zr#lNzDf0t%)d8GJ&@6Jzh{M3=iHBDvbj+!aLe;MnNm?5I0bjl48>0ooy;N#ps3{Tv*d`+Q$Jb8(jgI5+CK%)d%Q z+ZXGljf3%?hEd4Ooq&KY4aC`-5CCXrdh0GHaRVDLe*_Vt{M4B{T38(Lq4pwY|8O-+ zsO&g@c#8bwoT(N84s%}q}4)ZmzlOCIBw5`wczPn3CJ{6jBlekl0gLB5mW zWo3}=RX?vT+1`#mCbMNDTWdS>oPh7cBR6AC?k^=cFwzv`$Cguz9lK2}{c~0;k%e5# z1oh1B&Y8XZ;?`DvWG!;VM)f;$fklU5KF9sxZD2AikZ>2sHjjgT_vYs21)#g^4FtB^g?LA&c|@BN@4nfI zU%1M}){Ird8W207U-LT3zOdXyi6ARS+4%3ksy)fOP5XSQoth34zGwY`}_Q zv~#9`bWflDFnBT}eW)lh9Ky~MteI$IsgSq1fX>uP(&ZxmD&8#?MP~edW?^X`O5%83 z>;}>1)t0ZJGx&CHFcDe8(e=y{ie*kw_)f4*@iqNvxRY7 zlJ?e>70S-Q2hnNs6oct1{n}_GeR@*?auc(zH1e7usv0#s{7KTuXj)fS7}Sqj1nzf4 z_{TT`K`8WMtEegB|FLiWLkMiZ`TGSSnfk&PtziIO`4wpr-uvc@tLE5vcZ^Y`OM;w$ ze?!1?QZY9P3_m0P@5W`ua;jom(VFO@#PCjH_=_!q8P}^G`A%MULGfaNd-^XdAUPZ)Hk#(X!Xv#+ zpOV`oCp7&wU(g`&1}MF%<*~AQjrRjglpKy=C7H_(|ru~J<)&NK;3xYhNe6yMl`JNU~HM9poC>h>!v+_%6cQ$9di&T!~5vZTKpqc-*Z zpUjx6m?hFhuE0jMwL8^#eCiKZRlyHLT%vn@0qtHHopN8@`{`C_*Ey{Nl&B3#HaFrSEuGxOXe7qJx;~SykFaFyg%nK`Y`)ibjr1oa zIj~G)V53yCyJS=|Tyb@fZ^C!l+$$M)LyZ4D-DRNJX=a($>{8!p))CFOI!(lSmwMt8 z0XROff6W@g8Wq14j2d})YaE#RdeJ+L<~iq=HSPR#>ee1-YJU_arG zHmwyDbv&SR8S4(<)_4|S7YFdGAim3@20_@x=%gM-Q$Htk|9q6MRV&iniHfC->*k?k z_#m3NVZG3nmL30E0m8h0gOUgmg z@Wz`yt(Rc_8OAkLR#I}+;n@8vj=$Z3eh~9ZoK5cv216KEdG>6_L)$bS@JM9bDKeC( z-*dG#Pe_Y`&#YbK#Y{7#yECr}@$1VhCwdj;%!oN8wmR#Pu)9)W-1Vh)wZm8+!{@vj z%zwg}Q+PXn=QhsWIjam{5->LuYQrqGk*@r9po&5M^$bvxg0`7@o99ulfNUt0`Q`|E z(l=QI8SmIxU*`%|<v1k1L%bSl=p^UamM$@(36gAz703 zG>e(46?k8~aFR=SkshXIwYz7f-hYuct1a;EM?>utE^5g~V4&?-x^xLnc3JKcRJ42M z@@VfjP@-9QvRgY2un;Y?nCWMIq9)yU>92LZI%8L0C*@z-7xzud>snH_V-s86lb7h--^#IwH;TA_IE<|)3+*o=?=5NWXjg9gP1^EJ+~-ouf6FC}XbY_q!L zE%=qx!dJ`|+EW;_WOF-#+dA&C#cY$Ou26k;Ec&TG0i50wlm=ScZV zO>Lre zOfmFlH-2dBb^WSoK zUGx%oinnb+dvUbNoPG2(W{UsX82nyq&v9`IL$-e{k%73Q55odaAVxy83sK%47K37f zR6taCTGhK9Mup3{(LwC`>Cg?cvt{herr{8Cnkd`oJn|o63|A$i{pj=F#UmnHqH`kw z?o$Z{#BEa#E1u6&k8fXitu8T^I}NvL=fjpxs%j0Svze>>ibn(gKZ->hbYs=u|NVKE~X^lQ& zFyX9X3|&4fFPul?=DrMl_^dz@`$W=-J2dIC*~jPq%a@m{DuDJTmI2O+bY9TRJ}m#= zXW4aBj@xXm?=$plUS{@wC~0jGVE5LJzuC4FujHPQ^j&98bVaY6)>NyGvXR(3Mp!&zB_4{7Vb1q&M?tb85j7+j-tTA4&Lf&Kn!FV=P8M7#9wv}6_U9n}No90_yQ;SxSB>>IlypbjbvjUriXkgA znZ#@*#ISfgF{q%Cd-RPS5qRO2!-DLv*(F`6K{?8-82z1vP12WIPIDScjl@gO#}9`K zeD--#Q)H{SUW;o*5A8nPA1pj;H&mogR{N=krbwis%vBw1iqa8E5TAORG)N4U1o{fb z{@U_K-Bn-4$IXOr)1PVKz?YF(Xrk=AvylBAqpG8WhnFdbp|RZtJ0D~lt%BUXr9r47 z-beMvd}w5Vun;CXdE-A%rb7EC6t%~JUL8NqXvOszYY6TtPzQTjO>&cdgV|7*R;N29 zWEvxy8ycmrn!0pJXt|>V@Kg$%cj0u?a0t71OR;Tqg^F9e{z{j&x-Dp)7h!|4A3tp~ zJM_`y?2^drTP1u(o$hBBUSX*583=fbbB{>c4J8G$6!(zvCs+A?q3x#g#PgogjI zBkK|OQ^--9nkmTxHv)!*Ss#O;m)qR(;VIIBmdL}Bwz1eEoWg1Z7Qu@gu*pgMBKdVP zYx-F-${V4ZJCk=PkqN($A;q%| zI1yqy`JS zt6!?%(EI&s1rtuL^*D0kEVHY$ELY60ULh(ftHY_*6&qDjQAEtNd|Y(>f-gKFAIpAM za?q)qG9_$bCld~LiPa#@T$nfL_dQR=-G1qQ#Em=d3t0?oIbO?mumAjDVy(=jNU zGIxr|!hiYw{r>s+^tmil#1a+qrr}LD2Gq6J4}d_zZ=e}#5p`Ov{FjdPKGjdS$H8`| z&Ajz$4#qMCa$n(je9PgH98vgw?uRQ&lzwB=znF=5VyHGP*p`st78kvyg(G*g9Z4uz zp5bg>_{+DcSj|>=u|sE3p#^~gL6`yg!g}08kTs9aCvYsWD|AQb{<`}qX2~*EQMtVG z!;Go2!r#p7MeYjRyQc!2sIQ_QB-gRFe6W%rNk}?Bzkm3w*=(<|mh`4w6%{<4QHhO_ zFI$P90r$&X{pG@2Zt#96Kynd}Go}9nK#)M`U3S60cwOTo0Th69CpV>HE*fY=8SZ=S zEQJR_8*4hlKo`m@fD<8N1)X=$O`V6V$cZ+f7uVV7`H|ecmHH!D5KlagH}gaAUpPfB zDtfxteC&Q&DMHYOhSqRP$Obuvka4v>QxbLQm24JWb&0A?==!E}=;u)%j?YO_AgmSY zZx=0o-o#~B#XQTE=%;ZL=*{j zGr2uiy<+C;eSnBSGw&$XNz~wjA!R0{b%X3D*rK$#0GO2qK-w`7{2QP^|9%gZ`fxK) zO}z;LnI>l>TAoGE4{2^;AY`5}Fkf_>#XW8U~cu~EW!nG@>CjjyqszLq1+5IJF6Gk7A;x(t!k6S{gf4b}?3y0-nu#NS*jfZ>k+hBh1LFUzbug z;!llT&z>emgI_ZTuLIi2g(M=YwiPgrV2FJ^)k+b z0znO37VoHbQ;$orWDFr>E(dEh#y)5HUYn6!Mhiwwh~HiPW&6?0<#x9JDPWFv793ER zj5sEeSGcdnr*V}@0i0`4_MvDAj_j+plWcGdeL zhT&ilzmI32Vp*Ttf4e5oSw_x<&A3?rbQT7%I~fOzC^Nr*CWLGomcnj6?qBOmo8hkd zm1v{&r7$@})H~(Kory`z74Ocvay-!`hmV=lz}>;=f|+j#%r(UaGgTSge2|U~CD8L% zGUFN@DV>=%=d7x;k(o}1Wpk{*tgL@>rV&5)GCTgI*Qj}{gS08xD|z2bD%$tyj@G1@ z>(9=ysCG-!VNf*sG_F|`UDw;*c-S{(1&*m>(=e&F;}K1g-Pko7v-kM!;HknnCf&olvshlRKNz zW%zMBg`Hk$LapFU>Fj<~mt>_CPjEh(B^k6tUbXpY!e+7424Ny z_~F+hW(@r>mM>4Cahd5_w8_OU77hXiBw`{k2l2frYxKe?HSyaCIzx?o4y)h^!w%^T>Z$1TaAxMVatu%d_@SsQ+Kml;RAAPAbv`g5jL)*tJjR# zjw{pLp6~)Gl%*-heg^E}-&q7dcc5KMvbD?lNF%W$ROZW5fA%3rGHK8uD<}0_v@cRp z#&V8t;m5p2wG9$4X2-*$4(Q%jJ^8^?gEQ%z85DM{7jJd!lr8NZ+XzEXnIMIn+sWd@ z_CX3|TxmbdRKW91_GF0ijk*YtfkL4&PC#|+i*1ocNy#>6HK}eWWiNH-J&ws>j*Aym zGIEn2sA#cDX}hMzL>=Ip#zY|cKrgZw$#E<)bip;|&O5|?Kwa^Npkv;LI>&GU0XsEo z_h16+*9i(>Baf+Zm4L+3&;-fmMXe> z~ac-RL|lWWEsl_UljPY^zBEB23>; z5wh)^L;4Zbao9n*lefh97-$xhFeKoMQzfBsR&EG|k6fm-G*ax5X!@cuOMLR7CA$45 z_J7i$yvcFoFx>Nc4>>tAH+UdJZmF zOSV4{rMVly(Z=ca_6cFD1hN|kqV;*1Rh-#mA1vNU-V_L$u8G}V1fHnD8AB2bg<}2K z??t)hUN(CSwR2*#TZ4iyP~Ta$9m~o^I}WHY;BTn(#}pe9!p?1)C_D%I7)qsOUsD7# z$gW;aVHu2+T-Qs|C37^he*`Yp_hu4wYzN3wXH!%x8n>BXDBJJ#7W;Ek{tdECx_6W> z+C$1RsotrE(uJF71j@m{GUxm{bTw$~@!5r9jCY?6whUPypiVf4r;r<+G z5SSJqDcP$$2Quj32CMvjF*4Ja0LW6I` zj{O1$0K@kjDs_^Dl^ZV09E>kErI^v=-v!ntyxMTy&`=TJ!{y>N!7;;M#V|w`divc$%cJ;~7^O|eoF@g7x$GqAiGve{gstZ1$JG5SMPR}qTcfD; zXaSw!$Y>iA-?@fOtOh4G*OxAL{_&wYo$rN^^*o!cX(R%hGG^Lanoi(F7wC=&PCNRN z*z3WZ#8ec?PGV~7u~J9gDGZ!p^3KX_;P!!-Fu3`s)WbK3g)lGnRLS4%U$s(Sb>7at zKvd)kSL42Em1Xd?cAP4m!q?4Msb`}B>Z}VzPrF>}pNCLOGA$E0_RzDzy4=-U>MO0(m1cmDNr`{N0FZYj`XeD# zGWcCzS}XE$5YY92pXgTpG(Fe3bIeWLcnNpOC2YxEk)uTZEL)%&ZpY2d5F)ssHKP36 zcnOC?XZ;cQBjHNg&WBTWo_`g*!wMV`2N>Ru7D1O1fmZSqH`5X_S`!biBJ>vYX^cy1d+)~b+%NctHdsie@E1te*f#7NoLc( zUYAEo5w6*!camkj%d1X03ch?YvKa^Q>qz`Oia00fRsCQB+d-PYn?|%r>@4ONkuK39 zKiEZl-vt`N`V?n8EKjcjy1d%*5g5XmeAF%Ji-q(aFhuWS*^nCMdHh&Sq8cl;#rk~L zy+@A918XL=I^}bdw-$ys?eUGkk};8aXM&O>-VuKh8x4W|dz~otIO&24m*+UP=aU9mapGgG1}0CJOvpLyL?>UEduE3g?F z6XthWc4mKLf}CJ0Z1+kCXt2Mj8&n0I$2yfPD%jkw5?J2r-I~iwb49UMy(Pc4W&w=o z!~^Dw`aHp0Ug8lyk?}=fH;~PE1C^Y{89dV{&_Umum*A1JMNYe-r-4?30~R_ ztPKBaqaD!yX0x%P|IbSQ$4^H6S5)E~IRCT7*{8F^}I^WSK$&i@NJ|Kq1W{VR^(`Qkq~ zcH^(}+2Qf!%18fV!Z;G8qe+$_k^rX*Q z4Pr9C#7ce1GScXIkJkt?Oq|5vukUKycFE9!^hPJz1ai2-`xl9gHMWZ1p*%@N=@35s(S{RBu)j3n>Ow(hI69t&~N!`WbHujs?zVw)Q_=>=AF>MW?ZuocmC<0{?-72PkWcpHhy zJ@z2xm%eihB`jgGpyP9V>`gdyr-OrUwdHRefbYT2gsV-Lc&D!}qYic5LHk1xBrxgXw>Zhmsc)0Z1YSK zW+cCmXxO~Y%%X{(HKSgBjESH2y-ww?(OnYY0;NVMN>U{#ENGPmF3_@+V9cWJ9+_$; z)0AxsI0`|O`@2b(&FfBIHKXe&eA>T`SY8_wq&YWs*jW;|g)y&8R`WCy^x5 zTY)y)$Fv{x%Qkms|1KqNW{3i{*y*diyC|TN=5dQY{-RBsi5X|dqql|(o5v%MZe*qR z<~9Oxyf+t@`YQ64+F}rBbrzkjEKiRh|2>ZVr`>1?`Cl5H zRsQdnApeb@O!`|217Od?04NcrOjH3DMlo$85-|fB)^mWmNTX}EQoTExcOpYTIg3(v z^7<(w`Y}o#ssA9WM?Dd;_}9KVG@houUS$p8sb5 zlew4ngcnEV+d}?^x~7__9B=9FDU~98}@^RA;Llk zFJtBHQ{Sr(K$Z*O6GV7|VjzG4#40mKL@Z7DaxbSK14P)?MG=AnFhb;ZDL@cn5>i2= zH+6@W9lwjiMM-!@25zaA3AJ1=^Yss-5ifKYd7A8U7hL8rh~jHr{3>;;9|D?3l`*~6 zO1o(}cJwTqO7Pcpjq1^UIq;N*TRLTm+LyeUdX23Xg^p zbd5Ze_F)>kVZ82{vS>3}Zp)_fW`L9|p^yi`deYo(Y5;-YlxK;#6;RpzYa>tR4l;Q$PLBL#pqAn{~3OPrJ|k1V^$^3}%eXR+*O=5Cc5%E>{;)%(z1*~Sn|gV;>|yjK zZe5d!OW{-o7PGs4u)f#%R$N5mt?s`7_&?oISAC_ffJMLVi<1$Y;}lbBqo>pcgmW~4 z1QV9567Z9J#I1m?4!|OiyKTM6ol=CUG&aqK=-(4SWQYJF4^Kt49D*j0w1wQf1dl(h zaRH*6Q7#ZYOC2_pP}QU$K8x~EACbO@D6I5mVQjm_^uF9EF!i-RNx>f?OmUsF=bI(e6SRg@(A?9KUGcXLf zeaY>E^}74OiYq>o`S;idxvMFRwo%B@okZmAo>k5GumWXIbFfniv*71XdPmf(^?iDl5y{kiG$McyNL(7v8C0&5qd{%L`v2mTp!bz z&)+bczZ7POOdW@uWoQ8`zU_w+3hC_v;tySv6gEla9KyBv^~&tlOtc>j9oU1Pc9f%G zHt6z5T_*Ulx0)PWVilN0S%^j@wmS&LfZp7s>Qb1`86r8&qWZw8m$6cpF&|Tq#SZ|W zdO4_Y)U4x6bCeviz?VOTkJCX%EBtn}>^=7R1I|2D7*;Die_ zD>zG`A!H2{^V9O|9v77USid3>V6s4m(?`*}uaZ&fr~~{+aKwd!JC90C3_~NTMmgz6OLENO+6h~qNgcjY^sTpB4vab}g^XF!fFdD&4IoUu z*a%3S!dZ16Mf9z6rc2Ef%|xkYV&#;po>Et!+j9Wma_(g&vg zC$o>~AEE2k;coy0p!u^>&7a|qpVwaZm3j^8%=P}->z|Z*{S&BZe_Gq>E49^!9`ql2 zPzHY}07}2-Jb7D?>VJcIlF_sK04$xe+y&zgd~Bf?`b=w>{BrP zdFk8@1Qqs_)r2XfEb#y?`%t2Pi|cIxL1DGb9eP5s^i2m}!`db5da1S353+W76CbRe zj!Pv|Y+5|pM7)&6Pj>hBYKX5emcT}7;V>U|6M+^e=zx$u__QzXB5~{%=&%S6`7M^W z99fl}l`F%B7;SCY7W*`GCEx+`QBK8ughg1&30VnJZ%bHZCnoZc7d=fOu{`ClF$IG_ zj`&utx)zW`;L)kKwWOsm;@uJY)R{lW5i{p=)YW6#F-1~*;BsMISb1Wp z-p`ySSbh)z*}0%H)JyE=as1<5GJYE?0(ZJzJLxNR0-WnMxherhvr>lSy6d-h(_@QJTuq%pA|2(Lk|W zw;M@x4d6L5^?fgIH`karNhf-n5Lfry-Anad)O}dg5YRz{3Y)nmGwTu^%%dC)gQrRN zTxBqU5_90pedyxnz_oF^bx7p31@w`6!;Wz%4B!eA22_@Fn93eWU|Q z9Ol_Ii4^%QngR7a64iw)&UR9L7!}OjwaC{n%MOy?s+5LN;*MCJ45HYOx+JL|NK{J^ z0zF%mV{6N2w@YsJZEAD~QdS}t7~b;hd0IT^1h@5=x&4V;bZqkSz`t#P2lHo!&6)#>E5WzqS)1y@!qVQe}^c2D& z3(Ru3cij+fma!)2ei2Kb;MWw8ep zQfKD!m^=g&Ll-NwnkzZ+NufH83hmMhB5Zk)Z;|E*FFT*5PM`w7YDw>FuA&m z55ll02E=CE6UrOPE45w!iwv6Ry&Tm?7u+k=(Bgvj zsb}p(=QAM9NU3H-WMqCjq)sjar#!AB1*dN7-sv7EMWQ9Y6mtD(`f%rp`k~w;ELF9` z8EYDg-0>cO1OcUrmk!@sUe>{I<%$+!W2pDo^j>(%&0hE882!7nR2EN3<36^q|Q6l_v!z> z6dRrsWi)_juzG`@#Y)ZMg7j@nh72XgR#fURI-tr*CsksbA!30?R3#R8JradfxPu^W3rge|Ib6r$mKqIg`0hUHGWV@qNcNd?>N}aRav7VHML=70;Q9-1wzi$e>lHP`Jd!=9S zZB&H_HWF$`Dp;Yli#oN2yYW^R2RP+$4D-13V$Ns%OqSFYNkGc-f3;dX|IfzeR(pm2`fT{G-fG`F`w!njb@P59tw*sY18LQ3Z(^n1K(4QC zn(K>bR;`eQbQT>c`=HrGIl{})HH~;8RPXh5L|8lDB1*D%nO;Jo*0&zYte;v8QwCf` z^X^mTPOW?XmjQt;$^X2u8RY-kXs+`AyoB>Ve(KXd3@}mcc`>*|skW`l@+q5#M|gw{ zFYzb&6B$~p0iX=N?$U$^w6X?Fxu3=MkYIt)hGTa)g9mC zqa`@N68>+q(+KiEZZx-6^#9rEzg_Kz%Ku23E#(2fCQ5xx3|Hskvf!W8V4ZPwca*Ec zB(N#(CegGsg)5S3=Dpbm+eIjC zHD2cVA0Hnsj*q@S98D(Q&yT-c9bYc0vH~UNzugYbe`}+?y8oZ^{O7Iq!x01N3w1R^ zLBby|5B!{}gDf7YV;m-NmDKOnACS2C$~;Ae?iA!p)gWxWV2f1OC6c z)mYvCFYWw~pYimsxPs?C|L3#C@%NWU7Y{H0Z*4XM{=d`SSe^f8JpbNmKisj;nyq`& z2Wi+z`U!K_Uw5r@E7*ZFx%i23g9l@r-2}D+*q1V22mHN^JCZL>eLHL&q4irGzB~cZ2~3QiXhv$B~r)E>knOqEUD_5 z00;{+Q^92jDe};#$V<8mJ*x{61SuVuYaFJhx6u|NK{|Bsh){>RU7`d4@R zbDaOl*~j&(#aX&wXQRp4-ElIX&z6h&t9<@j4SxO`TbrHM>ij?3`4?8dA_*|(KX(_E zUiUX?gfN5R!O%KTi=u+mf`0%$PGNe;7K2_C#kGl;Z-5dS`$MmtDF z3cWwmh@HG=&>&E1Vek(LrdXE{%Tk#Ku{c>GbokhpRIG9}LmU&9LOIBA%3zN3P%8ZN z4NkQQmGc_EjX2~FYurQMo@$;OLdt@`($%mG8vggMwYWSI6yuOWZc3@jJQz-Z_yE;h z->5#F6dLm#tg;>auvc9`{|}BY4LP_c%U6T`Z#7#y{=3oYbXNMG=cE604^$!jj_$j9 z)hzog#{rx_3)yD3e1F|H>+3&J;cdvGwLT|6J~NfG0pHIe1I($(V3~W<2pdRTJ~UMS zC`<{BG>F31pk_#NFQ6sUpaTR9Gi@(+O9L4E@%Prf5C+5;f)EDnRrhfJ@5oJHfPX3C zGQub+12vY=B-xp^ta7D0n$pti!zsnp@ZM0wjr2po95UoMM~vCW8!>)7Z3xwU)f>{# z5c)h6MVsd885t6Y_x6_JbBX?-cs&IXBx#~5y3(1?g`LgVd&}xx`>5pX?XCZo+FAJ8 zQt-PVv3(yv%P3Y;ILuB&a0;2xhl~{Yr_h1rHsVX^A+Ai3aCCv08Nt7oz&i|x(=9!h zebqDw=6CpN*m0DzDjQC8|;xA9+bJD@N4eP>&Uzs$3ohA!{!>aFnr^X zLf?dy?8XFaJ@c}a2p`*kdTrVyF8{lrk8T{BVKqk~(^pg46I}Cf)Cm6d1u9Nr0o(F2 z0W6)aFeT7pClcOVLVGFt`Fp}S+6|X8t)Y@=gWrVq`19PTE(4J3nKpef+i&k*?T>=g}nMDL7fHJWHkN%tcIg zCCtt{^Jjoq2!yQ)rT3aYY;@T!HHRqrg@7etM=7qW0M)tESHp`WIGQ^{_4deRR5Ww$ zO#@D@oks+@E%?sydQ4b#yqfS%XN%@dG`*;p_ne!mw?$t+yxygkJNFs%mhUtsQ;7wU zTzaJ!ad70!WAEIU=FCG6A^%-U1%{I-pb$HK_0A)IDU2fQ$Wwa6E!=fqeU8>6>G?o@ z{LXu9_t1%<;QnFX8LIccxb8QjmeANx4aSk(wmU<07M(g?aHp@{dlc28uMgGWW9sD4 zS6jLxj`PGHr`?J}_3lq>hE*<3f26t*1GY(pfu+JWYPTk=|>=Fy`TN)Rt30F?U~*aFk6XD-AL_QwK{ayS!vu zL%=^pExD?YPMec++)E?+IOQDKFv79>!7ag@4!dAjr>E*8&)szhLJkYYV zj&rIC%gxwEv^d~@?4#x_2gS%F02OoRtWD-kcnf{$e7>+aQ|^ zyDJ5=XU0{i%$)>lzn{_b)2JjVI8Um^$UV=EQo0T)j*iVK%#wrV<#8oj zg>C*IiY+%Ta_K3zOX4 z1<4Lr>I#+vd64BmF3yXTZC#t};7Tjq=Vo_fq4mwQZ+U0EL0;G{x_heWvp$A07QHuY z?*p9$<}87Z3dISgjODwdGpOM|@=f-VY29yUBD51`SZ7OWDt_W?I?UD-dm;^x^^V;G zkk(mCY1rY_G|C^g^f8tSN%&Z+&c|rpaBUTQP#ZT)W_ZCfH}qAoUv0Xlshr53c{oH_ zz|e1Gz)L0lWNeUY?aq=u+z6783(Fj&t&UF}}`x#$A73B5o0KM?%UfH%n8rUv-tb>cU^w z-JEZ-`^u6%w@JD*Qy4VBCW!e{wPMV86uchm-gVL(v_avt(HhJLM7MBi$ z)qk)dHM)F~KAgIdPZlTw$uM79%f1--30nZ4$v)l+RJEH3SYmGe8*ciA!Sw zb@E0U3}t=l(nNjWb*S@GDI*nTGYzso>kcE&Ngnm6lTJ%R$~JGB*9)eZC2>uCDp_e% z3ufu9BEA4{$!^7V9vtG<04dS|B&!245+<})H|d^M>P!|a%>fJY$w|^QW4|Kqb(x!r z(7Oq7XCf=&tHVq=H;ujx(o+}sS%?eRcE*f)x5QkF3=)o)<>o<7mKkQ5@P%JuZX^EX z$`UK?b4zS0;jhc0A=PzxD$;E#?05#-4QOv$ZV$D2c?wsT-_WYvB$DNO3gmQ|$pgk` zED`fNIstI1^R>g00GN3&X_hjcG>g*Sl3ATYW@(Ys>)}8pVJ_xk(bn7b<4U_e_wz$0 zwn_&jJTguhGg8VryPInY{oWHnd2kwuIS^5a<*9-wVQsoB$lKyI0f3?eOWfD>zWzL0dfB=s*L_;I~5?dz!o9P!oi9Zvd8)h zt!Az~GTAR(I&Bkh6{B=1W3&k2r73sBeyXr!6OCAjqGB3OpN^bSX!ekLAexl?Kh#H{LST`;bb&4Do-NM zh4`)NGPHD}??pmXi}-4^-0tKlx-@q1=jsoEHoY-rLp8UM_p)P?5E&Vw5V#Q+6>bD+ zz@!eE%TLiEY#lErITmZvWXNn@!Ehir zfLt=o9N!cvv!);B>H|TRD*1~qkM3LsZ7@7GefN7|-u>S7bja^bCE>G89{XMdZnTL? zt=LPG_EIK&^Q7C|JYiCZ0;)@L8nY#R{?|#j`>zwiPn<|6rHsa;je67R%Wil2lHB2! z?iH!_BJ@r&;}q@OY#d>cB1h=L?5w3Do%x0=-XtyMJ+qvyDbdt$VFmT)AWwEkC9i@x zLPXX=fz;cq#_sLejvyfa(9oJl}*SWBk%#4hl)*_Q}4*!ymOax?W4 zQyw{mI@&I%^suDo%mc$+9!rZA<6Kb%iWU?S4Ae zUwzpD3TF0iUAU!=$|4QH--@#+G(8o8++OOUWpV9=PJ>woQ+b$nfVvxk!?;I9=f_rD zV@P-fnd(%IYhpbqpF06TW`ct=_fpd^qNVDscDpO=>fbM1R}yg> z?|mU#Sz-#?;D@9^^5_0-U@99)AxK^I$o81VmhQE5WE$OOlFA!LZdhe@|Gx>ric=Q< zURaN6B2s4)YLQTlwno(cpQ;;|g*}#=qx_ci_fCop4;3{?pKHW8t2aMVUqeO8WT(}MuZ@4`>V8T_1wFp&SSRXJZv_|1{nelj`M!EJMYUX zA|c<`Zi*b?-HpK*krBV>cH|E1SY0Um7MD z%mIs;BQAHB$H(37_*j)!?(}cfp0N037)vjM&CW?D9<)WN6tN1}%At$}9r!*c1D(=8 zraGp){;f0f?hEZRsSX+ zMi4+UdL`^-j(`7R%l!FdwPjY~H2WJT4!YEK4JGR&u>z)`+-HJ|<>tBzt|8i#{_a~v z`vuxf^^sZjsqKF}xhZG&zT5w~Uu;yjH2XO1ce~TRF7`dK+5kv#@MWWN=VK^jALe7Y zx6EU>48h21vkhNQAmYVg&vb%@)Z2tEN`5F^N?Y(=x)dbXiB&=@R6v^Nlz4*+tOzuZJb-?uPs@i-}Bb%#P>=yrmOSkn3M` z8lQVNbve-AFikq@FzMMlT4v_r?GYQ;T6X5t<609b^}o4kApObuOZZ%T;>y5Vm&w?* z3L1~9NeBjpOSt3~m3Q-2@5N2X5;neP)p`_}$m)KH?^*{W#)O7g$7H;w#{n>&ybppO6du<`$lgI_WYOvzQM zmL;GdSiT(0yxaX{i-ita91-7M9<_baD-W-(1qlu z`c^q2OWWd7^Wv=|DfdT7C~RRvP0F&KH9Yvgs9oBg><~=%2f_jW+|#1KK)~LKB@XpF zkwroN{A$Zmcc28P9rs8ajY0R#XFa0$ey=+|x~zk|epC2=m9VhI_>~43yeWj-&vqTG`J>1P9aLf(tc`+Kb7FCHEBNZfUNw zY9SAc9RgHGM%Cb7@Buuip&ZrFX@WqEupGPla%zX*?uQbY>@H0P&3A499Wkrr*s(J1*x;jB{lzlA&?CGfwq zYr=oK&j0c7@c;2{p}*g4ZolYuw_gB1xAVf4|9O1Gm?l-wW;J*ciZCs_Qdd}9cY#g# z^s?Li^pa%NeJKsBUf}(VUlRJ?AZ|=2B`TzFrP7VJL2LH-wSHuAf4OF;7%hCiw&8WqA=#mtW?c;8@ zeN1FRvSq)f1-LB8-KvGKv!$t3n)D;dFca9Omr@tP-$n_ulgPH5qO)HhW<6(?P9#MG z&K+H-@1?XA)CiF-5AviXCpW+<%fkQd%YR!ttz85D+go-1??(XtAMYFbhh1Gv!><1e zqRSc(&oR})f_jU}38j1gCtw`}9B=g&#NkK0;3Qwed%y&mx!75^_a7=s65W$56F2Tp z@w)ld+MhsxM9g;v2#GcpxaYl!dqiU~Ul0mO6DAJb#(?nA*WAJ-e5r=jNsjcjLMYG? z^9WaC_Pn%@q_vNaESm+L{%0R`d&kteOS7i2IP@@sFxjS#bPvgtjsVpj!(BNxD-4KT zoi>2@DFlwTL3_E^<+sQ;mf`<%fZJJ|WH%55mC%1%?Mp0l?L}Oxkc~)1Y-bZLAiPz7@qtG*xKYV5J)Z_+G(Fe?IgoU-kl_Q^%1igrKPK`1cq#vG1wR(Tlw!&1j!; zXR^tGEo_6vL29LX0$MIldv_^EYIBBz5d5L|OL+d<*fq_LK|cRM_g(9yHPVgkFR{~H zUkGxBZeQD1-NmH~K3s3WCU)|o@>neF>7YB zpHaX6CGY>u6~HC$|K8@7Y5%cZ%YPry`~UGSU;jV4l4%`#B&o*ceYY1J(F9%Mb^GgB zykNvK_9Qw2Qh&S5hLBwzd9+NaM4Jn2ag|o z^ZIN!!Bcb&AFjPo5>LG~^zm|S{|B{d4~1cu+=?do`vjwGo`^P-Bp9MFT-*O)bCMff=3HIhGPCPUm;#r1$FHXD!f5b!h8+p({6TYRvbQUhW1j8iE z=0g-gts#omGVcO=2}Z*S_Pw7re!!o_yy+xfVLw1;D(6HmJQ4m<8--dCNC1{xGb!nxJv^(^BI-kKLu+O$BJ?zN4_TkU{AFlWLOnp2I zQG#KRH1me`Dbr)>Xh-(UvA~1MEjTYom!KFP$8CvEkDAWJ$8TNmz+Wz-J@c-aANboRIPFi5}4O_ACFbvM|bmr{A zC|uiD5VU^JyyvzKzglPNz;V{ATVp$i{0DYvMLKUK_;2m*nEpTQ?b`n1Ug7_X*PjMi zhLapt|6dXhKjG}@aDuW8ObZQIm*@;#=pzBWUS!geI?>Wkk~nF4gEQ(-@HUBuI8Ec^ z1P!w|S^OHLnM`Pkcgw^^Qr|s7OTvHqm*AI9|L(<6hK3paaTdkPiJsr`X3PS1P!eRDMCelA9PxG6Z1*4H#Yy~bDcA{r(1(9e$FTFd^L z<1~9S8abE5S@x8)74K>gWjGn3AvSJVFOEjRn6W;(su5Q~1{7xc<;n^u|lX^Gn8C$D}c%JTZbIEoYOix%tyw7H1m z5Ti(IF{Id4S}Fa&N9oDsc`I%$o?!oIA=)aRwH~~$UNjFweS!3HNlT<3@Yfrt{3w2!tO&zu3TUonTbNRf*w~|TAGoGQ8%3Xf+0~7|)_(!-mW9*^G_o6uS7C7_bOPt7Sx|pX6 z>y{BfNT!ax&hT{Bvw}vxE;P)7OAH7g&Cqlv+G+*R!7;S>>dhm9HTgZBC2>Z@O;`9L z$%Z*5>X`3=?UHfH6?xUeg}spCcD)owzN6g)rL+lUY~&(>?@<{1Ef&DX`V_@iA@;|5 z2}Vh{pqM#TO9;p_^!XA+euxvT2E8Qo6x5P9^d%xd>z4u+r5!9oo?`lHs6(q2r)WW| z*?Qp;ILp{UG@EDV2^xOV1|B9DN52O?j-^t#=^-2eU7~jtgrP^U?gjSz03KYBTgDNA zfRt@NT$hW5mrX#PNgpVF-}!J-r`psLOz0Ku((>X6k7F1ih@)31LSubfQXFOE&Ybx( z{iudGxa613D8>=G2*q=!0n#W$<5YkT`N`2F(6s#7B!#Styj;AQ%DWY6gl?yl6xNp- zj;t?4qSMDZQj?0vEK3P&d3iIfaUtm~H_XXpfLhj46HzefahfG_pmQ3O66iQ+v!*7I zZhBv&>+M}1b{K_02%fy)up?8+w62W0uGjOsPh0=w4RWmFfhc{RUBvUqr&#|g$R^$e zN`oPPzP%tC#mSUhQg0q*LFirO_m8{|qj?z8);`Wq5T=x7g(6Cz?(>le42^M?diXOQ z&WR2n8e(r0C+a+8*b5>B_MWoNC1QnGA>8l`3gJ@h(XN)uHK_$ple_vCa zNgYJ^O8j!dynVqb&d#(45&9Fqmh5kcqL=6r8+}@~skF`*(-%&fKqEurLw@7eJ62X- zqvGkUxj`R{U#0iUee+hma+EfN#luJbbb{(r{l?jz8)FEi*9j41H4-ApV#q)iZYh<6 z^evA3AR2ecoKZBymK)CMyqPD3)!tm<#PYd&5#sY$rufWCgxyqkC&k$-!gmqwD{r9J zA>0wLR`wbU5!0J_HbDt)uulbj_GB8gLFn3!w?=u#HSepp#@OREZ76kxa+i^ReE#aB z;b+s4c=G)@jhT7Kch^HGMKY{wKWF7R0v&nV+&>r|sB zIq%0{oe0>Nu+0tevNX;P+oI!i63;__KyCt^m4!*yWf zVzz1p0LE_GWl{)RSs!E&njpdV&m_5kS~$j;VA}K7LTArXij{a5sSSz?Dhxx6lF|;? zEwe}m;)sIU0A$}0qD&dZH};D3+-VJ6%n`j7Hw|^xeD8fUil=q7NI=4NXaW~m(J5>9{!x+$Twy86y4-qlu7_$e5;i~t~dX- zEDH+le}tCdMiv2O@xQhW|L@(_R;~Yk0Q(<@PS>Hx&9y|bfsY1Ynt6}7QMt)wDsZ8p zcp8fNU*QbjzIOx*E~ycP;SBV_1WeX5O+KT)ba{oGrN;s_Hic1GEH@^=mlaR@3l#j3W$;Y&3pC^a4*( zf5!UE;HBrCg0Aoh4pV$J!3h>M=${GB>3vq83Nmy=L)M}&=&+_)63^n4apw}FK1=Wu z=_lwNo>=&Jb&OLK`SJ9O@Ego(!F%pC*1hKkITvCDPzksmnp!Bf|Ba_pmip-q;eUI_ z^#9!2sr^6iAO2-AB0vk61TjdNSsrq3dBIDgjV1O=`2Kx*Jz5h z5|~V6eoli~A87lO3dnfTwK-j|e}PpIDJoqT6{xPcz*JoS@77$nqW>2NBv-}*W%0lF z%=mActvdeyWAp#goVX}a;DNl_%i;*Egs6ry2BSPL4SejylCe{nCl=|bf*%%K#S$+p zS$xjE=pytb{H-ntET@SH~b4nOcZ(K zdXz#qkxLeWxC@{e=#ya>?32IGDaElyZ!2dAnC%()n;;{(NiX(w5<*cR=!8lVWWf-D zYFFXMNngj*vc*bvlpqzd7gezeRmwQfiU7@3@QUI8GtYuB-5BExs45Vh|AB&RW!LXY z@V~QT$p5zXYWd&2)Bj8rMGu@g-%(ig;|QBNOQ`HpV8N$Tq9yBiTi!EO!$B zVRNs&UB`d^2Ht;D_%ADfmkRuV7arBLdh;1=MokN!dgl}3B?BF%Gdv7N0ri;(vJ~n7 zm@vyF2=`E#^q@1n3GI$ZB~^6ptW5jfCQb((g20>KV6w~C-A?s zwY6o&f8MV7{|A8om7IVjou1GGijOw`Q@1t!tvJgT=p&_C_K-HY{8RKn<$x9eDEA(F z?yXV7fi)NO3#&QsYwQRQi4Q-bxgW$oHoYIICh$oLszCV7%|798@Dcm*Pjd!5>dteo z!Kfl_sPzw&y0Nr16+M9TR7DTdf2^Q?(Vqu0PBKLy>jjRC{YiuUkRNklP(^<8Js>}# zIAi)GR+|w}PiSwsK8b%;wM_gg*EYqG{5e+$5jOG(72<2&RVAO#_IYQa8qBmIum5Sh>Z|6KJlC`=W7~?KB3DW+A@4La8@J$tSQ%Oqr*J|9*6NAAWO;-6r8e`B}IyaUt6atRO1yqB5M zzSPOmVR&g}QtI$dcftQj2XcGy-&%W{M*hdv)^?r$;{o`;lK46HpgEQpNJdPh0&dDp zAITiu7Yhb+p*IPy=+Agw2k{A8-wU;xvlED}qQ_Rr3srYCtx$19+czi#0RbZZ-NQ{e ze{WXwX1p%nZ5`$3CiBZ5OP0*>^3Hj2^fI=&=T(@yDFIl0@*@c7c#apcU29)LhZR_&Cc6)QHRpbBr$Nx&yIyIP`5(Lli5U`m2 z7suhdkyLMau^u;7l${#f?~pAD>K7*)C$OC;wQ!JmA(JS%15o8PNemWA2&9K%5f$@z z4{BXUSQfiXZbOvfX#cvUI9oWEaoj!iL`*-{Hdlgw)%R zNeUK=XayZm8U8o&|3FH>+Wz}n(f<@O4I7qHO{{9(F~dVl^3ClSMi&y|HoMaCwjpM7 zjeek$^C*aZr4*uNNjicRa<q95fM`n=i7#zO^`xZNpA5KYI6tdwVpaSbiW#c)OpxZsf%mpeLa@ zV?dV<#zYAwp&f@9G)TyU*j%Bqo~Abo=P5+PATWVK9`GzAA?S#^1IoCa(vZDVKX_!& zKOpYGCy1Fg!dD(j)A^LdE=fIf5zncriZ+1AdL$5<7ta&&nngaukDI5US4wfZ@4ZX0 zH=T!BFbjDrqIz1ya}t)U>0Qh-I$jFq0FeBKD5)+3e~v;v&?pYW_=*gj%tM@Z*j!sr zy)Liws{0;PWq3Nv7G8rW+oG7BS-Q^Zx3&7D)>fRNu#Q?g$LSjw#RI|Ep5ElZ_eqM`JT}Eaor{99j=MjAhX#ZGgyDhy6ch|!rbW0 zqyh6u4C5)5BHBTR^ig=~oj|a<&j^A(cewtK)Cq(+D+&K;_mV;q-xV(2#Y2fUfP%5(KHy6oe)#f zYYfp8C&)u#8hgWd9%Z0Cy}+^t2T1+Usz?IOWB338xY5%6f9;;M!C5{J19*gPS7B4LM)*JVmo?*eC6aGy8H z(X_lq7(^H)>rIdNfP0(ZZURAt;C)Gh5U_TW)_tm!P0{CII-k<9*_8Oq{$N%Bf8!+P zaC?!&R{*+xj8n)QHcMiF0%~p`MfL1xef1_uJf+6(P(M|c3|0{Y8`9gu+nfXm9>t#v z8Zj6l%_w37fPfNmCbWQecZNnN2|C`fHgyy&@1JpQtwM8GlqgfCfXUu|_8x+mLF!+#P zxgaIyB*xTGZMsM^JcXG=IJ=6IPy5~lUc`}4F(W}i2DT#^?HC&6v*2-2>8=sQ%F*JC z^nR1cv$)XI^2R!s2Kt1^8i^W*S0s;K6er#+L_08@5cY5y>0q`w0CR$|GoQv{geys^swa`&-YVVMmR@hTk0aSqg=;4`N{R366}W|<(Jk{QcH;`oEk-NOQvuc>f1moF+ra{5 z^uHPZVP|V=r-uKBd;cx!PWKaCs^AxOogvn1$bMw-&oTXNpn%f%-xB|Mv$p?yK=`NL z%o}>}b2knsNa9)r8L;Zks#1ie8dKa4{HyU_ZiEHO;y-S;%=iy`HU0lE^gj#OY$4%U z;6>v*kIH5g=z#%ba-MtUy9+_KU@Mkz!IOw&l2mL5#@Q%0ZW+_ zljXa>L}l=Q8yr9h{I~XY4f>zNgskEJA^87dMqE!WuNCKc-|J`k`*{E9dEe>#zrEcy z^?$Yf|3lLMkhIlFi^mGvZm-0jUn6INT!qGiq2X89n_N&k5Aox!LZ5Y-h@zP5_;E6-{TFY5l{OD9YN+{c;UD5Z-vJINd;g92AFcLo&Hq0T z{IjHxHsJ3$_~o4FH^l#@=5Qm`Yz8b<^jM0_Ii=9ekm{?fs)!m}Ed&4BDcpnnzqM)F z|LoQ9|3L6B3;S!x`RDTg91R$%o#k;sG5oi;c8vI6o7`RShxpjYq=RQIDDpe!~;3m$7y=M2Wo^Uk*O3iHWb-XPF}tYGk_PRK?i^W_tIIefIlG{XR`d zA`XD3`O$n+@=QPc0KBiBlwG|b5fDEoTa*fY6P8@Q_uP4e?8Cb#_-l@HcWefU5x&Gp z(oIADu6+zYFn(1G{Aw!seaKn(-=WS*5>t%d{uzpQu4FEiXTx08_d$*QT9+j;*b z(~gRyK$-o|R@;dGwzFHue|UiRU$gh&NsX_o8wM4iIqBjs^R1)4&eHotYQ78 z@NdL_G<>(J^8sb~-wpj=yS3AQ5VIf) z@h!$AbYV0*c-=i7D}DbR82|R-e{b*X?3wYu>->+8nEx*Ze|si4Ys^5RhX0lL_Gqng zhaY#WK_1~xZRiYKY4qiEMzHo+DE8dlzL%F#Hq( zqq%m4f;@K=q;!&h^K48U(}K|l#JP|oL)bP(<9NU5d3|3KLs2E_cyUlksc-$%p1Y(N z_A>%j*_@>9t<#1RG0Y8tfqkT=WgI7;Z(SSX-)*7mEfMRN3GS{mS8HUkk*r3ERg`vY zV4rtCfb?55RZN7h#IGe&)Y$UfMEL6WRa4&3oKSDT7O6pVOUd%4UN44TQSu|&I+h2oS-8>v5|xWn4exo0#W=S@2T6mSwJG6HGX zYmO~bUoRL>4#=UJcS&{UKpIP6%62ZlF@EaCLLN0-A= zH$_4)s{miiQ~z=LA1nR`M&q{aKU(dUA^+KI@73|2 z9yk8Cii%z09*TZ?48xLUP4>~+2lqj9J`Dj6iAoZ$l6zV46<|rPZ~6`}?^NE&nN^BB z6f5k=%jFz^P-(+Mpc%~2{~suse|2VYDDP;;w#}A@NU~B`@_B&{mwRBKMadJBMIcOU?>CXw**#k{Zr zF_bE*aD|e{RetS4A>(l!0r(#1Kj~83HvPBN+SxYgzwH|S@00%fLTRQd^xqc;c}2z0 zlC4l4Nu;D9q+OSqu`nYgjffSd)QSfoDQWhSgd~yLkxt_Xa!ob`t_s1K&)q+S0rVRxho&(39Qc5A<2Lw=rD`66+6 zzwG~T?HzjOUWb}WtCM_zBb)?7qt9m*#Ov^^P!7=~DgCtL6usShGxdhmvIIR0<6y>K@&OuL8Jw-u8q~aaRUD6#vh%Ncq1SPm?6X^bw zWx`&j|FSle9G(N!+HmY}v+7o^3lA8^pHr*QhVDXhubu+X3i&=AlRCm=c6Qj#FSKH0|9a1NOz z!6m4QMnU8&U8N^+A{~rco4NzZIo)}QG3|ldro!>`*L2!Hl>Q?ZP0qD_%k*EnwQ1yk z+S{t*zdTO*&%rN}OQN~oPKn>H!2k0||AHgm=6ma;h~%rN$ANG>QFNe1hW0 zAoY+p3K19+BN#Em$ux*?>IEa>Swgx4WbF}v@P_deOpk+z^|QgB1KOd1!J&jK&nS`R zf+KVhR+)`-7EG7#T_M=(Y!akwu379|VBRGkL7FEku{k2ctBqga}Be`FvQ5{_P=pNrV2ip_L6tx zn%c|yFsMlfI_};&w*to~QAs-9FZZA}zDfh}ug-}V%$p4TO6;Viv@h>t6{5g+4{FNb zyMF&~^z>Je|Fl~zQ~tA8zyFU}{HcMy%cWEhC`pf4N>e#MtK zlop?VgAI@a709H4c1}(C_B6d)5+H$00cK;I!~~@z0D~no1!+KtkE6xeBF*sBRjKbF z9E0?XZnBM}R9Z6f(itJ9OYafAPF17Cq?kp0`4mLHNql`+gYRp9HeH-SVf2<<{x$F$ zLdT_yKS8OziWL^dY__R+BQLSNWEP^KP0W)nq9OTAgBPiVR|%R~+63ttn%X)WW0tkU zw-jYSJNCkZOW`3e!dKj{S#~-u_02Y&9Ex1DnJc!{@1@9vI}ZKV_K+Nv9ifeh zR`sMX8oG?59H4ebII`6KPnyo%tg{9Mk#$)GIVYgO&e0eFqN z=E>3T$d_L1Yk_r3Lgrl4|4ET}$}GP+uJQO{#$FOw*USn<$p~hL}F9N08I<_QpI~u?P+LLIiA#s+XgB} ztqmt`U0=qy$(XqaOX`tuu6Bi=cZ#!l5`n62j=if1j)+=MrjW0KF!Z9>3*!jpHly1I zVKA}@krhR;H$~)#h(-{jl+xA3+D0e|(qFAYK`4V=PA8Uz`5qMDX(3g#BJ8}_fw{$b zQCyQ>nT{C?SvH}O&v_}_uQvWKQ$IWA^j~(KYz`#IwX5m~1z}NFe3#AeO}U^@+UAZ4 zG0WcL^<2T;;A0=!1#9f0HA;`5Bm^*vLcB`!U$IfPz{H^9<{??7eQA0(9!gtar7b{d ziW9DQ4^nep`Zz&qQ~_A4+@2076P#%`7rQIR$Qn>9FtUv_`m|#M%Qi^rhn=nSB&va7 z*?<5<4TDy7tTM$J$jr^~(z;Ktg$*}GaXd@Bp6R{n!V+Nomp8EqyAtzBP>eA z>SfiIV`yOG0^f^Aj4Ql?NEPx<60=Y+MolmpPOwkv&2q*>1i-aU)+()BY63t#jZxx< zz>>zJlJS0lM9()LRIHk_F!GXJgYz3+KEBL-*IlMl<;)uy}` z{JtIX3s;uQ#w3gjCsmDjyxEjiVYduERodpXQAW&ixdT1c0xXAo|2+O}j_26PhdcO? zG0tA&&)FcNt=FrdD03+;@;Y~JESOcv4)9zkRK(+CJC&?Ta{H5cI0`~YCMMM;i}edO zG>(iBsMgcPweki7IYW2jOP-j!NwpZ>_1$OfmV(Y!C=h4*8z&TFNX(NHG6VoUjQ0Bs7d}I^qm^RmouF zYE6l~-;tL_OQ<#Bnja*1nCBskLsk{`gZB`QI`W1I(-SnzEG4ir=YW2Rq@U3I7qCCD z6*QX-%p8#YRgg`{hrsuO%$uh`@Wmbr5k_%1*(yc@u>u-@ONsB##~qAlt9+9Qm?I)} zgEK@PkGaM1{}Y--YMj~0iP69Nycloh}YX9;*5>f)>KLUL3U|HmuF{;7cfTU)zE{IA{3 zc8&iZ2LGR6l+6={=08%k=3dPFZljt%M0LWCICft|ib`=6CEzAuCSclY)T|Nq_HI{()TZjmRg}u!8lKAS*$C)bmo>}& zje6M^yPx8)l8&M%iMW}?+m*zd4{o|m>N$b^H@XkIT;mTEWH{0Ms6@VE&5KG)PpoDdgl>@0cj~nd`0j<>2w9dk1G(aDufNVXmIg{t5-zNt_HM zB-ki9&U*|ZXP_^vqe29hZCCl+HMdgY-l`n0T46o|d`a9+kBPW0h@&II2U+at+)cWp z3@0Tm2r61`5Vc8_L6+;kV%JP2me(%q>LgAC(xvXjzRiz0N{2YOG~QaZToF6o=#DuKe{PNQDiL}-PWc zc6+ae{|D0l@?>tO)Wg)O^eusPGwogl^{p!(UolRgey|oWiI&_QMR=NS%f z|NY->8S$SsTebf0A>hBNGOb*FW;Y7~sv-tvmD={LrQpO{fq3A#D+|$x6G!9-M#|Dz zHJ(XPloBdMJuH!gy0MS~QV)cBD)av$xBKnee{CD^zg@@we{}qRiK!UV&y*7k7Iv9j zF2#x$@CP?s8i0EMwFPB1RS@(Z!*oyr7R4ljK1tRNEXfRwi8W~!b6hhfs&8ibWWyc9 zGRz@v|C-=MFqYMctS%U%l1%Ao!10WpC{GH$l+I_cG2o^~O$HQ;WX|K{wN8r6vZ~55 z^3AR;&d8g%B^nxjsI9-gGx|?TPrd8|P(uH0?U?ePz0Df`KY;v)9W#~cQUgGb9C-yA zO-dntLo!XxgkC^}U6)`Zaa(J2RMq}-o&^-_sb-ZZ`ED2iq6Ge1dz(i5hrM=f|9>z4 zG@@ZNkBEBJQ!hY+xl_)gPvF}vhZvATr&(dqa+7Lce8lk2nBf}-eW?upd)s@4|L0b_ zhW~rz|5mjBHduwGXP7*n5K+(K*C5S!Vi!`Cm=Qr|O1c+TyXl>x$d9LI1kL0h4YH#w zRE#rD4g9wYy#mYAG#y`a*0vih@@6D{t#w4s=PnaY*_Io}Os~g{S+p>}OxZ5kuE_R! z5%q~xeB}$G*IaW|+7Qo};C{)bB$Og;CuPV8d>=>pSD}HE$57OG`cNO%C?&b}C+CRS zu<<;5Gdf#DL%FMIKq7=J)7N#;&NGat1#JdCU#DQF@kJ2gV+{A-utSJLpY>!E6$F4* zf?sf83P4U!1ZZqJ23U10phoNZK{^W&z3Qcv6B9N&Hh|q1ez&l>9V_^o@&;~fs~P{t z^XyQI2tC9UBcwE*%7FjoN6X#V3~>p$a`P;PyHjEkA|;K|ZbWX!tY&ReA@)G?u#g=q za)W$&nH(vj{+se^qgEiK7`f$|UE?^46AF!5Nq!P$dMXIE#BJfI<+C-XqRX3t7&8XZ z*kvx~Gnmi}*l2{OV|yXY|2%$yvv(=O`I-qHN;7U(0QV!!&~#>2u^0}s4Yed$luaC@ z$iDml^vqaLNYBnzA~2m{^=Wo-T~eXnG~-=o$gb=g7fDG3NGzRIk6T zWo2c9H*_O#{s=Zy&VJoQvwH9`GFl`36wHVrxe|=FVZ5>bTlMu>*Fx;%*1NAz5Gmo= z07ufzZBW@($D}4)(R7%566=J$yrhM-29d2w#VO9Oc!L8%$Lo`&`mqpcW{ zZU}s>qibt+RB9T2&ISE6qJ(b4O}3ASRBtRb1bd`=#o@8 zn;_GPMe4v>KFkl^u!$FdX*(WTp}@qQv?oZjpb`t7W3k5v7@*fyFYHQeg1w9Zh1K-G zU^0EqMCb)Cb4J1(kFyPi7eKz}nCq|X-U0?AJ`L6B%z%)U|C8?g?NR;fO?ma)?@%J3em}STU zCaZ)JuaL*K8oQ9=OuH8sD<)MH*2=~TI5%}&-7%Fw2TW)C&L|qWw`vr>;%IbQwh$w0 zXa+VKS(WgGE=*Hv3ho{M=dmk@`*nI7dVn(h|CWLOxA!*pYW#oy_&+Bpi_pGHWf-Jy zshKC^_2IuA(WQ)C-CclyqrAOc(*4lnR{P2No-X z|6EOT145w8{>SA1c6au6YxuuM`yXBHDYSFv2i|ip7nz?+?3wiftM{dThrc=cKR>`f z5;U9LLiVRJ_;0schW*b@t5v`M4@CddkYi2L!Q&+n$(wGWMRqq!8^GB7(R6bm^Hrrb zJo@LO*k3elHcG~)KFuv*of>-y*UBvebkJcGh#XVVO03A5DcaLXq(_?}A~dVONb9nx zbW_>nqFIuKT+b{DgPg}yefR7zn&JQ$YV&qeERK341 zG@%E*2YCv~rfN6d)RT#@{2ve%@GVLpl+-i(%}}R-XiPqR4)k+Jfg$`kOAu(Z6$NYs zcOEqn2{EQ3&}WP)`#;M7stE>(0z|}o0!g3VlPj&gaRizc-PkfIe@ZclAipp*rBMH? V|JDEMfA{ Date: Fri, 17 Apr 2026 16:52:00 +0530 Subject: [PATCH 023/203] more changes --- app/definitions/rest/helpers/index.ts | 2 +- app/definitions/rest/v1/chat.ts | 17 +++++ app/definitions/rest/v1/invites.ts | 4 +- app/ee/omnichannel/lib/index.ts | 6 +- app/lib/methods/loadMessagesForRoom.ts | 23 ++++-- app/lib/methods/loadMissedMessages.ts | 1 + app/lib/services/restApi.ts | 20 ++--- app/lib/services/sdk.ts | 100 +++++++++++++++++++++++-- 8 files changed, 147 insertions(+), 26 deletions(-) diff --git a/app/definitions/rest/helpers/index.ts b/app/definitions/rest/helpers/index.ts index 285b25c0907..1f585141ba0 100644 --- a/app/definitions/rest/helpers/index.ts +++ b/app/definitions/rest/helpers/index.ts @@ -26,7 +26,7 @@ type OperationsByPathPatternAndMethod< ? { pathPattern: TPathPattern; method: TMethod; - path: ReplacePlaceholders; + path: `/v1/${ReplacePlaceholders}`; params: GetParams; result: GetResult; } diff --git a/app/definitions/rest/v1/chat.ts b/app/definitions/rest/v1/chat.ts index f611c829423..b37a0c07187 100644 --- a/app/definitions/rest/v1/chat.ts +++ b/app/definitions/rest/v1/chat.ts @@ -56,6 +56,23 @@ export type ChatEndpoints = { }; }; }; + 'chat.syncMessages': { + GET: (params: { + roomId: IServerRoom['_id']; + offset?: number; + count?: number; + sort?: Record; + lastUpdate?: string; + next?: number; + previous?: number; + type?: 'DELETED' | 'UPDATED'; + }) => { + result: { + updated: IMessageFromServer[]; + deleted: IMessageFromServer[]; + } + }; + }; 'chat.delete': { POST: (params: { msgId: string; roomId: string }) => { _id: string; diff --git a/app/definitions/rest/v1/invites.ts b/app/definitions/rest/v1/invites.ts index e7779198494..d5846466816 100644 --- a/app/definitions/rest/v1/invites.ts +++ b/app/definitions/rest/v1/invites.ts @@ -8,7 +8,7 @@ export type InvitesEndpoints = { 'removeInvite/:_id': { DELETE: () => void; }; - '/v1/useInviteToken': { + 'useInviteToken': { POST: (params: { token: string }) => { room: { rid: IServerRoom['_id']; @@ -19,7 +19,7 @@ export type InvitesEndpoints = { }; }; }; - '/v1/validateInviteToken': { + 'validateInviteToken': { POST: (params: { token: string }) => { valid: boolean }; }; }; diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 9599d64b4a0..8dce971a320 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -10,7 +10,7 @@ export const isOmnichannelStatusAvailable = (statusLivechat: string | undefined) export const changeLivechatStatus = () => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '8.0.0')) { - return sdk.post('livechat/agent.status'); + return sdk.post('/v1/livechat/agent.status'); } // Method removed in 8.0.0 return sdk.methodCallWrapper('livechat:changeLivechatStatus'); @@ -31,7 +31,7 @@ export const getInquiriesQueued = (serverVersion: string) => { // @ts-ignore export const takeInquiry = (inquiryId: string, serverVersion: string) => { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.11.0')) { - return sdk.post('livechat/inquiries.take' as any, { inquiryId }); + return sdk.post('/v1/livechat/inquiries.take', { inquiryId }); } // Method removed in 8.0.0 return sdk.methodCallWrapper('livechat:takeInquiry', inquiryId); @@ -41,7 +41,7 @@ export const takeInquiry = (inquiryId: string, serverVersion: string) => { export const takeResume = (roomId: string) => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '8.0.0')) { - return sdk.post('livechat/room.resumeOnHold', { roomId }); + return sdk.post('/v1/livechat/room.resumeOnHold', { roomId }); } // Method removed in 8.0.0 return sdk.methodCallWrapper('livechat:resumeOnHold', roomId); diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts index 91b15757fe9..13924dc5898 100644 --- a/app/lib/methods/loadMessagesForRoom.ts +++ b/app/lib/methods/loadMessagesForRoom.ts @@ -7,7 +7,7 @@ import { type RoomTypes, roomTypeToApiType } from './roomTypeToApiType'; import sdk from '../services/sdk'; import updateMessages from './updateMessages'; import { generateLoadMoreId } from './helpers/generateLoadMoreId'; -import { type ChatGetMessage } from '@rocket.chat/rest-typings'; +import { type ChannelsHistoryParams, type GroupsHistoryParams, type ImHistoryParams } from '@rocket.chat/api-typings'; const COUNT = 50; @@ -25,18 +25,31 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t: return; } - const params: ChatGetMessage = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) }; + let params; + switch (apiType) { + case 'channels': + params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) } as ChannelsHistoryParams; + break; + case 'groups': + params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) } as GroupsHistoryParams; + break; + case 'im': + params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) } as ImHistoryParams; + break; + default: + return; + } let data; switch (apiType) { case 'channels': - data = await sdk.get('/v1/chat.getMessage', params); + data = await sdk.get('/v1/channels.history', params); break; case 'groups': - data = await sdk.get('groups.history', params); + data = await sdk.get('/v1/groups.history', params); break; case 'im': - data = await sdk.get('im.history', params); + data = await sdk.get('/v1/im.history', params); break; default: return; diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index 0c92a2a7d29..229a2706682 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -9,6 +9,7 @@ const count = 50; const syncMessages = async ({ roomId, next, type }: { roomId: string; next: number; type: 'UPDATED' | 'DELETED' }) => { const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, next: next.toString(), count, type }); + console.log('result', result); return result; }; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 89a59dfc025..57f4700e943 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -109,7 +109,7 @@ export const forgotPassword = (email: string) => export const sendConfirmationEmail = (email: string): Promise<{ success: boolean }> => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '8.0.0')) { - return sdk.post('users.sendConfirmationEmail', { email }); + return sdk.post('/v1/users.sendConfirmationEmail', { email }); } return sdk.methodCallWrapper('sendConfirmationEmail', email); @@ -411,7 +411,7 @@ export const closeLivechat = (rid: string, comment?: string, tags?: string[]) => params = { tags }; } if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '8.0.0')) { - return sdk.post('livechat/room.closeByUser', { rid, comment, ...params }); + return sdk.post('/v1/livechat/room.closeByUser', { rid, comment, ...params }); } // RC 0.29.0 return sdk.methodCallWrapper('livechat:closeRoom', rid, comment, { clientAction: true, ...params }); @@ -443,7 +443,7 @@ export const onHoldLivechat = (roomId: string) => sdk.post('/v1/livechat/room.on export const forwardLivechat = (transferData: any) => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '8.0.0')) { - return sdk.post('livechat/room.forward', transferData); + return sdk.post('/v1/livechat/room.forward', transferData); } // RC 0.36.0 return sdk.methodCallWrapper('livechat:transfer', transferData); @@ -482,7 +482,7 @@ export const getRoutingConfig = async (): Promise<{ }> => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.11.0')) { - const result = await sdk.get('livechat/config/routing'); + const result = await sdk.get('/v1/livechat/config/routing'); if (result.success) { return result.config; } @@ -495,7 +495,7 @@ export const getRoutingConfig = async (): Promise<{ export const getTagsList = async (): Promise => { const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '8.0.0')) { - const result = await sdk.get('livechat/tags'); + const result = await sdk.get('/v1/livechat/tags'); if (result.success) { return result.tags || []; } @@ -698,7 +698,7 @@ export const getAvatarSuggestion = async (): Promise<{ [service: string]: IAvata if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '5.4.0')) { // RC 5.4.0 - const result = await sdk.get('users.getAvatarSuggestion'); + const result = await sdk.get('/v1/users.getAvatarSuggestion'); if (result.success && 'suggestions' in result) { return result.suggestions; } @@ -984,17 +984,17 @@ export const emitTyping = (room: IRoom, typing = true, args: { tmid?: string } = return sdk.methodCall('stream-notify-room', `${room}/typing`, name, typing); }; -export function e2eResetOwnKey(userId: string): Promise<{ success?: boolean }> { +export function e2eResetOwnKey(): Promise<{ success?: boolean }> { // {} when TOTP is enabled unsubscribeRooms(); // RC 3.6.0 - sdk.post('/v1/users.resetE2EKey', { userId }); + sdk.post('/v1/users.resetE2EKey'); - return Promise.resolve({ success: true }); + return Promise.resolve({ success: true }); } -export function e2eResetRoomKey(rid: string, e2eKey: string, e2eKeyId: string): Promise { +export function e2eResetRoomKey(rid: string, e2eKey: string, e2eKeyId: string) { return sdk.post('/v1/e2e.resetRoomKey', { rid, e2eKey, e2eKeyId }); } diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index ba9d68c6ebb..ed69838ceb8 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -9,14 +9,20 @@ import { store as reduxStore } from '../store/auxStore'; import { random } from '../methods/helpers'; import UserPreferences from '../methods/userPreferences'; import { BASIC_AUTH_KEY } from '../methods/helpers/fetch'; +import { + type Serialized, + type MatchPathPattern, + type OperationParams, + type PathFor, + type ResultFor +} from '../../definitions/rest/helpers'; class Sdk { private sdk: DDPSDK | undefined; private code: any = null; private headers: Record = { - 'User-Agent': `RC Mobile; ${ - Platform.OS - } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` + 'User-Agent': `RC Mobile; ${Platform.OS + } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` }; async initialize(server: string): Promise { @@ -60,6 +66,7 @@ class Sdk { return this.headers; } + /* get: DDPSDK['rest']['get'] = (...args: Parameters) => { const [endpoint, params, options] = args; const sdk = this.ensureInitialized(); @@ -95,6 +102,89 @@ class Sdk { } }); }; + */ + + get>( + endpoint: TPath, + params: void extends OperationParams<'GET', MatchPathPattern> + ? void + : Serialized>> = undefined as void extends OperationParams< + 'GET', + MatchPathPattern + > + ? void + : Serialized>> + ): Promise>>> { + const sdk = this.ensureInitialized(); + // @ts-ignore + return sdk.rest.get(endpoint, params); + } + + post>( + endpoint: TPath, + params: void extends OperationParams<'POST', MatchPathPattern> + ? void + : Serialized>> = undefined as void extends OperationParams< + 'POST', + MatchPathPattern + > + ? void + : Serialized>> + ): Promise>> { + return new Promise(async (resolve, reject) => { + const isMethodCall = endpoint?.startsWith('method.call/'); + try { + const sdk = this.ensureInitialized(); + // @ts-ignore + const result = await sdk.rest.post(endpoint, params); + + /** + * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, + * responses have a different object structure + */ + if (isMethodCall) { + const response = JSON.parse(result.message); + if (response?.error) { + throw response.error; + } + return resolve(response.result); + } + return resolve(result); + } catch (e: any) { + const errorType = isMethodCall ? e?.error : e?.data?.errorType; + const totpInvalid = 'totp-invalid'; + const totpRequired = 'totp-required'; + if ([totpInvalid, totpRequired].includes(errorType)) { + const { details } = isMethodCall ? e : e?.data; + try { + await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); + return resolve(this.post(endpoint, params)); + } catch { + // twoFactor was canceled + return resolve({} as any); + } + } else { + reject(e); + } + } + }); + } + + delete>( + endpoint: TPath, + params: void extends OperationParams<'DELETE', MatchPathPattern> + ? void + : Serialized>> = undefined as void extends OperationParams< + 'DELETE', + MatchPathPattern + > + ? void + : Serialized>> + ): Promise>>> { + const sdk = this.ensureInitialized(); + // @ts-ignore + return sdk.rest.delete(endpoint, params); + } async twoFactorHandler({ method, @@ -192,7 +282,7 @@ class Sdk { onStreamData(name: string, callback: (...data: any) => void): Promise<{ stop: () => void }> { return new Promise((resolve) => { if (!this.current) { - resolve({ stop: () => {} }); + resolve({ stop: () => { } }); return; } const listener = this.current.client.onCollection(name, (ddpMessage: any) => { @@ -238,7 +328,7 @@ class Sdk { const [key, args] = Array.isArray(data) ? data : [data]; if (!this.current) { - return { stop: () => {} }; + return { stop: () => { } }; } const subscription = this.current.client.subscribe(`stream-${name}`, key, { useCollection: false, args: [args] }); From 6d04d2cb1b607d7872ecf1fe6a14d1ca89093d42 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:19:28 +0530 Subject: [PATCH 024/203] some more type fix --- app/definitions/rest/v1/auth.ts | 36 +++++++++++++++++++ app/definitions/rest/v1/index.ts | 4 ++- app/lib/services/sdk.ts | 9 ++++- app/sagas/troubleshootingNotification.ts | 2 +- app/views/RoomMembersView/helpers.ts | 2 +- app/views/RoomView/index.tsx | 2 +- app/views/SetUsernameView.tsx | 2 +- app/views/TeamChannelsView.tsx | 4 +-- app/views/ThreadMessagesView/index.tsx | 2 +- .../UserNotificationPreferencesView/index.tsx | 2 +- 10 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 app/definitions/rest/v1/auth.ts diff --git a/app/definitions/rest/v1/auth.ts b/app/definitions/rest/v1/auth.ts new file mode 100644 index 00000000000..5ba739427a8 --- /dev/null +++ b/app/definitions/rest/v1/auth.ts @@ -0,0 +1,36 @@ +import { type IUserEmail, type IUserSettings } from '../../IUser'; +import { type TUserStatus } from '../../TUserStatus'; + +export interface ILoginMeResponse { + _id: string; + name: string; + emails: IUserEmail[]; + status: TUserStatus; + statusConnection: string; + username: string; + utcOffset: number; + active: boolean; + roles: string[]; + settings: IUserSettings; + avatarUrl: string; +} + +export interface ILoginDataResponse { + authToken: string; + userId: string; + me: ILoginMeResponse; +} + +export type AuthEndpoints = { + 'login': { + POST: (params: { + user?: string; + password?: string; + resume?: string; + code?: string; + }) => { + status: string; + data: ILoginDataResponse; + }; + }; +}; diff --git a/app/definitions/rest/v1/index.ts b/app/definitions/rest/v1/index.ts index 0642359a1bb..1cc1b9e2fef 100644 --- a/app/definitions/rest/v1/index.ts +++ b/app/definitions/rest/v1/index.ts @@ -1,3 +1,4 @@ +import { type AuthEndpoints } from './auth'; import { type ChannelsEndpoints } from './channels'; import { type ChatEndpoints } from './chat'; import { type CustomUserStatusEndpoints } from './customUserStatus'; @@ -22,7 +23,8 @@ import { type DirectoryEndpoint } from './directory'; import { type AutoTranslateEndpoints } from './autotranslate'; import { type ModerationEndpoints } from './moderation'; -export type Endpoints = ChannelsEndpoints & +export type Endpoints = AuthEndpoints & + ChannelsEndpoints & ChatEndpoints & CustomUserStatusEndpoints & DmEndpoints & diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index ed69838ceb8..91113fdb124 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -143,6 +143,7 @@ class Sdk { * responses have a different object structure */ if (isMethodCall) { + // @ts-ignore const response = JSON.parse(result.message); if (response?.error) { throw response.error; @@ -201,6 +202,10 @@ class Sdk { async login(credentials: any): Promise { try { const loginResult = await this.post('/v1/login', credentials); + if (!loginResult?.success) { + return Promise.reject(new Error('Invalid response from server')); + } + // TODO: get/set headers from SDK instead? this.setHeaders({ 'X-Auth-Token': loginResult.data.authToken, 'X-User-Id': loginResult.data.userId }); await this.current?.account.loginWithToken(loginResult.data.authToken); @@ -238,7 +243,9 @@ class Sdk { const { user } = reduxStore.getState().login; if (API_Use_REST_For_DDP_Calls) { const url = isEmpty(user) ? 'method.callAnon' : 'method.call'; - const result = (await this.post(`/v1/${url}/${method}`, { + // TODO: fix this type + // @ts-ignore + const result = (await this.post(`/v1/${url}/${method}` as any, { message: EJSON.stringify({ msg: 'method', id: random(10), method, params }) })) as any; const response = JSON.parse(result.message) as any; diff --git a/app/sagas/troubleshootingNotification.ts b/app/sagas/troubleshootingNotification.ts index f1e12f723b7..18fc0425787 100644 --- a/app/sagas/troubleshootingNotification.ts +++ b/app/sagas/troubleshootingNotification.ts @@ -28,7 +28,7 @@ function* init() { try { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.5.0')) { const pushInfoResult = yield* call(pushInfo); - if (pushInfoResult) { + if (pushInfoResult && pushInfoResult.success) { pushGatewayEnabled = pushInfoResult.pushGatewayEnabled; defaultPushGateway = pushInfoResult.defaultPushGateway; } diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index 6d688023986..578d72a5585 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -39,7 +39,7 @@ export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, up try { const type = roomType; const result = await getRoomRoles(rid, type); - if (result) { + if (result && result.success) { updateState({ roomRoles: result.roles }); } } catch (e) { diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 8768c2cc186..7d9e9ca2cdd 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -700,7 +700,7 @@ class RoomView extends React.Component { this.setState({ roomUserId }, () => this.setHeader()); const result = await getUserInfo(roomUserId); - if (result) { + if (result && result.success) { return result.user; } } catch (e) { diff --git a/app/views/SetUsernameView.tsx b/app/views/SetUsernameView.tsx index 877653380c7..a53800a5ee9 100644 --- a/app/views/SetUsernameView.tsx +++ b/app/views/SetUsernameView.tsx @@ -63,7 +63,7 @@ const SetUsernameView = () => { useEffect(() => { const init = async () => { const suggestion = await getUsernameSuggestion(); - if (suggestion) { + if (suggestion && suggestion.success) { setValue('username', suggestion.result, { shouldValidate: true }); } }; diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 57d480fb316..eadd1e5e94a 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -340,7 +340,7 @@ class TeamChannelsView extends React.Component result.room._id !== room._id); this.setState({ data: newData }); } diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index a5e62bc322b..7fbcb7ad497 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -341,7 +341,7 @@ class ThreadMessagesView extends React.Component { async function getPreferences() { try { const result = await getUserPreferences(); - if (result.preferences) { + if (result && result.success) { setLoading(false); setPreferences(result.preferences as INotificationPreferences); } From 9b3077b7b5cf9977ade78ab12051355888ef96e7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:44:45 +0530 Subject: [PATCH 025/203] more api changse --- .../InAppNotification/IncomingCallNotification/index.tsx | 2 +- app/containers/MessageActions/index.tsx | 2 +- app/containers/MessageComposer/hooks/useAutocomplete.ts | 4 ++-- app/containers/TwoFactor/index.tsx | 2 +- app/containers/UIKit/VideoConferenceBlock/index.tsx | 2 +- app/lib/encryption/encryption.ts | 2 +- app/lib/encryption/room.ts | 2 +- app/lib/hooks/useEndpointData.test.tsx | 2 +- app/lib/hooks/useUserData.ts | 2 +- app/lib/methods/getCustomEmojis.ts | 2 +- app/lib/methods/getRoles.ts | 2 +- app/lib/methods/getSingleMessage.ts | 5 ++--- app/lib/methods/loadMissedMessages.ts | 5 ++--- app/lib/methods/sendMessage.ts | 2 +- app/lib/services/connect.ts | 8 ++++---- app/sagas/videoConf.ts | 2 +- app/views/LivechatEditView.tsx | 2 +- app/views/ReadReceiptView/index.tsx | 2 +- app/views/RoomActionsView/index.tsx | 6 +++--- app/views/RoomInfoView/index.tsx | 8 ++++---- app/views/SearchMessagesView/index.tsx | 8 ++++---- 21 files changed, 35 insertions(+), 37 deletions(-) diff --git a/app/containers/InAppNotification/IncomingCallNotification/index.tsx b/app/containers/InAppNotification/IncomingCallNotification/index.tsx index 65435e92c00..a886ab6e3e9 100644 --- a/app/containers/InAppNotification/IncomingCallNotification/index.tsx +++ b/app/containers/InAppNotification/IncomingCallNotification/index.tsx @@ -133,7 +133,7 @@ const IncomingCallNotification = ({ }: { notification: { rid: string; callId: string }; }): React.ReactElement | null => { - const { result } = useEndpointData('video-conference.info', { callId }); + const { result } = useEndpointData('/v1/video-conference.info', { callId }); const user = useUserData(rid); diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx index f0b8b182173..0bd6d9bd259 100644 --- a/app/containers/MessageActions/index.tsx +++ b/app/containers/MessageActions/index.tsx @@ -292,7 +292,7 @@ const MessageActions = React.memo( const handleReplyInDM = async (message: TAnyMessageModel) => { if (message?.u?.username) { const result = await createDirectMessage(message.u.username); - if (result) { + if (result?.success) { const { room } = result; const params = { rid: room.rid, diff --git a/app/containers/MessageComposer/hooks/useAutocomplete.ts b/app/containers/MessageComposer/hooks/useAutocomplete.ts index 78ba67cbddc..84b1f853b90 100644 --- a/app/containers/MessageComposer/hooks/useAutocomplete.ts +++ b/app/containers/MessageComposer/hooks/useAutocomplete.ts @@ -162,7 +162,7 @@ export const useAutocomplete = ({ return; } const response = await getCommandPreview(text, rid, commandParams); - if (response.preview) { + if (response?.success && response?.preview) { const previewItems = (response.preview?.items || []).map(item => ({ id: item.id, preview: item, @@ -179,7 +179,7 @@ export const useAutocomplete = ({ } if (type === '!') { const res = await getListCannedResponse({ text }); - if (res.cannedResponses) { + if (res?.success && res?.cannedResponses) { if (res.cannedResponses.length === 0) { setItems([ { diff --git a/app/containers/TwoFactor/index.tsx b/app/containers/TwoFactor/index.tsx index d0ea4584259..4fed0621948 100644 --- a/app/containers/TwoFactor/index.tsx +++ b/app/containers/TwoFactor/index.tsx @@ -94,7 +94,7 @@ const TwoFactor = React.memo(() => { try { if (params?.user) { clearErrors(); - const response = await sendEmailCode(params?.user); + await sendEmailCode(params?.user); showToast(I18n.t('Two_Factor_Success_message')); } diff --git a/app/containers/UIKit/VideoConferenceBlock/index.tsx b/app/containers/UIKit/VideoConferenceBlock/index.tsx index 7d7a784ba89..e8676b0b697 100644 --- a/app/containers/UIKit/VideoConferenceBlock/index.tsx +++ b/app/containers/UIKit/VideoConferenceBlock/index.tsx @@ -8,7 +8,7 @@ import VideoConferenceSkeletonLoading from './components/VideoConferenceSkeleton import VideoConferenceIssue from './components/VideoConferenceIssue'; export default function VideoConferenceBlock({ callId, blockId }: { callId: string; blockId: string }): React.ReactElement { - const { result, error } = useEndpointData('video-conference.info', { callId }); + const { result, error } = useEndpointData('/v1/video-conference.info', { callId }); if (result?.success) { const { users, type, status, createdBy, rid } = result; diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index d80d12a89df..3db51c203e0 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -490,7 +490,7 @@ class Encryption { } const result = await fetchUsersWaitingForGroupKey(sampleIds); - if (!Object.keys(result.usersWaitingForE2EKeys).length) { + if (!result?.success || !Object.keys(result.usersWaitingForE2EKeys).length) { return; } diff --git a/app/lib/encryption/room.ts b/app/lib/encryption/room.ts index 506face4186..2448c9362e2 100644 --- a/app/lib/encryption/room.ts +++ b/app/lib/encryption/room.ts @@ -314,7 +314,7 @@ export default class EncryptionRoom { try { const decryptedOldGroupKeys = await this.exportOldRoomKeys(this.subscription?.oldRoomKeys); const result = await e2eGetUsersOfRoomWithoutKey(this.roomId); - if (result.users) { + if (result?.success && result.users) { const { users } = result; if (!users.length) { return; diff --git a/app/lib/hooks/useEndpointData.test.tsx b/app/lib/hooks/useEndpointData.test.tsx index b32d75b422f..feae388db58 100644 --- a/app/lib/hooks/useEndpointData.test.tsx +++ b/app/lib/hooks/useEndpointData.test.tsx @@ -5,7 +5,7 @@ import { View, Text } from 'react-native'; import { useEndpointData } from './useEndpointData'; import sdk from '../services/sdk'; -const url = 'chat.getMessage'; +const url = '/v1/chat.getMessage'; const message = { _id: '9tYkmJ67wMwmvQouD', diff --git a/app/lib/hooks/useUserData.ts b/app/lib/hooks/useUserData.ts index c1fc3e7b9a3..6176266d1e3 100644 --- a/app/lib/hooks/useUserData.ts +++ b/app/lib/hooks/useUserData.ts @@ -28,7 +28,7 @@ const useUserData = (rid: string) => { } else { try { const result = await getUserInfo(rid); - if (result.user) { + if (result.success && result.user) { const { user } = result; const username = useRealName && user.name ? user.name : user.username; setUser({ diff --git a/app/lib/methods/getCustomEmojis.ts b/app/lib/methods/getCustomEmojis.ts index c3c04ad5f7a..2ec863ec59b 100644 --- a/app/lib/methods/getCustomEmojis.ts +++ b/app/lib/methods/getCustomEmojis.ts @@ -132,7 +132,7 @@ export function getCustomEmojis() { // RC 0.75.0 const result = await sdk.get('/v1/emoji-custom.list', params); - if (!result) { + if (!result || !result.success) { return resolve(); } diff --git a/app/lib/methods/getRoles.ts b/app/lib/methods/getRoles.ts index fc2f4a54319..aaaa7d71fbf 100644 --- a/app/lib/methods/getRoles.ts +++ b/app/lib/methods/getRoles.ts @@ -76,7 +76,7 @@ export function getRoles(): Promise { // RC 0.70.0 const result = await sdk.get('/v1/roles.list'); - if (!result.roles) { + if (!result.success) { return resolve(); } diff --git a/app/lib/methods/getSingleMessage.ts b/app/lib/methods/getSingleMessage.ts index 146239b7957..f347d44e6f9 100644 --- a/app/lib/methods/getSingleMessage.ts +++ b/app/lib/methods/getSingleMessage.ts @@ -1,11 +1,10 @@ -import { type IMessage } from '@rocket.chat/core-typings'; import { getSingleMessage as getSingleMessageService } from '../services/restApi'; -const getSingleMessage = (messageId: string): Promise => +const getSingleMessage = (messageId: string) => new Promise(async (resolve, reject) => { try { const result = await getSingleMessageService(messageId); - if (result.message) { + if (result?.success && result.message) { return resolve(result.message); } return reject(); diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index 229a2706682..b0397a063d2 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -8,9 +8,8 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; const count = 50; const syncMessages = async ({ roomId, next, type }: { roomId: string; next: number; type: 'UPDATED' | 'DELETED' }) => { - const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, next: next.toString(), count, type }); - console.log('result', result); - return result; + const { result, success } = await sdk.get('/v1/chat.syncMessages', { roomId, next: next.toString(), count, type }); + return success ? result : null; }; const getSyncMessagesFromCursor = async ( diff --git a/app/lib/methods/sendMessage.ts b/app/lib/methods/sendMessage.ts index c3762bf77d9..f9c8518be0a 100644 --- a/app/lib/methods/sendMessage.ts +++ b/app/lib/methods/sendMessage.ts @@ -52,7 +52,7 @@ async function sendMessageCall(message: any) { const { _id, tmid } = message; try { const result = await sdk.post('/v1/chat.sendMessage', { message }); - if (result.message) { + if (result?.success && result?.message) { // @ts-ignore return changeMessageStatus(_id, messagesStatus.SENT, tmid, result.message); } diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 2d56a2534c5..892bd5c3e7f 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -228,7 +228,7 @@ function stopListener(listener: any): boolean { return listener && listener.stop(); } -async function login(credentials: ICredentials, isFromWebView = false): Promise { +async function login(credentials: ICredentials): Promise { // TODO: other login methods: ldap, saml, cas, apple, oauth, oauth_custom const result = await sdk.login(credentials); const { me } = result; @@ -272,10 +272,10 @@ async function login(credentials: ICredentials, isFromWebView = false): Promise< } } -function loginTOTP(params: ICredentials, loginEmailPassword?: boolean, isFromWebView = false): Promise { +function loginTOTP(params: ICredentials, loginEmailPassword?: boolean): Promise { return new Promise(async (resolve, reject) => { try { - const result = await login(params, isFromWebView); + const result = await login(params); if (result) { return resolve(result); } @@ -343,7 +343,7 @@ function loginWithPassword({ user, password }: { user: string; password: string } async function loginOAuthOrSso(params: ICredentials, isFromWebView = true) { - const result = await loginTOTP(params, false, isFromWebView); + const result = await loginTOTP(params, false); store.dispatch(loginRequest({ resume: result.token }, false, isFromWebView)); } diff --git a/app/sagas/videoConf.ts b/app/sagas/videoConf.ts index 7838ac4e75f..0035c24d8d2 100644 --- a/app/sagas/videoConf.ts +++ b/app/sagas/videoConf.ts @@ -175,7 +175,7 @@ function* initCall({ payload: { mic, cam, direct, rid } }: { payload: TCallProps if (isServer5OrNewer) { try { const videoConfResponse = yield* call(videoConferenceStart, rid); - if (videoConfResponse.data) { + if (videoConfResponse.success && videoConfResponse.data) { if (direct && videoConfResponse.data.type === 'direct') { yield call(callUser, { rid, uid: videoConfResponse.data.calleeId, callId: videoConfResponse.data.callId }); } else { diff --git a/app/views/LivechatEditView.tsx b/app/views/LivechatEditView.tsx index 86b9b692e1c..64e55cdfa35 100644 --- a/app/views/LivechatEditView.tsx +++ b/app/views/LivechatEditView.tsx @@ -79,7 +79,7 @@ const LivechatEditView = ({ user, navigation, route, theme }: ILivechatEditViewP const handleGetCustomFields = async () => { const result = await getCustomFields(); - if (result.customFields?.length) { + if (result.success && result.customFields?.length) { const visitorCustomFields = result.customFields .filter(field => field.visibility !== 'hidden' && field.scope === 'visitor') .map(field => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' })) diff --git a/app/views/ReadReceiptView/index.tsx b/app/views/ReadReceiptView/index.tsx index a2f9f99c56a..08644c7a132 100644 --- a/app/views/ReadReceiptView/index.tsx +++ b/app/views/ReadReceiptView/index.tsx @@ -85,7 +85,7 @@ class ReadReceiptView extends React.Component { try { if (room?.visitor?._id) { const result = await getVisitorInfo(room.visitor._id); - if (result) { + if (result.success && result.visitor) { const { visitor } = result; const params: { os?: string; browser?: string } = {}; if (visitor.userAgent) { @@ -166,7 +166,7 @@ const RoomInfoView = (): React.ReactElement => { try { const roomUserId = getUidDirectMessage({ ...(room || { rid, t }), itsMe }); const result = await getUserInfo(roomUserId); - if (result && result.user) { + if (result.success && result.user) { const { user } = result; const r = handleRoles(user); setRoomUser({ ...user, roles: r }); @@ -203,7 +203,7 @@ const RoomInfoView = (): React.ReactElement => { try { if (!isDirect) { const result = await getRoomInfo(rid); - if (result) setRoom({ ...room, ...(result.room as unknown as ISubscription) }); + if (result.success && result.room) setRoom({ ...room, ...(result.room as unknown as ISubscription) }); } } catch (e) { log(e); @@ -219,7 +219,7 @@ const RoomInfoView = (): React.ReactElement => { if (!isEmpty(member)) return resolve(); try { const result = await createDirectMessage(roomUser.username); - if (result?.room) return resolve({ ...roomUser, rid: result.room.rid }); + if (result.success && result.room) return resolve({ ...roomUser, rid: result.room.rid }); } catch (e) { reject(e); } diff --git a/app/views/SearchMessagesView/index.tsx b/app/views/SearchMessagesView/index.tsx index 3ae66c4aa6b..e287b0e763d 100644 --- a/app/views/SearchMessagesView/index.tsx +++ b/app/views/SearchMessagesView/index.tsx @@ -140,7 +140,7 @@ class SearchMessagesView extends React.Component => { + searchMessages = async (searchText: string) => { if (!searchText) { return []; } @@ -160,10 +160,10 @@ class SearchMessagesView extends React.Component { + if (result.success && result.messages) { + const urlRenderMessages = result.messages.map(message => { if (message.urls && message.urls.length > 0) { - message.urls = message.urls?.map((url, index) => { + message.urls = message.urls.map((url, index) => { if (url.meta) { return { _id: index, From 17c846bbc17a7135031e970a582bbb639fd8d624 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:57:53 +0530 Subject: [PATCH 026/203] more fix --- app/lib/methods/getSettings.ts | 2 +- app/lib/methods/helpers/helpers.ts | 1 - app/lib/methods/subscriptions/rooms.ts | 3 ++- app/lib/services/restApi.ts | 4 +--- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/lib/methods/getSettings.ts b/app/lib/methods/getSettings.ts index 93aa99e694b..7ff241ab89e 100644 --- a/app/lib/methods/getSettings.ts +++ b/app/lib/methods/getSettings.ts @@ -165,7 +165,7 @@ export async function getSettings(): Promise { do { // TODO: why is no-await-in-loop enforced in the first place? /* eslint-disable no-await-in-loop */ - const response = await fetch(`https://${url}&offset=${offset}`); + const response = await fetch(`${url}&offset=${offset}`); const result = await response.json(); if (!result.success) { diff --git a/app/lib/methods/helpers/helpers.ts b/app/lib/methods/helpers/helpers.ts index 7f58c0b8c8e..08fb78fedb2 100644 --- a/app/lib/methods/helpers/helpers.ts +++ b/app/lib/methods/helpers/helpers.ts @@ -111,7 +111,6 @@ export async function hasPermission(permissions, rid?: any): Promise const loginUser = reduxStore.getState().login.user; const userRoles = loginUser?.roles || []; const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; - console.log('channel.edit-1', permissions, mergedRoles); return permissions.map(permission => permission?.some(r => mergedRoles.includes(r) ?? false)); } catch (e) { log(e); diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 5787d8363dd..7914f5b7278 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -440,7 +440,8 @@ export default function subscribeRooms() { try { // set the server that started this task subServer = sdk.current?.connection.url || ''; - sdk.subscribeRaw('stream-notify-user', 'user'); + const { id: userId } = store.getState().login.user; + sdk.subscribeRaw('stream-notify-user', userId); roomsSubscription = { stop: () => stop() }; return null; } catch (e) { diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index c7fa476de85..d8ab77d1203 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1022,9 +1022,7 @@ export function e2eResetOwnKey(): Promise<{ success?: boolean }> { unsubscribeRooms(); // RC 3.6.0 - sdk.post('/v1/users.resetE2EKey'); - - return Promise.resolve({ success: true }); + return sdk.post('/v1/users.resetE2EKey'); } export function e2eResetRoomKey(rid: string, e2eKey: string, e2eKeyId: string) { From 9d30eb5a4b1c40902ce80bebf0c4678eae2aa8f6 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:14:18 +0530 Subject: [PATCH 027/203] making code rabbit happy --- app/containers/Avatar/useAvatarETag.ts | 2 +- .../components/SendThreadToChannel.tsx | 4 ++-- .../MessageComposer/hooks/useAutocomplete.ts | 6 +++++ app/containers/TwoFactor/index.tsx | 7 +++--- app/definitions/rest/v1/chat.ts | 2 +- app/definitions/rest/v1/invites.ts | 4 ++-- app/ee/omnichannel/lib/index.ts | 3 +-- app/lib/encryption/encryption.ts | 2 +- app/lib/methods/getSettings.ts | 2 +- app/lib/methods/getSingleMessage.ts | 7 +++--- app/lib/methods/loadMissedMessages.ts | 2 +- app/lib/services/connect.ts | 21 +++++++++--------- app/lib/services/restApi.ts | 22 ++++++++++++------- app/lib/services/sdk.ts | 5 ++++- app/sagas/createChannel.js | 3 +-- app/sagas/videoConf.ts | 4 ++++ app/views/AddExistingChannelView/index.tsx | 2 +- app/views/ReadReceiptView/index.tsx | 6 ++--- app/views/RoomActionsView/index.tsx | 2 +- app/views/RoomInfoView/index.tsx | 1 + app/views/SetUsernameView.tsx | 10 ++++++--- app/views/TeamChannelsView.tsx | 10 ++++----- app/views/ThreadMessagesView/index.tsx | 6 +++-- .../UserNotificationPreferencesView/index.tsx | 4 ++-- 24 files changed, 82 insertions(+), 55 deletions(-) diff --git a/app/containers/Avatar/useAvatarETag.ts b/app/containers/Avatar/useAvatarETag.ts index 1795d3c53a6..555f7bfe2be 100644 --- a/app/containers/Avatar/useAvatarETag.ts +++ b/app/containers/Avatar/useAvatarETag.ts @@ -49,7 +49,7 @@ export const useAvatarETag = ({ if (record) { const observable = record.observe() as unknown as Observable; - subscription = observable.subscribe((r) => { + subscription = observable.subscribe(r => { setAvatarETag(r.avatarETag); }); } diff --git a/app/containers/MessageComposer/components/SendThreadToChannel.tsx b/app/containers/MessageComposer/components/SendThreadToChannel.tsx index 6bbbef8bfed..52cbd33de32 100644 --- a/app/containers/MessageComposer/components/SendThreadToChannel.tsx +++ b/app/containers/MessageComposer/components/SendThreadToChannel.tsx @@ -1,7 +1,7 @@ import { TouchableWithoutFeedback } from 'react-native-gesture-handler'; import { StyleSheet, Text } from 'react-native'; import React, { useEffect, useRef } from 'react'; -import { Subscription } from 'rxjs/internal/Subscription'; +import { Subscription } from 'rxjs'; import { Q } from '@nozbe/watermelondb'; import { useRoomContext } from '../../../views/RoomView/context'; @@ -55,7 +55,7 @@ export const SendThreadToChannel = (): React.ReactElement | null => { if (alsoSendThreadToChannelUserPref === 'default') { const db = database.active; const observable = db.get('threads').query(Q.where('id', tmid)).observe(); - // @ts-ignore: fix me + // @ts-ignore: fix me subscription.current = observable.subscribe(result => { setAlsoSendThreadToChannel(!result.length); }); diff --git a/app/containers/MessageComposer/hooks/useAutocomplete.ts b/app/containers/MessageComposer/hooks/useAutocomplete.ts index 84b1f853b90..d2e3ed02d0f 100644 --- a/app/containers/MessageComposer/hooks/useAutocomplete.ts +++ b/app/containers/MessageComposer/hooks/useAutocomplete.ts @@ -175,6 +175,9 @@ export const useAutocomplete = ({ updateAutocompleteVisible(true); accessibilityFocusOnInput(); } + } else { + setItems([]); + updateAutocompleteVisible(false); } } if (type === '!') { @@ -203,6 +206,9 @@ export const useAutocomplete = ({ updateAutocompleteVisible(true); accessibilityFocusOnInput(); } + } else if (res?.success && !res.cannedResponses) { + setItems([]); + updateAutocompleteVisible(false); } } } catch (e) { diff --git a/app/containers/TwoFactor/index.tsx b/app/containers/TwoFactor/index.tsx index 4fed0621948..62584346aac 100644 --- a/app/containers/TwoFactor/index.tsx +++ b/app/containers/TwoFactor/index.tsx @@ -94,9 +94,10 @@ const TwoFactor = React.memo(() => { try { if (params?.user) { clearErrors(); - await sendEmailCode(params?.user); - - showToast(I18n.t('Two_Factor_Success_message')); + const result = await sendEmailCode(params?.user); + if (result) { + showToast(I18n.t('Two_Factor_Success_message')); + } } } catch (e) { log(e); diff --git a/app/definitions/rest/v1/chat.ts b/app/definitions/rest/v1/chat.ts index b37a0c07187..21a1a625da4 100644 --- a/app/definitions/rest/v1/chat.ts +++ b/app/definitions/rest/v1/chat.ts @@ -70,7 +70,7 @@ export type ChatEndpoints = { result: { updated: IMessageFromServer[]; deleted: IMessageFromServer[]; - } + }; }; }; 'chat.delete': { diff --git a/app/definitions/rest/v1/invites.ts b/app/definitions/rest/v1/invites.ts index d5846466816..f725ec2ba9b 100644 --- a/app/definitions/rest/v1/invites.ts +++ b/app/definitions/rest/v1/invites.ts @@ -8,7 +8,7 @@ export type InvitesEndpoints = { 'removeInvite/:_id': { DELETE: () => void; }; - 'useInviteToken': { + useInviteToken: { POST: (params: { token: string }) => { room: { rid: IServerRoom['_id']; @@ -19,7 +19,7 @@ export type InvitesEndpoints = { }; }; }; - 'validateInviteToken': { + validateInviteToken: { POST: (params: { token: string }) => { valid: boolean }; }; }; diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 8dce971a320..8d7289bb3ef 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -17,12 +17,11 @@ export const changeLivechatStatus = () => { }; // RC 2.4.0 -// @ts-ignore export const getInquiriesQueued = (serverVersion: string) => { const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') ? '/v1/livechat/inquiries.queuedForUser' : '/v1/livechat/inquiries.queued'; - return sdk.get(url as any); + return sdk.get(url); }; // this inquiry is added to the db by the subscriptions stream diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 3db51c203e0..53d770e9bf3 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -490,7 +490,7 @@ class Encryption { } const result = await fetchUsersWaitingForGroupKey(sampleIds); - if (!result?.success || !Object.keys(result.usersWaitingForE2EKeys).length) { + if (!result?.success || !result.usersWaitingForE2EKeys || typeof result.usersWaitingForE2EKeys !== 'object' || !Object.keys(result.usersWaitingForE2EKeys).length) { return; } diff --git a/app/lib/methods/getSettings.ts b/app/lib/methods/getSettings.ts index 7ff241ab89e..603a944a27f 100644 --- a/app/lib/methods/getSettings.ts +++ b/app/lib/methods/getSettings.ts @@ -155,7 +155,7 @@ export async function getSettings(): Promise { const settingsParams = Object.keys(defaultSettings).filter(key => !loginSettings.includes(key)); // RC 0.60.0 let offset = 0; - let remaining + let remaining; let settings: IData[] = []; const serverVersion = reduxStore.getState().server.version; const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') diff --git a/app/lib/methods/getSingleMessage.ts b/app/lib/methods/getSingleMessage.ts index f347d44e6f9..383101f502f 100644 --- a/app/lib/methods/getSingleMessage.ts +++ b/app/lib/methods/getSingleMessage.ts @@ -1,13 +1,14 @@ import { getSingleMessage as getSingleMessageService } from '../services/restApi'; +import { type IMessage } from '../../definitions'; -const getSingleMessage = (messageId: string) => - new Promise(async (resolve, reject) => { +const getSingleMessage = (messageId: string): Promise => + new Promise(async (resolve, reject) => { try { const result = await getSingleMessageService(messageId); if (result?.success && result.message) { return resolve(result.message); } - return reject(); + return reject(new Error('Failed to get message')); } catch (e) { return reject(e); } diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index b0397a063d2..65c042bc42f 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -8,7 +8,7 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; const count = 50; const syncMessages = async ({ roomId, next, type }: { roomId: string; next: number; type: 'UPDATED' | 'DELETED' }) => { - const { result, success } = await sdk.get('/v1/chat.syncMessages', { roomId, next: next.toString(), count, type }); + const { result, success } = await sdk.get('/v1/chat.syncMessages', { roomId, next, count, type }); return success ? result : null; }; diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 892bd5c3e7f..5a43e22b26c 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -34,12 +34,12 @@ interface IServices { service: string; } -function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise { - return new Promise(async resolve => { - if (sdk.current?.connection.url === server) { - return resolve(); - } +async function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise { + if (sdk.current?.connection.url === server) { + return; + } + try { disconnect(); database.setActiveDB(server); @@ -48,8 +48,7 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr EventEmitter.emit('INQUIRY_UNSUBSCRIBE'); await sdk.initialize(server); - resolve(); - getSettings(); + await getSettings(); sdk.current?.connection.on('connection', status => { if (['connecting', 'reconnecting'].includes(status)) { @@ -219,9 +218,10 @@ function connect({ server, logoutOnError = false }: { server: string; logoutOnEr ); sdk.onCollection('stream-force_logout', () => store.dispatch(logout(true))); - - resolve(); - }); + } catch (e) { + console.error('Connection error:', e); + throw e; + } } function stopListener(listener: any): boolean { @@ -270,6 +270,7 @@ async function login(credentials: ICredentials): Promise { diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index d8ab77d1203..ae0c800a62b 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1068,13 +1068,19 @@ export const registerPushToken = () => return resolve(); }); -export const removePushToken = (): Promise => { +export const removePushToken = async (): Promise => { const token = getDeviceToken(); if (token) { - // RC 0.60.0 - sdk.delete('/v1/push.token', { token }); + try { + // RC 0.60.0 + await sdk.delete('/v1/push.token', { token }); + return true; + } catch (e) { + console.log(e); + return false; + } } - return Promise.resolve(); + return true; }; // RC 6.6.0 @@ -1115,8 +1121,8 @@ export const getRoomMembers = async ({ }; // RC 3.16.0 const result = await sdk.get(`/v1/${roomTypeToApiType(t)}.members`, params); - if (result) { - return result?.members; + if (result?.success === true) { + return result.members; } } // RC 0.42.0 @@ -1128,14 +1134,14 @@ export const e2eFetchMyKeys = async () => { // RC 0.70.0 const result = await sdk.get('/v1/e2e.fetchMyKeys'); // snake_case -> camelCase - if (result) { + if (result?.success === true && result.public_key && result.private_key) { return { success: true, publicKey: result.public_key, privateKey: result.private_key }; } - return result; + return { success: false }; }; export const logoutOtherLocations = () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 91113fdb124..8d8a91a88e7 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -218,8 +218,11 @@ class Sdk { methodCall(...args: any[]): Promise { return new Promise(async (resolve, reject) => { try { + if (!this.current || !this.current.client) { + throw new Error('SDK not initialized'); + } // @ts-ignore - const result = await this.current?.client.callAsyncWithOptions(...args, this.code || ''); + const result = await this.current.client.callAsyncWithOptions(...args, this.code || ''); return resolve(result); } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index 93e86c2fcee..36e0c1f62b1 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -57,12 +57,12 @@ const handleRequest = function* handleRequest({ data }) { ...result?.group }; } - console.log('new channel', sub) try { const db = database.active; const subCollection = db.get('subscriptions'); yield db.write(async () => { await subCollection.create(s => { + Object.assign(s, sub); s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); s.id = sub.rid; s._id = sub.rid; @@ -77,7 +77,6 @@ const handleRequest = function* handleRequest({ data }) { s.userMentions = sub.userMentions ?? 0; s.groupMentions = sub.groupMentions ?? 0; s.ro = sub.ro ?? false; - Object.assign(s, sub); }); }); diff --git a/app/sagas/videoConf.ts b/app/sagas/videoConf.ts index 0035c24d8d2..1b66c717b54 100644 --- a/app/sagas/videoConf.ts +++ b/app/sagas/videoConf.ts @@ -183,9 +183,13 @@ function* initCall({ payload: { mic, cam, direct, rid } }: { payload: TCallProps yield call(hideActionSheetRef); yield put(setCalling(false)); } + } else { + yield put(setCalling(false)); + yield call(hideActionSheetRef); } } catch (e) { yield put(setCalling(false)); + yield call(hideActionSheetRef); showErrorAlert(i18n.t('error-init-video-conf')); log(e); } diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index 758eb00d9af..d156cfb56f6 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -138,12 +138,12 @@ const AddExistingChannelView = () => { logEvent(events.CT_ADD_ROOM_TO_TEAM); const result = await addRoomsToTeam({ rooms: selected, teamId }); if (result) { - sendLoadingEvent({ visible: false }); Navigation.resetTo(); } } catch (e: any) { logEvent(events.CT_ADD_ROOM_TO_TEAM_F); showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {}); + } finally { sendLoadingEvent({ visible: false }); } }; diff --git a/app/views/ReadReceiptView/index.tsx b/app/views/ReadReceiptView/index.tsx index 08644c7a132..0940bd8062c 100644 --- a/app/views/ReadReceiptView/index.tsx +++ b/app/views/ReadReceiptView/index.tsx @@ -87,13 +87,13 @@ class ReadReceiptView extends React.Component { try { const result = await createDirectMessage(roomUser.username); if (result.success && result.room) return resolve({ ...roomUser, rid: result.room.rid }); + return resolve(); } catch (e) { reject(e); } diff --git a/app/views/SetUsernameView.tsx b/app/views/SetUsernameView.tsx index a53800a5ee9..78650834c11 100644 --- a/app/views/SetUsernameView.tsx +++ b/app/views/SetUsernameView.tsx @@ -62,9 +62,13 @@ const SetUsernameView = () => { useEffect(() => { const init = async () => { - const suggestion = await getUsernameSuggestion(); - if (suggestion && suggestion.success) { - setValue('username', suggestion.result, { shouldValidate: true }); + try { + const suggestion = await getUsernameSuggestion(); + if (suggestion && suggestion.success) { + setValue('username', suggestion.result, { shouldValidate: true }); + } + } catch (e) { + console.log('Failed to get username suggestion:', e); } }; init(); diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index eadd1e5e94a..84092181a08 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -338,18 +338,17 @@ class TeamChannelsView extends React.Component { if (i._id === item._id) { i.teamDefault = !i.teamDefault; @@ -376,9 +375,10 @@ class TeamChannelsView extends React.Component { try { const result = await getUserPreferences(); if (result && result.success) { - setLoading(false); setPreferences(result.preferences as INotificationPreferences); } + setLoading(false); } catch (error) { setLoading(false); log(error); @@ -61,7 +61,7 @@ const UserNotificationPreferencesView = () => { try { setPreferences({ ...previousPreferences, ...param }); const result = await setUserPreferences(userId, param); - if (!result) { + if (!result || result.success === false) { setPreferences(previousPreferences); } } catch (error) { From efed2b5eeab103b970ababc66154663cdf1e837f Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:25:24 +0530 Subject: [PATCH 028/203] revert few changes --- app/containers/Avatar/useAvatarETag.ts | 2 +- app/containers/MessageActions/index.tsx | 4 ++-- .../MessageComposer/components/SendThreadToChannel.tsx | 3 +-- app/containers/MessageComposer/hooks/useAutocomplete.ts | 7 ++----- app/containers/TwoFactor/index.tsx | 3 ++- app/ee/omnichannel/lib/subscriptions/inquiry.ts | 6 +++--- app/lib/encryption/encryption.ts | 2 +- app/lib/encryption/room.ts | 2 +- app/lib/hooks/useUserData.ts | 6 +++--- app/lib/methods/canOpenRoom.ts | 2 +- app/lib/methods/getCustomEmojis.ts | 2 +- app/lib/methods/getPermissions.ts | 3 ++- app/lib/methods/getSingleMessage.ts | 4 ++-- app/lib/methods/getUsersPresence.ts | 2 +- app/lib/methods/sendMessage.ts | 2 +- app/sagas/troubleshootingNotification.ts | 2 +- app/views/RoomInfoView/index.tsx | 2 +- 17 files changed, 26 insertions(+), 28 deletions(-) diff --git a/app/containers/Avatar/useAvatarETag.ts b/app/containers/Avatar/useAvatarETag.ts index 555f7bfe2be..4b8a2596c91 100644 --- a/app/containers/Avatar/useAvatarETag.ts +++ b/app/containers/Avatar/useAvatarETag.ts @@ -48,7 +48,7 @@ export const useAvatarETag = ({ } if (record) { - const observable = record.observe() as unknown as Observable; + const observable = record.observe() as Observable; subscription = observable.subscribe(r => { setAvatarETag(r.avatarETag); }); diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx index 0bd6d9bd259..a10a159e6e1 100644 --- a/app/containers/MessageActions/index.tsx +++ b/app/containers/MessageActions/index.tsx @@ -234,7 +234,7 @@ const MessageActions = React.memo( try { const db = database.active; const result = await markAsUnread({ messageId }); - if (result) { + if (result.success) { const subRecord = await getSubscriptionByRoomId(rid); if (!subRecord) { return; @@ -292,7 +292,7 @@ const MessageActions = React.memo( const handleReplyInDM = async (message: TAnyMessageModel) => { if (message?.u?.username) { const result = await createDirectMessage(message.u.username); - if (result?.success) { + if (result.success) { const { room } = result; const params = { rid: room.rid, diff --git a/app/containers/MessageComposer/components/SendThreadToChannel.tsx b/app/containers/MessageComposer/components/SendThreadToChannel.tsx index 52cbd33de32..e752e89c271 100644 --- a/app/containers/MessageComposer/components/SendThreadToChannel.tsx +++ b/app/containers/MessageComposer/components/SendThreadToChannel.tsx @@ -1,7 +1,7 @@ import { TouchableWithoutFeedback } from 'react-native-gesture-handler'; import { StyleSheet, Text } from 'react-native'; import React, { useEffect, useRef } from 'react'; -import { Subscription } from 'rxjs'; +import { type Subscription } from 'rxjs'; import { Q } from '@nozbe/watermelondb'; import { useRoomContext } from '../../../views/RoomView/context'; @@ -55,7 +55,6 @@ export const SendThreadToChannel = (): React.ReactElement | null => { if (alsoSendThreadToChannelUserPref === 'default') { const db = database.active; const observable = db.get('threads').query(Q.where('id', tmid)).observe(); - // @ts-ignore: fix me subscription.current = observable.subscribe(result => { setAlsoSendThreadToChannel(!result.length); }); diff --git a/app/containers/MessageComposer/hooks/useAutocomplete.ts b/app/containers/MessageComposer/hooks/useAutocomplete.ts index d2e3ed02d0f..dc1c34f153d 100644 --- a/app/containers/MessageComposer/hooks/useAutocomplete.ts +++ b/app/containers/MessageComposer/hooks/useAutocomplete.ts @@ -162,7 +162,7 @@ export const useAutocomplete = ({ return; } const response = await getCommandPreview(text, rid, commandParams); - if (response?.success && response?.preview) { + if (response.success) { const previewItems = (response.preview?.items || []).map(item => ({ id: item.id, preview: item, @@ -182,7 +182,7 @@ export const useAutocomplete = ({ } if (type === '!') { const res = await getListCannedResponse({ text }); - if (res?.success && res?.cannedResponses) { + if (res.success) { if (res.cannedResponses.length === 0) { setItems([ { @@ -206,9 +206,6 @@ export const useAutocomplete = ({ updateAutocompleteVisible(true); accessibilityFocusOnInput(); } - } else if (res?.success && !res.cannedResponses) { - setItems([]); - updateAutocompleteVisible(false); } } } catch (e) { diff --git a/app/containers/TwoFactor/index.tsx b/app/containers/TwoFactor/index.tsx index 62584346aac..bb1607b0694 100644 --- a/app/containers/TwoFactor/index.tsx +++ b/app/containers/TwoFactor/index.tsx @@ -95,7 +95,8 @@ const TwoFactor = React.memo(() => { if (params?.user) { clearErrors(); const result = await sendEmailCode(params?.user); - if (result) { + + if (result.success) { showToast(I18n.t('Two_Factor_Success_message')); } } diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index cc8d6103ebb..d55ea8a2676 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -82,15 +82,15 @@ export default function subscribeInquiry() { throw new Error('inquiry: @subscribeInquiry user.id not found'); } - getAgentDepartments(user.id).then((result: any) => { - if (result?.success) { + getAgentDepartments(user.id).then((result) => { + if (result.success) { const { departments } = result; if (!departments?.length || hasRole('livechat-manager')) { sdk.subscribe(streamTopic, 'public'); } - const departmentIds = departments?.map(({ departmentId }: { departmentId: string }) => departmentId) || []; + const departmentIds = departments?.map(({ departmentId }) => departmentId) || []; departmentIds.forEach((departmentId: string) => { // subscribe to all departments of the agent sdk.subscribe(streamTopic, `department/${departmentId}`); diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 53d770e9bf3..3ba91014d58 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -490,7 +490,7 @@ class Encryption { } const result = await fetchUsersWaitingForGroupKey(sampleIds); - if (!result?.success || !result.usersWaitingForE2EKeys || typeof result.usersWaitingForE2EKeys !== 'object' || !Object.keys(result.usersWaitingForE2EKeys).length) { + if (!result.success || !Object.keys(result.usersWaitingForE2EKeys).length) { return; } diff --git a/app/lib/encryption/room.ts b/app/lib/encryption/room.ts index 2448c9362e2..04058659758 100644 --- a/app/lib/encryption/room.ts +++ b/app/lib/encryption/room.ts @@ -314,7 +314,7 @@ export default class EncryptionRoom { try { const decryptedOldGroupKeys = await this.exportOldRoomKeys(this.subscription?.oldRoomKeys); const result = await e2eGetUsersOfRoomWithoutKey(this.roomId); - if (result?.success && result.users) { + if (result.success) { const { users } = result; if (!users.length) { return; diff --git a/app/lib/hooks/useUserData.ts b/app/lib/hooks/useUserData.ts index 6176266d1e3..b2cdbb4c7ae 100644 --- a/app/lib/hooks/useUserData.ts +++ b/app/lib/hooks/useUserData.ts @@ -28,12 +28,12 @@ const useUserData = (rid: string) => { } else { try { const result = await getUserInfo(rid); - if (result.success && result.user) { + if (result.success) { const { user } = result; const username = useRealName && user.name ? user.name : user.username; setUser({ - username: username || '', - avatar: user.username || '', + username, + avatar: user.username, uid: user._id, type: SubscriptionType.DIRECT, direct: true diff --git a/app/lib/methods/canOpenRoom.ts b/app/lib/methods/canOpenRoom.ts index 5226029284c..a0b0d8479dd 100644 --- a/app/lib/methods/canOpenRoom.ts +++ b/app/lib/methods/canOpenRoom.ts @@ -17,7 +17,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: // if the dm already exists it'll return the existent if (type === ERoomTypes.DIRECT && !rid) { const result = await createDirectMessage(name); - if (result.room) { + if (result.success) { const { room } = result; return { ...room, diff --git a/app/lib/methods/getCustomEmojis.ts b/app/lib/methods/getCustomEmojis.ts index 2ec863ec59b..36e1b3d76f5 100644 --- a/app/lib/methods/getCustomEmojis.ts +++ b/app/lib/methods/getCustomEmojis.ts @@ -132,7 +132,7 @@ export function getCustomEmojis() { // RC 0.75.0 const result = await sdk.get('/v1/emoji-custom.list', params); - if (!result || !result.success) { + if (!result.success) { return resolve(); } diff --git a/app/lib/methods/getPermissions.ts b/app/lib/methods/getPermissions.ts index 4d65a84f988..f22eb5386c3 100644 --- a/app/lib/methods/getPermissions.ts +++ b/app/lib/methods/getPermissions.ts @@ -187,7 +187,8 @@ export function getPermissions(): Promise { } // RC 0.73.0 const result = await sdk.get('/v1/permissions.listAll', params); - if (!result) { + + if (!result.success) { return resolve(); } diff --git a/app/lib/methods/getSingleMessage.ts b/app/lib/methods/getSingleMessage.ts index 383101f502f..b0ac1e9a88f 100644 --- a/app/lib/methods/getSingleMessage.ts +++ b/app/lib/methods/getSingleMessage.ts @@ -5,10 +5,10 @@ const getSingleMessage = (messageId: string): Promise => new Promise(async (resolve, reject) => { try { const result = await getSingleMessageService(messageId); - if (result?.success && result.message) { + if (result.success) { return resolve(result.message); } - return reject(new Error('Failed to get message')); + return reject(); } catch (e) { return reject(e); } diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index 16af8886294..edee13dfb7d 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -72,7 +72,7 @@ export async function getUsersPresence(usersParams: string[]) { const result = (await sdk.get('users.presence' as any, params as any)) as any; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) { - sdk.subscribeRaw('stream-user-presence', '', { added: usersParams }); + sdk.subscribeRaw('stream-user-presence', ['', { added: usersParams }]); } if (result.success) { diff --git a/app/lib/methods/sendMessage.ts b/app/lib/methods/sendMessage.ts index f9c8518be0a..5d37394f4a9 100644 --- a/app/lib/methods/sendMessage.ts +++ b/app/lib/methods/sendMessage.ts @@ -52,7 +52,7 @@ async function sendMessageCall(message: any) { const { _id, tmid } = message; try { const result = await sdk.post('/v1/chat.sendMessage', { message }); - if (result?.success && result?.message) { + if (result.success) { // @ts-ignore return changeMessageStatus(_id, messagesStatus.SENT, tmid, result.message); } diff --git a/app/sagas/troubleshootingNotification.ts b/app/sagas/troubleshootingNotification.ts index 18fc0425787..193568890e9 100644 --- a/app/sagas/troubleshootingNotification.ts +++ b/app/sagas/troubleshootingNotification.ts @@ -28,7 +28,7 @@ function* init() { try { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.5.0')) { const pushInfoResult = yield* call(pushInfo); - if (pushInfoResult && pushInfoResult.success) { + if (pushInfoResult.success) { pushGatewayEnabled = pushInfoResult.pushGatewayEnabled; defaultPushGateway = pushInfoResult.defaultPushGateway; } diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index ee36af57c3e..ebca5380649 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -166,7 +166,7 @@ const RoomInfoView = (): React.ReactElement => { try { const roomUserId = getUidDirectMessage({ ...(room || { rid, t }), itsMe }); const result = await getUserInfo(roomUserId); - if (result.success && result.user) { + if (result.success) { const { user } = result; const r = handleRoles(user); setRoomUser({ ...user, roles: r }); From ef3942eb0f2e9259b116a785d03645f871bd7095 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:25:54 +0530 Subject: [PATCH 029/203] more revert --- app/views/RoomMembersView/helpers.ts | 2 +- app/views/RoomView/index.tsx | 2 +- app/views/TeamChannelsView.tsx | 2 +- app/views/ThreadMessagesView/index.tsx | 2 +- app/views/UserNotificationPreferencesView/index.tsx | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index 578d72a5585..2df3a3a1e05 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -39,7 +39,7 @@ export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, up try { const type = roomType; const result = await getRoomRoles(rid, type); - if (result && result.success) { + if (result.success) { updateState({ roomRoles: result.roles }); } } catch (e) { diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 6aba8449927..056ec2dfd03 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -700,7 +700,7 @@ class RoomView extends React.Component { this.setState({ roomUserId }, () => this.setHeader()); const result = await getUserInfo(roomUserId); - if (result && result.success) { + if (result.success) { return result.user; } } catch (e) { diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 84092181a08..4fc88e749c0 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -406,7 +406,7 @@ class TeamChannelsView extends React.Component result.room._id !== room._id); this.setState({ data: newData }); } diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 2edd950426f..06dcc0ceb00 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -341,7 +341,7 @@ class ThreadMessagesView extends React.Component { async function getPreferences() { try { const result = await getUserPreferences(); - if (result && result.success) { + if (result.success) { setPreferences(result.preferences as INotificationPreferences); } setLoading(false); From 6246907367d09eaa69aeee6e05c1c456d1521ef6 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:29:40 +0530 Subject: [PATCH 030/203] more revert --- app/lib/services/restApi.ts | 2 +- app/views/ForgotPasswordView.tsx | 2 +- app/views/RoomInfoView/index.tsx | 2 +- app/views/SearchMessagesView/index.tsx | 6 +++--- app/views/SetUsernameView.tsx | 10 +++------- app/views/TeamChannelsView.tsx | 4 ++-- app/views/UserNotificationPreferencesView/index.tsx | 2 +- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index ae0c800a62b..da5e40f8b1e 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1121,7 +1121,7 @@ export const getRoomMembers = async ({ }; // RC 3.16.0 const result = await sdk.get(`/v1/${roomTypeToApiType(t)}.members`, params); - if (result?.success === true) { + if (result.success) { return result.members; } } diff --git a/app/views/ForgotPasswordView.tsx b/app/views/ForgotPasswordView.tsx index b26a80eaaea..509701a1239 100644 --- a/app/views/ForgotPasswordView.tsx +++ b/app/views/ForgotPasswordView.tsx @@ -52,7 +52,7 @@ const ForgotPasswordView = (): React.ReactElement => { logEvent(events.FP_FORGOT_PASSWORD); setIsFetching(true); const result = await forgotPassword(email); - if (result) { + if (result.success) { navigation.pop(); showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert')); } diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index ebca5380649..c5f4eb07b96 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -219,7 +219,7 @@ const RoomInfoView = (): React.ReactElement => { if (!isEmpty(member)) return resolve(); try { const result = await createDirectMessage(roomUser.username); - if (result.success && result.room) return resolve({ ...roomUser, rid: result.room.rid }); + if (result.success) return resolve({ ...roomUser, rid: result.room.rid }); return resolve(); } catch (e) { reject(e); diff --git a/app/views/SearchMessagesView/index.tsx b/app/views/SearchMessagesView/index.tsx index e287b0e763d..d6752f1be82 100644 --- a/app/views/SearchMessagesView/index.tsx +++ b/app/views/SearchMessagesView/index.tsx @@ -160,10 +160,10 @@ class SearchMessagesView extends React.Component { + if (result.success) { + const urlRenderMessages = result.messages?.map(message => { if (message.urls && message.urls.length > 0) { - message.urls = message.urls.map((url, index) => { + message.urls = message.urls?.map((url, index) => { if (url.meta) { return { _id: index, diff --git a/app/views/SetUsernameView.tsx b/app/views/SetUsernameView.tsx index 78650834c11..6e36da60f30 100644 --- a/app/views/SetUsernameView.tsx +++ b/app/views/SetUsernameView.tsx @@ -62,13 +62,9 @@ const SetUsernameView = () => { useEffect(() => { const init = async () => { - try { - const suggestion = await getUsernameSuggestion(); - if (suggestion && suggestion.success) { - setValue('username', suggestion.result, { shouldValidate: true }); - } - } catch (e) { - console.log('Failed to get username suggestion:', e); + const suggestion = await getUsernameSuggestion(); + if (suggestion.success) { + setValue('username', suggestion.result, { shouldValidate: true }); } }; init(); diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 4fc88e749c0..ee9037a5396 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -339,7 +339,7 @@ class TeamChannelsView extends React.Component { if (i._id === item._id) { i.teamDefault = !i.teamDefault; diff --git a/app/views/UserNotificationPreferencesView/index.tsx b/app/views/UserNotificationPreferencesView/index.tsx index 49c8dbf885e..c1802f4f8c1 100644 --- a/app/views/UserNotificationPreferencesView/index.tsx +++ b/app/views/UserNotificationPreferencesView/index.tsx @@ -61,7 +61,7 @@ const UserNotificationPreferencesView = () => { try { setPreferences({ ...previousPreferences, ...param }); const result = await setUserPreferences(userId, param); - if (!result || result.success === false) { + if (!result.success) { setPreferences(previousPreferences); } } catch (error) { From 54b0cd4aedd228609ef077e24b4774d3a998ddaf Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:35:25 +0530 Subject: [PATCH 031/203] more revert --- app/views/AddExistingChannelView/index.tsx | 2 +- app/views/ReadReceiptView/index.tsx | 8 ++++---- app/views/RegisterView/index.tsx | 2 +- app/views/RoomActionsView/index.tsx | 6 +++--- app/views/RoomInfoView/index.tsx | 4 ++-- app/views/RoomMembersView/helpers.ts | 2 +- app/views/TeamChannelsView.tsx | 8 ++++---- app/views/ThreadMessagesView/index.tsx | 6 ++---- app/views/UserNotificationPreferencesView/index.tsx | 4 ++-- 9 files changed, 20 insertions(+), 22 deletions(-) diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index d156cfb56f6..6d21ed0801e 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -137,7 +137,7 @@ const AddExistingChannelView = () => { try { logEvent(events.CT_ADD_ROOM_TO_TEAM); const result = await addRoomsToTeam({ rooms: selected, teamId }); - if (result) { + if (result.success) { Navigation.resetTo(); } } catch (e: any) { diff --git a/app/views/ReadReceiptView/index.tsx b/app/views/ReadReceiptView/index.tsx index 0940bd8062c..6df57998f3c 100644 --- a/app/views/ReadReceiptView/index.tsx +++ b/app/views/ReadReceiptView/index.tsx @@ -85,15 +85,15 @@ class ReadReceiptView extends React.Component { try { const response = await register({ name, email, pass: password, username }); - if (response) { + if (response.success) { if (Accounts_EmailVerification) { showErrorAlert(I18n.t('Verify_email_desc'), I18n.t('Registration_Succeeded')); navigation.goBack(); diff --git a/app/views/RoomActionsView/index.tsx b/app/views/RoomActionsView/index.tsx index e67a001ec0d..70d06e16fd9 100644 --- a/app/views/RoomActionsView/index.tsx +++ b/app/views/RoomActionsView/index.tsx @@ -198,7 +198,7 @@ class RoomActionsView extends React.Component { try { if (room?.visitor?._id) { const result = await getVisitorInfo(room.visitor._id); - if (result.success && result.visitor) { + if (result.success) { const { visitor } = result; const params: { os?: string; browser?: string } = {}; if (visitor.userAgent) { @@ -203,7 +203,7 @@ const RoomInfoView = (): React.ReactElement => { try { if (!isDirect) { const result = await getRoomInfo(rid); - if (result.success && result.room) setRoom({ ...room, ...(result.room as unknown as ISubscription) }); + if (result.success) setRoom({ ...room, ...(result.room as unknown as ISubscription) }); } } catch (e) { log(e); diff --git a/app/views/RoomMembersView/helpers.ts b/app/views/RoomMembersView/helpers.ts index 2df3a3a1e05..df6f27ababd 100644 --- a/app/views/RoomMembersView/helpers.ts +++ b/app/views/RoomMembersView/helpers.ts @@ -39,7 +39,7 @@ export const fetchRoomMembersRoles = async (roomType: TRoomType, rid: string, up try { const type = roomType; const result = await getRoomRoles(rid, type); - if (result.success) { + if (result?.success) { updateState({ roomRoles: result.roles }); } } catch (e) { diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index ee9037a5396..6b91e7392e4 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -338,17 +338,18 @@ class TeamChannelsView extends React.Component { try { const result = await getUserPreferences(); if (result.success) { - setPreferences(result.preferences as INotificationPreferences); + setLoading(false); + setPreferences(result.preferences); } - setLoading(false); } catch (error) { setLoading(false); log(error); From 3e134cdff0780c3a0c0e44d24898f3989b951fcf Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:57:29 +0530 Subject: [PATCH 032/203] revert --- app/lib/methods/loadMissedMessages.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index 65c042bc42f..0bdb228d3c6 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -8,8 +8,9 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; const count = 50; const syncMessages = async ({ roomId, next, type }: { roomId: string; next: number; type: 'UPDATED' | 'DELETED' }) => { - const { result, success } = await sdk.get('/v1/chat.syncMessages', { roomId, next, count, type }); - return success ? result : null; + // @ts-ignore // this method dont have type + const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, next, count, type }); + return result; }; const getSyncMessagesFromCursor = async ( From 122ecff68ac931a59cada13c79c1886f28b011d4 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:59:29 +0530 Subject: [PATCH 033/203] more revert --- app/lib/methods/canOpenRoom.ts | 1 + app/lib/methods/getRooms.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/app/lib/methods/canOpenRoom.ts b/app/lib/methods/canOpenRoom.ts index a0b0d8479dd..e668c1d624e 100644 --- a/app/lib/methods/canOpenRoom.ts +++ b/app/lib/methods/canOpenRoom.ts @@ -42,6 +42,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: // we'll get info from the room if ((type === ERoomTypes.CHANNEL || type === ERoomTypes.GROUP) && !rid) { // RC 0.72.0 + // @ts-ignore const result: any = await sdk.get(`/v1/${restTypes[type]}.info`, params); if (result.success) { const room = result[type]; diff --git a/app/lib/methods/getRooms.ts b/app/lib/methods/getRooms.ts index c17a6790282..003c17d1210 100644 --- a/app/lib/methods/getRooms.ts +++ b/app/lib/methods/getRooms.ts @@ -5,10 +5,15 @@ export function getRooms(updatedSince: Date) { // rooms.get: Since RC 0.62.0 if (updatedSince) { const updatedDate = updatedSince.toISOString(); + // TODO: missing definitions from server return Promise.all([ + // @ts-ignore sdk.get('/v1/subscriptions.get', { updatedSince: updatedDate }), + // @ts-ignore sdk.get('/v1/rooms.get', { updatedSince: updatedDate }) ]); } + // TODO: missing definitions from server + // @ts-ignore return Promise.all([sdk.get('/v1/subscriptions.get', {}), sdk.get('/v1/rooms.get', {})]); } From 88920162a453b48d8e3cf5f5e0e7e91dcd4a3574 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:01:01 +0530 Subject: [PATCH 034/203] more revert --- app/containers/TwoFactor/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/containers/TwoFactor/index.tsx b/app/containers/TwoFactor/index.tsx index bb1607b0694..40c3d93c482 100644 --- a/app/containers/TwoFactor/index.tsx +++ b/app/containers/TwoFactor/index.tsx @@ -94,9 +94,9 @@ const TwoFactor = React.memo(() => { try { if (params?.user) { clearErrors(); - const result = await sendEmailCode(params?.user); + const response = await sendEmailCode(params?.user); - if (result.success) { + if (response.success) { showToast(I18n.t('Two_Factor_Success_message')); } } From ba3ee5fe4368f9026e1fb1e527f14e20f937f8b3 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:03:57 +0530 Subject: [PATCH 035/203] format code --- app/definitions/rest/v1/auth.ts | 9 ++---- .../omnichannel/lib/subscriptions/inquiry.ts | 30 +++++++++--------- app/lib/methods/getRooms.ts | 2 +- app/lib/services/connect.ts | 6 ++-- app/lib/services/restApi.ts | 7 +++-- app/lib/services/sdk.ts | 31 ++++++++++--------- app/views/TeamChannelsView.tsx | 2 +- 7 files changed, 43 insertions(+), 44 deletions(-) diff --git a/app/definitions/rest/v1/auth.ts b/app/definitions/rest/v1/auth.ts index 5ba739427a8..c49499f0af7 100644 --- a/app/definitions/rest/v1/auth.ts +++ b/app/definitions/rest/v1/auth.ts @@ -22,13 +22,8 @@ export interface ILoginDataResponse { } export type AuthEndpoints = { - 'login': { - POST: (params: { - user?: string; - password?: string; - resume?: string; - code?: string; - }) => { + login: { + POST: (params: { user?: string; password?: string; resume?: string; code?: string }) => { status: string; data: ILoginDataResponse; }; diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index d55ea8a2676..d9aaaac5572 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -82,21 +82,23 @@ export default function subscribeInquiry() { throw new Error('inquiry: @subscribeInquiry user.id not found'); } - getAgentDepartments(user.id).then((result) => { - if (result.success) { - const { departments } = result; - - if (!departments?.length || hasRole('livechat-manager')) { - sdk.subscribe(streamTopic, 'public'); + getAgentDepartments(user.id) + .then(result => { + if (result.success) { + const { departments } = result; + + if (!departments?.length || hasRole('livechat-manager')) { + sdk.subscribe(streamTopic, 'public'); + } + + const departmentIds = departments?.map(({ departmentId }) => departmentId) || []; + departmentIds.forEach((departmentId: string) => { + // subscribe to all departments of the agent + sdk.subscribe(streamTopic, `department/${departmentId}`); + }); } - - const departmentIds = departments?.map(({ departmentId }) => departmentId) || []; - departmentIds.forEach((departmentId: string) => { - // subscribe to all departments of the agent - sdk.subscribe(streamTopic, `department/${departmentId}`); - }); - } - }).catch((e: unknown) => console.log(e)); + }) + .catch((e: unknown) => console.log(e)); return { stop: () => stop() diff --git a/app/lib/methods/getRooms.ts b/app/lib/methods/getRooms.ts index 003c17d1210..5427ec432f8 100644 --- a/app/lib/methods/getRooms.ts +++ b/app/lib/methods/getRooms.ts @@ -15,5 +15,5 @@ export function getRooms(updatedSince: Date) { } // TODO: missing definitions from server // @ts-ignore - return Promise.all([sdk.get('/v1/subscriptions.get', {}), sdk.get('/v1/rooms.get', {})]); + return Promise.all([sdk.get('/v1/subscriptions.get'), sdk.get('/v1/rooms.get')]); } diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 5a43e22b26c..558d807e08d 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -151,8 +151,8 @@ async function connect({ server, logoutOnError = false }: { server: string; logo store.dispatch(setActiveUsers(activeUsersBatch)); }); _setUserTimer.setUserTimer = null; - //@ts-ignore - fix me - _activeUsers.activeUsers = {} + //@ts-ignore - fix me + _activeUsers.activeUsers = {}; return null; }, 10000); } @@ -443,4 +443,4 @@ export { stopListener, getLoginServices, determineAuthType -}; \ No newline at end of file +}; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index da5e40f8b1e..04acdd68cc9 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -85,7 +85,7 @@ export const e2eRequestRoomKey = (rid: string, e2eKeyId: string): Promise<{ mess sdk.methodCall('stream-notify-room-users', `${rid}/e2ekeyRequest`, rid, e2eKeyId); // RC 5.5 -export const e2eAcceptSuggestedGroupKey = (rid: string)=> sdk.post('/v1/e2e.acceptSuggestedGroupKey', { rid }); +export const e2eAcceptSuggestedGroupKey = (rid: string) => sdk.post('/v1/e2e.acceptSuggestedGroupKey', { rid }); // RC 5.5 export const e2eRejectSuggestedGroupKey = (rid: string) => sdk.post('/v1/e2e.rejectSuggestedGroupKey', { rid }); @@ -546,7 +546,7 @@ export const deleteRoom = (roomId: string, t: RoomTypes) => { } return sdk.post(`/v1/${roomTypeToApiType(t)}.delete`, { roomId }); -} +}; export const toggleMuteUserInRoom = (rid: string, username: string, userId: string, mute: boolean) => { const serverVersion = reduxStore.getState().server.version; @@ -1156,7 +1156,8 @@ export function getUserInfo(userId: string) { export const toggleFavorite = (roomId: string, favorite: boolean) => sdk.post('/v1/rooms.favorite', { roomId, favorite }); -export const sendInvitationReply = (roomId: string, action: 'accept' | 'reject') => sdk.post('/v1/rooms.invite', { roomId, action }); +export const sendInvitationReply = (roomId: string, action: 'accept' | 'reject') => + sdk.post('/v1/rooms.invite', { roomId, action }); export const videoConferenceJoin = (callId: string, cam?: boolean, mic?: boolean) => sdk.post('/v1/video-conference.join', { callId, state: { cam: !!cam, mic: mic === undefined ? true : mic } }); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 8d8a91a88e7..3845ec2cd2d 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -21,8 +21,9 @@ class Sdk { private sdk: DDPSDK | undefined; private code: any = null; private headers: Record = { - 'User-Agent': `RC Mobile; ${Platform.OS - } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` + 'User-Agent': `RC Mobile; ${ + Platform.OS + } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` }; async initialize(server: string): Promise { @@ -109,9 +110,9 @@ class Sdk { params: void extends OperationParams<'GET', MatchPathPattern> ? void : Serialized>> = undefined as void extends OperationParams< - 'GET', - MatchPathPattern - > + 'GET', + MatchPathPattern + > ? void : Serialized>> ): Promise>>> { @@ -125,9 +126,9 @@ class Sdk { params: void extends OperationParams<'POST', MatchPathPattern> ? void : Serialized>> = undefined as void extends OperationParams< - 'POST', - MatchPathPattern - > + 'POST', + MatchPathPattern + > ? void : Serialized>> ): Promise>> { @@ -176,9 +177,9 @@ class Sdk { params: void extends OperationParams<'DELETE', MatchPathPattern> ? void : Serialized>> = undefined as void extends OperationParams< - 'DELETE', - MatchPathPattern - > + 'DELETE', + MatchPathPattern + > ? void : Serialized>> ): Promise>>> { @@ -290,9 +291,9 @@ class Sdk { } onStreamData(name: string, callback: (...data: any) => void): Promise<{ stop: () => void }> { - return new Promise((resolve) => { + return new Promise(resolve => { if (!this.current) { - resolve({ stop: () => { } }); + resolve({ stop: () => {} }); return; } const listener = this.current.client.onCollection(name, (ddpMessage: any) => { @@ -338,7 +339,7 @@ class Sdk { const [key, args] = Array.isArray(data) ? data : [data]; if (!this.current) { - return { stop: () => { } }; + return { stop: () => {} }; } const subscription = this.current.client.subscribe(`stream-${name}`, key, { useCollection: false, args: [args] }); @@ -369,4 +370,4 @@ class Sdk { const sdk = new Sdk(); -export default sdk; \ No newline at end of file +export default sdk; diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index 6b91e7392e4..81933fcdfeb 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -338,7 +338,7 @@ class TeamChannelsView extends React.Component Date: Mon, 27 Apr 2026 23:06:34 +0530 Subject: [PATCH 036/203] lint fix --- app/lib/methods/loadMessagesForRoom.ts | 3 ++- app/lib/services/connect.ts | 14 +++++++------- app/lib/services/restApi.ts | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts index 13924dc5898..c23a695fbd5 100644 --- a/app/lib/methods/loadMessagesForRoom.ts +++ b/app/lib/methods/loadMessagesForRoom.ts @@ -1,3 +1,5 @@ +import { type ChannelsHistoryParams, type GroupsHistoryParams, type ImHistoryParams } from '@rocket.chat/api-typings'; + import dayjs from '../dayjs'; import { MessageTypeLoad } from '../constants/messageTypeLoad'; import { type IMessage, type TMessageModel } from '../../definitions'; @@ -7,7 +9,6 @@ import { type RoomTypes, roomTypeToApiType } from './roomTypeToApiType'; import sdk from '../services/sdk'; import updateMessages from './updateMessages'; import { generateLoadMoreId } from './helpers/generateLoadMoreId'; -import { type ChannelsHistoryParams, type GroupsHistoryParams, type ImHistoryParams } from '@rocket.chat/api-typings'; const COUNT = 50; diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 558d807e08d..4d10dbbfaac 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -12,18 +12,18 @@ import { store } from '../store/auxStore'; import { loginRequest, logout, setLoginServices, setUser } from '../../actions/login'; import sdk from './sdk'; import I18n from '../../i18n'; -import { ICredentials, ILoggedUser, STATUSES } from '../../definitions'; +import { type ICredentials, type ILoggedUser, STATUSES } from '../../definitions'; import { connectRequest, connectSuccess, disconnect as disconnectAction } from '../../actions/connect'; import { updatePermission } from '../../actions/permissions'; import EventEmitter from '../methods/helpers/events'; import { updateSettings } from '../../actions/settings'; import { defaultSettings } from '../constants/defaultSettings'; import { compareServerVersion, isIOS } from '../methods/helpers'; -import { onRolesChanged } from '../../lib/methods/getRoles'; -import { getSettings } from '../../lib/methods/getSettings'; -import { setPresenceCap } from '../../lib/methods/getUsersPresence'; -import { _setUser, _activeUsers, _setUserTimer } from '../../lib/methods/setUser'; -import { unsubscribeRooms } from '../../lib/methods/subscribeRooms'; +import { onRolesChanged } from '../methods/getRoles'; +import { getSettings } from '../methods/getSettings'; +import { setPresenceCap } from '../methods/getUsersPresence'; +import { _setUser, _activeUsers, _setUserTimer } from '../methods/setUser'; +import { unsubscribeRooms } from '../methods/subscribeRooms'; interface IServices { [index: string]: string | boolean; @@ -151,7 +151,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo store.dispatch(setActiveUsers(activeUsersBatch)); }); _setUserTimer.setUserTimer = null; - //@ts-ignore - fix me + // @ts-ignore - fix me _activeUsers.activeUsers = {}; return null; }, 10000); diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 04acdd68cc9..1b64e9040c1 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -450,7 +450,7 @@ export const forwardLivechat = (transferData: any) => { return sdk.methodCallWrapper('livechat:transfer', transferData); }; -//fix me +// fix me export const getDepartmentInfo = (departmentId: string) => // RC 2.2.0 sdk.get(`/v1/livechat/department/${departmentId}?includeAgents=false`); From 41d88d29f766e5e9dcd05c7f221018da15937f77 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 27 Apr 2026 23:14:03 +0530 Subject: [PATCH 037/203] final revert --- app/lib/methods/loadMessagesForRoom.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/app/lib/methods/loadMessagesForRoom.ts b/app/lib/methods/loadMessagesForRoom.ts index c23a695fbd5..a3a87028879 100644 --- a/app/lib/methods/loadMessagesForRoom.ts +++ b/app/lib/methods/loadMessagesForRoom.ts @@ -1,5 +1,3 @@ -import { type ChannelsHistoryParams, type GroupsHistoryParams, type ImHistoryParams } from '@rocket.chat/api-typings'; - import dayjs from '../dayjs'; import { MessageTypeLoad } from '../constants/messageTypeLoad'; import { type IMessage, type TMessageModel } from '../../definitions'; @@ -26,20 +24,7 @@ async function load({ rid: roomId, latest, t }: { rid: string; latest?: Date; t: return; } - let params; - switch (apiType) { - case 'channels': - params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) } as ChannelsHistoryParams; - break; - case 'groups': - params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) } as GroupsHistoryParams; - break; - case 'im': - params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) } as ImHistoryParams; - break; - default: - return; - } + const params = { roomId, showThreadMessages: false, count: COUNT, ...(lastTs && { latest: lastTs }) }; let data; switch (apiType) { From fc0fd2113f3f4c67060c14e1a68ff3246002ff06 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 28 Apr 2026 01:55:02 +0530 Subject: [PATCH 038/203] login fix --- app/lib/services/sdk.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 3845ec2cd2d..c82610d3ecf 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -202,16 +202,13 @@ class Sdk { async login(credentials: any): Promise { try { - const loginResult = await this.post('/v1/login', credentials); - if (!loginResult?.success) { - return Promise.reject(new Error('Invalid response from server')); - } - - // TODO: get/set headers from SDK instead? - this.setHeaders({ 'X-Auth-Token': loginResult.data.authToken, 'X-User-Id': loginResult.data.userId }); - await this.current?.account.loginWithToken(loginResult.data.authToken); - return loginResult.data; + await this.current?.account.loginWithPassword(credentials.user, credentials.password); + // const loginResult = await this.post('/v1/login', credentials); + // if (!loginResult?.success) { + // return Promise.reject(new Error('Invalid response from server')); + // } } catch (e) { + console.log('login error', e); return Promise.reject(e); } } From 396b6debf5903d8ba4e1c6c759d3b2a3c2859c8c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:17:24 +0530 Subject: [PATCH 039/203] login fix --- app/lib/services/sdk.ts | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index c82610d3ecf..8762ab73673 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -27,7 +27,7 @@ class Sdk { }; async initialize(server: string): Promise { - this.sdk = await DDPSDK.create(server); + this.sdk = DDPSDK.create(server); this.setBasicAuth(); this.sdk.rest.handleTwoFactorChallenge(this.twoFactorHandler); return this.sdk; @@ -118,7 +118,9 @@ class Sdk { ): Promise>>> { const sdk = this.ensureInitialized(); // @ts-ignore - return sdk.rest.get(endpoint, params); + return sdk.rest.get(endpoint, params, { + headers: this.headers + }); } post>( @@ -185,7 +187,9 @@ class Sdk { ): Promise>>> { const sdk = this.ensureInitialized(); // @ts-ignore - return sdk.rest.delete(endpoint, params); + return sdk.rest.delete(endpoint, params, { + headers: this.headers + }); } async twoFactorHandler({ @@ -202,13 +206,21 @@ class Sdk { async login(credentials: any): Promise { try { - await this.current?.account.loginWithPassword(credentials.user, credentials.password); - // const loginResult = await this.post('/v1/login', credentials); - // if (!loginResult?.success) { - // return Promise.reject(new Error('Invalid response from server')); - // } - } catch (e) { - console.log('login error', e); + const loginResult = await this.post('/v1/login', credentials); + if (!loginResult?.success) { + return Promise.reject(new Error('Invalid response from server')); + } + await this.current?.account.loginWithToken(loginResult.data.authToken); + return loginResult.data; + } catch (e: any) { + if (e instanceof Response) { + try { + const data = await e.clone().json(); + return Promise.reject({ status: e.status, data }); + } catch { + return Promise.reject({ status: e.status, data: {} }); + } + } return Promise.reject(e); } } From 818ffd77c21619107a828ba9382d26a17ad79aec Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:35:06 +0530 Subject: [PATCH 040/203] more fix --- app/containers/Avatar/Avatar.tsx | 4 +- .../Avatar/__snapshots__/Avatar.test.tsx.snap | 68 +++++++++++++----- .../Chip/__snapshots__/Chip.test.tsx.snap | 12 +++- app/externalModules.d.ts | 1 - app/lib/methods/getSettings.ts | 6 +- app/lib/methods/helpers/fetch.ts | 21 ++---- app/lib/methods/logout.ts | 21 ++++-- .../sendFileMessage/sendFileMessage.ts | 10 +-- .../sendFileMessage/sendFileMessageV2.ts | 11 ++- .../methods/uploadAvatar/uploadAvatar.test.ts | 7 +- app/lib/methods/uploadAvatar/uploadAvatar.ts | 11 ++- app/lib/services/connect.ts | 2 +- app/lib/services/sdk.ts | 32 +++++++-- app/sagas/createChannel.js | 2 +- app/sagas/selectServer.ts | 2 +- package.json | 1 - yarn.lock | 70 +------------------ 17 files changed, 137 insertions(+), 144 deletions(-) diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index 089730b0112..5dee3080a99 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { View } from 'react-native'; import { Image } from 'expo-image'; -import { settings as RocketChatSettings } from '@rocket.chat/sdk'; import Emoji from '../markdown/components/emoji/Emoji'; import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl'; @@ -10,6 +9,7 @@ import { type IAvatar } from './interfaces'; import MarkdownContext from '../markdown/contexts/MarkdownContext'; import I18n from '../../i18n'; import Touch from '../Touch'; +import sdk from '../../lib/services/sdk'; const Avatar = React.memo( ({ @@ -88,7 +88,7 @@ const Avatar = React.memo( style={avatarStyle} source={{ uri, - headers: RocketChatSettings.customHeaders + headers: sdk.getHeaders() }} priority='high' /> diff --git a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap index 940a7909f6d..0eeef1b9492 100644 --- a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap +++ b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap @@ -37,7 +37,9 @@ exports[`Story Snapshots: AvatarBase64 should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "data:image/png;base64,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", }, ] @@ -92,7 +94,9 @@ exports[`Story Snapshots: AvatarExternalProviderUrl should match snapshot 1`] = source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://hips.hearstapps.com/hmg-prod/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg?crop=0.752xw:1.00xh;0.175xw,0&resize=1200:*&format=png&size=112", }, ] @@ -147,7 +151,9 @@ exports[`Story Snapshots: AvatarPath should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=112", }, ] @@ -202,7 +208,9 @@ exports[`Story Snapshots: AvatarRoomId should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/room/devWBbYr7inwupPqK?format=png&size=112", }, ] @@ -257,7 +265,9 @@ exports[`Story Snapshots: AvatarText should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -312,7 +322,9 @@ exports[`Story Snapshots: AvatarUrl should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg", }, ] @@ -367,7 +379,9 @@ exports[`Story Snapshots: Channel should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/@general?format=png&size=112", }, ] @@ -422,7 +436,9 @@ exports[`Story Snapshots: Children should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -515,7 +531,9 @@ exports[`Story Snapshots: CustomBorderRadius should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -572,7 +590,9 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -627,7 +647,9 @@ exports[`Story Snapshots: Direct should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=112", }, ] @@ -747,7 +769,9 @@ exports[`Story Snapshots: RoomAvatarExternalProviderUrl should match snapshot 1` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://cdn.pensador.com/img/authors/ho/me/homer-simpson-l.jpg?format=png&size=112", }, ] @@ -802,7 +826,9 @@ exports[`Story Snapshots: Static should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg", }, ] @@ -915,7 +941,9 @@ exports[`Story Snapshots: Touchable should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, ] @@ -972,7 +1000,9 @@ exports[`Story Snapshots: WithETag should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=112&etag=5ag8KffJcZj9m5rCv", }, ] @@ -1027,7 +1057,9 @@ exports[`Story Snapshots: WithoutETag should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=112", }, ] @@ -1082,7 +1114,9 @@ exports[`Story Snapshots: WrongServer should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://google.com/avatar/Avatar?format=png&size=112", }, ] diff --git a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap index 113a0ba0758..9073ab74019 100644 --- a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap +++ b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap @@ -208,7 +208,9 @@ exports[`Story Snapshots: ChipText should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, ] @@ -391,7 +393,9 @@ exports[`Story Snapshots: ChipWithShortText should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, ] @@ -804,7 +808,9 @@ exports[`Story Snapshots: ChipWithoutIcon should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, ] diff --git a/app/externalModules.d.ts b/app/externalModules.d.ts index 9f81cb860fa..1e3402c836a 100644 --- a/app/externalModules.d.ts +++ b/app/externalModules.d.ts @@ -1,5 +1,4 @@ declare module 'remove-markdown'; -declare module '@rocket.chat/sdk'; declare module 'react-native-config-reader'; declare module 'react-native-mime-types'; declare module 'react-native-restart'; diff --git a/app/lib/methods/getSettings.ts b/app/lib/methods/getSettings.ts index 603a944a27f..1740c934671 100644 --- a/app/lib/methods/getSettings.ts +++ b/app/lib/methods/getSettings.ts @@ -157,10 +157,10 @@ export async function getSettings(): Promise { let offset = 0; let remaining; let settings: IData[] = []; - const serverVersion = reduxStore.getState().server.version; + const { version: serverVersion, server } = reduxStore.getState().server; const url = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0') - ? `${sdk.current?.connection.url}/api/v1/settings.public?_id=${settingsParams.join(',')}` - : `${sdk.current?.connection.url}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}`; + ? `${server}/api/v1/settings.public?_id=${settingsParams.join(',')}` + : `${server}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}`; // Iterate over paginated results to retrieve all settings do { // TODO: why is no-await-in-loop enforced in the first place? diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index d15a050e8fc..3bdeb7e9d07 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -1,6 +1,7 @@ import { Platform } from 'react-native'; import DeviceInfo from 'react-native-device-info'; -import { settings as RocketChatSettings } from '@rocket.chat/sdk'; + +import sdk from '../../services/sdk'; export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; @@ -26,29 +27,15 @@ export const headers: CustomHeaders = { } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` }; -let _basicAuth; export const setBasicAuth = (basicAuth: string | null): void => { - _basicAuth = basicAuth; - if (basicAuth) { - RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` }; - } else { - RocketChatSettings.customHeaders = headers; - } + sdk.setBasicAuth(basicAuth); }; export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; -RocketChatSettings.customHeaders = headers; - export default (url: string, options: IOptions = {}): Promise => { - let customOptions = { ...options, headers: RocketChatSettings.customHeaders }; + let customOptions = { ...options, headers: sdk.getHeaders() }; if (options && options.headers) { customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } }; } - // TODO: Check if this really works and if anyone else has complained about this problem. - // if (RocketChat.controller) { - // // @ts-ignore - // const { signal } = RocketChat.controller; - // customOptions = { ...customOptions, signal }; - // } return fetch(url, customOptions); }; diff --git a/app/lib/methods/logout.ts b/app/lib/methods/logout.ts index 6402a35af41..39b4b16e0ab 100644 --- a/app/lib/methods/logout.ts +++ b/app/lib/methods/logout.ts @@ -1,8 +1,7 @@ -import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; import type Model from '@nozbe/watermelondb/Model'; +import { DDPSDK } from '@rocket.chat/ddp-client'; import { getDeviceToken } from '../notifications'; -import { isSsl } from './helpers'; import { BASIC_AUTH_KEY } from './helpers/fetch'; import database, { getDatabase } from '../database'; import log from './helpers/log'; @@ -65,18 +64,28 @@ export async function removeServer({ server }: { server: string }): Promise = { + ...sdk.getHeaders(), 'Content-Type': 'multipart/form-data', - 'X-Auth-Token': token, - 'X-User-Id': id + ...(token ? { 'X-Auth-Token': token } : {}), + ...(id ? { 'X-User-Id': id } : {}) }; uploadQueue[uploadPath] = new FileUpload(uploadUrl, headers, formData, async (loaded, total) => { diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts index 30369aef2a9..22383b6c3e0 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts @@ -1,5 +1,3 @@ -import { settings as RocketChatSettings } from '@rocket.chat/sdk'; - import { type TSendFileMessageFileInfo, type IUser, type TUploadModel } from '../../../definitions'; import database from '../../database'; import { Encryption } from '../../encryption'; @@ -7,6 +5,7 @@ import { copyFileToCacheDirectoryIfNeeded, createUploadRecord, persistUploadErro import FileUpload from '../helpers/fileUpload'; import { type IFormData } from '../helpers/fileUpload/definitions'; import fetch from '../helpers/fetch'; +import sdk from '../../services/sdk'; export async function sendFileMessageV2( rid: string, @@ -20,11 +19,11 @@ export async function sendFileMessageV2( let uploadRecord: TUploadModel | null; try { const { id, token } = user; - const headers = { - ...RocketChatSettings.customHeaders, + const headers: Record = { + ...sdk.getHeaders(), 'Content-Type': 'multipart/form-data', - 'X-Auth-Token': token, - 'X-User-Id': id + ...(token ? { 'X-Auth-Token': token } : {}), + ...(id ? { 'X-User-Id': id } : {}) }; const db = database.active; diff --git a/app/lib/methods/uploadAvatar/uploadAvatar.test.ts b/app/lib/methods/uploadAvatar/uploadAvatar.test.ts index 9af796eda23..09b13157c7d 100644 --- a/app/lib/methods/uploadAvatar/uploadAvatar.test.ts +++ b/app/lib/methods/uploadAvatar/uploadAvatar.test.ts @@ -20,8 +20,11 @@ jest.mock('../sendFileMessage/utils', () => ({ copyFileToCacheDirectoryIfNeeded: jest.fn((p: string) => Promise.resolve(`cached:${p}`)) })); -jest.mock('@rocket.chat/sdk', () => ({ - settings: { customHeaders: { 'X-Custom': 'custom' } } +jest.mock('../../services/sdk', () => ({ + __esModule: true, + default: { + getHeaders: () => ({ 'X-Custom': 'custom' }) + } })); const baseState = { diff --git a/app/lib/methods/uploadAvatar/uploadAvatar.ts b/app/lib/methods/uploadAvatar/uploadAvatar.ts index 6dfe5c48ff9..72635683e4b 100644 --- a/app/lib/methods/uploadAvatar/uploadAvatar.ts +++ b/app/lib/methods/uploadAvatar/uploadAvatar.ts @@ -1,20 +1,19 @@ -import { settings as RocketChatSettings } from '@rocket.chat/sdk'; - import FileUpload from '../helpers/fileUpload'; import { type IFormData } from '../helpers/fileUpload/definitions'; import { copyFileToCacheDirectoryIfNeeded } from '../sendFileMessage/utils'; import { store as reduxStore } from '../../store/auxStore'; +import sdk from '../../services/sdk'; export const uploadUserAvatarMultipart = async (localUri: string, mimeType: string, filename: string): Promise => { const { server } = reduxStore.getState().server; const { id, token } = reduxStore.getState().login.user; const filePath = await copyFileToCacheDirectoryIfNeeded(localUri, filename); const formData: IFormData[] = [{ name: 'image', uri: filePath, type: mimeType, filename }]; - const headers = { - ...RocketChatSettings.customHeaders, + const headers: Record = { + ...sdk.getHeaders(), 'Content-Type': 'multipart/form-data', - 'X-Auth-Token': token, - 'X-User-Id': id + ...(token ? { 'X-Auth-Token': token } : {}), + ...(id ? { 'X-User-Id': id } : {}) }; const upload = new FileUpload(`${server}/api/v1/users.setAvatar`, headers, formData); await upload.send(); diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 4d10dbbfaac..16dc2acc985 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -35,7 +35,7 @@ interface IServices { } async function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise { - if (sdk.current?.connection.url === server) { + if (sdk.server === server) { return; } diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 8762ab73673..2a1dd587a5c 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -19,6 +19,7 @@ import { class Sdk { private sdk: DDPSDK | undefined; + private serverUrl: string | undefined; private code: any = null; private headers: Record = { 'User-Agent': `RC Mobile; ${ @@ -26,13 +27,18 @@ class Sdk { } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` }; - async initialize(server: string): Promise { + initialize(server: string): DDPSDK { this.sdk = DDPSDK.create(server); - this.setBasicAuth(); + this.serverUrl = server; + this.loadBasicAuth(); this.sdk.rest.handleTwoFactorChallenge(this.twoFactorHandler); return this.sdk; } + get server(): string | undefined { + return this.serverUrl; + } + private ensureInitialized(): DDPSDK { if (!this.current) { throw new Error('SDK not initialized'); @@ -49,11 +55,12 @@ class Sdk { this.sdk.connection.close(); this.sdk = undefined; } + this.serverUrl = undefined; return null; } - private setBasicAuth(): void { - const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${this.sdk?.connection.url}`); + private loadBasicAuth(): void { + const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${this.serverUrl}`); if (basicAuth) { this.setHeaders({ Authorization: `Basic ${basicAuth}` }); } @@ -67,6 +74,23 @@ class Sdk { return this.headers; } + setBasicAuth(basicAuth: string | null): void { + const url = this.serverUrl; + if (basicAuth) { + if (url) { + UserPreferences.setString(`${BASIC_AUTH_KEY}-${url}`, basicAuth); + } + this.setHeaders({ Authorization: `Basic ${basicAuth}` }); + } else { + if (url) { + UserPreferences.removeItem(`${BASIC_AUTH_KEY}-${url}`); + } + const next = { ...this.headers }; + delete next.Authorization; + this.headers = next; + } + } + /* get: DDPSDK['rest']['get'] = (...args: Parameters) => { const [endpoint, params, options] = args; diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index 36e0c1f62b1..8e3a6151bb8 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -62,8 +62,8 @@ const handleRequest = function* handleRequest({ data }) { const subCollection = db.get('subscriptions'); yield db.write(async () => { await subCollection.create(s => { - Object.assign(s, sub); s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); + Object.assign(s, sub); s.id = sub.rid; s._id = sub.rid; s.ts = sub.ts || new Date(); diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index b24c16774a8..00975655bdb 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -137,7 +137,7 @@ const getServerInfoSaga = function* getServerInfoSaga({ server, raiseError = tru const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }: ISelectServerAction) { try { - if (sdk.current?.connection?.url === server) { + if (sdk.server === server) { yield put(appStart({ root: RootEnum.ROOT_INSIDE })); yield put(selectServerCancel()); return; diff --git a/package.json b/package.json index 3e4ce479808..de27b1684e0 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@rocket.chat/ddp-client": "^0.3.50", "@rocket.chat/message-parser": "0.31.31", "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto", - "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", "@rocket.chat/ui-kit": "^0.39.0", "axios": "0.30.3", "bytebuffer": "~5.0.1", diff --git a/yarn.lock b/yarn.lock index 22d4c42ead2..50cd25d63de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4347,16 +4347,6 @@ ajv "^8.17.1" ajv-formats "^3.0.1" -"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": - version "1.3.3-mobile" - resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/b6d2b3f25b0ff8283dd71faf6dae720c47fecd8f" - dependencies: - js-sha256 "^0.9.0" - lru-cache "^4.1.1" - mem "^4.0.0" - tiny-events "^1.0.1" - universal-websocket-client "^1.0.2" - "@rocket.chat/ui-kit@^0.39.0": version "0.39.0" resolved "https://registry.yarnpkg.com/@rocket.chat/ui-kit/-/ui-kit-0.39.0.tgz#6eb656224201cb36b089a2c24da3fc6b35a07d96" @@ -9543,7 +9533,7 @@ js-base64@3.6.1: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.6.1.tgz#555aae398b74694b4037af1f8a5a6209d170efbe" integrity sha512-Frdq2+tRRGLQUIQOgsIGSCd1VePCS2fsddTG5dTCqR0JHgltXWfsxnY0gIXPoMeRmdom6Oyq+UMOFg5suduOjQ== -js-sha256@0.9.0, js-sha256@^0.9.0: +js-sha256@0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== @@ -9971,14 +9961,6 @@ lru-cache@^11.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.4.tgz#ecb523ebb0e6f4d837c807ad1abaea8e0619770d" integrity sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg== -lru-cache@^4.1.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -10007,13 +9989,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-or-similar@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" @@ -10049,15 +10024,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - memoize-one@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -10330,7 +10296,7 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -10769,16 +10735,6 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== - -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -11149,11 +11105,6 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - psl@^1.1.33: version "1.15.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.15.0.tgz#bdace31896f1d97cec6a79e8224898ce93d974c6" @@ -12937,11 +12888,6 @@ through@^2.3.6: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -tiny-events@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tiny-events/-/tiny-events-1.0.1.tgz#74690e99abb8a43c8fed3236a3c3872b27ce6376" - integrity sha512-QuhRLBsUWwrj+7mVvffHWmtHmMjt4GihlCN8/WucyHBqDINW9n9K5xsdfK3MdIeJIHRlmI4zI6izU1jbD3kn6Q== - tiny-invariant@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" @@ -13319,13 +13265,6 @@ unique-string@~2.0.0: dependencies: crypto-random-string "^2.0.0" -universal-websocket-client@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/universal-websocket-client/-/universal-websocket-client-1.0.3.tgz#ea37a0907d017c840a97c9c83c14768a2fc6cd2f" - integrity sha512-ZQF2hSasp4IM7Y3E+IAkA40c6kclsfhpligALvnuecEnU1dTJQZ5XlgXWLt3C8+uizDw5x48kCMBDPnLg/EVzA== - dependencies: - ws "^8.18.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -13820,11 +13759,6 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" From b1b4acce5440e11bf7c98570bf6e3e702c8e2f40 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:45:09 +0530 Subject: [PATCH 041/203] fix stream-notify-user --- app/lib/methods/subscriptions/rooms.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 7914f5b7278..03d2c325097 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -441,7 +441,16 @@ export default function subscribeRooms() { // set the server that started this task subServer = sdk.current?.connection.url || ''; const { id: userId } = store.getState().login.user; - sdk.subscribeRaw('stream-notify-user', userId); + [ + 'message', + 'notification', + 'rooms-changed', + 'subscriptions-changed', + 'uiInteraction', + 'e2ekeyRequest', + 'userData', + 'video-conference' + ].forEach(event => sdk.subscribeRaw('stream-notify-user', `${userId}/${event}`)); roomsSubscription = { stop: () => stop() }; return null; } catch (e) { From 27138b915374c048a04a099eac60a42e495ac36c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:51:04 +0530 Subject: [PATCH 042/203] directory fix --- app/lib/services/restApi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 1b64e9040c1..2d9ee97ad84 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -939,14 +939,14 @@ export const getDirectory = ({ const params: any = { count, offset, - sort + sort: JSON.stringify(sort) }; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.0.0')) { params.text = text; params.type = type; params.workspace = workspace; } else { - params.query = { text, type, workspace }; + params.query = JSON.stringify({ text, type, workspace }); } return sdk.get('/v1/directory', params); }; From 60d2fe9398d95ddf1717c639743120872f2a40b6 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:02:24 +0530 Subject: [PATCH 043/203] added normalizeResponseError --- app/lib/services/sdk.ts | 53 ++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 2a1dd587a5c..1be12faad61 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -17,6 +17,15 @@ import { type ResultFor } from '../../definitions/rest/helpers'; +async function normalizeResponseError(response: Response): Promise<{ status: number; data: any }> { + try { + const data = await response.clone().json(); + return { status: response.status, data }; + } catch { + return { status: response.status, data: {} }; + } +} + class Sdk { private sdk: DDPSDK | undefined; private serverUrl: string | undefined; @@ -129,7 +138,7 @@ class Sdk { }; */ - get>( + async get>( endpoint: TPath, params: void extends OperationParams<'GET', MatchPathPattern> ? void @@ -141,10 +150,14 @@ class Sdk { : Serialized>> ): Promise>>> { const sdk = this.ensureInitialized(); - // @ts-ignore - return sdk.rest.get(endpoint, params, { - headers: this.headers - }); + try { + // @ts-ignore + return await sdk.rest.get(endpoint, params, { + headers: this.headers + }); + } catch (e: any) { + throw e instanceof Response ? await normalizeResponseError(e) : e; + } } post>( @@ -179,11 +192,14 @@ class Sdk { } return resolve(result); } catch (e: any) { - const errorType = isMethodCall ? e?.error : e?.data?.errorType; + // @rocket.chat/api-client rejects with the raw fetch Response on REST errors. + // Normalize to { status, data } so callers (and the totp branch below) can read e.data.* + const normalized = !isMethodCall && e instanceof Response ? await normalizeResponseError(e) : e; + const errorType = isMethodCall ? normalized?.error : normalized?.data?.errorType; const totpInvalid = 'totp-invalid'; const totpRequired = 'totp-required'; if ([totpInvalid, totpRequired].includes(errorType)) { - const { details } = isMethodCall ? e : e?.data; + const { details } = isMethodCall ? normalized : normalized?.data; try { await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); return resolve(this.post(endpoint, params)); @@ -192,13 +208,13 @@ class Sdk { return resolve({} as any); } } else { - reject(e); + reject(normalized); } } }); } - delete>( + async delete>( endpoint: TPath, params: void extends OperationParams<'DELETE', MatchPathPattern> ? void @@ -210,10 +226,14 @@ class Sdk { : Serialized>> ): Promise>>> { const sdk = this.ensureInitialized(); - // @ts-ignore - return sdk.rest.delete(endpoint, params, { - headers: this.headers - }); + try { + // @ts-ignore + return await sdk.rest.delete(endpoint, params, { + headers: this.headers + }); + } catch (e: any) { + throw e instanceof Response ? await normalizeResponseError(e) : e; + } } async twoFactorHandler({ @@ -238,12 +258,7 @@ class Sdk { return loginResult.data; } catch (e: any) { if (e instanceof Response) { - try { - const data = await e.clone().json(); - return Promise.reject({ status: e.status, data }); - } catch { - return Promise.reject({ status: e.status, data: {} }); - } + return Promise.reject(await normalizeResponseError(e)); } return Promise.reject(e); } From b36b84c2340676aae8fb51eac8ca334330db1466 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:59:52 +0530 Subject: [PATCH 044/203] fix type error --- app/lib/methods/actions.ts | 7 +++++-- app/lib/services/restApi.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/lib/methods/actions.ts b/app/lib/methods/actions.ts index 7945f9ad7a5..a173beb9670 100644 --- a/app/lib/methods/actions.ts +++ b/app/lib/methods/actions.ts @@ -108,8 +108,11 @@ export async function triggerAction({ const payload = rest.payload ?? rest.value; try { - const { userId, authToken } = sdk.current.currentLogin; - const { host } = sdk.current.client; + const { userId, authToken } = sdk.currentLogin; + const host = sdk.server; + if (!host) { + throw new Error('SDK not initialized'); + } const interaction = toUserInteraction({ type, actionId, diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 2d9ee97ad84..17d87c9b881 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1182,7 +1182,7 @@ export const getUsersRoles = async (): Promise => { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.10.0')) { // RC 7.10.0 const response = await sdk.get('/v1/roles.getUsersInPublicRoles'); - if (response.users) { + if (response.success) { return response.users; } return false; From 49d5946473d1e3b1f9f5fecd719523c87658a298 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:20:28 +0530 Subject: [PATCH 045/203] added api typings --- app/definitions/rest/v1/chat.ts | 21 +++++++++++++++++++++ app/definitions/rest/v1/invites.ts | 3 +++ app/definitions/rest/v1/push.ts | 1 + app/definitions/rest/v1/teams.ts | 6 ++++++ app/definitions/rest/v1/users.ts | 2 +- app/lib/services/restApi.ts | 2 +- 6 files changed, 33 insertions(+), 2 deletions(-) diff --git a/app/definitions/rest/v1/chat.ts b/app/definitions/rest/v1/chat.ts index 21a1a625da4..cf04579f505 100644 --- a/app/definitions/rest/v1/chat.ts +++ b/app/definitions/rest/v1/chat.ts @@ -105,6 +105,27 @@ export type ChatEndpoints = { success: boolean; }; }; + 'chat.sendMessage': { + POST: (params: { + message: { + _id?: string; + rid: string; + msg?: string; + alias?: string; + emoji?: string; + avatar?: string; + attachments?: any[]; + tmid?: string; + tshow?: boolean; + blocks?: any[]; + customFields?: Record; + previewUrls?: string[]; + }; + }) => { + message: IMessage; + success: boolean; + }; + }; 'chat.getPinnedMessages': { GET: (params: { roomId: IServerRoom['_id']; offset: number; count: number }) => { messages: IMessageFromServer[]; diff --git a/app/definitions/rest/v1/invites.ts b/app/definitions/rest/v1/invites.ts index f725ec2ba9b..b9079cc00d6 100644 --- a/app/definitions/rest/v1/invites.ts +++ b/app/definitions/rest/v1/invites.ts @@ -2,6 +2,9 @@ import type { IInvite } from '../../IInvite'; import type { IServerRoom } from '../../IRoom'; export type InvitesEndpoints = { + findOrCreateInvite: { + POST: (params: { rid: string; days: number; maxUses: number }) => IInvite; + }; listInvites: { GET: () => Array; }; diff --git a/app/definitions/rest/v1/push.ts b/app/definitions/rest/v1/push.ts index f00f737bb0d..7d33dcce455 100644 --- a/app/definitions/rest/v1/push.ts +++ b/app/definitions/rest/v1/push.ts @@ -14,6 +14,7 @@ export type PushEndpoints = { userId: string; }; }; + DELETE: (params: { token: string }) => { success: boolean }; }; 'push.info': { GET: () => TPushInfo; diff --git a/app/definitions/rest/v1/teams.ts b/app/definitions/rest/v1/teams.ts index 73acf9d6083..07e5b12e352 100644 --- a/app/definitions/rest/v1/teams.ts +++ b/app/definitions/rest/v1/teams.ts @@ -3,6 +3,9 @@ import { type IServerTeamUpdateRoom, type ITeam, type TEAM_TYPE } from '../../IT import { type PaginatedResult } from '../helpers/PaginatedResult'; export type TeamsEndpoints = { + 'teams.delete': { + POST: (params: { teamId?: string; teamName?: string; roomsToRemove?: string[] }) => { success: boolean }; + }; 'teams.removeRoom': { POST: (params: { roomId: string; teamId: string }) => { room: IServerRoom }; }; @@ -38,4 +41,7 @@ export type TeamsEndpoints = { filter?: any; }) => PaginatedResult<{ rooms: IServerTeamUpdateRoom[] }>; }; + 'teams.leave': { + POST: (params: { teamId: string; rooms: string[] }) => { success: boolean }; + }; }; diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index 980c07510be..cd857e83b50 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -58,7 +58,7 @@ export type UsersEndpoints = { POST: (params: { userId: string }) => {}; }; 'users.setAvatar': { - POST: (params: { avatarUrl: string }) => { success: boolean }; + POST: (params: { avatarUrl: string; userId?: string }) => { success: boolean }; }; 'users.removeOtherTokens': { POST: (params: { userId: string }) => {}; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 17d87c9b881..ee5d4773f86 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -432,7 +432,7 @@ export const returnLivechat = (rid: string, departmentId?: string): Promise const serverVersion = reduxStore.getState().server.version; if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.12.0')) { - return sdk.post('/v1/livechat/room.returnAsInquiry', { roomId: rid, departmentId }); + return sdk.post('/v1/livechat/inquiries.returnAsInquiry', { roomId: rid, departmentId }); } // RC 0.72.0 From 21eded29f6160b67cb610edab30940b659151954 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:38:51 +0530 Subject: [PATCH 046/203] test fix --- app/lib/methods/actions.test.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/lib/methods/actions.test.ts b/app/lib/methods/actions.test.ts index 6bf08853022..47613ea410f 100644 --- a/app/lib/methods/actions.test.ts +++ b/app/lib/methods/actions.test.ts @@ -21,15 +21,11 @@ jest.mock('../navigation/appNavigation', () => ({ jest.mock('../services/sdk', () => ({ __esModule: true, default: { - current: { - currentLogin: { - userId: 'user-id', - authToken: 'auth-token' - }, - client: { - host: 'https://chat.example.com' - } - } + currentLogin: { + userId: 'user-id', + authToken: 'auth-token' + }, + server: 'https://chat.example.com' } })); From 4fc942e8db530124dc57b1e7b6c9922effbcfd31 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 1 May 2026 20:32:11 +0530 Subject: [PATCH 047/203] fix more api typings --- app/definitions/rest/v1/channels.ts | 2 +- app/definitions/rest/v1/groups.ts | 3 +++ app/definitions/rest/v1/teams.ts | 2 +- app/lib/services/restApi.ts | 4 ++-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/definitions/rest/v1/channels.ts b/app/definitions/rest/v1/channels.ts index c02e76cdac9..7c6961c3fd4 100644 --- a/app/definitions/rest/v1/channels.ts +++ b/app/definitions/rest/v1/channels.ts @@ -71,7 +71,7 @@ export type ChannelsEndpoints = { }; }; 'channels.join': { - POST: (params: { roomId: string; joinCode: string | null }) => { channel: IServerRoom }; + POST: (params: { roomId: string; joinCode?: string | null }) => { channel: IServerRoom; success: boolean }; }; 'channels.close': { POST: (params: { roomId: string }) => {}; diff --git a/app/definitions/rest/v1/groups.ts b/app/definitions/rest/v1/groups.ts index 93ac8c9f43f..1043784ea0a 100644 --- a/app/definitions/rest/v1/groups.ts +++ b/app/definitions/rest/v1/groups.ts @@ -68,6 +68,9 @@ export type GroupsEndpoints = { 'groups.close': { POST: (params: { roomId: string }) => {}; }; + 'groups.open': { + POST: (params: { roomId: string } | { roomName: string }) => { success: boolean }; + }; 'groups.kick': { POST: (params: { roomId: string; userId: string }) => {}; }; diff --git a/app/definitions/rest/v1/teams.ts b/app/definitions/rest/v1/teams.ts index 07e5b12e352..e49a0c60747 100644 --- a/app/definitions/rest/v1/teams.ts +++ b/app/definitions/rest/v1/teams.ts @@ -42,6 +42,6 @@ export type TeamsEndpoints = { }) => PaginatedResult<{ rooms: IServerTeamUpdateRoom[] }>; }; 'teams.leave': { - POST: (params: { teamId: string; rooms: string[] }) => { success: boolean }; + POST: (params: ({ teamId: string } | { teamName: string }) & { rooms?: string[] }) => { success: boolean }; }; }; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index ee5d4773f86..8606428efb6 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -741,7 +741,7 @@ export const setAvatarFromService = async ({ // RC 8.0.0 — REST users.setAvatar (multipart image or JSON avatarUrl) if (service === 'url' && typeof data === 'string') { - await sdk.post('users.setAvatar', { avatarUrl: data }); + await sdk.post('/v1/users.setAvatar', { avatarUrl: data }); return; } @@ -751,7 +751,7 @@ export const setAvatarFromService = async ({ } if (isHttpUrl) { - await sdk.post('users.setAvatar', { avatarUrl: url }); + await sdk.post('/v1/users.setAvatar', { avatarUrl: url }); return; } From c4b1385fb8997affab23c048d08b9da6c2c366dd Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 1 May 2026 20:33:54 +0530 Subject: [PATCH 048/203] fix test --- app/lib/services/setAvatarFromService.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/services/setAvatarFromService.test.ts b/app/lib/services/setAvatarFromService.test.ts index d52901a81a9..06812c223e3 100644 --- a/app/lib/services/setAvatarFromService.test.ts +++ b/app/lib/services/setAvatarFromService.test.ts @@ -60,7 +60,7 @@ describe('setAvatarFromService', () => { service: 'url', url: 'https://example.com/a.png' }); - expect(sdk.post).toHaveBeenCalledWith('users.setAvatar', { + expect(sdk.post).toHaveBeenCalledWith('/v1/users.setAvatar', { avatarUrl: 'https://example.com/a.png' }); expect(sdk.methodCallWrapper).not.toHaveBeenCalled(); @@ -85,7 +85,7 @@ describe('setAvatarFromService', () => { service: 'upload', url: 'https://example.com/remote.jpg' }); - expect(sdk.post).toHaveBeenCalledWith('users.setAvatar', { + expect(sdk.post).toHaveBeenCalledWith('/v1/users.setAvatar', { avatarUrl: 'https://example.com/remote.jpg' }); expect(uploadUserAvatarMultipart).not.toHaveBeenCalled(); @@ -98,7 +98,7 @@ describe('setAvatarFromService', () => { service: 'google', url: 'https://lh3.googleusercontent.com/a/abc' }); - expect(sdk.post).toHaveBeenCalledWith('users.setAvatar', { + expect(sdk.post).toHaveBeenCalledWith('/v1/users.setAvatar', { avatarUrl: 'https://lh3.googleusercontent.com/a/abc' }); expect(uploadUserAvatarMultipart).not.toHaveBeenCalled(); From fbf9f5ec5090b8876b7b6e8d91da1b0f66c35939 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 1 May 2026 21:44:54 +0530 Subject: [PATCH 049/203] fix methodCall --- app/lib/services/sdk.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 1be12faad61..1909a9bbbea 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -270,8 +270,8 @@ class Sdk { if (!this.current || !this.current.client) { throw new Error('SDK not initialized'); } - // @ts-ignore - const result = await this.current.client.callAsyncWithOptions(...args, this.code || ''); + const [method, ...params] = args; + const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, this.code || ''); return resolve(result); } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { From e4ab324cae9c540ee648f448a8285bf7b8efd409 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 2 May 2026 00:23:22 +0530 Subject: [PATCH 050/203] fix --- app/lib/constants/defaultSettings.ts | 2 +- app/lib/services/sdk.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/lib/constants/defaultSettings.ts b/app/lib/constants/defaultSettings.ts index aac112fd0f2..0b1c5b30b39 100644 --- a/app/lib/constants/defaultSettings.ts +++ b/app/lib/constants/defaultSettings.ts @@ -244,7 +244,7 @@ export const defaultSettings = { type: 'valueAsBoolean' }, Accounts_AllowInvisibleStatusOption: { - type: 'valueAsString' + type: 'valueAsBoolean' }, Canned_Responses_Enable: { type: 'valueAsBoolean' diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 1909a9bbbea..21c936e20a4 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -6,7 +6,7 @@ import DeviceInfo from 'react-native-device-info'; import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; -import { random } from '../methods/helpers'; +import { compareServerVersion, random } from '../methods/helpers'; import UserPreferences from '../methods/userPreferences'; import { BASIC_AUTH_KEY } from '../methods/helpers/fetch'; import { @@ -326,8 +326,14 @@ class Sdk { return Promise.resolve(subscriptions); } try { + const { version: serverVersion } = reduxStore.getState().server; + const topic = 'stream-notify-room'; + const typingEvent = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.0.0') ? 'user-activity' : 'typing'; subscriptions.push(this.current.client.subscribe('stream-room-messages', rid)); - subscriptions.push(this.current.client.subscribe('stream-notify-room', rid, { useCollection: false })); + subscriptions.push(this.current.client.subscribe(topic, `${rid}/${typingEvent}`)); + subscriptions.push(this.current.client.subscribe(topic, `${rid}/deleteMessage`)); + subscriptions.push(this.current.client.subscribe(topic, `${rid}/deleteMessageBulk`)); + subscriptions.push(this.current.client.subscribe(topic, `${rid}/messagesRead`)); return Promise.resolve(subscriptions); } catch (e) { return Promise.resolve(subscriptions); From 6e9606ef452eeb6cf03c7ececd41cf5969bde765 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 2 May 2026 01:19:56 +0530 Subject: [PATCH 051/203] fix test --- .maestro/tests/room/room.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.maestro/tests/room/room.yaml b/.maestro/tests/room/room.yaml index b29ad58be49..017b487192b 100644 --- a/.maestro/tests/room/room.yaml +++ b/.maestro/tests/room/room.yaml @@ -405,7 +405,7 @@ tags: message: 'message to delete' - extendedWaitUntil: visible: - text: '.*message to delete.*' + id: 'message-content-message to delete' timeout: 60000 - longPressOn: id: 'message-content-message to delete' @@ -430,7 +430,7 @@ tags: text: 'Delete' - extendedWaitUntil: notVisible: - text: '.*message to delete.*' + id: 'message-content-message to delete' timeout: 60000 - runFlow: '../../helpers/go-back.yaml' From b6397142d454abf11fc2ab4e211aa9ef4bc76d7b Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 2 May 2026 01:32:35 +0530 Subject: [PATCH 052/203] fix star message loading --- app/lib/services/restApi.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 8606428efb6..f5389aa61fc 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -768,7 +768,7 @@ export const getFiles = (roomId: string, type: SubscriptionType, offset: number) return sdk.get(`/v1/${roomTypeToApiType(t)}.files`, { roomId, offset, - sort: { uploadedAt: -1 } + sort: JSON.stringify({ uploadedAt: -1 }) }); }; @@ -794,7 +794,7 @@ export const getMessages = ({ const params: any = { roomId, offset, - sort: { ts: -1 } + sort: JSON.stringify({ ts: -1 }) }; if (mentionIds && mentionIds.length > 0) { @@ -814,19 +814,24 @@ export const getMessages = ({ const params: any = { roomId, offset, - sort: { ts: -1 } + sort: JSON.stringify({ ts: -1 }) }; + const query: any = {}; if (mentionIds && mentionIds.length > 0) { - params.query = { ...params.query, 'mentions._id': { $in: mentionIds } }; + query['mentions._id'] = { $in: mentionIds }; } if (starredIds && starredIds.length > 0) { - params.query = { ...params.query, 'starred._id': { $in: starredIds } }; + query['starred._id'] = { $in: starredIds }; } if (pinned) { - params.query = { ...params.query, pinned: true }; + query.pinned = true; + } + + if (Object.keys(query).length > 0) { + params.query = JSON.stringify(query); } // RC 0.59.0 From d2ce24ca73d50b0cadd0f58c444f989f724c470c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 3 May 2026 03:27:47 +0530 Subject: [PATCH 053/203] disable keyboard focus on e2e builds --- app/views/RoomView/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index 3675c7a5782..91c467c2c0e 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -264,7 +264,7 @@ class RoomView extends React.Component { } // Skip autofocus in development because simulators always report a keyboard as connected, // which would force the composer to open on every focus while debugging. - if (__DEV__) { + if (__DEV__ || process.env.RUNNING_E2E_TESTS === 'true') { return; } const hasExternalKeyboard = isExternalKeyboardConnected(); From e735369296d1757316198cfc168700642f51a6d8 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 3 May 2026 20:13:31 +0530 Subject: [PATCH 054/203] logout change --- app/lib/services/sdk.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 21c936e20a4..b3130bf2c4d 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -385,7 +385,10 @@ class Sdk { async logout(): Promise { if (this.current?.account) { - await this.current.account.logout(); + await Promise.race([ + this.current.account.logout(), + new Promise(resolve => setTimeout(resolve, 5000)) + ]); } } From 0db8b2de4304a48f63806a909e8784cf12c7a04d Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Sun, 3 May 2026 14:53:44 +0000 Subject: [PATCH 055/203] chore: format code and fix lint issues --- app/lib/services/sdk.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index b3130bf2c4d..af2b80be66a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -385,10 +385,7 @@ class Sdk { async logout(): Promise { if (this.current?.account) { - await Promise.race([ - this.current.account.logout(), - new Promise(resolve => setTimeout(resolve, 5000)) - ]); + await Promise.race([this.current.account.logout(), new Promise(resolve => setTimeout(resolve, 5000))]); } } From 4f31ec1443b3a73bb8943ea18ba3dcc5da7b8661 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 3 May 2026 20:33:28 +0530 Subject: [PATCH 056/203] rerun ci --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index af2b80be66a..273b604cca9 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -384,7 +384,7 @@ class Sdk { } async logout(): Promise { - if (this.current?.account) { + if (this.current?.account) { await Promise.race([this.current.account.logout(), new Promise(resolve => setTimeout(resolve, 5000))]); } } From 34d7df3dff43ed83194dc6b595bddd2d9db79d24 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 3 May 2026 20:33:32 +0530 Subject: [PATCH 057/203] revert --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 273b604cca9..af2b80be66a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -384,7 +384,7 @@ class Sdk { } async logout(): Promise { - if (this.current?.account) { + if (this.current?.account) { await Promise.race([this.current.account.logout(), new Promise(resolve => setTimeout(resolve, 5000))]); } } From 2b1a2120b740feca224de7461deb83312a28fd38 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 3 May 2026 22:56:47 +0530 Subject: [PATCH 058/203] making code rabbit happy --- .../MessageComposer/hooks/useAutocomplete.ts | 3 + app/definitions/rest/v1/chat.ts | 1 + app/definitions/rest/v1/teams.ts | 2 +- .../omnichannel/lib/subscriptions/inquiry.ts | 21 ++++++- app/lib/methods/helpers/fetch.ts | 5 +- app/lib/methods/subscriptions/rooms.ts | 22 ++++++- app/lib/services/restApi.ts | 57 +++++++++---------- app/sagas/videoConf.ts | 1 + app/views/ThreadMessagesView/index.tsx | 2 + 9 files changed, 75 insertions(+), 39 deletions(-) diff --git a/app/containers/MessageComposer/hooks/useAutocomplete.ts b/app/containers/MessageComposer/hooks/useAutocomplete.ts index dc1c34f153d..d8638b3f9f9 100644 --- a/app/containers/MessageComposer/hooks/useAutocomplete.ts +++ b/app/containers/MessageComposer/hooks/useAutocomplete.ts @@ -206,6 +206,9 @@ export const useAutocomplete = ({ updateAutocompleteVisible(true); accessibilityFocusOnInput(); } + } else { + setItems([]); + updateAutocompleteVisible(false); } } } catch (e) { diff --git a/app/definitions/rest/v1/chat.ts b/app/definitions/rest/v1/chat.ts index cf04579f505..71b7e89ffba 100644 --- a/app/definitions/rest/v1/chat.ts +++ b/app/definitions/rest/v1/chat.ts @@ -70,6 +70,7 @@ export type ChatEndpoints = { result: { updated: IMessageFromServer[]; deleted: IMessageFromServer[]; + cursor?: { next?: number; previous?: number }; }; }; }; diff --git a/app/definitions/rest/v1/teams.ts b/app/definitions/rest/v1/teams.ts index e49a0c60747..507bbf5a670 100644 --- a/app/definitions/rest/v1/teams.ts +++ b/app/definitions/rest/v1/teams.ts @@ -4,7 +4,7 @@ import { type PaginatedResult } from '../helpers/PaginatedResult'; export type TeamsEndpoints = { 'teams.delete': { - POST: (params: { teamId?: string; teamName?: string; roomsToRemove?: string[] }) => { success: boolean }; + POST: (params: ({ teamId: string } | { teamName: string }) & { roomsToRemove?: string[] }) => { success: boolean }; }; 'teams.removeRoom': { POST: (params: { roomId: string; teamId: string }) => { room: IServerRoom }; diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index d9aaaac5572..815136d4fff 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -24,6 +24,7 @@ const removeListener = (listener: any) => listener.stop(); let connectedListener: any; let queueListener: any; +let departmentListeners: any[] = []; const streamTopic = 'stream-livechat-inquiry-queue-observer'; @@ -70,6 +71,14 @@ export default function subscribeInquiry() { queueListener.then(removeListener); queueListener = false; } + departmentListeners.forEach(listener => { + try { + removeListener(listener); + } catch (e) { + log(e); + } + }); + departmentListeners = []; }; connectedListener = sdk.onStreamData('connected', handleConnection); @@ -87,18 +96,24 @@ export default function subscribeInquiry() { if (result.success) { const { departments } = result; + const trackSubscription = (sub: ReturnType) => { + if (sub) { + departmentListeners.push(sub); + } + }; + if (!departments?.length || hasRole('livechat-manager')) { - sdk.subscribe(streamTopic, 'public'); + trackSubscription(sdk.subscribe(streamTopic, 'public')); } const departmentIds = departments?.map(({ departmentId }) => departmentId) || []; departmentIds.forEach((departmentId: string) => { // subscribe to all departments of the agent - sdk.subscribe(streamTopic, `department/${departmentId}`); + trackSubscription(sdk.subscribe(streamTopic, `department/${departmentId}`)); }); } }) - .catch((e: unknown) => console.log(e)); + .catch((e: unknown) => log(e)); return { stop: () => stop() diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index 3bdeb7e9d07..ed578e37af2 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -33,9 +33,6 @@ export const setBasicAuth = (basicAuth: string | null): void => { export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; export default (url: string, options: IOptions = {}): Promise => { - let customOptions = { ...options, headers: sdk.getHeaders() }; - if (options && options.headers) { - customOptions = { ...customOptions, headers: { ...options.headers, ...customOptions.headers } }; - } + const customOptions = { ...options, headers: { ...sdk.getHeaders(), ...(options.headers || {}) } }; return fetch(url, customOptions); }; diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 03d2c325097..ada13d87112 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -42,6 +42,7 @@ let streamListener: Promise | false; let subServer: string; let queue: { [key: string]: ISubscription | IRoom } = {}; let subTimer: ReturnType | null | false = null; +let rawSubscriptionHandles: { stop: () => void }[] = []; const WINDOW_TIME = 500; export let roomsSubscription: { stop: () => void } | null = null; @@ -427,6 +428,14 @@ export default function subscribeRooms() { streamListener.then(removeListener); streamListener = false; } + rawSubscriptionHandles.forEach(handle => { + try { + handle.stop(); + } catch (e) { + log(e); + } + }); + rawSubscriptionHandles = []; queue = {}; if (subTimer) { clearTimeout(subTimer); @@ -440,7 +449,11 @@ export default function subscribeRooms() { try { // set the server that started this task subServer = sdk.current?.connection.url || ''; - const { id: userId } = store.getState().login.user; + const { user } = store.getState().login; + const userId = user?.id; + if (!userId) { + throw new Error('subscribeRooms: missing userId, cannot create stream-notify-user subscriptions'); + } [ 'message', 'notification', @@ -450,7 +463,12 @@ export default function subscribeRooms() { 'e2ekeyRequest', 'userData', 'video-conference' - ].forEach(event => sdk.subscribeRaw('stream-notify-user', `${userId}/${event}`)); + ].forEach(event => { + const handle = sdk.subscribeRaw('stream-notify-user', `${userId}/${event}`); + if (handle) { + rawSubscriptionHandles.push(handle); + } + }); roomsSubscription = { stop: () => stop() }; return null; } catch (e) { diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index f5389aa61fc..0f546dea9ce 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1053,39 +1053,38 @@ export const editMessage = async (message: Pick - new Promise(async resolve => { - const token = getDeviceToken(); - if (token) { - const type: IPushTokenTypes = isIOS ? 'apn' : 'gcm'; - const data = { - value: token, - type, - appName: getBundleId - }; - try { - // RC 0.60.0 - await sdk.post('/v1/push.token', data); - } catch (error) { - console.log(error); - } - } - return resolve(); - }); +export const registerPushToken = async (): Promise => { + const token = getDeviceToken(); + if (!token) { + return; + } + const type: IPushTokenTypes = isIOS ? 'apn' : 'gcm'; + const data = { + value: token, + type, + appName: getBundleId + }; + try { + // RC 0.60.0 + await sdk.post('/v1/push.token', data); + } catch (error) { + console.log(error); + } +}; export const removePushToken = async (): Promise => { const token = getDeviceToken(); - if (token) { - try { - // RC 0.60.0 - await sdk.delete('/v1/push.token', { token }); - return true; - } catch (e) { - console.log(e); - return false; - } + if (!token) { + return true; + } + try { + // RC 0.60.0 + const response = await sdk.delete('/v1/push.token', { token }); + return response?.success === true; + } catch (e) { + console.log(e); + return false; } - return true; }; // RC 6.6.0 diff --git a/app/sagas/videoConf.ts b/app/sagas/videoConf.ts index 1b66c717b54..03da4e102ea 100644 --- a/app/sagas/videoConf.ts +++ b/app/sagas/videoConf.ts @@ -186,6 +186,7 @@ function* initCall({ payload: { mic, cam, direct, rid } }: { payload: TCallProps } else { yield put(setCalling(false)); yield call(hideActionSheetRef); + showErrorAlert(i18n.t('error-init-video-conf')); } } catch (e) { yield put(setCalling(false)); diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index c83b0da2960..ee701d2cecf 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -348,6 +348,8 @@ class ThreadMessagesView extends React.Component Date: Wed, 20 May 2026 19:43:48 +0530 Subject: [PATCH 059/203] changes --- app/lib/methods/subscriptions/rooms.ts | 4 +- app/lib/services/connect.test.ts | 78 +++++- app/lib/services/connect.ts | 21 +- app/lib/services/ddpSocket.test.ts | 183 -------------- app/lib/services/restApi.test.ts | 8 +- app/lib/services/sdk.test.ts | 235 ++++++++++++++++++ app/lib/services/sdk.ts | 104 ++++++++ app/lib/services/voip/MediaSessionInstance.ts | 2 +- jest.config.js | 2 +- 9 files changed, 443 insertions(+), 194 deletions(-) delete mode 100644 app/lib/services/ddpSocket.test.ts create mode 100644 app/lib/services/sdk.test.ts diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index ada13d87112..7e6a8d5e05f 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -462,7 +462,9 @@ export default function subscribeRooms() { 'uiInteraction', 'e2ekeyRequest', 'userData', - 'video-conference' + 'video-conference', + 'media-signal', + 'media-calls' ].forEach(event => { const handle = sdk.subscribeRaw('stream-notify-user', `${userId}/${event}`); if (handle) { diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index d632eb0f917..1cf26aa7626 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -1,6 +1,30 @@ -import { determineAuthType, disconnect } from './connect'; +import { checkAndReopen, determineAuthType, disconnect } from './connect'; import { mediaSessionInstance } from './voip/MediaSessionInstance'; +jest.mock('./sdk', () => { + const state: { current: any } = { current: undefined }; + return { + __esModule: true, + default: { + get current() { + return state.current; + }, + disconnect: jest.fn(), + probe: jest.fn().mockResolvedValue(true), + forceReopen: jest.fn().mockResolvedValue(true) + }, + __setCurrent: (value: any) => { + state.current = value; + } + }; +}); + +const sdkMock = jest.requireMock('./sdk') as { + default: { probe: jest.Mock; forceReopen: jest.Mock }; + __setCurrent: (v: any) => void; +}; +const setSdkCurrent = (value: any) => sdkMock.__setCurrent(value); + jest.mock('./voip/MediaSessionInstance', () => ({ mediaSessionInstance: { reset: jest.fn() } })); @@ -313,4 +337,56 @@ describe('VoIP media session lifecycle (disconnect)', () => { }); }); +describe('checkAndReopen', () => { + beforeEach(() => { + sdkMock.default.probe.mockReset().mockResolvedValue(true); + sdkMock.default.forceReopen.mockReset().mockResolvedValue(true); + }); + + afterEach(() => { + setSdkCurrent(undefined); + }); + + it('resolves false when sdk.current is undefined', async () => { + setSdkCurrent(undefined); + await expect(checkAndReopen()).resolves.toBe(false); + expect(sdkMock.default.probe).not.toHaveBeenCalled(); + expect(sdkMock.default.forceReopen).not.toHaveBeenCalled(); + }); + + it.each(['disconnected', 'closed', 'reconnecting', 'idle', 'failed'])( + 'calls forceReopen without probing when status is %s', + async status => { + setSdkCurrent({ connection: { status } }); + await expect(checkAndReopen()).resolves.toBe(true); + expect(sdkMock.default.probe).not.toHaveBeenCalled(); + expect(sdkMock.default.forceReopen).toHaveBeenCalledTimes(1); + } + ); + + it('probes when status is connected and returns true without reopening on live socket', async () => { + sdkMock.default.probe.mockResolvedValue(true); + setSdkCurrent({ connection: { status: 'connected' } }); + await expect(checkAndReopen()).resolves.toBe(true); + expect(sdkMock.default.probe).toHaveBeenCalledTimes(1); + expect(sdkMock.default.forceReopen).not.toHaveBeenCalled(); + }); + + it('probes when status is connected and forces reopen on zombie socket', async () => { + sdkMock.default.probe.mockResolvedValue(false); + sdkMock.default.forceReopen.mockResolvedValue(true); + setSdkCurrent({ connection: { status: 'connected' } }); + await expect(checkAndReopen()).resolves.toBe(true); + expect(sdkMock.default.probe).toHaveBeenCalledTimes(1); + expect(sdkMock.default.forceReopen).toHaveBeenCalledTimes(1); + }); + + it('forwards forceReopen failure (resolves false, does not throw) when probe fails too', async () => { + sdkMock.default.probe.mockResolvedValue(false); + sdkMock.default.forceReopen.mockResolvedValue(false); + setSdkCurrent({ connection: { status: 'connected' } }); + await expect(checkAndReopen()).resolves.toBe(false); + }); +}); + // Note: Apple authentication when isIOS is true is tested in connect.ios.test.ts diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 80baa8bab2e..ecc484b869f 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -349,8 +349,24 @@ async function loginOAuthOrSso(params: ICredentials, isFromWebView = true) { store.dispatch(loginRequest({ resume: result.token }, false, isFromWebView)); } -function checkAndReopen() { - return sdk.current.checkAndReopen(); +/** + * Decide whether the socket needs reopening before warm-accept replays signals. + * - status !== 'connected' → socket known dead, force reopen + * - status === 'connected' → probe (2s ping/pong). Alive → no-op. + * Dead (zombie socket, no `onclose` fired) → force reopen. + * Status alone is not enough on mobile, where NAT timeouts leave the socket in + * `connected` until the ddp-client's 60s passive heartbeat catches it. Probing + * keeps the warm-accept budget bounded. + */ +async function checkAndReopen(): Promise { + const conn = sdk.current?.connection; + if (!conn) return false; + if (conn.status !== 'connected') { + return sdk.forceReopen(); + } + const alive = await sdk.probe(); + if (alive) return true; + return sdk.forceReopen(); } /** @@ -471,7 +487,6 @@ export { loginOAuthOrSso, checkAndReopen, awaitDdpLoggedIn, - abort, connect, disconnect, getWebsocketInfo, diff --git a/app/lib/services/ddpSocket.test.ts b/app/lib/services/ddpSocket.test.ts deleted file mode 100644 index 1fe4a165697..00000000000 --- a/app/lib/services/ddpSocket.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { Socket } = require('@rocket.chat/sdk/lib/drivers/ddp'); - -const buildSocket = () => { - const socket = new Socket({ - logger: { debug: jest.fn(), info: jest.fn(), error: jest.fn(), warn: jest.fn() }, - timeout: 10000 - }); - const send = jest.fn(); - const close = jest.fn(); - socket.connection = { - send, - close, - readyState: 1, - onopen: jest.fn(), - onmessage: jest.fn(), - onerror: jest.fn(), - onclose: jest.fn() - }; - return { socket, send, close }; -}; - -describe('Socket.probe', () => { - afterEach(() => { - jest.useRealTimers(); - }); - - it('resolves true when pong arrives within deadline', async () => { - const { socket } = buildSocket(); - const probePromise = socket.probe(); - socket.emit('pong'); - await expect(probePromise).resolves.toBe(true); - }); - - it('resolves false when no pong arrives within 2s deadline', async () => { - jest.useFakeTimers(); - const { socket } = buildSocket(); - const probePromise = socket.probe(); - await jest.advanceTimersByTimeAsync(2000); - await expect(probePromise).resolves.toBe(false); - }); - - it('resolves false when raw connection.send throws', async () => { - const { socket, send } = buildSocket(); - send.mockImplementation(() => { - throw new Error('boom'); - }); - await expect(socket.probe()).resolves.toBe(false); - }); -}); - -describe('Socket.forceReopen awaitability', () => { - it('returned promise resolves only after open() resolves', async () => { - const { socket } = buildSocket(); - let resolveOpen: () => void = () => undefined; - const openPromise = new Promise(res => { - resolveOpen = res; - }); - socket.open = jest.fn(() => openPromise); - - let settled = false; - const result = socket.forceReopen(); - result.then(() => { - settled = true; - }); - - await Promise.resolve(); - await Promise.resolve(); - expect(settled).toBe(false); - - resolveOpen(); - await result; - expect(settled).toBe(true); - }); - - it('concurrent invocations share the same in-flight reconnect', async () => { - const { socket } = buildSocket(); - let resolveOpen: () => void = () => undefined; - const openPromise = new Promise(res => { - resolveOpen = res; - }); - const openMock = jest.fn(() => openPromise); - socket.open = openMock; - - const a = socket.forceReopen(); - const b = socket.forceReopen(); - - expect(openMock).toHaveBeenCalledTimes(1); - - resolveOpen(); - await Promise.all([a, b]); - expect(openMock).toHaveBeenCalledTimes(1); - }); - - it("emits 'close' synchronously before opening so app-level Redux disconnect dispatches", async () => { - const { socket } = buildSocket(); - socket.open = jest.fn(() => Promise.resolve()); - const closeListener = jest.fn(); - socket.on('close', closeListener); - const reopenPromise = socket.forceReopen(); - // Synchronous emit: must have fired by the time forceReopen returns. - expect(closeListener).toHaveBeenCalledTimes(1); - const [event] = closeListener.mock.calls[0]; - expect(event).toEqual({ code: 4000 }); - await reopenPromise; - }); -}); - -describe('Socket.checkAndReopen bucket dispatch', () => { - const buildWithSpies = () => { - const { socket } = buildSocket(); - const { ping } = socket.config; - const forceReopen = jest.fn(); - const probe = jest.fn(); - socket.forceReopen = forceReopen; - socket.probe = probe; - return { socket, forceReopen, probe, ping }; - }; - - it('stale (elapsed > ping*2) calls forceReopen and skips probe', async () => { - const { socket, forceReopen, probe, ping } = buildWithSpies(); - socket.lastPing = Date.now() - ping * 2 - 1000; - await socket.checkAndReopen(); - expect(forceReopen).toHaveBeenCalledTimes(1); - expect(probe).not.toHaveBeenCalled(); - }); - - it('fresh (elapsed < 2000ms) is a no-op', async () => { - const { socket, forceReopen, probe } = buildWithSpies(); - socket.lastPing = Date.now() - 500; - await socket.checkAndReopen(); - expect(forceReopen).not.toHaveBeenCalled(); - expect(probe).not.toHaveBeenCalled(); - }); - - it('gray-zone with successful probe does not call forceReopen', async () => { - const { socket, forceReopen, probe } = buildWithSpies(); - probe.mockResolvedValue(true); - socket.lastPing = Date.now() - 5000; - await socket.checkAndReopen(); - expect(probe).toHaveBeenCalledTimes(1); - expect(forceReopen).not.toHaveBeenCalled(); - }); - - it('gray-zone with failed probe calls forceReopen', async () => { - const { socket, forceReopen, probe } = buildWithSpies(); - probe.mockResolvedValue(false); - socket.lastPing = Date.now() - 5000; - await socket.checkAndReopen(); - expect(probe).toHaveBeenCalledTimes(1); - expect(forceReopen).toHaveBeenCalledTimes(1); - }); - - it('stale and probe-fail buckets resolve only after forceReopen resolves', async () => { - const tryBucket = async (setup: (s: any) => void) => { - const { socket } = buildSocket(); - let resolveReopen: () => void = () => undefined; - const reopenPromise = new Promise(res => { - resolveReopen = res; - }); - socket.forceReopen = jest.fn(() => reopenPromise); - socket.probe = jest.fn().mockResolvedValue(false); - setup(socket); - let settled = false; - const p = socket.checkAndReopen().then(() => { - settled = true; - }); - await Promise.resolve(); - await Promise.resolve(); - expect(settled).toBe(false); - resolveReopen(); - await p; - expect(settled).toBe(true); - }; - - await tryBucket(socket => { - socket.lastPing = Date.now() - socket.config.ping * 2 - 1000; - }); - await tryBucket(socket => { - socket.lastPing = Date.now() - 5000; - }); - }); -}); diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index 0eeeab394b2..810a28eb0a1 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -160,7 +160,7 @@ describe('registerPushToken', () => { expect(mockSdkPost).toHaveBeenCalledTimes(1); expect(mockSdkPost).toHaveBeenCalledWith( - 'push.token', + '/v1/push.token', expect.objectContaining({ id: 'unique-device-id', value: 'apns-token', @@ -181,7 +181,7 @@ describe('registerPushToken', () => { expect(mockSdkPost).toHaveBeenCalledTimes(1); expect(mockSdkPost).toHaveBeenCalledWith( - 'push.token', + '/v1/push.token', expect.objectContaining({ id: 'unique-device-id', value: 'fcm-token', @@ -212,7 +212,7 @@ describe('registerPushToken', () => { await registerPushToken(); expect(mockSdkPost).toHaveBeenCalledWith( - 'push.token', + '/v1/push.token', expect.objectContaining({ id: 'unique-device-id', value: 'apns-token', @@ -252,7 +252,7 @@ describe('registerPushToken', () => { await registerPushToken(); expect(mockSdkPost).toHaveBeenCalledWith( - 'push.token', + '/v1/push.token', expect.objectContaining({ id: 'unique-device-id' }) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts new file mode 100644 index 00000000000..dd173aaa27b --- /dev/null +++ b/app/lib/services/sdk.test.ts @@ -0,0 +1,235 @@ +import sdk from './sdk'; + +const setInternalSdk = (value: any) => { + (sdk as unknown as { sdk: any }).sdk = value; +}; + +const buildFakeDdp = () => { + const pongHandlers = new Set<() => void>(); + const ping = jest.fn(); + const on = jest.fn((event: string, cb: () => void) => { + if (event === 'pong') pongHandlers.add(cb); + return () => pongHandlers.delete(cb); + }); + const emitPong = () => pongHandlers.forEach(cb => cb()); + return { ping, on, emitPong, pongHandlers }; +}; + +const buildFakeConnection = (overrides: Partial<{ close: jest.Mock; reconnect: jest.Mock }> = {}) => ({ + close: jest.fn(), + reconnect: jest.fn().mockResolvedValue(true), + ...overrides +}); + +const buildFakeSdk = (opts: { ddp?: any; connection?: any } = {}) => { + const fakeDdp = opts.ddp ?? buildFakeDdp(); + const fakeConnection = opts.connection ?? buildFakeConnection(); + return { + client: { ddp: fakeDdp }, + connection: fakeConnection, + __fakeDdp: fakeDdp, + __fakeConnection: fakeConnection + }; +}; + +afterEach(() => { + setInternalSdk(undefined); + (sdk as unknown as { reopenInFlight: Promise | null }).reopenInFlight = null; + jest.useRealTimers(); +}); + +describe('Sdk.probe', () => { + it('returns false when sdk.current is undefined', async () => { + setInternalSdk(undefined); + await expect(sdk.probe()).resolves.toBe(false); + }); + + it('returns false when inner ddp client lacks ping/on (defensive)', async () => { + setInternalSdk({ client: {}, connection: buildFakeConnection() }); + await expect(sdk.probe()).resolves.toBe(false); + }); + + it('resolves true when pong fires before the timeout', async () => { + const fake = buildFakeSdk(); + setInternalSdk(fake); + const promise = sdk.probe(2000); + fake.__fakeDdp.emitPong(); + await expect(promise).resolves.toBe(true); + expect(fake.__fakeDdp.ping).toHaveBeenCalledTimes(1); + }); + + it('resolves false when timeout elapses without pong', async () => { + jest.useFakeTimers(); + const fake = buildFakeSdk(); + setInternalSdk(fake); + const promise = sdk.probe(2000); + jest.advanceTimersByTime(2000); + await expect(promise).resolves.toBe(false); + }); + + it('resolves false when ping() throws', async () => { + const fake = buildFakeSdk(); + fake.__fakeDdp.ping.mockImplementation(() => { + throw new Error('boom'); + }); + setInternalSdk(fake); + await expect(sdk.probe()).resolves.toBe(false); + }); + + it('removes the pong listener after settling so it does not leak', async () => { + const fake = buildFakeSdk(); + setInternalSdk(fake); + const promise = sdk.probe(2000); + fake.__fakeDdp.emitPong(); + await promise; + expect(fake.__fakeDdp.pongHandlers.size).toBe(0); + }); + + it('ignores a late pong after timeout (does not double-resolve)', async () => { + jest.useFakeTimers(); + const fake = buildFakeSdk(); + setInternalSdk(fake); + const promise = sdk.probe(2000); + jest.advanceTimersByTime(2000); + await expect(promise).resolves.toBe(false); + // Late pong arrives. With the listener cleaned up, this is a no-op. + expect(() => fake.__fakeDdp.emitPong()).not.toThrow(); + }); +}); + +describe('Sdk.forceReopen', () => { + it('returns false when sdk.current is undefined', async () => { + setInternalSdk(undefined); + await expect(sdk.forceReopen()).resolves.toBe(false); + }); + + it('calls connection.close then connection.reconnect', async () => { + const fake = buildFakeSdk(); + setInternalSdk(fake); + await sdk.forceReopen(); + expect(fake.__fakeConnection.close).toHaveBeenCalledTimes(1); + expect(fake.__fakeConnection.reconnect).toHaveBeenCalledTimes(1); + // close before reconnect + const closeOrder = fake.__fakeConnection.close.mock.invocationCallOrder[0]; + const reconnectOrder = fake.__fakeConnection.reconnect.mock.invocationCallOrder[0]; + expect(closeOrder).toBeLessThan(reconnectOrder); + }); + + it('resolves true when reconnect resolves truthy', async () => { + const fake = buildFakeSdk(); + setInternalSdk(fake); + await expect(sdk.forceReopen()).resolves.toBe(true); + }); + + it('resolves false when reconnect rejects (does not throw)', async () => { + const fake = buildFakeSdk({ + connection: buildFakeConnection({ reconnect: jest.fn().mockRejectedValue(new Error('conn in progress')) }) + }); + setInternalSdk(fake); + await expect(sdk.forceReopen()).resolves.toBe(false); + }); + + it('coalesces concurrent calls to the same in-flight promise', async () => { + let resolveReconnect: (v: boolean) => void = () => undefined; + const reconnect = jest.fn( + () => + new Promise(res => { + resolveReconnect = res; + }) + ); + const fake = buildFakeSdk({ connection: buildFakeConnection({ reconnect }) }); + setInternalSdk(fake); + + const a = sdk.forceReopen(); + const b = sdk.forceReopen(); + + expect(a).toBe(b); + expect(reconnect).toHaveBeenCalledTimes(1); + expect(fake.__fakeConnection.close).toHaveBeenCalledTimes(1); + + resolveReconnect(true); + await Promise.all([a, b]); + }); + + it('clears the in-flight slot after the reopen settles so the next call starts fresh', async () => { + const fake = buildFakeSdk(); + setInternalSdk(fake); + await sdk.forceReopen(); + await sdk.forceReopen(); + expect(fake.__fakeConnection.reconnect).toHaveBeenCalledTimes(2); + expect(fake.__fakeConnection.close).toHaveBeenCalledTimes(2); + }); + + it('still proceeds when close() throws', async () => { + const fake = buildFakeSdk({ + connection: buildFakeConnection({ + close: jest.fn(() => { + throw new Error('already closed'); + }) + }) + }); + setInternalSdk(fake); + await expect(sdk.forceReopen()).resolves.toBe(true); + expect(fake.__fakeConnection.reconnect).toHaveBeenCalledTimes(1); + }); +}); + +describe('Sdk.subscribeNotifyUser', () => { + const EXPECTED_EVENTS = [ + 'message', + 'notification', + 'rooms-changed', + 'subscriptions-changed', + 'uiInteraction', + 'e2ekeyRequest', + 'userData', + 'video-conference', + 'media-signal', + 'media-calls' + ]; + + const buildFakeSdkWithSubscribe = (user: { id: string } | undefined) => { + const subscribe = jest.fn((_name: string, _key: string) => ({ id: `sub-${_key}`, stop: jest.fn() })); + return { + client: { subscribe }, + account: { user }, + __subscribe: subscribe + }; + }; + + it('returns [] when sdk.current is undefined', () => { + setInternalSdk(undefined); + expect(sdk.subscribeNotifyUser()).toEqual([]); + }); + + it('returns [] when no logged-in user and no userId arg', () => { + setInternalSdk(buildFakeSdkWithSubscribe(undefined)); + expect(sdk.subscribeNotifyUser()).toEqual([]); + }); + + it('subscribes to all 10 user-notify events using the logged-in user id', () => { + const fake = buildFakeSdkWithSubscribe({ id: 'user-42' }); + setInternalSdk(fake); + const handles = sdk.subscribeNotifyUser(); + expect(handles).toHaveLength(EXPECTED_EVENTS.length); + EXPECTED_EVENTS.forEach(event => { + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-user', `user-42/${event}`); + }); + }); + + it('includes the patched media-signal and media-calls events', () => { + const fake = buildFakeSdkWithSubscribe({ id: 'user-42' }); + setInternalSdk(fake); + sdk.subscribeNotifyUser(); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-user', 'user-42/media-signal'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-user', 'user-42/media-calls'); + }); + + it('prefers the explicit userId argument over the logged-in user id', () => { + const fake = buildFakeSdkWithSubscribe({ id: 'logged-in-user' }); + setInternalSdk(fake); + sdk.subscribeNotifyUser('override-user'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-user', 'override-user/message'); + expect(fake.__subscribe).not.toHaveBeenCalledWith('stream-notify-user', 'logged-in-user/message'); + }); +}); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index af2b80be66a..6561237d3a2 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -26,10 +26,22 @@ async function normalizeResponseError(response: Response): Promise<{ status: num } } +/** + * Preserve the old-SDK call shape `sdk.current?.subscribeNotifyUser?.()` by + * declaring the method on `DDPSDK`. The implementation lives on our `Sdk` + * wrapper; `initialize()` glues it onto the DDPSDK instance at runtime. + */ +declare module '@rocket.chat/ddp-client' { + interface DDPSDK { + subscribeNotifyUser?: () => ReturnType; + } +} + class Sdk { private sdk: DDPSDK | undefined; private serverUrl: string | undefined; private code: any = null; + private reopenInFlight: Promise | null = null; private headers: Record = { 'User-Agent': `RC Mobile; ${ Platform.OS @@ -38,6 +50,7 @@ class Sdk { initialize(server: string): DDPSDK { this.sdk = DDPSDK.create(server); + this.sdk.subscribeNotifyUser = () => this.subscribeNotifyUser(); this.serverUrl = server; this.loadBasicAuth(); this.sdk.rest.handleTwoFactorChallenge(this.twoFactorHandler); @@ -68,6 +81,66 @@ class Sdk { return null; } + /** + * Active liveness check: sends a raw DDP `ping` and resolves true if `pong` + * arrives within `timeoutMs`. Bypasses the request pipeline so a zombie + * socket can't make us wait indefinitely. Reaches into the private + * `MinimalDDPClient` because ddp-client doesn't expose `pong` at the + * `ClientStream` layer. + */ + probe(timeoutMs = 2000): Promise { + return new Promise(resolve => { + const ddp = (this.current?.client as unknown as { ddp?: { ping?: (id?: string) => void; on?: (e: 'pong', cb: () => void) => () => void } } | undefined)?.ddp; + if (!ddp?.ping || !ddp?.on) { + resolve(false); + return; + } + let settled = false; + let off: (() => void) | undefined; + const finish = (alive: boolean) => { + if (settled) return; + settled = true; + clearTimeout(timer); + off?.(); + resolve(alive); + }; + const timer = setTimeout(() => finish(false), timeoutMs); + try { + off = ddp.on('pong', () => finish(true)); + ddp.ping(); + } catch { + finish(false); + } + }); + } + + /** + * Tear down the current socket and rebuild from scratch. Concurrent callers + * share one in-flight promise so parallel warm-accepts don't race each other. + * Resolves true once the new socket is connected; false on failure or when + * there is no current SDK. + */ + forceReopen(): Promise { + if (this.reopenInFlight) return this.reopenInFlight; + const current = this.current; + if (!current) return Promise.resolve(false); + try { + current.connection.close(); + } catch { + // ignore — we're rebuilding anyway + } + const p = current.connection + .reconnect() + .then(v => Boolean(v)) + .catch(() => false); + this.reopenInFlight = p; + const clear = () => { + if (this.reopenInFlight === p) this.reopenInFlight = null; + }; + p.then(clear, clear); + return p; + } + private loadBasicAuth(): void { const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${this.serverUrl}`); if (basicAuth) { @@ -368,6 +441,37 @@ class Sdk { return this.current?.client.subscribe(...args); } + /** + * Re-subscribe to the per-user `stream-notify-user` channels needed for VoIP, + * notifications, and presence. Used after `forceReopen()` — once the socket + * is rebuilt, any prior subscriptions are server-side stale and must be + * re-established or the user receives no signals (incoming call answers, + * messages, etc.) until the next full saga re-run. + * + * Ports the old SDK's `subscribeNotifyUser()` helper. Returns an empty array + * when there is no current SDK or no logged-in user. + */ + subscribeNotifyUser(userId?: string) { + const client = this.current?.client; + const uid = userId ?? this.current?.account.user?.id; + if (!client || !uid) { + return []; + } + const events = [ + 'message', + 'notification', + 'rooms-changed', + 'subscriptions-changed', + 'uiInteraction', + 'e2ekeyRequest', + 'userData', + 'video-conference', + 'media-signal', + 'media-calls' + ]; + return events.map(event => client.subscribe('stream-notify-user', `${uid}/${event}`)); + } + get currentLogin() { const user = this.current?.account.user; if (!user) { diff --git a/app/lib/services/voip/MediaSessionInstance.ts b/app/lib/services/voip/MediaSessionInstance.ts index 2db70ef452e..436ddb16850 100644 --- a/app/lib/services/voip/MediaSessionInstance.ts +++ b/app/lib/services/voip/MediaSessionInstance.ts @@ -110,7 +110,7 @@ class MediaSessionInstance { this.instance = mediaSessionStore.getInstance(userId); }); - this.mediaSignalListener = sdk.onStreamData('stream-notify-user', async (ddpMessage: IDDPMessage) => { + this.mediaSignalListener = await sdk.onStreamData('stream-notify-user', async (ddpMessage: IDDPMessage) => { if (!this.instance) { return; } diff --git a/jest.config.js b/jest.config.js index b2b30897d93..b0b20cedc3c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,7 @@ module.exports = { modulePathIgnorePatterns: ['/.*worktrees/'], testPathIgnorePatterns: ['e2e', 'node_modules', '/.*worktrees/'], transformIgnorePatterns: [ - 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|@rocket.chat/ui-kit|@rocket.chat/sdk|tiny-events)' + 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|@rocket.chat/ui-kit|tiny-events)' ], preset: './jest.preset.js', cacheDirectory: '/.jest-cache', From 7d1a82b82dad8b7c9297c320cc99e5a6250ff549 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 20 May 2026 23:18:01 +0530 Subject: [PATCH 060/203] reconnection logic on background --- app/lib/services/sdk.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 6561237d3a2..f9453a281a3 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -50,6 +50,19 @@ class Sdk { initialize(server: string): DDPSDK { this.sdk = DDPSDK.create(server); + + // ddp-client's TimeoutControl (60s passive heartbeat) stops resetting after + // the first background close because it lacks a connection.on('connected') + // listener in its create() setup. This causes a reconnect loop on foreground: + // timeout fires → closes socket → reconnect → timeout fires immediately again. + // Monkey-patch reconnect() to re-arm the timeout on every successful reconnect. + const origReconnect = this.sdk.connection.reconnect.bind(this.sdk.connection); + this.sdk.connection.reconnect = () => + origReconnect().then((r: boolean) => { + this.sdk?.timeoutControl?.reset(); + return r; + }) as Promise; + this.sdk.subscribeNotifyUser = () => this.subscribeNotifyUser(); this.serverUrl = server; this.loadBasicAuth(); From c260ded95fd22f08d6899b469e626bd73d7ab326 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 20 May 2026 23:28:21 +0530 Subject: [PATCH 061/203] lint fix --- app/lib/services/sdk.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index f9453a281a3..c0844bbabe5 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -103,7 +103,13 @@ class Sdk { */ probe(timeoutMs = 2000): Promise { return new Promise(resolve => { - const ddp = (this.current?.client as unknown as { ddp?: { ping?: (id?: string) => void; on?: (e: 'pong', cb: () => void) => () => void } } | undefined)?.ddp; + const ddp = ( + this.current?.client as unknown as + | { + ddp?: { ping?: (id?: string) => void; on?: (e: 'pong', cb: () => void) => () => void }; + } + | undefined + )?.ddp; if (!ddp?.ping || !ddp?.on) { resolve(false); return; @@ -135,7 +141,7 @@ class Sdk { */ forceReopen(): Promise { if (this.reopenInFlight) return this.reopenInFlight; - const current = this.current; + const { current } = this; if (!current) return Promise.resolve(false); try { current.connection.close(); From 720dfa609c021f8e89bd657410974eff80037931 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 26 May 2026 15:56:45 +0530 Subject: [PATCH 062/203] changes --- app/lib/services/restApi.ts | 3 +- app/lib/services/sdk.test.ts | 45 +++++++ app/lib/services/sdk.ts | 75 +---------- patches/@rocket.chat+sdk+1.3.3-mobile.patch | 138 -------------------- 4 files changed, 49 insertions(+), 212 deletions(-) delete mode 100644 patches/@rocket.chat+sdk+1.3.3-mobile.patch diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 3ee858830a6..b5069a60aee 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1,4 +1,3 @@ -import { settings as RocketChatSettings } from '@rocket.chat/sdk'; import { getUniqueId } from 'react-native-device-info'; import type { ServerMediaSignal } from '@rocket.chat/media-signaling'; @@ -1049,7 +1048,7 @@ export const editMediaMessage = async ( const response = await fetch(`${server.server}/api/v1/rooms.mediaConfirm/${rid}/${fileId}`, { method: 'POST', headers: { - ...RocketChatSettings.customHeaders, + ...sdk.getHeaders(), 'Content-Type': 'application/json', 'X-Auth-Token': user.token, 'X-User-Id': user.id diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index dd173aaa27b..c9f58527e7c 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -233,3 +233,48 @@ describe('Sdk.subscribeNotifyUser', () => { expect(fake.__subscribe).not.toHaveBeenCalledWith('stream-notify-user', 'logged-in-user/message'); }); }); + +describe('Sdk.login', () => { + const buildFakeSdkWithLogin = (postResult: any, loginWithToken = jest.fn().mockResolvedValue(undefined)) => { + const post = jest.fn().mockResolvedValue(postResult); + return { + client: { ddp: buildFakeDdp() }, + connection: buildFakeConnection(), + account: { loginWithToken }, + rest: { post, handleTwoFactorChallenge: jest.fn() }, + __post: post, + __loginWithToken: loginWithToken + }; + }; + + it('rejects when sdk is not initialized', async () => { + setInternalSdk(undefined); + await expect(sdk.login({ user: 'test', password: 'test' })).rejects.toThrow(); + }); + + it('calls loginWithToken with the authToken from the REST response', async () => { + const fake = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } + }); + setInternalSdk(fake); + const result = await sdk.login({ user: 'john', password: 'secret' }); + expect(fake.__loginWithToken).toHaveBeenCalledWith('tok-abc'); + expect(result.authToken).toBe('tok-abc'); + }); + + it('rejects when the REST response has success: false', async () => { + const fake = buildFakeSdkWithLogin({ success: false }); + setInternalSdk(fake); + await expect(sdk.login({ user: 'john', password: 'wrong' })).rejects.toThrow('Invalid response from server'); + }); + + it('rejects and normalizes a raw fetch Response error', async () => { + const fake = buildFakeSdkWithLogin(null); + fake.__post.mockRejectedValue(new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })); + setInternalSdk(fake); + const err: any = await sdk.login({ user: 'john', password: 'bad' }).catch(e => e); + expect(err.status).toBe(401); + expect(err.data.error).toBe('Unauthorized'); + }); +}); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index c0844bbabe5..f244019361c 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -66,7 +66,7 @@ class Sdk { this.sdk.subscribeNotifyUser = () => this.subscribeNotifyUser(); this.serverUrl = server; this.loadBasicAuth(); - this.sdk.rest.handleTwoFactorChallenge(this.twoFactorHandler); + this.sdk.rest.handleTwoFactorChallenge(this.twoFactorHandler.bind(this)); return this.sdk; } @@ -150,7 +150,7 @@ class Sdk { } const p = current.connection .reconnect() - .then(v => Boolean(v)) + .then((v: unknown) => Boolean(v)) .catch(() => false); this.reopenInFlight = p; const clear = () => { @@ -192,44 +192,6 @@ class Sdk { } } - /* - get: DDPSDK['rest']['get'] = (...args: Parameters) => { - const [endpoint, params, options] = args; - const sdk = this.ensureInitialized(); - return sdk.rest.get(endpoint, params, { - ...options, - headers: { - ...this.headers, - ...options?.headers - } - }); - }; - - post: DDPSDK['rest']['post'] = (...args: Parameters) => { - const [endpoint, params, options] = args; - const sdk = this.ensureInitialized(); - return sdk.rest.post(endpoint, params, { - ...options, - headers: { - ...this.headers, - ...options?.headers - } - }); - }; - - delete: DDPSDK['rest']['delete'] = (...args: Parameters) => { - const [endpoint, params, options] = args; - const sdk = this.ensureInitialized(); - return sdk.rest.delete(endpoint, params, { - ...options, - headers: { - ...this.headers, - ...options?.headers - } - }); - }; - */ - async get>( endpoint: TPath, params: void extends OperationParams<'GET', MatchPathPattern> @@ -268,7 +230,7 @@ class Sdk { try { const sdk = this.ensureInitialized(); // @ts-ignore - const result = await sdk.rest.post(endpoint, params); + const result = await sdk.rest.post(endpoint, params, { headers: this.headers }); /** * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, @@ -512,37 +474,6 @@ class Sdk { } } - _stream(name: string, data: unknown, cb: (...data: any) => void) { - const [key, args] = Array.isArray(data) ? data : [data]; - - if (!this.current) { - return { stop: () => {} }; - } - - const subscription = this.current.client.subscribe(`stream-${name}`, key, { useCollection: false, args: [args] }); - - const stop = subscription.stop.bind(subscription); - const cancel = [ - () => stop(), - this.current?.client.onCollection(`stream-${name}`, (data: any) => { - if (data.collection !== `stream-${name}`) { - return; - } - if (data.msg === 'added') { - return; - } - if (data.fields.eventName === key) { - cb(data); - } - }) - ]; - - return Object.assign(subscription, { - stop: () => { - cancel.forEach(fn => fn()); - } - }); - } } const sdk = new Sdk(); diff --git a/patches/@rocket.chat+sdk+1.3.3-mobile.patch b/patches/@rocket.chat+sdk+1.3.3-mobile.patch deleted file mode 100644 index a5462330ed5..00000000000 --- a/patches/@rocket.chat+sdk+1.3.3-mobile.patch +++ /dev/null @@ -1,138 +0,0 @@ -diff --git a/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts b/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts -index 19d31ae..3f33982 100644 ---- a/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts -+++ b/node_modules/@rocket.chat/sdk/lib/drivers/ddp.ts -@@ -175,15 +175,115 @@ export class Socket extends EventEmitter { - return Promise.resolve() - } - -- // Call open directly, so it skips openTimeout -- checkAndReopen = () => { -- if (!this.connected) { -- if (this.openTimeout) { -- clearTimeout(this.openTimeout as any) -- delete this.openTimeout -+ /** -+ * Probe an existing connection by sending a raw `ping` and waiting for `pong`. -+ * Bypasses the SDK send() pipeline (which would await `pong` indefinitely on -+ * a zombie socket). Resolves true if a pong arrives within 2s; false on -+ * timeout or synchronous send throw. -+ */ -+ probe = (): Promise => { -+ return new Promise(resolve => { -+ let settled = false -+ const finish = (alive: boolean) => { -+ if (settled) return -+ settled = true -+ this.off('pong', onPong) -+ clearTimeout(timer) -+ resolve(alive) -+ } -+ const onPong = () => finish(true) -+ const timer = setTimeout(() => finish(false), 2000) -+ this.once('pong', onPong) -+ try { -+ if (!this.connection) return finish(false) -+ this.connection.send(JSON.stringify({ msg: 'ping' })) -+ } catch { -+ finish(false) -+ } -+ }) -+ } -+ -+ _reopenInFlight: Promise | null = null -+ -+ /** -+ * Tear down the existing connection and start a fresh one. Used when the -+ * socket is known or suspected dead. Emits `disconnected` so any in-flight -+ * send() promises (which register `once('disconnected', reject)`) reject -+ * visibly instead of hanging forever, and emits `close` synchronously so -+ * app-level `close` listeners (e.g. Redux `disconnectAction` dispatch in -+ * `connect.ts`) flip to disconnected before the new socket opens; otherwise -+ * the next `'connected'` event would short-circuit on stale Redux state and -+ * the loginRequest → subscribeNotifyUser chain would never re-run, leaving -+ * the fresh socket without `${userId}/media-signal` or any other sub. -+ * Concurrent invocations join the same in-flight reconnect promise so the -+ * parallel callers don't tear each other down. Resolves only after the new -+ * socket has reached `OPEN` and the DDP `connect` handshake has been acked. -+ */ -+ forceReopen = (): Promise => { -+ if (this._reopenInFlight) return this._reopenInFlight -+ this.lastPing = 0 -+ if (this.openTimeout) { -+ clearTimeout(this.openTimeout as any) -+ delete this.openTimeout -+ } -+ if (this.pingTimeout) { -+ clearTimeout(this.pingTimeout as any) -+ delete this.pingTimeout -+ } -+ this.emit('disconnected') -+ this.emit('close', { code: userDisconnectCloseCode }) -+ this.subscriptions = {} -+ if (this.connection) { -+ try { -+ this.connection.onopen = null as any -+ this.connection.onmessage = null as any -+ this.connection.onerror = null as any -+ this.connection.onclose = null as any -+ this.connection.close(userDisconnectCloseCode) -+ } catch (e) { -+ this.logger.debug(`[ddp] forceReopen close error: ${(e as Error).message}`) - } -- this.open() -+ this.connection = undefined -+ } -+ // Mark socket fresh so the next checkAndReopen doesn't see lastPing=0 -+ // and falsely classify the brand-new connection as stale. -+ const p = Promise.resolve(this.open()).then(() => { this.lastPing = Date.now() }) -+ this._reopenInFlight = p -+ const clear = () => { if (this._reopenInFlight === p) this._reopenInFlight = null } -+ p.then(clear, clear) -+ return p -+ } -+ -+ /** -+ * Decide whether the socket needs reopening when the app foregrounds. -+ * Three buckets keyed on time since last incoming message (`lastPing`): -+ * - stale (> ping*2): force reopen, no probe (already past alive()) -+ * - fresh (< 2000ms): no-op (recent traffic confirms liveness) -+ * - gray (in between): probe; if no pong in 2s, force reopen -+ */ -+ checkAndReopen = async (): Promise => { -+ const elapsed = Date.now() - this.lastPing -+ if (!this.connection || this.connection.readyState !== 1) { -+ this.logger.info(`[ddp] checkAndReopen bucket=stale elapsed=${elapsed}`) -+ await this.forceReopen() -+ return -+ } -+ if (elapsed > this.config.ping * 2) { -+ this.logger.info(`[ddp] checkAndReopen bucket=stale elapsed=${elapsed}`) -+ await this.forceReopen() -+ return -+ } -+ if (elapsed < 2000) { -+ this.logger.info(`[ddp] checkAndReopen bucket=fresh elapsed=${elapsed}`) -+ return -+ } -+ const alive = await this.probe() -+ if (alive) { -+ this.logger.info(`[ddp] checkAndReopen bucket=probe-pass elapsed=${elapsed}`) -+ return - } -+ this.logger.info(`[ddp] checkAndReopen bucket=probe-fail elapsed=${elapsed}`) -+ await this.forceReopen() - } - - /** Clear connection and try to connect again. */ -@@ -549,7 +649,9 @@ export class DDPDriver extends EventEmitter implements ISocket, IDriver { - 'uiInteraction', - 'e2ekeyRequest', - 'userData', -- 'video-conference' -+ 'video-conference', -+ 'media-signal', -+ 'media-calls' - ].map(event => this.subscribe(topic, `${this.userId}/${event}`, false))) - } - From 1340541040fa267554677cba131d305d325e4e77 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Tue, 26 May 2026 10:29:23 +0000 Subject: [PATCH 063/203] chore: format code and fix lint issues --- app/lib/services/sdk.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index f244019361c..871b4b0c60a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -473,7 +473,6 @@ class Sdk { await Promise.race([this.current.account.logout(), new Promise(resolve => setTimeout(resolve, 5000))]); } } - } const sdk = new Sdk(); From 22c31452dc3ab90b6f9e7101039224d3b03c3d01 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 26 May 2026 16:35:11 +0530 Subject: [PATCH 064/203] lint fix --- app/lib/services/sdk.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index f244019361c..871b4b0c60a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -473,7 +473,6 @@ class Sdk { await Promise.race([this.current.account.logout(), new Promise(resolve => setTimeout(resolve, 5000))]); } } - } const sdk = new Sdk(); From a323f62891a49717598787e5dfeca1f980eb80e2 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 26 May 2026 16:58:15 +0530 Subject: [PATCH 065/203] rerun ci --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 871b4b0c60a..0029e18fad1 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -3,7 +3,7 @@ import isEmpty from 'lodash/isEmpty'; import { type ClientStream, DDPSDK } from '@rocket.chat/ddp-client'; import { Platform } from 'react-native'; import DeviceInfo from 'react-native-device-info'; - + import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; import { compareServerVersion, random } from '../methods/helpers'; From b362cf7173384a5af97f82c17018403da8176b2c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 26 May 2026 16:58:20 +0530 Subject: [PATCH 066/203] revert --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 0029e18fad1..871b4b0c60a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -3,7 +3,7 @@ import isEmpty from 'lodash/isEmpty'; import { type ClientStream, DDPSDK } from '@rocket.chat/ddp-client'; import { Platform } from 'react-native'; import DeviceInfo from 'react-native-device-info'; - + import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; import { compareServerVersion, random } from '../methods/helpers'; From fbdd9015e525384b2712e068eb9a9eb065bb08b7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 26 May 2026 17:10:43 +0530 Subject: [PATCH 067/203] rerun --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 871b4b0c60a..0ff1310115b 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -16,7 +16,7 @@ import { type PathFor, type ResultFor } from '../../definitions/rest/helpers'; - + async function normalizeResponseError(response: Response): Promise<{ status: number; data: any }> { try { const data = await response.clone().json(); From 6854fd5363a21cc0116cf04b02822be328ba8303 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 26 May 2026 17:10:48 +0530 Subject: [PATCH 068/203] revert --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 0ff1310115b..871b4b0c60a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -16,7 +16,7 @@ import { type PathFor, type ResultFor } from '../../definitions/rest/helpers'; - + async function normalizeResponseError(response: Response): Promise<{ status: number; data: any }> { try { const data = await response.clone().json(); From 3c91c2a30776e3ac8ba502d315f9d9a55f397351 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 28 May 2026 02:23:31 +0530 Subject: [PATCH 069/203] coderabbit changes --- app/definitions/rest/v1/e2e.ts | 2 +- app/lib/encryption/encryption.ts | 8 ++++++-- app/lib/methods/logout.ts | 5 ++++- app/views/AddExistingChannelView/index.tsx | 4 +++- app/views/UserNotificationPreferencesView/index.tsx | 4 ++-- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/definitions/rest/v1/e2e.ts b/app/definitions/rest/v1/e2e.ts index c4b8878ff22..0c68dbc0fd8 100644 --- a/app/definitions/rest/v1/e2e.ts +++ b/app/definitions/rest/v1/e2e.ts @@ -20,7 +20,7 @@ export type E2eEndpoints = { }; 'e2e.fetchUsersWaitingForGroupKey': { GET: (params: { roomIds: string[] }) => { - usersWaitingForE2EKeys: any; + usersWaitingForE2EKeys: Record; }; }; 'e2e.provideUsersSuggestedGroupKeys': { diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index ffe13c69655..a7cf6fd5727 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -490,11 +490,15 @@ class Encryption { } const result = await fetchUsersWaitingForGroupKey(sampleIds); - if (!result.success || !Object.keys(result.usersWaitingForE2EKeys).length) { + if (!result?.success) { + return; + } + const { usersWaitingForE2EKeys } = result; + if (!usersWaitingForE2EKeys || !Object.keys(usersWaitingForE2EKeys).length) { return; } - const userKeysWithRooms = await this.getSuggestedE2EEKeys(result.usersWaitingForE2EKeys); + const userKeysWithRooms = await this.getSuggestedE2EEKeys(usersWaitingForE2EKeys); if (!Object.keys(userKeysWithRooms).length) { return; diff --git a/app/lib/methods/logout.ts b/app/lib/methods/logout.ts index 6f64c583b31..612eb3587e6 100644 --- a/app/lib/methods/logout.ts +++ b/app/lib/methods/logout.ts @@ -75,7 +75,10 @@ export async function removeServer({ server }: { server: string }): Promise { } } catch (e: any) { logEvent(events.CT_ADD_ROOM_TO_TEAM_F); - showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {}); + const errorKey = e?.data?.error ?? 'error-unknown'; + showErrorAlert(I18n.t(errorKey), I18n.t('Add_Existing_Channel'), () => {}); + log(e); } finally { sendLoadingEvent({ visible: false }); } diff --git a/app/views/UserNotificationPreferencesView/index.tsx b/app/views/UserNotificationPreferencesView/index.tsx index 2b153f02c18..c54d948ef21 100644 --- a/app/views/UserNotificationPreferencesView/index.tsx +++ b/app/views/UserNotificationPreferencesView/index.tsx @@ -45,12 +45,12 @@ const UserNotificationPreferencesView = () => { try { const result = await getUserPreferences(); if (result.success) { - setLoading(false); setPreferences(result.preferences); } } catch (error) { - setLoading(false); log(error); + } finally { + setLoading(false); } } getPreferences(); From b13b35613450bee952a00dbb4a5d9e00192accdf Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 28 May 2026 02:52:16 +0530 Subject: [PATCH 070/203] code improvements --- app/ee/omnichannel/lib/index.ts | 2 +- .../omnichannel/lib/subscriptions/inquiry.ts | 3 +++ app/lib/methods/canOpenRoom.ts | 2 +- app/lib/methods/subscriptions/rooms.ts | 15 ++--------- app/lib/services/sdk.ts | 27 ++++++++++--------- 5 files changed, 21 insertions(+), 28 deletions(-) diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 8d7289bb3ef..37d2bd3a4e3 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -27,7 +27,7 @@ export const getInquiriesQueued = (serverVersion: string) => { // this inquiry is added to the db by the subscriptions stream // and will be removed by the queue stream // RC 2.4.0 -// @ts-ignore +// @ts-ignore — legacy servers use sdk.methodCallWrapper() which lacks proper type definitions export const takeInquiry = (inquiryId: string, serverVersion: string) => { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.11.0')) { return sdk.post('/v1/livechat/inquiries.take', { inquiryId }); diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index 815136d4fff..7c1b848fc2e 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -25,6 +25,7 @@ const removeListener = (listener: any) => listener.stop(); let connectedListener: any; let queueListener: any; let departmentListeners: any[] = []; +let stopped = false; const streamTopic = 'stream-livechat-inquiry-queue-observer'; @@ -63,6 +64,7 @@ export default function subscribeInquiry() { }; const stop = () => { + stopped = true; if (connectedListener) { connectedListener.then(removeListener); connectedListener = false; @@ -93,6 +95,7 @@ export default function subscribeInquiry() { getAgentDepartments(user.id) .then(result => { + if (stopped) return; if (result.success) { const { departments } = result; diff --git a/app/lib/methods/canOpenRoom.ts b/app/lib/methods/canOpenRoom.ts index d6d72a4ab50..1a0fce399aa 100644 --- a/app/lib/methods/canOpenRoom.ts +++ b/app/lib/methods/canOpenRoom.ts @@ -42,7 +42,7 @@ async function open({ type, rid, name }: { type: ERoomTypes; rid: string; name: // we'll get info from the room if ((type === ERoomTypes.CHANNEL || type === ERoomTypes.GROUP) && !rid) { // RC 0.72.0 - // @ts-ignore + // @ts-ignore — dynamic path construction using restTypes[type] cannot be statically verified const result: any = await sdk.get(`/v1/${restTypes[type]}.info`, params); if (result.success) { const room = result[type]; diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 7e6a8d5e05f..ec1c9dcfeb1 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -26,7 +26,7 @@ import { type TThreadModel, SubscriptionType } from '../../../definitions'; -import sdk from '../../services/sdk'; +import sdk, { NOTIFY_USER_EVENTS } from '../../services/sdk'; import { type IDDPMessage } from '../../../definitions/IDDPMessage'; import { getSubscriptionByRoomId } from '../../database/services/Subscription'; import { getMessageById } from '../../database/services/Message'; @@ -454,18 +454,7 @@ export default function subscribeRooms() { if (!userId) { throw new Error('subscribeRooms: missing userId, cannot create stream-notify-user subscriptions'); } - [ - 'message', - 'notification', - 'rooms-changed', - 'subscriptions-changed', - 'uiInteraction', - 'e2ekeyRequest', - 'userData', - 'video-conference', - 'media-signal', - 'media-calls' - ].forEach(event => { + NOTIFY_USER_EVENTS.forEach(event => { const handle = sdk.subscribeRaw('stream-notify-user', `${userId}/${event}`); if (handle) { rawSubscriptionHandles.push(handle); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 871b4b0c60a..3dc9f9b5f85 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -26,6 +26,19 @@ async function normalizeResponseError(response: Response): Promise<{ status: num } } +export const NOTIFY_USER_EVENTS = [ + 'message', + 'notification', + 'rooms-changed', + 'subscriptions-changed', + 'uiInteraction', + 'e2ekeyRequest', + 'userData', + 'video-conference', + 'media-signal', + 'media-calls' +] as const; + /** * Preserve the old-SDK call shape `sdk.current?.subscribeNotifyUser?.()` by * declaring the method on `DDPSDK`. The implementation lives on our `Sdk` @@ -438,19 +451,7 @@ class Sdk { if (!client || !uid) { return []; } - const events = [ - 'message', - 'notification', - 'rooms-changed', - 'subscriptions-changed', - 'uiInteraction', - 'e2ekeyRequest', - 'userData', - 'video-conference', - 'media-signal', - 'media-calls' - ]; - return events.map(event => client.subscribe('stream-notify-user', `${uid}/${event}`)); + return NOTIFY_USER_EVENTS.map(event => client.subscribe('stream-notify-user', `${uid}/${event}`)); } get currentLogin() { From 34ac089def83310480e30020aa476a65213977f8 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 28 May 2026 03:00:36 +0530 Subject: [PATCH 071/203] more improve --- app/ee/omnichannel/lib/index.ts | 2 +- .../omnichannel/lib/subscriptions/inquiry.ts | 2 ++ app/lib/services/sdk.test.ts | 19 +++---------------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 37d2bd3a4e3..be40def8126 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -27,7 +27,7 @@ export const getInquiriesQueued = (serverVersion: string) => { // this inquiry is added to the db by the subscriptions stream // and will be removed by the queue stream // RC 2.4.0 -// @ts-ignore — legacy servers use sdk.methodCallWrapper() which lacks proper type definitions +// @ts-ignore — endpoint '/v1/livechat/inquiries.take' not in SDK's typed path dictionary for older servers export const takeInquiry = (inquiryId: string, serverVersion: string) => { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.11.0')) { return sdk.post('/v1/livechat/inquiries.take', { inquiryId }); diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index 7c1b848fc2e..19cf958040c 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -30,6 +30,8 @@ let stopped = false; const streamTopic = 'stream-livechat-inquiry-queue-observer'; export default function subscribeInquiry() { + stopped = false; + const handleConnection = () => { store.dispatch(inquiryRequest()); }; diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index c9f58527e7c..1517b62e171 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -1,4 +1,4 @@ -import sdk from './sdk'; +import sdk, { NOTIFY_USER_EVENTS } from './sdk'; const setInternalSdk = (value: any) => { (sdk as unknown as { sdk: any }).sdk = value; @@ -175,19 +175,6 @@ describe('Sdk.forceReopen', () => { }); describe('Sdk.subscribeNotifyUser', () => { - const EXPECTED_EVENTS = [ - 'message', - 'notification', - 'rooms-changed', - 'subscriptions-changed', - 'uiInteraction', - 'e2ekeyRequest', - 'userData', - 'video-conference', - 'media-signal', - 'media-calls' - ]; - const buildFakeSdkWithSubscribe = (user: { id: string } | undefined) => { const subscribe = jest.fn((_name: string, _key: string) => ({ id: `sub-${_key}`, stop: jest.fn() })); return { @@ -211,8 +198,8 @@ describe('Sdk.subscribeNotifyUser', () => { const fake = buildFakeSdkWithSubscribe({ id: 'user-42' }); setInternalSdk(fake); const handles = sdk.subscribeNotifyUser(); - expect(handles).toHaveLength(EXPECTED_EVENTS.length); - EXPECTED_EVENTS.forEach(event => { + expect(handles).toHaveLength(NOTIFY_USER_EVENTS.length); + NOTIFY_USER_EVENTS.forEach(event => { expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-user', `user-42/${event}`); }); }); From de84c6f95b4d6b0b49e64f398bd8ed4e750bdd71 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 28 May 2026 23:07:57 +0530 Subject: [PATCH 072/203] changes --- app/lib/methods/subscriptions/rooms.ts | 14 ++---------- app/lib/services/connect.ts | 15 +++++-------- app/lib/services/restApi.ts | 10 +++++++-- app/lib/services/sdk.ts | 21 +++++------------- app/sagas/createChannel.js | 13 ----------- .../rocket.chat-media-signaling-0.2.0.tgz | Bin 74929 -> 0 bytes 6 files changed, 22 insertions(+), 51 deletions(-) delete mode 100644 packages/rocket.chat-media-signaling-0.2.0.tgz diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index ec1c9dcfeb1..750c807de45 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -26,7 +26,7 @@ import { type TThreadModel, SubscriptionType } from '../../../definitions'; -import sdk, { NOTIFY_USER_EVENTS } from '../../services/sdk'; +import sdk from '../../services/sdk'; import { type IDDPMessage } from '../../../definitions/IDDPMessage'; import { getSubscriptionByRoomId } from '../../database/services/Subscription'; import { getMessageById } from '../../database/services/Message'; @@ -449,17 +449,7 @@ export default function subscribeRooms() { try { // set the server that started this task subServer = sdk.current?.connection.url || ''; - const { user } = store.getState().login; - const userId = user?.id; - if (!userId) { - throw new Error('subscribeRooms: missing userId, cannot create stream-notify-user subscriptions'); - } - NOTIFY_USER_EVENTS.forEach(event => { - const handle = sdk.subscribeRaw('stream-notify-user', `${userId}/${event}`); - if (handle) { - rawSubscriptionHandles.push(handle); - } - }); + rawSubscriptionHandles = sdk.subscribeNotifyUser(); roomsSubscription = { stop: () => stop() }; return null; } catch (e) { diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index ee3fb85836c..7a7e5cce1a7 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -24,7 +24,7 @@ import { compareServerVersion, isIOS } from '../methods/helpers'; import { onRolesChanged } from '../methods/getRoles'; import { getSettings } from '../methods/getSettings'; import { setPresenceCap } from '../methods/getUsersPresence'; -import { _setUser, _activeUsers, _setUserTimer } from '../methods/setUser'; +import { _setUser, _activeUsers, _setUserTimer, type IActiveUsers } from '../methods/setUser'; import { unsubscribeRooms } from '../methods/subscribeRooms'; interface IServices { @@ -166,8 +166,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo store.dispatch(setActiveUsers(activeUsersBatch)); }); _setUserTimer.setUserTimer = null; - // @ts-ignore - fix me - _activeUsers.activeUsers = {}; + _activeUsers.activeUsers = {} as IActiveUsers; return null; }, 10000); } @@ -244,7 +243,6 @@ function stopListener(listener: any): boolean { } async function login(credentials: ICredentials): Promise { - // TODO: other login methods: ldap, saml, cas, apple, oauth, oauth_custom const result = await sdk.login(credentials); const { me } = result; const serverVersion = store.getState().server.version; @@ -262,7 +260,6 @@ async function login(credentials: ICredentials): Promise export const e2eSetRoomKeyID = (rid: string, keyID: string) => // RC 0.70.0 - sdk.methodCallWrapper('e2e.setRoomKeyID', rid, keyID); + sdk.post('/v1/e2e.setRoomKeyID', { rid, keyID }); export const e2eUpdateGroupKey = (uid: string, rid: string, key: string): any => // RC 0.70.0 @@ -248,10 +248,16 @@ export const teamListRoomsOfUser = ({ teamId, userId }: { teamId: string; userId sdk.get('/v1/teams.listRoomsOfUser', { teamId, userId }); export const convertChannelToTeam = ({ rid, name, type }: { rid: string; name: string; type: 'c' | 'p' }) => { + const serverVersion = reduxStore.getState().server.version; let params; if (type === 'c') { // https://github.com/RocketChat/Rocket.Chat/pull/25279 - params = { channelId: rid }; + params = compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.8.0') + ? { channelId: rid } + : { + channelId: rid, + channelName: name + }; } else { params = { roomId: rid, diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 3dc9f9b5f85..1b729dcc9a2 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -1,14 +1,12 @@ import EJSON from 'ejson'; import isEmpty from 'lodash/isEmpty'; import { type ClientStream, DDPSDK } from '@rocket.chat/ddp-client'; -import { Platform } from 'react-native'; -import DeviceInfo from 'react-native-device-info'; import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; import { compareServerVersion, random } from '../methods/helpers'; import UserPreferences from '../methods/userPreferences'; -import { BASIC_AUTH_KEY } from '../methods/helpers/fetch'; +import { BASIC_AUTH_KEY, headers as defaultHeaders } from '../methods/helpers/fetch'; import { type Serialized, type MatchPathPattern, @@ -55,11 +53,7 @@ class Sdk { private serverUrl: string | undefined; private code: any = null; private reopenInFlight: Promise | null = null; - private headers: Record = { - 'User-Agent': `RC Mobile; ${ - Platform.OS - } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` - }; + private headers: Record = { ...defaultHeaders } as Record; initialize(server: string): DDPSDK { this.sdk = DDPSDK.create(server); @@ -305,7 +299,6 @@ class Sdk { async twoFactorHandler({ method, - // emailOrUsername, TODO: what is this for? invalidAttempt }: { method: 'totp' | 'email' | 'password'; @@ -362,12 +355,11 @@ class Sdk { const { user } = reduxStore.getState().login; if (API_Use_REST_For_DDP_Calls) { const url = isEmpty(user) ? 'method.callAnon' : 'method.call'; - // TODO: fix this type - // @ts-ignore - const result = (await this.post(`/v1/${url}/${method}` as any, { + const endpoint = `/v1/${url}/${method}` as PathFor<'POST'>; + const result = (await this.post(endpoint, { message: EJSON.stringify({ msg: 'method', id: random(10), method, params }) - })) as any; - const response = JSON.parse(result.message) as any; + } as any)) as unknown as { message: string }; + const response = JSON.parse(result.message); if (response?.error) { throw response.error; } @@ -379,7 +371,6 @@ class Sdk { } return param; }); - // @ts-ignore return this.methodCall(method, ...parsedParams); } diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index 8e3a6151bb8..2916653cfb4 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -64,19 +64,6 @@ const handleRequest = function* handleRequest({ data }) { await subCollection.create(s => { s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); Object.assign(s, sub); - s.id = sub.rid; - s._id = sub.rid; - s.ts = sub.ts || new Date(); - s.ls = sub.ls || new Date(); - s.lr = sub.lr || new Date(); - s.lm = sub.lm || new Date(); - s.roomUpdatedAt = sub.roomUpdatedAt || new Date(); - s.open = sub.open ?? true; - s.alert = sub.alert ?? false; - s.unread = sub.unread ?? 0; - s.userMentions = sub.userMentions ?? 0; - s.groupMentions = sub.groupMentions ?? 0; - s.ro = sub.ro ?? false; }); }); diff --git a/packages/rocket.chat-media-signaling-0.2.0.tgz b/packages/rocket.chat-media-signaling-0.2.0.tgz deleted file mode 100644 index 4fa5387e0da5c440af6d5f2b18aa1b29e01026bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74929 zcmY(KQ*Xxj(ZI;j^M!uIheoj$Mne9EL4z+#*hD9P!QEY5lDo}-r^hCO z{_C%xKQvg$ay@o^ZC!1vd_#h!s(@|-SbY1stoY<#*+n)Q78;t`MuSJr^jkx$qant_ z1FDEfD%ns^R>?pP!r1+A${|LHd-z-g;3%lMKW}Pk%3MB|@%-iArurmnW_5>v-?zD~ z>FILq811>`-!C2og2-P7iEDnA>VceI-;`vTxGh9Ct`+5xAZ}mO1^WKp# zI*>vK0LGqNGh&~zj9H>%hbu_6FH*5Q=}?VJXMp29ki_mSKwMPe)~7X3C)EYWOciKa z6X*r(X?weD8v?bou>U5keFNEkCj9t_LYfM9?!oD=25naILw$|RogpK7iE`TieBbeE z_atZFuCx95}

rS3^2 z#CY%3%d^3GbfJ3#2j%^g6S05Un>2|Kx*{{Qu(P@SB31m zV(o9G`ZYZ~-_XAdnysE}YK3A18MB7c(Bb+VeLoj3U>%;lI)pmUuL6-(_*4)}6~S98 z2*^MgB}FrxSkcpgMx#R`vCBZZykb#wFyw6rNyfQ$#L#Q;F6-o;6G<#`H1xo~cAPWQ zMJO|2oKoUmdD-TQ$l{;@qz2lhvP?|HY^14~oGs}6au=W1DSPkIYXVhcO^RWz}kO-BZA(Zx59#w^ml+O8}2<_yq+%?cQZGBz907xD-+KO zDz)21j`1gxUNusIdACB{v>jYX0>fCBsi4&867O#e>L#(+d8Lqm0A26Py=?+Mbbt3; zh}W41R=72a4_IIq>HFXrRk$c-#MTi`1MBfH++lPe%7A(YG)Z0$ykGMNOkF5YuOVOy zDfd_3pWBxt1piRMbXIRn&G$4{stB|UErC}$Vm0gZ4BPmV|Z0DIU0gr&`3=Pf|gZwUz&Z~ z1B^v4`wjvs&A5Tk0Ju*pMZpiVt;0kUKz~V+GeVqoFK~m&T#jmb)@g8-UZM%ZJ0C4u z#B)L{jm9^-%t7WT8SrtTew@p>v9V!{zgGO+7Q%A32+tlNjK1ulU#Ks#kp9(DEqdGh z>|ZM6NtzQv2%-qGioysETE27xga8voR58+4q@|zvvrMw9nmA!_6yZ}m-9e@URfs#H z?7*llf~1dB+tY3PW7p_btAns~#2U?b%+~=V;%qJ=D?JFbSd(vuj$`nt{YMqxk1o0T zO49LI#bg;@bKx`b3hlhD6^keCCp;I=Fg5(K4VF{Bju?l84&M2A4y$~5(|WDymD@eX zLO;YrU8duTe$7M~a6Rbas<9xoPok;V{VFC`gd#v-*pn)OZeD?wVWn&X889LX8zjlg ziI*cjJ^g%mfN+T|xmJN2$+Lj)Av6(XjWiQfIwTO_MBt5oe=Ua+Av`O{R-N*>U>fcl zSP4C2f#h^opGGfnLS>w3JV6D}=&i#*5gSuHoc-bpm!`*$js*W>4w*Dvq#5>Q;uj;- zJiwKs3s+G?(xHP}9w?^jF3G3lRpsY^TlAN5V{a$ou8>4cb}@5+DsQOoXv1I}T+C@h zzo%iE=PkP!6SlypB~CWY_Gy9D^_)%P2+#jpv}btgk#N{#xPN5wI=akY&m@IJUIRFA z>s@D^P6Yl<-a6B(7q_sCOZ@A1W80963G{`!b!f8%NwLtH#OHub-FzPxMALwU(83Q& zE;`aK(+9-_GhLo@N&Q?~!ra|48$CSpzEc)$|1pPxB7D6R8jDDnhSz%uK99lnSPwzrjbJBT=S8F5 zUqw*i8|JjE7`MEVXeZV4SkDpM`ec*~{pUFn!>An!CGa=J^?Ngq>d5c~$o-REPRHs8y5A12RU=Ouog! zBw9uS3IkU>FnHtRJ_iy9>WBi&Uo5%gh02w~BU#*V4(BAwOEr?C7i=QY8aVfBB zCyQ8%&a2#qqR2ySU%(wzmLeR3{;2Fb{x_KIc3Q}-evYY0*F%{)!SeN9Kc#YFSMd7? zHOC3}2L9WR8Xaz8;w$`{EfQVe(aa>}BP7DZWY zo9$L8IZsSwVqmHNyk@*iYq75Dxd3V-tyJS5u%O;NMLm%*5s!>y6+uFtEIcIFhxa5>%9y{B!rUKY9Om>NoW10Xl<^#s>WMn?|`mIo?2 zl+9hdeB&`sRO$@~k1L1Sk>Yk$VWp-|Dhce7^A0_EU&x&Slk9Dh67i?}Zei+`-sQ6i z;Zv00Q@9l(n2?b(lw2WozX9C zlCPrT)PwHU_R@aSjnYg3&S-vAJG{C~DtQ6%n1gq+3X*v_+0ScZe`e(RRXyLjTCr*B zT;K11Q0>?2r4;V<`uT77yIS#6@J_MzY1J$-b|k20+h|~mTMBBiqk%+tspr?5P37g7mjskJJ$Nr6=>NLcmT+f86f~oe z6r=~H^~1_6T;*n;k6_2qF>^d6^quA3M~QNWu!lawPF=nWO00b_KHA!HpdSmFSPL4Z z6Mxun&A_{jOMXxh2Js8U>wJ@!o+Z*UL78Po8Y^$7EMl)RI1K88S$F3Ss{>JkZ(gz4rpNoqV(Z;4 zNF~qki#L83K!}65@zu4Ke?)pVKZ&7t8Bc@o4-WEq z$6=PK4~EI-Z@+iJg$Q@v#|9$6k`Lkq<#$Q^oXvJ%=%`x$RDJY0@&~z%oLn5H<)5cj z#yAgSJP{_2&Ba)jz_1sp@T*qDn{0-Vb&Ck}O*Slqz1|XZ+OE{g5}YeF_hQR5|tgw9I|bV~P~X6CgbK@|?yB zf_Ku5E`o4|1tY?*M7!FXOJVM29W?GAmEBF-^q;5{`?KswKi*TmvP+qe5|yN`md4A2 zVar`wwXahNze5eB5oEN5JX?7pV8~_kfT^5KNnCyaJr=0G3*uW2Duf|?Q?Tg z#Xq^84P*OO?yY=?MsKIg0x`;BKu3ka)%OZDO9}q=^1NW=8wb`Di$|0dMehKGV%vAE zE~*ThfAUQlGGb%SiQSg)xtVgY{K&`O@E5^=Ny%B1lFW zq@MQ4Q(9!LS9(LX(R->EHzH3&#S8_Z8{&gEd0=!RxOE|q)br;U1(f$7bdU|(^9Shq z@LYhqpn%(P{x~r)x=Cn=$%+Gp_6UQRxKUf0jwyBBDA3Wksy7gF_5#kFdO5ei{#vhH zn08g67e?y(K?eRlyXrn51=CG`BotG7vX$DrnvE%*%8y=E`@SN(?q!GcaK)SXT6>i` z44BfaT0DviJWSio=AI>m?Zq;V$2^Pna>-)3LQ#W4n#1Z!Rye4c+{#*enIpA^Edg#n zXB0hCRl&xbs2i(T0Q_M3-5seAlfX(!x7=5@)%xUf&MUGrKmu%Vde`iYHHxWQWzOV|ApQ1g0Vy+v z6Fh(3yea7utIlRt!~ICaByi}AkMg_w-W^%Cmm_EnbAE4Rt!ep3295{x+i&}h*u_?G zwk^izL269McYj+6U*`j*-oVy8LIDkt7SbPobB4aZ+)zyqdf<oJMu)c{E$_j<>CRrgJF)Iad)Ad^TV;YG&r_KT}=}P3P_3~oP>-Y%?Ooi$>wKOl(a@<{h7@9k_N0R4|Wmu}&t=zU-2kdM1LboHV>;OSB{g_W{2d1x0HUvRKoV%S$Nm zt-3bIJ0nAsmhLH2sr+kr?I@%NE+&CC8ebFmTxuwXV=2$Mn|FA0e(gtVo)5FfXI7hj zzRyHftPAmf=n~W{#t)U=dx=c4y8r=?b;Z+B9ZBMEa|O8>C(&1+%~_HK*#2_rYid%-~Z z9gs`|o}xPJX`N;H_(PkUGjpiw7eY?mJKti=37jKt^hvd=i~57q9mN)tJMB}nEB59O zR95esyx~udrGb?$V@<>BVUBt7_JFmOww`ws*)uei>xd{KkSH0YwuGNMJdZ-vdK+Ss zh9lYw9iG6FYnwyjtgNS`OJ&U%!KWKZ?Jrv~>GHXVEX&Jz7 zlI7j`=;iAiqt!U;8MrY^=(%B*nap_8g+0>0k;inEsT^5Wau>rs;^i#mRbaXO(Q<7yjGO#Fn`UA*MVjo{-C zOZ?SZNPV{D0gc4w?VQ8+@pYrWDax?A&A+O@sSI({3gpAlHAlbt=Y_fqG`qxn zlwI7{KoW1z~%{tl$#7|dNP zvAmkm>2zQgyyZ*ur;XU_xegd{T{k-9?m1;kV82F3D2IVwMy6sw#(;yPp08EVEV$(E^IOW?46unxuFS#eY<@G?d z+?dxB{^oX%U7Tv&861q9K3sr$K01rW(pHLN(URiP96Ubn^DlviYM>6sTd9ZsCo%2Hsk7HTFE#E9y{8h*ZlP#7q=~C# zus{_~pxO)p4Q|!P@tpK5&Y~fFHbo;N*X5Pw!{&FjJ%4G&AI>?6<`!8@t75n#sG~FS z;mJSxmKm?V+1MM?3^0bNcesUjolGG0Hgoo zGf?c1P|Py%7R3it00h>g!;=?p&~vTdJzhk)EmabK{YaL}uA0)G=x=i4B3(J_8;3U7 zCaP3-FaMdYmC4PsePq@<)t^A8b!2g+K9(E)_pzn4kx^~a@i361%Yz}+Pm{da`S*Yy zwKCOBdb!~h_)OuL<^;+VdL#EF3rXqbpU^^z*Y}jwaQ^BRru`yu5lO}_$Rk8$L@4UI z`)ut$y`7v~7>Y6kNL7fF@|&0y%}v6EQ`{(6=Sjc563N^go%o-m_2 zhzj*JD*Cc5FSj(5?yZNd>qY+KrA9tvClkLJoL88PBrKyH_^NINfvE1 zU2}+H^HL_(ty~$bmBcoL)WZHDM?i~~ELB*fO+S&8(XIAmyRAwbSLB?FAJS-NAsN)- zrmkw8A5>_yNKWGk7R)jNyy0s{!NHQElxS=KT4SU62YMTzQ7$~-{+#t`F#8I~_(l6D znFZgYpA@s4+g652Hd}O7rPI4(r^jf-&3={2Ku`r%qthFSo{^PWstuKJo=_S>dsA(6 zYI;TRb1%uLT0^seo4Q5=O`6=K4>@;wv!_6mW}U?N#Y)T;Ew3k^OoXN2qWUz$PK$N1 zMPvoK(F*I@T+<=KPDJ-NJeZdPIGa*^otN+5BO}IS2X{ zs#4@6ck9sAfa`cE+bOA#Hk+)03stiXFH!cC?Y)hz%5d4W$sw=br;<#c_F*`LkX*P^ z-Iw{LicMu*W1OAzaD47UdB5e14WVhEgJv#z^Ujvi*u`v$1sEJ>#EMUI;q`ypiv_DO z(9{|aq}+ec;F;u_#KCOKYI@~)>~Py+fh>V#Qu;=6uCoBib)__7dVbXjH*}kMnbBj= z@Fb0&9c)4qRo%#+-@VA1@?wKU&v*3>#y= z^LqKd*686%s)|&n-IWV_1ASq*OoBXGU7b##bd;1CTGUbXA8TC1wHZst;d`n>u@tm7 zWTi%tkcKn~21o=z;-gUr4<~vS^Zw_JkaGcD5xe0~ij(qcWPBnGE2W)f#&n|46qDbNhY%Al^KaJ%( z(vyb1l2}ow=b_@=9x9=Q4yBy={_Stq|N2RY5>VctTb23*w6|a$Ji&q-tBvEXvkkE3 zYwh?^&pmx|n^ZrutcSx+1^6d~qsZ=t5e6glV$XsX?WihUnTtCHzpJmqno1JdZ6Y{{b}DN>j>06Y{_6Jnh1qdyB_(I6a61cF zn(~aXcHF6=cPkf*8x2L*B2TI%4p}ZXL9zTp9+-H-P&Dcpl4(#4;xaiEgWL>yAjWXAN3yu_LJIE;#$^yz zw_?n$iYOLYXRd{3-=?G0V)QLaijAcvZbK{aL%6;&T1fB-6tKe8cf#w6$B9#6Cz@<>&J6mI)`m>SX&Uy3E*EWKu!N z-?Cu5!b0p`-lIUc%Li{C%+4tGLJ>R#?fGd;;x~kEbzApxW=$xWg>lj^&>sy_`$$`q z1=56l(Y19!Eh~IBFUPo%RQ~4h$2LYzGEtNMCqY`)%WY0o>05!H`}v_PI~QPw)}{8XWtqqO;dxby=8PkJe?5fX z<_Dp5h-R!@Im*H)0W)=ZyXfyjsPm6ZL8&L(+Qgfa3w3sRA->q&pbhScerF$Y2yU zC>jdId#JLNo?J&3#~2j{%6y@-wb9U6Cj1(DW=jN21S2h#JB{;uw)`j(BlB|>h-*a* z(}h?V0D?RCNqdjsiP;WR(wITmRBgO!!gA&*MZvJP?Lid-j!d(y(uLKvva5nD?q>pO zni`&H3LQ;#LPY`TG1reQryy7c1kzvVWFqMH6)%EnB1IM$WJ4dcUk~#F z94SBhY)-J$>cL$pCoANR@QKusN@#7`FoMjEOrdtT2T#}!1oCU7Ccyx|dyJYL->^Y; zexV{O_cWhP_8*eM{ohgw6Gn{b&H^jIsaB>*oXVRW-TUFI0}m0=An9I!hN0f8KN+T& zRDe(TrAiBxKs7VBvJHk-?TlZ#7b>Q3l+}S1I^;bD->hx$>KlK^&HC#SRX_Hjxk2d> zVksnh+RUAIfyHz!yDN^-#rXPV%ek>xbZE1r^6aG!RELAR!uJMQadxF-<=w6xI&RcY z{-;W|jcl*!VcD&UeZU`0Jv}5J^$fGV2|<*one;%D+f0U6etUja^pu z`}nb~Gk(-Bt>69ROtXd*fCr2^pLSVBBB79CrP{lUW*MG_WuHsf!ZwS};+k-1CyDnu z5{IIPdR)M$(uRHy`>gRoW%rXmw9*q>#4c-xZdJNf7i!q=PBNXkff8BHZJTXza*H0sm)>l7N@QwGxMx0%XkT4T!Q*d|j0N0vb?QPo;&e>{Q; zxEAencA^<%WLC}X__FYyMfsN84*^4E(jF_Kq`(=lO`$!uVjX+TW3wvUX{@4pj0lSu zQ;MQ9+GFM1RlW5lHiy;RHVf`cE!)jUoB}tLxyALIuUrR<2sGniL~s&^~6^ragc!xAYHh>!87a9&!CaQ8HF$p} zpm|@??Av%Zj|LAGA}HqQj0gAbcb^Rgd6GVV=k`FapF10{dTzi01!LMB++YX+t%6fl}xi3v>Emz=R>ah)eAS&_Wse7yp{qvyUw3B>*VR zqp<+gzX1}KELdjhWWdtOgXCM9bF0okdb2%30c>BOl2iTkOXpwEKMJbW$9?j1fh`2~ z-1nfyGWopfGZ4m^K9Vt-+fN?ApLKUuu7d^YPo2zQWfeB5G$@6Qhbm;aC1@{Nrq|yZ z>oDc+$V^13Nw+-1o$kek*8k8VMc3AWX4Wo%zWR&QN#aJ<|HXkdVqo;`?7u2F z3eAU2_;_9M)GM%MF|j$TKIM@^MAy zMA92NM|r;XXAR8@_pkI%{}B4VF*?w@s=zt^%6(bSay<}T^c}4pyWj|%ml5V7C=rr*V7y7zn&~5>Z zybpal-x}X~u_jCb19sHb3c7W{bF-`_=XpGW$fk`7m%WpUn&1jAK}-PGEyM8}Q-`Kk zq-mCkdCTMpoSxllDA2hZOK#a6=gmatJqF%WE}@9@xAhog`?Q-nE=BI@aGheqwe2Tb zR5x(z;$cBrUv-(ikgvUr|a7Rl<(0Qpa%Rz-W&q z8kY|jwjKSH*76hi7V8s`QY^+PICRm?d-nG~+}8{*fi`bIwE8d_IB);1}UwJ+0L(*ZAJlT|L_50F|gI$@{DQVSEoK_;mN}FcXL)n zNJWHa0f%3cHVbFM?64?Rlzw9qRa;1ZttIR!{pvRa$8hH?zAZ5@xe;YWsxf0Rj%=cu z_ude+u4pW&i@a&;HZ>#hdA?!@{@Q_p){X==DJ+?42!8*vx3$xL?CENH^SJ`KGkgNh z6%Bl)0V^;3pLr6V%sf8Wramfr;35SrN8qwRK1)sgjosDMlA5=tg-3q#&4EC0^Ir@bTJ3A$6i!uCw~V#|CcrQdo81ZEC|5% z#xErVJefKLvFEiv_e_mkyzY1lg3dQr&_S5Rph{$4@AsW`mjghaeeDyVMH0l{;Fv0> zX}){v!)-l$t6mJUCizl(?77+m$+NfQ{-f#SP61>V+i$vD!PRSh`e$DNUL6C^H_+Mf zLle-R4pa-;#Qu`LY5wHW=!JU(U5>4T++{%*{O;nQQV=MmRiFs8nL z%k=;^K!w_O-z%5#iKo9F#O#9Tydgc!DW4H})+|&Vrc~6M#Z7RHVE2*FnQu&ZErk^K z!IUqcc<+hD?41YlYs=^HXrBODi(Y!d@in5pUwjar@{fF8{PqH&{S&y8ndSGMrq3Rr zbH^hfM+)@(7}v%p^N$g1o(6e~zW`jJ;~P9(+A$Eiwco=&`G5C*$2kRjvhq#O2^Y1yoY!&`rVPoBL2+*`%}S4es1LwI1ZYR^-vzK=$|v2W?>*CB z3`<~PYSkOolc2_bgrqPC3$3mcka)g2ybE#ZW=~w7_uM&2FQ_S8vO#|xbZVIOpa@? zz#$h*FcT^s)arOTkHV0G*8-~+QM2A>keYCOV^8llpz@}+TrNA2M+4eb_1NRyR^QnF zNf45y$q}&5JbP4Js3KX<6hjPr%yQFC5WlM9T3%*-oRyYELc65W0p+QI z1l#=>rod8PKwF!B?a!bt@ke`nP$q)U%Ok@*$f^v0bzdAlJO?9MRpKFF{5eyq4RXE$ zdEcDw^!c;lzD}PK?t}3A^}DY1x!(c6&+;sgv$eG>sHGCboi1ijNteR>{~@*Fx@`x` zkPDBT*CW(>BUFg@*=;ZsDXg!j)5kR0O$*p)7 zIT0L|3$)sG%&f0bmqOj`NKgnZ=3zBTX znZu#@k5{!#CfK6^Kfa>MaBe*5e@$Z_B!Ur!yrpmY3~}Q?)9b8h?}t638TDYANp#ZJ zn&{m44qHG&d0O8EsH-^=0PF6?p(0$3ZN1lS6Pb$3H#bmdD>$IV^1TkM207<>Dwfa4 z$yILY;fqe5ar=hCWBJ6Q;#Y7DOLeDLMvN$;;^46WQq0)2tweuVQKeDHS^xGq-?%Lo zLpu(24J7&<*BchFm~k((fz_PK`{g0QL>%Ey2pt3-6YN{++mOCirpr*c(3qYkoOk^C z{KpUR{0B|uGjCO+SzBNP*o%d1w!agUtD1TENnDWcSEPkvV-Y)xHnN82c_YzI+}tvl z8^$0ZKR0!pCD_)nVRB~6bOV{VQvx41Hk(o_USB?B*zbAupuT+XW0(UfsI?MrVzYil zKa5z*BYPaCK2aljTjV1Bizoez1D9MXDRWFXqPwkwU3Hp9^cf>M#j9`s0!Zt_|Wx zu1tKLC+e6upL*GfITN%59H9`#&-H4~imk2DmX3sVd`^5hi2c-NKHplaJSYa%;&quj zg(Py=_2f~iA;3#P;mfr;Lri!5oE4iB$5RDXv1Ch-v>&)0)T!T^Xge#@0ER_v*ttr8 znKgJTT0I@S`6Dz6uw?^fB%kX--Ep91u4Iqi-Ii|m-JOm}-md#ujORy(q6fmuo@RWS z6W1-^Lr#a)>Wcaap}fX=8MLd~>UH-b_BGR3X7{!7&&sj#oabE*!Unz`e19h0n9=N| zpTBgD-)o9`fyT`-Jxgv}cq^Wv=g2zAhNL}obbwV)>l6$;Omr=M2z1BdKaosm+^40| z085)dudm&`BgZXs%+_=0oC^weJ&ss52eW>4q{{g>RPpk0_<#KR*>R~8375ym{qE7& z-o`-?5@;PT^}LGV^H5&k!jl#pl@-Fz&z#N!F-B}~z>yx#7S_4D#wz83C+dEiRQ3tUvG_|TAYQ@0W&xz))p+8(4brss={J%8?WEFfqIcvO}nMI_Qn%_ zvN@-Kt-D4rpceX=e}*>%|5xlq1p)R2<8x(s{mgviS%fvHI+ztH!Y=r`sB-Rflzt#t zbjYHT6TrD{pm=wU^WEi5JJyI`k+veoY;irevlnGJe3AEAL3cNfpbrwL(9H3_LQzB+Cj%k`wpD}Bju9vOsoXq7+hi8!xpBu* zqx{G0)6@G;PNFq=pZn**luZ2cC+l0d{#!6Cy5hGWj+UC;RbO{P+Yf2rR@w8S+Yd)8 zUnA$?4ubkkRTcG7BrE$!=4};-lU_(%yi<;IaQyJ>lTWEx(l8AI|3gMP!}N!Jgl&by z;vOCgX$K6z>W1+&GI|c(dE1Xz!wTk6L)bNfzU3mbz}AUlAnGkawTY+%S+^(C@b_Wz zzQyq7X;Iq99I*@R4FbT~@7d40Bfl1JyXR7q#QrvEbI8}^wqK>`062pHy{3>Q z>_}D@yq_`LeXKp8+Q&zGKajn>J)iIKJAg-X1h6$u=(Cj#5q}S62_6A^=tfNVhvAP0 z&P)vt4vHtigWmltg_E%0msCA z6d_+V7Xo71oeu#fYT!dFR_x)nDKJ*Z_d^@?p`_cOZDY74_&vLce z%ro^O7DS__k7NNd-pYu4S1y>j<<8BOkj%6z2Q@Y|!MwJly&_c673lr`c57_z4tSSP zBkQGAB*Xk9k-sBw125Gu}L$NW4sq=h|u`PUr zuuEA}e$!JeL~Ij{cxi~MB6beCX%92MSk$>*Jv1PdgBTU#+}qSMGIFV(()A*;0Ix=6 zUa-GJRKfExMyf6fxRTd{M1S_JQ4bzI-p}}f4EeAy-6d5PZ%h8RQieOlwd*w9n!@AB z#eiJMw;nKeUWWx3;lAqkGm72kTBpYkgcgZ>T)qpxZl3l@cs|>~F4dFfSye3Zf8PKk z{AY36JOA0bH2am|rnch3sf9W%Ct2}8l;us^*DWO}Z0-bWqUcY-Sm?spNWS9tje#`- zcnwc~k(g$Ok*9oAs;Z?>tv?+M(KgV2G7*dotk^eH-;3AeVZRFECdsKEDeU9ZH_BgVH1+Z?3&zEH0*N2X* zq!p=wROJH*HCxk`AR(4)2AL9?M$dotLu4X{^F2IiQVxW zw((Aj5J<&lEsu;bkof4W_e2dQbE@mva})HgNC|ZeI!Mr0hp|9FICGq_t+2& z_zZ+Xk`HJgFRjMM^AB6PAxBTQk1xt(v6f#9dBL30R$G%+Mkr96qqgSWMjc$7(9j={ zXZIU+EIuyA1}SiSso~?$HSN}jUc0P}z}-)nxahRaF1?V}Fi(74M{&I7I6?FQf2=S+ z_?$v+V(t94R|*P}1hu84S3ZWQ*&A+XrhXmrmAhw)|Lve>B5ESr!}ICWL@w&m=YWQ` zf-QGY8w>h{871g$qTo)wvH(9VCfE;5KM5&BnW{V?p`Px2;uwG6oGD+6+v)OY8Mrr? zTr(jsA%>>_r}~YJM2VN7GtD?tng0Br8wVF!akAZ~VL;4beV0CYvaHOgum1*N*uhU9`Q0W|t1%cbxi2Xe+*}ME*o@i*z09 z!6Ij}0V_O5`EvObOe^9}5vcpY;>v|>f}&XIZB;AubJl#xq%xnFL|I&2`Z3Y;kOz3f z_1d`wI{vU5t0sF~XmMKx7x3*oo0Qy|aJBy4*b;ek;S$GJZMIBwfk6htCe#T%UDpQr z!uNeX+sWV73mNTj>I2T}ZPp5F2ueW6F6M!|PNn;KEhzpCg?zxHM1?GjZmIeY#kTur z=_9J^DU}+n*SS+WBdQ#HtPBCbDR?Z1PVntqTArj9BqALnBrGCuR_`+KK$TW%omr}) zkWo7+e4bEJSp5eRQVjf0&2!cSxxxi_#NyMeyzaYlsZjp&xbOq-?vT~FCe0G{^lEtxSGK_%2#& z$a>uP|Li}CPH>K}xSC8Cmr8~0tM0U3hlp(;Uq7M#OI(>It6%lIis%9Z<9L_FQsAF9YEyJTKxo@7u+Y)h(Vs} z8&*Ueyd(d*<&n7O?7j&6mKR;*l8bKWl`yJKVZOTv=DNU~e5IK=nC360nb2tPV|e`z zPb@D&s`9pb!0}iU>JHtqzgC{)tm(z=!wzq28IU>wvN#5~JIgZcA!!hTTAGd)0jt|y zpFX;v3-*T|PgIMK#P}Uc?7A$PXIBLBuJGc^!&ghZ?yhpDyx)r2BYO6_u=t6ZRkd;U zQJ~7to^`{T^KacQz?*-zVfmG}o*Pl1{KM;t$miAxh!z$Um}fR*@%kKk2Do^`nC<)t zy7T$h_o+VMU%n%}c#Ts%;Ex{t5ffoajQz*A{7!&30bx6h8kchX(qx%)f_G5cA?wteC@kyetELQ;T#f69I=QpwLxLrTR2i5DPhpj)c zk#iVJrj|a9_^JgOO3eFem`{yMT;n@sjw-9wOEV@a(7Dx1eVk8jcO?74o~UlN7K;hdP%~%nMt}R zY90h1LLJ_8duEEHA}N38hBa_h)I-uk#e>?0{cs6A?JOFn6))k znfQ0t`d^|i_}pt9%~|OqJUd5&@t`WpdL)G9FKbnz{%C+q3GWtJFeaJ?eyYmZ z>PV+@4aYXxbBJA~Wl=(}Cp3g|Cx7$#>ep^%Xr3Ki2*qYyXdH$p5KdYE{n98aKBZ=Ml41F}O>}_#DGG zx>iDx2-?+z2X~D2f`q9uDc|d7(d3Nh=CW*0kFy|uB@NR1$z_8E)ChEyqpRL;3AK&u z76KmTefVV@P2BwIP->s!YN~WN8D~kV z%=XIsubW?$;}PVmfaEtSjPVlt23aP$MF~qY$O%H``Jr5zO2}J`sr>!l*%qZujbw5& zb)<^@_;t2AKSfS)4Vr2t0Gr6%F>njeuL7U3?jF4};k9M*B6GiR^oHlx3k=Mn70=V) zPcZywI8IIR6Ak{IT4qK~mzX@1U+DK^*Tqv4RBj-(0-YO3E_bm*?Wa%BLkU0gsM5#MRZNO1eELeCAeGoopA?1@(Rxjp%&hRb5YRu;C%~KH0nHC!oVumT+ zg1O^g(e8%EU3yN%8P4_sBkV_1VuH6RgBVPxzj%ffZ^Zs`hI#0MT)XDWta;E33vYs) zAR!|PZ@R0(H>k|kf)u@bEv_84Om@RmWv#5!+g-`2#Mj|p-uQvUxGq9gujwaN zbEvnIje1MWli0!AWr`$Y8YjlP+oODq&0FX;Jr1t@yonukYTPGZ;QaFpA+xf$?`6KL zr`7~Di77@ZPqMfC&*Cr}fAjNsopUhQ1)Q6n3QzJA7 zx-9&f8sNBH?+^xPEMPpe#BSFo#c8Dw%$J(71$^yyQEm20zhbBv?sOg{6ydH%f~x~Y zjkb(>;Vi3QYs{l*swWK=rt9X@jCI|oC?@x{(F`@#xy!YOLk7<~)Av=T|2nS(A?YWt zr({YqcFK9eq zIh1ivIQYc)^3i%p=RcMvounDjT5eIs$r+mj(H4_!%@izU+478ca?p!G9Hz+4`QLzT z^@m!<;yrrAT5SIowq07h%F$|)K-%>>$%~u3E58RZujQ@Vz||-~x3M5BckQll<2p#y zGDm7%g2Ecn)qek9J>R2EY${%|&3)@NU$lc8y`bAg&w&x?d6hf1QYXY)VISzqNFxx`TWxo0|Cw<>7^nxwXZM91?8L zXPwgWhu_blFyu*%Bjwd_>2Q*y5U3}898EzmoO6ILx*Fg*2OZk@;w{5hX@zPumsX5y z-G-X0OuXcAaM`e-X(Zkzvr-GV?7oIW_FE7YWvbdJnayQYBX-KiX{L$Pkh7#nS%8b3 zr3Ful2-D)81;VtPvW%bD66E2 z0z6BD+FM2Pm3V(e$wXs&`202LP+9dmSTIC9u4Lj)Fm&<+OR9>OjPXK_AiOi3PtIUB z^0mj(x$(k7CX`c^2~8zkT{3!G-}H}q!PtY3ht(4;NLVCak;YoZ9{c!;?c{8drlH!Q zFbG{rxICy?rlY25wOp~(8V&j#Su(sU>eza;7P@VJbXVzF0_u{aQ)o|XfVzH2mt3Kx zEUpv*i{?%{AS<31#3IS}P?^lYtE60EH&)pwf`(9GBFi>O3X+L41S0Z;g((0eOv@Bc z6gAZn`%)|a4d#@<1NIbX+!xK)L3|Nh=4_sG>GgBLE!9~R!sb6G>4n%|nDQL=g^jYPh(l3CHN3_kHhohV;g zBKV|ovu%z+se%;Np(r)*6mR_B1gV%~%!??_hfb@` z?gQ~gf4P6WfAZp&{kLxqUZ1?)|M}oG+hZMV<#y01F6TuM$ChWZ7J3TBRcq38<7X%4 zlgBk(;Hx)$(-XK^8=e_y$rGWlXZUVj_)=}-dVL>ZWu9-U{3iW18u1rF5=J4^e1)2P zMEK>2@T7k;#}wP-{9#Z8FGLmggPLsO`hEolsHoH#jKq>bR*ZU1T5?gR5IP)ykurBi zmewB?$LkPex17Hrq)Q*ouzQQ&RPA5d8^-UZbT##%kD>?WqMGx4dfP8t1dG5p7yXan zA71{-yFFzX=C=U|>AB$rgtgq#tAotkU8J+xV7SAQkh%%KsQ&d0`Ly{lM&BM4x?t>B zxGHKtv??>}4L$>%e;p_J{D!7%lim9!86^Kgk1HMpU%UT*@#I1NJWoa@lx$YPQJ zqUvAY2~yab^q_`zRS49me$Y?@i;CY5)FAFYW7V|?;+#CiPUbTZ5MD-^xW`_lS=B*u z+HON~R+0PFEH%7RJna`2jra$Sp5mryt3uY zOZ%(vuot5Zt`CdE9$r>bPz@$vHRTc=enuY{BSVU}C*n=}o&de-xV>q!Gx7a!#Jg=$ z*;V!1y?Kcp4*3S36DWm~DPcXw+mHjc6zjF;h$EswkqOKJ2uut?e~>_q@v!?)bg#;* zz(c?ma|GaF{jXA)Ahv7o?iYCYVCQSxn|zg2&!%N|hMlX83BcD3BQ{2lLqo(w;@_g; zES(nwE=nMwM0H!yaW&F#I&;Erc@lc))_MY(RahYCrATKY{6nkfAi%ndKZ*Ma!-rJL zd-0h~0|)^E4~SFHQK1XP=_LywWCUudpdVq%B5*#M&%mn+hR7jO2wX){geZ%O0ez!+(4UglGDl3mEO7K`5GA8{9`d}a3Ly1b;mEvt^5|K4 zH?UrMTM9r_@gV_`p-7cz+G{A$C5z?Hv$IK}(Hkbf_D)wE46gwQOHE=&wnUMWfF#&xlbg zUvk32Rv(DVIz=?)>AdK)iGbCYPgu7GdGtRJGXZ?F1FRc>z+~bq^>1o}mC3CEW}0zs z*Ow~-z?gS)9`jLg-BnQAb8jLRcO@3?V2ry%fApSUSijh2-gcUf`pCDB9ekdF-AooI zfWIaDLcd+S6xmyL%9t1MRMmw2w`p<2laQ?Re3b`-mQq9;0CfUdM>5fj)yg_R9cce0xE@D((cL;HGC*bB7ocT%wVf$Doy zfI{qAvA*HfQU?h0cC2f4sj4YAFQ-oGpJPpcx+a}N4P@;CJ32VYN}RV>>RM?RRUk8c zi_0`*>2wzJf_JW5Hu#1Rfq-SlUtzA6->&~!ZprpjU$C0Ww9azi*V>Ez@ z3#OV8Jc#91SvTiJQ?2==v1Dl~hep3P-Gle&gD`B`(~7GSBxsKfX(hmowJ`}Q5-YpfEj>}+Hl+O+C+^LoBV+XVv0%>tdE8y_<0I zAUX>ewA4+Rc^X6myVyb>qOEg|^%YoHJ_b$$Fwa3I!+FNii$p90O_xTzH}62uImgIz z&W>LGmkdhh>>@}CvV+%)quw@4ly)H1n&<`aV{s1V$ zey|^rKgj(~!CTaT?Yxld7e@9l0v#_V(undBGxI>?7@#qxp35rWFuMLF7~ zK3K-bpcTP2$aRa&xd|S^D3r+=#2v%Wc_xxH$fOR!+<%c~pZj>w21;OyB8z~9v5bfF zB!ndx`4)13XaeJ!;7QuYf=2^8n^If6?)b1;Fenlm+O|hen+7QxZ~fy0OS^$xPrk?fB!-* zbOCJn?_cbtE`G1Ckwb&j*)eSeXu?<;t7vrn7ErA%F0{qCMosp|60uz{>iYkhVR|>E z<3>boILUiO94HA`E$mG#2ES7#U=NkljkMb$A`E6QF!SN4a>r;%TZ3oOa^PDzo5h#uLobmk5P}cm-FK zbRqrC#hO)m->GYg*%>2}228_;WGB)WB$5ZG=jh(ZHS$H}odDpMWUZuvS)-NYQ;AbO*p9NoBQ>?kGM zWJ6_wYwaDAyKHqfSZX4&KwtjQVOW6Y!GBiJ|KNprB+-;W%`I`jyUMavsEy$tl1u&&6>#c z&`rE%VYsFkcN($!+jkx?*96SN0aH6@v~iwZk+C-oEimhkJWXDd*!91T-n|_N3XR5> zkS<+*a`FQi>j7|dwUM;O-l7%%0t&4;RN3)k2cp}-TYR3Vzi|Y4N2psiFv3|BM1%Qcp~h57uEErrjgxHa-1AL3I{nc{P?xmFejp0|jlBL(zhg)}+oATWeOY zz7-)A7@u=;dSiukUZKA~JbVxYCR0`v)LWwPAjzNZlpW|RBAI~-gYvLm5!W)MBU+?j zUuX{QKuyFocAD|vGZir-NvGA=$S;-|9H?=pS@@w}EsdiSMDE*$@=nAaN6ExR48|Wg}OQyG8U#AZ_aZFvj0kBWyzaF&v`mkHA&Id&Tz z`_k#cZbN$2_fje&O1f?I`v3QT_k%{if*KNpeLFA`wYqw{53Wx=#L26=M(jE$Yig;A zVLRfjFAIn5U?zZ#@)S3WO2;pG5kzsmW54+2LD{v`mMc?u9gaJN7v0ntREU^YwESk_M()SKC^Dc=%U9BKS?PBM zC*PLZfy&IpVnK@*yuVpUQQK(;r7E9}i%;}J=SF}e9vrf;ce^|`w}gQd5L5)8rNr0j zl259*cgm{KP%vWQ8`c~7PrOcF$hO^{SCQkR8z*XC^s@t6zk}3j=9X%b6p*=g1suwF zB|ALW7eY-3?S3cFY)TJLz*JL(+Mre5t#oWOS+c=Drqr(foTf1k5>LFfH?Llcz--d9 zhR{pbhN$p3=PQnFCN-{${r0hnJ2s9`i*oNVUr@TWLsu(y$Gh`*lGQkX$@ewk2G%b5 zg(?kwxAF7TQpvs{LO-N~_(j@3#?;#`hjEiJLSS#B1-vhI4V z(RHQSdr|*+6h}NMhRRH}QW4nt`p;Wi+naX%*XQf{@3&k3SsL!~utniLJpT{!*pEhM zd>d6fUxk$euOJ(izW+i03<|K_0Y-#bLN(vmP;q^dvPiaKYsO)*7V@ zH$G0Ip>E zMwkBy!Vt+ih~I0+qyblkFoiqpW2ZVZa0S6c-=F#qdna^O5G`9BX= zxB=?<|K{eiEsOte!>zF9|F_8hzci6I9{ifX|GM-qihOTCRLh3EuiCPLCi;K=#HRo4 zO=!QS|NB7y^ue!L!C#F2!8u?9%ADN7{lE46`QyiH`oACauO0lF=>NLgoUS=F{hn{J;Hpt^c`|KhadsKPL5f#e-s=@z+sau!YEh(HRVegTWBK4i8^c`XSKA zcXl5%sgDqApzZ1!q#%xOQ+xDnX{;)$q_tM?h5N=fpkI2llc!L((l#qDE$GR73Oc5F zF3Qv8(F~MJRMT3J5^4LX9tqe) z=x*UxX-VT963R{ys2zx8Z$+t&X)ee&%2n*MK*{*}KnGY{VM@-l;w(Orsk zmy`&Cq^UdlyNq1o4 z4wQZ2zHOER<09Ffw&JG5>>as-wi8qt$qWEAO(VDanO{oZLv$7$#Q0!?3dHSCuj<|= zGddZyJscY0*fzd<{M#I^diS@f!~c(XoTC#vk(0n;znUmk%dV900PYV}y= zRRJrQ&EiW6g|N)Xr(REhQ{9IU9kbYdP%5eqL^yY;p#IM+9ew5)ict(@tanFiZz-)S z#r9C@u*;U3<{I;i!_U8Ekg`3~dfA`!5hU{zZj+fv|6cWk5WzHooeqhHo!tlGl0Q1$ zKR!75aPa=!hvSpOx5oz`{GvI89HZn7`zp zjXT7=-10*fBw;!|LaF6d06V{QwwQ59(5@n2KXAZ|UxFlzc}8#LbCO;pfcFrT08H{t zFmpzYCP|v{P;Yj`3%6MrPD#&&$-<&!a;UeXfETLGlf$TLHua5Qt;gvlmqRuyeA)Q&l_ZJiL*P}RyV0~<>}aNJS1*MC42OZF49gF8F{ z6}X1AU6SjWY0vQ-x=k5qRN)fL=As25O?dGph!FW@7Qgn9NT>7svWIC}vGt=zkOtZH@P>DoT5!Q;YHc_>sKec?SxAj%Rfgm402ajACWB+j)ve-Wmh{NHNuhBka;uD z896-4W5hMW#vSh2q~H=cHu}Ll2Kg|;N-Qq~6wD34cUild{C))l#~kWa zhW1eX){b7(zZisNDs-%XMLNtteV0h`^Z+1MmsjL!=t*9W z@3I$IBLyb=`Lgctk^NR@p!iA=jiZrJxB!2VE#N;~3-BcKIaCAM&9&6s<-5*T=tZzP zs8=9*YuUccxHL4ILi9*@U^Q7z)tF5c>YUF(;nFXHfpNq-;sEDlEEQBY5RBAXPhow_OeYH%;V z&3UQx=_(b`&EK!KMoRs4*Us1+rcn6v3?6OKc*p*_H7F}2o(1Qe1<55tg&7lRxi5KP zciA4WsOaRP$|zW2#ndbc?Us)Rpp{}tikYCZNND(>VC7jfI#d0M6jZjJ!eg59?(0`p z?N?!st#yNXfz%<~8)-wsmb$!V?lZb32O1=%){^RZ5fvKZYezU*9$(J*$yQ%FUmu1i zTP@I5Go%riTMb+d+lN{#!4y=qUlzBYWzIbT1%^(wMqkP{N*PBbR?E^0RB#G!l;8Jd z9bGUe3fA&_3oiBrFEwC~$vDNF`2uje9&zKaEL|b$u^n@;?*tj@fDglNZ?{2C8I>YR zm(-SMtTC%XB)VUuJFCii-9%dYG?Cj& zsGa3M+)0j5s=(x6@~<^x%NA^pRsYy^>YzGj;8N7@fB#}#e2WeFQ=Lg=@w=&H*2UO* zeUPCagJfvNVaxHbjgAVpmNLsB&}uD`LJz|_(@&}LI}E9eaGH`u+k1~JSD_OJtwkd) zVXdLe6-TxeV{7m!e{dmGt5~cm)!F6_8+pC9Lw*63vACm&)C@#uPmu##dNmb|P$i-< zqLCcCk)1Zi$et+?D3}-N(Pc8~0rN1vq_NUrA7c50FLsI>Z{tO;*RzbXweQor!}#1@ zQmKXz7DK0K@I{bUjn&F*lQeA;kWeseaI{7`)0gvwiE`+O=sRk4+HZ!54Fx!3k)CwJ zM~>FJ>&_o>HFu+ z_9}p#&Wm?r*bu_nSpTAx|KEfc2Y*Ib&zySnIQo39HTLj4Q#)dwuHN0}2dg#lW~Od< zc9wPRUpTi?wUrn&*UkH|$8wLY9LRfE+Q_*Lp?0PtnOoPi)epIA0+KSV} zD`~WM$M>3v+yTZ>62$SP?-ks}W&W19i@TJ8Tzva;_t7^Ew{NVYmc6>kg!G-w+2J8fpUC_EDWS=ovuHj2*lismR@{77x zb;(XN%k1~YiLFg7$%fr<8Uws$k64C%sel^yW$Bw*l@qRK>-yLlUsa(|xP7b|rmS^e zgoFG}sk@$xaLpa;xfY48XHJ5jCHl4>sG>bXQBlv5O z@N}NDpeXosR^;$Vh7dk@WXoExX%b&5&qDk?ofqKLf_#iKc&&n7fHwNK6vx^mV2{s&0xJI^2)ixZ2XO0P?I>p}5f4uwr=H;tMXwqg#dR$&GUX|l_2cHJ zT~aFrE<px$>KSN&dQ6V;k`XS^XED{=^C~>@^ z${pR%D+mBD^kSnRL$5%D@i>TL9=5)g+qyQJkLwmSFa5}OcdC8gnmWB}(_b$kN3~He z)A)>Yh|NIN_u0IVCEV)?TjJCywHWJFO&cu?FMX$4gToVr3J_XK`@pe;?=#$_GNT^nV-jxxGUnrBiaEz6n~1JZiJrk#Du5 z0bV%EtyYL8gCu>at&Y?xTOPWdSJ+d%9OUNMl3$ci+ul`u3_x9kdJE?KT|v&>7tM|f z`NXs%B;5PTN1oRqvN*F9n*8BAI@cfn?p`+4S~`w@Qkk*HgpZ7kBM_ z>#0(DBkwr=$xbx@QIS{Nub6~fAKv}m_g0;j;Q~92!3AHxvZtGyo6blwIAF&YL4fJQ z%wDCpwCg@?wsVuqlaUq`g6-029DtEq8OLP8D&=zDw;TX$8@!WI$8wESqgWV^qNhxM zHb|3hN5+H*(`l!#r}Rm2xEFc@!OQB#%e4d=#Av#wapPE`Qpi#Lh%f9$&Eq07s2TRy z`rbSu=0nGvouNJ0TYgcplwQM0h>Q=;F4c#2_hm-GatG8f(si+UoUhkcvE@~>ti2zI^a}gLKJF4hyhdz>cwbQ#T)Cl!&{<9~$s0`U0?87gREE;pFn*3{;nRdgiP((# zWUr_;g6L%`Vt+nIGZE$~qpCzi6Y9RO=ot@1s`dcGaX7fi$>411CX?zuFft%oW-*~M zcs^8Mc278$eUxoc4MR0GQOWLsjyLKb1Mw)QM$u484PlM_=n<<%)sN2j(LKO&a?D5B zYm;-^PPT1i+17Py81t>~)PuPi)+SQgSIJ4M1sbZgg?A2pDRN5n@0w`?U5;%%cdIpCNi^_njBpFsb-EdQuQPuN!y&$195%Mo>8tn-=ao>D$^B#t_GA6cuoS<_d7)0T zraVdeBP-KN$hH0leKUFAxSnHX|IW7jv2J|@B)ZttpsJx$5n8=?Ye`qPE$TY*UoH#7 z57FMAUq>CthWg*nH=o<`-;*cLw$}3BZTuPjDEU&399{&ca9M*f0h*eQpkGRZlV6$~NUG5c}&z%Di%}q}4|y&-V7b+6-bR$OK<1h)H-V|nr7DQr3}^}JcB1)jRK5?Lj3<1O z77<2Udv-?_lU4M2C6-~(KUU*GRoGDEqsoWEQ)h@uGEo8k+UtKmfA-9I|2=v3bWQ)a zSpQr4uvLz0TJ5?q&yGfCd>WLFi84>AEU?}G|Ji%@<~D9+Uv&Sw_NPespLO=J5{aZJ zT2bA)7lRp6jG{P<;yCoKeX3|lmPAveOi>SO)jFU37J7i`Mx*CNJ)JPM%8Qx-FnBil z`D>SM;^KI|01v@`dA={k`HtQ7X?>gR+xNsXm%K@!tppKi!%7r`+l+r%i9xiJzu0`$avh($h=_IK4Xm@w@;2r*S$p zB|G!~)JtqHcbv9P#|0(%D zo=rSRfc`H@MQ-n*weJwrzB^s}&S|eg<$0E+h;V9w5*?cYwGsZwP0R3^@BSbAy&Z<) z|NATr49OxU7LZY+7HskT{c(cxM7#FSv{RvjH=Bw7Rg{l2%+C400QMVDd9z#sDa5}` zZ}RW|N^^G#X#d%gLL2}7uWZ8pi_dQwuZTe{5FeRlErFkUc67YwVlJNSnQsR+nKRe>*0#ozE#qSLyh)vDF8x3#~vIF`Os7gNkc|cfwc=KExwZIIln83dz|7ZXIhwNX_@Vj320Xl&pIwg@rU9dQH&2Yun*>3v+2ME)T!^{=)oUYLYWM+O%wIe19=NzCJs?F*VOWADzuj;U9ijH~+J* zOe=>M-+e#-F6|KWw-TrZWJKw|{7D|IkVw)3VuMpc&XO@T&cTDgK}TFgM+b z=-0RZr0gG@()tWQnf=$$dXN4$HySJb-xJY)B{w5PvkPd_{O$4VWO17&x-Jq)95a?ZR0J*76`NY1VeW9OnWhDerTG%cY9d_rrghBdq@Z2*@4oZ z(z+GKhR@8zn`ajh?;tj4>6L485|R353C+Wg*q@z~Na9PUoNJ{o;$rIg*X93=W(@vG zbD^!P>DLU<$SD8nu!1H0-)4u~f1AyX#)|)YKKd^%_R@Dj=~k-C6w+f32lpmY>L$Ar z^fci$K1WJ@rZp}jr7p7?$$Ct`R+lavc|72&u2>|cu@EPeiKu!1G$zquKl|3HiB~yf4-N10uPX)AKBK{u`}kaQ`=2&Bn_A_xa9$wg;-5dUM{hjZ^I^ z&jf{n8#z$!G$W;&^bRNYvAOjZ!L4tbgx{NyQk%3kh?SWtsq59%e>x^0QTjA;!kqrY zDQ5JaPKwaKIhjrWPTqReM@o&%&yS=MDXNt^(NI*cooS`c;E%CJ<&N$|;*i@BA9D6x zH_X?OQm^wT=_=x?GJhR(r!Ft0*JEDeYoydyTjM-Z>YUd85-IfsYqOQ`hO`k$>|XCW zSiDR=)#J#OPE>Lmt!+fmOrmrn66W_!q|_VSgbS-ufGJ9cX++9JfnE@m5p=TL`!=0! zyP@5?JMB%a-LUTMI3~IQPqR3Q!bv6HLwzJlyY6)>qN2Q@bsU>%IE!LfrI9%^Cy{CA zyr5H5(^u_ZZb3cpKJ6LE`?ils!!(e<$W9_o8laI(Q<@F8qseQh z4}NdnxbMIx;2+zW>^oS|&C<}kXxNI-8dQX-)+%WS72WvW(TeaID%yet6~Dlnx#l%t z^0i7%HLvOPx@paptqDX0Yl4IVe`>Yg(Mx@uwvk-hHrUd+0k)!|*YCX7IIs5gzFUwh zt<)84#cRzM&y~FkrqWJTx&|G)2C!4SF0S9V;$@=LWdbKY$?08kM+XYq#(TX@U*nhD z0oV?@XsMkx2bwN7TvzycSSq_qv*osbD@}B!RG76(3mVX*LBHxQLztMc7W)J5CiI=N zs=%A=h`fQ#>E5hI1xrw+HTULg6c7Ns5O3h+MJ&`iNndH{Mo5lV&O%@vRoe)r5K% z_Qcz1f*xed8T4U)nvj0Nj6o@Ck~0J%CZ&*QXUn@8wX{)t_s*z=Fk@ZydcbTXMsLpS zoxRkML2Zlq=U?sYr8{CvZ^t`3J8xAF?49o@D@U$lyc}@8Tw7+cZlc>{ZPrt22F+Y| z-9+naGN!b9sPA{jLYo)1w;l%r-89d1OXu(@DbwthReRkvAD8=k5}ihCUw4)Ix(kQN zQ|*!2&NxSpB|)PUPHMsATy;A=*Zb^h*y*c5J0e8gJ{&e8vPbO0{%ywLd5^cFl#FGk zW2dj0X4KitI>|%Y2L)aJ99?9L`aPPticY)E6zvUiqVvW>KMF15jKi|PGg~O@!%klv zbYgD}Yt_At$&BIZ(;Zj1`a6Afsn-+21=**Y(NXyD_=xey_c|Q36T?=_ZJ|B4W=((0 zvuGUWt%0Qh-inPi9TVL2i+s$=7jYpU2h8V zNVXw2<5-+m9KTz?>yQh9DZJ%u+)iJe>1oIunDY?bMT{}n>8p3woJZU_R438T-m%9C zO)pZI{JVrOxAaPeP9Lhn>nMkYTY?;)a<**R4GMo5%-Uk}yrqP$dJ9bZsc_2 z?F`lNC)>u(P|c!gdW-G!)$6D&Y_ZW)*hyRd<({c#82LzdifN=lAKS0@)nQ94{k9=K zK8w8PsWbrF)8m3ug_b#T9r2_4@<-o!jc0p*%Xpzx@8f&#@w=2u-*$kdt7>=i*A7=|CXd(P}V{j9x7UMcZWrb9EV+%`$LHt0!>Qzeb@D>$Fm> zcx4xQT>H<%hWQlPe;N$~_QLHy&CV+R_qpvqHg>WOS*Q096_`7{LrY*4^!p=}FLf0Imz0jrATsxOTq zNr#^>ny;;{Qd?cH+H`GmTB*-M>A|TT63l_4ye*=r-!Yzkr%KKR2lgXw@xi;Z*}W* zMAnJ*IQagY*Qm3gR+bypXiZY4H)SpD+`WCe5oy6Ph z=Xop>T3^IvBv)R+OkZz1$l8(iu}@9?wj0^cK}>b=ruDWweN<)VrmsT{%WnGq*@8p7gv zA5JDVBp{6eKqVdQH}mk6`Y7Nh;E0*`t%#^%e7*tRkLCBw>Vsz2%)k$|7qPcaeDzG| z!L`oz%`V$FW@hQ2{KYYE@2FMBKT78Hl;rms{GyY}?#vk)EQumOXRz zIssSXmo}TT5pf=!@%`re5huY+78wA=42W1*YFqxASH|lR?6t5=D-uWMmAPB~TNr9N z069%6g9>-=l31rLHklD6D|M^I8THC|*A?^#`{v)3ebceA0!McdbHBX46+-3UCTToi72W|m_X zQJ-G{X{?J^N0Bqc%*d$bUWz%d%|tT8R#=JNl>aT`!D6mRVSU@sjxkCf-k2+ha>YaN zOM$Y&4k9*AnNeactyjE>|t-M20AJ50tk1aL-upfR8C~scCBB z7n6C9zXu;VvrDs!p6vm@zYAfPCUtiy^><`?7x{q1FIIZgOt7Wo^U=u8)WqEjj} z?NvK=_g_D&QO;SvMj~G1tWC0;ABj3KZjES8r9+S1^NzpAxgZ@8HjgD1g&YL&U-U%n zaWFQctJ)KtM)059_3CFk%TV-7`Zjr_#I3I*cx3TKCswIBhJ^3n%;cMHgH$GB$4nhctqJ>j66k%YIy&p7Q}m}FVa>u3!^7r(fJ&-6H!$Mh7Gj$u%6`iVI%wtt*B`>0 zO@QLS)dS^~d3X_#r)ek|Ja1@@#E~|E1|kF_!VT~hyNNy8C0aRoCQYevpnW!D{vD^f zOJ~|T%A4mNaST}?qljw$q*U{#RM+@ZU9J$?h}X`$N}Z=0Xlgc)*12RAdVbA0Z+LnU z<(jwLu3!fc`pAeK>N{~4;4ix=^eNdpU6EKJXcpbeNFU5jBh>7VeBOAr1aEAboRHsf zNp)N;+U4Rp$c1KVa_#d`g&T8#J#>7Ya}uCxdx>;z>)bT1W#}`!=EqF7-Op@K z^K#>f_u2I_`=Vrek_a0yW=WQ|0b1H5v!!pWjL5*8ox~~ArKJu638_8Dd52iNNbc^G zmM$>1^x>+-rop>$fQP&Inj$sTn%soBS5~Zb^Q2j4!_+{$S%O`Bnam z%WufVY53x6U_^+RH9&4c%A?crgCcu~-7o8X-Iu%}mm@2_4>>O}Ey^dwtinU=7z>XdSc^^izSo+zzr-@b6Jf_3lbkcnS#N?4__c@Ohd>@``>uOrzR?^QIn2;I#(2c$IW-v-eR*O(WT zwZ59g@SEWtIi$i#S)x26RCSK^5!B@fN_{(&!x<07OVD-7$? z{v&up2^M#Gzq2PC-wS50yEfA(nvyO1o688U3(G$iQf8FNvVpZWn7<0wl@J#fA*-t` zuNw+dl>*~*Bg8AOwi~1medn_=hvNI&t9kGqLLfkYz_wD!GNNZo!aRgec zY_i{Mfvrf`##F|mK=o3JdFz?J(c{mUEZc)V?y(i-Q$BmDkJI0n`l5aj-{wa$JPpg*uUegsCJW1%&@bpI*v< zjvw_hGM@TQSdYpkRM@F*bR~)T@e;kHoigyMH;v9ga$i66Gop7u=EbKn1>`c$2ZXcb zYxb>o`D^Z6?BA7o1P1zeL!rPwg!#_*S}KQfgacPnvM&KrWl!Jn)PDkdur}eD|R-Aj>rm-`Cou zQ;@DXPL@=8fhq}Z)&`VuBM7sk!f4K!pWxfCuWLI7O`)1t5kv)APO?UZwC4rpMF&yI zK%ASK{tPN)YHEAT@FJ^6oPjc&&$}mFL*hMzWv(9XOTP|AVUjS}@psBX7VP>F?!- zrt118{NLP9MGEz{Kc5xyTVa1G;Cu)hQ88>dD3ve6Ho;kV2o^@2{5%qjC2F2L!7)eg zAf%rK!=G^IH(YvYW`0Y}ZzsB}N11x^gG!a^k;$MnPP<*pZ+oxps3w!W?i4Q?w`bZ% z{3~qww0^6jD0aI&#(x*i2u005QulnnmSttAsO2_auW;z8^I5f5QZ0;jp4-j0_f_uL z*y*M)DH#_l8Mk!EHczJ{@eip;r7N;MeKA@ypLXG6auOaSP6)|r)Li5i2NSQu20C!r zI^2|GJr2p25-q`?8)5RDNVd|`V-gNC_D>&kVYL=Zmf2CUf(n#E7%=sm!dh zvX@DE9U%Y8BvfEf^Ob~o;_&eoNz|)Z)e?h<_4ZM^z%E}EJjHeoS$HlLh_|EymVh?i7WB-o$c;aG}w+uz8@Fz z@!M?EeObmY@{yegme%3Z(w%DLUyppgt{!YLJ8dR*iP|4Tc2NBI>u)?)SXEA&%CZ<_ zMfBl@;A0R!o0>Qy+hI7tX8hQs2k!vK_tavgw9Npi%D7^>UY}q}ulrkOReYk8wvT1t zNZx|fmif^1%v=$(7Yfkiq8X-d(flkwy1s-UlN7fRri`;M($*_E7eL-L3D=PD@#-cC{Ny_vd7-?5^z47alH_%aw2D~qwBb6xj6Qi{PhdR`)`{6M{}7uEe- zmw4C+9vtGK_f6kRu92ahVhT69c3m$!c^=C6d)dYia*VoT)GN z94?)nv2#Q+jg?YEtE^PCDo=q-Wizn#<}GV-L_E!{ zJFW7&H8})@v@?qVj4btIsaKLhu8&zu;47#ZiFE0-`istYeu`pWn&hwEdkwg!q~7my zx}tSd3TQEvS4`An}mWJa$gjMRlZ2>%*k+ z{=cyyuxr7Rjnb_SB0()}bdBs6uEsP3&TqvuXG+q(889$(&xtmbXz@x>)cf1(@TlUP zV*9yUlsIioZZn3?QtxBZ>jwO=M=d{j z?(;ea0Xq|IQ4hZ9noDKE;x$aR+cvI8mX#?SdCDy2%8aBMd4b`lPZVvW9}uZ&(W%$* zmkgHV(A7_vlo%PbXi-J!kqMaSy5*5n>ihKD?u}!T@T9`Xi_WS|^}_WW_M{iJyypcRB(C2Xp?{m>*eh@+-Q&q(zS81*Vf?2d04tQ@ zn)6!`gi_DZXK^s}UCNiTqv*&t2dhhK(I?@TO@c4Qdv(}{Dmc21h~$n47E9gqXEhqM z3xs(wInzkJn~#e_&6k}AAs2>8A+Bh)Vi!9#-#G{y=3J7QU6Zx0q(jc)$rYgKaf} zVs{aAiNR~E7;xL}Yv(Bv7#hx|tG62~&~q1~@$<`+w$5c;Ki<&Vj3es2F>7j0M5EV*i#W1)Ow<+L@Ka5*&km-17zjCOX;Z!EePWTGMRP>VTw=qrExZ z1b7X`{_T}o%Rz5fE6A-8m)%k#=Z`ZOCeyO=wuW0UB6LG7pfN}4>=^**E6qYZs+drS z3X1IxgNImnnV<}8rL{1J{8ft?FUSh{!;|f@X zJ7YIw-yTy{(d?iKMWqQDo=1XhNvcv+HobYhr2mzPUMsgn#-8uRW_tb?uPX2Ke@dUgBLCT?{n&?u8@S&bMf4L$ zw9C1RcsaIvrSHSus?w`$ZngG=QnUP#cdDdwqgKXMFIFfPWu;;|uWn4{D;`dc9#p@&` zI!&dP&(f?2e-scM$-p?v9o;6x>#UNyPHev$))js|T1zg22f%Zit!#$fKloRUl?Y%5bG!@ZoB8=QdaiD+wa(3&vq4t{=rhppHbZhT*-z~bD$tAgO~ z^u!Rb&aM@`*t$5Mh?7>Qq`1tW-e75Qd3f=LJxcD7Ql89Mmzz!T>Mtl+mqQUbZ-w(b zXHfT1P$WAo%;PFV?#KN64m?fTkRVPQ)xZW(V|6I7z9P~8lF~udUpmwMmuasGF9l^u zVbR7#=2t$q{ci_VzMX44mf2M>W&R3+{g}ve%V_;<_%lN4DrSWUmC0>Hp`IA{&0ciV zusrpp@Sm?HBW}_Hxlu-bMFZ@(6zKS8f>nd05^wP@*z<3wBl!|at;WSI`!L6nfj|3j zskQq-TI9WTu{3y}W@g=~2K<_jgbf+x^cfZ8@$SYjh?R2xq7$o`>}bf%F^b` z<~uhFy)Bp2j&8l^N)NTRbKMr4cBkURn2vDzz_zWkQ8La41x;c3X&wfI&{Pm3+yN(t zeiCd6f!uP30ZP}Un*ra?W$^>tzt1i<>WTH21GQfZko^tOIHj;TOk9i=F0NPE|6I!A z>Y&<&u-eXTc_8MWja#XEl}{Mu)D2Oy79(wq<`Pi`0!N$25nPu+8%X}e5KKU=-FR?2yvmRHdlPx z{vq}N%ff}24COWTTcN8n0og*B77p(l{UjodD?Bk6BOY48mVA$}o+eJ!vsoOQ*Cai?ijjpp%i4$1hv@T^1_qI&l=P{_TE|gv#=viE4Eg zEa6QYEERiFy*-~i>fdJ`Bz4X_q|GibbfGxhg?g$q15u71jd6O1C-bwx`}4=Qw|~pA!25X^)yg8E zKZ70RHn;z88K~;ch?&XXfd2AR>FZ@FDzh2BKg*G@h5Zz#)t6o+SbITB!_(N%)c3TP zMH5X^m6`ax8CtTXM!)P*6Vo;4C01A=? zIA43`x7N^30dpH0Nx%^9ok|4u!4Nl+*aO03F}gV1z1CB40i^c-jA=!hWKWlT}?U3uU>h%Eit99@H#ThCaGRh zz2*-xulV!DpOSs3eFv1A=|(xvMP$gHac%hPbRMdjW7TZ1ZkZGR=A=I-X92hG%Am6s z2V2A^z}&*w6_Wc7j-@)guf2YA2nIy5vU6ukp3LDUu7PT$J|IZR5_-i2_@HAq=&iMS zX%Bu5DYk9I()asEtFt&87@?RO_LHDmQD%cY-zRXDup=FeZ{~m9ib_YGL*I(KR};gZ zu^LuM*9dt#*ht8YO&bicr?}MlfG4y6zk`w^(5m^mOY8_4jrDoe^y!=I2H0nnN(#q| z%}!QgiB57;j^Ux#P4__-t!fVe3qfGrT2m09L5*us)`!P=ji7;v==@0Lb;YWB&iZAA znz$PS&?m#dyo*6Tn}}#{`Xz0>U&_;7c#2s`5G44+456;LMlV7ZBmcuJGrI8v(VUI zt?b#8|Ho>KGC29w=XxOAKrtF>PXg?@&$&+y>^9Vr9q}9DaHnc1pensq4a0_kaw#b~yn86_!b$Kf6RAuW--jLZv-v#sGgC$sLqZkJ@lUh={Nlm8w?4v|?2|Alb3n9) z3?PslP52Jtd5x`nSJ4#R*oSxaw=$<1gjFgxV1`O3lkZ8wA~SP&_1tl|ddbGL*-(oG z8!cGs3?dlSe*}L=5y0)~!E%u|I9Md@U@#T5o1-%kFd#Q__-uTRtMgkrkij&a2JkFiYtnLn5`bJB4DjvxY&DwKtS<-z-P zk{Y4scMbT&zfVA-T;L2(t#c~cQI%g>){8KC~gso4Rz`#OMqj))tT_UzEtZ}iG0uSw6=RYWfXs`|c ze(I0S+<)YmyGyeAciuX`IZDD2P4Ho`>D;!*k4Z9eP^Y=Hu~tf)LOeJIRBxaeryr=T zXE_D6v}n8n79zkwsnC02ADof0m~#2_(jw%>0@mLGX*A2!f|-(x$Kk*1+(lk19jSl# zd#_3`YFMJK3*_R5Rw@XlneG@~vMr_`Ra-Y>clE~;h?1?@yQlynsDI|k=U z$hz|3nm5Zo{g|Aa`BTr|oxO!lPtWL^!>67;=3hnDLrj9ecY{FvVEXi!zJQhT?WgC- z3eL;qM2%9`j=(t)J62+G zBGR8Ofm*f$j2amqw@3}>2$Cjc`B{B%nHZvM>-KdOTI#w_-yF|P|9pBG`qL9mqQC*H z^RA^m9dq9(A{l?=1G3$d`EPVA2m~!?QP!YsCWqHOxHTpvA3iu!0UetWxvv57R>MVs zhi&tkvbyBlvuev>N%LaI+v*wBK;QsRL(Z3Cqg>hj_2e{OLPpE&16ds7Gp<53YpGYFxN_M7#I8M`JY`j z{SdsFU*(RAqHzz}f8lv9oF%@+6T}I7Jc} zU~&rL*$9MKINO*3zTd#WGip1)L4cv!OM)wY<>*mquJ;t-M$Xle7O#*Oul~*G*LP6# z=`^me6c7EdZkRy0F%4*$@uEFn<$`@1vu@=DaOF14qR7$zeNHo&d62TZe0EzC zk$s0&RW_8nOyzjdHfAQq=>D}$38Z_cNGExKJ>u0M>Z#{{qIJ4^E~fLQP-Jq?p#DmpYfZU>;{NU6kZU*-<7}Gt zX>HC0WD=;Rk#__c{iE~T{E@bvcDrl(^3URH7cBh2iEJGvtu!S5@?b@Tkx#9C6ypUS ziD5;V_F4U{{+o0O9SOz{izxWCqS(^dG36~vW|7qUI{iHtvf2updJC$S5(RXtlOZ=| z^Z#3e2?w9{XMnet)BXJ~&%JKn_W+H5yXbs?%Q{sgTpJHn7Vs%B=9?*VI0&l%X;8}p z+aZ*3*BnajNHZe+$3mtd&raSZ60p!Xzb$=zu+RnG=W^33`}saC?&n15YeTH* z`wGy5caKLRA-$}xB7SP;y-?U@8U(QQa`Acl?-{$=JRGR2Zyx}>d+uVR-i#e;@7L*6tU z`1=3WobO#BCBH2d8W-rz-6hFB<^ah2(3x7GQYd6JnhkL`{Q|od4XKaDm9KOsF zn>ECa6Sn{H%XE!>fm*SGUYWLxkD z3Cu3HL!j-1!5U7)vX$TOJCLVfudqN@>7L;&7#SiGU0wZn4guU|MK%S0$oeb3$oP9* zq96PE;-agG=mFZ%QWp%nW#1xSb-X0*+)yieoB%Y*kmXrWBh-k;8Ws!y2>)c4{EdIL z%l$qJOGMzNaucjQ+bg^EfWn=332{Jp1XhkGfRoI2K!2i@-_&3C>Yo{St^zyODp5#9YwvfG0cN|4jPYI}LT8#a1tfjR#O8(-$QKBml<=>Um25?-J0g`2y-Sd*7XS zd)oX9sXhk;{HC6J&AILY#LT^OFevj0@a+O=lJ`&Y13AF)!4@z*2b3=I@b@pj08n|_ ziJjg`698`p5dZjokp)!u0q)Krz}@F8Ao=OW@Elx7d=j+a-|*b4(Fe3kV)DMonsv&QwZ)!A({K(p_!9LxWMiERv{G>)oWAn)4CDH> z%fY_Iujk3M-`4cULtCz}QWE4)xcJ~+(_D327(^Ok^BF`%)Njgs5xTxN2e*W**Uq}D z%Wsx;O>{7i>}Rg4z>YLqhvKFUrTU>P5OR&jYvW2GF`;BY!!tmX3S?HV9G`=G^X{OBU-=QRcD*&~q9lrY z!ag774|!;+evtlic<$y=Jdh%LeLM8)23~4M(2}-+tG{4gXo;HY1sR=nd)3tIU(5zk z_HP64&tSNOwzGLtyjY^bbFV(A$5}N$zc{pL-1g3TLXL+0IR1mlfA44&{z$Y(1VEAD zA^u!t~u!sAMBky8U+ zeDowO)lDy6Yv%y0xdIP;oXpJrmtCzFpsNZRFfEI{9S^z5Kx^n0P@#``KazLF2{8ZFGm6D-nVff)URERIaVsdFlhH%M72Tx0)Cz;d~ywa zk<&q~{TUxoKa#9*j%K6h?ulu`9siZm7KuoI9S_ZoV`4<5??CuJQ`$GNO$FyUi{me` zo9E(51O=>Wv7ZgFdoXyYJ&<~t(M_LuE(21v-)Z+tIG;z^lF7Eq9~seXf7e`XhTkLX z9=6&^%U;<9lw%ldD4C*_QvPI6UbrUzP!dhrb4t4K>RU`KG%qI2C}e`{@KuDUCSLgr zXOLK$HW5|Qh+`+K8lTmE$4ILqF++D;q$ZbYT-wQYDXO+MAw-IpxN@RfbW#MYM9qS$n`N5s_j^JukgwmRiC>fI@d- zy97M(dQrnQ+FfmT!9Ds;OBj5GK37+KWgXz}v$A2?8MA0tXiR5KYs)^_kw- zJ&tsW7{*;gN`2NK1Op!k^o%Xw-Jx(B>iN*1e}f(lFjKEt>jzM!2_0&xyw%L*);BoX zslPW$lxxGu2ICEs;wTZWu}_qFWG$4E3-#tm1!oR&l=jFms*btfe?bo+sO?51>jfeMf1Hi=E`x`oLY&5tQG?D5`oD>pe8u*>amn_GH@$72Q5~< z117`pJgg)aEDwyy9i71=q|M(dRwkJL&4`|fL^PQHkJ-ZUJ=p9~@9q-cS!Hg5$eTg!*@1Ep$FIF8*g69n?9~% z5Mkw*Ke*d0@uK5w9$_=?3O<_WS|zDoaC3UmaCz28ghozV5pG<_PyflaG!jk05DASJ z)H#!F*te!UgYY7O!(sqi?#*dYpb=UMfqWm|M0Br^k74t+N0VXGz0)lI+Fj7Xv!U2E zobjeJ+0-_x)qd}iR-~7{aK5G=?evz5LD5>*9G8>`J;;=&ThRY#M|vpVRQy7f=oLzY zJbk1%>&KZNSmDq!$=W@@1iuI~QMRH!W4bgI0F2V1?qT>3B+vuPAOsg{jK~SoUU}zGRckn9iBY9y1Zjx1* z3*C5Xc!T#C0a#^=B#uH20rnG0`gPyz?srvQLhVI#BuJW+hOvJy8?B96XfILu8Kobn zPRjK8fLpBnoc99(SYcS4E(M%|meL+nK~0P2PG{+Zmn=I`t`corP0zf~Wr@tIP_u{1 zHD+pPdDG`peQvqt7ypQaW;|6pd6f`#Xn_CG75F(UWt#4YkOSlS&TGu~?;04tJUsM( zaCr8~-vYYdAi2jQP|!Kw4PbklDg$e7lZxiScgu6i5WFLQ3Q9hRSPoaAt@>Wu?IUzA zP^qI#rt>gRCO7^9!Xck)02}JULUzq9iUCguG^Z9V_9h(l^1Vn{EGC89IX>im+2xt+@YlggG*$K){%_iga%Sj7=9!haJA z3hsZ=_^;j&rNFVvM}inSG|9#O6GDuF0cD|G)!$)_I_vMKe`Z`z59E04LS~_mc7EOj zncb@t_ld=7_qy+?En;ZxVsy3dU9zj)Uc0C69W>8I*jjy4pvlu-u2Sbh>N@9Lc%h~z z>EjrOrn8n>A)~*X**)WUY=^S8h&zA~ef3p{P3)%~RLeAVIrhN3t!kO_>Oh+lT9gS)du8^^CvIkuMY-wI zqqAKE5_JEsJha6;zk2Hogpo+=GB*lC(EI7Rni`jb;&|WI?_-+m9;x*f-v(otI_+F! zdIE4beVVOk^+#pg|M1eZi1v3jJ2WKEJ*yq|hP43p!T$_1@ZF#rTo~vExAs2le5tZb z>tEsFKM7)at)#Wyh{&VvU>T0uLSxlh11b*TgGM%@zj*shT#LgE`VDS{$sGX=iu#Nz zvfX2er+BpNe)DDndfJl_i3dgj+IhSaOc;#;-_}04uFY>j!IU>kX6G2m;XQCmDW^vJ zWJ~=lvw@Zr#lNzDf0t%)d8GJ&@6Jzh{M3=iHBDvbj+!aLe;MnNm?5I0bjl48>0ooy;N#ps3{Tv*d`+Q$Jb8(jgI5+CK%)d%Q z+ZXGljf3%?hEd4Ooq&KY4aC`-5CCXrdh0GHaRVDLe*_Vt{M4B{T38(Lq4pwY|8O-+ zsO&g@c#8bwoT(N84s%}q}4)ZmzlOCIBw5`wczPn3CJ{6jBlekl0gLB5mW zWo3}=RX?vT+1`#mCbMNDTWdS>oPh7cBR6AC?k^=cFwzv`$Cguz9lK2}{c~0;k%e5# z1oh1B&Y8XZ;?`DvWG!;VM)f;$fklU5KF9sxZD2AikZ>2sHjjgT_vYs21)#g^4FtB^g?LA&c|@BN@4nfI zU%1M}){Ird8W207U-LT3zOdXyi6ARS+4%3ksy)fOP5XSQoth34zGwY`}_Q zv~#9`bWflDFnBT}eW)lh9Ky~MteI$IsgSq1fX>uP(&ZxmD&8#?MP~edW?^X`O5%83 z>;}>1)t0ZJGx&CHFcDe8(e=y{ie*kw_)f4*@iqNvxRY7 zlJ?e>70S-Q2hnNs6oct1{n}_GeR@*?auc(zH1e7usv0#s{7KTuXj)fS7}Sqj1nzf4 z_{TT`K`8WMtEegB|FLiWLkMiZ`TGSSnfk&PtziIO`4wpr-uvc@tLE5vcZ^Y`OM;w$ ze?!1?QZY9P3_m0P@5W`ua;jom(VFO@#PCjH_=_!q8P}^G`A%MULGfaNd-^XdAUPZ)Hk#(X!Xv#+ zpOV`oCp7&wU(g`&1}MF%<*~AQjrRjglpKy=C7H_(|ru~J<)&NK;3xYhNe6yMl`JNU~HM9poC>h>!v+_%6cQ$9di&T!~5vZTKpqc-*Z zpUjx6m?hFhuE0jMwL8^#eCiKZRlyHLT%vn@0qtHHopN8@`{`C_*Ey{Nl&B3#HaFrSEuGxOXe7qJx;~SykFaFyg%nK`Y`)ibjr1oa zIj~G)V53yCyJS=|Tyb@fZ^C!l+$$M)LyZ4D-DRNJX=a($>{8!p))CFOI!(lSmwMt8 z0XROff6W@g8Wq14j2d})YaE#RdeJ+L<~iq=HSPR#>ee1-YJU_arG zHmwyDbv&SR8S4(<)_4|S7YFdGAim3@20_@x=%gM-Q$Htk|9q6MRV&iniHfC->*k?k z_#m3NVZG3nmL30E0m8h0gOUgmg z@Wz`yt(Rc_8OAkLR#I}+;n@8vj=$Z3eh~9ZoK5cv216KEdG>6_L)$bS@JM9bDKeC( z-*dG#Pe_Y`&#YbK#Y{7#yECr}@$1VhCwdj;%!oN8wmR#Pu)9)W-1Vh)wZm8+!{@vj z%zwg}Q+PXn=QhsWIjam{5->LuYQrqGk*@r9po&5M^$bvxg0`7@o99ulfNUt0`Q`|E z(l=QI8SmIxU*`%|<v1k1L%bSl=p^UamM$@(36gAz703 zG>e(46?k8~aFR=SkshXIwYz7f-hYuct1a;EM?>utE^5g~V4&?-x^xLnc3JKcRJ42M z@@VfjP@-9QvRgY2un;Y?nCWMIq9)yU>92LZI%8L0C*@z-7xzud>snH_V-s86lb7h--^#IwH;TA_IE<|)3+*o=?=5NWXjg9gP1^EJ+~-ouf6FC}XbY_q!L zE%=qx!dJ`|+EW;_WOF-#+dA&C#cY$Ou26k;Ec&TG0i50wlm=ScZV zO>Lre zOfmFlH-2dBb^WSoK zUGx%oinnb+dvUbNoPG2(W{UsX82nyq&v9`IL$-e{k%73Q55odaAVxy83sK%47K37f zR6taCTGhK9Mup3{(LwC`>Cg?cvt{herr{8Cnkd`oJn|o63|A$i{pj=F#UmnHqH`kw z?o$Z{#BEa#E1u6&k8fXitu8T^I}NvL=fjpxs%j0Svze>>ibn(gKZ->hbYs=u|NVKE~X^lQ& zFyX9X3|&4fFPul?=DrMl_^dz@`$W=-J2dIC*~jPq%a@m{DuDJTmI2O+bY9TRJ}m#= zXW4aBj@xXm?=$plUS{@wC~0jGVE5LJzuC4FujHPQ^j&98bVaY6)>NyGvXR(3Mp!&zB_4{7Vb1q&M?tb85j7+j-tTA4&Lf&Kn!FV=P8M7#9wv}6_U9n}No90_yQ;SxSB>>IlypbjbvjUriXkgA znZ#@*#ISfgF{q%Cd-RPS5qRO2!-DLv*(F`6K{?8-82z1vP12WIPIDScjl@gO#}9`K zeD--#Q)H{SUW;o*5A8nPA1pj;H&mogR{N=krbwis%vBw1iqa8E5TAORG)N4U1o{fb z{@U_K-Bn-4$IXOr)1PVKz?YF(Xrk=AvylBAqpG8WhnFdbp|RZtJ0D~lt%BUXr9r47 z-beMvd}w5Vun;CXdE-A%rb7EC6t%~JUL8NqXvOszYY6TtPzQTjO>&cdgV|7*R;N29 zWEvxy8ycmrn!0pJXt|>V@Kg$%cj0u?a0t71OR;Tqg^F9e{z{j&x-Dp)7h!|4A3tp~ zJM_`y?2^drTP1u(o$hBBUSX*583=fbbB{>c4J8G$6!(zvCs+A?q3x#g#PgogjI zBkK|OQ^--9nkmTxHv)!*Ss#O;m)qR(;VIIBmdL}Bwz1eEoWg1Z7Qu@gu*pgMBKdVP zYx-F-${V4ZJCk=PkqN($A;q%| zI1yqy`JS zt6!?%(EI&s1rtuL^*D0kEVHY$ELY60ULh(ftHY_*6&qDjQAEtNd|Y(>f-gKFAIpAM za?q)qG9_$bCld~LiPa#@T$nfL_dQR=-G1qQ#Em=d3t0?oIbO?mumAjDVy(=jNU zGIxr|!hiYw{r>s+^tmil#1a+qrr}LD2Gq6J4}d_zZ=e}#5p`Ov{FjdPKGjdS$H8`| z&Ajz$4#qMCa$n(je9PgH98vgw?uRQ&lzwB=znF=5VyHGP*p`st78kvyg(G*g9Z4uz zp5bg>_{+DcSj|>=u|sE3p#^~gL6`yg!g}08kTs9aCvYsWD|AQb{<`}qX2~*EQMtVG z!;Go2!r#p7MeYjRyQc!2sIQ_QB-gRFe6W%rNk}?Bzkm3w*=(<|mh`4w6%{<4QHhO_ zFI$P90r$&X{pG@2Zt#96Kynd}Go}9nK#)M`U3S60cwOTo0Th69CpV>HE*fY=8SZ=S zEQJR_8*4hlKo`m@fD<8N1)X=$O`V6V$cZ+f7uVV7`H|ecmHH!D5KlagH}gaAUpPfB zDtfxteC&Q&DMHYOhSqRP$Obuvka4v>QxbLQm24JWb&0A?==!E}=;u)%j?YO_AgmSY zZx=0o-o#~B#XQTE=%;ZL=*{j zGr2uiy<+C;eSnBSGw&$XNz~wjA!R0{b%X3D*rK$#0GO2qK-w`7{2QP^|9%gZ`fxK) zO}z;LnI>l>TAoGE4{2^;AY`5}Fkf_>#XW8U~cu~EW!nG@>CjjyqszLq1+5IJF6Gk7A;x(t!k6S{gf4b}?3y0-nu#NS*jfZ>k+hBh1LFUzbug z;!llT&z>emgI_ZTuLIi2g(M=YwiPgrV2FJ^)k+b z0znO37VoHbQ;$orWDFr>E(dEh#y)5HUYn6!Mhiwwh~HiPW&6?0<#x9JDPWFv793ER zj5sEeSGcdnr*V}@0i0`4_MvDAj_j+plWcGdeL zhT&ilzmI32Vp*Ttf4e5oSw_x<&A3?rbQT7%I~fOzC^Nr*CWLGomcnj6?qBOmo8hkd zm1v{&r7$@})H~(Kory`z74Ocvay-!`hmV=lz}>;=f|+j#%r(UaGgTSge2|U~CD8L% zGUFN@DV>=%=d7x;k(o}1Wpk{*tgL@>rV&5)GCTgI*Qj}{gS08xD|z2bD%$tyj@G1@ z>(9=ysCG-!VNf*sG_F|`UDw;*c-S{(1&*m>(=e&F;}K1g-Pko7v-kM!;HknnCf&olvshlRKNz zW%zMBg`Hk$LapFU>Fj<~mt>_CPjEh(B^k6tUbXpY!e+7424Ny z_~F+hW(@r>mM>4Cahd5_w8_OU77hXiBw`{k2l2frYxKe?HSyaCIzx?o4y)h^!w%^T>Z$1TaAxMVatu%d_@SsQ+Kml;RAAPAbv`g5jL)*tJjR# zjw{pLp6~)Gl%*-heg^E}-&q7dcc5KMvbD?lNF%W$ROZW5fA%3rGHK8uD<}0_v@cRp z#&V8t;m5p2wG9$4X2-*$4(Q%jJ^8^?gEQ%z85DM{7jJd!lr8NZ+XzEXnIMIn+sWd@ z_CX3|TxmbdRKW91_GF0ijk*YtfkL4&PC#|+i*1ocNy#>6HK}eWWiNH-J&ws>j*Aym zGIEn2sA#cDX}hMzL>=Ip#zY|cKrgZw$#E<)bip;|&O5|?Kwa^Npkv;LI>&GU0XsEo z_h16+*9i(>Baf+Zm4L+3&;-fmMXe> z~ac-RL|lWWEsl_UljPY^zBEB23>; z5wh)^L;4Zbao9n*lefh97-$xhFeKoMQzfBsR&EG|k6fm-G*ax5X!@cuOMLR7CA$45 z_J7i$yvcFoFx>Nc4>>tAH+UdJZmF zOSV4{rMVly(Z=ca_6cFD1hN|kqV;*1Rh-#mA1vNU-V_L$u8G}V1fHnD8AB2bg<}2K z??t)hUN(CSwR2*#TZ4iyP~Ta$9m~o^I}WHY;BTn(#}pe9!p?1)C_D%I7)qsOUsD7# z$gW;aVHu2+T-Qs|C37^he*`Yp_hu4wYzN3wXH!%x8n>BXDBJJ#7W;Ek{tdECx_6W> z+C$1RsotrE(uJF71j@m{GUxm{bTw$~@!5r9jCY?6whUPypiVf4r;r<+G z5SSJqDcP$$2Quj32CMvjF*4Ja0LW6I` zj{O1$0K@kjDs_^Dl^ZV09E>kErI^v=-v!ntyxMTy&`=TJ!{y>N!7;;M#V|w`divc$%cJ;~7^O|eoF@g7x$GqAiGve{gstZ1$JG5SMPR}qTcfD; zXaSw!$Y>iA-?@fOtOh4G*OxAL{_&wYo$rN^^*o!cX(R%hGG^Lanoi(F7wC=&PCNRN z*z3WZ#8ec?PGV~7u~J9gDGZ!p^3KX_;P!!-Fu3`s)WbK3g)lGnRLS4%U$s(Sb>7at zKvd)kSL42Em1Xd?cAP4m!q?4Msb`}B>Z}VzPrF>}pNCLOGA$E0_RzDzy4=-U>MO0(m1cmDNr`{N0FZYj`XeD# zGWcCzS}XE$5YY92pXgTpG(Fe3bIeWLcnNpOC2YxEk)uTZEL)%&ZpY2d5F)ssHKP36 zcnOC?XZ;cQBjHNg&WBTWo_`g*!wMV`2N>Ru7D1O1fmZSqH`5X_S`!biBJ>vYX^cy1d+)~b+%NctHdsie@E1te*f#7NoLc( zUYAEo5w6*!camkj%d1X03ch?YvKa^Q>qz`Oia00fRsCQB+d-PYn?|%r>@4ONkuK39 zKiEZl-vt`N`V?n8EKjcjy1d%*5g5XmeAF%Ji-q(aFhuWS*^nCMdHh&Sq8cl;#rk~L zy+@A918XL=I^}bdw-$ys?eUGkk};8aXM&O>-VuKh8x4W|dz~otIO&24m*+UP=aU9mapGgG1}0CJOvpLyL?>UEduE3g?F z6XthWc4mKLf}CJ0Z1+kCXt2Mj8&n0I$2yfPD%jkw5?J2r-I~iwb49UMy(Pc4W&w=o z!~^Dw`aHp0Ug8lyk?}=fH;~PE1C^Y{89dV{&_Umum*A1JMNYe-r-4?30~R_ ztPKBaqaD!yX0x%P|IbSQ$4^H6S5)E~IRCT7*{8F^}I^WSK$&i@NJ|Kq1W{VR^(`Qkq~ zcH^(}+2Qf!%18fV!Z;G8qe+$_k^rX*Q z4Pr9C#7ce1GScXIkJkt?Oq|5vukUKycFE9!^hPJz1ai2-`xl9gHMWZ1p*%@N=@35s(S{RBu)j3n>Ow(hI69t&~N!`WbHujs?zVw)Q_=>=AF>MW?ZuocmC<0{?-72PkWcpHhy zJ@z2xm%eihB`jgGpyP9V>`gdyr-OrUwdHRefbYT2gsV-Lc&D!}qYic5LHk1xBrxgXw>Zhmsc)0Z1YSK zW+cCmXxO~Y%%X{(HKSgBjESH2y-ww?(OnYY0;NVMN>U{#ENGPmF3_@+V9cWJ9+_$; z)0AxsI0`|O`@2b(&FfBIHKXe&eA>T`SY8_wq&YWs*jW;|g)y&8R`WCy^x5 zTY)y)$Fv{x%Qkms|1KqNW{3i{*y*diyC|TN=5dQY{-RBsi5X|dqql|(o5v%MZe*qR z<~9Oxyf+t@`YQ64+F}rBbrzkjEKiRh|2>ZVr`>1?`Cl5H zRsQdnApeb@O!`|217Od?04NcrOjH3DMlo$85-|fB)^mWmNTX}EQoTExcOpYTIg3(v z^7<(w`Y}o#ssA9WM?Dd;_}9KVG@houUS$p8sb5 zlew4ngcnEV+d}?^x~7__9B=9FDU~98}@^RA;Llk zFJtBHQ{Sr(K$Z*O6GV7|VjzG4#40mKL@Z7DaxbSK14P)?MG=AnFhb;ZDL@cn5>i2= zH+6@W9lwjiMM-!@25zaA3AJ1=^Yss-5ifKYd7A8U7hL8rh~jHr{3>;;9|D?3l`*~6 zO1o(}cJwTqO7Pcpjq1^UIq;N*TRLTm+LyeUdX23Xg^p zbd5Ze_F)>kVZ82{vS>3}Zp)_fW`L9|p^yi`deYo(Y5;-YlxK;#6;RpzYa>tR4l;Q$PLBL#pqAn{~3OPrJ|k1V^$^3}%eXR+*O=5Cc5%E>{;)%(z1*~Sn|gV;>|yjK zZe5d!OW{-o7PGs4u)f#%R$N5mt?s`7_&?oISAC_ffJMLVi<1$Y;}lbBqo>pcgmW~4 z1QV9567Z9J#I1m?4!|OiyKTM6ol=CUG&aqK=-(4SWQYJF4^Kt49D*j0w1wQf1dl(h zaRH*6Q7#ZYOC2_pP}QU$K8x~EACbO@D6I5mVQjm_^uF9EF!i-RNx>f?OmUsF=bI(e6SRg@(A?9KUGcXLf zeaY>E^}74OiYq>o`S;idxvMFRwo%B@okZmAo>k5GumWXIbFfniv*71XdPmf(^?iDl5y{kiG$McyNL(7v8C0&5qd{%L`v2mTp!bz z&)+bczZ7POOdW@uWoQ8`zU_w+3hC_v;tySv6gEla9KyBv^~&tlOtc>j9oU1Pc9f%G zHt6z5T_*Ulx0)PWVilN0S%^j@wmS&LfZp7s>Qb1`86r8&qWZw8m$6cpF&|Tq#SZ|W zdO4_Y)U4x6bCeviz?VOTkJCX%EBtn}>^=7R1I|2D7*;Die_ zD>zG`A!H2{^V9O|9v77USid3>V6s4m(?`*}uaZ&fr~~{+aKwd!JC90C3_~NTMmgz6OLENO+6h~qNgcjY^sTpB4vab}g^XF!fFdD&4IoUu z*a%3S!dZ16Mf9z6rc2Ef%|xkYV&#;po>Et!+j9Wma_(g&vg zC$o>~AEE2k;coy0p!u^>&7a|qpVwaZm3j^8%=P}->z|Z*{S&BZe_Gq>E49^!9`ql2 zPzHY}07}2-Jb7D?>VJcIlF_sK04$xe+y&zgd~Bf?`b=w>{BrP zdFk8@1Qqs_)r2XfEb#y?`%t2Pi|cIxL1DGb9eP5s^i2m}!`db5da1S353+W76CbRe zj!Pv|Y+5|pM7)&6Pj>hBYKX5emcT}7;V>U|6M+^e=zx$u__QzXB5~{%=&%S6`7M^W z99fl}l`F%B7;SCY7W*`GCEx+`QBK8ughg1&30VnJZ%bHZCnoZc7d=fOu{`ClF$IG_ zj`&utx)zW`;L)kKwWOsm;@uJY)R{lW5i{p=)YW6#F-1~*;BsMISb1Wp z-p`ySSbh)z*}0%H)JyE=as1<5GJYE?0(ZJzJLxNR0-WnMxherhvr>lSy6d-h(_@QJTuq%pA|2(Lk|W zw;M@x4d6L5^?fgIH`karNhf-n5Lfry-Anad)O}dg5YRz{3Y)nmGwTu^%%dC)gQrRN zTxBqU5_90pedyxnz_oF^bx7p31@w`6!;Wz%4B!eA22_@Fn93eWU|Q z9Ol_Ii4^%QngR7a64iw)&UR9L7!}OjwaC{n%MOy?s+5LN;*MCJ45HYOx+JL|NK{J^ z0zF%mV{6N2w@YsJZEAD~QdS}t7~b;hd0IT^1h@5=x&4V;bZqkSz`t#P2lHo!&6)#>E5WzqS)1y@!qVQe}^c2D& z3(Ru3cij+fma!)2ei2Kb;MWw8ep zQfKD!m^=g&Ll-NwnkzZ+NufH83hmMhB5Zk)Z;|E*FFT*5PM`w7YDw>FuA&m z55ll02E=CE6UrOPE45w!iwv6Ry&Tm?7u+k=(Bgvj zsb}p(=QAM9NU3H-WMqCjq)sjar#!AB1*dN7-sv7EMWQ9Y6mtD(`f%rp`k~w;ELF9` z8EYDg-0>cO1OcUrmk!@sUe>{I<%$+!W2pDo^j>(%&0hE882!7nR2EN3<36^q|Q6l_v!z> z6dRrsWi)_juzG`@#Y)ZMg7j@nh72XgR#fURI-tr*CsksbA!30?R3#R8JradfxPu^W3rge|Ib6r$mKqIg`0hUHGWV@qNcNd?>N}aRav7VHML=70;Q9-1wzi$e>lHP`Jd!=9S zZB&H_HWF$`Dp;Yli#oN2yYW^R2RP+$4D-13V$Ns%OqSFYNkGc-f3;dX|IfzeR(pm2`fT{G-fG`F`w!njb@P59tw*sY18LQ3Z(^n1K(4QC zn(K>bR;`eQbQT>c`=HrGIl{})HH~;8RPXh5L|8lDB1*D%nO;Jo*0&zYte;v8QwCf` z^X^mTPOW?XmjQt;$^X2u8RY-kXs+`AyoB>Ve(KXd3@}mcc`>*|skW`l@+q5#M|gw{ zFYzb&6B$~p0iX=N?$U$^w6X?Fxu3=MkYIt)hGTa)g9mC zqa`@N68>+q(+KiEZZx-6^#9rEzg_Kz%Ku23E#(2fCQ5xx3|Hskvf!W8V4ZPwca*Ec zB(N#(CegGsg)5S3=Dpbm+eIjC zHD2cVA0Hnsj*q@S98D(Q&yT-c9bYc0vH~UNzugYbe`}+?y8oZ^{O7Iq!x01N3w1R^ zLBby|5B!{}gDf7YV;m-NmDKOnACS2C$~;Ae?iA!p)gWxWV2f1OC6c z)mYvCFYWw~pYimsxPs?C|L3#C@%NWU7Y{H0Z*4XM{=d`SSe^f8JpbNmKisj;nyq`& z2Wi+z`U!K_Uw5r@E7*ZFx%i23g9l@r-2}D+*q1V22mHN^JCZL>eLHL&q4irGzB~cZ2~3QiXhv$B~r)E>knOqEUD_5 z00;{+Q^92jDe};#$V<8mJ*x{61SuVuYaFJhx6u|NK{|Bsh){>RU7`d4@R zbDaOl*~j&(#aX&wXQRp4-ElIX&z6h&t9<@j4SxO`TbrHM>ij?3`4?8dA_*|(KX(_E zUiUX?gfN5R!O%KTi=u+mf`0%$PGNe;7K2_C#kGl;Z-5dS`$MmtDF z3cWwmh@HG=&>&E1Vek(LrdXE{%Tk#Ku{c>GbokhpRIG9}LmU&9LOIBA%3zN3P%8ZN z4NkQQmGc_EjX2~FYurQMo@$;OLdt@`($%mG8vggMwYWSI6yuOWZc3@jJQz-Z_yE;h z->5#F6dLm#tg;>auvc9`{|}BY4LP_c%U6T`Z#7#y{=3oYbXNMG=cE604^$!jj_$j9 z)hzog#{rx_3)yD3e1F|H>+3&J;cdvGwLT|6J~NfG0pHIe1I($(V3~W<2pdRTJ~UMS zC`<{BG>F31pk_#NFQ6sUpaTR9Gi@(+O9L4E@%Prf5C+5;f)EDnRrhfJ@5oJHfPX3C zGQub+12vY=B-xp^ta7D0n$pti!zsnp@ZM0wjr2po95UoMM~vCW8!>)7Z3xwU)f>{# z5c)h6MVsd885t6Y_x6_JbBX?-cs&IXBx#~5y3(1?g`LgVd&}xx`>5pX?XCZo+FAJ8 zQt-PVv3(yv%P3Y;ILuB&a0;2xhl~{Yr_h1rHsVX^A+Ai3aCCv08Nt7oz&i|x(=9!h zebqDw=6CpN*m0DzDjQC8|;xA9+bJD@N4eP>&Uzs$3ohA!{!>aFnr^X zLf?dy?8XFaJ@c}a2p`*kdTrVyF8{lrk8T{BVKqk~(^pg46I}Cf)Cm6d1u9Nr0o(F2 z0W6)aFeT7pClcOVLVGFt`Fp}S+6|X8t)Y@=gWrVq`19PTE(4J3nKpef+i&k*?T>=g}nMDL7fHJWHkN%tcIg zCCtt{^Jjoq2!yQ)rT3aYY;@T!HHRqrg@7etM=7qW0M)tESHp`WIGQ^{_4deRR5Ww$ zO#@D@oks+@E%?sydQ4b#yqfS%XN%@dG`*;p_ne!mw?$t+yxygkJNFs%mhUtsQ;7wU zTzaJ!ad70!WAEIU=FCG6A^%-U1%{I-pb$HK_0A)IDU2fQ$Wwa6E!=fqeU8>6>G?o@ z{LXu9_t1%<;QnFX8LIccxb8QjmeANx4aSk(wmU<07M(g?aHp@{dlc28uMgGWW9sD4 zS6jLxj`PGHr`?J}_3lq>hE*<3f26t*1GY(pfu+JWYPTk=|>=Fy`TN)Rt30F?U~*aFk6XD-AL_QwK{ayS!vu zL%=^pExD?YPMec++)E?+IOQDKFv79>!7ag@4!dAjr>E*8&)szhLJkYYV zj&rIC%gxwEv^d~@?4#x_2gS%F02OoRtWD-kcnf{$e7>+aQ|^ zyDJ5=XU0{i%$)>lzn{_b)2JjVI8Um^$UV=EQo0T)j*iVK%#wrV<#8oj zg>C*IiY+%Ta_K3zOX4 z1<4Lr>I#+vd64BmF3yXTZC#t};7Tjq=Vo_fq4mwQZ+U0EL0;G{x_heWvp$A07QHuY z?*p9$<}87Z3dISgjODwdGpOM|@=f-VY29yUBD51`SZ7OWDt_W?I?UD-dm;^x^^V;G zkk(mCY1rY_G|C^g^f8tSN%&Z+&c|rpaBUTQP#ZT)W_ZCfH}qAoUv0Xlshr53c{oH_ zz|e1Gz)L0lWNeUY?aq=u+z6783(Fj&t&UF}}`x#$A73B5o0KM?%UfH%n8rUv-tb>cU^w z-JEZ-`^u6%w@JD*Qy4VBCW!e{wPMV86uchm-gVL(v_avt(HhJLM7MBi$ z)qk)dHM)F~KAgIdPZlTw$uM79%f1--30nZ4$v)l+RJEH3SYmGe8*ciA!Sw zb@E0U3}t=l(nNjWb*S@GDI*nTGYzso>kcE&Ngnm6lTJ%R$~JGB*9)eZC2>uCDp_e% z3ufu9BEA4{$!^7V9vtG<04dS|B&!245+<})H|d^M>P!|a%>fJY$w|^QW4|Kqb(x!r z(7Oq7XCf=&tHVq=H;ujx(o+}sS%?eRcE*f)x5QkF3=)o)<>o<7mKkQ5@P%JuZX^EX z$`UK?b4zS0;jhc0A=PzxD$;E#?05#-4QOv$ZV$D2c?wsT-_WYvB$DNO3gmQ|$pgk` zED`fNIstI1^R>g00GN3&X_hjcG>g*Sl3ATYW@(Ys>)}8pVJ_xk(bn7b<4U_e_wz$0 zwn_&jJTguhGg8VryPInY{oWHnd2kwuIS^5a<*9-wVQsoB$lKyI0f3?eOWfD>zWzL0dfB=s*L_;I~5?dz!o9P!oi9Zvd8)h zt!Az~GTAR(I&Bkh6{B=1W3&k2r73sBeyXr!6OCAjqGB3OpN^bSX!ekLAexl?Kh#H{LST`;bb&4Do-NM zh4`)NGPHD}??pmXi}-4^-0tKlx-@q1=jsoEHoY-rLp8UM_p)P?5E&Vw5V#Q+6>bD+ zz@!eE%TLiEY#lErITmZvWXNn@!Ehir zfLt=o9N!cvv!);B>H|TRD*1~qkM3LsZ7@7GefN7|-u>S7bja^bCE>G89{XMdZnTL? zt=LPG_EIK&^Q7C|JYiCZ0;)@L8nY#R{?|#j`>zwiPn<|6rHsa;je67R%Wil2lHB2! z?iH!_BJ@r&;}q@OY#d>cB1h=L?5w3Do%x0=-XtyMJ+qvyDbdt$VFmT)AWwEkC9i@x zLPXX=fz;cq#_sLejvyfa(9oJl}*SWBk%#4hl)*_Q}4*!ymOax?W4 zQyw{mI@&I%^suDo%mc$+9!rZA<6Kb%iWU?S4Ae zUwzpD3TF0iUAU!=$|4QH--@#+G(8o8++OOUWpV9=PJ>woQ+b$nfVvxk!?;I9=f_rD zV@P-fnd(%IYhpbqpF06TW`ct=_fpd^qNVDscDpO=>fbM1R}yg> z?|mU#Sz-#?;D@9^^5_0-U@99)AxK^I$o81VmhQE5WE$OOlFA!LZdhe@|Gx>ric=Q< zURaN6B2s4)YLQTlwno(cpQ;;|g*}#=qx_ci_fCop4;3{?pKHW8t2aMVUqeO8WT(}MuZ@4`>V8T_1wFp&SSRXJZv_|1{nelj`M!EJMYUX zA|c<`Zi*b?-HpK*krBV>cH|E1SY0Um7MD z%mIs;BQAHB$H(37_*j)!?(}cfp0N037)vjM&CW?D9<)WN6tN1}%At$}9r!*c1D(=8 zraGp){;f0f?hEZRsSX+ zMi4+UdL`^-j(`7R%l!FdwPjY~H2WJT4!YEK4JGR&u>z)`+-HJ|<>tBzt|8i#{_a~v z`vuxf^^sZjsqKF}xhZG&zT5w~Uu;yjH2XO1ce~TRF7`dK+5kv#@MWWN=VK^jALe7Y zx6EU>48h21vkhNQAmYVg&vb%@)Z2tEN`5F^N?Y(=x)dbXiB&=@R6v^Nlz4*+tOzuZJb-?uPs@i-}Bb%#P>=yrmOSkn3M` z8lQVNbve-AFikq@FzMMlT4v_r?GYQ;T6X5t<609b^}o4kApObuOZZ%T;>y5Vm&w?* z3L1~9NeBjpOSt3~m3Q-2@5N2X5;neP)p`_}$m)KH?^*{W#)O7g$7H;w#{n>&ybppO6du<`$lgI_WYOvzQM zmL;GdSiT(0yxaX{i-ita91-7M9<_baD-W-(1qlu z`c^q2OWWd7^Wv=|DfdT7C~RRvP0F&KH9Yvgs9oBg><~=%2f_jW+|#1KK)~LKB@XpF zkwroN{A$Zmcc28P9rs8ajY0R#XFa0$ey=+|x~zk|epC2=m9VhI_>~43yeWj-&vqTG`J>1P9aLf(tc`+Kb7FCHEBNZfUNw zY9SAc9RgHGM%Cb7@Buuip&ZrFX@WqEupGPla%zX*?uQbY>@H0P&3A499Wkrr*s(J1*x;jB{lzlA&?CGfwq zYr=oK&j0c7@c;2{p}*g4ZolYuw_gB1xAVf4|9O1Gm?l-wW;J*ciZCs_Qdd}9cY#g# z^s?Li^pa%NeJKsBUf}(VUlRJ?AZ|=2B`TzFrP7VJL2LH-wSHuAf4OF;7%hCiw&8WqA=#mtW?c;8@ zeN1FRvSq)f1-LB8-KvGKv!$t3n)D;dFca9Omr@tP-$n_ulgPH5qO)HhW<6(?P9#MG z&K+H-@1?XA)CiF-5AviXCpW+<%fkQd%YR!ttz85D+go-1??(XtAMYFbhh1Gv!><1e zqRSc(&oR})f_jU}38j1gCtw`}9B=g&#NkK0;3Qwed%y&mx!75^_a7=s65W$56F2Tp z@w)ld+MhsxM9g;v2#GcpxaYl!dqiU~Ul0mO6DAJb#(?nA*WAJ-e5r=jNsjcjLMYG? z^9WaC_Pn%@q_vNaESm+L{%0R`d&kteOS7i2IP@@sFxjS#bPvgtjsVpj!(BNxD-4KT zoi>2@DFlwTL3_E^<+sQ;mf`<%fZJJ|WH%55mC%1%?Mp0l?L}Oxkc~)1Y-bZLAiPz7@qtG*xKYV5J)Z_+G(Fe?IgoU-kl_Q^%1igrKPK`1cq#vG1wR(Tlw!&1j!; zXR^tGEo_6vL29LX0$MIldv_^EYIBBz5d5L|OL+d<*fq_LK|cRM_g(9yHPVgkFR{~H zUkGxBZeQD1-NmH~K3s3WCU)|o@>neF>7YB zpHaX6CGY>u6~HC$|K8@7Y5%cZ%YPry`~UGSU;jV4l4%`#B&o*ceYY1J(F9%Mb^GgB zykNvK_9Qw2Qh&S5hLBwzd9+NaM4Jn2ag|o z^ZIN!!Bcb&AFjPo5>LG~^zm|S{|B{d4~1cu+=?do`vjwGo`^P-Bp9MFT-*O)bCMff=3HIhGPCPUm;#r1$FHXD!f5b!h8+p({6TYRvbQUhW1j8iE z=0g-gts#omGVcO=2}Z*S_Pw7re!!o_yy+xfVLw1;D(6HmJQ4m<8--dCNC1{xGb!nxJv^(^BI-kKLu+O$BJ?zN4_TkU{AFlWLOnp2I zQG#KRH1me`Dbr)>Xh-(UvA~1MEjTYom!KFP$8CvEkDAWJ$8TNmz+Wz-J@c-aANboRIPFi5}4O_ACFbvM|bmr{A zC|uiD5VU^JyyvzKzglPNz;V{ATVp$i{0DYvMLKUK_;2m*nEpTQ?b`n1Ug7_X*PjMi zhLapt|6dXhKjG}@aDuW8ObZQIm*@;#=pzBWUS!geI?>Wkk~nF4gEQ(-@HUBuI8Ec^ z1P!w|S^OHLnM`Pkcgw^^Qr|s7OTvHqm*AI9|L(<6hK3paaTdkPiJsr`X3PS1P!eRDMCelA9PxG6Z1*4H#Yy~bDcA{r(1(9e$FTFd^L z<1~9S8abE5S@x8)74K>gWjGn3AvSJVFOEjRn6W;(su5Q~1{7xc<;n^u|lX^Gn8C$D}c%JTZbIEoYOix%tyw7H1m z5Ti(IF{Id4S}Fa&N9oDsc`I%$o?!oIA=)aRwH~~$UNjFweS!3HNlT<3@Yfrt{3w2!tO&zu3TUonTbNRf*w~|TAGoGQ8%3Xf+0~7|)_(!-mW9*^G_o6uS7C7_bOPt7Sx|pX6 z>y{BfNT!ax&hT{Bvw}vxE;P)7OAH7g&Cqlv+G+*R!7;S>>dhm9HTgZBC2>Z@O;`9L z$%Z*5>X`3=?UHfH6?xUeg}spCcD)owzN6g)rL+lUY~&(>?@<{1Ef&DX`V_@iA@;|5 z2}Vh{pqM#TO9;p_^!XA+euxvT2E8Qo6x5P9^d%xd>z4u+r5!9oo?`lHs6(q2r)WW| z*?Qp;ILp{UG@EDV2^xOV1|B9DN52O?j-^t#=^-2eU7~jtgrP^U?gjSz03KYBTgDNA zfRt@NT$hW5mrX#PNgpVF-}!J-r`psLOz0Ku((>X6k7F1ih@)31LSubfQXFOE&Ybx( z{iudGxa613D8>=G2*q=!0n#W$<5YkT`N`2F(6s#7B!#Styj;AQ%DWY6gl?yl6xNp- zj;t?4qSMDZQj?0vEK3P&d3iIfaUtm~H_XXpfLhj46HzefahfG_pmQ3O66iQ+v!*7I zZhBv&>+M}1b{K_02%fy)up?8+w62W0uGjOsPh0=w4RWmFfhc{RUBvUqr&#|g$R^$e zN`oPPzP%tC#mSUhQg0q*LFirO_m8{|qj?z8);`Wq5T=x7g(6Cz?(>le42^M?diXOQ z&WR2n8e(r0C+a+8*b5>B_MWoNC1QnGA>8l`3gJ@h(XN)uHK_$ple_vCa zNgYJ^O8j!dynVqb&d#(45&9Fqmh5kcqL=6r8+}@~skF`*(-%&fKqEurLw@7eJ62X- zqvGkUxj`R{U#0iUee+hma+EfN#luJbbb{(r{l?jz8)FEi*9j41H4-ApV#q)iZYh<6 z^evA3AR2ecoKZBymK)CMyqPD3)!tm<#PYd&5#sY$rufWCgxyqkC&k$-!gmqwD{r9J zA>0wLR`wbU5!0J_HbDt)uulbj_GB8gLFn3!w?=u#HSepp#@OREZ76kxa+i^ReE#aB z;b+s4c=G)@jhT7Kch^HGMKY{wKWF7R0v&nV+&>r|sB zIq%0{oe0>Nu+0tevNX;P+oI!i63;__KyCt^m4!*yWf zVzz1p0LE_GWl{)RSs!E&njpdV&m_5kS~$j;VA}K7LTArXij{a5sSSz?Dhxx6lF|;? zEwe}m;)sIU0A$}0qD&dZH};D3+-VJ6%n`j7Hw|^xeD8fUil=q7NI=4NXaW~m(J5>9{!x+$Twy86y4-qlu7_$e5;i~t~dX- zEDH+le}tCdMiv2O@xQhW|L@(_R;~Yk0Q(<@PS>Hx&9y|bfsY1Ynt6}7QMt)wDsZ8p zcp8fNU*QbjzIOx*E~ycP;SBV_1WeX5O+KT)ba{oGrN;s_Hic1GEH@^=mlaR@3l#j3W$;Y&3pC^a4*( zf5!UE;HBrCg0Aoh4pV$J!3h>M=${GB>3vq83Nmy=L)M}&=&+_)63^n4apw}FK1=Wu z=_lwNo>=&Jb&OLK`SJ9O@Ego(!F%pC*1hKkITvCDPzksmnp!Bf|Ba_pmip-q;eUI_ z^#9!2sr^6iAO2-AB0vk61TjdNSsrq3dBIDgjV1O=`2Kx*Jz5h z5|~V6eoli~A87lO3dnfTwK-j|e}PpIDJoqT6{xPcz*JoS@77$nqW>2NBv-}*W%0lF z%=mActvdeyWAp#goVX}a;DNl_%i;*Egs6ry2BSPL4SejylCe{nCl=|bf*%%K#S$+p zS$xjE=pytb{H-ntET@SH~b4nOcZ(K zdXz#qkxLeWxC@{e=#ya>?32IGDaElyZ!2dAnC%()n;;{(NiX(w5<*cR=!8lVWWf-D zYFFXMNngj*vc*bvlpqzd7gezeRmwQfiU7@3@QUI8GtYuB-5BExs45Vh|AB&RW!LXY z@V~QT$p5zXYWd&2)Bj8rMGu@g-%(ig;|QBNOQ`HpV8N$Tq9yBiTi!EO!$B zVRNs&UB`d^2Ht;D_%ADfmkRuV7arBLdh;1=MokN!dgl}3B?BF%Gdv7N0ri;(vJ~n7 zm@vyF2=`E#^q@1n3GI$ZB~^6ptW5jfCQb((g20>KV6w~C-A?s zwY6o&f8MV7{|A8om7IVjou1GGijOw`Q@1t!tvJgT=p&_C_K-HY{8RKn<$x9eDEA(F z?yXV7fi)NO3#&QsYwQRQi4Q-bxgW$oHoYIICh$oLszCV7%|798@Dcm*Pjd!5>dteo z!Kfl_sPzw&y0Nr16+M9TR7DTdf2^Q?(Vqu0PBKLy>jjRC{YiuUkRNklP(^<8Js>}# zIAi)GR+|w}PiSwsK8b%;wM_gg*EYqG{5e+$5jOG(72<2&RVAO#_IYQa8qBmIum5Sh>Z|6KJlC`=W7~?KB3DW+A@4La8@J$tSQ%Oqr*J|9*6NAAWO;-6r8e`B}IyaUt6atRO1yqB5M zzSPOmVR&g}QtI$dcftQj2XcGy-&%W{M*hdv)^?r$;{o`;lK46HpgEQpNJdPh0&dDp zAITiu7Yhb+p*IPy=+Agw2k{A8-wU;xvlED}qQ_Rr3srYCtx$19+czi#0RbZZ-NQ{e ze{WXwX1p%nZ5`$3CiBZ5OP0*>^3Hj2^fI=&=T(@yDFIl0@*@c7c#apcU29)LhZR_&Cc6)QHRpbBr$Nx&yIyIP`5(Lli5U`m2 z7suhdkyLMau^u;7l${#f?~pAD>K7*)C$OC;wQ!JmA(JS%15o8PNemWA2&9K%5f$@z z4{BXUSQfiXZbOvfX#cvUI9oWEaoj!iL`*-{Hdlgw)%R zNeUK=XayZm8U8o&|3FH>+Wz}n(f<@O4I7qHO{{9(F~dVl^3ClSMi&y|HoMaCwjpM7 zjeek$^C*aZr4*uNNjicRa<q95fM`n=i7#zO^`xZNpA5KYI6tdwVpaSbiW#c)OpxZsf%mpeLa@ zV?dV<#zYAwp&f@9G)TyU*j%Bqo~Abo=P5+PATWVK9`GzAA?S#^1IoCa(vZDVKX_!& zKOpYGCy1Fg!dD(j)A^LdE=fIf5zncriZ+1AdL$5<7ta&&nngaukDI5US4wfZ@4ZX0 zH=T!BFbjDrqIz1ya}t)U>0Qh-I$jFq0FeBKD5)+3e~v;v&?pYW_=*gj%tM@Z*j!sr zy)Liws{0;PWq3Nv7G8rW+oG7BS-Q^Zx3&7D)>fRNu#Q?g$LSjw#RI|Ep5ElZ_eqM`JT}Eaor{99j=MjAhX#ZGgyDhy6ch|!rbW0 zqyh6u4C5)5BHBTR^ig=~oj|a<&j^A(cewtK)Cq(+D+&K;_mV;q-xV(2#Y2fUfP%5(KHy6oe)#f zYYfp8C&)u#8hgWd9%Z0Cy}+^t2T1+Usz?IOWB338xY5%6f9;;M!C5{J19*gPS7B4LM)*JVmo?*eC6aGy8H z(X_lq7(^H)>rIdNfP0(ZZURAt;C)Gh5U_TW)_tm!P0{CII-k<9*_8Oq{$N%Bf8!+P zaC?!&R{*+xj8n)QHcMiF0%~p`MfL1xef1_uJf+6(P(M|c3|0{Y8`9gu+nfXm9>t#v z8Zj6l%_w37fPfNmCbWQecZNnN2|C`fHgyy&@1JpQtwM8GlqgfCfXUu|_8x+mLF!+#P zxgaIyB*xTGZMsM^JcXG=IJ=6IPy5~lUc`}4F(W}i2DT#^?HC&6v*2-2>8=sQ%F*JC z^nR1cv$)XI^2R!s2Kt1^8i^W*S0s;K6er#+L_08@5cY5y>0q`w0CR$|GoQv{geys^swa`&-YVVMmR@hTk0aSqg=;4`N{R366}W|<(Jk{QcH;`oEk-NOQvuc>f1moF+ra{5 z^uHPZVP|V=r-uKBd;cx!PWKaCs^AxOogvn1$bMw-&oTXNpn%f%-xB|Mv$p?yK=`NL z%o}>}b2knsNa9)r8L;Zks#1ie8dKa4{HyU_ZiEHO;y-S;%=iy`HU0lE^gj#OY$4%U z;6>v*kIH5g=z#%ba-MtUy9+_KU@Mkz!IOw&l2mL5#@Q%0ZW+_ zljXa>L}l=Q8yr9h{I~XY4f>zNgskEJA^87dMqE!WuNCKc-|J`k`*{E9dEe>#zrEcy z^?$Yf|3lLMkhIlFi^mGvZm-0jUn6INT!qGiq2X89n_N&k5Aox!LZ5Y-h@zP5_;E6-{TFY5l{OD9YN+{c;UD5Z-vJINd;g92AFcLo&Hq0T z{IjHxHsJ3$_~o4FH^l#@=5Qm`Yz8b<^jM0_Ii=9ekm{?fs)!m}Ed&4BDcpnnzqM)F z|LoQ9|3L6B3;S!x`RDTg91R$%o#k;sG5oi;c8vI6o7`RShxpjYq=RQIDDpe!~;3m$7y=M2Wo^Uk*O3iHWb-XPF}tYGk_PRK?i^W_tIIefIlG{XR`d zA`XD3`O$n+@=QPc0KBiBlwG|b5fDEoTa*fY6P8@Q_uP4e?8Cb#_-l@HcWefU5x&Gp z(oIADu6+zYFn(1G{Aw!seaKn(-=WS*5>t%d{uzpQu4FEiXTx08_d$*QT9+j;*b z(~gRyK$-o|R@;dGwzFHue|UiRU$gh&NsX_o8wM4iIqBjs^R1)4&eHotYQ78 z@NdL_G<>(J^8sb~-wpj=yS3AQ5VIf) z@h!$AbYV0*c-=i7D}DbR82|R-e{b*X?3wYu>->+8nEx*Ze|si4Ys^5RhX0lL_Gqng zhaY#WK_1~xZRiYKY4qiEMzHo+DE8dlzL%F#Hq( zqq%m4f;@K=q;!&h^K48U(}K|l#JP|oL)bP(<9NU5d3|3KLs2E_cyUlksc-$%p1Y(N z_A>%j*_@>9t<#1RG0Y8tfqkT=WgI7;Z(SSX-)*7mEfMRN3GS{mS8HUkk*r3ERg`vY zV4rtCfb?55RZN7h#IGe&)Y$UfMEL6WRa4&3oKSDT7O6pVOUd%4UN44TQSu|&I+h2oS-8>v5|xWn4exo0#W=S@2T6mSwJG6HGX zYmO~bUoRL>4#=UJcS&{UKpIP6%62ZlF@EaCLLN0-A= zH$_4)s{miiQ~z=LA1nR`M&q{aKU(dUA^+KI@73|2 z9yk8Cii%z09*TZ?48xLUP4>~+2lqj9J`Dj6iAoZ$l6zV46<|rPZ~6`}?^NE&nN^BB z6f5k=%jFz^P-(+Mpc%~2{~suse|2VYDDP;;w#}A@NU~B`@_B&{mwRBKMadJBMIcOU?>CXw**#k{Zr zF_bE*aD|e{RetS4A>(l!0r(#1Kj~83HvPBN+SxYgzwH|S@00%fLTRQd^xqc;c}2z0 zlC4l4Nu;D9q+OSqu`nYgjffSd)QSfoDQWhSgd~yLkxt_Xa!ob`t_s1K&)q+S0rVRxho&(39Qc5A<2Lw=rD`66+6 zzwG~T?HzjOUWb}WtCM_zBb)?7qt9m*#Ov^^P!7=~DgCtL6usShGxdhmvIIR0<6y>K@&OuL8Jw-u8q~aaRUD6#vh%Ncq1SPm?6X^bw zWx`&j|FSle9G(N!+HmY}v+7o^3lA8^pHr*QhVDXhubu+X3i&=AlRCm=c6Qj#FSKH0|9a1NOz z!6m4QMnU8&U8N^+A{~rco4NzZIo)}QG3|ldro!>`*L2!Hl>Q?ZP0qD_%k*EnwQ1yk z+S{t*zdTO*&%rN}OQN~oPKn>H!2k0||AHgm=6ma;h~%rN$ANG>QFNe1hW0 zAoY+p3K19+BN#Em$ux*?>IEa>Swgx4WbF}v@P_deOpk+z^|QgB1KOd1!J&jK&nS`R zf+KVhR+)`-7EG7#T_M=(Y!akwu379|VBRGkL7FEku{k2ctBqga}Be`FvQ5{_P=pNrV2ip_L6tx zn%c|yFsMlfI_};&w*to~QAs-9FZZA}zDfh}ug-}V%$p4TO6;Viv@h>t6{5g+4{FNb zyMF&~^z>Je|Fl~zQ~tA8zyFU}{HcMy%cWEhC`pf4N>e#MtK zlop?VgAI@a709H4c1}(C_B6d)5+H$00cK;I!~~@z0D~no1!+KtkE6xeBF*sBRjKbF z9E0?XZnBM}R9Z6f(itJ9OYafAPF17Cq?kp0`4mLHNql`+gYRp9HeH-SVf2<<{x$F$ zLdT_yKS8OziWL^dY__R+BQLSNWEP^KP0W)nq9OTAgBPiVR|%R~+63ttn%X)WW0tkU zw-jYSJNCkZOW`3e!dKj{S#~-u_02Y&9Ex1DnJc!{@1@9vI}ZKV_K+Nv9ifeh zR`sMX8oG?59H4ebII`6KPnyo%tg{9Mk#$)GIVYgO&e0eFqN z=E>3T$d_L1Yk_r3Lgrl4|4ET}$}GP+uJQO{#$FOw*USn<$p~hL}F9N08I<_QpI~u?P+LLIiA#s+XgB} ztqmt`U0=qy$(XqaOX`tuu6Bi=cZ#!l5`n62j=if1j)+=MrjW0KF!Z9>3*!jpHly1I zVKA}@krhR;H$~)#h(-{jl+xA3+D0e|(qFAYK`4V=PA8Uz`5qMDX(3g#BJ8}_fw{$b zQCyQ>nT{C?SvH}O&v_}_uQvWKQ$IWA^j~(KYz`#IwX5m~1z}NFe3#AeO}U^@+UAZ4 zG0WcL^<2T;;A0=!1#9f0HA;`5Bm^*vLcB`!U$IfPz{H^9<{??7eQA0(9!gtar7b{d ziW9DQ4^nep`Zz&qQ~_A4+@2076P#%`7rQIR$Qn>9FtUv_`m|#M%Qi^rhn=nSB&va7 z*?<5<4TDy7tTM$J$jr^~(z;Ktg$*}GaXd@Bp6R{n!V+Nomp8EqyAtzBP>eA z>SfiIV`yOG0^f^Aj4Ql?NEPx<60=Y+MolmpPOwkv&2q*>1i-aU)+()BY63t#jZxx< zz>>zJlJS0lM9()LRIHk_F!GXJgYz3+KEBL-*IlMl<;)uy}` z{JtIX3s;uQ#w3gjCsmDjyxEjiVYduERodpXQAW&ixdT1c0xXAo|2+O}j_26PhdcO? zG0tA&&)FcNt=FrdD03+;@;Y~JESOcv4)9zkRK(+CJC&?Ta{H5cI0`~YCMMM;i}edO zG>(iBsMgcPweki7IYW2jOP-j!NwpZ>_1$OfmV(Y!C=h4*8z&TFNX(NHG6VoUjQ0Bs7d}I^qm^RmouF zYE6l~-;tL_OQ<#Bnja*1nCBskLsk{`gZB`QI`W1I(-SnzEG4ir=YW2Rq@U3I7qCCD z6*QX-%p8#YRgg`{hrsuO%$uh`@Wmbr5k_%1*(yc@u>u-@ONsB##~qAlt9+9Qm?I)} zgEK@PkGaM1{}Y--YMj~0iP69Nycloh}YX9;*5>f)>KLUL3U|HmuF{;7cfTU)zE{IA{3 zc8&iZ2LGR6l+6={=08%k=3dPFZljt%M0LWCICft|ib`=6CEzAuCSclY)T|Nq_HI{()TZjmRg}u!8lKAS*$C)bmo>}& zje6M^yPx8)l8&M%iMW}?+m*zd4{o|m>N$b^H@XkIT;mTEWH{0Ms6@VE&5KG)PpoDdgl>@0cj~nd`0j<>2w9dk1G(aDufNVXmIg{t5-zNt_HM zB-ki9&U*|ZXP_^vqe29hZCCl+HMdgY-l`n0T46o|d`a9+kBPW0h@&II2U+at+)cWp z3@0Tm2r61`5Vc8_L6+;kV%JP2me(%q>LgAC(xvXjzRiz0N{2YOG~QaZToF6o=#DuKe{PNQDiL}-PWc zc6+ae{|D0l@?>tO)Wg)O^eusPGwogl^{p!(UolRgey|oWiI&_QMR=NS%f z|NY->8S$SsTebf0A>hBNGOb*FW;Y7~sv-tvmD={LrQpO{fq3A#D+|$x6G!9-M#|Dz zHJ(XPloBdMJuH!gy0MS~QV)cBD)av$xBKnee{CD^zg@@we{}qRiK!UV&y*7k7Iv9j zF2#x$@CP?s8i0EMwFPB1RS@(Z!*oyr7R4ljK1tRNEXfRwi8W~!b6hhfs&8ibWWyc9 zGRz@v|C-=MFqYMctS%U%l1%Ao!10WpC{GH$l+I_cG2o^~O$HQ;WX|K{wN8r6vZ~55 z^3AR;&d8g%B^nxjsI9-gGx|?TPrd8|P(uH0?U?ePz0Df`KY;v)9W#~cQUgGb9C-yA zO-dntLo!XxgkC^}U6)`Zaa(J2RMq}-o&^-_sb-ZZ`ED2iq6Ge1dz(i5hrM=f|9>z4 zG@@ZNkBEBJQ!hY+xl_)gPvF}vhZvATr&(dqa+7Lce8lk2nBf}-eW?upd)s@4|L0b_ zhW~rz|5mjBHduwGXP7*n5K+(K*C5S!Vi!`Cm=Qr|O1c+TyXl>x$d9LI1kL0h4YH#w zRE#rD4g9wYy#mYAG#y`a*0vih@@6D{t#w4s=PnaY*_Io}Os~g{S+p>}OxZ5kuE_R! z5%q~xeB}$G*IaW|+7Qo};C{)bB$Og;CuPV8d>=>pSD}HE$57OG`cNO%C?&b}C+CRS zu<<;5Gdf#DL%FMIKq7=J)7N#;&NGat1#JdCU#DQF@kJ2gV+{A-utSJLpY>!E6$F4* zf?sf83P4U!1ZZqJ23U10phoNZK{^W&z3Qcv6B9N&Hh|q1ez&l>9V_^o@&;~fs~P{t z^XyQI2tC9UBcwE*%7FjoN6X#V3~>p$a`P;PyHjEkA|;K|ZbWX!tY&ReA@)G?u#g=q za)W$&nH(vj{+se^qgEiK7`f$|UE?^46AF!5Nq!P$dMXIE#BJfI<+C-XqRX3t7&8XZ z*kvx~Gnmi}*l2{OV|yXY|2%$yvv(=O`I-qHN;7U(0QV!!&~#>2u^0}s4Yed$luaC@ z$iDml^vqaLNYBnzA~2m{^=Wo-T~eXnG~-=o$gb=g7fDG3NGzRIk6T zWo2c9H*_O#{s=Zy&VJoQvwH9`GFl`36wHVrxe|=FVZ5>bTlMu>*Fx;%*1NAz5Gmo= z07ufzZBW@($D}4)(R7%566=J$yrhM-29d2w#VO9Oc!L8%$Lo`&`mqpcW{ zZU}s>qibt+RB9T2&ISE6qJ(b4O}3ASRBtRb1bd`=#o@8 zn;_GPMe4v>KFkl^u!$FdX*(WTp}@qQv?oZjpb`t7W3k5v7@*fyFYHQeg1w9Zh1K-G zU^0EqMCb)Cb4J1(kFyPi7eKz}nCq|X-U0?AJ`L6B%z%)U|C8?g?NR;fO?ma)?@%J3em}STU zCaZ)JuaL*K8oQ9=OuH8sD<)MH*2=~TI5%}&-7%Fw2TW)C&L|qWw`vr>;%IbQwh$w0 zXa+VKS(WgGE=*Hv3ho{M=dmk@`*nI7dVn(h|CWLOxA!*pYW#oy_&+Bpi_pGHWf-Jy zshKC^_2IuA(WQ)C-CclyqrAOc(*4lnR{P2No-X z|6EOT145w8{>SA1c6au6YxuuM`yXBHDYSFv2i|ip7nz?+?3wiftM{dThrc=cKR>`f z5;U9LLiVRJ_;0schW*b@t5v`M4@CddkYi2L!Q&+n$(wGWMRqq!8^GB7(R6bm^Hrrb zJo@LO*k3elHcG~)KFuv*of>-y*UBvebkJcGh#XVVO03A5DcaLXq(_?}A~dVONb9nx zbW_>nqFIuKT+b{DgPg}yefR7zn&JQ$YV&qeERK341 zG@%E*2YCv~rfN6d)RT#@{2ve%@GVLpl+-i(%}}R-XiPqR4)k+Jfg$`kOAu(Z6$NYs zcOEqn2{EQ3&}WP)`#;M7stE>(0z|}o0!g3VlPj&gaRizc-PkfIe@ZclAipp*rBMH? V|JDEMfA{ Date: Thu, 28 May 2026 23:54:21 +0530 Subject: [PATCH 073/203] move sdk changes to a patch --- app/lib/services/sdk.test.ts | 157 +++---------------- app/lib/services/sdk.ts | 75 +-------- patches/@rocket.chat+ddp-client+0.3.51.patch | 98 ++++++++++++ 3 files changed, 119 insertions(+), 211 deletions(-) create mode 100644 patches/@rocket.chat+ddp-client+0.3.51.patch diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 1517b62e171..a29975e2f3e 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -4,37 +4,14 @@ const setInternalSdk = (value: any) => { (sdk as unknown as { sdk: any }).sdk = value; }; -const buildFakeDdp = () => { - const pongHandlers = new Set<() => void>(); - const ping = jest.fn(); - const on = jest.fn((event: string, cb: () => void) => { - if (event === 'pong') pongHandlers.add(cb); - return () => pongHandlers.delete(cb); - }); - const emitPong = () => pongHandlers.forEach(cb => cb()); - return { ping, on, emitPong, pongHandlers }; -}; - -const buildFakeConnection = (overrides: Partial<{ close: jest.Mock; reconnect: jest.Mock }> = {}) => ({ - close: jest.fn(), - reconnect: jest.fn().mockResolvedValue(true), +const buildFakeConnection = (overrides: Partial<{ probe: jest.Mock; forceReopen: jest.Mock }> = {}) => ({ + probe: jest.fn().mockResolvedValue(true), + forceReopen: jest.fn().mockResolvedValue(true), ...overrides }); -const buildFakeSdk = (opts: { ddp?: any; connection?: any } = {}) => { - const fakeDdp = opts.ddp ?? buildFakeDdp(); - const fakeConnection = opts.connection ?? buildFakeConnection(); - return { - client: { ddp: fakeDdp }, - connection: fakeConnection, - __fakeDdp: fakeDdp, - __fakeConnection: fakeConnection - }; -}; - afterEach(() => { setInternalSdk(undefined); - (sdk as unknown as { reopenInFlight: Promise | null }).reopenInFlight = null; jest.useRealTimers(); }); @@ -44,57 +21,18 @@ describe('Sdk.probe', () => { await expect(sdk.probe()).resolves.toBe(false); }); - it('returns false when inner ddp client lacks ping/on (defensive)', async () => { - setInternalSdk({ client: {}, connection: buildFakeConnection() }); - await expect(sdk.probe()).resolves.toBe(false); + it('delegates to connection.probe() and forwards the timeout', async () => { + const connection = buildFakeConnection(); + setInternalSdk({ connection }); + await expect(sdk.probe(1500)).resolves.toBe(true); + expect(connection.probe).toHaveBeenCalledWith(1500); }); - it('resolves true when pong fires before the timeout', async () => { - const fake = buildFakeSdk(); - setInternalSdk(fake); - const promise = sdk.probe(2000); - fake.__fakeDdp.emitPong(); - await expect(promise).resolves.toBe(true); - expect(fake.__fakeDdp.ping).toHaveBeenCalledTimes(1); - }); - - it('resolves false when timeout elapses without pong', async () => { - jest.useFakeTimers(); - const fake = buildFakeSdk(); - setInternalSdk(fake); - const promise = sdk.probe(2000); - jest.advanceTimersByTime(2000); - await expect(promise).resolves.toBe(false); - }); - - it('resolves false when ping() throws', async () => { - const fake = buildFakeSdk(); - fake.__fakeDdp.ping.mockImplementation(() => { - throw new Error('boom'); - }); - setInternalSdk(fake); + it('returns the result from connection.probe()', async () => { + const connection = buildFakeConnection({ probe: jest.fn().mockResolvedValue(false) }); + setInternalSdk({ connection }); await expect(sdk.probe()).resolves.toBe(false); }); - - it('removes the pong listener after settling so it does not leak', async () => { - const fake = buildFakeSdk(); - setInternalSdk(fake); - const promise = sdk.probe(2000); - fake.__fakeDdp.emitPong(); - await promise; - expect(fake.__fakeDdp.pongHandlers.size).toBe(0); - }); - - it('ignores a late pong after timeout (does not double-resolve)', async () => { - jest.useFakeTimers(); - const fake = buildFakeSdk(); - setInternalSdk(fake); - const promise = sdk.probe(2000); - jest.advanceTimersByTime(2000); - await expect(promise).resolves.toBe(false); - // Late pong arrives. With the listener cleaned up, this is a no-op. - expect(() => fake.__fakeDdp.emitPong()).not.toThrow(); - }); }); describe('Sdk.forceReopen', () => { @@ -103,75 +41,18 @@ describe('Sdk.forceReopen', () => { await expect(sdk.forceReopen()).resolves.toBe(false); }); - it('calls connection.close then connection.reconnect', async () => { - const fake = buildFakeSdk(); - setInternalSdk(fake); - await sdk.forceReopen(); - expect(fake.__fakeConnection.close).toHaveBeenCalledTimes(1); - expect(fake.__fakeConnection.reconnect).toHaveBeenCalledTimes(1); - // close before reconnect - const closeOrder = fake.__fakeConnection.close.mock.invocationCallOrder[0]; - const reconnectOrder = fake.__fakeConnection.reconnect.mock.invocationCallOrder[0]; - expect(closeOrder).toBeLessThan(reconnectOrder); - }); - - it('resolves true when reconnect resolves truthy', async () => { - const fake = buildFakeSdk(); - setInternalSdk(fake); + it('delegates to connection.forceReopen()', async () => { + const connection = buildFakeConnection(); + setInternalSdk({ connection }); await expect(sdk.forceReopen()).resolves.toBe(true); + expect(connection.forceReopen).toHaveBeenCalledTimes(1); }); - it('resolves false when reconnect rejects (does not throw)', async () => { - const fake = buildFakeSdk({ - connection: buildFakeConnection({ reconnect: jest.fn().mockRejectedValue(new Error('conn in progress')) }) - }); - setInternalSdk(fake); + it('returns the result from connection.forceReopen()', async () => { + const connection = buildFakeConnection({ forceReopen: jest.fn().mockResolvedValue(false) }); + setInternalSdk({ connection }); await expect(sdk.forceReopen()).resolves.toBe(false); }); - - it('coalesces concurrent calls to the same in-flight promise', async () => { - let resolveReconnect: (v: boolean) => void = () => undefined; - const reconnect = jest.fn( - () => - new Promise(res => { - resolveReconnect = res; - }) - ); - const fake = buildFakeSdk({ connection: buildFakeConnection({ reconnect }) }); - setInternalSdk(fake); - - const a = sdk.forceReopen(); - const b = sdk.forceReopen(); - - expect(a).toBe(b); - expect(reconnect).toHaveBeenCalledTimes(1); - expect(fake.__fakeConnection.close).toHaveBeenCalledTimes(1); - - resolveReconnect(true); - await Promise.all([a, b]); - }); - - it('clears the in-flight slot after the reopen settles so the next call starts fresh', async () => { - const fake = buildFakeSdk(); - setInternalSdk(fake); - await sdk.forceReopen(); - await sdk.forceReopen(); - expect(fake.__fakeConnection.reconnect).toHaveBeenCalledTimes(2); - expect(fake.__fakeConnection.close).toHaveBeenCalledTimes(2); - }); - - it('still proceeds when close() throws', async () => { - const fake = buildFakeSdk({ - connection: buildFakeConnection({ - close: jest.fn(() => { - throw new Error('already closed'); - }) - }) - }); - setInternalSdk(fake); - await expect(sdk.forceReopen()).resolves.toBe(true); - expect(fake.__fakeConnection.reconnect).toHaveBeenCalledTimes(1); - }); }); describe('Sdk.subscribeNotifyUser', () => { @@ -225,7 +106,7 @@ describe('Sdk.login', () => { const buildFakeSdkWithLogin = (postResult: any, loginWithToken = jest.fn().mockResolvedValue(undefined)) => { const post = jest.fn().mockResolvedValue(postResult); return { - client: { ddp: buildFakeDdp() }, + client: { ddp: {} }, connection: buildFakeConnection(), account: { loginWithToken }, rest: { post, handleTwoFactorChallenge: jest.fn() }, diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 1b729dcc9a2..c5cfeeed46e 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -52,24 +52,11 @@ class Sdk { private sdk: DDPSDK | undefined; private serverUrl: string | undefined; private code: any = null; - private reopenInFlight: Promise | null = null; private headers: Record = { ...defaultHeaders } as Record; initialize(server: string): DDPSDK { this.sdk = DDPSDK.create(server); - // ddp-client's TimeoutControl (60s passive heartbeat) stops resetting after - // the first background close because it lacks a connection.on('connected') - // listener in its create() setup. This causes a reconnect loop on foreground: - // timeout fires → closes socket → reconnect → timeout fires immediately again. - // Monkey-patch reconnect() to re-arm the timeout on every successful reconnect. - const origReconnect = this.sdk.connection.reconnect.bind(this.sdk.connection); - this.sdk.connection.reconnect = () => - origReconnect().then((r: boolean) => { - this.sdk?.timeoutControl?.reset(); - return r; - }) as Promise; - this.sdk.subscribeNotifyUser = () => this.subscribeNotifyUser(); this.serverUrl = server; this.loadBasicAuth(); @@ -101,70 +88,12 @@ class Sdk { return null; } - /** - * Active liveness check: sends a raw DDP `ping` and resolves true if `pong` - * arrives within `timeoutMs`. Bypasses the request pipeline so a zombie - * socket can't make us wait indefinitely. Reaches into the private - * `MinimalDDPClient` because ddp-client doesn't expose `pong` at the - * `ClientStream` layer. - */ probe(timeoutMs = 2000): Promise { - return new Promise(resolve => { - const ddp = ( - this.current?.client as unknown as - | { - ddp?: { ping?: (id?: string) => void; on?: (e: 'pong', cb: () => void) => () => void }; - } - | undefined - )?.ddp; - if (!ddp?.ping || !ddp?.on) { - resolve(false); - return; - } - let settled = false; - let off: (() => void) | undefined; - const finish = (alive: boolean) => { - if (settled) return; - settled = true; - clearTimeout(timer); - off?.(); - resolve(alive); - }; - const timer = setTimeout(() => finish(false), timeoutMs); - try { - off = ddp.on('pong', () => finish(true)); - ddp.ping(); - } catch { - finish(false); - } - }); + return this.current?.connection.probe(timeoutMs) ?? Promise.resolve(false); } - /** - * Tear down the current socket and rebuild from scratch. Concurrent callers - * share one in-flight promise so parallel warm-accepts don't race each other. - * Resolves true once the new socket is connected; false on failure or when - * there is no current SDK. - */ forceReopen(): Promise { - if (this.reopenInFlight) return this.reopenInFlight; - const { current } = this; - if (!current) return Promise.resolve(false); - try { - current.connection.close(); - } catch { - // ignore — we're rebuilding anyway - } - const p = current.connection - .reconnect() - .then((v: unknown) => Boolean(v)) - .catch(() => false); - this.reopenInFlight = p; - const clear = () => { - if (this.reopenInFlight === p) this.reopenInFlight = null; - }; - p.then(clear, clear); - return p; + return this.current?.connection.forceReopen() ?? Promise.resolve(false); } private loadBasicAuth(): void { diff --git a/patches/@rocket.chat+ddp-client+0.3.51.patch b/patches/@rocket.chat+ddp-client+0.3.51.patch new file mode 100644 index 00000000000..1c2da7316ac --- /dev/null +++ b/patches/@rocket.chat+ddp-client+0.3.51.patch @@ -0,0 +1,98 @@ +--- a/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts ++++ b/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts +@@ -21,6 +21,19 @@ + connect(): Promise; + reconnect(): Promise; + close(): void; ++ /** ++ * Active liveness check: sends a raw DDP `ping` and resolves true if `pong` ++ * arrives within `timeoutMs`. Useful for detecting zombie sockets that ++ * remain in `status === 'connected'` but whose underlying transport is dead ++ * (e.g. mobile NAT timeouts that don't fire `onclose`). ++ */ ++ probe(timeoutMs?: number): Promise; ++ /** ++ * Tear down the current socket and reconnect from scratch. Concurrent ++ * callers share one in-flight promise so parallel calls don't race each ++ * other. Resolves true once the new socket is connected; false on failure. ++ */ ++ forceReopen(): Promise; + } + interface WebSocketConstructor { + new (url: string | URL, protocols?: string | string[]): WebSocket; +@@ -43,11 +56,14 @@ + ws: WebSocket | undefined; + retryCount: number; + queue: Set; ++ private reopenInFlight; + constructor(url: string, WS: WebSocketConstructor, client: DDPClient, retryOptions?: RetryOptions); + private emitStatus; + reconnect(): Promise; + connect(): Promise; + close(): void; ++ probe(timeoutMs?: number): Promise; ++ forceReopen(): Promise; + static create(url: string, webSocketImpl: WebSocketConstructor, client: DDPClient, retryOptions?: RetryOptions): ConnectionImpl; + } + export {}; +--- a/node_modules/@rocket.chat/ddp-client/dist/Connection.js ++++ b/node_modules/@rocket.chat/ddp-client/dist/Connection.js +@@ -11,6 +11,7 @@ + this.status = 'idle'; + this.retryCount = 0; + this.queue = new Set(); ++ this.reopenInFlight = null; + this.ssl = url.startsWith('https') || url.startsWith('wss'); + this.url = url.replace(/^https?:\/\//, '').replace(/^wss?:\/\//, ''); + this.client.onDispatchMessage((message) => { +@@ -114,6 +115,50 @@ + (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close(); + this.emitStatus(); + } ++ probe(timeoutMs = 2000) { ++ return new Promise((resolve) => { ++ const ddp = this.client; ++ if (!ddp || typeof ddp.ping !== 'function' || typeof ddp.on !== 'function') { ++ resolve(false); ++ return; ++ } ++ let settled = false; ++ let off; ++ const finish = (alive) => { ++ if (settled) return; ++ settled = true; ++ clearTimeout(timer); ++ if (off) off(); ++ resolve(alive); ++ }; ++ const timer = setTimeout(() => finish(false), timeoutMs); ++ try { ++ off = ddp.on('pong', () => finish(true)); ++ ddp.ping(); ++ } ++ catch (_a) { ++ finish(false); ++ } ++ }); ++ } ++ forceReopen() { ++ if (this.reopenInFlight) return this.reopenInFlight; ++ try { ++ this.close(); ++ } ++ catch (_a) { ++ // ignore — we're rebuilding anyway ++ } ++ const p = this.reconnect() ++ .then((v) => Boolean(v)) ++ .catch(() => false); ++ this.reopenInFlight = p; ++ const clear = () => { ++ if (this.reopenInFlight === p) this.reopenInFlight = null; ++ }; ++ p.then(clear, clear); ++ return p; ++ } + static create(url, webSocketImpl, client, retryOptions = { retryCount: 0, retryTime: 1000 }) { + return new ConnectionImpl(url, webSocketImpl, client, retryOptions); + } From 4acde09c4902c3d4fa440ce3f0312d635d8574b0 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 28 May 2026 23:57:37 +0530 Subject: [PATCH 074/203] remove away from get and delete in sdk --- app/lib/services/sdk.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index c5cfeeed46e..6d2ca7e2979 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -142,7 +142,7 @@ class Sdk { const sdk = this.ensureInitialized(); try { // @ts-ignore - return await sdk.rest.get(endpoint, params, { + return sdk.rest.get(endpoint, params, { headers: this.headers }); } catch (e: any) { @@ -218,7 +218,7 @@ class Sdk { const sdk = this.ensureInitialized(); try { // @ts-ignore - return await sdk.rest.delete(endpoint, params, { + return sdk.rest.delete(endpoint, params, { headers: this.headers }); } catch (e: any) { From ff913aa8f2ee8e76ea0f172cbfb51d526c503188 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 00:42:50 +0530 Subject: [PATCH 075/203] more changes --- app/lib/methods/logout.ts | 3 +-- app/lib/services/connect.ts | 20 ++---------------- patches/@rocket.chat+ddp-client+0.3.51.patch | 22 +++++++++++++++++--- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/app/lib/methods/logout.ts b/app/lib/methods/logout.ts index 612eb3587e6..8077ba9e94d 100644 --- a/app/lib/methods/logout.ts +++ b/app/lib/methods/logout.ts @@ -74,8 +74,7 @@ export async function removeServer({ server }: { server: string }): Promise { - const conn = sdk.current?.connection; - if (!conn) return false; - if (conn.status !== 'connected') { - return sdk.forceReopen(); - } - const alive = await sdk.probe(); - if (alive) return true; - return sdk.forceReopen(); +function checkAndReopen(): Promise { + return sdk.current?.connection.checkAndReopen() ?? Promise.resolve(false); } /** diff --git a/patches/@rocket.chat+ddp-client+0.3.51.patch b/patches/@rocket.chat+ddp-client+0.3.51.patch index 1c2da7316ac..d7ff1a4e684 100644 --- a/patches/@rocket.chat+ddp-client+0.3.51.patch +++ b/patches/@rocket.chat+ddp-client+0.3.51.patch @@ -1,6 +1,6 @@ --- a/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts +++ b/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts -@@ -21,6 +21,19 @@ +@@ -21,6 +21,26 @@ connect(): Promise; reconnect(): Promise; close(): void; @@ -17,10 +17,17 @@ + * other. Resolves true once the new socket is connected; false on failure. + */ + forceReopen(): Promise; ++ /** ++ * Ensure the socket is usable. If `status !== 'connected'`, force-reopens. ++ * If status is `connected`, runs a `probe()` to detect zombie sockets; on ++ * a dead probe, force-reopens. Resolves true when the socket is healthy or ++ * was successfully reopened. ++ */ ++ checkAndReopen(probeTimeoutMs?: number): Promise; } interface WebSocketConstructor { new (url: string | URL, protocols?: string | string[]): WebSocket; -@@ -43,11 +56,14 @@ +@@ -43,11 +63,15 @@ ws: WebSocket | undefined; retryCount: number; queue: Set; @@ -32,6 +39,7 @@ close(): void; + probe(timeoutMs?: number): Promise; + forceReopen(): Promise; ++ checkAndReopen(probeTimeoutMs?: number): Promise; static create(url: string, webSocketImpl: WebSocketConstructor, client: DDPClient, retryOptions?: RetryOptions): ConnectionImpl; } export {}; @@ -45,7 +53,7 @@ this.ssl = url.startsWith('https') || url.startsWith('wss'); this.url = url.replace(/^https?:\/\//, '').replace(/^wss?:\/\//, ''); this.client.onDispatchMessage((message) => { -@@ -114,6 +115,50 @@ +@@ -114,6 +115,58 @@ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.close(); this.emitStatus(); } @@ -92,6 +100,14 @@ + }; + p.then(clear, clear); + return p; ++ } ++ async checkAndReopen(probeTimeoutMs = 2000) { ++ if (this.status !== 'connected') { ++ return this.forceReopen(); ++ } ++ const alive = await this.probe(probeTimeoutMs); ++ if (alive) return true; ++ return this.forceReopen(); + } static create(url, webSocketImpl, client, retryOptions = { retryCount: 0, retryTime: 1000 }) { return new ConnectionImpl(url, webSocketImpl, client, retryOptions); From d8639f46daf0333a822e63592d22b38955d82b5c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 00:51:21 +0530 Subject: [PATCH 076/203] test update --- app/lib/services/connect.test.ts | 42 ++++++-------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 1cf26aa7626..ad4d78ee7f0 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -338,11 +338,6 @@ describe('VoIP media session lifecycle (disconnect)', () => { }); describe('checkAndReopen', () => { - beforeEach(() => { - sdkMock.default.probe.mockReset().mockResolvedValue(true); - sdkMock.default.forceReopen.mockReset().mockResolvedValue(true); - }); - afterEach(() => { setSdkCurrent(undefined); }); @@ -350,41 +345,18 @@ describe('checkAndReopen', () => { it('resolves false when sdk.current is undefined', async () => { setSdkCurrent(undefined); await expect(checkAndReopen()).resolves.toBe(false); - expect(sdkMock.default.probe).not.toHaveBeenCalled(); - expect(sdkMock.default.forceReopen).not.toHaveBeenCalled(); - }); - - it.each(['disconnected', 'closed', 'reconnecting', 'idle', 'failed'])( - 'calls forceReopen without probing when status is %s', - async status => { - setSdkCurrent({ connection: { status } }); - await expect(checkAndReopen()).resolves.toBe(true); - expect(sdkMock.default.probe).not.toHaveBeenCalled(); - expect(sdkMock.default.forceReopen).toHaveBeenCalledTimes(1); - } - ); - - it('probes when status is connected and returns true without reopening on live socket', async () => { - sdkMock.default.probe.mockResolvedValue(true); - setSdkCurrent({ connection: { status: 'connected' } }); - await expect(checkAndReopen()).resolves.toBe(true); - expect(sdkMock.default.probe).toHaveBeenCalledTimes(1); - expect(sdkMock.default.forceReopen).not.toHaveBeenCalled(); }); - it('probes when status is connected and forces reopen on zombie socket', async () => { - sdkMock.default.probe.mockResolvedValue(false); - sdkMock.default.forceReopen.mockResolvedValue(true); - setSdkCurrent({ connection: { status: 'connected' } }); + it('delegates to connection.checkAndReopen() and returns its result', async () => { + const checkAndReopenMock = jest.fn().mockResolvedValue(true); + setSdkCurrent({ connection: { checkAndReopen: checkAndReopenMock } }); await expect(checkAndReopen()).resolves.toBe(true); - expect(sdkMock.default.probe).toHaveBeenCalledTimes(1); - expect(sdkMock.default.forceReopen).toHaveBeenCalledTimes(1); + expect(checkAndReopenMock).toHaveBeenCalledTimes(1); }); - it('forwards forceReopen failure (resolves false, does not throw) when probe fails too', async () => { - sdkMock.default.probe.mockResolvedValue(false); - sdkMock.default.forceReopen.mockResolvedValue(false); - setSdkCurrent({ connection: { status: 'connected' } }); + it('forwards false when connection.checkAndReopen() resolves false', async () => { + const checkAndReopenMock = jest.fn().mockResolvedValue(false); + setSdkCurrent({ connection: { checkAndReopen: checkAndReopenMock } }); await expect(checkAndReopen()).resolves.toBe(false); }); }); From 8ca2abb9b972c5449761d747e35607c944be1d5f Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 00:51:24 +0530 Subject: [PATCH 077/203] snapshot update --- .../__snapshots__/Message.test.tsx.snap | 1200 +++++------------ .../__snapshots__/index.test.tsx.snap | 56 +- .../__snapshots__/Item.test.tsx.snap | 32 +- .../__snapshots__/LoadMore.test.tsx.snap | 36 +- .../__snapshots__/Item.test.tsx.snap | 48 +- 5 files changed, 343 insertions(+), 1029 deletions(-) diff --git a/app/containers/message/__snapshots__/Message.test.tsx.snap b/app/containers/message/__snapshots__/Message.test.tsx.snap index fd100c8e6f4..d0b5a65168d 100644 --- a/app/containers/message/__snapshots__/Message.test.tsx.snap +++ b/app/containers/message/__snapshots__/Message.test.tsx.snap @@ -243,9 +243,7 @@ exports[`Story Snapshots: Archived should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -705,9 +703,7 @@ exports[`Story Snapshots: ArchivedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -1175,9 +1171,7 @@ exports[`Story Snapshots: AttachmentWithTextAndLink should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -1946,9 +1940,7 @@ exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapsh source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -2717,9 +2709,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/no.avatar?format=png&size=72", }, ] @@ -3174,9 +3164,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5023,9 +5011,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5480,9 +5466,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5950,9 +5934,7 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -6407,9 +6389,7 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -6877,9 +6857,7 @@ exports[`Story Snapshots: BlockQuote should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7367,9 +7345,7 @@ Testing block quote" source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7916,9 +7892,7 @@ exports[`Story Snapshots: BlockQuoteLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -8406,9 +8380,7 @@ Testing block quote" source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -8955,9 +8927,7 @@ exports[`Story Snapshots: Broadcast should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -9545,9 +9515,7 @@ exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -10135,9 +10103,7 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -10744,9 +10710,7 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -11568,9 +11532,7 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -12177,9 +12139,7 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -13001,9 +12961,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -13612,9 +13570,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -14577,9 +14533,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -15188,9 +15142,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -16153,9 +16105,7 @@ exports[`Story Snapshots: ColoredAttachments should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -17385,9 +17335,7 @@ exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -18617,9 +18565,7 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -19429,9 +19375,7 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -20512,9 +20456,7 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -21324,9 +21266,7 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -22407,9 +22347,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -22926,9 +22864,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -23651,9 +23587,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -24165,9 +24099,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -24635,9 +24567,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=93.60000000000001", }, ] @@ -25154,9 +25084,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -25879,9 +25807,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=93.60000000000001", }, ] @@ -26393,9 +26319,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -26863,9 +26787,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -27441,9 +27363,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -28021,9 +27941,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -28601,9 +28519,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -29194,9 +29110,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -29772,9 +29686,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -30352,9 +30264,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -30932,9 +30842,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -31525,9 +31433,7 @@ exports[`Story Snapshots: Edited should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -32291,9 +32197,7 @@ exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -33057,9 +32961,7 @@ exports[`Story Snapshots: Editing should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -33527,9 +33429,7 @@ exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -33997,9 +33897,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -34453,9 +34351,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -34890,9 +34786,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -35440,9 +35334,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -35902,9 +35794,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -36383,9 +36273,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -36910,9 +36798,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -37366,9 +37252,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -37803,9 +37687,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -38353,9 +38235,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -38815,9 +38695,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -39296,9 +39174,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -39823,9 +39699,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -40648,9 +40522,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -41733,9 +41605,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -42045,9 +41915,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -42579,9 +42447,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -43021,9 +42887,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -43617,9 +43481,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -44515,9 +44377,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -45340,9 +45200,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -46425,9 +46283,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -46737,9 +46593,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -47271,9 +47125,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -47713,9 +47565,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -48309,9 +48159,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -49120,9 +48968,7 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -49776,9 +49622,7 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -50519,9 +50363,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -51115,9 +50957,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -51711,9 +51551,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -52307,9 +52145,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -53428,9 +53264,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -54024,9 +53858,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -54620,9 +54452,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -55216,9 +55046,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -56324,9 +56152,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -57088,9 +56914,7 @@ exports[`Story Snapshots: FullName should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -57558,9 +57382,7 @@ exports[`Story Snapshots: FullNameLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -58028,9 +57850,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -58485,9 +58305,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=72", }, ] @@ -59390,9 +59208,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -59860,9 +59676,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -60317,9 +60131,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=93.60000000000001", }, ] @@ -61222,9 +61034,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -61690,9 +61500,7 @@ exports[`Story Snapshots: Ignored should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -62017,9 +61825,7 @@ exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -62346,9 +62152,7 @@ exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -62802,9 +62606,7 @@ exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -63258,9 +63060,7 @@ exports[`Story Snapshots: Katex should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -63682,9 +63482,7 @@ exports[`Story Snapshots: KatexArray should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -64106,9 +63904,7 @@ exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -64530,9 +64326,7 @@ exports[`Story Snapshots: KatexLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -64958,9 +64752,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -65595,9 +65387,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -66182,9 +65972,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -66964,9 +66752,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -67601,9 +67387,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -68188,9 +67972,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -68966,9 +68748,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -69423,9 +69203,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -69917,9 +69695,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -70409,9 +70185,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -70851,9 +70625,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -71376,9 +71148,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -71864,9 +71634,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -72337,9 +72105,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -73797,9 +73563,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -74254,9 +74018,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -74748,9 +74510,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -75240,9 +75000,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -75682,9 +75440,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -76207,9 +75963,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -76695,9 +76449,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -77168,9 +76920,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -78628,9 +78378,7 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -79264,9 +79012,7 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -79895,9 +79641,7 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -80531,9 +80275,7 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -81162,9 +80904,7 @@ exports[`Story Snapshots: Message should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -81572,9 +81312,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -82165,9 +81903,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -82984,9 +82720,7 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -83727,9 +83461,7 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -84483,9 +84215,7 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -85226,9 +84956,7 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -85982,9 +85710,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -86611,9 +86337,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -87547,9 +87271,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -88353,9 +88075,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -89233,9 +88953,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -89849,9 +89567,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -90542,9 +90258,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -91322,9 +91036,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -91938,9 +91650,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -92631,9 +92341,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -93411,9 +93119,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -94040,9 +93746,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -94713,9 +94417,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -95519,9 +95221,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -96400,9 +96100,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -97321,9 +97019,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -97756,9 +97452,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -98191,9 +97885,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -98626,9 +98318,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -99061,9 +98751,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -99496,9 +99184,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -99931,9 +99617,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -100366,9 +100050,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -100801,9 +100483,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -101135,9 +100815,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -102056,9 +101734,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -102491,9 +102167,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -102926,9 +102600,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -103361,9 +103033,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -103796,9 +103466,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -104231,9 +103899,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -104666,9 +104332,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -105101,9 +104765,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -105536,9 +105198,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -105869,9 +105529,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -106240,9 +105898,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -106964,9 +106620,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -107335,9 +106989,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -108059,9 +107711,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -108430,9 +108080,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -109167,9 +108815,7 @@ exports[`Story Snapshots: Pinned should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -109921,9 +109567,7 @@ exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -110675,9 +110319,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -111676,9 +111318,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -113146,9 +112786,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -114147,9 +113785,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -115618,9 +115254,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -116422,9 +116056,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -116740,9 +116372,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -117058,9 +116688,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -117392,9 +117020,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -118196,9 +117822,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -118514,9 +118138,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -118832,9 +118454,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -119280,9 +118900,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -119598,9 +119216,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -119916,9 +119532,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -120234,9 +119848,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -120682,9 +120294,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -121000,9 +120610,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -121318,9 +120926,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -121636,9 +121242,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -121969,9 +121573,7 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -122500,9 +122102,7 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -123209,9 +122809,7 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -123740,9 +123338,7 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -124449,9 +124045,7 @@ exports[`Story Snapshots: StaticAvatar should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg", }, ] @@ -124919,9 +124513,7 @@ exports[`Story Snapshots: StaticAvatarLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg", }, ] @@ -125302,9 +124894,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -125545,9 +125135,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -125788,9 +125376,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126034,9 +125620,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126277,9 +125861,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126520,9 +126102,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126763,9 +126343,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127006,9 +126584,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127249,9 +126825,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127492,9 +127066,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127735,9 +127307,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127978,9 +127548,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128221,9 +127789,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128464,9 +128030,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128707,9 +128271,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128950,9 +128512,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129193,9 +128753,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129436,9 +128994,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129679,9 +129235,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129922,9 +129476,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130165,9 +129717,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130408,9 +129958,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130651,9 +130199,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130894,9 +130440,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -131137,9 +130681,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -131380,9 +130922,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -131614,9 +131154,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -131857,9 +131395,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132100,9 +131636,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132346,9 +131880,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132589,9 +132121,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132832,9 +132362,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133075,9 +132603,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133318,9 +132844,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133561,9 +133085,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133804,9 +133326,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134047,9 +133567,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134290,9 +133808,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134533,9 +134049,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134776,9 +134290,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135019,9 +134531,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135262,9 +134772,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135505,9 +135013,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135748,9 +135254,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135991,9 +135495,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136234,9 +135736,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136477,9 +135977,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136720,9 +136218,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136963,9 +136459,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -137206,9 +136700,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -137449,9 +136941,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -137692,9 +137182,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -138005,9 +137493,7 @@ exports[`Story Snapshots: Temp should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -138472,9 +137958,7 @@ exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -138947,9 +138431,7 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -139628,9 +139110,7 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -140309,9 +139789,7 @@ exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -140779,9 +140257,7 @@ exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -141250,9 +140726,7 @@ exports[`Story Snapshots: Translated should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -141756,9 +141230,7 @@ exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -142261,9 +141733,7 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -143447,9 +142917,7 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -144633,9 +144101,7 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -145227,9 +144693,7 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -146102,9 +145566,7 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -146598,9 +146060,7 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -147050,9 +146510,7 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -147546,9 +147004,7 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -147998,9 +147454,7 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -148592,9 +148046,7 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -149467,9 +148919,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -149838,9 +149288,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -150562,9 +150010,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -150933,9 +150379,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -151657,9 +151101,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -152028,9 +151470,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -152765,9 +152205,7 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -153241,9 +152679,7 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=72", }, ] @@ -153730,9 +153166,7 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -154206,9 +153640,7 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=93.60000000000001", }, ] @@ -154695,9 +154127,7 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -157590,9 +157020,7 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -160474,9 +159902,7 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -161792,9 +161218,7 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -162868,9 +162292,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -163542,9 +162964,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -164704,9 +164124,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -165491,9 +164909,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -166165,9 +165581,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -167192,9 +166606,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -167979,9 +167391,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -168653,9 +168063,7 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -169640,9 +169048,7 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -170372,9 +169778,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -171004,9 +170408,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -171537,9 +170939,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] diff --git a/app/views/CallView/__snapshots__/index.test.tsx.snap b/app/views/CallView/__snapshots__/index.test.tsx.snap index 0b830f93ff6..d84af521010 100644 --- a/app/views/CallView/__snapshots__/index.test.tsx.snap +++ b/app/views/CallView/__snapshots__/index.test.tsx.snap @@ -125,9 +125,7 @@ exports[`Story Snapshots: ConnectedCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -999,9 +997,7 @@ exports[`Story Snapshots: ConnectingCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -1879,9 +1875,7 @@ exports[`Story Snapshots: MutedAndOnHold should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -2753,9 +2747,7 @@ exports[`Story Snapshots: MutedCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -3627,9 +3619,7 @@ exports[`Story Snapshots: OnHoldCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -4501,9 +4491,7 @@ exports[`Story Snapshots: SpeakerOn should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -5358,9 +5346,7 @@ exports[`Story Snapshots: TabletConnectedCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -6204,9 +6190,7 @@ exports[`Story Snapshots: TabletConnectingCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -7056,9 +7040,7 @@ exports[`Story Snapshots: TabletMutedAndOnHold should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -7902,9 +7884,7 @@ exports[`Story Snapshots: TabletMutedCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -8748,9 +8728,7 @@ exports[`Story Snapshots: TabletOnHoldCall should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -9594,9 +9572,7 @@ exports[`Story Snapshots: TabletSpeakerOn should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -10440,9 +10416,7 @@ exports[`Story Snapshots: TabletWithDialpad should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -11303,9 +11277,7 @@ exports[`Story Snapshots: WithDialpad should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] diff --git a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap index 792d2047ad4..bcf8e8f6146 100644 --- a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap +++ b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap @@ -142,9 +142,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -525,9 +523,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -908,9 +904,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1293,9 +1287,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1676,9 +1668,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2079,9 +2069,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2449,9 +2437,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2819,9 +2805,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] diff --git a/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap b/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap index 0a5abbdf57c..972037a4c40 100644 --- a/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap +++ b/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap @@ -771,9 +771,7 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -1888,9 +1886,7 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -2793,9 +2789,7 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -3369,9 +3363,7 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -4486,9 +4478,7 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5391,9 +5381,7 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5967,9 +5955,7 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7084,9 +7070,7 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7989,9 +7973,7 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] diff --git a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap index 389f9a62f8d..cad2bda1670 100644 --- a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap +++ b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap @@ -142,9 +142,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -666,9 +664,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1190,9 +1186,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1701,9 +1695,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2245,9 +2237,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2753,9 +2743,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -3261,9 +3249,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -3769,9 +3755,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -4277,9 +4261,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -4805,9 +4787,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -5316,9 +5296,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -5827,9 +5805,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", - }, + "headers": {}, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] From eb293b46f75cbae8b214e79cb16cb2a4215b9c6b Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 01:01:24 +0530 Subject: [PATCH 078/203] added some more unit test --- app/lib/services/sdk.test.ts | 23 ++++++++++++++++++++++- app/lib/services/sdk.ts | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index a29975e2f3e..c7903136b22 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -1,4 +1,4 @@ -import sdk, { NOTIFY_USER_EVENTS } from './sdk'; +import sdk, { NOTIFY_USER_EVENTS, normalizeResponseError } from './sdk'; const setInternalSdk = (value: any) => { (sdk as unknown as { sdk: any }).sdk = value; @@ -146,3 +146,24 @@ describe('Sdk.login', () => { expect(err.data.error).toBe('Unauthorized'); }); }); + +describe('normalizeResponseError', () => { + it('parses a JSON body and returns { status, data }', async () => { + const response = new Response(JSON.stringify({ error: 'not-found' }), { status: 404 }); + const result = await normalizeResponseError(response); + expect(result).toEqual({ status: 404, data: { error: 'not-found' } }); + }); + + it('returns { status, data: {} } when the body is not valid JSON', async () => { + const response = new Response('plain text error', { status: 500 }); + const result = await normalizeResponseError(response); + expect(result).toEqual({ status: 500, data: {} }); + }); + + it('does not consume the original response (uses clone)', async () => { + const response = new Response(JSON.stringify({ ok: true }), { status: 200 }); + await normalizeResponseError(response); + const bodyStillReadable = await response.text(); + expect(bodyStillReadable).toBe(JSON.stringify({ ok: true })); + }); +}); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 6d2ca7e2979..a391cc1aadd 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -15,7 +15,7 @@ import { type ResultFor } from '../../definitions/rest/helpers'; -async function normalizeResponseError(response: Response): Promise<{ status: number; data: any }> { +export async function normalizeResponseError(response: Response): Promise<{ status: number; data: any }> { try { const data = await response.clone().json(); return { status: response.status, data }; From cb9d3c0161c17a9379058c1b81dad2f85d609d1a Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 01:19:26 +0530 Subject: [PATCH 079/203] more changes --- app/definitions/rest/v1/commands.ts | 8 +++++++- app/definitions/rest/v1/users.ts | 16 ++++++++++++++++ app/lib/methods/getSlashCommands.ts | 3 +-- app/lib/methods/getUsersPresence.ts | 9 ++++----- app/lib/methods/loadMissedMessages.ts | 2 +- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/app/definitions/rest/v1/commands.ts b/app/definitions/rest/v1/commands.ts index 375ce9fb735..0d666ce68b5 100644 --- a/app/definitions/rest/v1/commands.ts +++ b/app/definitions/rest/v1/commands.ts @@ -1,6 +1,12 @@ -import { type IPreviewItem } from '../../ISlashCommand'; +import { type IPreviewItem, type ISlashCommandResult } from '../../ISlashCommand'; export type CommandsEndpoints = { + 'commands.list': { + GET: (params?: { count?: number; offset?: number; sort?: string }) => { + commands: ISlashCommandResult[]; + success: boolean; + }; + }; 'commands.preview': { GET: (params: { command: string; params: string; roomId: string }) => { preview?: { diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index cd857e83b50..907b208ef6d 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -1,6 +1,7 @@ import { type IAvatarSuggestion, type IProfileParams } from '../../IProfile'; import type { ITeam } from '../../ITeam'; import type { IUser, INotificationPreferences, IUserPreferences, IUserRegistered } from '../../IUser'; +import type { TUserStatus } from '../../TUserStatus'; export type UsersEndpoints = { 'users.2fa.sendEmailCode': { @@ -69,6 +70,21 @@ export type UsersEndpoints = { success: boolean; }; }; + 'users.presence': { + GET: (params?: { ids?: string; from?: string }) => { + users: { + _id: string; + name: string; + username: string; + status: TUserStatus; + utcOffset: number; + statusText?: string; + avatarETag?: string; + }[]; + full: boolean; + success: boolean; + }; + }; 'users.deleteOwnAccount': { POST: (params: { password: string; confirmRelinquish: boolean }) => { success: boolean }; }; diff --git a/app/lib/methods/getSlashCommands.ts b/app/lib/methods/getSlashCommands.ts index 8df67d989f4..39b387b7b2e 100644 --- a/app/lib/methods/getSlashCommands.ts +++ b/app/lib/methods/getSlashCommands.ts @@ -11,8 +11,7 @@ export function getSlashCommands() { return new Promise(async resolve => { try { // RC 0.60.2 - // @ts-ignore - const result = await sdk.get('commands.list'); + const result = await sdk.get('/v1/commands.list'); if (!result.success) { return resolve(); diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index edee13dfb7d..7ac74680f8e 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -7,7 +7,6 @@ import { store as reduxStore } from '../store/auxStore'; import { setActiveUsers } from '../../actions/activeUsers'; import { setUser } from '../../actions/login'; import database from '../database'; -import { type IUser } from '../../definitions'; import sdk from '../services/sdk'; import { compareServerVersion } from './helpers'; import log from './helpers/log'; @@ -69,7 +68,7 @@ export async function getUsersPresence(usersParams: string[]) { try { // RC 1.1.0 - const result = (await sdk.get('users.presence' as any, params as any)) as any; + const result = await sdk.get('/v1/users.presence', params); if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) { sdk.subscribeRaw('stream-user-presence', ['', { added: usersParams }]); @@ -79,14 +78,14 @@ export async function getUsersPresence(usersParams: string[]) { const { users } = result; const activeUsers = usersParams.reduce((ret: IActiveUsers, id) => { - const user = users.find((u: IUser) => u._id === id) ?? { _id: id, status: 'offline' }; + const user = users.find(u => u._id === id) ?? { _id: id, status: 'offline' as const, statusText: undefined }; const { _id, status, statusText } = user; if (loggedUser && loggedUser.id === _id) { reduxStore.dispatch(setUser({ status, statusText })); } - ret[_id] = { status, statusText }; + ret[_id] = { status, statusText: statusText ?? '' }; return ret; }, {}); InteractionManager.runAfterInteractions(() => { @@ -95,7 +94,7 @@ export async function getUsersPresence(usersParams: string[]) { const db = database.active; const userCollection = db.get('users'); - users.forEach(async (user: IUser) => { + users.forEach(async user => { try { const userRecord = await userCollection.find(user._id); await db.write(async () => { diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index 0bdb228d3c6..a8e7abc42d8 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -82,7 +82,7 @@ async function load({ } // RC 0.60.0 // @ts-ignore // this method dont have type - const { result } = await sdk.get('chat.syncMessages', { roomId, lastUpdate: lastOpenISOString }); + const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, lastUpdate: lastOpenISOString }); return result; } From 4638d99de0efc21725c3c16f7b57d9f0a693c2a3 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 01:58:56 +0530 Subject: [PATCH 080/203] patch for typing --- patches/@rocket.chat+rest-typings+8.4.1.patch | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 patches/@rocket.chat+rest-typings+8.4.1.patch diff --git a/patches/@rocket.chat+rest-typings+8.4.1.patch b/patches/@rocket.chat+rest-typings+8.4.1.patch new file mode 100644 index 00000000000..333b88793d3 --- /dev/null +++ b/patches/@rocket.chat+rest-typings+8.4.1.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/@rocket.chat/rest-typings/dist/v1/push.d.ts b/node_modules/@rocket.chat/rest-typings/dist/v1/push.d.ts +--- a/node_modules/@rocket.chat/rest-typings/dist/v1/push.d.ts ++++ b/node_modules/@rocket.chat/rest-typings/dist/v1/push.d.ts +@@ -1,4 +1,4 @@ +-import type { IMessage, IPushNotificationConfig, IPushTokenTypes } from '@rocket.chat/core-typings'; ++import type { IMessage, IPushNotificationConfig, IPushToken, IPushTokenTypes } from '@rocket.chat/core-typings'; + type PushTokenProps = { + id?: string; + type: IPushTokenTypes; +@@ -26,6 +26,16 @@ + success: true; + }; + }; ++ '/v1/push.token': { ++ POST: (payload: PushTokenProps) => { ++ result: IPushToken; ++ }; ++ DELETE: (payload: { ++ token: string; ++ }) => { ++ success: boolean; ++ }; ++ }; + }; + export {}; + //# sourceMappingURL=push.d.ts.map From 47aacded4c768a0b5c9aaa45388790412820a684 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 02:20:29 +0530 Subject: [PATCH 081/203] Merge branch 'develop' into feat-ddp-client From f2c0ec9124c99935aa6faa8f3a69f80a481a869d Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 02:24:21 +0530 Subject: [PATCH 082/203] test fixed --- app/lib/methods/loadMessagesForRoom.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/methods/loadMessagesForRoom.test.ts b/app/lib/methods/loadMessagesForRoom.test.ts index 2b8cd7e7882..7d211aa2e47 100644 --- a/app/lib/methods/loadMessagesForRoom.test.ts +++ b/app/lib/methods/loadMessagesForRoom.test.ts @@ -91,7 +91,7 @@ describe('loadMessagesForRoom', () => { expect(mockedSdkGet).toHaveBeenCalledTimes(2); expect(mockedSdkGet).toHaveBeenNthCalledWith( 2, - 'channels.history', + '/v1/channels.history', expect.objectContaining({ roomId: 'ROOM_ID', latest: firstBatch[firstBatch.length - 1].ts From 887f8c56fb370e762a24567e695c1934ed26fb9d Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 04:04:26 +0530 Subject: [PATCH 083/203] get settings test --- app/lib/methods/getSettings.test.ts | 151 ++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 app/lib/methods/getSettings.test.ts diff --git a/app/lib/methods/getSettings.test.ts b/app/lib/methods/getSettings.test.ts new file mode 100644 index 00000000000..331a0036dae --- /dev/null +++ b/app/lib/methods/getSettings.test.ts @@ -0,0 +1,151 @@ +import { getLoginSettings, getSettings } from './getSettings'; +import { initStore } from '../store/auxStore'; +import { mockedStore } from '../../reducers/mockedStore'; +import { selectServerClear, selectServerRequest } from '../../actions/server'; +import fetch from './helpers/fetch'; + +const STALE_SDK_HOST = 'https://old-logged-in.example'; + +jest.mock('./helpers/fetch', () => jest.fn()); +jest.mock('./helpers/log', () => ({ __esModule: true, default: jest.fn() })); +jest.mock('./getUsersPresence', () => ({ setPresenceCap: jest.fn() })); +jest.mock('../services/sdk', () => ({ + __esModule: true, + default: { subscribe: jest.fn(), getHeaders: jest.fn(() => ({})), server: 'https://old-logged-in.example' } +})); +jest.mock('../database', () => ({ + __esModule: true, + default: { + active: { + get: () => ({ query: () => ({ fetch: () => Promise.resolve([]) }) }), + write: (cb: () => Promise) => cb(), + batch: () => Promise.resolve() + }, + servers: { + get: () => ({ find: () => Promise.resolve({ autoLock: false, autoLockTime: null, update: () => Promise.resolve() }) }), + write: (cb: () => Promise) => cb() + } + } +})); + +const mockedFetch = fetch as jest.MockedFunction; + +// URL passed to the Nth fetch call (settings are fetched with a trailing &offset=). +const fetchUrl = (callIndex: number): string => mockedFetch.mock.calls[callIndex][0] as string; + +describe('getSettings', () => { + beforeAll(() => { + initStore(mockedStore); + }); + + beforeEach(() => { + jest.clearAllMocks(); + // Reset the shared, process-global server slice so state does not bleed between tests. + mockedStore.dispatch(selectServerClear()); + // Empty settings -> the pagination loop exits on the first iteration and the DB layer + // stays a no-op, so the test only observes the fetched URL. + mockedFetch.mockResolvedValue({ json: () => Promise.resolve({ success: true, total: 0, settings: [] }) } as any); + }); + + describe('builds the URL from the redux server slice', () => { + it('uses the _id query form on server version >= 7.0.0', async () => { + mockedStore.dispatch(selectServerRequest('https://new.example', '7.0.0')); + + await getSettings(); + + expect(mockedFetch).toHaveBeenCalled(); + expect(fetchUrl(0)).toContain('https://new.example/api/v1/settings.public?_id='); + }); + + it('uses the $in query form on server version < 7.0.0', async () => { + mockedStore.dispatch(selectServerRequest('https://new.example', '6.0.0')); + + await getSettings(); + + expect(fetchUrl(0)).toContain('https://new.example/api/v1/settings.public?query='); + }); + + it('re-targets the fetch at the newly selected workspace, not the previous one', async () => { + // Switch to workspace A and fetch its settings. + mockedStore.dispatch(selectServerRequest('https://workspace-a.example', '7.0.0')); + await getSettings(); + + // Switch to workspace B (e.g. adding/changing server) and fetch again. + mockedStore.dispatch(selectServerRequest('https://workspace-b.example', '7.0.0')); + await getSettings(); + + expect(fetchUrl(0)).toContain('https://workspace-a.example/api/v1/settings.public'); + expect(fetchUrl(1)).toContain('https://workspace-b.example/api/v1/settings.public'); + expect(fetchUrl(1)).not.toContain('workspace-a.example'); + }); + + it('ignores the SDK host entirely (does not fetch the old logged-in server)', async () => { + mockedStore.dispatch(selectServerRequest('https://new.example', '7.0.0')); + + await getSettings(); + + expect(fetchUrl(0)).toContain('https://new.example/api/v1/settings.public'); + expect(fetchUrl(0)).not.toContain(STALE_SDK_HOST); + }); + }); + + describe('applies the freshly fetched settings to redux after switching servers', () => { + // settings.length must equal `total` so the pagination loop in getSettings exits after one fetch. + const respondWithSiteName = (siteName: string) => ({ + json: () => + Promise.resolve({ + success: true, + total: 1, + offset: 0, + settings: [{ _id: 'Site_Name', value: siteName }] + }) + }); + + it('writes the new server\'s settings into the redux settings slice', async () => { + mockedFetch.mockResolvedValueOnce(respondWithSiteName('Workspace A') as any); + mockedStore.dispatch(selectServerRequest('https://workspace-a.example', '7.0.0')); + + await getSettings(); + + expect(mockedStore.getState().settings.Site_Name).toBe('Workspace A'); + }); + + it('overwrites stale settings from the previous server with the latest fetch', async () => { + // Seed redux with workspace A's settings. + mockedFetch.mockResolvedValueOnce(respondWithSiteName('Workspace A') as any); + mockedStore.dispatch(selectServerRequest('https://workspace-a.example', '7.0.0')); + await getSettings(); + + // Switch to workspace B; the second fetch returns workspace B's value. + mockedFetch.mockResolvedValueOnce(respondWithSiteName('Workspace B') as any); + mockedStore.dispatch(selectServerRequest('https://workspace-b.example', '7.0.0')); + await getSettings(); + + expect(fetchUrl(1)).toContain('https://workspace-b.example/api/v1/settings.public'); + expect(mockedStore.getState().settings.Site_Name).toBe('Workspace B'); + }); + }); +}); + +describe('getLoginSettings', () => { + beforeAll(() => { + initStore(mockedStore); + }); + + beforeEach(() => { + jest.clearAllMocks(); + mockedStore.dispatch(selectServerClear()); + mockedFetch.mockResolvedValue({ json: () => Promise.resolve({ success: true, settings: [] }) } as any); + }); + + it('builds the URL from its server argument, not the redux server slice', async () => { + // Redux points at a different (already logged-in) server... + mockedStore.dispatch(selectServerRequest('https://other.example', '7.0.0')); + + // ...but getLoginSettings must target the workspace passed as an argument. + await getLoginSettings({ server: 'https://added.workspace', serverVersion: '7.0.0' }); + + expect(fetchUrl(0)).toContain('https://added.workspace/api/v1/settings.public?_id='); + expect(fetchUrl(0)).not.toContain('other.example'); + }); +}); From 685eab3294d8f93a5fce2b0c9e2a81f4577a20b0 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Thu, 28 May 2026 22:37:36 +0000 Subject: [PATCH 084/203] chore: format code and fix lint issues --- app/lib/methods/getSettings.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/methods/getSettings.test.ts b/app/lib/methods/getSettings.test.ts index 331a0036dae..0ed22ac2e92 100644 --- a/app/lib/methods/getSettings.test.ts +++ b/app/lib/methods/getSettings.test.ts @@ -101,7 +101,7 @@ describe('getSettings', () => { }) }); - it('writes the new server\'s settings into the redux settings slice', async () => { + it("writes the new server's settings into the redux settings slice", async () => { mockedFetch.mockResolvedValueOnce(respondWithSiteName('Workspace A') as any); mockedStore.dispatch(selectServerRequest('https://workspace-a.example', '7.0.0')); From 84bcdc2daf46884885426b981a6a864951c3efc6 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 04:26:52 +0530 Subject: [PATCH 085/203] retrigger ci --- app/lib/methods/getSettings.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/methods/getSettings.test.ts b/app/lib/methods/getSettings.test.ts index 331a0036dae..7b406ea4b07 100644 --- a/app/lib/methods/getSettings.test.ts +++ b/app/lib/methods/getSettings.test.ts @@ -12,7 +12,7 @@ jest.mock('./getUsersPresence', () => ({ setPresenceCap: jest.fn() })); jest.mock('../services/sdk', () => ({ __esModule: true, default: { subscribe: jest.fn(), getHeaders: jest.fn(() => ({})), server: 'https://old-logged-in.example' } -})); +})); jest.mock('../database', () => ({ __esModule: true, default: { From 672146c9df482add9c42d8f91f1f2e7e25926039 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 04:26:56 +0530 Subject: [PATCH 086/203] revert --- app/lib/methods/getSettings.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/methods/getSettings.test.ts b/app/lib/methods/getSettings.test.ts index 7b406ea4b07..331a0036dae 100644 --- a/app/lib/methods/getSettings.test.ts +++ b/app/lib/methods/getSettings.test.ts @@ -12,7 +12,7 @@ jest.mock('./getUsersPresence', () => ({ setPresenceCap: jest.fn() })); jest.mock('../services/sdk', () => ({ __esModule: true, default: { subscribe: jest.fn(), getHeaders: jest.fn(() => ({})), server: 'https://old-logged-in.example' } -})); +})); jest.mock('../database', () => ({ __esModule: true, default: { From c64f908f4aa9f07109385ef0e6a86a30b4c3f40c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 29 May 2026 14:44:36 +0530 Subject: [PATCH 087/203] improvements --- app/lib/services/sdk.ts | 1 + app/views/ThreadMessagesView/index.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index a391cc1aadd..9b69dee405e 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -55,6 +55,7 @@ class Sdk { private headers: Record = { ...defaultHeaders } as Record; initialize(server: string): DDPSDK { + this.code = null; this.sdk = DDPSDK.create(server); this.sdk.subscribeNotifyUser = () => this.subscribeNotifyUser(); diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index ee701d2cecf..c587b1a319b 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -353,7 +353,7 @@ class ThreadMessagesView extends React.Component Date: Fri, 29 May 2026 15:07:01 +0530 Subject: [PATCH 088/203] test update --- .../tests/room/room-last-message-thread-50-plus.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.maestro/tests/room/room-last-message-thread-50-plus.yaml b/.maestro/tests/room/room-last-message-thread-50-plus.yaml index 4a212d38fb2..3c79beaf672 100644 --- a/.maestro/tests/room/room-last-message-thread-50-plus.yaml +++ b/.maestro/tests/room/room-last-message-thread-50-plus.yaml @@ -13,10 +13,12 @@ tags: PASSWORD: ${output.account.adminPassword} # should open a room where the last message is a thread containing more than 50 messages. -- runFlow: - file: '../../helpers/navigate-to-room.yaml' - env: - ROOM: 'maestro_test_load_threads' +- tapOn: + id: 'rooms-list-view-item-maestro_test_load_threads' +- extendedWaitUntil: + visible: + id: 'room-view-title-maestro_test_load_threads' + timeout: 60000 - extendedWaitUntil: visible: text: '.*message 50.*' From c0642704b95fe8bf3102e1d80563370963f640b6 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 02:44:16 +0530 Subject: [PATCH 089/203] changes --- app/lib/methods/helpers/defaultHeaders.ts | 19 +++++++++++++++++++ app/lib/methods/helpers/fetch.ts | 20 ++------------------ app/lib/methods/loadMissedMessages.ts | 2 +- app/lib/services/sdk.ts | 9 +++++---- 4 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 app/lib/methods/helpers/defaultHeaders.ts diff --git a/app/lib/methods/helpers/defaultHeaders.ts b/app/lib/methods/helpers/defaultHeaders.ts new file mode 100644 index 00000000000..7b199b25411 --- /dev/null +++ b/app/lib/methods/helpers/defaultHeaders.ts @@ -0,0 +1,19 @@ +import { Platform } from 'react-native'; +import DeviceInfo from 'react-native-device-info'; + +export interface CustomHeaders { + 'User-Agent'?: string; + Authorization?: string; + 'Content-Type'?: string; + 'X-Auth-Token'?: string; + 'X-User-Id'?: string; +} + +// this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js" +export const headers: CustomHeaders = { + 'User-Agent': `RC Mobile; ${ + Platform.OS + } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` +}; + +export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index ed578e37af2..ec803409848 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -1,17 +1,9 @@ -import { Platform } from 'react-native'; -import DeviceInfo from 'react-native-device-info'; - import sdk from '../../services/sdk'; +import { type CustomHeaders, headers, BASIC_AUTH_KEY } from './defaultHeaders'; export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; -interface CustomHeaders { - 'User-Agent'?: string; - Authorization?: string; - 'Content-Type'?: string; - 'X-Auth-Token'?: string; - 'X-User-Id'?: string; -} +export { headers, BASIC_AUTH_KEY }; interface IOptions { headers?: CustomHeaders; @@ -20,17 +12,9 @@ interface IOptions { body?: any; } -// this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js" -export const headers: CustomHeaders = { - 'User-Agent': `RC Mobile; ${ - Platform.OS - } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` -}; - export const setBasicAuth = (basicAuth: string | null): void => { sdk.setBasicAuth(basicAuth); }; -export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; export default (url: string, options: IOptions = {}): Promise => { const customOptions = { ...options, headers: { ...sdk.getHeaders(), ...(options.headers || {}) } }; diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index a8e7abc42d8..fc11c55d0c0 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -109,7 +109,7 @@ export async function loadMissedMessages(args: { await updateMessages({ rid: args.rid, update: updated, remove: deleted }); if (deletedNext || updatedNext) { - loadMissedMessages({ + await loadMissedMessages({ rid: args.rid, lastOpen: args.lastOpen, updatedNext, diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 9b69dee405e..c89ac7b3b5b 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -6,7 +6,7 @@ import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; import { compareServerVersion, random } from '../methods/helpers'; import UserPreferences from '../methods/userPreferences'; -import { BASIC_AUTH_KEY, headers as defaultHeaders } from '../methods/helpers/fetch'; +import { BASIC_AUTH_KEY, headers as defaultHeaders } from '../methods/helpers/defaultHeaders'; import { type Serialized, type MatchPathPattern, @@ -56,6 +56,7 @@ class Sdk { initialize(server: string): DDPSDK { this.code = null; + this.headers = { ...defaultHeaders } as Record; this.sdk = DDPSDK.create(server); this.sdk.subscribeNotifyUser = () => this.subscribeNotifyUser(); @@ -143,7 +144,7 @@ class Sdk { const sdk = this.ensureInitialized(); try { // @ts-ignore - return sdk.rest.get(endpoint, params, { + return await sdk.rest.get(endpoint, params, { headers: this.headers }); } catch (e: any) { @@ -219,7 +220,7 @@ class Sdk { const sdk = this.ensureInitialized(); try { // @ts-ignore - return sdk.rest.delete(endpoint, params, { + return await sdk.rest.delete(endpoint, params, { headers: this.headers }); } catch (e: any) { @@ -261,7 +262,7 @@ class Sdk { throw new Error('SDK not initialized'); } const [method, ...params] = args; - const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, this.code || ''); + const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(this.code ? [this.code] : [])); return resolve(result); } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { From 1a151878b0b413dbdf443d463096adec9e247c88 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 03:18:37 +0530 Subject: [PATCH 090/203] revert connect change --- app/lib/services/connect.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index bf953915975..8824d05c658 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -412,11 +412,13 @@ async function getWebsocketInfo({ return { success: true }; - } catch { - return { - success: false, - message: I18n.t('Websocket_disabled', { contact: I18n.t('Contact_your_server_admin') }) - }; + } catch (err: any) { + if (err?.message?.includes('400')) { + return { + success: false, + message: I18n.t('Websocket_disabled', { contact: I18n.t('Contact_your_server_admin') }) + }; + } } } From 305984d4aeaa0c1be333b1e6cc38dfb84e7c4d2e Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 03:19:29 +0530 Subject: [PATCH 091/203] fix --- app/lib/services/connect.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 8824d05c658..e0eece70b4f 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -419,6 +419,8 @@ async function getWebsocketInfo({ message: I18n.t('Websocket_disabled', { contact: I18n.t('Contact_your_server_admin') }) }; } + + return { success: true }; } } From 0283cbbf24353da9486dc73aea6a27dcadafeb66 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 03:28:39 +0530 Subject: [PATCH 092/203] fix for 2fa --- app/lib/services/sdk.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index c89ac7b3b5b..8ba7581c34b 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -185,23 +185,22 @@ class Sdk { return resolve(result); } catch (e: any) { // @rocket.chat/api-client rejects with the raw fetch Response on REST errors. - // Normalize to { status, data } so callers (and the totp branch below) can read e.data.* + // Normalize to { status, data } so callers can read e.data.* const normalized = !isMethodCall && e instanceof Response ? await normalizeResponseError(e) : e; - const errorType = isMethodCall ? normalized?.error : normalized?.data?.errorType; - const totpInvalid = 'totp-invalid'; - const totpRequired = 'totp-required'; - if ([totpInvalid, totpRequired].includes(errorType)) { - const { details } = isMethodCall ? normalized : normalized?.data; - try { - await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); - return resolve(this.post(endpoint, params)); - } catch { - // twoFactor was canceled - return resolve({} as any); + if (isMethodCall) { + // DDP-over-REST errors arrive as EJSON objects (HTTP 200), not Response instances, + // so the api-client's handleTwoFactorChallenge never intercepts them. + const errorType = normalized?.error; + if (errorType === 'totp-required' || errorType === 'totp-invalid') { + try { + await twoFactor({ method: normalized?.details?.method, invalid: errorType === 'totp-invalid' }); + return resolve(this.post(endpoint, params)); + } catch { + return resolve({} as any); + } } - } else { - reject(normalized); } + reject(normalized); } }); } From c8a415c677cfe8f7deddfd4612c99310f22df836 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 03:32:42 +0530 Subject: [PATCH 093/203] revert dm delete --- app/lib/services/restApi.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 4778a6ae0bf..f9ad0b38595 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -549,13 +549,9 @@ export const leaveRoom = (roomId: string, t: RoomTypes) => // RC 0.48.0 sdk.post(`/v1/${roomTypeToApiType(t)}.leave`, { roomId }); -export const deleteRoom = (roomId: string, t: RoomTypes) => { - if (t === 'd') { - return sdk.post(`/v1/im.close`, { roomId }); - } - - return sdk.post(`/v1/${roomTypeToApiType(t)}.delete`, { roomId }); -}; +export const deleteRoom = (roomId: string, t: RoomTypes) => + // RC 0.49.0 + sdk.post(`/v1/${roomTypeToApiType(t)}.delete`, { roomId }); export const toggleMuteUserInRoom = (rid: string, username: string, userId: string, mute: boolean) => { const serverVersion = reduxStore.getState().server.version; From fccba68d57d0532fa6293efd1524d7b9ecb49735 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 03:46:34 +0530 Subject: [PATCH 094/203] test fix --- app/lib/services/connect.test.ts | 299 +++++++++++++++++++++++++++---- 1 file changed, 261 insertions(+), 38 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index ad4d78ee7f0..9ccd3f75ab3 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -1,40 +1,134 @@ -import { checkAndReopen, determineAuthType, disconnect } from './connect'; +import { checkAndReopen, connect, determineAuthType, disconnect } from './connect'; import { mediaSessionInstance } from './voip/MediaSessionInstance'; +import { pendingHangups } from './voip/pendingHangups'; + +jest.mock('./voip/MediaSessionInstance', () => ({ + mediaSessionInstance: { reset: jest.fn(), drainPendingHangups: jest.fn() } +})); + +// Mock the isIOS helper +jest.mock('../methods/helpers/deviceInfo', () => ({ + ...jest.requireActual('../methods/helpers/deviceInfo'), + isIOS: false +})); + +// --- SDK mock --- +// Captures the single 'connection' status listener registered by connect(). +// All event-driven tests invoke it directly with a status string. +const mockConnectionOn = jest.fn(); +const mockConnectionConnect = jest.fn().mockResolvedValue(undefined); +const mockConnectionCheckAndReopen = jest.fn().mockResolvedValue(true); +const mockSdkInitialize = jest.fn().mockResolvedValue(undefined); +const mockSdkOnCollection = jest.fn(); +const mockSdkDisconnect = jest.fn(); jest.mock('./sdk', () => { - const state: { current: any } = { current: undefined }; + const state: { server: string | undefined; currentEnabled: boolean } = { server: undefined, currentEnabled: true }; return { __esModule: true, default: { - get current() { - return state.current; + get server() { + return state.server; }, - disconnect: jest.fn(), - probe: jest.fn().mockResolvedValue(true), - forceReopen: jest.fn().mockResolvedValue(true) + disconnect: (...args: any[]) => mockSdkDisconnect(...args), + initialize: (s: string) => mockSdkInitialize(s), + onCollection: (...args: any[]) => mockSdkOnCollection(...args), + get current() { + if (!state.currentEnabled) return undefined; + return { + connection: { + on: (event: string, cb: any) => mockConnectionOn(event, cb), + connect: () => mockConnectionConnect(), + checkAndReopen: () => mockConnectionCheckAndReopen() + } + }; + } + }, + __setServer: (v: string | undefined) => { + state.server = v; }, - __setCurrent: (value: any) => { - state.current = value; + __setCurrentEnabled: (v: boolean) => { + state.currentEnabled = v; } }; }); const sdkMock = jest.requireMock('./sdk') as { - default: { probe: jest.Mock; forceReopen: jest.Mock }; - __setCurrent: (v: any) => void; + __setServer: (v: string | undefined) => void; + __setCurrentEnabled: (v: boolean) => void; }; -const setSdkCurrent = (value: any) => sdkMock.__setCurrent(value); -jest.mock('./voip/MediaSessionInstance', () => ({ - mediaSessionInstance: { reset: jest.fn() } +// --- Store mock --- +type MockStoreState = { + meteor: { connected: boolean }; + login: { user: unknown; isAuthenticated: boolean }; + settings: Record; + server: { version: string }; +}; +const mockStoreGetState = jest.fn(() => ({ + meteor: { connected: false }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } +})); +const mockStoreDispatch = jest.fn(); +const mockStoreSubscribe = jest.fn<() => void, [() => void]>(() => () => undefined); + +jest.mock('../store/auxStore', () => ({ + store: { + getState: () => mockStoreGetState(), + dispatch: (action: unknown) => mockStoreDispatch(action), + subscribe: (cb: () => void) => mockStoreSubscribe(cb) + } })); -// Mock the isIOS helper -jest.mock('../methods/helpers/deviceInfo', () => ({ - ...jest.requireActual('../methods/helpers/deviceInfo'), - isIOS: false +jest.mock('../database', () => ({ + __esModule: true, + default: { setActiveDB: jest.fn(), active: { get: jest.fn() } } })); +jest.mock('../methods/subscribeRooms', () => ({ unsubscribeRooms: jest.fn() })); +jest.mock('../methods/getSettings', () => ({ getSettings: jest.fn().mockResolvedValue(undefined) })); +jest.mock('../methods/helpers/events', () => ({ + __esModule: true, + default: { emit: jest.fn(), on: jest.fn(), removeListener: jest.fn() } +})); +jest.mock('../methods/helpers/protectedFunction', () => ({ __esModule: true, default: (fn: any) => fn })); +jest.mock('../methods/helpers/log', () => ({ __esModule: true, default: jest.fn() })); +jest.mock('../methods/setUser', () => ({ _setUser: jest.fn(), _activeUsers: { activeUsers: {} }, _setUserTimer: {} })); +jest.mock('../methods/getRoles', () => ({ onRolesChanged: jest.fn() })); +jest.mock('../methods/getUsersPresence', () => ({ setPresenceCap: jest.fn() })); + +jest.mock('../../actions/connect', () => ({ + connectRequest: jest.fn().mockReturnValue({ type: 'CONNECT_REQUEST' }), + connectSuccess: jest.fn().mockReturnValue({ type: 'CONNECT_SUCCESS' }), + disconnect: jest.fn().mockReturnValue({ type: 'DISCONNECT' }) +})); +jest.mock('../../actions/login', () => ({ + loginRequest: jest.fn().mockReturnValue({ type: 'LOGIN_REQUEST' }), + logout: jest.fn().mockReturnValue({ type: 'LOGOUT' }), + setLoginServices: jest.fn().mockReturnValue({ type: 'SET_LOGIN_SERVICES' }), + setUser: jest.fn().mockReturnValue({ type: 'SET_USER' }) +})); +jest.mock('../../actions/settings', () => ({ updateSettings: jest.fn().mockReturnValue({ type: 'UPDATE_SETTINGS' }) })); +jest.mock('../../actions/permissions', () => ({ updatePermission: jest.fn().mockReturnValue({ type: 'UPDATE_PERMISSION' }) })); +jest.mock('../../actions/activeUsers', () => ({ setActiveUsers: jest.fn().mockReturnValue({ type: 'SET_ACTIVE_USERS' }) })); + +// --- Helpers --- +const flushMicrotasks = async (): Promise => { + for (let i = 0; i < 5; i += 1) { + // eslint-disable-next-line no-await-in-loop + await Promise.resolve(); + } +}; + +/** Returns the status-handler callback registered via connection.on('connection', cb). */ +const getCapturedConnectionListener = (): (status: string) => void => { + const call = mockConnectionOn.mock.calls.find(([event]) => event === 'connection'); + if (!call) throw new Error('connection listener was never registered'); + return call[1]; +}; + interface IServices { [index: string]: string | boolean; name: string; @@ -44,6 +138,7 @@ interface IServices { service: string; } +// ───────────────────────────────────────────────────────────────────────────── describe('determineAuthType', () => { beforeEach(() => { jest.clearAllMocks(); @@ -86,7 +181,7 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('saml'); // Should continue to next conditions + expect(result).toBe('saml'); }); it('should not return oauth_custom when custom is false', () => { @@ -99,7 +194,7 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('saml'); // Should continue to next conditions + expect(result).toBe('saml'); }); }); @@ -144,7 +239,7 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('not_supported'); // Should fall through to not_supported since isIOS is mocked as false + expect(result).toBe('not_supported'); }); it('should return not_supported when service is apple and name is empty but isIOS is false', () => { @@ -157,7 +252,7 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('not_supported'); // Should fall through to not_supported since isIOS is mocked as false + expect(result).toBe('not_supported'); }); }); @@ -258,7 +353,7 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('oauth'); // name 'github' should be used + expect(result).toBe('oauth'); }); it('should use service as authName when name is empty', () => { @@ -271,7 +366,7 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('oauth'); // service 'facebook' should be used + expect(result).toBe('oauth'); }); it('should use service as authName when name is null', () => { @@ -284,14 +379,14 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('oauth'); // service 'google' should be used + expect(result).toBe('oauth'); }); }); describe('priority order', () => { it('should prioritize oauth_custom over other types', () => { const services: IServices = { - name: 'github', // This would normally return 'oauth' + name: 'github', custom: true, showButton: true, buttonLabelText: 'Custom GitHub', @@ -299,12 +394,12 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('oauth_custom'); // Should return oauth_custom first + expect(result).toBe('oauth_custom'); }); it('should prioritize saml over oauth', () => { const services: IServices = { - name: 'github', // This would normally return 'oauth' + name: 'github', custom: false, showButton: true, buttonLabelText: 'SAML GitHub', @@ -312,12 +407,12 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('saml'); // Should return saml before checking for oauth + expect(result).toBe('saml'); }); it('should prioritize cas over oauth', () => { const services: IServices = { - name: 'github', // This would normally return 'oauth' + name: 'github', custom: false, showButton: true, buttonLabelText: 'CAS GitHub', @@ -325,11 +420,12 @@ describe('determineAuthType', () => { }; const result = determineAuthType(services); - expect(result).toBe('cas'); // Should return cas before checking for oauth + expect(result).toBe('cas'); }); }); }); +// ───────────────────────────────────────────────────────────────────────────── describe('VoIP media session lifecycle (disconnect)', () => { it('calls mediaSessionInstance.reset when disconnect runs', () => { disconnect(); @@ -337,26 +433,153 @@ describe('VoIP media session lifecycle (disconnect)', () => { }); }); +// ───────────────────────────────────────────────────────────────────────────── +describe('connect — connection status handler', () => { + const SERVER = 'https://example.com'; + + beforeEach(() => { + jest.clearAllMocks(); + sdkMock.__setServer(undefined); + pendingHangups.clear(); + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + }); + + it('returns early without initializing when server is already active', async () => { + sdkMock.__setServer(SERVER); + await connect({ server: SERVER }); + expect(mockSdkInitialize).not.toHaveBeenCalled(); + }); + + it('dispatches connectSuccess and loginRequest(resume) on reconnect when user has a token', async () => { + await connect({ server: SERVER }); + const listener = getCapturedConnectionListener(); + const connectSuccessMock = jest.requireMock('../../actions/connect').connectSuccess; + const loginRequestMock = jest.requireMock('../../actions/login').loginRequest; + + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: { token: 'auth-token-123' }, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + + listener('connected'); + + expect(connectSuccessMock).toHaveBeenCalled(); + expect(loginRequestMock).toHaveBeenCalledWith({ resume: 'auth-token-123' }, false); + }); + + it('does not dispatch connectSuccess when socket was already marked connected', async () => { + await connect({ server: SERVER }); + const listener = getCapturedConnectionListener(); + const connectSuccessMock = jest.requireMock('../../actions/connect').connectSuccess; + + mockStoreGetState.mockReturnValue({ + meteor: { connected: true }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + + listener('connected'); + + expect(connectSuccessMock).not.toHaveBeenCalled(); + }); + + it('does not dispatch loginRequest when user has no token', async () => { + await connect({ server: SERVER }); + const listener = getCapturedConnectionListener(); + const loginRequestMock = jest.requireMock('../../actions/login').loginRequest; + + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: { token: null }, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + + listener('connected'); + + expect(loginRequestMock).not.toHaveBeenCalled(); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +describe('connect — pendingHangups drain on reconnect', () => { + const SERVER = 'https://example.com'; + + beforeEach(() => { + jest.clearAllMocks(); + sdkMock.__setServer(undefined); + pendingHangups.clear(); + // isAuthenticated + connected = true so awaitDdpLoggedIn resolves immediately + mockStoreGetState.mockReturnValue({ + meteor: { connected: true }, + login: { user: null, isAuthenticated: true }, + settings: {}, + server: { version: '6.0.0' } + }); + }); + + it('drains pendingHangups via mediaSessionInstance after disconnected → connected', async () => { + pendingHangups.record('call-a'); + await connect({ server: SERVER }); + const listener = getCapturedConnectionListener(); + + listener('disconnected'); + listener('connected'); + await flushMicrotasks(); + + expect(mediaSessionInstance.drainPendingHangups).toHaveBeenCalledTimes(1); + }); + + it('does not drain when connected fires without a prior disconnected', async () => { + pendingHangups.record('call-a'); + await connect({ server: SERVER }); + const listener = getCapturedConnectionListener(); + + listener('connected'); + await flushMicrotasks(); + + expect(mediaSessionInstance.drainPendingHangups).not.toHaveBeenCalled(); + }); + + it('skips drainPendingHangups when pendingHangups is empty', async () => { + await connect({ server: SERVER }); + const listener = getCapturedConnectionListener(); + + listener('disconnected'); + listener('connected'); + await flushMicrotasks(); + + expect(mediaSessionInstance.drainPendingHangups).not.toHaveBeenCalled(); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── describe('checkAndReopen', () => { afterEach(() => { - setSdkCurrent(undefined); + sdkMock.__setCurrentEnabled(true); }); it('resolves false when sdk.current is undefined', async () => { - setSdkCurrent(undefined); + sdkMock.__setCurrentEnabled(false); await expect(checkAndReopen()).resolves.toBe(false); }); it('delegates to connection.checkAndReopen() and returns its result', async () => { - const checkAndReopenMock = jest.fn().mockResolvedValue(true); - setSdkCurrent({ connection: { checkAndReopen: checkAndReopenMock } }); + mockConnectionCheckAndReopen.mockResolvedValueOnce(true); await expect(checkAndReopen()).resolves.toBe(true); - expect(checkAndReopenMock).toHaveBeenCalledTimes(1); + expect(mockConnectionCheckAndReopen).toHaveBeenCalledTimes(1); }); it('forwards false when connection.checkAndReopen() resolves false', async () => { - const checkAndReopenMock = jest.fn().mockResolvedValue(false); - setSdkCurrent({ connection: { checkAndReopen: checkAndReopenMock } }); + mockConnectionCheckAndReopen.mockResolvedValueOnce(false); await expect(checkAndReopen()).resolves.toBe(false); }); }); From 040ed9017d81a165f8173cdf5bb81345c165f031 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Sat, 30 May 2026 22:18:18 +0000 Subject: [PATCH 095/203] chore: format code and fix lint issues --- app/lib/services/connect.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 9ccd3f75ab3..f641704b4f4 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -123,7 +123,7 @@ const flushMicrotasks = async (): Promise => { }; /** Returns the status-handler callback registered via connection.on('connection', cb). */ -const getCapturedConnectionListener = (): (status: string) => void => { +const getCapturedConnectionListener = (): ((status: string) => void) => { const call = mockConnectionOn.mock.calls.find(([event]) => event === 'connection'); if (!call) throw new Error('connection listener was never registered'); return call[1]; From 8d69734d98579218fbf030f58f7eabc2f07904c5 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 04:00:39 +0530 Subject: [PATCH 096/203] fix test --- app/lib/services/connect.test.ts | 34 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 9ccd3f75ab3..47ba42129a7 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -6,15 +6,14 @@ jest.mock('./voip/MediaSessionInstance', () => ({ mediaSessionInstance: { reset: jest.fn(), drainPendingHangups: jest.fn() } })); -// Mock the isIOS helper jest.mock('../methods/helpers/deviceInfo', () => ({ ...jest.requireActual('../methods/helpers/deviceInfo'), isIOS: false })); // --- SDK mock --- -// Captures the single 'connection' status listener registered by connect(). -// All event-driven tests invoke it directly with a status string. +// The new DDP SDK registers a single connection.on('connection', cb) listener. +// All event-driven tests invoke getCapturedConnectionListener() to drive it. const mockConnectionOn = jest.fn(); const mockConnectionConnect = jest.fn().mockResolvedValue(undefined); const mockConnectionCheckAndReopen = jest.fn().mockResolvedValue(true); @@ -440,7 +439,6 @@ describe('connect — connection status handler', () => { beforeEach(() => { jest.clearAllMocks(); sdkMock.__setServer(undefined); - pendingHangups.clear(); mockStoreGetState.mockReturnValue({ meteor: { connected: false }, login: { user: null, isAuthenticated: false }, @@ -526,19 +524,19 @@ describe('connect — pendingHangups drain on reconnect', () => { }); }); - it('drains pendingHangups via mediaSessionInstance after disconnected → connected', async () => { + it('drains pendingHangups via mediaSessionInstance after close → connected', async () => { pendingHangups.record('call-a'); await connect({ server: SERVER }); const listener = getCapturedConnectionListener(); - listener('disconnected'); + listener('closed'); listener('connected'); await flushMicrotasks(); expect(mediaSessionInstance.drainPendingHangups).toHaveBeenCalledTimes(1); }); - it('does not drain when connected fires without a prior disconnected', async () => { + it('does not drain when "connected" fires without a prior "close"', async () => { pendingHangups.record('call-a'); await connect({ server: SERVER }); const listener = getCapturedConnectionListener(); @@ -553,12 +551,32 @@ describe('connect — pendingHangups drain on reconnect', () => { await connect({ server: SERVER }); const listener = getCapturedConnectionListener(); - listener('disconnected'); + listener('closed'); listener('connected'); await flushMicrotasks(); expect(mediaSessionInstance.drainPendingHangups).not.toHaveBeenCalled(); }); + + it('stops the previous pendingHangups connected listener when connect runs again', async () => { + // Arm the drain on the first connection + pendingHangups.record('call-a'); + await connect({ server: SERVER }); + const firstListener = getCapturedConnectionListener(); + firstListener('closed'); + + // Reconnect to a new server — new closure resets pendingHangupsDrainArmed to false + sdkMock.__setServer(undefined); + mockConnectionOn.mockClear(); + await connect({ server: 'https://other.example.com' }); + const secondListener = getCapturedConnectionListener(); + + // Firing 'connected' on the new connection should NOT drain (armed state was not carried over) + secondListener('connected'); + await flushMicrotasks(); + + expect(mediaSessionInstance.drainPendingHangups).not.toHaveBeenCalled(); + }); }); // ───────────────────────────────────────────────────────────────────────────── From 243e71cfd02f5301166e82de5dd118b410edb0a7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 17:29:03 +0530 Subject: [PATCH 097/203] test update --- .../__snapshots__/Message.test.tsx.snap | 1200 ++++++++++++----- .../__snapshots__/index.test.tsx.snap | 56 +- .../__snapshots__/Item.test.tsx.snap | 32 +- .../__snapshots__/LoadMore.test.tsx.snap | 36 +- .../__snapshots__/Item.test.tsx.snap | 48 +- 5 files changed, 1029 insertions(+), 343 deletions(-) diff --git a/app/containers/message/__snapshots__/Message.test.tsx.snap b/app/containers/message/__snapshots__/Message.test.tsx.snap index d0b5a65168d..fd100c8e6f4 100644 --- a/app/containers/message/__snapshots__/Message.test.tsx.snap +++ b/app/containers/message/__snapshots__/Message.test.tsx.snap @@ -243,7 +243,9 @@ exports[`Story Snapshots: Archived should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -703,7 +705,9 @@ exports[`Story Snapshots: ArchivedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -1171,7 +1175,9 @@ exports[`Story Snapshots: AttachmentWithTextAndLink should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -1940,7 +1946,9 @@ exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapsh source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -2709,7 +2717,9 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/no.avatar?format=png&size=72", }, ] @@ -3164,7 +3174,9 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5011,7 +5023,9 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5466,7 +5480,9 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5934,7 +5950,9 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -6389,7 +6407,9 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -6857,7 +6877,9 @@ exports[`Story Snapshots: BlockQuote should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7345,7 +7367,9 @@ Testing block quote" source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7892,7 +7916,9 @@ exports[`Story Snapshots: BlockQuoteLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -8380,7 +8406,9 @@ Testing block quote" source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -8927,7 +8955,9 @@ exports[`Story Snapshots: Broadcast should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -9515,7 +9545,9 @@ exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -10103,7 +10135,9 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -10710,7 +10744,9 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -11532,7 +11568,9 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -12139,7 +12177,9 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -12961,7 +13001,9 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -13570,7 +13612,9 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -14533,7 +14577,9 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -15142,7 +15188,9 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -16105,7 +16153,9 @@ exports[`Story Snapshots: ColoredAttachments should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -17335,7 +17385,9 @@ exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -18565,7 +18617,9 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -19375,7 +19429,9 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -20456,7 +20512,9 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -21266,7 +21324,9 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -22347,7 +22407,9 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -22864,7 +22926,9 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -23587,7 +23651,9 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -24099,7 +24165,9 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -24567,7 +24635,9 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=93.60000000000001", }, ] @@ -25084,7 +25154,9 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -25807,7 +25879,9 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=93.60000000000001", }, ] @@ -26319,7 +26393,9 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -26787,7 +26863,9 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -27363,7 +27441,9 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -27941,7 +28021,9 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -28519,7 +28601,9 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -29110,7 +29194,9 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -29686,7 +29772,9 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -30264,7 +30352,9 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -30842,7 +30932,9 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -31433,7 +31525,9 @@ exports[`Story Snapshots: Edited should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -32197,7 +32291,9 @@ exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -32961,7 +33057,9 @@ exports[`Story Snapshots: Editing should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -33429,7 +33527,9 @@ exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -33897,7 +33997,9 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -34351,7 +34453,9 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -34786,7 +34890,9 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -35334,7 +35440,9 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -35794,7 +35902,9 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -36273,7 +36383,9 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -36798,7 +36910,9 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -37252,7 +37366,9 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -37687,7 +37803,9 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -38235,7 +38353,9 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -38695,7 +38815,9 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -39174,7 +39296,9 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -39699,7 +39823,9 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -40522,7 +40648,9 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -41605,7 +41733,9 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -41915,7 +42045,9 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -42447,7 +42579,9 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -42887,7 +43021,9 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -43481,7 +43617,9 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -44377,7 +44515,9 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -45200,7 +45340,9 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -46283,7 +46425,9 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -46593,7 +46737,9 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -47125,7 +47271,9 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -47565,7 +47713,9 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -48159,7 +48309,9 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -48968,7 +49120,9 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -49622,7 +49776,9 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -50363,7 +50519,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -50957,7 +51115,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -51551,7 +51711,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -52145,7 +52307,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -53264,7 +53428,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -53858,7 +54024,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -54452,7 +54620,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -55046,7 +55216,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -56152,7 +56324,9 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -56914,7 +57088,9 @@ exports[`Story Snapshots: FullName should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -57382,7 +57558,9 @@ exports[`Story Snapshots: FullNameLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -57850,7 +58028,9 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -58305,7 +58485,9 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=72", }, ] @@ -59208,7 +59390,9 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -59676,7 +59860,9 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -60131,7 +60317,9 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=93.60000000000001", }, ] @@ -61034,7 +61222,9 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -61500,7 +61690,9 @@ exports[`Story Snapshots: Ignored should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -61825,7 +62017,9 @@ exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -62152,7 +62346,9 @@ exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -62606,7 +62802,9 @@ exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -63060,7 +63258,9 @@ exports[`Story Snapshots: Katex should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -63482,7 +63682,9 @@ exports[`Story Snapshots: KatexArray should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -63904,7 +64106,9 @@ exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -64326,7 +64530,9 @@ exports[`Story Snapshots: KatexLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -64752,7 +64958,9 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -65387,7 +65595,9 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -65972,7 +66182,9 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -66752,7 +66964,9 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -67387,7 +67601,9 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -67972,7 +68188,9 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -68748,7 +68966,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -69203,7 +69423,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -69695,7 +69917,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -70185,7 +70409,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -70625,7 +70851,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -71148,7 +71376,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -71634,7 +71864,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -72105,7 +72337,9 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -73563,7 +73797,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -74018,7 +74254,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -74510,7 +74748,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -75000,7 +75240,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -75440,7 +75682,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -75963,7 +76207,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -76449,7 +76695,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -76920,7 +77168,9 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, ] @@ -78378,7 +78628,9 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -79012,7 +79264,9 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -79641,7 +79895,9 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -80275,7 +80531,9 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -80904,7 +81162,9 @@ exports[`Story Snapshots: Message should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -81312,7 +81572,9 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -81903,7 +82165,9 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -82720,7 +82984,9 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -83461,7 +83727,9 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -84215,7 +84483,9 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -84956,7 +85226,9 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -85710,7 +85982,9 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -86337,7 +86611,9 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -87271,7 +87547,9 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -88075,7 +88353,9 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -88953,7 +89233,9 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -89567,7 +89849,9 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -90258,7 +90542,9 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -91036,7 +91322,9 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -91650,7 +91938,9 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -92341,7 +92631,9 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -93119,7 +93411,9 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -93746,7 +94040,9 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -94417,7 +94713,9 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -95221,7 +95519,9 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -96100,7 +96400,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -97019,7 +97321,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -97452,7 +97756,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -97885,7 +98191,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -98318,7 +98626,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -98751,7 +99061,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -99184,7 +99496,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -99617,7 +99931,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -100050,7 +100366,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -100483,7 +100801,9 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -100815,7 +101135,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -101734,7 +102056,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -102167,7 +102491,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -102600,7 +102926,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -103033,7 +103361,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -103466,7 +103796,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -103899,7 +104231,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -104332,7 +104666,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -104765,7 +105101,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -105198,7 +105536,9 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -105529,7 +105869,9 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -105898,7 +106240,9 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -106620,7 +106964,9 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -106989,7 +107335,9 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -107711,7 +108059,9 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -108080,7 +108430,9 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -108815,7 +109167,9 @@ exports[`Story Snapshots: Pinned should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -109567,7 +109921,9 @@ exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -110319,7 +110675,9 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -111318,7 +111676,9 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -112786,7 +113146,9 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -113785,7 +114147,9 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -115254,7 +115618,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -116056,7 +116422,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -116372,7 +116740,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -116688,7 +117058,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -117020,7 +117392,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -117822,7 +118196,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -118138,7 +118514,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -118454,7 +118832,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -118900,7 +119280,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -119216,7 +119598,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -119532,7 +119916,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -119848,7 +120234,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -120294,7 +120682,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -120610,7 +121000,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -120926,7 +121318,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -121242,7 +121636,9 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -121573,7 +121969,9 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -122102,7 +122500,9 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -122809,7 +123209,9 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -123338,7 +123740,9 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -124045,7 +124449,9 @@ exports[`Story Snapshots: StaticAvatar should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg", }, ] @@ -124513,7 +124919,9 @@ exports[`Story Snapshots: StaticAvatarLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg", }, ] @@ -124894,7 +125302,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -125135,7 +125545,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -125376,7 +125788,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -125620,7 +126034,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -125861,7 +126277,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126102,7 +126520,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126343,7 +126763,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126584,7 +127006,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -126825,7 +127249,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127066,7 +127492,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127307,7 +127735,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127548,7 +127978,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -127789,7 +128221,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128030,7 +128464,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128271,7 +128707,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128512,7 +128950,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128753,7 +129193,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -128994,7 +129436,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129235,7 +129679,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129476,7 +129922,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129717,7 +130165,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -129958,7 +130408,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130199,7 +130651,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130440,7 +130894,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130681,7 +131137,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -130922,7 +131380,9 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, ] @@ -131154,7 +131614,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -131395,7 +131857,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -131636,7 +132100,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -131880,7 +132346,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132121,7 +132589,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132362,7 +132832,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132603,7 +133075,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -132844,7 +133318,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133085,7 +133561,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133326,7 +133804,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133567,7 +134047,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -133808,7 +134290,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134049,7 +134533,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134290,7 +134776,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134531,7 +135019,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -134772,7 +135262,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135013,7 +135505,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135254,7 +135748,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135495,7 +135991,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135736,7 +136234,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -135977,7 +136477,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136218,7 +136720,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136459,7 +136963,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136700,7 +137206,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -136941,7 +137449,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -137182,7 +137692,9 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, ] @@ -137493,7 +138005,9 @@ exports[`Story Snapshots: Temp should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -137958,7 +138472,9 @@ exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -138431,7 +138947,9 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -139110,7 +139628,9 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -139789,7 +140309,9 @@ exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -140257,7 +140779,9 @@ exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -140726,7 +141250,9 @@ exports[`Story Snapshots: Translated should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -141230,7 +141756,9 @@ exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -141733,7 +142261,9 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -142917,7 +143447,9 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -144101,7 +144633,9 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -144693,7 +145227,9 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -145566,7 +146102,9 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -146060,7 +146598,9 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -146510,7 +147050,9 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -147004,7 +147546,9 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -147454,7 +147998,9 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -148046,7 +148592,9 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -148919,7 +149467,9 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -149288,7 +149838,9 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, ] @@ -150010,7 +150562,9 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -150379,7 +150933,9 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, ] @@ -151101,7 +151657,9 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -151470,7 +152028,9 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, ] @@ -152205,7 +152765,9 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -152679,7 +153241,9 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=72", }, ] @@ -153166,7 +153730,9 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -153640,7 +154206,9 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=93.60000000000001", }, ] @@ -154127,7 +154695,9 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -157020,7 +157590,9 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -159902,7 +160474,9 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -161218,7 +161792,9 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -162292,7 +162868,9 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -162964,7 +163542,9 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -164124,7 +164704,9 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -164909,7 +165491,9 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -165581,7 +166165,9 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -166606,7 +167192,9 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -167391,7 +167979,9 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -168063,7 +168653,9 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -169048,7 +169640,9 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -169778,7 +170372,9 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -170408,7 +171004,9 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] @@ -170939,7 +171537,9 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, ] diff --git a/app/views/CallView/__snapshots__/index.test.tsx.snap b/app/views/CallView/__snapshots__/index.test.tsx.snap index d84af521010..0b830f93ff6 100644 --- a/app/views/CallView/__snapshots__/index.test.tsx.snap +++ b/app/views/CallView/__snapshots__/index.test.tsx.snap @@ -125,7 +125,9 @@ exports[`Story Snapshots: ConnectedCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -997,7 +999,9 @@ exports[`Story Snapshots: ConnectingCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -1875,7 +1879,9 @@ exports[`Story Snapshots: MutedAndOnHold should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -2747,7 +2753,9 @@ exports[`Story Snapshots: MutedCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -3619,7 +3627,9 @@ exports[`Story Snapshots: OnHoldCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -4491,7 +4501,9 @@ exports[`Story Snapshots: SpeakerOn should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -5346,7 +5358,9 @@ exports[`Story Snapshots: TabletConnectedCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -6190,7 +6204,9 @@ exports[`Story Snapshots: TabletConnectingCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -7040,7 +7056,9 @@ exports[`Story Snapshots: TabletMutedAndOnHold should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -7884,7 +7902,9 @@ exports[`Story Snapshots: TabletMutedCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -8728,7 +8748,9 @@ exports[`Story Snapshots: TabletOnHoldCall should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -9572,7 +9594,9 @@ exports[`Story Snapshots: TabletSpeakerOn should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -10416,7 +10440,9 @@ exports[`Story Snapshots: TabletWithDialpad should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] @@ -11277,7 +11303,9 @@ exports[`Story Snapshots: WithDialpad should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, ] diff --git a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap index bcf8e8f6146..792d2047ad4 100644 --- a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap +++ b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap @@ -142,7 +142,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -523,7 +525,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -904,7 +908,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1287,7 +1293,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1668,7 +1676,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2069,7 +2079,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2437,7 +2449,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2805,7 +2819,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] diff --git a/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap b/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap index 972037a4c40..0a5abbdf57c 100644 --- a/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap +++ b/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap @@ -771,7 +771,9 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -1886,7 +1888,9 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -2789,7 +2793,9 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -3363,7 +3369,9 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -4478,7 +4486,9 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5381,7 +5391,9 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -5955,7 +5967,9 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7070,7 +7084,9 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] @@ -7973,7 +7989,9 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, ] diff --git a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap index cad2bda1670..389f9a62f8d 100644 --- a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap +++ b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap @@ -142,7 +142,9 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -664,7 +666,9 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1186,7 +1190,9 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -1695,7 +1701,9 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2237,7 +2245,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -2743,7 +2753,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -3249,7 +3261,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -3755,7 +3769,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -4261,7 +4277,9 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -4787,7 +4805,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -5296,7 +5316,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] @@ -5805,7 +5827,9 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` source={ [ { - "headers": {}, + "headers": { + "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, ] From 945bbb8b9760895eef1727b4b26a88a87b817e10 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 31 May 2026 17:35:18 +0530 Subject: [PATCH 098/203] improve --- app/lib/services/restApi.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index f9ad0b38595..af776e17e84 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1044,16 +1044,16 @@ export const editMediaMessage = async ( fileId: string, body: { description?: string; filename: string; msg?: string } ) => { - const { login, server } = reduxStore.getState(); - const { user } = login; + const { server } = reduxStore.getState(); + const { authToken, userId } = sdk.currentLogin; // RC 8.4.0 const response = await fetch(`${server.server}/api/v1/rooms.mediaConfirm/${rid}/${fileId}`, { method: 'POST', headers: { ...sdk.getHeaders(), 'Content-Type': 'application/json', - 'X-Auth-Token': user.token, - 'X-User-Id': user.id + 'X-Auth-Token': authToken, + 'X-User-Id': userId }, body: JSON.stringify({ description: body.description, From 7b9ad20ed39794e74367d7db4bdfb8a68e153e24 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 1 Jun 2026 18:30:00 +0530 Subject: [PATCH 099/203] more test and changes --- .../lib/subscriptions/inquiry.test.ts | 173 +++++++ .../omnichannel/lib/subscriptions/inquiry.ts | 12 +- app/lib/methods/helpers/fetch.test.ts | 80 +++ app/lib/methods/logout.test.ts | 176 +++++++ .../sendFileMessage/sendFileMessage.test.ts | 250 +++++++++ app/lib/methods/subscriptions/rooms.test.ts | 159 ++++++ app/lib/services/connect.test.ts | 205 +++++++- app/lib/services/connect.ts | 14 +- app/lib/services/sdk.test.ts | 481 +++++++++++++++++- app/lib/services/sdk.ts | 13 +- app/lib/services/twoFactor.test.ts | 70 +++ 11 files changed, 1609 insertions(+), 24 deletions(-) create mode 100644 app/ee/omnichannel/lib/subscriptions/inquiry.test.ts create mode 100644 app/lib/methods/helpers/fetch.test.ts create mode 100644 app/lib/methods/logout.test.ts create mode 100644 app/lib/methods/sendFileMessage/sendFileMessage.test.ts create mode 100644 app/lib/methods/subscriptions/rooms.test.ts create mode 100644 app/lib/services/twoFactor.test.ts diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts new file mode 100644 index 00000000000..44f1d23e535 --- /dev/null +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts @@ -0,0 +1,173 @@ +/* eslint-disable import/first */ +jest.mock('../../../../lib/services/sdk', () => ({ + __esModule: true, + default: { + onStreamData: jest.fn(), + subscribe: jest.fn() + } +})); + +jest.mock('../../../../lib/store/auxStore', () => ({ + store: { + getState: jest.fn(() => ({ + login: { user: { id: 'agent-1', roles: ['livechat-agent'] } }, + inquiry: { queued: [] } + })), + dispatch: jest.fn() + } +})); + +jest.mock('../../../../lib/services/restApi', () => ({ + getAgentDepartments: jest.fn() +})); + +jest.mock('../../../../lib/methods/helpers', () => ({ + hasRole: jest.fn() +})); + +jest.mock('../../../../lib/methods/helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + +import subscribeInquiry from './inquiry'; +import sdk from '../../../../lib/services/sdk'; +import { store } from '../../../../lib/store/auxStore'; +import { getAgentDepartments } from '../../../../lib/services/restApi'; +import { hasRole } from '../../../../lib/methods/helpers'; +import { inquiryQueueAdd, inquiryQueueRemove, inquiryQueueUpdate } from '../../actions/inquiry'; + +const flushPromises = () => new Promise(resolve => setImmediate(resolve)); + +describe('subscribeInquiry', () => { + let queueHandler: (msg: any) => void; + const streamStop = jest.fn(); + const deptStop = jest.fn(); + + beforeEach(() => { + streamStop.mockReset(); + deptStop.mockReset(); + (sdk.onStreamData as jest.Mock).mockReset().mockImplementation((_name: string, handler: (m: any) => void) => { + queueHandler = handler; + return Promise.resolve({ stop: streamStop }); + }); + (sdk.subscribe as jest.Mock).mockReset().mockReturnValue({ stop: deptStop }); + (getAgentDepartments as jest.Mock).mockReset().mockResolvedValue({ success: true, departments: [] }); + (hasRole as jest.Mock).mockReset().mockReturnValue(false); + (store.getState as jest.Mock).mockReturnValue({ + login: { user: { id: 'agent-1' } }, + inquiry: { queued: [] } + }); + (store.dispatch as jest.Mock).mockReset(); + }); + + it('Fix 4 regression: does NOT subscribe to a "connected" collection (was a broken DDP listener)', () => { + subscribeInquiry(); + const subscribed = (sdk.onStreamData as jest.Mock).mock.calls.map(([name]) => name); + expect(subscribed).not.toContain('connected'); + }); + + it('subscribes only to stream-livechat-inquiry-queue-observer via onStreamData', () => { + subscribeInquiry(); + expect(sdk.onStreamData).toHaveBeenCalledTimes(1); + expect(sdk.onStreamData).toHaveBeenCalledWith('stream-livechat-inquiry-queue-observer', expect.any(Function)); + }); + + it('subscribes to the public queue when the agent has no departments', async () => { + (getAgentDepartments as jest.Mock).mockResolvedValue({ success: true, departments: [] }); + subscribeInquiry(); + await flushPromises(); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-livechat-inquiry-queue-observer', 'public'); + }); + + it('subscribes to the public queue when the user is a livechat-manager (even with departments)', async () => { + (getAgentDepartments as jest.Mock).mockResolvedValue({ success: true, departments: [{ departmentId: 'd1' }] }); + (hasRole as jest.Mock).mockImplementation((role: string) => role === 'livechat-manager'); + subscribeInquiry(); + await flushPromises(); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-livechat-inquiry-queue-observer', 'public'); + }); + + it('subscribes to each department channel for the agent', async () => { + (getAgentDepartments as jest.Mock).mockResolvedValue({ + success: true, + departments: [{ departmentId: 'd1' }, { departmentId: 'd2' }] + }); + subscribeInquiry(); + await flushPromises(); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-livechat-inquiry-queue-observer', 'department/d1'); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-livechat-inquiry-queue-observer', 'department/d2'); + }); + + it('returns a stop function that unsubscribes stream + department listeners', async () => { + (getAgentDepartments as jest.Mock).mockResolvedValue({ + success: true, + departments: [{ departmentId: 'd1' }] + }); + const result = subscribeInquiry() as { stop: () => void }; + await flushPromises(); + result.stop(); + await flushPromises(); + expect(streamStop).toHaveBeenCalled(); + expect(deptStop).toHaveBeenCalled(); + }); + + it('does not create department subscriptions when stop() is called before getAgentDepartments resolves', async () => { + let resolveDepts: (v: any) => void; + (getAgentDepartments as jest.Mock).mockReturnValue( + new Promise(resolve => { + resolveDepts = resolve; + }) + ); + const result = subscribeInquiry() as { stop: () => void }; + result.stop(); + resolveDepts!({ success: true, departments: [{ departmentId: 'd1' }] }); + await flushPromises(); + // `sdk.subscribe` should never be invoked after stop() + expect(sdk.subscribe).not.toHaveBeenCalled(); + }); + + it('rejects when the logged-in user has no id', async () => { + (store.getState as jest.Mock).mockReturnValue({ + login: { user: {} }, + inquiry: { queued: [] } + }); + await expect(subscribeInquiry() as any).rejects.toBeUndefined(); + }); + + it('queue handler dispatches inquiryQueueAdd for new queued items', () => { + subscribeInquiry(); + queueHandler({ + fields: { args: [{ type: 'inserted', _id: 'inq-1', status: 'queued', rid: 'r1' }] } + }); + expect(store.dispatch).toHaveBeenCalledWith(inquiryQueueAdd({ _id: 'inq-1', status: 'queued', rid: 'r1' } as any)); + }); + + it('queue handler dispatches inquiryQueueUpdate for items already on the queue', () => { + (store.getState as jest.Mock).mockReturnValue({ + login: { user: { id: 'agent-1' } }, + inquiry: { queued: [{ _id: 'inq-1' }] } + }); + subscribeInquiry(); + queueHandler({ + fields: { args: [{ type: 'changed', _id: 'inq-1', status: 'queued', rid: 'r1' }] } + }); + expect(store.dispatch).toHaveBeenCalledWith(inquiryQueueUpdate({ _id: 'inq-1', status: 'queued', rid: 'r1' } as any)); + }); + + it('queue handler dispatches inquiryQueueRemove when an item leaves the queue', () => { + subscribeInquiry(); + queueHandler({ + fields: { args: [{ type: 'changed', _id: 'inq-1', status: 'taken', rid: 'r1' }] } + }); + expect(store.dispatch).toHaveBeenCalledWith(inquiryQueueRemove('inq-1')); + }); + + it('queue handler ignores "added" events (deduplicated by "changed")', () => { + subscribeInquiry(); + queueHandler({ + fields: { args: [{ type: 'added', _id: 'inq-1', status: 'queued', rid: 'r1' }] } + }); + expect(store.dispatch).not.toHaveBeenCalled(); + }); +}); diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index 19cf958040c..124d54931d4 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -1,6 +1,6 @@ import log from '../../../../lib/methods/helpers/log'; import { store } from '../../../../lib/store/auxStore'; -import { inquiryQueueAdd, inquiryQueueRemove, inquiryQueueUpdate, inquiryRequest } from '../../actions/inquiry'; +import { inquiryQueueAdd, inquiryQueueRemove, inquiryQueueUpdate } from '../../actions/inquiry'; import sdk from '../../../../lib/services/sdk'; import { type IOmnichannelRoom } from '../../../../definitions'; import { hasRole } from '../../../../lib/methods/helpers'; @@ -22,7 +22,6 @@ interface IDdpMessage { const removeListener = (listener: any) => listener.stop(); -let connectedListener: any; let queueListener: any; let departmentListeners: any[] = []; let stopped = false; @@ -32,10 +31,6 @@ const streamTopic = 'stream-livechat-inquiry-queue-observer'; export default function subscribeInquiry() { stopped = false; - const handleConnection = () => { - store.dispatch(inquiryRequest()); - }; - const handleQueueMessageReceived = (ddpMessage: IDdpMessage) => { const [{ type, ...sub }] = ddpMessage.fields.args; @@ -67,10 +62,6 @@ export default function subscribeInquiry() { const stop = () => { stopped = true; - if (connectedListener) { - connectedListener.then(removeListener); - connectedListener = false; - } if (queueListener) { queueListener.then(removeListener); queueListener = false; @@ -85,7 +76,6 @@ export default function subscribeInquiry() { departmentListeners = []; }; - connectedListener = sdk.onStreamData('connected', handleConnection); queueListener = sdk.onStreamData(streamTopic, handleQueueMessageReceived); try { diff --git a/app/lib/methods/helpers/fetch.test.ts b/app/lib/methods/helpers/fetch.test.ts new file mode 100644 index 00000000000..83a715f52a0 --- /dev/null +++ b/app/lib/methods/helpers/fetch.test.ts @@ -0,0 +1,80 @@ +/* eslint-disable import/first */ +jest.mock('../../services/sdk', () => ({ + __esModule: true, + default: { + getHeaders: jest.fn(), + setBasicAuth: jest.fn() + } +})); + +import customFetch, { setBasicAuth } from './fetch'; +import sdk from '../../services/sdk'; + +const originalFetch = global.fetch; + +describe('helpers/fetch', () => { + let fetchMock: jest.Mock; + + beforeEach(() => { + fetchMock = jest.fn().mockResolvedValue({ ok: true }); + global.fetch = fetchMock as any; + (sdk.getHeaders as jest.Mock).mockReset().mockReturnValue({ 'User-Agent': 'RC Mobile' }); + (sdk.setBasicAuth as jest.Mock).mockReset(); + }); + + afterAll(() => { + global.fetch = originalFetch; + }); + + it('includes sdk.getHeaders() in every request', async () => { + await customFetch('/api/v1/test'); + expect(fetchMock).toHaveBeenCalledWith( + '/api/v1/test', + expect.objectContaining({ + headers: expect.objectContaining({ 'User-Agent': 'RC Mobile' }) + }) + ); + }); + + it('lets per-request headers override sdk.getHeaders() on conflict', async () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'X-Custom': 'base' }); + await customFetch('/api/v1/test', { headers: { 'X-Custom': 'override' } as any }); + const call = fetchMock.mock.calls[0][1]; + expect(call.headers['X-Custom']).toBe('override'); + }); + + it('forwards method and body options unchanged', async () => { + await customFetch('/api/v1/test', { method: 'POST', body: '{"a":1}' }); + const call = fetchMock.mock.calls[0][1]; + expect(call.method).toBe('POST'); + expect(call.body).toBe('{"a":1}'); + }); + + it('does not mutate the base headers object across multiple calls', async () => { + const base = { 'X-Base': 'one' }; + (sdk.getHeaders as jest.Mock).mockReturnValue(base); + await customFetch('/a', { headers: { 'X-Per-Request': 'a' } as any }); + await customFetch('/b', { headers: { 'X-Per-Request': 'b' } as any }); + expect(base).toEqual({ 'X-Base': 'one' }); + expect(fetchMock.mock.calls[0][1].headers['X-Per-Request']).toBe('a'); + expect(fetchMock.mock.calls[1][1].headers['X-Per-Request']).toBe('b'); + }); + + it('returns the underlying fetch promise unchanged', async () => { + const expected = { ok: true, status: 200 }; + fetchMock.mockResolvedValueOnce(expected); + await expect(customFetch('/api/v1/test')).resolves.toBe(expected); + }); +}); + +describe('helpers/fetch setBasicAuth', () => { + it('delegates to sdk.setBasicAuth', () => { + setBasicAuth('dXNlcjpwYXNz'); + expect(sdk.setBasicAuth).toHaveBeenCalledWith('dXNlcjpwYXNz'); + }); + + it('forwards null to clear basic auth', () => { + setBasicAuth(null); + expect(sdk.setBasicAuth).toHaveBeenCalledWith(null); + }); +}); diff --git a/app/lib/methods/logout.test.ts b/app/lib/methods/logout.test.ts new file mode 100644 index 00000000000..4dea28e3651 --- /dev/null +++ b/app/lib/methods/logout.test.ts @@ -0,0 +1,176 @@ +/* eslint-disable import/first */ +jest.mock('@rocket.chat/ddp-client', () => ({ + DDPSDK: { + createAndConnect: jest.fn() + } +})); + +jest.mock('./userPreferences', () => ({ + __esModule: true, + default: { + getString: jest.fn(), + setString: jest.fn(), + removeItem: jest.fn() + } +})); + +jest.mock('../notifications', () => ({ + getDeviceToken: jest.fn() +})); + +jest.mock('./helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + +jest.mock('../database', () => ({ + __esModule: true, + default: { + servers: { + get: jest.fn(() => ({ + find: jest.fn().mockRejectedValue(new Error('not found')) + })), + write: jest.fn(), + batch: jest.fn() + } + }, + getDatabase: jest.fn(() => ({ + write: jest.fn(), + unsafeResetDatabase: jest.fn() + })) +})); + +jest.mock('../services/connect', () => ({ + disconnect: jest.fn() +})); + +jest.mock('../services/sdk', () => ({ + __esModule: true, + default: { + current: undefined, + logout: jest.fn() + } +})); + +jest.mock('../services/restApi', () => ({ + removePushToken: jest.fn() +})); + +jest.mock('./subscriptions/rooms', () => ({ + roomsSubscription: null +})); + +jest.mock('./getUsersPresence', () => ({ + _activeUsersSubTimeout: { activeUsersSubTimeout: false } +})); + +import { DDPSDK } from '@rocket.chat/ddp-client'; + +import { removeServer } from './logout'; +import UserPreferences from './userPreferences'; +import { getDeviceToken } from '../notifications'; +import log from './helpers/log'; + +describe('removeServer', () => { + beforeEach(() => { + (UserPreferences.getString as jest.Mock).mockReset(); + (getDeviceToken as jest.Mock).mockReset(); + (DDPSDK.createAndConnect as jest.Mock).mockReset(); + (log as jest.Mock).mockReset(); + }); + + it('short-circuits when no stored userId is found for the server', async () => { + (UserPreferences.getString as jest.Mock).mockReturnValue(null); + await removeServer({ server: 'https://example.com' }); + expect(DDPSDK.createAndConnect).not.toHaveBeenCalled(); + }); + + it('connects, logs in with the resume token, removes push token, and logs out', async () => { + (UserPreferences.getString as jest.Mock).mockImplementation((key: string) => { + if (key === 'reactnativemeteor_usertoken-https://example.com') return 'user-id-1'; + if (key === 'reactnativemeteor_usertoken-user-id-1') return 'resume-tok'; + return null; + }); + (getDeviceToken as jest.Mock).mockReturnValue('push-tok'); + const tempSdk = { + account: { + loginWithToken: jest.fn().mockResolvedValue(undefined), + logout: jest.fn().mockResolvedValue(undefined) + }, + rest: { delete: jest.fn().mockResolvedValue({ success: true }) }, + connection: { close: jest.fn() } + }; + (DDPSDK.createAndConnect as jest.Mock).mockResolvedValue(tempSdk); + + await removeServer({ server: 'https://example.com' }); + + expect(DDPSDK.createAndConnect).toHaveBeenCalledWith('https://example.com'); + expect(tempSdk.account.loginWithToken).toHaveBeenCalledWith('resume-tok'); + expect(tempSdk.rest.delete).toHaveBeenCalledWith('/v1/push.token', { token: 'push-tok' }); + expect(tempSdk.account.logout).toHaveBeenCalled(); + expect(tempSdk.connection.close).toHaveBeenCalled(); + }); + + it('skips push-token removal when no device token exists', async () => { + (UserPreferences.getString as jest.Mock).mockImplementation((key: string) => + key === 'reactnativemeteor_usertoken-https://example.com' ? 'user-id-1' : 'resume-tok' + ); + (getDeviceToken as jest.Mock).mockReturnValue(null); + const tempSdk = { + account: { loginWithToken: jest.fn(), logout: jest.fn() }, + rest: { delete: jest.fn() }, + connection: { close: jest.fn() } + }; + (DDPSDK.createAndConnect as jest.Mock).mockResolvedValue(tempSdk); + + await removeServer({ server: 'https://example.com' }); + + expect(tempSdk.rest.delete).not.toHaveBeenCalled(); + expect(tempSdk.account.logout).toHaveBeenCalled(); + }); + + it('logs a warning when push-token deletion reports success:false', async () => { + (UserPreferences.getString as jest.Mock).mockImplementation((key: string) => + key === 'reactnativemeteor_usertoken-https://example.com' ? 'u' : 'r' + ); + (getDeviceToken as jest.Mock).mockReturnValue('push-tok'); + const tempSdk = { + account: { loginWithToken: jest.fn(), logout: jest.fn() }, + rest: { delete: jest.fn().mockResolvedValue({ success: false }) }, + connection: { close: jest.fn() } + }; + (DDPSDK.createAndConnect as jest.Mock).mockResolvedValue(tempSdk); + + await removeServer({ server: 'https://example.com' }); + expect(log).toHaveBeenCalledWith(expect.any(Error)); + }); + + it('still closes the temp connection in finally when the connect step throws', async () => { + (UserPreferences.getString as jest.Mock).mockImplementation((key: string) => + key === 'reactnativemeteor_usertoken-https://example.com' ? 'u' : 'r' + ); + (DDPSDK.createAndConnect as jest.Mock).mockRejectedValue(new Error('conn failed')); + await removeServer({ server: 'https://example.com' }); + expect(log).toHaveBeenCalledWith(expect.any(Error)); + }); + + it('closes the connection in finally even when logout throws', async () => { + (UserPreferences.getString as jest.Mock).mockImplementation((key: string) => + key === 'reactnativemeteor_usertoken-https://example.com' ? 'u' : 'r' + ); + (getDeviceToken as jest.Mock).mockReturnValue('push-tok'); + const close = jest.fn(); + const tempSdk = { + account: { + loginWithToken: jest.fn(), + logout: jest.fn().mockRejectedValue(new Error('logout failed')) + }, + rest: { delete: jest.fn().mockResolvedValue({ success: true }) }, + connection: { close } + }; + (DDPSDK.createAndConnect as jest.Mock).mockResolvedValue(tempSdk); + + await removeServer({ server: 'https://example.com' }); + expect(close).toHaveBeenCalled(); + }); +}); diff --git a/app/lib/methods/sendFileMessage/sendFileMessage.test.ts b/app/lib/methods/sendFileMessage/sendFileMessage.test.ts new file mode 100644 index 00000000000..3f790b13819 --- /dev/null +++ b/app/lib/methods/sendFileMessage/sendFileMessage.test.ts @@ -0,0 +1,250 @@ +/* eslint-disable import/first */ +jest.mock('../../services/sdk', () => ({ + __esModule: true, + default: { + getHeaders: jest.fn(() => ({ 'User-Agent': 'RC Mobile test' })) + } +})); + +jest.mock('../../store/auxStore', () => ({ + store: { getState: jest.fn(() => ({ server: { version: '6.10.0' } })) } +})); + +jest.mock('../helpers', () => ({ + compareServerVersion: jest.fn((_v: string, op: string, _target: string) => { + // 6.10.0 vs 6.10.0 → not lowerThan + if (op === 'lowerThan') return false; + return true; + }) +})); + +jest.mock('../../database', () => { + const update = jest.fn(); + const destroyPermanently = jest.fn(); + const uploadRecord = { + id: undefined, + update, + destroyPermanently + }; + return { + __esModule: true, + default: { + active: { + get: jest.fn(() => ({ + find: jest.fn().mockRejectedValue(new Error('not found')), + create: jest.fn((cb: any) => { + cb(uploadRecord); + return Promise.resolve(uploadRecord); + }), + schema: {} + })), + write: jest.fn((fn: any) => Promise.resolve(fn())) + } + }, + __uploadRecord: uploadRecord, + __update: update + }; +}); + +const mockSend = jest.fn().mockResolvedValue({ file: { _id: 'f1', url: '/f1' } }); +const mockState: { + progressCb?: (loaded: number, total: number) => void; + headers?: any; + url?: string; + formData?: any; +} = {}; + +jest.mock('../helpers/fileUpload', () => ({ + __esModule: true, + default: jest.fn().mockImplementation((url: string, headers: any, formData: any, progressCb: any) => { + mockState.url = url; + mockState.headers = headers; + mockState.formData = formData; + mockState.progressCb = progressCb; + return { send: mockSend }; + }) +})); + +jest.mock('./utils', () => ({ + copyFileToCacheDirectoryIfNeeded: jest.fn().mockImplementation((p: string) => Promise.resolve(p)), + getUploadPath: jest.fn(() => 'upload-path-1'), + persistUploadError: jest.fn().mockResolvedValue(undefined), + createUploadRecord: jest.fn().mockResolvedValue(['upload-path-1', { update: jest.fn(), destroyPermanently: jest.fn() }]), + uploadQueue: {} +})); + +const mockEncryptFile = jest.fn().mockResolvedValue({ + file: { path: '/p', name: 'n', type: 't' }, + getContent: jest.fn().mockResolvedValue({ algorithm: 'a' }), + fileContent: { algorithm: 'a' } +}); +jest.mock('../../encryption', () => ({ + Encryption: { + encryptFile: (...args: any[]) => mockEncryptFile(...args) + } +})); + +jest.mock('../helpers/fetch', () => ({ + __esModule: true, + default: jest.fn().mockResolvedValue({ ok: true }) +})); + +jest.mock('../helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + +jest.mock('@nozbe/watermelondb/RawRecord', () => ({ + sanitizedRaw: jest.fn(() => ({})) +})); + +jest.mock('react-native', () => ({ + Alert: { alert: jest.fn() } +})); + +import { sendFileMessage } from './index'; +import { sendFileMessage as sendFileMessageV1 } from './sendFileMessage'; +import { sendFileMessageV2 } from './sendFileMessageV2'; +import sdk from '../../services/sdk'; +import fetch from '../helpers/fetch'; +import { store } from '../../store/auxStore'; + +beforeEach(() => { + mockSend.mockClear(); + mockState.progressCb = undefined; + mockState.headers = undefined; + mockState.url = undefined; + mockState.formData = undefined; + mockEncryptFile.mockClear(); + (fetch as jest.Mock).mockClear(); + (sdk.getHeaders as jest.Mock).mockReset().mockReturnValue({ 'User-Agent': 'RC Mobile test' }); + (store.getState as jest.Mock).mockReturnValue({ server: { version: '6.10.0' } }); +}); + +describe('sendFileMessage router', () => { + it('routes to V1 for RC < 6.10.0', async () => { + (store.getState as jest.Mock).mockReturnValue({ server: { version: '6.9.0' } }); + const { compareServerVersion } = require('../helpers'); + (compareServerVersion as jest.Mock).mockReturnValueOnce(true); + await sendFileMessage('rid', { name: 'a', type: 't', path: '/p', size: 1 } as any, undefined, 'https://x.com', { + id: 'u', + token: 't' + }); + // V1 hits /v1/rooms.upload + expect(mockState.url).toBe('https://x.com/api/v1/rooms.upload/rid'); + }); + + it('routes to V2 for RC >= 6.10.0', async () => { + const { compareServerVersion } = require('../helpers'); + (compareServerVersion as jest.Mock).mockReturnValueOnce(false); + await sendFileMessage('rid', { name: 'a', type: 't', path: '/p', size: 1 } as any, undefined, 'https://x.com', { + id: 'u', + token: 't' + }); + expect(mockState.url).toBe('https://x.com/api/v1/rooms.media/rid'); + }); +}); + +describe('sendFileMessage V1 headers', () => { + it('spreads sdk.getHeaders() + Content-Type + auth headers when user has token + id', async () => { + await sendFileMessageV1( + 'rid', + { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, + undefined, + 'https://x.com', + { id: 'u-1', token: 'tok-1' } + ); + expect(mockState.headers).toMatchObject({ + 'User-Agent': 'RC Mobile test', + 'Content-Type': 'multipart/form-data', + 'X-Auth-Token': 'tok-1', + 'X-User-Id': 'u-1' + }); + }); + + it('omits X-Auth-Token when user.token is undefined', async () => { + await sendFileMessageV1( + 'rid', + { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, + undefined, + 'https://x.com', + { id: 'u-1' } + ); + expect(mockState.headers['X-Auth-Token']).toBeUndefined(); + expect(mockState.headers['X-User-Id']).toBe('u-1'); + }); + + it('omits X-User-Id when user.id is undefined', async () => { + await sendFileMessageV1( + 'rid', + { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, + undefined, + 'https://x.com', + { token: 'tok-1' } + ); + expect(mockState.headers['X-User-Id']).toBeUndefined(); + expect(mockState.headers['X-Auth-Token']).toBe('tok-1'); + }); + + it('progress callback updates upload record progress field', async () => { + await sendFileMessageV1( + 'rid', + { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, + undefined, + 'https://x.com', + { id: 'u-1', token: 'tok-1' } + ); + const dbMod = require('../../database'); + await mockState.progressCb!(50, 100); + expect(dbMod.__update).toHaveBeenCalledWith(expect.any(Function)); + // The update callback assigns u.progress = Math.floor((50/100)*100) = 50 + const updateFn = dbMod.__update.mock.calls[0][0]; + const u: any = {}; + updateFn(u); + expect(u.progress).toBe(50); + }); +}); + +describe('sendFileMessage V2', () => { + const baseFile = { name: 'a', type: 't', path: '/p', size: 1 } as any; + + it('invokes Encryption.encryptFile with (rid, fileInfo)', async () => { + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + expect(mockEncryptFile).toHaveBeenCalledWith('rid', baseFile); + }); + + it('uploads to /v1/rooms.media/{rid} with merged headers', async () => { + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + expect(mockState.url).toBe('https://x.com/api/v1/rooms.media/rid'); + expect(mockState.headers).toMatchObject({ + 'User-Agent': 'RC Mobile test', + 'Content-Type': 'multipart/form-data', + 'X-Auth-Token': 'tok-1', + 'X-User-Id': 'u-1' + }); + }); + + it('confirms via /v1/rooms.mediaConfirm with JSON Content-Type override', async () => { + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + expect(fetch).toHaveBeenCalledWith( + 'https://x.com/api/v1/rooms.mediaConfirm/rid/f1', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ 'Content-Type': 'application/json' }) + }) + ); + }); + + it('includes a content form-data entry when Encryption returns fileContent', async () => { + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + const contentField = mockState.formData.find((e: any) => e.name === 'content'); + expect(contentField).toBeDefined(); + expect(JSON.parse(contentField.data)).toEqual({ algorithm: 'a' }); + }); + + it('omits X-Auth-Token / X-User-Id when user has neither', async () => { + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', {}); + expect(mockState.headers['X-Auth-Token']).toBeUndefined(); + expect(mockState.headers['X-User-Id']).toBeUndefined(); + }); +}); diff --git a/app/lib/methods/subscriptions/rooms.test.ts b/app/lib/methods/subscriptions/rooms.test.ts new file mode 100644 index 00000000000..1b45a236d83 --- /dev/null +++ b/app/lib/methods/subscriptions/rooms.test.ts @@ -0,0 +1,159 @@ +/* eslint-disable import/first */ +jest.mock('../../services/sdk', () => ({ + __esModule: true, + default: { + current: undefined, + onStreamData: jest.fn(), + subscribeNotifyUser: jest.fn(() => []) + } +})); + +jest.mock('../../database', () => ({ + __esModule: true, + default: { active: { get: jest.fn(), write: jest.fn(), batch: jest.fn() } } +})); + +jest.mock('../../store/auxStore', () => ({ + store: { + getState: jest.fn(() => ({ room: { subscribedRoom: undefined, rid: undefined, isDeleting: false } })), + dispatch: jest.fn() + } +})); + +jest.mock('../helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + +jest.mock('../helpers/protectedFunction', () => ({ + __esModule: true, + default: (fn: any) => fn +})); + +jest.mock('../actions', () => ({ + handlePayloadUserInteraction: jest.fn() +})); + +jest.mock('../../encryption', () => ({ + Encryption: { + decryptPendingSubscriptions: jest.fn(), + decryptPendingMessages: jest.fn(), + getRoomInstance: jest.fn(), + stopRoom: jest.fn(), + provideRoomKeyToUser: jest.fn().mockResolvedValue(undefined), + decryptMessage: jest.fn() + } +})); + +jest.mock('../updateMessages', () => ({ + __esModule: true, + default: jest.fn().mockResolvedValue(undefined) +})); + +jest.mock('../getRoom', () => ({ + getRoom: jest.fn() +})); + +import subscribeRooms from './rooms'; +import sdk from '../../services/sdk'; +import { handlePayloadUserInteraction } from '../actions'; +import { Encryption } from '../../encryption'; +import * as roomsModule from './rooms'; + +describe('subscribeRooms', () => { + let capturedHandler: (msg: any) => Promise | void; + const stopMock = jest.fn(); + const rawStop = jest.fn(); + + beforeEach(async () => { + // Clean up any prior subscription first (before resetting mocks, so its stop() doesn't pollute counts) + const prior = roomsModule.roomsSubscription; + if (prior) prior.stop(); + await Promise.resolve(); + stopMock.mockReset(); + rawStop.mockReset(); + (sdk.onStreamData as jest.Mock).mockReset().mockImplementation((_name: string, handler: (m: any) => void) => { + capturedHandler = handler; + return Promise.resolve({ stop: stopMock }); + }); + (sdk.subscribeNotifyUser as jest.Mock).mockReset().mockReturnValue([{ stop: rawStop }, { stop: rawStop }]); + (sdk as any).current = { connection: { url: 'wss://example.com/websocket' } }; + (handlePayloadUserInteraction as jest.Mock).mockReset(); + (Encryption.provideRoomKeyToUser as jest.Mock).mockClear(); + }); + + it('registers a stream-notify-user listener via sdk.onStreamData', () => { + subscribeRooms(); + expect(sdk.onStreamData).toHaveBeenCalledWith('stream-notify-user', expect.any(Function)); + }); + + it('registers per-user raw subscriptions via subscribeNotifyUser', () => { + subscribeRooms(); + expect(sdk.subscribeNotifyUser).toHaveBeenCalledTimes(1); + }); + + it('exposes a roomsSubscription with a stop() function', () => { + subscribeRooms(); + expect(roomsModule.roomsSubscription).not.toBeNull(); + expect(typeof roomsModule.roomsSubscription?.stop).toBe('function'); + }); + + it('stop() unsubscribes the stream listener and stops raw handles', async () => { + subscribeRooms(); + // Wait for the onStreamData promise to settle so streamListener is a Promise + await Promise.resolve(); + roomsModule.roomsSubscription?.stop(); + await Promise.resolve(); + expect(stopMock).toHaveBeenCalled(); + expect(rawStop).toHaveBeenCalledTimes(2); + }); + + it('stop() clears roomsSubscription to null', () => { + subscribeRooms(); + roomsModule.roomsSubscription?.stop(); + expect(roomsModule.roomsSubscription).toBeNull(); + }); + + it('captures the server URL into subServer when subscribing', () => { + subscribeRooms(); + // Drive a message that should be ignored because url matches (i.e. handler runs through) + const handlerCallsBeforeMessage = (handlePayloadUserInteraction as jest.Mock).mock.calls.length; + capturedHandler({ + msg: 'changed', + fields: { eventName: 'u1/uiInteraction', args: [{ type: 'banner.open', payload: 'p' }] } + }); + expect((handlePayloadUserInteraction as jest.Mock).mock.calls.length).toBeGreaterThan(handlerCallsBeforeMessage); + }); + + it('uiInteraction events dispatch via handlePayloadUserInteraction', async () => { + subscribeRooms(); + await capturedHandler({ + msg: 'changed', + fields: { + eventName: 'u1/uiInteraction', + args: [{ type: 'banner.open', extra: true }] + } + }); + expect(handlePayloadUserInteraction).toHaveBeenCalledWith('banner.open', { extra: true }); + }); + + it('e2ekeyRequest events invoke Encryption.provideRoomKeyToUser with (keyId, roomId)', async () => { + subscribeRooms(); + await capturedHandler({ + msg: 'changed', + fields: { eventName: 'u1/e2ekeyRequest', args: ['rid-1', 'key-1'] } + }); + expect(Encryption.provideRoomKeyToUser).toHaveBeenCalledWith('key-1', 'rid-1'); + }); + + it('drops messages from a stale server (url mismatch)', async () => { + subscribeRooms(); + // Change current server URL after subscription started + (sdk as any).current = { connection: { url: 'wss://different.com/websocket' } }; + await capturedHandler({ + msg: 'changed', + fields: { eventName: 'u1/uiInteraction', args: [{ type: 'banner.open', payload: 'p' }] } + }); + expect(handlePayloadUserInteraction).not.toHaveBeenCalled(); + }); +}); diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index ec300ef988b..fb293ceae21 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -1,4 +1,4 @@ -import { checkAndReopen, connect, determineAuthType, disconnect } from './connect'; +import { checkAndReopen, connect, determineAuthType, disconnect, login, loginTOTP } from './connect'; import { mediaSessionInstance } from './voip/MediaSessionInstance'; import { pendingHangups } from './voip/pendingHangups'; @@ -21,6 +21,9 @@ const mockSdkInitialize = jest.fn().mockResolvedValue(undefined); const mockSdkOnCollection = jest.fn(); const mockSdkDisconnect = jest.fn(); +const mockSdkLogin = jest.fn(); +const mockAccountUser: { value: any } = { value: undefined }; + jest.mock('./sdk', () => { const state: { server: string | undefined; currentEnabled: boolean } = { server: undefined, currentEnabled: true }; return { @@ -32,9 +35,11 @@ jest.mock('./sdk', () => { disconnect: (...args: any[]) => mockSdkDisconnect(...args), initialize: (s: string) => mockSdkInitialize(s), onCollection: (...args: any[]) => mockSdkOnCollection(...args), + login: (...args: any[]) => mockSdkLogin(...args), get current() { if (!state.currentEnabled) return undefined; return { + account: { user: mockAccountUser.value }, connection: { on: (event: string, cb: any) => mockConnectionOn(event, cb), connect: () => mockConnectionConnect(), @@ -52,6 +57,15 @@ jest.mock('./sdk', () => { }; }); +const mockTwoFactor = jest.fn(); +jest.mock('./twoFactor', () => ({ + twoFactor: (...args: any[]) => mockTwoFactor(...args) +})); + +jest.mock('../../ee/omnichannel/actions/inquiry', () => ({ + inquiryRequest: jest.fn().mockReturnValue({ type: 'INQUIRY_REQUEST' }) +})); + const sdkMock = jest.requireMock('./sdk') as { __setServer: (v: string | undefined) => void; __setCurrentEnabled: (v: boolean) => void; @@ -603,3 +617,192 @@ describe('checkAndReopen', () => { }); // Note: Apple authentication when isIOS is true is tested in connect.ios.test.ts + +// ───────────────────────────────────────────────────────────────────────────── +describe('login', () => { + beforeEach(() => { + mockSdkLogin.mockReset(); + mockStoreGetState.mockReturnValue({ + meteor: { connected: true }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + mockAccountUser.value = undefined; + }); + + it('throws when sdk.login result has no me', async () => { + mockSdkLogin.mockResolvedValue({}); + await expect(login({ user: 'u', password: 'p' } as any)).rejects.toThrow("Couldn't fetch user data"); + }); + + it('throws when sdk.current.account.user is missing', async () => { + mockSdkLogin.mockResolvedValue({ me: { username: 'john' } }); + mockAccountUser.value = undefined; + await expect(login({ user: 'u', password: 'p' } as any)).rejects.toThrow('Login failed: no user returned'); + }); + + it('returns an ILoggedUser combining account.user + me', async () => { + mockAccountUser.value = { id: 'u-1', token: 'tok-1' }; + mockSdkLogin.mockResolvedValue({ + me: { username: 'john', name: 'John D', language: 'en', emails: [{ address: 'j@x.com' }] } + }); + const result = await login({ user: 'john', password: 'p' } as any); + expect(result).toEqual( + expect.objectContaining({ + id: 'u-1', + token: 'tok-1', + username: 'john', + name: 'John D', + language: 'en' + }) + ); + }); + + it('reads showMessageInMainThread / enableMessageParserEarlyAdoption from me on RC < 5.0', async () => { + mockStoreGetState.mockReturnValue({ + meteor: { connected: true }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '4.9.0' } + }); + mockAccountUser.value = { id: 'u-1', token: 'tok-1' }; + mockSdkLogin.mockResolvedValue({ + me: { + username: 'john', + settings: { preferences: { enableMessageParserEarlyAdoption: false, showMessageInMainThread: true } } + } + }); + const result = await login({ user: 'john', password: 'p' } as any); + expect(result?.enableMessageParserEarlyAdoption).toBe(false); + expect(result?.showMessageInMainThread).toBe(true); + }); +}); + +describe('loginTOTP', () => { + beforeEach(() => { + mockSdkLogin.mockReset(); + mockTwoFactor.mockReset(); + mockStoreDispatch.mockReset(); + mockStoreGetState.mockReturnValue({ + meteor: { connected: true }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + mockAccountUser.value = { id: 'u-1', token: 'tok-1' }; + }); + + it('returns the login result when no 2FA challenge is raised', async () => { + mockSdkLogin.mockResolvedValue({ me: { username: 'john' } }); + const result = await loginTOTP({ user: 'john', password: 'p' } as any); + expect(result.username).toBe('john'); + }); + + it('prompts twoFactor with details.method and retries when totp-required', async () => { + mockSdkLogin + .mockRejectedValueOnce({ data: { error: 'totp-required', details: { method: 'totp' } } }) + .mockResolvedValueOnce({ me: { username: 'john' } }); + mockTwoFactor.mockResolvedValue({ twoFactorCode: '123456', twoFactorMethod: 'totp' }); + + const result = await loginTOTP({ user: 'john', password: 'p' } as any, true); + expect(mockTwoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: false }); + expect(result.username).toBe('john'); + }); + + it('passes invalid:true when retrying after totp-invalid', async () => { + mockSdkLogin + .mockRejectedValueOnce({ + data: { error: 'totp-invalid', details: { method: 'totp', error: 'totp-invalid' } } + }) + .mockResolvedValueOnce({ me: { username: 'john' } }); + mockTwoFactor.mockResolvedValue({ twoFactorCode: '999999', twoFactorMethod: 'totp' }); + await loginTOTP({ user: 'john', password: 'p' } as any, true); + expect(mockTwoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: true }); + }); + + it('normalizes ldapPass to password on RC >= 3.9.0 before 2FA retry', async () => { + mockSdkLogin + .mockRejectedValueOnce({ data: { error: 'totp-required', details: { method: 'totp' } } }) + .mockResolvedValueOnce({ me: { username: 'john' } }); + mockTwoFactor.mockResolvedValue({ twoFactorCode: '111111', twoFactorMethod: 'totp' }); + await loginTOTP({ username: 'john', ldapPass: 'secret-ldap' } as any, true); + const retryArgs = mockSdkLogin.mock.calls[1][0]; + expect(retryArgs).toMatchObject({ user: 'john', password: 'secret-ldap', code: '111111' }); + expect(retryArgs).not.toHaveProperty('ldapPass'); + }); + + it('wraps the retry params in a totp envelope when loginEmailPassword is falsy', async () => { + mockSdkLogin + .mockRejectedValueOnce({ data: { error: 'totp-required', details: { method: 'totp' } } }) + .mockResolvedValueOnce({ me: { username: 'service' } }); + mockTwoFactor.mockResolvedValue({ twoFactorCode: '222222', twoFactorMethod: 'totp' }); + await loginTOTP({ user: 'service', password: 'p' } as any); + const retryArgs = mockSdkLogin.mock.calls[1][0]; + expect(retryArgs).toMatchObject({ totp: { login: expect.any(Object), code: '222222' } }); + }); + + it('rejects when twoFactor is cancelled', async () => { + mockSdkLogin.mockRejectedValue({ data: { error: 'totp-required', details: { method: 'totp' } } }); + mockTwoFactor.mockRejectedValue(new Error('cancelled')); + await expect(loginTOTP({ user: 'john', password: 'p' } as any, true)).rejects.toBeUndefined(); + }); + + it('rejects non-2FA errors as-is', async () => { + mockSdkLogin.mockRejectedValue(new Error('500 server')); + await expect(loginTOTP({ user: 'john', password: 'p' } as any)).rejects.toThrow('500 server'); + }); +}); + +// ───────────────────────────────────────────────────────────────────────────── +// Fix 4 regression — inquiry dispatch on reconnect +describe('connection status handler (Fix 4 regression)', () => { + beforeEach(() => { + mockConnectionOn.mockReset(); + mockStoreDispatch.mockReset(); + sdkMock.__setServer(undefined); + }); + + const setLivechatRoles = (roles: string[]) => { + jest.resetModules(); + jest.doMock('../methods/helpers', () => ({ + ...jest.requireActual('../methods/helpers'), + hasRole: jest.fn((r: string) => roles.includes(r)) + })); + }; + + it('dispatches inquiryRequest on connected when user is a livechat-agent', async () => { + setLivechatRoles(['livechat-agent']); + const { connect: freshConnect } = require('./connect'); + await freshConnect({ server: 'https://x.com' }); + const listener = getCapturedConnectionListener(); + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: { token: 't' }, isAuthenticated: true }, + settings: {}, + server: { version: '6.0.0' } + }); + listener('connected'); + await flushMicrotasks(); + const actions = mockStoreDispatch.mock.calls.map(([action]) => (action as any).type); + expect(actions).toContain('INQUIRY_REQUEST'); + }); + + it('does NOT dispatch inquiryRequest on connected when user has no livechat role', async () => { + setLivechatRoles([]); + const { connect: freshConnect } = require('./connect'); + sdkMock.__setServer(undefined); + await freshConnect({ server: 'https://no-livechat.com' }); + const listener = getCapturedConnectionListener(); + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: { token: 't' }, isAuthenticated: true }, + settings: {}, + server: { version: '6.0.0' } + }); + listener('connected'); + await flushMicrotasks(); + const actions = mockStoreDispatch.mock.calls.map(([action]) => (action as any).type); + expect(actions).not.toContain('INQUIRY_REQUEST'); + }); +}); diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index e0eece70b4f..bc71d66596a 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -20,7 +20,8 @@ import { updatePermission } from '../../actions/permissions'; import EventEmitter from '../methods/helpers/events'; import { updateSettings } from '../../actions/settings'; import { defaultSettings } from '../constants/defaultSettings'; -import { compareServerVersion, isIOS } from '../methods/helpers'; +import { compareServerVersion, hasRole, isIOS } from '../methods/helpers'; +import { inquiryRequest } from '../../ee/omnichannel/actions/inquiry'; import { onRolesChanged } from '../methods/getRoles'; import { getSettings } from '../methods/getSettings'; import { setPresenceCap } from '../methods/getUsersPresence'; @@ -78,6 +79,12 @@ async function connect({ server, logoutOnError = false }: { server: string; logo if (user?.token) { store.dispatch(loginRequest({ resume: user.token }, logoutOnError)); } + // Omnichannel inquiry queue must be refreshed on (re)connect — the previous SDK had a + // `'connected'` event that inquiry.ts listened to; the new DDP client's `onCollection` + // doesn't fire for connection events, so the dispatch is centralized here. + if (hasRole('livechat-agent') || hasRole('livechat-manager')) { + store.dispatch(inquiryRequest()); + } } if (['disconnected', 'closed'].includes(status)) { pendingHangupsDrainArmed = true; @@ -420,7 +427,10 @@ async function getWebsocketInfo({ }; } - return { success: true }; + return { + success: false, + message: I18n.t('Invalid_URL') + }; } } diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index c7903136b22..6e10d80edff 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -1,15 +1,69 @@ +/* eslint-disable import/first */ +jest.mock('./twoFactor', () => ({ + twoFactor: jest.fn() +})); + +jest.mock('../store/auxStore', () => ({ + store: { + getState: jest.fn(() => ({ + settings: { API_Use_REST_For_DDP_Calls: false }, + login: { user: { id: 'u1', token: 't1' } }, + server: { version: '6.0.0' } + })), + dispatch: jest.fn() + } +})); + +jest.mock('../methods/userPreferences', () => ({ + __esModule: true, + default: { + getString: jest.fn(), + setString: jest.fn(), + removeItem: jest.fn() + } +})); + +jest.mock('@rocket.chat/ddp-client', () => ({ + DDPSDK: { + create: jest.fn(), + createAndConnect: jest.fn() + } +})); + +import { DDPSDK } from '@rocket.chat/ddp-client'; + import sdk, { NOTIFY_USER_EVENTS, normalizeResponseError } from './sdk'; +import { twoFactor } from './twoFactor'; +import { store as reduxStore } from '../store/auxStore'; +import UserPreferences from '../methods/userPreferences'; const setInternalSdk = (value: any) => { (sdk as unknown as { sdk: any }).sdk = value; }; -const buildFakeConnection = (overrides: Partial<{ probe: jest.Mock; forceReopen: jest.Mock }> = {}) => ({ +const buildFakeConnection = (overrides: Partial<{ probe: jest.Mock; forceReopen: jest.Mock; close: jest.Mock }> = {}) => ({ probe: jest.fn().mockResolvedValue(true), forceReopen: jest.fn().mockResolvedValue(true), + close: jest.fn(), ...overrides }); +beforeEach(() => { + (twoFactor as jest.Mock).mockReset(); + (reduxStore.getState as jest.Mock).mockReset().mockReturnValue({ + settings: { API_Use_REST_For_DDP_Calls: false }, + login: { user: { id: 'u1', token: 't1' } }, + server: { version: '6.0.0' } + }); + (UserPreferences.getString as jest.Mock).mockReset(); + (UserPreferences.setString as jest.Mock).mockReset(); + (UserPreferences.removeItem as jest.Mock).mockReset(); + (DDPSDK.create as jest.Mock).mockReset(); + (DDPSDK.createAndConnect as jest.Mock).mockReset(); + (sdk as any).code = null; + (sdk as any).serverUrl = undefined; +}); + afterEach(() => { setInternalSdk(undefined); jest.useRealTimers(); @@ -167,3 +221,428 @@ describe('normalizeResponseError', () => { expect(bodyStillReadable).toBe(JSON.stringify({ ok: true })); }); }); + +const buildFakeSdkWithRest = (overrides: any = {}) => ({ + client: { ddp: {}, ...(overrides.client || {}) }, + connection: buildFakeConnection(overrides.connection), + account: overrides.account || { user: { id: 'u1', token: 't1' } }, + rest: { + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), + handleTwoFactorChallenge: jest.fn(), + ...(overrides.rest || {}) + } +}); + +describe('Sdk.post', () => { + it('posts to a REST endpoint and returns the result', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post.mockResolvedValue({ success: true, data: { id: 'abc' } }); + setInternalSdk(fake); + const result = await (sdk as any).post('/v1/channels.create', { name: 'test' }); + expect(fake.rest.post).toHaveBeenCalledWith( + '/v1/channels.create', + { name: 'test' }, + expect.objectContaining({ headers: expect.any(Object) }) + ); + expect(result).toEqual({ success: true, data: { id: 'abc' } }); + }); + + it('normalizes a fetch Response error to { status, data }', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post.mockRejectedValue(new Response(JSON.stringify({ error: 'bad' }), { status: 400 })); + setInternalSdk(fake); + await expect((sdk as any).post('/v1/channels.create', {})).rejects.toEqual({ status: 400, data: { error: 'bad' } }); + }); + + it('rethrows non-Response errors as-is', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post.mockRejectedValue(new Error('network')); + setInternalSdk(fake); + await expect((sdk as any).post('/v1/channels.create', {})).rejects.toThrow('network'); + }); + + it('unwraps result.message for /v1/method.call endpoints (Fix 1 regression)', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post.mockResolvedValue({ message: JSON.stringify({ msg: 'result', result: { ok: true } }) }); + setInternalSdk(fake); + const result = await (sdk as any).post('/v1/method.call/someMethod', { message: '{}' }); + expect(result).toEqual({ ok: true }); + }); + + it('unwraps result.message for /v1/method.callAnon endpoints', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post.mockResolvedValue({ message: JSON.stringify({ msg: 'result', result: 'anon-ok' }) }); + setInternalSdk(fake); + const result = await (sdk as any).post('/v1/method.callAnon/someMethod', { message: '{}' }); + expect(result).toBe('anon-ok'); + }); + + it('throws the inner error from method-call responses', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post.mockResolvedValue({ + message: JSON.stringify({ msg: 'result', error: { error: 'app-error', details: {} } }) + }); + setInternalSdk(fake); + await expect((sdk as any).post('/v1/method.call/myMethod', { message: '{}' })).rejects.toEqual({ + error: 'app-error', + details: {} + }); + }); + + it('retries with 2FA when method call returns totp-required', async () => { + const fake = buildFakeSdkWithRest(); + const err = { message: JSON.stringify({ msg: 'result', error: { error: 'totp-required', details: { method: 'totp' } } }) }; + const ok = { message: JSON.stringify({ msg: 'result', result: { ok: true } }) }; + fake.rest.post.mockResolvedValueOnce(err).mockResolvedValueOnce(ok); + (twoFactor as jest.Mock).mockResolvedValue({ twoFactorCode: '123456', twoFactorMethod: 'totp' }); + setInternalSdk(fake); + const result = await (sdk as any).post('/v1/method.call/myMethod', { message: '{}' }); + expect(fake.rest.post).toHaveBeenCalledTimes(2); + expect(twoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: false }); + expect(result).toEqual({ ok: true }); + }); + + it('returns {} when 2FA is cancelled during method-call retry', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post.mockResolvedValue({ + message: JSON.stringify({ msg: 'result', error: { error: 'totp-required', details: { method: 'totp' } } }) + }); + (twoFactor as jest.Mock).mockRejectedValue(new Error('cancelled')); + setInternalSdk(fake); + const result = await (sdk as any).post('/v1/method.call/myMethod', { message: '{}' }); + expect(result).toEqual({}); + }); +}); + +describe('Sdk.methodCall', () => { + const buildFakeSdkWithMethod = (callAsyncWithOptions: jest.Mock) => ({ + client: { callAsyncWithOptions }, + connection: buildFakeConnection(), + account: { user: { id: 'u1', token: 't1' } } + }); + + it('forwards method + params to callAsyncWithOptions', async () => { + const callAsync = jest.fn().mockResolvedValue({ result: 'ok' }); + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + const result = await (sdk as any).methodCall('myMethod', 'arg1', 42); + expect(callAsync).toHaveBeenCalledWith('myMethod', {}, 'arg1', 42); + expect(result).toEqual({ result: 'ok' }); + }); + + it('appends stored TOTP code to params when present', async () => { + const callAsync = jest.fn().mockResolvedValue({}); + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + (sdk as any).code = '654321'; + await (sdk as any).methodCall('myMethod', 'arg1'); + expect(callAsync).toHaveBeenCalledWith('myMethod', {}, 'arg1', '654321'); + }); + + it('handles totp-required by prompting twoFactor and retrying', async () => { + const callAsync = jest + .fn() + .mockRejectedValueOnce({ error: 'totp-required', details: { method: 'totp' } }) + .mockResolvedValueOnce({ result: 'ok' }); + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + (twoFactor as jest.Mock).mockResolvedValue({ twoFactorCode: '123456', twoFactorMethod: 'totp' }); + const result = await (sdk as any).methodCall('myMethod'); + expect(twoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: false }); + expect(result).toEqual({ result: 'ok' }); + }); + + it('marks invalid:true when retrying after totp-invalid', async () => { + const callAsync = jest + .fn() + .mockRejectedValueOnce({ error: 'totp-invalid', details: { method: 'totp' } }) + .mockResolvedValueOnce({ result: 'ok' }); + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + (twoFactor as jest.Mock).mockResolvedValue({ twoFactorCode: '999999', twoFactorMethod: 'totp' }); + await (sdk as any).methodCall('myMethod'); + expect(twoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: true }); + }); + + it('returns {} when twoFactor is cancelled mid-retry', async () => { + const callAsync = jest.fn().mockRejectedValue({ error: 'totp-required', details: { method: 'totp' } }); + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + (twoFactor as jest.Mock).mockRejectedValue(new Error('cancelled')); + const result = await (sdk as any).methodCall('myMethod'); + expect(result).toEqual({}); + }); + + it('rejects non-2FA errors', async () => { + const callAsync = jest.fn().mockRejectedValue(new Error('some error')); + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + await expect((sdk as any).methodCall('myMethod')).rejects.toThrow('some error'); + }); + + it('rejects when sdk is not initialized', async () => { + setInternalSdk(undefined); + await expect((sdk as any).methodCall('myMethod')).rejects.toThrow('SDK not initialized'); + }); +}); + +describe('Sdk.methodCallWrapper', () => { + it('uses methodCall (WebSocket) when API_Use_REST_For_DDP_Calls is false', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ + settings: { API_Use_REST_For_DDP_Calls: false }, + login: { user: { id: 'u1' } } + }); + const methodCallSpy = jest.spyOn(sdk as any, 'methodCall').mockResolvedValue('ws-result'); + const result = await (sdk as any).methodCallWrapper('myMethod', 'arg1'); + expect(methodCallSpy).toHaveBeenCalledWith('myMethod', 'arg1'); + expect(result).toBe('ws-result'); + methodCallSpy.mockRestore(); + }); + + it('uses /v1/method.call (REST) when API_Use_REST_For_DDP_Calls is true and user is present', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ + settings: { API_Use_REST_For_DDP_Calls: true }, + login: { user: { id: 'u1' } } + }); + const postSpy = jest.spyOn(sdk as any, 'post').mockResolvedValue('rest-result'); + const result = await (sdk as any).methodCallWrapper('myMethod', { x: 1 }); + expect(postSpy).toHaveBeenCalledWith('/v1/method.call/myMethod', expect.objectContaining({ message: expect.any(String) })); + expect(result).toBe('rest-result'); + postSpy.mockRestore(); + }); + + it('uses /v1/method.callAnon when user is empty and REST is enabled', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ + settings: { API_Use_REST_For_DDP_Calls: true }, + login: { user: {} } + }); + const postSpy = jest.spyOn(sdk as any, 'post').mockResolvedValue('anon'); + await (sdk as any).methodCallWrapper('myMethod'); + expect(postSpy).toHaveBeenCalledWith('/v1/method.callAnon/myMethod', expect.any(Object)); + postSpy.mockRestore(); + }); + + it('converts Date params to { $date: ms } for the WebSocket path', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ + settings: { API_Use_REST_For_DDP_Calls: false }, + login: { user: { id: 'u1' } } + }); + const methodCallSpy = jest.spyOn(sdk as any, 'methodCall').mockResolvedValue(undefined); + const ts = 1704067200000; + await (sdk as any).methodCallWrapper('myMethod', new Date(ts)); + expect(methodCallSpy).toHaveBeenCalledWith('myMethod', { $date: ts }); + methodCallSpy.mockRestore(); + }); +}); + +describe('Sdk.get', () => { + it('forwards params and headers to rest.get', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.get.mockResolvedValue({ users: [] }); + setInternalSdk(fake); + const result = await (sdk as any).get('/v1/users.list', { count: 10 }); + expect(fake.rest.get).toHaveBeenCalledWith( + '/v1/users.list', + { count: 10 }, + expect.objectContaining({ headers: expect.any(Object) }) + ); + expect(result).toEqual({ users: [] }); + }); + + it('normalizes fetch Response errors', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.get.mockRejectedValue(new Response('{}', { status: 500 })); + setInternalSdk(fake); + await expect((sdk as any).get('/v1/users.list')).rejects.toEqual({ status: 500, data: {} }); + }); + + it('rethrows non-Response errors', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.get.mockRejectedValue(new Error('boom')); + setInternalSdk(fake); + await expect((sdk as any).get('/v1/users.list')).rejects.toThrow('boom'); + }); +}); + +describe('Sdk.delete', () => { + it('forwards params and headers to rest.delete', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.delete.mockResolvedValue({ success: true }); + setInternalSdk(fake); + const result = await (sdk as any).delete('/v1/push.token', { token: 'abc' }); + expect(fake.rest.delete).toHaveBeenCalledWith( + '/v1/push.token', + { token: 'abc' }, + expect.objectContaining({ headers: expect.any(Object) }) + ); + expect(result).toEqual({ success: true }); + }); + + it('normalizes a Response error', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.delete.mockRejectedValue(new Response(JSON.stringify({ error: 'nope' }), { status: 403 })); + setInternalSdk(fake); + await expect((sdk as any).delete('/v1/push.token', { token: 'abc' })).rejects.toEqual({ + status: 403, + data: { error: 'nope' } + }); + }); +}); + +describe('Sdk header lifecycle', () => { + it('includes a User-Agent by default', () => { + const headers = sdk.getHeaders(); + expect(headers['User-Agent']).toMatch(/^RC Mobile/); + }); + + it('adds Authorization on setBasicAuth(token) and persists to UserPreferences', () => { + (sdk as any).serverUrl = 'https://example.com'; + sdk.setBasicAuth('dXNlcjpwYXNz'); + expect(sdk.getHeaders().Authorization).toBe('Basic dXNlcjpwYXNz'); + expect(UserPreferences.setString).toHaveBeenCalledWith('BASIC_AUTH_KEY-https://example.com', 'dXNlcjpwYXNz'); + }); + + it('removes Authorization on setBasicAuth(null) and clears UserPreferences', () => { + (sdk as any).serverUrl = 'https://example.com'; + sdk.setBasicAuth('dXNlcjpwYXNz'); + sdk.setBasicAuth(null); + expect(sdk.getHeaders().Authorization).toBeUndefined(); + expect(UserPreferences.removeItem).toHaveBeenCalledWith('BASIC_AUTH_KEY-https://example.com'); + }); +}); + +describe('Sdk.onStreamData', () => { + it('resolves with a noop stop when sdk.current is undefined', async () => { + setInternalSdk(undefined); + const { stop } = await sdk.onStreamData('any', jest.fn()); + expect(typeof stop).toBe('function'); + expect(() => stop()).not.toThrow(); + }); + + it('subscribes via client.onCollection and exposes the handle as stop', async () => { + const stopHandle = jest.fn(); + const onCollection = jest.fn().mockReturnValue(stopHandle); + setInternalSdk({ client: { onCollection } }); + const { stop } = await sdk.onStreamData('stream-notify-user', jest.fn()); + expect(onCollection).toHaveBeenCalledWith('stream-notify-user', expect.any(Function)); + expect(stop).toBe(stopHandle); + }); + + it('invokes the callback only when ddpMessage has fields', async () => { + const cb = jest.fn(); + const onCollection = jest.fn((_: string, handler: (m: any) => void) => { + handler({ msg: 'changed' }); + handler({ msg: 'changed', fields: { eventName: 'x' } }); + return jest.fn(); + }); + setInternalSdk({ client: { onCollection } }); + await sdk.onStreamData('test', cb); + expect(cb).toHaveBeenCalledTimes(1); + expect(cb).toHaveBeenCalledWith({ msg: 'changed', fields: { eventName: 'x' } }); + }); +}); + +describe('Sdk.initialize', () => { + it('calls DDPSDK.create with the server URL and stores it', () => { + const handleTwoFactorChallenge = jest.fn(); + const fake = { rest: { handleTwoFactorChallenge } } as any; + (DDPSDK.create as jest.Mock).mockReturnValue(fake); + sdk.initialize('https://example.com'); + expect(DDPSDK.create).toHaveBeenCalledWith('https://example.com'); + expect(sdk.server).toBe('https://example.com'); + }); + + it('attaches subscribeNotifyUser onto the DDPSDK instance', () => { + const handleTwoFactorChallenge = jest.fn(); + const fake = { rest: { handleTwoFactorChallenge } } as any; + (DDPSDK.create as jest.Mock).mockReturnValue(fake); + sdk.initialize('https://example.com'); + expect(typeof fake.subscribeNotifyUser).toBe('function'); + }); + + it('registers handleTwoFactorChallenge on the REST client', () => { + const handleTwoFactorChallenge = jest.fn(); + (DDPSDK.create as jest.Mock).mockReturnValue({ rest: { handleTwoFactorChallenge } }); + sdk.initialize('https://example.com'); + expect(handleTwoFactorChallenge).toHaveBeenCalledWith(expect.any(Function)); + }); + + it('resets headers to defaults — does not carry Authorization across server switches', () => { + (sdk as any).serverUrl = 'https://old'; + sdk.setBasicAuth('dXNlcjpwYXNz'); + expect(sdk.getHeaders().Authorization).toBe('Basic dXNlcjpwYXNz'); + (DDPSDK.create as jest.Mock).mockReturnValue({ rest: { handleTwoFactorChallenge: jest.fn() } }); + sdk.initialize('https://new'); + expect(sdk.getHeaders().Authorization).toBeUndefined(); + }); + + it('loads Basic Auth from UserPreferences on initialize when present', () => { + (UserPreferences.getString as jest.Mock).mockImplementation((key: string) => + key === 'BASIC_AUTH_KEY-https://example.com' ? 'cmVzdW1lOnRva2Vu' : null + ); + (DDPSDK.create as jest.Mock).mockReturnValue({ rest: { handleTwoFactorChallenge: jest.fn() } }); + sdk.initialize('https://example.com'); + expect(sdk.getHeaders().Authorization).toBe('Basic cmVzdW1lOnRva2Vu'); + }); + + it('clears any stored TOTP code on initialize', () => { + (sdk as any).code = '123456'; + (DDPSDK.create as jest.Mock).mockReturnValue({ rest: { handleTwoFactorChallenge: jest.fn() } }); + sdk.initialize('https://example.com'); + expect((sdk as any).code).toBeNull(); + }); +}); + +describe('Sdk.subscribeRoom', () => { + const buildRoomSubscribeMock = () => { + const subscribe = jest.fn((..._args: any[]) => ({ id: 'sub', stop: jest.fn() })); + return { client: { subscribe }, __subscribe: subscribe }; + }; + + it('returns [] when sdk.current is undefined', async () => { + setInternalSdk(undefined); + await expect(sdk.subscribeRoom('rid-1')).resolves.toEqual([]); + }); + + it('subscribes to user-activity for RC >= 4.0', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ server: { version: '4.0.0' } }); + const fake = buildRoomSubscribeMock(); + setInternalSdk(fake); + await sdk.subscribeRoom('rid-1'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-room', 'rid-1/user-activity'); + }); + + it('falls back to typing for RC < 4.0', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ server: { version: '3.9.0' } }); + const fake = buildRoomSubscribeMock(); + setInternalSdk(fake); + await sdk.subscribeRoom('rid-1'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-room', 'rid-1/typing'); + }); + + it('subscribes to all 5 room channels', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ server: { version: '6.0.0' } }); + const fake = buildRoomSubscribeMock(); + setInternalSdk(fake); + const subs = await sdk.subscribeRoom('rid-1'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-room-messages', 'rid-1'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-room', 'rid-1/deleteMessage'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-room', 'rid-1/deleteMessageBulk'); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-room', 'rid-1/messagesRead'); + expect(subs).toHaveLength(5); + }); +}); + +describe('Sdk.disconnect', () => { + it('closes the connection and clears server when sdk is initialized', () => { + const close = jest.fn(); + setInternalSdk({ connection: { close } }); + (sdk as any).serverUrl = 'https://x.com'; + expect(sdk.disconnect()).toBeNull(); + expect(close).toHaveBeenCalledTimes(1); + expect(sdk.server).toBeUndefined(); + }); + + it('returns null without throwing when sdk is not initialized', () => { + setInternalSdk(undefined); + (sdk as any).serverUrl = 'https://x.com'; + expect(sdk.disconnect()).toBeNull(); + expect(sdk.server).toBeUndefined(); + }); +}); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 8ba7581c34b..b4256ea728c 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -164,7 +164,7 @@ class Sdk { : Serialized>> ): Promise>> { return new Promise(async (resolve, reject) => { - const isMethodCall = endpoint?.startsWith('method.call/'); + const isMethodCall = !!endpoint?.includes('/v1/method.call'); try { const sdk = this.ensureInitialized(); // @ts-ignore @@ -280,20 +280,15 @@ class Sdk { }); } - async methodCallWrapper(method: string, ...params: any[]): Promise { + methodCallWrapper(method: string, ...params: any[]): Promise { const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings; const { user } = reduxStore.getState().login; if (API_Use_REST_For_DDP_Calls) { const url = isEmpty(user) ? 'method.callAnon' : 'method.call'; const endpoint = `/v1/${url}/${method}` as PathFor<'POST'>; - const result = (await this.post(endpoint, { + return this.post(endpoint, { message: EJSON.stringify({ msg: 'method', id: random(10), method, params }) - } as any)) as unknown as { message: string }; - const response = JSON.parse(result.message); - if (response?.error) { - throw response.error; - } - return response.result; + } as any); } const parsedParams = params.map(param => { if (param instanceof Date) { diff --git a/app/lib/services/twoFactor.test.ts b/app/lib/services/twoFactor.test.ts new file mode 100644 index 00000000000..453c530bb11 --- /dev/null +++ b/app/lib/services/twoFactor.test.ts @@ -0,0 +1,70 @@ +/* eslint-disable import/first */ +jest.mock('../methods/helpers/events', () => ({ + __esModule: true, + default: { emit: jest.fn() } +})); + +jest.mock('../../containers/TwoFactor', () => ({ + TWO_FACTOR: 'TWO_FACTOR' +})); + +import { twoFactor } from './twoFactor'; +import EventEmitter from '../methods/helpers/events'; + +describe('twoFactor', () => { + beforeEach(() => { + (EventEmitter.emit as jest.Mock).mockReset(); + }); + + it('emits a TWO_FACTOR event with method, invalid, params, submit, cancel', () => { + twoFactor({ method: 'totp', invalid: false }); + expect(EventEmitter.emit).toHaveBeenCalledWith( + 'TWO_FACTOR', + expect.objectContaining({ + method: 'totp', + invalid: false, + submit: expect.any(Function), + cancel: expect.any(Function) + }) + ); + }); + + it('forwards params through the emitted event', () => { + const params = { user: 'john', password: 'p' }; + twoFactor({ method: 'email', invalid: false, params }); + const emitted = (EventEmitter.emit as jest.Mock).mock.calls[0][1]; + expect(emitted.params).toBe(params); + }); + + it('resolves with { twoFactorCode, twoFactorMethod } when submit is called', async () => { + const promise = twoFactor({ method: 'email', invalid: false }); + const emitted = (EventEmitter.emit as jest.Mock).mock.calls[0][1]; + emitted.submit('123456'); + await expect(promise).resolves.toEqual({ twoFactorCode: '123456', twoFactorMethod: 'email' }); + }); + + it('preserves the method on the resolved value even when the prompt is for a different method', async () => { + const promise = twoFactor({ method: 'totp', invalid: true }); + const emitted = (EventEmitter.emit as jest.Mock).mock.calls[0][1]; + emitted.submit('999999'); + await expect(promise).resolves.toEqual({ twoFactorCode: '999999', twoFactorMethod: 'totp' }); + }); + + it('rejects (with no value) when cancel is called', async () => { + const promise = twoFactor({ method: 'totp', invalid: false }); + const emitted = (EventEmitter.emit as jest.Mock).mock.calls[0][1]; + emitted.cancel(); + await expect(promise).rejects.toBeUndefined(); + }); + + it('emits a fresh handler pair per call (concurrent prompts are independent)', async () => { + const p1 = twoFactor({ method: 'totp', invalid: false }); + const p2 = twoFactor({ method: 'email', invalid: false }); + const e1 = (EventEmitter.emit as jest.Mock).mock.calls[0][1]; + const e2 = (EventEmitter.emit as jest.Mock).mock.calls[1][1]; + e1.submit('111111'); + e2.cancel(); + await expect(p1).resolves.toEqual({ twoFactorCode: '111111', twoFactorMethod: 'totp' }); + await expect(p2).rejects.toBeUndefined(); + }); +}); From 948a8bb0d7e1218fb99bc256838698d58a264d16 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:10:51 +0530 Subject: [PATCH 100/203] another test --- app/lib/services/sdk.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 6e10d80edff..9cf92d43583 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -429,6 +429,16 @@ describe('Sdk.methodCallWrapper', () => { expect(methodCallSpy).toHaveBeenCalledWith('myMethod', { $date: ts }); methodCallSpy.mockRestore(); }); + + it('propagates rejection from post() when the REST method call errors', async () => { + (reduxStore.getState as jest.Mock).mockReturnValue({ + settings: { API_Use_REST_For_DDP_Calls: true }, + login: { user: { id: 'u1' } } + }); + const postSpy = jest.spyOn(sdk as any, 'post').mockRejectedValue({ error: 'app-error' }); + await expect((sdk as any).methodCallWrapper('myMethod')).rejects.toEqual({ error: 'app-error' }); + postSpy.mockRestore(); + }); }); describe('Sdk.get', () => { From f5b53c3cc46fd2b1063fa1fd0802e27809165760 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:11:59 +0530 Subject: [PATCH 101/203] fix suggestion by coderabbit --- app/lib/services/connect.test.ts | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index fb293ceae21..f62de29c2d1 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -112,6 +112,12 @@ jest.mock('../methods/setUser', () => ({ _setUser: jest.fn(), _activeUsers: { ac jest.mock('../methods/getRoles', () => ({ onRolesChanged: jest.fn() })); jest.mock('../methods/getUsersPresence', () => ({ setPresenceCap: jest.fn() })); +const mockHasRole = jest.fn(); +jest.mock('../methods/helpers', () => ({ + ...jest.requireActual('../methods/helpers'), + hasRole: (...args: any[]) => mockHasRole(...args) +})); + jest.mock('../../actions/connect', () => ({ connectRequest: jest.fn().mockReturnValue({ type: 'CONNECT_REQUEST' }), connectSuccess: jest.fn().mockReturnValue({ type: 'CONNECT_SUCCESS' }), @@ -760,21 +766,13 @@ describe('connection status handler (Fix 4 regression)', () => { beforeEach(() => { mockConnectionOn.mockReset(); mockStoreDispatch.mockReset(); + mockHasRole.mockReset(); sdkMock.__setServer(undefined); }); - const setLivechatRoles = (roles: string[]) => { - jest.resetModules(); - jest.doMock('../methods/helpers', () => ({ - ...jest.requireActual('../methods/helpers'), - hasRole: jest.fn((r: string) => roles.includes(r)) - })); - }; - it('dispatches inquiryRequest on connected when user is a livechat-agent', async () => { - setLivechatRoles(['livechat-agent']); - const { connect: freshConnect } = require('./connect'); - await freshConnect({ server: 'https://x.com' }); + mockHasRole.mockImplementation((r: string) => r === 'livechat-agent'); + await connect({ server: 'https://x.com' }); const listener = getCapturedConnectionListener(); mockStoreGetState.mockReturnValue({ meteor: { connected: false }, @@ -789,10 +787,8 @@ describe('connection status handler (Fix 4 regression)', () => { }); it('does NOT dispatch inquiryRequest on connected when user has no livechat role', async () => { - setLivechatRoles([]); - const { connect: freshConnect } = require('./connect'); - sdkMock.__setServer(undefined); - await freshConnect({ server: 'https://no-livechat.com' }); + mockHasRole.mockReturnValue(false); + await connect({ server: 'https://no-livechat.com' }); const listener = getCapturedConnectionListener(); mockStoreGetState.mockReturnValue({ meteor: { connected: false }, From 2d8bd30fb5f4a61e72d5ac299af1a36ac2b4f148 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 2 Jun 2026 01:35:14 +0530 Subject: [PATCH 102/203] more tests --- app/ee/omnichannel/lib/index.test.ts | 147 +++++++ app/lib/methods/getUsersPresence.test.ts | 289 ++++++++++++++ .../sendFileMessage/sendFileMessageV2.test.ts | 177 +++++++++ app/lib/methods/subscriptions/room.test.ts | 375 ++++++++++++++++++ 4 files changed, 988 insertions(+) create mode 100644 app/ee/omnichannel/lib/index.test.ts create mode 100644 app/lib/methods/getUsersPresence.test.ts create mode 100644 app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts create mode 100644 app/lib/methods/subscriptions/room.test.ts diff --git a/app/ee/omnichannel/lib/index.test.ts b/app/ee/omnichannel/lib/index.test.ts new file mode 100644 index 00000000000..756bd1079c9 --- /dev/null +++ b/app/ee/omnichannel/lib/index.test.ts @@ -0,0 +1,147 @@ +/* eslint-disable import/first */ +jest.mock('../../../lib/services/sdk', () => ({ + __esModule: true, + default: { + get: jest.fn(), + post: jest.fn(), + methodCallWrapper: jest.fn(), + onStreamData: jest.fn(), + subscribe: jest.fn() + } +})); + +jest.mock('../../../lib/store/auxStore', () => ({ + store: { + getState: jest.fn(() => ({ + settings: {}, + login: { user: { id: 'u1', token: 't1' } }, + server: { version: '8.0.0' }, + inquiry: { queued: [] } + })), + dispatch: jest.fn() + } +})); + +jest.mock('../../../lib/methods/helpers', () => ({ + compareServerVersion: jest.fn(), + hasRole: jest.fn(() => false) +})); + +jest.mock('../../../lib/methods/helpers/events', () => ({ + __esModule: true, + default: { + addEventListener: jest.fn(), + removeListener: jest.fn(), + emit: jest.fn() + } +})); + +jest.mock('./subscriptions/inquiry', () => ({ + __esModule: true, + default: jest.fn().mockResolvedValue({ stop: jest.fn() }) +})); + +jest.mock('../../../lib/services/restApi', () => ({ + getAgentDepartments: jest.fn().mockResolvedValue({ success: true, departments: [] }) +})); + +import sdk from '../../../lib/services/sdk'; +import { store } from '../../../lib/store/auxStore'; +import { compareServerVersion } from '../../../lib/methods/helpers'; + +const mockSdkPost = sdk.post as jest.Mock; +const mockMethodCallWrapper = sdk.methodCallWrapper as jest.Mock; +const mockCompareServerVersion = compareServerVersion as jest.Mock; + +beforeEach(() => { + jest.clearAllMocks(); + mockSdkPost.mockResolvedValue({ success: true }); + mockMethodCallWrapper.mockResolvedValue(undefined); +}); + +// We need to import after the mocks are set up, and re-require each test +// to avoid module-level side effects interfering between tests. +// The Omnichannel class constructor runs on module load, so we isolate with jest.isolateModules. + +describe('changeLivechatStatus', () => { + it('uses REST POST for server >= 8.0.0', async () => { + mockCompareServerVersion.mockReturnValue(true); + (store.getState as jest.Mock).mockReturnValue({ server: { version: '8.0.0' } }); + + let changeLivechatStatus: () => any; + jest.isolateModules(() => { + ({ changeLivechatStatus } = require('./index')); + }); + + await changeLivechatStatus!(); + + expect(mockSdkPost).toHaveBeenCalledWith('/v1/livechat/agent.status'); + expect(mockMethodCallWrapper).not.toHaveBeenCalled(); + }); + + it('uses methodCallWrapper for server < 8.0.0', async () => { + mockCompareServerVersion.mockReturnValue(false); + (store.getState as jest.Mock).mockReturnValue({ server: { version: '7.9.0' } }); + + let changeLivechatStatus: () => any; + jest.isolateModules(() => { + ({ changeLivechatStatus } = require('./index')); + }); + + await changeLivechatStatus!(); + + expect(mockMethodCallWrapper).toHaveBeenCalledWith('livechat:changeLivechatStatus'); + expect(mockSdkPost).not.toHaveBeenCalled(); + }); +}); + +describe('takeInquiry', () => { + it('uses REST POST for server >= 7.11.0', async () => { + mockCompareServerVersion.mockReturnValue(true); + + let takeInquiry: (inquiryId: string, serverVersion: string) => any; + jest.isolateModules(() => { + ({ takeInquiry } = require('./index')); + }); + + await takeInquiry!('inq1', '7.11.0'); + + expect(mockSdkPost).toHaveBeenCalledWith('/v1/livechat/inquiries.take', { inquiryId: 'inq1' }); + expect(mockMethodCallWrapper).not.toHaveBeenCalled(); + }); + + it('uses methodCallWrapper for server < 7.11.0', async () => { + mockCompareServerVersion.mockReturnValue(false); + + let takeInquiry: (inquiryId: string, serverVersion: string) => any; + jest.isolateModules(() => { + ({ takeInquiry } = require('./index')); + }); + + await takeInquiry!('inq1', '7.10.0'); + + expect(mockMethodCallWrapper).toHaveBeenCalledWith('livechat:takeInquiry', 'inq1'); + expect(mockSdkPost).not.toHaveBeenCalled(); + }); +}); + +describe('isOmnichannelStatusAvailable', () => { + it('returns true when statusLivechat is "available"', () => { + let isOmnichannelStatusAvailable: (status: string | undefined) => boolean; + jest.isolateModules(() => { + ({ isOmnichannelStatusAvailable } = require('./index')); + }); + + expect(isOmnichannelStatusAvailable!('available')).toBe(true); + }); + + it('returns false when statusLivechat is not "available"', () => { + let isOmnichannelStatusAvailable: (status: string | undefined) => boolean; + jest.isolateModules(() => { + ({ isOmnichannelStatusAvailable } = require('./index')); + }); + + expect(isOmnichannelStatusAvailable!('not-available')).toBe(false); + expect(isOmnichannelStatusAvailable!(undefined)).toBe(false); + }); +}); diff --git a/app/lib/methods/getUsersPresence.test.ts b/app/lib/methods/getUsersPresence.test.ts new file mode 100644 index 00000000000..dbb9ecc3377 --- /dev/null +++ b/app/lib/methods/getUsersPresence.test.ts @@ -0,0 +1,289 @@ +/* eslint-disable import/first */ +jest.mock('@nozbe/watermelondb/RawRecord', () => ({ + sanitizedRaw: jest.fn((raw: any) => raw) +})); + +jest.mock('@nozbe/watermelondb', () => ({ + Q: { + where: jest.fn(), + gt: jest.fn(), + lt: jest.fn(), + gte: jest.fn(), + lte: jest.fn(), + or: jest.fn(), + like: jest.fn(), + oneOf: jest.fn() + } +})); + +jest.mock('../services/sdk', () => ({ + __esModule: true, + default: { + subscribe: jest.fn(), + subscribeRaw: jest.fn(), + get: jest.fn() + } +})); + +jest.mock('../store/auxStore', () => ({ + store: { + getState: jest.fn(), + dispatch: jest.fn() + } +})); + +jest.mock('../database', () => ({ + __esModule: true, + default: { + active: { + get: jest.fn(() => ({ + find: jest.fn().mockRejectedValue(new Error('not found')), + query: jest.fn(() => ({ fetch: jest.fn().mockResolvedValue([]) })), + create: jest.fn() + })), + write: jest.fn((fn: any) => Promise.resolve(fn())) + } + } +})); + +jest.mock('./helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + +jest.mock('./helpers', () => ({ + compareServerVersion: jest.requireActual('./helpers/compareServerVersion').compareServerVersion +})); + +jest.mock('./userPreferences', () => ({ + __esModule: true, + default: { + getBool: jest.fn().mockReturnValue(null), + setBool: jest.fn(), + removeItem: jest.fn() + } +})); + +jest.mock('../../actions/activeUsers', () => ({ + setActiveUsers: jest.fn((users: any) => ({ type: 'SET_ACTIVE_USERS', users })) +})); + +jest.mock('../../actions/login', () => ({ + setUser: jest.fn((user: any) => ({ type: 'SET_USER', user })) +})); + +jest.mock('../../actions/app', () => ({ + setNotificationPresenceCap: jest.fn((val: boolean) => ({ type: 'SET_NOTIFICATION_PRESENCE_CAP', val })) +})); + +jest.mock('react-native', () => ({ + InteractionManager: { + runAfterInteractions: jest.fn((fn: () => void) => fn()) + } +})); + +import sdk from '../services/sdk'; +import { store as reduxStore } from '../store/auxStore'; +import { + subscribeUsersPresence, + getUsersPresence, + getUserPresence, + setPresenceCap, + _activeUsersSubTimeout +} from './getUsersPresence'; +import { setActiveUsers } from '../../actions/activeUsers'; +import { setUser } from '../../actions/login'; +import { setNotificationPresenceCap } from '../../actions/app'; +import userPreferences from './userPreferences'; + +function setVersion(version: string, extraState: Record = {}) { + (reduxStore.getState as jest.Mock).mockReturnValue({ + server: { version }, + login: { user: { id: 'u1', username: 'testuser' } }, + settings: {}, + ...extraState + }); +} + +describe('subscribeUsersPresence', () => { + beforeEach(() => { + (sdk.subscribe as jest.Mock).mockReset(); + (reduxStore.dispatch as jest.Mock).mockReset(); + // Reset the activeUsersSubTimeout between tests + if (_activeUsersSubTimeout.activeUsersSubTimeout) { + clearTimeout(_activeUsersSubTimeout.activeUsersSubTimeout as number); + _activeUsersSubTimeout.activeUsersSubTimeout = false; + } + }); + + it('subscribes stream-notify-logged user-status for server >= 1.1.0 and < 4.1.0', () => { + setVersion('3.0.0'); + subscribeUsersPresence(); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-notify-logged', 'user-status'); + }); + + it('does NOT subscribe stream-notify-logged user-status for server >= 4.1.0', () => { + setVersion('4.1.0'); + subscribeUsersPresence(); + const userStatusCall = (sdk.subscribe as jest.Mock).mock.calls.find( + (call: any[]) => call[0] === 'stream-notify-logged' && call[1] === 'user-status' + ); + expect(userStatusCall).toBeUndefined(); + }); + + it('schedules activeUsers subscription for server < 1.1.0', () => { + jest.useFakeTimers(); + setVersion('1.0.0'); + subscribeUsersPresence(); + jest.runAllTimers(); + expect(sdk.subscribe).toHaveBeenCalledWith('activeUsers'); + jest.useRealTimers(); + }); + + it('always subscribes stream-notify-logged updateAvatar regardless of version', () => { + setVersion('5.0.0'); + subscribeUsersPresence(); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-notify-logged', 'updateAvatar'); + }); + + it('always subscribes stream-notify-logged Users:NameChanged regardless of version', () => { + setVersion('5.0.0'); + subscribeUsersPresence(); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-notify-logged', 'Users:NameChanged'); + }); +}); + +describe('getUsersPresence', () => { + beforeEach(() => { + (sdk.get as jest.Mock).mockReset(); + (sdk.subscribeRaw as jest.Mock).mockReset(); + (reduxStore.dispatch as jest.Mock).mockReset(); + }); + + it('returns early when Presence_broadcast_disabled is true', async () => { + setVersion('4.1.0', { settings: { Presence_broadcast_disabled: true } }); + await getUsersPresence(['uid1']); + expect(sdk.get).not.toHaveBeenCalled(); + }); + + it('calls /v1/users.presence with ids for server >= 3.0.0', async () => { + setVersion('4.1.0', { settings: {} }); + (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: [] }); + await getUsersPresence(['uid1', 'uid2']); + expect(sdk.get).toHaveBeenCalledWith('/v1/users.presence', { ids: 'uid1,uid2' }); + }); + + it('returns early when server >= 3.0.0 and usersParams is empty', async () => { + setVersion('4.1.0', { settings: {} }); + await getUsersPresence([]); + expect(sdk.get).not.toHaveBeenCalled(); + }); + + it('calls /v1/users.presence without ids for server 1.1.0 - 2.x', async () => { + setVersion('1.1.0', { settings: {} }); + (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: [] }); + await getUsersPresence(['uid1']); + expect(sdk.get).toHaveBeenCalledWith('/v1/users.presence', {}); + }); + + it('subscribes stream-user-presence via subscribeRaw for server >= 4.1.0 on success', async () => { + setVersion('4.1.0', { settings: {} }); + (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: [] }); + await getUsersPresence(['uid1']); + expect(sdk.subscribeRaw).toHaveBeenCalledWith('stream-user-presence', ['', { added: ['uid1'] }]); + }); + + it('dispatches setActiveUsers with user presence data', async () => { + setVersion('4.1.0', { settings: {} }); + const mockUsers = [{ _id: 'uid1', status: 'online', statusText: 'busy' }]; + (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: mockUsers }); + await getUsersPresence(['uid1']); + expect(setActiveUsers).toHaveBeenCalledWith({ + uid1: { status: 'online', statusText: 'busy' } + }); + expect(reduxStore.dispatch).toHaveBeenCalled(); + }); + + it('uses offline status for users not in the response', async () => { + setVersion('4.1.0', { settings: {} }); + (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: [] }); + await getUsersPresence(['uid-not-found']); + expect(setActiveUsers).toHaveBeenCalledWith({ + 'uid-not-found': { status: 'offline', statusText: '' } + }); + }); + + it('dispatches setUser when the current user is in the response', async () => { + setVersion('4.1.0', { + login: { user: { id: 'u1', username: 'testuser' } }, + settings: {} + }); + (reduxStore.getState as jest.Mock).mockReturnValue({ + server: { version: '4.1.0' }, + login: { user: { id: 'u1', username: 'testuser' } }, + settings: {} + }); + const mockUsers = [{ _id: 'u1', status: 'away', statusText: 'in a meeting' }]; + (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: mockUsers }); + await getUsersPresence(['u1']); + expect(setUser).toHaveBeenCalledWith({ status: 'away', statusText: 'in a meeting' }); + }); + + it('does not call SDK when server version is < 1.1.0', async () => { + setVersion('1.0.0', { settings: {} }); + await getUsersPresence(['uid1']); + expect(sdk.get).not.toHaveBeenCalled(); + }); +}); + +describe('getUserPresence', () => { + beforeEach(() => { + jest.useFakeTimers(); + (sdk.get as jest.Mock).mockReset().mockResolvedValue({ success: true, users: [] }); + setVersion('4.1.0', { settings: {} }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('batches uid and calls getUsersPresence after 2 seconds', () => { + getUserPresence('uid-batch-1'); + getUserPresence('uid-batch-2'); + // Not called yet + expect(sdk.get).not.toHaveBeenCalled(); + jest.runAllTimers(); + // After timer fires, should have called sdk.get + expect(sdk.get).toHaveBeenCalled(); + }); +}); + +describe('setPresenceCap', () => { + beforeEach(() => { + (userPreferences.getBool as jest.Mock).mockReset(); + (userPreferences.setBool as jest.Mock).mockReset(); + (userPreferences.removeItem as jest.Mock).mockReset(); + (reduxStore.dispatch as jest.Mock).mockReset(); + }); + + it('sets the presence cap preference and dispatches when enabled and not previously set to false', async () => { + (userPreferences.getBool as jest.Mock).mockReturnValue(null); + await setPresenceCap(true); + expect(userPreferences.setBool).toHaveBeenCalledWith('NOTIFICATION_PRESENCE_CAP', true); + expect(setNotificationPresenceCap).toHaveBeenCalledWith(true); + expect(reduxStore.dispatch).toHaveBeenCalled(); + }); + + it('does not re-set the cap when it is already explicitly set to false', async () => { + (userPreferences.getBool as jest.Mock).mockReturnValue(false); + await setPresenceCap(true); + expect(userPreferences.setBool).not.toHaveBeenCalled(); + }); + + it('removes the preference and dispatches false when disabled', async () => { + await setPresenceCap(false); + expect(userPreferences.removeItem).toHaveBeenCalledWith('NOTIFICATION_PRESENCE_CAP'); + expect(setNotificationPresenceCap).toHaveBeenCalledWith(false); + expect(reduxStore.dispatch).toHaveBeenCalled(); + }); +}); diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts new file mode 100644 index 00000000000..ee00e04ec2d --- /dev/null +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts @@ -0,0 +1,177 @@ +/* eslint-disable import/first */ +jest.mock('../../services/sdk', () => ({ + __esModule: true, + default: { + get: jest.fn(), + post: jest.fn(), + methodCallWrapper: jest.fn(), + getHeaders: jest.fn(() => ({ 'User-Agent': 'RC Mobile test' })) + } +})); + +jest.mock('../../store/auxStore', () => ({ + store: { + getState: jest.fn(() => ({ + settings: {}, + login: { user: { id: 'u1', token: 't1' } }, + server: { version: '6.0.0' } + })), + dispatch: jest.fn() + } +})); + +jest.mock('../../database', () => ({ + __esModule: true, + default: { + active: { + get: jest.fn(() => ({ + find: jest.fn().mockRejectedValue(new Error('not found')), + schema: {} + })), + write: jest.fn((fn: any) => Promise.resolve(fn())) + } + } +})); + +const mockSend = jest.fn().mockResolvedValue({ file: { _id: 'file1', url: '/uploads/file1' } }); +const mockState: { + url?: string; + headers?: any; + formData?: any; +} = {}; + +jest.mock('../helpers/fileUpload', () => ({ + __esModule: true, + default: jest.fn().mockImplementation((url: string, headers: any, formData: any) => { + mockState.url = url; + mockState.headers = headers; + mockState.formData = formData; + return { send: mockSend }; + }) +})); + +jest.mock('./utils', () => ({ + copyFileToCacheDirectoryIfNeeded: jest.fn().mockImplementation((p: string) => Promise.resolve(p)), + getUploadPath: jest.fn(() => 'upload-path-1'), + persistUploadError: jest.fn().mockResolvedValue(undefined), + createUploadRecord: jest.fn().mockResolvedValue(['upload-path-1', { update: jest.fn(), destroyPermanently: jest.fn() }]), + uploadQueue: {} +})); + +const mockEncryptFile = jest.fn().mockResolvedValue({ + file: { path: '/p', name: 'testfile.txt', type: 'text/plain' }, + getContent: undefined, + fileContent: undefined +}); + +jest.mock('../../encryption', () => ({ + Encryption: { + encryptFile: (...args: any[]) => mockEncryptFile(...args) + } +})); + +jest.mock('../helpers/fetch', () => ({ + __esModule: true, + default: jest.fn().mockResolvedValue({ ok: true }) +})); + +jest.mock('../helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + +jest.mock('@nozbe/watermelondb/RawRecord', () => ({ + sanitizedRaw: jest.fn(() => ({})) +})); + +jest.mock('react-native', () => ({ + Alert: { alert: jest.fn() } +})); + +import { sendFileMessageV2 } from './sendFileMessageV2'; +import sdk from '../../services/sdk'; +import fetch from '../helpers/fetch'; + +const baseFile: any = { name: 'testfile.txt', type: 'text/plain', path: '/p', size: 100 }; + +beforeEach(() => { + jest.clearAllMocks(); + mockSend.mockResolvedValue({ file: { _id: 'file1', url: '/uploads/file1' } }); + mockState.url = undefined; + mockState.headers = undefined; + mockState.formData = undefined; + mockEncryptFile.mockResolvedValue({ + file: { path: '/p', name: 'testfile.txt', type: 'text/plain' }, + getContent: undefined, + fileContent: undefined + }); + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'User-Agent': 'RC Mobile test' }); + (fetch as jest.Mock).mockResolvedValue({ ok: true }); +}); + +describe('sendFileMessageV2', () => { + it('builds form data with correct file fields', async () => { + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + + expect(mockState.formData).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'file', + type: 'text/plain', + filename: 'testfile.txt', + uri: '/p' + }) + ]) + ); + }); + + it('includes auth headers from sdk.getHeaders()', async () => { + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + + expect(mockState.headers).toMatchObject({ + 'User-Agent': 'RC Mobile test', + 'Content-Type': 'multipart/form-data', + 'X-Auth-Token': 'tok1', + 'X-User-Id': 'u1' + }); + }); + + it('includes content form-data entry when Encryption returns fileContent', async () => { + mockEncryptFile.mockResolvedValue({ + file: { path: '/p', name: 'testfile.txt', type: 'text/plain' }, + getContent: jest.fn().mockResolvedValue({ algorithm: 'rc4' }), + fileContent: { algorithm: 'rc4' } + }); + + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + + const contentField = mockState.formData?.find((e: any) => e.name === 'content'); + expect(contentField).toBeDefined(); + expect(JSON.parse(contentField.data)).toEqual({ algorithm: 'rc4' }); + }); + + it('throws and calls persistUploadError on upload failure', async () => { + mockSend.mockRejectedValue(new Error('upload failed')); + const { persistUploadError, uploadQueue } = require('./utils'); + // ensure uploadQueue has the path entry so the error path triggers persistUploadError + uploadQueue['upload-path-1'] = {}; + + await expect( + sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }) + ).rejects.toThrow('upload failed'); + + expect(persistUploadError).toHaveBeenCalledWith(baseFile.path, 'rid1'); + }); + + it('confirms upload via POST to /v1/rooms.mediaConfirm', async () => { + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + + expect(fetch).toHaveBeenCalledWith( + 'https://server.com/api/v1/rooms.mediaConfirm/rid1/file1', + expect.objectContaining({ + method: 'POST', + headers: expect.objectContaining({ 'Content-Type': 'application/json' }) + }) + ); + }); +}); diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts new file mode 100644 index 00000000000..0444dbe680a --- /dev/null +++ b/app/lib/methods/subscriptions/room.test.ts @@ -0,0 +1,375 @@ +/* eslint-disable import/first */ +jest.mock('@nozbe/watermelondb/RawRecord', () => ({ + sanitizedRaw: jest.fn((raw: any) => raw) +})); + +jest.mock('@nozbe/watermelondb', () => ({ + Q: { + where: jest.fn(), + gt: jest.fn(), + lt: jest.fn(), + gte: jest.fn(), + lte: jest.fn(), + or: jest.fn(), + like: jest.fn(), + oneOf: jest.fn() + } +})); + +jest.mock('../../services/sdk', () => ({ + __esModule: true, + default: { + current: undefined, + subscribeRoom: jest.fn(), + onStreamData: jest.fn() + } +})); + +jest.mock('../../database', () => ({ + __esModule: true, + default: { + active: { + get: jest.fn(() => ({ + find: jest.fn().mockResolvedValue(null), + query: jest.fn(() => ({ fetch: jest.fn().mockResolvedValue([]) })), + prepareCreate: jest.fn() + })), + write: jest.fn((fn: any) => Promise.resolve(fn())), + batch: jest.fn() + } + } +})); + +jest.mock('../../store/auxStore', () => ({ + store: { + getState: jest.fn(() => ({ + login: { user: { id: 'u1', username: 'testuser', name: 'Test User' } }, + settings: { UI_Use_Real_Name: false }, + room: { subscribedRoom: 'ROOM_001' } + })), + dispatch: jest.fn() + } +})); + +jest.mock('../helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + +jest.mock('../helpers/protectedFunction', () => ({ + __esModule: true, + default: (fn: any) => fn +})); + +jest.mock('../helpers/buildMessage', () => ({ + __esModule: true, + default: (msg: any) => msg +})); + +jest.mock('../helpers', () => ({ + debounce: (_fn: any, _ms: number) => _fn +})); + +jest.mock('../../encryption', () => ({ + Encryption: { + decryptMessage: jest.fn().mockImplementation((m: any) => Promise.resolve(m)) + } +})); + +jest.mock('../loadMissedMessages', () => ({ + loadMissedMessages: jest.fn().mockResolvedValue(undefined) +})); + +jest.mock('../readMessages', () => ({ + readMessages: jest.fn().mockResolvedValue(undefined) +})); + +jest.mock('../updateLastOpen', () => ({ + updateLastOpen: jest.fn().mockResolvedValue(undefined) +})); + +jest.mock('../helpers/markMessagesRead', () => ({ + __esModule: true, + default: jest.fn().mockResolvedValue(undefined) +})); + +jest.mock('../../database/services/Message', () => ({ + getMessageById: jest.fn().mockResolvedValue(null) +})); + +jest.mock('../../database/services/Thread', () => ({ + getThreadById: jest.fn().mockResolvedValue(null) +})); + +jest.mock('../../database/services/ThreadMessage', () => ({ + getThreadMessageById: jest.fn().mockResolvedValue(null) +})); + +jest.mock('../../../actions/usersTyping', () => ({ + addUserTyping: jest.fn((name: string) => ({ type: 'USERS_TYPING_ADD', username: name })), + removeUserTyping: jest.fn((name: string) => ({ type: 'USERS_TYPING_REMOVE', username: name })), + clearUserTyping: jest.fn(() => ({ type: 'USERS_TYPING_CLEAR' })) +})); + +jest.mock('../../../actions/room', () => ({ + subscribeRoom: jest.fn((rid: string) => ({ type: 'ROOM_SUBSCRIBE', rid })), + unsubscribeRoom: jest.fn((rid: string) => ({ type: 'ROOM_UNSUBSCRIBE', rid })) +})); + +jest.mock('react-native', () => ({ + InteractionManager: { + runAfterInteractions: jest.fn((fn: () => void) => fn()) + } +})); + +jest.mock('ejson', () => ({ + fromJSONValue: jest.fn((v: any) => v) +})); + +import RoomSubscription from './room'; +import sdk from '../../services/sdk'; +import { store as reduxStore } from '../../store/auxStore'; +import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping'; +import { subscribeRoom, unsubscribeRoom } from '../../../actions/room'; +import database from '../../database'; + +const TEST_RID = 'ROOM_001'; + +describe('RoomSubscription', () => { + let stopMock: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + stopMock = jest.fn(); + (sdk.subscribeRoom as jest.Mock).mockResolvedValue([{ unsubscribe: jest.fn() }]); + (sdk.onStreamData as jest.Mock).mockResolvedValue({ stop: stopMock }); + (reduxStore.getState as jest.Mock).mockReturnValue({ + login: { user: { id: 'u1', username: 'testuser', name: 'Test User' } }, + settings: { UI_Use_Real_Name: false }, + room: { subscribedRoom: TEST_RID } + }); + }); + + describe('subscribe()', () => { + it('calls sdk.subscribeRoom with the correct rid', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + expect(sdk.subscribeRoom).toHaveBeenCalledWith(TEST_RID); + }); + + it('registers stream-notify-room listener via sdk.onStreamData', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + expect(sdk.onStreamData).toHaveBeenCalledWith('stream-notify-room', expect.any(Function)); + }); + + it('registers stream-room-messages listener via sdk.onStreamData', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + expect(sdk.onStreamData).toHaveBeenCalledWith('stream-room-messages', expect.any(Function)); + }); + + it('dispatches subscribeRoom action with the correct rid', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + expect(subscribeRoom).toHaveBeenCalledWith(TEST_RID); + expect(reduxStore.dispatch).toHaveBeenCalledWith(expect.objectContaining({ rid: TEST_RID })); + }); + + it('isAlive flag prevents double-subscribe side-effects when already unsubscribed', async () => { + const sub = new RoomSubscription(TEST_RID); + // Mark as dead before subscribing + (sub as any).isAlive = false; + await sub.subscribe(); + // unsubscribe should have been called automatically due to isAlive=false + expect(unsubscribeRoom).toHaveBeenCalledWith(TEST_RID); + }); + }); + + describe('unsubscribe()', () => { + it('calls stop() on all registered stream listeners', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + await sub.unsubscribe(); + // 4 listeners registered (connected, close, stream-notify-room, stream-room-messages) + expect(stopMock).toHaveBeenCalled(); + }); + + it('dispatches unsubscribeRoom and clearUserTyping actions', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + (reduxStore.dispatch as jest.Mock).mockClear(); + await sub.unsubscribe(); + expect(unsubscribeRoom).toHaveBeenCalledWith(TEST_RID); + expect(clearUserTyping).toHaveBeenCalled(); + }); + + it('sets isAlive to false', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + await sub.unsubscribe(); + expect((sub as any).isAlive).toBe(false); + }); + }); + + describe('handleNotifyRoomReceived — typing event', () => { + let capturedRoomHandler: (msg: any) => Promise; + + beforeEach(async () => { + (sdk.onStreamData as jest.Mock).mockImplementation((name: string, handler: any) => { + if (name === 'stream-notify-room') capturedRoomHandler = handler; + return Promise.resolve({ stop: stopMock }); + }); + }); + + it('dispatches addUserTyping when another user starts typing', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + await capturedRoomHandler({ + fields: { + eventName: `${TEST_RID}/typing`, + args: ['otheruser', true] + } + }); + expect(addUserTyping).toHaveBeenCalledWith('otheruser'); + expect(reduxStore.dispatch).toHaveBeenCalledWith(expect.objectContaining({ username: 'otheruser' })); + }); + + it('dispatches removeUserTyping when another user stops typing', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + await capturedRoomHandler({ + fields: { + eventName: `${TEST_RID}/typing`, + args: ['otheruser', false] + } + }); + expect(removeUserTyping).toHaveBeenCalledWith('otheruser'); + }); + + it('does not dispatch typing for the current user', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + (reduxStore.dispatch as jest.Mock).mockClear(); + await capturedRoomHandler({ + fields: { + eventName: `${TEST_RID}/typing`, + args: ['testuser', true] + } + }); + expect(addUserTyping).not.toHaveBeenCalled(); + }); + + it('ignores typing events for a different room', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + (reduxStore.dispatch as jest.Mock).mockClear(); + await capturedRoomHandler({ + fields: { + eventName: 'OTHER_ROOM/typing', + args: ['otheruser', true] + } + }); + expect(addUserTyping).not.toHaveBeenCalled(); + }); + }); + + describe('handleNotifyRoomReceived — user-activity event', () => { + let capturedRoomHandler: (msg: any) => Promise; + + beforeEach(async () => { + (sdk.onStreamData as jest.Mock).mockImplementation((name: string, handler: any) => { + if (name === 'stream-notify-room') capturedRoomHandler = handler; + return Promise.resolve({ stop: stopMock }); + }); + }); + + it('dispatches addUserTyping when user-activity includes user-typing', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + await capturedRoomHandler({ + fields: { + eventName: `${TEST_RID}/user-activity`, + args: ['otheruser', ['user-typing']] + } + }); + expect(addUserTyping).toHaveBeenCalledWith('otheruser'); + }); + + it('dispatches removeUserTyping when user-activity has empty activities', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + await capturedRoomHandler({ + fields: { + eventName: `${TEST_RID}/user-activity`, + args: ['otheruser', []] + } + }); + expect(removeUserTyping).toHaveBeenCalledWith('otheruser'); + }); + }); + + describe('handleNotifyRoomReceived — deleteMessage event', () => { + let capturedRoomHandler: (msg: any) => Promise; + let mockFind: jest.Mock; + let mockWrite: jest.Mock; + let mockBatch: jest.Mock; + + beforeEach(async () => { + mockFind = jest.fn().mockRejectedValue(new Error('not found')); + mockWrite = jest.fn((fn: any) => Promise.resolve(fn())); + mockBatch = jest.fn().mockResolvedValue(undefined); + + (database.active.get as jest.Mock).mockReturnValue({ + find: mockFind + }); + (database.active.write as jest.Mock).mockImplementation(mockWrite); + (database.active.batch as jest.Mock).mockImplementation(mockBatch); + + (sdk.onStreamData as jest.Mock).mockImplementation((name: string, handler: any) => { + if (name === 'stream-notify-room') capturedRoomHandler = handler; + return Promise.resolve({ stop: stopMock }); + }); + }); + + it('attempts to find and delete message, thread, and thread message from DB', async () => { + const sub = new RoomSubscription(TEST_RID); + await sub.subscribe(); + await capturedRoomHandler({ + fields: { + eventName: `${TEST_RID}/deleteMessage`, + args: [{ _id: 'msg-to-delete' }] + } + }); + // find is called for messages, threads, and thread_messages + expect(mockFind).toHaveBeenCalledWith('msg-to-delete'); + }); + }); + + describe('handleMessageReceived', () => { + // NOTE: updateMessage returns a Promise that never calls resolve() in its main path, + // so tests that await the full message-processing flow will timeout. + // The tests below verify only what can be observed synchronously or via non-awaited calls. + + it.todo('calls Encryption.decryptMessage with the received message'); + it.todo('writes a new message record to the database when message does not exist'); + it.todo('updates an existing message record in the database'); + it.todo('creates thread record when message has tlm field'); + it.todo('creates thread message record when message has tmid field'); + }); + + describe('removeListener()', () => { + it('calls stop() on a resolved promise listener', async () => { + const sub = new RoomSubscription(TEST_RID); + const innerStop = jest.fn(); + const promise = Promise.resolve({ stop: innerStop }); + await (sub as any).removeListener(promise); + expect(innerStop).toHaveBeenCalled(); + }); + + it('does nothing when promise is undefined', async () => { + const sub = new RoomSubscription(TEST_RID); + await expect((sub as any).removeListener(undefined)).resolves.toBeUndefined(); + }); + }); +}); From 09fcfe426e10b3a3496ccfc2a1331a690ffe422a Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 2 Jun 2026 01:39:48 +0530 Subject: [PATCH 103/203] remove todo test --- app/lib/methods/subscriptions/room.test.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts index 0444dbe680a..2bbabcaabd5 100644 --- a/app/lib/methods/subscriptions/room.test.ts +++ b/app/lib/methods/subscriptions/room.test.ts @@ -346,18 +346,6 @@ describe('RoomSubscription', () => { }); }); - describe('handleMessageReceived', () => { - // NOTE: updateMessage returns a Promise that never calls resolve() in its main path, - // so tests that await the full message-processing flow will timeout. - // The tests below verify only what can be observed synchronously or via non-awaited calls. - - it.todo('calls Encryption.decryptMessage with the received message'); - it.todo('writes a new message record to the database when message does not exist'); - it.todo('updates an existing message record in the database'); - it.todo('creates thread record when message has tlm field'); - it.todo('creates thread message record when message has tmid field'); - }); - describe('removeListener()', () => { it('calls stop() on a resolved promise listener', async () => { const sub = new RoomSubscription(TEST_RID); From 52711b091b56a15b03c822a77564cd183b907bca Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 2 Jun 2026 01:49:58 +0530 Subject: [PATCH 104/203] fix eslint... --- app/lib/methods/subscriptions/room.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts index 2bbabcaabd5..8e0611a84ba 100644 --- a/app/lib/methods/subscriptions/room.test.ts +++ b/app/lib/methods/subscriptions/room.test.ts @@ -215,7 +215,7 @@ describe('RoomSubscription', () => { describe('handleNotifyRoomReceived — typing event', () => { let capturedRoomHandler: (msg: any) => Promise; - beforeEach(async () => { + beforeEach(() => { (sdk.onStreamData as jest.Mock).mockImplementation((name: string, handler: any) => { if (name === 'stream-notify-room') capturedRoomHandler = handler; return Promise.resolve({ stop: stopMock }); @@ -277,7 +277,7 @@ describe('RoomSubscription', () => { describe('handleNotifyRoomReceived — user-activity event', () => { let capturedRoomHandler: (msg: any) => Promise; - beforeEach(async () => { + beforeEach(() => { (sdk.onStreamData as jest.Mock).mockImplementation((name: string, handler: any) => { if (name === 'stream-notify-room') capturedRoomHandler = handler; return Promise.resolve({ stop: stopMock }); @@ -315,7 +315,7 @@ describe('RoomSubscription', () => { let mockWrite: jest.Mock; let mockBatch: jest.Mock; - beforeEach(async () => { + beforeEach(() => { mockFind = jest.fn().mockRejectedValue(new Error('not found')); mockWrite = jest.fn((fn: any) => Promise.resolve(fn())); mockBatch = jest.fn().mockResolvedValue(undefined); From 5145c0862c5d15af65de316b7e2e71c081bc99e3 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 2 Jun 2026 03:14:17 +0530 Subject: [PATCH 105/203] more changes --- app/lib/services/connect.test.ts | 12 +++++ app/lib/services/connect.ts | 11 ++-- app/lib/services/sdk.test.ts | 86 ++++++++++++++++++++++++++++++++ app/lib/services/sdk.ts | 27 +++++----- 4 files changed, 117 insertions(+), 19 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index f62de29c2d1..50a481cbcf2 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -727,6 +727,18 @@ describe('loginTOTP', () => { expect(mockTwoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: true }); }); + it('passes invalid:true when e.data.error is totp-invalid even if details.error is absent', async () => { + mockSdkLogin + .mockRejectedValueOnce({ + // details.error is intentionally absent — only e.data.error is set + data: { error: 'totp-invalid', details: { method: 'totp' } } + }) + .mockResolvedValueOnce({ me: { username: 'john' } }); + mockTwoFactor.mockResolvedValue({ twoFactorCode: '777777', twoFactorMethod: 'totp' }); + await loginTOTP({ user: 'john', password: 'p' } as any, true); + expect(mockTwoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: true }); + }); + it('normalizes ldapPass to password on RC >= 3.9.0 before 2FA retry', async () => { mockSdkLogin .mockRejectedValueOnce({ data: { error: 'totp-required', details: { method: 'totp' } } }) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index bc71d66596a..fe55d046863 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -126,8 +126,6 @@ async function connect({ server, logoutOnError = false }: { server: string; logo } catch (e) { log(e); } - } else { - console.warn(`Setting with _id '${_id}' is not present in defaultSettings.`); } } }) @@ -303,7 +301,7 @@ function loginTOTP(params: ICredentials, loginEmailPassword?: boolean): Promise< if (e.data?.error && (e.data.error === 'totp-required' || e.data.error === 'totp-invalid')) { const { details } = e.data; try { - const code = await twoFactor({ method: details?.method || 'totp', invalid: details?.error === 'totp-invalid' }); + const code = await twoFactor({ method: details?.method || 'totp', invalid: e.data.error === 'totp-invalid' }); if (loginEmailPassword) { store.dispatch(setUser({ username: params.user || params.username })); @@ -412,10 +410,9 @@ async function getWebsocketInfo({ }: { server: string; }): Promise<{ success: true } | { success: false; message: string }> { + let probeSdk: DDPSDK | undefined; try { - const sdk = await DDPSDK.createAndConnect(server); - sdk.connection.close(); - + probeSdk = await DDPSDK.createAndConnect(server); return { success: true }; @@ -431,6 +428,8 @@ async function getWebsocketInfo({ success: false, message: I18n.t('Invalid_URL') }; + } finally { + probeSdk?.connection.close(); } } diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 9cf92d43583..5f8a0054a0d 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -314,6 +314,63 @@ describe('Sdk.post', () => { const result = await (sdk as any).post('/v1/method.call/myMethod', { message: '{}' }); expect(result).toEqual({}); }); + + it('retries with 2FA when normal API returns totp-required', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post + .mockRejectedValueOnce( + new Response(JSON.stringify({ errorType: 'totp-required', details: { method: 'totp' } }), { status: 401 }) + ) + .mockResolvedValueOnce({ success: true }); + (twoFactor as jest.Mock).mockResolvedValue({ twoFactorCode: '999888', twoFactorMethod: 'totp' }); + + setInternalSdk(fake); + const result = await (sdk as any).post('/v1/channels.create', { name: 'test' }); + + expect(fake.rest.post).toHaveBeenCalledTimes(2); + expect(twoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: false }); + expect(result).toEqual({ success: true }); + }); + + it('passes invalid:true to twoFactor when normal API returns totp-invalid', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post + .mockRejectedValueOnce( + new Response(JSON.stringify({ errorType: 'totp-invalid', details: { method: 'email' } }), { status: 401 }) + ) + .mockResolvedValueOnce({ success: true }); + (twoFactor as jest.Mock).mockResolvedValue({ twoFactorCode: '111222', twoFactorMethod: 'email' }); + + setInternalSdk(fake); + await (sdk as any).post('/v1/channels.create', { name: 'test' }); + + expect(twoFactor).toHaveBeenCalledWith({ method: 'email', invalid: true }); + expect(fake.rest.post).toHaveBeenCalledTimes(2); + }); + + it('prompts twoFactor twice when first code is wrong and retries a third time', async () => { + const fake = buildFakeSdkWithRest(); + fake.rest.post + .mockRejectedValueOnce( + new Response(JSON.stringify({ errorType: 'totp-required', details: { method: 'totp' } }), { status: 401 }) + ) + .mockRejectedValueOnce( + new Response(JSON.stringify({ errorType: 'totp-invalid', details: { method: 'totp' } }), { status: 401 }) + ) + .mockResolvedValueOnce({ success: true }); + (twoFactor as jest.Mock) + .mockResolvedValueOnce({ twoFactorCode: '111111', twoFactorMethod: 'totp' }) + .mockResolvedValueOnce({ twoFactorCode: '222222', twoFactorMethod: 'totp' }); + + setInternalSdk(fake); + const result = await (sdk as any).post('/v1/channels.create', { name: 'test' }); + + expect(fake.rest.post).toHaveBeenCalledTimes(3); + expect(twoFactor).toHaveBeenCalledTimes(2); + expect(twoFactor).toHaveBeenNthCalledWith(1, { method: 'totp', invalid: false }); + expect(twoFactor).toHaveBeenNthCalledWith(2, { method: 'totp', invalid: true }); + expect(result).toEqual({ success: true }); + }); }); describe('Sdk.methodCall', () => { @@ -362,6 +419,27 @@ describe('Sdk.methodCall', () => { expect(twoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: true }); }); + it('uses the second code — not the first — when the first TOTP attempt is invalid', async () => { + // Flow: totp-required → code '111111' stored → totp-invalid → code '222222' stored → 3rd call uses '222222' + const callAsync = jest + .fn() + .mockRejectedValueOnce({ error: 'totp-required', details: { method: 'totp' } }) + .mockRejectedValueOnce({ error: 'totp-invalid', details: { method: 'totp' } }) + .mockResolvedValueOnce({ result: 'ok' }); + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + (twoFactor as jest.Mock) + .mockResolvedValueOnce({ twoFactorCode: '111111', twoFactorMethod: 'totp' }) + .mockResolvedValueOnce({ twoFactorCode: '222222', twoFactorMethod: 'totp' }); + + await (sdk as any).methodCall('myMethod'); + + expect(callAsync).toHaveBeenCalledTimes(3); + // 2nd call carries the first code + expect(callAsync).toHaveBeenNthCalledWith(2, 'myMethod', {}, { twoFactorCode: '111111', twoFactorMethod: 'totp' }); + // 3rd call must carry the NEW code, not the stale first one + expect(callAsync).toHaveBeenNthCalledWith(3, 'myMethod', {}, { twoFactorCode: '222222', twoFactorMethod: 'totp' }); + }); + it('returns {} when twoFactor is cancelled mid-retry', async () => { const callAsync = jest.fn().mockRejectedValue({ error: 'totp-required', details: { method: 'totp' } }); setInternalSdk(buildFakeSdkWithMethod(callAsync)); @@ -515,6 +593,14 @@ describe('Sdk header lifecycle', () => { expect(sdk.getHeaders().Authorization).toBeUndefined(); expect(UserPreferences.removeItem).toHaveBeenCalledWith('BASIC_AUTH_KEY-https://example.com'); }); + + it('getHeaders returns a copy — mutating it does not affect internal headers', () => { + (sdk as any).serverUrl = 'https://example.com'; + sdk.setBasicAuth('dXNlcjpwYXNz'); + const headers = sdk.getHeaders(); + delete (headers as any).Authorization; // mutate the returned copy + expect(sdk.getHeaders().Authorization).toBe('Basic dXNlcjpwYXNz'); + }); }); describe('Sdk.onStreamData', () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index b4256ea728c..586e6dc312b 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -110,7 +110,7 @@ class Sdk { } getHeaders(): Record { - return this.headers; + return { ...this.headers }; } setBasicAuth(basicAuth: string | null): void { @@ -187,20 +187,21 @@ class Sdk { // @rocket.chat/api-client rejects with the raw fetch Response on REST errors. // Normalize to { status, data } so callers can read e.data.* const normalized = !isMethodCall && e instanceof Response ? await normalizeResponseError(e) : e; - if (isMethodCall) { - // DDP-over-REST errors arrive as EJSON objects (HTTP 200), not Response instances, - // so the api-client's handleTwoFactorChallenge never intercepts them. - const errorType = normalized?.error; - if (errorType === 'totp-required' || errorType === 'totp-invalid') { - try { - await twoFactor({ method: normalized?.details?.method, invalid: errorType === 'totp-invalid' }); - return resolve(this.post(endpoint, params)); - } catch { - return resolve({} as any); - } + const errorType = isMethodCall ? normalized?.error : normalized?.data?.errorType; + const totpInvalid = 'totp-invalid'; + const totpRequired = 'totp-required'; + if ([totpInvalid, totpRequired].includes(errorType)) { + const { details } = isMethodCall ? normalized : normalized?.data; + try { + await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); + return resolve(this.post(endpoint, params)); + } catch { + // twoFactor was canceled + return resolve({} as any); } + } else { + reject(e); } - reject(normalized); } }); } From 2ad19571ad3f5c1afa36581ce881b5c29000d03c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 2 Jun 2026 03:14:37 +0530 Subject: [PATCH 106/203] fix --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 586e6dc312b..6f6557ed39d 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -200,7 +200,7 @@ class Sdk { return resolve({} as any); } } else { - reject(e); + reject(normalized); } } }); From 18777a4a2cf09eb571189733739e8dfdd53e320d Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 5 Jun 2026 19:52:56 +0530 Subject: [PATCH 107/203] test(deepLinking): update sdk mock from client.host to sdk.server Prerequisite for the deepLinking.js fixes: the mock now exposes `server` (the Sdk wrapper getter) instead of the removed `current.client.host` property from the old ddp-client. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- app/sagas/__tests__/deepLinking.test.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/sagas/__tests__/deepLinking.test.ts b/app/sagas/__tests__/deepLinking.test.ts index 2ab7473d779..afe2c5610c3 100644 --- a/app/sagas/__tests__/deepLinking.test.ts +++ b/app/sagas/__tests__/deepLinking.test.ts @@ -35,11 +35,8 @@ jest.mock('../../lib/services/connect', () => ({ jest.mock('../../lib/services/sdk', () => ({ __esModule: true, default: { - current: { - client: { - host: '' - } - } + server: undefined, + current: undefined } })); @@ -386,13 +383,13 @@ describe('deepLinking saga — server already connected, should skip changing se jest.mocked(goRoom).mockResolvedValue(undefined); // Key setup: SDK websocket is already open to HOST - (sdk.current as any).client.host = HOST; + (sdk as any).server = HOST; }); afterEach(() => { jest.useRealTimers(); // Reset so other describe blocks see the default empty host - (sdk.current as any).client.host = ''; + (sdk as any).server = undefined; }); /** From 2819208b3653314c07f030f592ef6111ec7c36f9 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:26:02 +0530 Subject: [PATCH 108/203] deeplinking changes --- app/sagas/__tests__/deepLinking.test.ts | 60 +++++++++++++++++++++++++ app/sagas/deepLinking.js | 4 +- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/app/sagas/__tests__/deepLinking.test.ts b/app/sagas/__tests__/deepLinking.test.ts index afe2c5610c3..19314137a35 100644 --- a/app/sagas/__tests__/deepLinking.test.ts +++ b/app/sagas/__tests__/deepLinking.test.ts @@ -96,6 +96,8 @@ import { goRoom } from '../../lib/methods/helpers/goRoom'; import { waitForNavigationReady } from '../../lib/navigation/appNavigation'; import sdk from '../../lib/services/sdk'; import EventEmitter from '../../lib/methods/helpers/events'; +import { localAuthenticate } from '../../lib/methods/helpers/localAuthentication'; +import { CURRENT_SERVER, TOKEN_KEY } from '../../lib/constants/keys'; // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -448,3 +450,61 @@ describe('deepLinking saga — server already connected, should skip changing se emitSpy.mockRestore(); }); }); + +describe('deepLinking saga — handleShareExtension server-already-connected guard', () => { + const SERVER = 'https://my.server.com'; + const USER_TOKEN = 'user-token-123'; + + beforeEach(() => { + jest.mocked(UserPreferences.getString).mockImplementation((key: string) => { + if (key === CURRENT_SERVER) return SERVER; + if (key === `${TOKEN_KEY}-${SERVER}`) return USER_TOKEN; + return null; + }); + jest.mocked(getServerById).mockResolvedValue({ id: SERVER, version: '6.0.0' } as any); + jest.mocked(localAuthenticate).mockResolvedValue(undefined); + (sdk as any).server = undefined; + }); + + afterEach(() => { + (sdk as any).server = undefined; + }); + + /** + * Critical regression: when the SDK is already connected to the server, + * selectServer short-circuits without emitting LOGIN.SUCCESS. + * The saga MUST NOT block on take(LOGIN.SUCCESS) in that case. + */ + it('navigates to ROOT_SHARE_EXTENSION without waiting for LOGIN.SUCCESS when sdk.server === server', async () => { + (sdk as any).server = SERVER; // already connected + const store = setupStore(); + + store.dispatch(deepLinkingOpen({ type: 'shareextension' } as any)); + await flushSagaMicrotasks(); + await flushSagaMicrotasks(); + await flushSagaMicrotasks(); + + expect(store.getState().app.root).toBe(RootEnum.ROOT_SHARE_EXTENSION); + }); + + /** + * Normal path: SDK is connected to a different server. The saga must + * wait for LOGIN.SUCCESS before navigating to ROOT_SHARE_EXTENSION. + */ + it('blocks on take(LOGIN.SUCCESS) then navigates when sdk.server !== server', async () => { + (sdk as any).server = 'https://other.server.com'; // different server + const store = setupStore(); + + store.dispatch(deepLinkingOpen({ type: 'shareextension' } as any)); + await flushSagaMicrotasks(); + await flushSagaMicrotasks(); + + // Still blocked — LOGIN.SUCCESS not yet dispatched + expect(store.getState().app.root).not.toBe(RootEnum.ROOT_SHARE_EXTENSION); + + store.dispatch(loginSuccess({ id: 'u1', token: USER_TOKEN } as any)); + await flushSagaMicrotasks(); + + expect(store.getState().app.root).toBe(RootEnum.ROOT_SHARE_EXTENSION); + }); +}); diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 5eee341408d..78a5814a863 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -152,7 +152,7 @@ const handleShareExtension = function* handleOpen({ params }) { return; } yield put(selectServerRequest(server, serverRecord.version)); - if (sdk.current?.client?.host !== server) { + if (sdk.server !== server) { yield take(types.LOGIN.SUCCESS); } yield put(shareSetParams(params)); @@ -226,7 +226,7 @@ const handleOpen = function* handleOpen({ params }) { return; } // if the host is different from the current one, we need to connect to it before navigating - const hostAlreadyConnected = sdk.current?.client?.host === host; + const hostAlreadyConnected = sdk.server === host; if (!hostAlreadyConnected) { yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); yield put(serverInitAdd(server)); From 4cf875f20515029088ee047c69d00693a4ce3ba2 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 02:23:11 +0530 Subject: [PATCH 109/203] Added mock device-info for versions to fix header snapshot --- __mocks__/react-native-device-info.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/__mocks__/react-native-device-info.js b/__mocks__/react-native-device-info.js index 427998a1712..3097a204b27 100644 --- a/__mocks__/react-native-device-info.js +++ b/__mocks__/react-native-device-info.js @@ -1,3 +1,7 @@ import mockDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock'; +mockDeviceInfo.getSystemVersion = jest.fn(() => '14.0'); +mockDeviceInfo.getVersion = jest.fn(() => '1.0.0'); +mockDeviceInfo.getBuildNumber = jest.fn(() => '1'); + export default mockDeviceInfo; From 73a85deb94cf3db11d86a51050433683e7191062 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 02:23:21 +0530 Subject: [PATCH 110/203] story update --- .../Avatar/__snapshots__/Avatar.test.tsx.snap | 34 +- .../Chip/__snapshots__/Chip.test.tsx.snap | 6 +- .../__snapshots__/DirectoryItem.test.tsx.snap | 14 +- .../NotifierComponent.test.tsx.snap | 8 +- .../__snapshots__/PeerItem.test.tsx.snap | 4 +- .../__snapshots__/PeerList.test.tsx.snap | 6 +- .../__snapshots__/SelectedPeer.test.tsx.snap | 8 +- .../__snapshots__/RoomItem.test.tsx.snap | 100 +-- .../__snapshots__/Message.test.tsx.snap | 600 +++++++++--------- .../__snapshots__/index.test.tsx.snap | 28 +- .../__snapshots__/CallerInfo.test.tsx.snap | 4 +- .../__snapshots__/Item.test.tsx.snap | 16 +- .../__snapshots__/Item.test.tsx.snap | 8 +- .../__snapshots__/LoadMore.test.tsx.snap | 18 +- .../__snapshots__/Item.test.tsx.snap | 24 +- 15 files changed, 439 insertions(+), 439 deletions(-) diff --git a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap index ecb8f0874b4..b1e2129b8df 100644 --- a/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap +++ b/app/containers/Avatar/__snapshots__/Avatar.test.tsx.snap @@ -38,7 +38,7 @@ exports[`Story Snapshots: AvatarBase64 should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "data:image/png;base64,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", }, @@ -95,7 +95,7 @@ exports[`Story Snapshots: AvatarExternalProviderUrl should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://hips.hearstapps.com/hmg-prod/images/dog-puppy-on-garden-royalty-free-image-1586966191.jpg?crop=0.752xw:1.00xh;0.175xw,0&resize=1200:*&format=png&size=112", }, @@ -152,7 +152,7 @@ exports[`Story Snapshots: AvatarPath should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=112", }, @@ -209,7 +209,7 @@ exports[`Story Snapshots: AvatarRoomId should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/room/devWBbYr7inwupPqK?format=png&size=112", }, @@ -266,7 +266,7 @@ exports[`Story Snapshots: AvatarText should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, @@ -323,7 +323,7 @@ exports[`Story Snapshots: AvatarUrl should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg", }, @@ -380,7 +380,7 @@ exports[`Story Snapshots: Channel should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@general?format=png&size=112", }, @@ -437,7 +437,7 @@ exports[`Story Snapshots: Children should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, @@ -533,7 +533,7 @@ exports[`Story Snapshots: CustomBorderRadius should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, @@ -592,7 +592,7 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, @@ -649,7 +649,7 @@ exports[`Story Snapshots: Direct should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=112", }, @@ -771,7 +771,7 @@ exports[`Story Snapshots: RoomAvatarExternalProviderUrl should match snapshot 1` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://cdn.pensador.com/img/authors/ho/me/homer-simpson-l.jpg?format=png&size=112", }, @@ -828,7 +828,7 @@ exports[`Story Snapshots: Static should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://user-images.githubusercontent.com/29778115/89444446-14738480-d728-11ea-9412-75fd978d95fb.jpg", }, @@ -965,7 +965,7 @@ exports[`Story Snapshots: Touchable should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Avatar?format=png&size=112", }, @@ -1025,7 +1025,7 @@ exports[`Story Snapshots: WithETag should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=112&etag=5ag8KffJcZj9m5rCv", }, @@ -1082,7 +1082,7 @@ exports[`Story Snapshots: WithoutETag should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/djorkaeff.alexandre?format=png&size=112", }, @@ -1139,7 +1139,7 @@ exports[`Story Snapshots: WrongServer should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://google.com/avatar/Avatar?format=png&size=112", }, diff --git a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap index 9073ab74019..ef93cfb3e89 100644 --- a/app/containers/Chip/__snapshots__/Chip.test.tsx.snap +++ b/app/containers/Chip/__snapshots__/Chip.test.tsx.snap @@ -209,7 +209,7 @@ exports[`Story Snapshots: ChipText should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, @@ -394,7 +394,7 @@ exports[`Story Snapshots: ChipWithShortText should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, @@ -809,7 +809,7 @@ exports[`Story Snapshots: ChipWithoutIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=56", }, diff --git a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap index 376f230643c..0e4ed472dce 100644 --- a/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap +++ b/app/containers/DirectoryItem/__snapshots__/DirectoryItem.test.tsx.snap @@ -153,7 +153,7 @@ exports[`Story Snapshots: CustomStyle should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@S?format=png&size=60", }, @@ -433,7 +433,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@G?format=png&size=60", }, @@ -713,7 +713,7 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/A?format=png&size=60", }, @@ -962,7 +962,7 @@ exports[`Story Snapshots: LongRoomName should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@L?format=png&size=60", }, @@ -1452,7 +1452,7 @@ exports[`Story Snapshots: TeamMain should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@E?format=png&size=60", }, @@ -1732,7 +1732,7 @@ exports[`Story Snapshots: WithRightLabel should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@G?format=png&size=60", }, @@ -2031,7 +2031,7 @@ exports[`Story Snapshots: WithoutDescription should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@N?format=png&size=60", }, diff --git a/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap b/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap index 299c643eae3..868a63c1120 100644 --- a/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap +++ b/app/containers/InAppNotification/__snapshots__/NotifierComponent.test.tsx.snap @@ -158,7 +158,7 @@ exports[`Story Snapshots: ChannelMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/room/rid-1?format=png&size=96", }, @@ -504,7 +504,7 @@ exports[`Story Snapshots: DirectMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=96", }, @@ -850,7 +850,7 @@ exports[`Story Snapshots: WithBlackTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/room/rid-1?format=png&size=96", }, @@ -1196,7 +1196,7 @@ exports[`Story Snapshots: WithDarkTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/room/rid-1?format=png&size=96", }, diff --git a/app/containers/NewMediaCall/__snapshots__/PeerItem.test.tsx.snap b/app/containers/NewMediaCall/__snapshots__/PeerItem.test.tsx.snap index 43d9d449fdf..1238850476e 100644 --- a/app/containers/NewMediaCall/__snapshots__/PeerItem.test.tsx.snap +++ b/app/containers/NewMediaCall/__snapshots__/PeerItem.test.tsx.snap @@ -120,7 +120,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/alice.johnson?format=png&size=56", }, @@ -450,7 +450,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/very.long.username?format=png&size=56", }, diff --git a/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap b/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap index edea1b8d2ff..3e242201d58 100644 --- a/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap +++ b/app/containers/NewMediaCall/__snapshots__/PeerList.test.tsx.snap @@ -315,7 +315,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/alice.johnson?format=png&size=56", }, @@ -520,7 +520,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/very.long.username?format=png&size=56", }, @@ -725,7 +725,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.smith?format=png&size=56", }, diff --git a/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap b/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap index 1ee4385af09..02088725e5a 100644 --- a/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap +++ b/app/containers/NewMediaCall/__snapshots__/SelectedPeer.test.tsx.snap @@ -101,7 +101,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/alice.johnson?format=png&size=56", }, @@ -509,7 +509,7 @@ exports[`Story Snapshots: All should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/this.is.a.very.long.username.for.storybook.preview?format=png&size=56", }, @@ -757,7 +757,7 @@ exports[`Story Snapshots: LongUsername should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/this.is.a.very.long.username.for.storybook.preview?format=png&size=56", }, @@ -1191,7 +1191,7 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/alice.johnson?format=png&size=56", }, diff --git a/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap b/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap index fecaa188218..ea6370e963d 100644 --- a/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap +++ b/app/containers/RoomItem/__snapshots__/RoomItem.test.tsx.snap @@ -489,7 +489,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -1131,7 +1131,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -1814,7 +1814,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -2497,7 +2497,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -3180,7 +3180,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -3863,7 +3863,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -4546,7 +4546,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -5229,7 +5229,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -5912,7 +5912,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -6595,7 +6595,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -7278,7 +7278,7 @@ exports[`Story Snapshots: Alerts should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -7965,7 +7965,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -8596,7 +8596,7 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -9279,7 +9279,7 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -9962,7 +9962,7 @@ exports[`Story Snapshots: CondensedRoomItem should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=72", }, @@ -14423,7 +14423,7 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -15078,7 +15078,7 @@ exports[`Story Snapshots: InvitedRoom should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -15738,7 +15738,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -16396,7 +16396,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -17054,7 +17054,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -17712,7 +17712,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -18370,7 +18370,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -19084,7 +19084,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -19798,7 +19798,7 @@ exports[`Story Snapshots: LastMessage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -20517,7 +20517,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -21144,7 +21144,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -21771,7 +21771,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -22398,7 +22398,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -23025,7 +23025,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -23652,7 +23652,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -24279,7 +24279,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -24906,7 +24906,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -25533,7 +25533,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -26160,7 +26160,7 @@ exports[`Story Snapshots: OmnichannelIcon should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -26792,7 +26792,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -27455,7 +27455,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -28150,7 +28150,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -28813,7 +28813,7 @@ exports[`Story Snapshots: Tag should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -29512,7 +29512,7 @@ exports[`Story Snapshots: Touch should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -30143,7 +30143,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -30778,7 +30778,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -31405,7 +31405,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -32032,7 +32032,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -32659,7 +32659,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -33286,7 +33286,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, @@ -33913,7 +33913,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -34540,7 +34540,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -35167,7 +35167,7 @@ exports[`Story Snapshots: Type should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/@rocket.cat?format=png&size=96", }, @@ -35799,7 +35799,7 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=96", }, @@ -36434,7 +36434,7 @@ exports[`Story Snapshots: User should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=96", }, diff --git a/app/containers/message/__snapshots__/Message.test.tsx.snap b/app/containers/message/__snapshots__/Message.test.tsx.snap index fd100c8e6f4..a7370f9ea72 100644 --- a/app/containers/message/__snapshots__/Message.test.tsx.snap +++ b/app/containers/message/__snapshots__/Message.test.tsx.snap @@ -244,7 +244,7 @@ exports[`Story Snapshots: Archived should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -706,7 +706,7 @@ exports[`Story Snapshots: ArchivedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -1176,7 +1176,7 @@ exports[`Story Snapshots: AttachmentWithTextAndLink should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -1947,7 +1947,7 @@ exports[`Story Snapshots: AttachmentWithTextAndLinkLargeFont should match snapsh [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -2718,7 +2718,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/no.avatar?format=png&size=72", }, @@ -3175,7 +3175,7 @@ exports[`Story Snapshots: Avatar should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -5024,7 +5024,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -5481,7 +5481,7 @@ exports[`Story Snapshots: Basic should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -5951,7 +5951,7 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -6408,7 +6408,7 @@ exports[`Story Snapshots: BasicLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -6878,7 +6878,7 @@ exports[`Story Snapshots: BlockQuote should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -7368,7 +7368,7 @@ Testing block quote" [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -7917,7 +7917,7 @@ exports[`Story Snapshots: BlockQuoteLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -8407,7 +8407,7 @@ Testing block quote" [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -8956,7 +8956,7 @@ exports[`Story Snapshots: Broadcast should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -9546,7 +9546,7 @@ exports[`Story Snapshots: BroadcastLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -10136,7 +10136,7 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -10745,7 +10745,7 @@ exports[`Story Snapshots: CollapsedAttachments should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -11569,7 +11569,7 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -12178,7 +12178,7 @@ exports[`Story Snapshots: CollapsedAttachmentsLargeFont should match snapshot 1` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -13002,7 +13002,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -13613,7 +13613,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithText should match snapshot 1` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -14578,7 +14578,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -15189,7 +15189,7 @@ exports[`Story Snapshots: CollapsibleAttachmentWithTextLargeFont should match sn [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -16154,7 +16154,7 @@ exports[`Story Snapshots: ColoredAttachments should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -17386,7 +17386,7 @@ exports[`Story Snapshots: ColoredAttachmentsLargeFont should match snapshot 1`] [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -18618,7 +18618,7 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -19430,7 +19430,7 @@ exports[`Story Snapshots: CustomFields should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -20513,7 +20513,7 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -21325,7 +21325,7 @@ exports[`Story Snapshots: CustomFieldsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -22408,7 +22408,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -22927,7 +22927,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -23652,7 +23652,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -24166,7 +24166,7 @@ exports[`Story Snapshots: DateAndUnreadSeparators should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -24636,7 +24636,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=93.60000000000001", }, @@ -25155,7 +25155,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -25880,7 +25880,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=93.60000000000001", }, @@ -26394,7 +26394,7 @@ exports[`Story Snapshots: DateAndUnreadSeparatorsLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -26864,7 +26864,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -27442,7 +27442,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -28022,7 +28022,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -28602,7 +28602,7 @@ exports[`Story Snapshots: Discussion should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -29195,7 +29195,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -29773,7 +29773,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -30353,7 +30353,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -30933,7 +30933,7 @@ exports[`Story Snapshots: DiscussionLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -31526,7 +31526,7 @@ exports[`Story Snapshots: Edited should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -32292,7 +32292,7 @@ exports[`Story Snapshots: EditedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -33058,7 +33058,7 @@ exports[`Story Snapshots: Editing should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -33528,7 +33528,7 @@ exports[`Story Snapshots: EditingLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -33998,7 +33998,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -34454,7 +34454,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -34891,7 +34891,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -35441,7 +35441,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -35903,7 +35903,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -36384,7 +36384,7 @@ exports[`Story Snapshots: Emojis should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -36911,7 +36911,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -37367,7 +37367,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -37804,7 +37804,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -38354,7 +38354,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -38816,7 +38816,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -39297,7 +39297,7 @@ exports[`Story Snapshots: EmojisLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -39824,7 +39824,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -40649,7 +40649,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -41734,7 +41734,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -42046,7 +42046,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -42580,7 +42580,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -43022,7 +43022,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -43618,7 +43618,7 @@ exports[`Story Snapshots: Encrypted should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -44516,7 +44516,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -45341,7 +45341,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -46426,7 +46426,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -46738,7 +46738,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -47272,7 +47272,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -47714,7 +47714,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -48310,7 +48310,7 @@ exports[`Story Snapshots: EncryptedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -49121,7 +49121,7 @@ exports[`Story Snapshots: Error should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -49777,7 +49777,7 @@ exports[`Story Snapshots: ErrorLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -50520,7 +50520,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -51116,7 +51116,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -51712,7 +51712,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -52308,7 +52308,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenames should match snapshot 1`] [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -53429,7 +53429,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -54025,7 +54025,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -54621,7 +54621,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -55217,7 +55217,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -56325,7 +56325,7 @@ exports[`Story Snapshots: FileAttachmentsWithFilenamesLargeFont should match sna [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -57089,7 +57089,7 @@ exports[`Story Snapshots: FullName should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -57559,7 +57559,7 @@ exports[`Story Snapshots: FullNameLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -58029,7 +58029,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -58486,7 +58486,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=72", }, @@ -59391,7 +59391,7 @@ exports[`Story Snapshots: GroupedMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -59861,7 +59861,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -60318,7 +60318,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=93.60000000000001", }, @@ -61223,7 +61223,7 @@ exports[`Story Snapshots: GroupedMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -61691,7 +61691,7 @@ exports[`Story Snapshots: Ignored should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -62018,7 +62018,7 @@ exports[`Story Snapshots: IgnoredLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -62347,7 +62347,7 @@ exports[`Story Snapshots: InlineKatex should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -62803,7 +62803,7 @@ exports[`Story Snapshots: InlineKatexLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -63259,7 +63259,7 @@ exports[`Story Snapshots: Katex should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -63683,7 +63683,7 @@ exports[`Story Snapshots: KatexArray should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -64107,7 +64107,7 @@ exports[`Story Snapshots: KatexArrayLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -64531,7 +64531,7 @@ exports[`Story Snapshots: KatexLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -64959,7 +64959,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -65596,7 +65596,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -66183,7 +66183,7 @@ exports[`Story Snapshots: Lists should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -66965,7 +66965,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -67602,7 +67602,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -68189,7 +68189,7 @@ exports[`Story Snapshots: ListsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -68967,7 +68967,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -69424,7 +69424,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -69918,7 +69918,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -70410,7 +70410,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -70852,7 +70852,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -71377,7 +71377,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -71865,7 +71865,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -72338,7 +72338,7 @@ exports[`Story Snapshots: LongNameUser should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -73798,7 +73798,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -74255,7 +74255,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -74749,7 +74749,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -75241,7 +75241,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -75683,7 +75683,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -76208,7 +76208,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -76696,7 +76696,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -77169,7 +77169,7 @@ exports[`Story Snapshots: LongNameUserLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=93.60000000000001", }, @@ -78629,7 +78629,7 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -79265,7 +79265,7 @@ exports[`Story Snapshots: Mentions should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -79896,7 +79896,7 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -80532,7 +80532,7 @@ exports[`Story Snapshots: MentionsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -81163,7 +81163,7 @@ exports[`Story Snapshots: Message should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -81573,7 +81573,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -82166,7 +82166,7 @@ exports[`Story Snapshots: MessageWithNestedReplyAndFile should match snapshot 1` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -82985,7 +82985,7 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -83728,7 +83728,7 @@ exports[`Story Snapshots: MessageWithReadReceipt should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -84484,7 +84484,7 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -85227,7 +85227,7 @@ exports[`Story Snapshots: MessageWithReadReceiptLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -85983,7 +85983,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -86612,7 +86612,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -87548,7 +87548,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -88354,7 +88354,7 @@ exports[`Story Snapshots: MessageWithReply should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -89234,7 +89234,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -89850,7 +89850,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -90543,7 +90543,7 @@ exports[`Story Snapshots: MessageWithReplyAndFile should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -91323,7 +91323,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -91939,7 +91939,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -92632,7 +92632,7 @@ exports[`Story Snapshots: MessageWithReplyAndFileLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -93412,7 +93412,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -94041,7 +94041,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -94714,7 +94714,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -95520,7 +95520,7 @@ exports[`Story Snapshots: MessageWithReplyLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -96401,7 +96401,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -97322,7 +97322,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -97757,7 +97757,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -98192,7 +98192,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -98627,7 +98627,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -99062,7 +99062,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -99497,7 +99497,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -99932,7 +99932,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -100367,7 +100367,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -100802,7 +100802,7 @@ exports[`Story Snapshots: MessageWithThread should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -101136,7 +101136,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -102057,7 +102057,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -102492,7 +102492,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -102927,7 +102927,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -103362,7 +103362,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -103797,7 +103797,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -104232,7 +104232,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -104667,7 +104667,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -105102,7 +105102,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -105537,7 +105537,7 @@ exports[`Story Snapshots: MessageWithThreadLargeFont should match snapshot 1`] = [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -105870,7 +105870,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, @@ -106241,7 +106241,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, @@ -106965,7 +106965,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, @@ -107336,7 +107336,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, @@ -108060,7 +108060,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -108431,7 +108431,7 @@ exports[`Story Snapshots: Names should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -109168,7 +109168,7 @@ exports[`Story Snapshots: Pinned should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -109922,7 +109922,7 @@ exports[`Story Snapshots: PinnedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -110676,7 +110676,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -111677,7 +111677,7 @@ exports[`Story Snapshots: Reactions should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -113147,7 +113147,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -114148,7 +114148,7 @@ exports[`Story Snapshots: ReactionsLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -115619,7 +115619,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -116423,7 +116423,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -116741,7 +116741,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -117059,7 +117059,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButton should m [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -117393,7 +117393,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -118197,7 +118197,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -118515,7 +118515,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -118833,7 +118833,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadButtonLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -119281,7 +119281,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -119599,7 +119599,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -119917,7 +119917,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -120235,7 +120235,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReply should ma [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -120683,7 +120683,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -121001,7 +121001,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -121319,7 +121319,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -121637,7 +121637,7 @@ exports[`Story Snapshots: SequentialThreadMessagesFollowingThreadReplyLargeFont [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -121970,7 +121970,7 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -122501,7 +122501,7 @@ exports[`Story Snapshots: ShowButtonAsAttachment should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -123210,7 +123210,7 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -123741,7 +123741,7 @@ exports[`Story Snapshots: ShowButtonAsAttachmentLargeFont should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -124450,7 +124450,7 @@ exports[`Story Snapshots: StaticAvatar should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg", }, @@ -124920,7 +124920,7 @@ exports[`Story Snapshots: StaticAvatarLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg", }, @@ -125303,7 +125303,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -125546,7 +125546,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -125789,7 +125789,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -126035,7 +126035,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -126278,7 +126278,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -126521,7 +126521,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -126764,7 +126764,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -127007,7 +127007,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -127250,7 +127250,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -127493,7 +127493,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -127736,7 +127736,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -127979,7 +127979,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -128222,7 +128222,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -128465,7 +128465,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -128708,7 +128708,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -128951,7 +128951,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -129194,7 +129194,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -129437,7 +129437,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -129680,7 +129680,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -129923,7 +129923,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -130166,7 +130166,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -130409,7 +130409,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -130652,7 +130652,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -130895,7 +130895,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -131138,7 +131138,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -131381,7 +131381,7 @@ exports[`Story Snapshots: SystemMessages should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=40", }, @@ -131615,7 +131615,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -131858,7 +131858,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -132101,7 +132101,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -132347,7 +132347,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -132590,7 +132590,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -132833,7 +132833,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -133076,7 +133076,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -133319,7 +133319,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -133562,7 +133562,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -133805,7 +133805,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -134048,7 +134048,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -134291,7 +134291,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -134534,7 +134534,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -134777,7 +134777,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -135020,7 +135020,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -135263,7 +135263,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -135506,7 +135506,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -135749,7 +135749,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -135992,7 +135992,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -136235,7 +136235,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -136478,7 +136478,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -136721,7 +136721,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -136964,7 +136964,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -137207,7 +137207,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -137450,7 +137450,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -137693,7 +137693,7 @@ exports[`Story Snapshots: SystemMessagesLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=52", }, @@ -138006,7 +138006,7 @@ exports[`Story Snapshots: Temp should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -138473,7 +138473,7 @@ exports[`Story Snapshots: TempLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -138948,7 +138948,7 @@ exports[`Story Snapshots: ThumbnailFromServer should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -139629,7 +139629,7 @@ exports[`Story Snapshots: ThumbnailFromServerLargeFont should match snapshot 1`] [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -140310,7 +140310,7 @@ exports[`Story Snapshots: TimeFormat should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -140780,7 +140780,7 @@ exports[`Story Snapshots: TimeFormatLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -141251,7 +141251,7 @@ exports[`Story Snapshots: Translated should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -141757,7 +141757,7 @@ exports[`Story Snapshots: TranslatedLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -142262,7 +142262,7 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdown should match snapshot [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -143448,7 +143448,7 @@ exports[`Story Snapshots: TwoShortCustomFieldsWithMarkdownLargeFont should match [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -144634,7 +144634,7 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -145228,7 +145228,7 @@ exports[`Story Snapshots: URL should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -146103,7 +146103,7 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -146599,7 +146599,7 @@ exports[`Story Snapshots: URLImagePreview should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -147051,7 +147051,7 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -147547,7 +147547,7 @@ exports[`Story Snapshots: URLImagePreviewLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -147999,7 +147999,7 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -148593,7 +148593,7 @@ exports[`Story Snapshots: URLLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -149468,7 +149468,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, @@ -149839,7 +149839,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/jd?format=png&size=72", }, @@ -150563,7 +150563,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, @@ -150934,7 +150934,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=72", }, @@ -151658,7 +151658,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -152029,7 +152029,7 @@ exports[`Story Snapshots: Usernames should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/johndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusernamejohndoeverylongusername?format=png&size=72", }, @@ -152766,7 +152766,7 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -153242,7 +153242,7 @@ exports[`Story Snapshots: WithAlias should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=72", }, @@ -153731,7 +153731,7 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -154207,7 +154207,7 @@ exports[`Story Snapshots: WithAliasLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.?format=png&size=93.60000000000001", }, @@ -154696,7 +154696,7 @@ exports[`Story Snapshots: WithAudio should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -157591,7 +157591,7 @@ exports[`Story Snapshots: WithAudioLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -160475,7 +160475,7 @@ exports[`Story Snapshots: WithFile should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -161793,7 +161793,7 @@ exports[`Story Snapshots: WithFileLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -162869,7 +162869,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -163543,7 +163543,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -164705,7 +164705,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -165492,7 +165492,7 @@ exports[`Story Snapshots: WithImage should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -166166,7 +166166,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -167193,7 +167193,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -167980,7 +167980,7 @@ exports[`Story Snapshots: WithImageLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -168654,7 +168654,7 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -169641,7 +169641,7 @@ exports[`Story Snapshots: WithVideo should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -170373,7 +170373,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -171005,7 +171005,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, @@ -171538,7 +171538,7 @@ exports[`Story Snapshots: WithVideoLargeFont should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=93.60000000000001", }, diff --git a/app/views/CallView/__snapshots__/index.test.tsx.snap b/app/views/CallView/__snapshots__/index.test.tsx.snap index 0b830f93ff6..1f01a1906af 100644 --- a/app/views/CallView/__snapshots__/index.test.tsx.snap +++ b/app/views/CallView/__snapshots__/index.test.tsx.snap @@ -126,7 +126,7 @@ exports[`Story Snapshots: ConnectedCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -1000,7 +1000,7 @@ exports[`Story Snapshots: ConnectingCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -1880,7 +1880,7 @@ exports[`Story Snapshots: MutedAndOnHold should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -2754,7 +2754,7 @@ exports[`Story Snapshots: MutedCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -3628,7 +3628,7 @@ exports[`Story Snapshots: OnHoldCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -4502,7 +4502,7 @@ exports[`Story Snapshots: SpeakerOn should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -5359,7 +5359,7 @@ exports[`Story Snapshots: TabletConnectedCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -6205,7 +6205,7 @@ exports[`Story Snapshots: TabletConnectingCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -7057,7 +7057,7 @@ exports[`Story Snapshots: TabletMutedAndOnHold should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -7903,7 +7903,7 @@ exports[`Story Snapshots: TabletMutedCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -8749,7 +8749,7 @@ exports[`Story Snapshots: TabletOnHoldCall should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -9595,7 +9595,7 @@ exports[`Story Snapshots: TabletSpeakerOn should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -10441,7 +10441,7 @@ exports[`Story Snapshots: TabletWithDialpad should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -11304,7 +11304,7 @@ exports[`Story Snapshots: WithDialpad should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, diff --git a/app/views/CallView/components/__snapshots__/CallerInfo.test.tsx.snap b/app/views/CallView/components/__snapshots__/CallerInfo.test.tsx.snap index 37430109eed..49eb2a245b1 100644 --- a/app/views/CallView/components/__snapshots__/CallerInfo.test.tsx.snap +++ b/app/views/CallView/components/__snapshots__/CallerInfo.test.tsx.snap @@ -98,7 +98,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/bob.burnquist?format=png&size=240", }, @@ -253,7 +253,7 @@ exports[`Story Snapshots: UsernameOnly should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=240", }, diff --git a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap index 792d2047ad4..25a2a278ff3 100644 --- a/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap +++ b/app/views/DiscussionsView/__snapshots__/Item.test.tsx.snap @@ -143,7 +143,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -526,7 +526,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -909,7 +909,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -1294,7 +1294,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -1677,7 +1677,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -2080,7 +2080,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -2450,7 +2450,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -2820,7 +2820,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, diff --git a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap index 7f6dd412fe7..66eb252d698 100644 --- a/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap +++ b/app/views/NewMessageView/__snapshots__/Item.test.tsx.snap @@ -99,7 +99,7 @@ exports[`NewMessageView Item should match snapshot when hasMediaCallPermission i [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=60", }, @@ -260,7 +260,7 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=60", }, @@ -483,7 +483,7 @@ exports[`Story Snapshots: LongName should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/long.name?format=png&size=60", }, @@ -706,7 +706,7 @@ exports[`Story Snapshots: NoVoiceCallPermission should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=60", }, diff --git a/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap b/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap index 0a5abbdf57c..f6539287d39 100644 --- a/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap +++ b/app/views/RoomView/LoadMore/__snapshots__/LoadMore.test.tsx.snap @@ -772,7 +772,7 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -1889,7 +1889,7 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -2794,7 +2794,7 @@ exports[`Story Snapshots: BlackTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -3370,7 +3370,7 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -4487,7 +4487,7 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -5392,7 +5392,7 @@ exports[`Story Snapshots: DarkTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -5968,7 +5968,7 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -7085,7 +7085,7 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, @@ -7990,7 +7990,7 @@ exports[`Story Snapshots: LightTheme should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/diego.mello?format=png&size=72", }, diff --git a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap index 389f9a62f8d..f7ecdacf765 100644 --- a/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap +++ b/app/views/ThreadMessagesView/__snapshots__/Item.test.tsx.snap @@ -143,7 +143,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -667,7 +667,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -1191,7 +1191,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -1702,7 +1702,7 @@ exports[`Story Snapshots: Badge should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -2246,7 +2246,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -2754,7 +2754,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -3262,7 +3262,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -3770,7 +3770,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -4278,7 +4278,7 @@ exports[`Story Snapshots: Content should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -4806,7 +4806,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -5317,7 +5317,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, @@ -5828,7 +5828,7 @@ exports[`Story Snapshots: Themes should match snapshot 1`] = ` [ { "headers": { - "User-Agent": "RC Mobile; ios unknown; vunknown (unknown)", + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", }, "uri": "https://open.rocket.chat/avatar/rocket.cat?format=png&size=72", }, From 978429ff9289b2199b1c71591ee8cc5a6b65c553 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 02:44:51 +0530 Subject: [PATCH 111/203] remove tserror --- app/ee/omnichannel/lib/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index be40def8126..4fc1a82dcb2 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -27,7 +27,6 @@ export const getInquiriesQueued = (serverVersion: string) => { // this inquiry is added to the db by the subscriptions stream // and will be removed by the queue stream // RC 2.4.0 -// @ts-ignore — endpoint '/v1/livechat/inquiries.take' not in SDK's typed path dictionary for older servers export const takeInquiry = (inquiryId: string, serverVersion: string) => { if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '7.11.0')) { return sdk.post('/v1/livechat/inquiries.take', { inquiryId }); From ef96dee055e5025f0207e036f5d4de82a82fe8df Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 02:53:08 +0530 Subject: [PATCH 112/203] Move back basicauthkey to fetch --- app/lib/methods/helpers/defaultHeaders.ts | 2 -- app/lib/methods/helpers/fetch.ts | 9 ++++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/lib/methods/helpers/defaultHeaders.ts b/app/lib/methods/helpers/defaultHeaders.ts index 7b199b25411..840928d77f4 100644 --- a/app/lib/methods/helpers/defaultHeaders.ts +++ b/app/lib/methods/helpers/defaultHeaders.ts @@ -15,5 +15,3 @@ export const headers: CustomHeaders = { Platform.OS } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` }; - -export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index ec803409848..a9a5b2bac8d 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -1,10 +1,8 @@ import sdk from '../../services/sdk'; -import { type CustomHeaders, headers, BASIC_AUTH_KEY } from './defaultHeaders'; +import { type CustomHeaders, headers } from './defaultHeaders'; export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; -export { headers, BASIC_AUTH_KEY }; - interface IOptions { headers?: CustomHeaders; signal?: AbortSignal; @@ -12,11 +10,16 @@ interface IOptions { body?: any; } +export { headers }; + export const setBasicAuth = (basicAuth: string | null): void => { sdk.setBasicAuth(basicAuth); }; +export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; + export default (url: string, options: IOptions = {}): Promise => { const customOptions = { ...options, headers: { ...sdk.getHeaders(), ...(options.headers || {}) } }; return fetch(url, customOptions); }; + From cf07ca2632290301d29d187973c6996f7aa0b60a Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 02:54:40 +0530 Subject: [PATCH 113/203] Rename custom header to default header, matching file naming --- app/lib/methods/helpers/defaultHeaders.ts | 4 ++-- app/lib/methods/helpers/fetch.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lib/methods/helpers/defaultHeaders.ts b/app/lib/methods/helpers/defaultHeaders.ts index 840928d77f4..28865a08551 100644 --- a/app/lib/methods/helpers/defaultHeaders.ts +++ b/app/lib/methods/helpers/defaultHeaders.ts @@ -1,7 +1,7 @@ import { Platform } from 'react-native'; import DeviceInfo from 'react-native-device-info'; -export interface CustomHeaders { +export interface DefaultHeaders { 'User-Agent'?: string; Authorization?: string; 'Content-Type'?: string; @@ -10,7 +10,7 @@ export interface CustomHeaders { } // this form is required by Rocket.Chat's parser in "app/statistics/server/lib/UAParserCustom.js" -export const headers: CustomHeaders = { +export const headers: DefaultHeaders = { 'User-Agent': `RC Mobile; ${ Platform.OS } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index a9a5b2bac8d..2ad42d6bfff 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -1,10 +1,10 @@ import sdk from '../../services/sdk'; -import { type CustomHeaders, headers } from './defaultHeaders'; +import { type DefaultHeaders, headers } from './defaultHeaders'; export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; interface IOptions { - headers?: CustomHeaders; + headers?: DefaultHeaders; signal?: AbortSignal; method?: TMethods; body?: any; From 1d7ce92edeb821ed3cf1898d0f375ab1c5b73ab6 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Mon, 8 Jun 2026 21:26:16 +0000 Subject: [PATCH 114/203] chore: format code and fix lint issues --- app/lib/methods/helpers/fetch.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index 2ad42d6bfff..a27fc5a96e7 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -22,4 +22,3 @@ export default (url: string, options: IOptions = {}): Promise => { const customOptions = { ...options, headers: { ...sdk.getHeaders(), ...(options.headers || {}) } }; return fetch(url, customOptions); }; - From f983538bd6d2000b527436689cbb32966dc141b8 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 03:21:25 +0530 Subject: [PATCH 115/203] add typed endpoint for media-calls.stateSignals --- app/definitions/rest/v1/index.ts | 2 ++ app/definitions/rest/v1/mediaCalls.ts | 7 +++++++ app/lib/services/restApi.test.ts | 2 +- app/lib/services/restApi.ts | 8 ++------ app/lib/services/voip/MediaSessionInstance.ts | 7 +++++-- 5 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 app/definitions/rest/v1/mediaCalls.ts diff --git a/app/definitions/rest/v1/index.ts b/app/definitions/rest/v1/index.ts index 1cc1b9e2fef..79ba77385a1 100644 --- a/app/definitions/rest/v1/index.ts +++ b/app/definitions/rest/v1/index.ts @@ -21,6 +21,7 @@ import { type CommandsEndpoints } from './commands'; import { type PushEndpoints } from './push'; import { type DirectoryEndpoint } from './directory'; import { type AutoTranslateEndpoints } from './autotranslate'; +import { type MediaCallsEndpoints } from './mediaCalls'; import { type ModerationEndpoints } from './moderation'; export type Endpoints = AuthEndpoints & @@ -32,6 +33,7 @@ export type Endpoints = AuthEndpoints & GroupsEndpoints & ImEndpoints & InvitesEndpoints & + MediaCallsEndpoints & OmnichannelEndpoints & PermissionsEndpoints & RolesEndpoints & diff --git a/app/definitions/rest/v1/mediaCalls.ts b/app/definitions/rest/v1/mediaCalls.ts new file mode 100644 index 00000000000..586187dece7 --- /dev/null +++ b/app/definitions/rest/v1/mediaCalls.ts @@ -0,0 +1,7 @@ +import type { ServerMediaSignal } from '@rocket.chat/media-signaling'; + +export type MediaCallsEndpoints = { + 'media-calls.stateSignals': { + GET: (params: { contractId: string }) => { signals: ServerMediaSignal[]; success: boolean }; + }; +}; diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index 8608ae9dfea..3032fc1b749 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -83,7 +83,7 @@ describe('mediaCallsStateSignals', () => { const result = await mediaCallsStateSignals('device-contract-id-123'); - expect(mockSdkGet).toHaveBeenCalledWith('media-calls.stateSignals', { contractId: 'device-contract-id-123' }); + expect(mockSdkGet).toHaveBeenCalledWith('/v1/media-calls.stateSignals', { contractId: 'device-contract-id-123' }); expect(result).toEqual({ signals: [], success: true }); }); diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index af776e17e84..c4940267435 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1,5 +1,4 @@ import { getUniqueId } from 'react-native-device-info'; -import type { ServerMediaSignal } from '@rocket.chat/media-signaling'; import { type IAvatarSuggestion, @@ -1281,12 +1280,9 @@ export const getUsersRoles = async (): Promise => { export const getSupportedVersionsCloud = (uniqueId?: string, domain?: string) => fetch(`https://releases.rocket.chat/v2/server/supportedVersions?uniqueId=${uniqueId}&domain=${domain}&source=mobile`); -export const mediaCallsStateSignals = async (contractId: string): Promise<{ signals: ServerMediaSignal[]; success: boolean }> => { +export const mediaCallsStateSignals = async (contractId: string) => { try { - const result = await ( - sdk.get as unknown as (path: string, params?: object) => Promise<{ signals: ServerMediaSignal[]; success: boolean }> - )('media-calls.stateSignals', { contractId }); - return result; + return await sdk.get('/v1/media-calls.stateSignals', { contractId }); } catch { return { signals: [], success: false }; } diff --git a/app/lib/services/voip/MediaSessionInstance.ts b/app/lib/services/voip/MediaSessionInstance.ts index 210598fab50..d690a14595a 100644 --- a/app/lib/services/voip/MediaSessionInstance.ts +++ b/app/lib/services/voip/MediaSessionInstance.ts @@ -72,8 +72,11 @@ class MediaSessionInstance { return; } try { - const { signals } = await mediaCallsStateSignals(getUniqueIdSync()); - for (const signal of signals) { + const res = await mediaCallsStateSignals(getUniqueIdSync()); + if (!res.success) { + throw new Error('Failed to fetch media calls state signals'); + } + for (const signal of res.signals) { // Sequential replay: signals depend on prior state mutations. // eslint-disable-next-line no-await-in-loop await this.instance.processSignal(signal); From 0813eefb45dc90e3df18ac490a472bb877362fbc Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 03:42:49 +0530 Subject: [PATCH 116/203] e2e test revert --- .../tests/room/room-last-message-thread-50-plus.yaml | 10 ++++------ .maestro/tests/room/room.yaml | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.maestro/tests/room/room-last-message-thread-50-plus.yaml b/.maestro/tests/room/room-last-message-thread-50-plus.yaml index 3c79beaf672..4a212d38fb2 100644 --- a/.maestro/tests/room/room-last-message-thread-50-plus.yaml +++ b/.maestro/tests/room/room-last-message-thread-50-plus.yaml @@ -13,12 +13,10 @@ tags: PASSWORD: ${output.account.adminPassword} # should open a room where the last message is a thread containing more than 50 messages. -- tapOn: - id: 'rooms-list-view-item-maestro_test_load_threads' -- extendedWaitUntil: - visible: - id: 'room-view-title-maestro_test_load_threads' - timeout: 60000 +- runFlow: + file: '../../helpers/navigate-to-room.yaml' + env: + ROOM: 'maestro_test_load_threads' - extendedWaitUntil: visible: text: '.*message 50.*' diff --git a/.maestro/tests/room/room.yaml b/.maestro/tests/room/room.yaml index 20d811af130..40732df488b 100644 --- a/.maestro/tests/room/room.yaml +++ b/.maestro/tests/room/room.yaml @@ -405,7 +405,7 @@ tags: message: 'message to delete' - extendedWaitUntil: visible: - id: 'message-content-message to delete' + text: '.*message to delete.*' timeout: 60000 - longPressOn: id: 'message-content-message to delete' @@ -430,7 +430,7 @@ tags: text: 'Delete' - extendedWaitUntil: notVisible: - id: 'message-content-message to delete' + text: '.*message to delete.*' timeout: 60000 - runFlow: '../../helpers/go-back.yaml' From dd57f5f1014126093c63c5aa858af93c8e18b459 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 03:43:50 +0530 Subject: [PATCH 117/203] sdk changes --- app/lib/services/sdk.ts | 79 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 6f6557ed39d..d560c892c85 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -6,7 +6,8 @@ import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; import { compareServerVersion, random } from '../methods/helpers'; import UserPreferences from '../methods/userPreferences'; -import { BASIC_AUTH_KEY, headers as defaultHeaders } from '../methods/helpers/defaultHeaders'; +import { headers as defaultHeaders } from '../methods/helpers/defaultHeaders'; +import { BASIC_AUTH_KEY } from '../methods/helpers/fetch'; import { type Serialized, type MatchPathPattern, @@ -152,7 +153,7 @@ class Sdk { } } - post>( + async post>( endpoint: TPath, params: void extends OperationParams<'POST', MatchPathPattern> ? void @@ -163,48 +164,46 @@ class Sdk { ? void : Serialized>> ): Promise>> { - return new Promise(async (resolve, reject) => { - const isMethodCall = !!endpoint?.includes('/v1/method.call'); - try { - const sdk = this.ensureInitialized(); + const isMethodCall = !!endpoint?.includes('/v1/method.call'); + try { + const sdk = this.ensureInitialized(); + // @ts-ignore + const result = await sdk.rest.post(endpoint, params, { headers: this.headers }); + + /** + * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, + * responses have a different object structure + */ + if (isMethodCall) { // @ts-ignore - const result = await sdk.rest.post(endpoint, params, { headers: this.headers }); - - /** - * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, - * responses have a different object structure - */ - if (isMethodCall) { - // @ts-ignore - const response = JSON.parse(result.message); - if (response?.error) { - throw response.error; - } - return resolve(response.result); + const response = JSON.parse(result.message); + if (response?.error) { + throw response.error; } - return resolve(result); - } catch (e: any) { - // @rocket.chat/api-client rejects with the raw fetch Response on REST errors. - // Normalize to { status, data } so callers can read e.data.* - const normalized = !isMethodCall && e instanceof Response ? await normalizeResponseError(e) : e; - const errorType = isMethodCall ? normalized?.error : normalized?.data?.errorType; - const totpInvalid = 'totp-invalid'; - const totpRequired = 'totp-required'; - if ([totpInvalid, totpRequired].includes(errorType)) { - const { details } = isMethodCall ? normalized : normalized?.data; - try { - await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); - return resolve(this.post(endpoint, params)); - } catch { - // twoFactor was canceled - return resolve({} as any); - } - } else { - reject(normalized); + return response.result; + } + return result; + } catch (e: any) { + // @rocket.chat/api-client rejects with the raw fetch Response on REST errors. + // Normalize to { status, data } so callers can read e.data.* + const normalized = !isMethodCall && e instanceof Response ? await normalizeResponseError(e) : e; + const errorType = isMethodCall ? normalized?.error : normalized?.data?.errorType; + const totpInvalid = 'totp-invalid'; + const totpRequired = 'totp-required'; + if ([totpInvalid, totpRequired].includes(errorType)) { + const { details } = isMethodCall ? normalized : normalized?.data; + try { + await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); + return this.post(endpoint, params); + } catch { + // twoFactor was canceled + return {} as ResultFor<'POST', MatchPathPattern>; } + } else { + throw normalized; } - }); - } + } + }; async delete>( endpoint: TPath, From 1cd9614b58436e02d65a0b5fe06b3b7adfb7e230 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 03:51:44 +0530 Subject: [PATCH 118/203] added restApi mock in voip --- .../NewMediaCall/VoipCallLifecycle.integration.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/containers/NewMediaCall/VoipCallLifecycle.integration.test.tsx b/app/containers/NewMediaCall/VoipCallLifecycle.integration.test.tsx index a56c90e0a50..b9ed3848e65 100644 --- a/app/containers/NewMediaCall/VoipCallLifecycle.integration.test.tsx +++ b/app/containers/NewMediaCall/VoipCallLifecycle.integration.test.tsx @@ -161,6 +161,10 @@ jest.mock('../../lib/services/voip/navigateToCallRoom', () => ({ jest.mock('../../lib/services/voip/playCallEndedSound', () => ({ playCallEndedSound: jest.fn() })); +// mediaCallsStateSignals → restApi → API call. Mock to prevent network requests in tests. +jest.mock('../../lib/services/restApi', () => ({ + mediaCallsStateSignals: () => Promise.resolve({ signals: [], success: true }) +})); const mockHideActionSheet = jest.fn(); jest.mock('../ActionSheet', () => ({ From 76c1ad0ad19e5e814b8f770df39a77f1d2d9f09b Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Mon, 8 Jun 2026 22:23:27 +0000 Subject: [PATCH 119/203] chore: format code and fix lint issues --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index d560c892c85..f5077c6e16d 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -203,7 +203,7 @@ class Sdk { throw normalized; } } - }; + } async delete>( endpoint: TPath, From 8f40f66e779cf57be8c13e004dbb108ad71bd034 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 04:01:33 +0530 Subject: [PATCH 120/203] revert the changes of useAutoComplete --- app/containers/MessageComposer/hooks/useAutocomplete.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/containers/MessageComposer/hooks/useAutocomplete.ts b/app/containers/MessageComposer/hooks/useAutocomplete.ts index d8638b3f9f9..906fe8d631e 100644 --- a/app/containers/MessageComposer/hooks/useAutocomplete.ts +++ b/app/containers/MessageComposer/hooks/useAutocomplete.ts @@ -175,9 +175,6 @@ export const useAutocomplete = ({ updateAutocompleteVisible(true); accessibilityFocusOnInput(); } - } else { - setItems([]); - updateAutocompleteVisible(false); } } if (type === '!') { @@ -206,9 +203,6 @@ export const useAutocomplete = ({ updateAutocompleteVisible(true); accessibilityFocusOnInput(); } - } else { - setItems([]); - updateAutocompleteVisible(false); } } } catch (e) { From 66c648e5505ee5039248acb31f426e31003ccadc Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 04:35:21 +0530 Subject: [PATCH 121/203] centralize auth headers in sdk --- .../MessageComposer/MessageComposer.test.tsx | 3 +- .../components/RecordAudio/RecordAudio.tsx | 4 +- app/lib/methods/sendFileMessage/index.ts | 7 +- .../sendFileMessage/sendAttachments.ts | 4 +- .../sendFileMessage/sendFileMessage.test.ts | 65 ++++--------------- .../sendFileMessage/sendFileMessage.ts | 10 +-- .../sendFileMessage/sendFileMessageV2.test.ts | 17 ++--- .../sendFileMessage/sendFileMessageV2.ts | 8 +-- app/lib/services/sdk.test.ts | 33 +++++++++- app/lib/services/sdk.ts | 10 +++ app/views/RoomView/UploadProgress.tsx | 4 +- 11 files changed, 72 insertions(+), 93 deletions(-) diff --git a/app/containers/MessageComposer/MessageComposer.test.tsx b/app/containers/MessageComposer/MessageComposer.test.tsx index 8a966dead24..3768b9661a6 100644 --- a/app/containers/MessageComposer/MessageComposer.test.tsx +++ b/app/containers/MessageComposer/MessageComposer.test.tsx @@ -789,8 +789,7 @@ describe('MessageComposer', () => { type: 'image/png' }), undefined, - 'https://open.rocket.chat', - expect.objectContaining({ id: 'abc' }) + 'https://open.rocket.chat' ) ); expect(onSendMessage).not.toHaveBeenCalled(); diff --git a/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx b/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx index 78197ba218a..98fd2486001 100644 --- a/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx +++ b/app/containers/MessageComposer/components/RecordAudio/RecordAudio.tsx @@ -3,7 +3,6 @@ import React, { type ReactElement, useEffect, useRef } from 'react'; import { Audio } from 'expo-av'; import { getInfoAsync } from 'expo-file-system/legacy'; import { useKeepAwake } from 'expo-keep-awake'; -import { shallowEqual } from 'react-redux'; import { useTheme } from '../../../../theme'; import { BaseButton } from '../Buttons'; @@ -32,7 +31,6 @@ export const RecordAudio = (): ReactElement | null => { const { setRecordingAudio } = useMessageComposerApi(); const { rid, tmid } = useRoomContext(); const server = useAppSelector(state => state.server.server); - const user = useAppSelector(state => ({ id: state.login.user.id, token: state.login.user.token }), shallowEqual); const permissionToUpload = useCanUploadFile(rid); useKeepAwake(); @@ -108,7 +106,7 @@ export const RecordAudio = (): ReactElement | null => { if (fileInfo) { if (permissionToUpload) { - await sendFileMessage(rid, fileInfo, tmid, server, user); + await sendFileMessage(rid, fileInfo, tmid, server); } } } catch (e) { diff --git a/app/lib/methods/sendFileMessage/index.ts b/app/lib/methods/sendFileMessage/index.ts index 0fe4c76b76c..e859b86ec2a 100644 --- a/app/lib/methods/sendFileMessage/index.ts +++ b/app/lib/methods/sendFileMessage/index.ts @@ -1,4 +1,4 @@ -import { type IUpload, type TSendFileMessageFileInfo, type IUser } from '../../../definitions'; +import { type IUpload, type TSendFileMessageFileInfo } from '../../../definitions'; import { store } from '../../store/auxStore'; import { compareServerVersion } from '../helpers'; import { sendFileMessage as sendFileMessageV1 } from './sendFileMessage'; @@ -9,13 +9,12 @@ export const sendFileMessage = ( fileInfo: TSendFileMessageFileInfo, tmid: string | undefined, server: string, - user: Partial>, isForceTryAgain?: boolean ): Promise => { const { version: serverVersion } = store.getState().server; if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) { - return sendFileMessageV1(rid, fileInfo as IUpload, tmid, server, user, isForceTryAgain); + return sendFileMessageV1(rid, fileInfo as IUpload, tmid, server, isForceTryAgain); } - return sendFileMessageV2(rid, fileInfo, tmid, server, user, isForceTryAgain); + return sendFileMessageV2(rid, fileInfo, tmid, server, isForceTryAgain); }; diff --git a/app/lib/methods/sendFileMessage/sendAttachments.ts b/app/lib/methods/sendFileMessage/sendAttachments.ts index 70f0683d910..0bd64d6990b 100644 --- a/app/lib/methods/sendFileMessage/sendAttachments.ts +++ b/app/lib/methods/sendFileMessage/sendAttachments.ts @@ -8,7 +8,6 @@ export const sendAttachments = ({ rid, tmid, server, - user, altTextSupported, getMsg }: { @@ -47,8 +46,7 @@ export const sendAttachments = ({ width }, tmid, - server, - user + server ); }) ); diff --git a/app/lib/methods/sendFileMessage/sendFileMessage.test.ts b/app/lib/methods/sendFileMessage/sendFileMessage.test.ts index 3f790b13819..9d6bbf33965 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessage.test.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessage.test.ts @@ -126,73 +126,38 @@ describe('sendFileMessage router', () => { (store.getState as jest.Mock).mockReturnValue({ server: { version: '6.9.0' } }); const { compareServerVersion } = require('../helpers'); (compareServerVersion as jest.Mock).mockReturnValueOnce(true); - await sendFileMessage('rid', { name: 'a', type: 't', path: '/p', size: 1 } as any, undefined, 'https://x.com', { - id: 'u', - token: 't' - }); - // V1 hits /v1/rooms.upload + await sendFileMessage('rid', { name: 'a', type: 't', path: '/p', size: 1 } as any, undefined, 'https://x.com'); expect(mockState.url).toBe('https://x.com/api/v1/rooms.upload/rid'); }); it('routes to V2 for RC >= 6.10.0', async () => { const { compareServerVersion } = require('../helpers'); (compareServerVersion as jest.Mock).mockReturnValueOnce(false); - await sendFileMessage('rid', { name: 'a', type: 't', path: '/p', size: 1 } as any, undefined, 'https://x.com', { - id: 'u', - token: 't' - }); + await sendFileMessage('rid', { name: 'a', type: 't', path: '/p', size: 1 } as any, undefined, 'https://x.com'); expect(mockState.url).toBe('https://x.com/api/v1/rooms.media/rid'); }); }); describe('sendFileMessage V1 headers', () => { - it('spreads sdk.getHeaders() + Content-Type + auth headers when user has token + id', async () => { + it('spreads sdk.getHeaders() + Content-Type', async () => { await sendFileMessageV1( 'rid', { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, undefined, - 'https://x.com', - { id: 'u-1', token: 'tok-1' } + 'https://x.com' ); expect(mockState.headers).toMatchObject({ 'User-Agent': 'RC Mobile test', - 'Content-Type': 'multipart/form-data', - 'X-Auth-Token': 'tok-1', - 'X-User-Id': 'u-1' + 'Content-Type': 'multipart/form-data' }); }); - it('omits X-Auth-Token when user.token is undefined', async () => { - await sendFileMessageV1( - 'rid', - { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, - undefined, - 'https://x.com', - { id: 'u-1' } - ); - expect(mockState.headers['X-Auth-Token']).toBeUndefined(); - expect(mockState.headers['X-User-Id']).toBe('u-1'); - }); - - it('omits X-User-Id when user.id is undefined', async () => { - await sendFileMessageV1( - 'rid', - { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, - undefined, - 'https://x.com', - { token: 'tok-1' } - ); - expect(mockState.headers['X-User-Id']).toBeUndefined(); - expect(mockState.headers['X-Auth-Token']).toBe('tok-1'); - }); - it('progress callback updates upload record progress field', async () => { await sendFileMessageV1( 'rid', { name: 'a', type: 't', path: '/p', size: 1, store: 'Uploads', description: '' } as any, undefined, - 'https://x.com', - { id: 'u-1', token: 'tok-1' } + 'https://x.com' ); const dbMod = require('../../database'); await mockState.progressCb!(50, 100); @@ -209,23 +174,21 @@ describe('sendFileMessage V2', () => { const baseFile = { name: 'a', type: 't', path: '/p', size: 1 } as any; it('invokes Encryption.encryptFile with (rid, fileInfo)', async () => { - await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com'); expect(mockEncryptFile).toHaveBeenCalledWith('rid', baseFile); }); it('uploads to /v1/rooms.media/{rid} with merged headers', async () => { - await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com'); expect(mockState.url).toBe('https://x.com/api/v1/rooms.media/rid'); expect(mockState.headers).toMatchObject({ 'User-Agent': 'RC Mobile test', - 'Content-Type': 'multipart/form-data', - 'X-Auth-Token': 'tok-1', - 'X-User-Id': 'u-1' + 'Content-Type': 'multipart/form-data' }); }); it('confirms via /v1/rooms.mediaConfirm with JSON Content-Type override', async () => { - await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com'); expect(fetch).toHaveBeenCalledWith( 'https://x.com/api/v1/rooms.mediaConfirm/rid/f1', expect.objectContaining({ @@ -236,15 +199,9 @@ describe('sendFileMessage V2', () => { }); it('includes a content form-data entry when Encryption returns fileContent', async () => { - await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', { id: 'u-1', token: 'tok-1' }); + await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com'); const contentField = mockState.formData.find((e: any) => e.name === 'content'); expect(contentField).toBeDefined(); expect(JSON.parse(contentField.data)).toEqual({ algorithm: 'a' }); }); - - it('omits X-Auth-Token / X-User-Id when user has neither', async () => { - await sendFileMessageV2('rid', baseFile, undefined, 'https://x.com', {}); - expect(mockState.headers['X-Auth-Token']).toBeUndefined(); - expect(mockState.headers['X-User-Id']).toBeUndefined(); - }); }); diff --git a/app/lib/methods/sendFileMessage/sendFileMessage.ts b/app/lib/methods/sendFileMessage/sendFileMessage.ts index 22a8bbaf6f3..778d72a8c9c 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessage.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessage.ts @@ -1,7 +1,7 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { Alert } from 'react-native'; -import { type IUser, type TSendFileMessageFileInfo, type TUploadModel } from '../../../definitions'; +import { type TSendFileMessageFileInfo, type TUploadModel } from '../../../definitions'; import i18n from '../../../i18n'; import database from '../../database'; import FileUpload from '../helpers/fileUpload'; @@ -15,12 +15,10 @@ export async function sendFileMessage( fileInfo: TSendFileMessageFileInfo, tmid: string | undefined, server: string, - user: Partial>, isForceTryAgain?: boolean ): Promise { let uploadPath: string | null = ''; try { - const { id, token } = user; const uploadUrl = `${server}/api/v1/rooms.upload/${rid}`; fileInfo.rid = rid; @@ -83,11 +81,9 @@ export async function sendFileMessage( }); } - const headers: Record = { + const headers = { ...sdk.getHeaders(), - 'Content-Type': 'multipart/form-data', - ...(token ? { 'X-Auth-Token': token } : {}), - ...(id ? { 'X-User-Id': id } : {}) + 'Content-Type': 'multipart/form-data' }; uploadQueue[uploadPath] = new FileUpload(uploadUrl, headers, formData, async (loaded, total) => { diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts index ee00e04ec2d..815b11468d2 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts @@ -111,7 +111,7 @@ beforeEach(() => { describe('sendFileMessageV2', () => { it('builds form data with correct file fields', async () => { - await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com'); expect(mockState.formData).toEqual( expect.arrayContaining([ @@ -125,14 +125,12 @@ describe('sendFileMessageV2', () => { ); }); - it('includes auth headers from sdk.getHeaders()', async () => { - await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + it('includes headers from sdk.getHeaders()', async () => { + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com'); expect(mockState.headers).toMatchObject({ 'User-Agent': 'RC Mobile test', - 'Content-Type': 'multipart/form-data', - 'X-Auth-Token': 'tok1', - 'X-User-Id': 'u1' + 'Content-Type': 'multipart/form-data' }); }); @@ -143,7 +141,7 @@ describe('sendFileMessageV2', () => { fileContent: { algorithm: 'rc4' } }); - await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com'); const contentField = mockState.formData?.find((e: any) => e.name === 'content'); expect(contentField).toBeDefined(); @@ -153,18 +151,17 @@ describe('sendFileMessageV2', () => { it('throws and calls persistUploadError on upload failure', async () => { mockSend.mockRejectedValue(new Error('upload failed')); const { persistUploadError, uploadQueue } = require('./utils'); - // ensure uploadQueue has the path entry so the error path triggers persistUploadError uploadQueue['upload-path-1'] = {}; await expect( - sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }) + sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com') ).rejects.toThrow('upload failed'); expect(persistUploadError).toHaveBeenCalledWith(baseFile.path, 'rid1'); }); it('confirms upload via POST to /v1/rooms.mediaConfirm', async () => { - await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com', { id: 'u1', token: 'tok1' }); + await sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com'); expect(fetch).toHaveBeenCalledWith( 'https://server.com/api/v1/rooms.mediaConfirm/rid1/file1', diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts index 22383b6c3e0..8fefbe75acc 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts @@ -1,4 +1,4 @@ -import { type TSendFileMessageFileInfo, type IUser, type TUploadModel } from '../../../definitions'; +import { type TSendFileMessageFileInfo, type TUploadModel } from '../../../definitions'; import database from '../../database'; import { Encryption } from '../../encryption'; import { copyFileToCacheDirectoryIfNeeded, createUploadRecord, persistUploadError, uploadQueue } from './utils'; @@ -12,18 +12,14 @@ export async function sendFileMessageV2( fileInfo: TSendFileMessageFileInfo, tmid: string | undefined, server: string, - user: Partial>, isForceTryAgain?: boolean ): Promise { let uploadPath: string | null = ''; let uploadRecord: TUploadModel | null; try { - const { id, token } = user; const headers: Record = { ...sdk.getHeaders(), - 'Content-Type': 'multipart/form-data', - ...(token ? { 'X-Auth-Token': token } : {}), - ...(id ? { 'X-User-Id': id } : {}) + 'Content-Type': 'multipart/form-data' }; const db = database.active; diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 5f8a0054a0d..f512a60c895 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -157,12 +157,16 @@ describe('Sdk.subscribeNotifyUser', () => { }); describe('Sdk.login', () => { - const buildFakeSdkWithLogin = (postResult: any, loginWithToken = jest.fn().mockResolvedValue(undefined)) => { + const buildFakeSdkWithLogin = ( + postResult: any, + loginWithToken = jest.fn().mockResolvedValue(undefined), + logout = jest.fn().mockResolvedValue(undefined) + ) => { const post = jest.fn().mockResolvedValue(postResult); return { client: { ddp: {} }, connection: buildFakeConnection(), - account: { loginWithToken }, + account: { loginWithToken, logout }, rest: { post, handleTwoFactorChallenge: jest.fn() }, __post: post, __loginWithToken: loginWithToken @@ -199,6 +203,31 @@ describe('Sdk.login', () => { expect(err.status).toBe(401); expect(err.data.error).toBe('Unauthorized'); }); + + it('sets X-Auth-Token and X-User-Id in getHeaders after successful login', async () => { + const fake = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } + }); + setInternalSdk(fake); + await sdk.login({ user: 'john', password: 'secret' }); + const headers = sdk.getHeaders(); + expect(headers['X-Auth-Token']).toBe('tok-abc'); + expect(headers['X-User-Id']).toBe('uid-1'); + }); + + it('clears X-Auth-Token and X-User-Id from getHeaders after logout', async () => { + const fake = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } + }); + setInternalSdk(fake); + await sdk.login({ user: 'john', password: 'secret' }); + await sdk.logout(); + const headers = sdk.getHeaders(); + expect(headers['X-Auth-Token']).toBe(''); + expect(headers['X-User-Id']).toBe(''); + }); }); describe('normalizeResponseError', () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index f5077c6e16d..49fd9096516 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -245,6 +245,12 @@ class Sdk { return Promise.reject(new Error('Invalid response from server')); } await this.current?.account.loginWithToken(loginResult.data.authToken); + + this.setHeaders({ + 'X-Auth-Token': loginResult.data.authToken, + 'X-User-Id': loginResult.data.userId + }); + return loginResult.data; } catch (e: any) { if (e instanceof Response) { @@ -389,6 +395,10 @@ class Sdk { if (this.current?.account) { await Promise.race([this.current.account.logout(), new Promise(resolve => setTimeout(resolve, 5000))]); } + this.setHeaders({ + 'X-Auth-Token': '', + 'X-User-Id': '' + }); } } diff --git a/app/views/RoomView/UploadProgress.tsx b/app/views/RoomView/UploadProgress.tsx index 36d3bd14f0f..9337b936650 100644 --- a/app/views/RoomView/UploadProgress.tsx +++ b/app/views/RoomView/UploadProgress.tsx @@ -153,7 +153,7 @@ class UploadProgress extends Component { - const { rid, baseUrl: server, user } = this.props; + const { rid, baseUrl: server } = this.props; try { const db = database.active; @@ -162,7 +162,7 @@ class UploadProgress extends Component Date: Tue, 9 Jun 2026 04:36:34 +0530 Subject: [PATCH 122/203] lint fix --- app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts index 815b11468d2..5eb37749576 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts @@ -153,9 +153,7 @@ describe('sendFileMessageV2', () => { const { persistUploadError, uploadQueue } = require('./utils'); uploadQueue['upload-path-1'] = {}; - await expect( - sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com') - ).rejects.toThrow('upload failed'); + await expect(sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com')).rejects.toThrow('upload failed'); expect(persistUploadError).toHaveBeenCalledWith(baseFile.path, 'rid1'); }); From 96661cb61b6dbbb5a53c551950c23c6e18283f19 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 04:39:31 +0530 Subject: [PATCH 123/203] making ts happy --- app/lib/services/restApi.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index 3032fc1b749..12108d0ba9e 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -96,8 +96,9 @@ describe('mediaCallsStateSignals', () => { const result = await mediaCallsStateSignals('device-id'); - expect(result.signals).toHaveLength(2); expect(result.success).toBe(true); + if (!result.success) return; + expect(result.signals).toHaveLength(2); }); it('returns empty signals and success false when sdk.get throws', async () => { @@ -105,8 +106,10 @@ describe('mediaCallsStateSignals', () => { const result = await mediaCallsStateSignals('device-id'); - expect(result.signals).toEqual([]); expect(result.success).toBe(false); + if (!result.success && 'signals' in result) { + expect(result.signals).toEqual([]); + } }); it('returns empty signals and success false when sdk.get returns an error response', async () => { @@ -114,8 +117,10 @@ describe('mediaCallsStateSignals', () => { const result = await mediaCallsStateSignals('device-id'); - expect(result.signals).toEqual([]); expect(result.success).toBe(false); + if (!result.success && 'signals' in result) { + expect(result.signals).toEqual([]); + } }); }); From b70c1c9f7df49677a932d642a6b7b45f0e2fa67d Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:41:14 +0530 Subject: [PATCH 124/203] added unit test to make sure header change for deeplink login and server switch --- app/lib/services/sdk.test.ts | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index f512a60c895..02c7ece006a 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -228,6 +228,82 @@ describe('Sdk.login', () => { expect(headers['X-Auth-Token']).toBe(''); expect(headers['X-User-Id']).toBe(''); }); + + it('sets the auth headers from a resume-token (deep link) login response', async () => { + // Deep link logins reach sdk.login with `{ resume }` instead of `{ user, password }`, + // but the header centralization reads authToken/userId from the same /v1/login response. + const fake = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-deep', userId: 'uid-deep', me: { username: 'jane' } } + }); + setInternalSdk(fake); + await sdk.login({ resume: 'resume-token-xyz' }); + expect(fake.__post).toHaveBeenCalledWith( + '/v1/login', + { resume: 'resume-token-xyz' }, + expect.objectContaining({ headers: expect.any(Object) }) + ); + const headers = sdk.getHeaders(); + expect(headers['X-Auth-Token']).toBe('tok-deep'); + expect(headers['X-User-Id']).toBe('uid-deep'); + }); + + it('uses each server own token/user after switching servers', async () => { + // Switching servers goes through initialize(), which resets headers to defaults. + // Login on server1 → token1/user1; re-initialize for server2 → headers cleared; + // login on server2 → token2/user2 (server1 credentials never leak). + const server1Sdk = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-1', userId: 'uid-1', me: { username: 'user1' } } + }); + (DDPSDK.create as jest.Mock).mockReturnValueOnce(server1Sdk); + sdk.initialize('https://server1.com'); + await sdk.login({ user: 'user1', password: 'pass1' }); + expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-1'); + expect(sdk.getHeaders()['X-User-Id']).toBe('uid-1'); + + const server2Sdk = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-2', userId: 'uid-2', me: { username: 'user2' } } + }); + (DDPSDK.create as jest.Mock).mockReturnValueOnce(server2Sdk); + sdk.initialize('https://server2.com'); + // initialize() must wipe server1 credentials before the new login. + expect(sdk.getHeaders()['X-Auth-Token']).toBeUndefined(); + expect(sdk.getHeaders()['X-User-Id']).toBeUndefined(); + + await sdk.login({ user: 'user2', password: 'pass2' }); + expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-2'); + expect(sdk.getHeaders()['X-User-Id']).toBe('uid-2'); + }); + + it('switches to the deep link server credentials when already logged in on another server', async () => { + // Already authenticated on server1. + const server1Sdk = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-1', userId: 'uid-1', me: { username: 'user1' } } + }); + (DDPSDK.create as jest.Mock).mockReturnValueOnce(server1Sdk); + sdk.initialize('https://server1.com'); + await sdk.login({ user: 'user1', password: 'pass1' }); + expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-1'); + + // A deep link targeting server2 re-initializes on the new host, then logs in with the resume token. + const server2Sdk = buildFakeSdkWithLogin({ + success: true, + data: { authToken: 'tok-2', userId: 'uid-2', me: { username: 'user2' } } + }); + (DDPSDK.create as jest.Mock).mockReturnValueOnce(server2Sdk); + sdk.initialize('https://server2.com'); + await sdk.login({ resume: 'deeplink-resume-token' }); + + const headers = sdk.getHeaders(); + expect(headers['X-Auth-Token']).toBe('tok-2'); + expect(headers['X-User-Id']).toBe('uid-2'); + // server1 credentials must not survive the deep link switch. + expect(headers['X-Auth-Token']).not.toBe('tok-1'); + expect(headers['X-User-Id']).not.toBe('uid-1'); + }); }); describe('normalizeResponseError', () => { From a734133c2d97ccbe5eab7a012e7ecebd26cdbf7e Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:56:52 +0530 Subject: [PATCH 125/203] fix anti pattern --- app/lib/services/sdk.ts | 42 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 49fd9096516..0b2b0381c6a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -260,30 +260,28 @@ class Sdk { } } - methodCall(...args: any[]): Promise { - return new Promise(async (resolve, reject) => { - try { - if (!this.current || !this.current.client) { - throw new Error('SDK not initialized'); - } - const [method, ...params] = args; - const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(this.code ? [this.code] : [])); - return resolve(result); - } catch (e: any) { - if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { - const { details } = e; - try { - this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' }); - return resolve(this.methodCall(...args)); - } catch { - // twoFactor was canceled - return resolve({}); - } - } else { - reject(e); + async methodCall(...args: any[]): Promise { + try { + if (!this.current || !this.current.client) { + throw new Error('SDK not initialized'); + } + const [method, ...params] = args; + const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(this.code ? [this.code] : [])); + return result; + } catch (e: any) { + if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { + const { details } = e; + try { + this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' }); + return this.methodCall(...args); + } catch { + // twoFactor was canceled + return {}; } + } else { + return Promise.reject(e); } - }); + } } methodCallWrapper(method: string, ...params: any[]): Promise { From f59404c22c1a17ea441b3fc845b75898a4cf1227 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:11:52 +0530 Subject: [PATCH 126/203] lock file fix --- pnpm-lock.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82970bd6c87..5f5c86a9a84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,8 +81,8 @@ importers: specifier: ^7.3.3 version: 7.3.3(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@react-navigation/native-stack': - specifier: ^7.3.23 - version: 7.9.0(7eddc8a5adcca82013f95b3203fbc34c) + specifier: ^7.17.5 + version: 7.17.5(20e22ec03db44899c27c93ee7866e802) '@rocket.chat/core-typings': specifier: 8.3.2 version: 8.3.2(@types/node@25.0.3)(typescript@5.9.3) @@ -2468,7 +2468,7 @@ packages: engines: {node: 22.16.0} '@rocket.chat/mobile-crypto@https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/5a390be1271bfe3d4b4e8c7ae870249f2c9a8cb0': - resolution: {gitHosted: true, integrity: sha512-gk3YAmpBh34zGwDH+j5gjFFvHyvkXCm3jdTUe1EdLfyjzhi9sU6xUYH8JjgVlJ6NnGxxCO5hmsdn6QRkB3vL7g==, tarball: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/5a390be1271bfe3d4b4e8c7ae870249f2c9a8cb0} + resolution: {gitHosted: true, tarball: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/5a390be1271bfe3d4b4e8c7ae870249f2c9a8cb0} version: 0.3.0 peerDependencies: react: '*' From ec42521f34db69f79e8faa1220ecab568c1d7a31 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 1 Jul 2026 23:27:23 +0530 Subject: [PATCH 127/203] changes --- app/externalModules.d.ts | 1 - app/lib/methods/getCustomEmojis.ts | 2 +- app/lib/methods/getPermissions.ts | 2 +- app/lib/services/connect.ts | 8 +++----- app/lib/services/restApi.ts | 25 +++++++++++++++---------- app/sagas/__tests__/deepLinking.test.ts | 3 +++ 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/app/externalModules.d.ts b/app/externalModules.d.ts index 1b220ba2dd3..4935ca2295f 100644 --- a/app/externalModules.d.ts +++ b/app/externalModules.d.ts @@ -1,5 +1,4 @@ declare module 'remove-markdown'; -declare module '@rocket.chat/sdk'; declare module 'react-native-mime-types'; declare module 'react-native-restart'; declare module 'react-native-math-view'; diff --git a/app/lib/methods/getCustomEmojis.ts b/app/lib/methods/getCustomEmojis.ts index 36e1b3d76f5..eb47c5d7b33 100644 --- a/app/lib/methods/getCustomEmojis.ts +++ b/app/lib/methods/getCustomEmojis.ts @@ -111,7 +111,7 @@ export function getCustomEmojis() { if (compareServerVersion(serverVersion, 'lowerThan', '0.75.0')) { // RC 0.61.0 // @ts-ignore - const result = await sdk.get('emoji-custom'); + const result = await sdk.get('/v1/emoji-custom'); // @ts-ignore let { emojis } = result; emojis = emojis.filter((emoji: TCustomEmojiModel) => !updatedSince || emoji._updatedAt.toISOString() > updatedSince); diff --git a/app/lib/methods/getPermissions.ts b/app/lib/methods/getPermissions.ts index 22c34323407..6692204fe46 100644 --- a/app/lib/methods/getPermissions.ts +++ b/app/lib/methods/getPermissions.ts @@ -171,7 +171,7 @@ export function getPermissions(): Promise { if (serverVersion && compareServerVersion(serverVersion, 'lowerThan', '0.73.0')) { // RC 0.66.0 // @ts-ignore - const result: any = await sdk.get('permissions.list'); + const result: any = await sdk.get('/v1/permissions.list'); if (!result.success) { return resolve(); } diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 52286dd8a06..291042aa980 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -26,10 +26,8 @@ import { onRolesChanged } from '../methods/getRoles'; import { getSettings } from '../methods/getSettings'; import { setPresenceCap } from '../methods/getUsersPresence'; import { _setUser, type IActiveUsers, _setUserTimer, _activeUsers } from '../methods/setUser'; -import { compareServerVersion } from '../methods/helpers/compareServerVersion'; -import { isIOS } from '../methods/helpers/deviceInfo'; -import { isSsl } from '../methods/helpers/isSsl'; import { normalizeStatusExpiresAt } from '../methods/helpers/normalizeStatusExpiresAt'; +import { unsubscribeRooms } from '../methods/subscribeRooms'; import fetch from '../methods/helpers/fetch'; interface IServices { @@ -150,8 +148,8 @@ async function connect({ server, logoutOnError = false }: { server: string; logo const userStatus = ddpMessage.fields.args[0]; const { uid } = ddpMessage.fields; const [, status, statusText, statusSource, statusExpiresAtRaw] = userStatus; - const statusExpiresAt = normalizeStatusExpiresAt(statusExpiresAtRaw); - const newStatus = { status: STATUSES[status], statusText, statusSource, statusExpiresAt }; + const statusExpiresAt = normalizeStatusExpiresAt(statusExpiresAtRaw); + const newStatus = { status: STATUSES[status], statusText, statusSource, statusExpiresAt }; const { user: loggedUser } = store.getState().login; if (loggedUser && loggedUser.id === uid) { diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index e661599cad3..45780c84a40 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -324,7 +324,7 @@ export const setUserPreferences = (userId: string, data: Partial // RC 1.2.0 - sdk.post('users.setStatus', { status, message, ...(expiresAt !== undefined && { expiresAt }) }); + sdk.post('/v1/users.setStatus', { status, message, ...(expiresAt !== undefined && { expiresAt }) }); export const setReaction = (emoji: string, messageId: string) => // RC 0.62.2 @@ -1043,16 +1043,16 @@ export const editMediaMessage = async ( fileId: string, body: { description?: string; filename: string; msg?: string } ) => { - const { server } = reduxStore.getState(); - const { authToken, userId } = sdk.currentLogin; + const serverUrl = sdk.server; + if (!serverUrl) { + throw new Error('SDK not initialized'); + } // RC 8.4.0 - const response = await fetch(`${server.server}/api/v1/rooms.mediaConfirm/${rid}/${fileId}`, { + const response = await fetch(`${serverUrl}/api/v1/rooms.mediaConfirm/${rid}/${fileId}`, { method: 'POST', headers: { ...sdk.getHeaders(), - 'Content-Type': 'application/json', - 'X-Auth-Token': authToken, - 'X-User-Id': userId + 'Content-Type': 'application/json' }, body: JSON.stringify({ description: body.description, @@ -1162,9 +1162,14 @@ export const removePushToken = async (): Promise => { if (token) { lastToken = ''; lastVoipToken = ''; - // RC 0.60.0 - const response = await sdk.delete('/v1/push.token', { token }); - return response?.success === true; + try { + // RC 0.60.0 + const response = await sdk.delete('/v1/push.token', { token }); + return response?.success === true; + } catch (e) { + log(e); + return false; + } } return false; }; diff --git a/app/sagas/__tests__/deepLinking.test.ts b/app/sagas/__tests__/deepLinking.test.ts index 6f4d5eec99f..7fa9206f9fb 100644 --- a/app/sagas/__tests__/deepLinking.test.ts +++ b/app/sagas/__tests__/deepLinking.test.ts @@ -715,6 +715,9 @@ describe('deepLinking saga — handleShareExtension server-already-connected gua await flushSagaMicrotasks(); expect(store.getState().app.root).toBe(RootEnum.ROOT_SHARE_EXTENSION); + }); +}); + // ─── handleOAuth — single-use credentialToken dedup guard ──────────────────── describe('deepLinking saga — handleOAuth dedup guard', () => { From e7d264233a31a1c55033e8015164145268dc2981 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 1 Jul 2026 23:59:20 +0530 Subject: [PATCH 128/203] More changes --- app/containers/markdown/Markdown.test.tsx | 2 ++ app/definitions/rest/v1/users.ts | 3 +++ app/lib/methods/getUsersPresence.test.ts | 3 ++- app/lib/methods/getUsersPresence.ts | 10 ++++++++-- app/lib/services/connect.test.ts | 7 +++++-- .../__snapshots__/RoomInfoViewAvatar.test.tsx.snap | 8 ++++++-- 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/app/containers/markdown/Markdown.test.tsx b/app/containers/markdown/Markdown.test.tsx index 3beca437ef3..43c17e9bbd1 100644 --- a/app/containers/markdown/Markdown.test.tsx +++ b/app/containers/markdown/Markdown.test.tsx @@ -1,4 +1,6 @@ import { generateSnapshots } from '../../../.rnstorybook/generateSnapshots'; import * as stories from './Markdown.stories'; +jest.useFakeTimers({ now: new Date('2026-01-01T12:00:00Z').getTime() }); + generateSnapshots(stories); diff --git a/app/definitions/rest/v1/users.ts b/app/definitions/rest/v1/users.ts index 128d2e115be..1c5e4dc2f22 100644 --- a/app/definitions/rest/v1/users.ts +++ b/app/definitions/rest/v1/users.ts @@ -2,6 +2,7 @@ import { type IAvatarSuggestion, type IProfileParams } from '../../IProfile'; import type { ITeam } from '../../ITeam'; import type { IUser, INotificationPreferences, IUserPreferences, IUserRegistered } from '../../IUser'; import type { TUserStatus } from '../../TUserStatus'; +import type { TStatusSource } from '../../TStatusSource'; export type UsersEndpoints = { 'users.2fa.sendEmailCode': { @@ -80,6 +81,8 @@ export type UsersEndpoints = { utcOffset: number; statusText?: string; avatarETag?: string; + statusExpiresAt?: string; + statusSource?: TStatusSource; }[]; full: boolean; success: boolean; diff --git a/app/lib/methods/getUsersPresence.test.ts b/app/lib/methods/getUsersPresence.test.ts index dbb9ecc3377..6465b7a3f9e 100644 --- a/app/lib/methods/getUsersPresence.test.ts +++ b/app/lib/methods/getUsersPresence.test.ts @@ -52,7 +52,8 @@ jest.mock('./helpers/log', () => ({ })); jest.mock('./helpers', () => ({ - compareServerVersion: jest.requireActual('./helpers/compareServerVersion').compareServerVersion + compareServerVersion: jest.requireActual('./helpers/compareServerVersion').compareServerVersion, + normalizeStatusExpiresAt: jest.fn((value: unknown) => value) })); jest.mock('./userPreferences', () => ({ diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index 0546c416eff..a4e59b6789d 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -78,7 +78,13 @@ export async function getUsersPresence(usersParams: string[]) { const { users } = result; const activeUsers = usersParams.reduce((ret: IActiveUsers, id) => { - const user = users.find((u: IUser) => u._id === id) ?? { _id: id, status: 'offline' }; + const user = users.find(u => u._id === id) ?? { + _id: id, + status: 'offline', + statusText: '', + statusExpiresAt: undefined, + statusSource: undefined + }; const { _id, status, statusText, statusExpiresAt, statusSource } = user; const normalizedStatusExpiresAt = normalizeStatusExpiresAt(statusExpiresAt); @@ -86,7 +92,7 @@ export async function getUsersPresence(usersParams: string[]) { reduxStore.dispatch(setUser({ status, statusText, statusExpiresAt: normalizedStatusExpiresAt, statusSource })); } - ret[_id] = { status, statusText, statusExpiresAt: normalizedStatusExpiresAt, statusSource }; + ret[_id] = { status, statusText: statusText || '', statusExpiresAt: normalizedStatusExpiresAt, statusSource }; return ret; }, {}); InteractionManager.runAfterInteractions(() => { diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 4bc16a036ea..e33d6e7a13b 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -79,7 +79,7 @@ type MockStoreState = { meteor: { connected: boolean }; login: { user: unknown; isAuthenticated: boolean }; settings: Record; - server: { version: string }; + server?: { version: string }; }; const mockStoreGetState = jest.fn(() => ({ meteor: { connected: false }, @@ -151,6 +151,10 @@ const getCapturedConnectionListener = (): ((status: string) => void) => { return call[1]; }; +/** Returns the collection handlers registered via sdk.onCollection(event, cb) for a given event. */ +const getHandlersByEvent = (event: string): Array<(ddpMessage: any) => void> => + mockSdkOnCollection.mock.calls.filter(([collection]) => collection === event).map(([, cb]) => cb); + interface IServices { [index: string]: string | boolean; name: string; @@ -668,7 +672,6 @@ describe('connect — stream-notify-logged updateAvatar', () => { beforeEach(async () => { jest.clearAllMocks(); - mockOnStreamDataStops.length = 0; mockStoreGetState.mockReturnValue({ meteor: { connected: false }, login: { user: null, isAuthenticated: false }, diff --git a/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap b/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap index 1baf0606fea..c12b1a2834b 100644 --- a/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap +++ b/app/views/RoomInfoView/components/__snapshots__/RoomInfoViewAvatar.test.tsx.snap @@ -49,7 +49,9 @@ exports[`Story Snapshots: Default should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", + }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=240", }, ] @@ -117,7 +119,9 @@ exports[`Story Snapshots: WithEdit should match snapshot 1`] = ` source={ [ { - "headers": undefined, + "headers": { + "User-Agent": "RC Mobile; ios 14.0; v1.0.0 (1)", + }, "uri": "https://open.rocket.chat/avatar/john.doe?format=png&size=240", }, ] From 53fcdec1c3eeee694b80339e70a735b5f855c665 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:04:08 +0530 Subject: [PATCH 129/203] test lint fix --- app/lib/services/restApi.test.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index 12108d0ba9e..c07a43cbba4 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -106,10 +106,7 @@ describe('mediaCallsStateSignals', () => { const result = await mediaCallsStateSignals('device-id'); - expect(result.success).toBe(false); - if (!result.success && 'signals' in result) { - expect(result.signals).toEqual([]); - } + expect(result).toEqual({ signals: [], success: false }); }); it('returns empty signals and success false when sdk.get returns an error response', async () => { @@ -117,10 +114,7 @@ describe('mediaCallsStateSignals', () => { const result = await mediaCallsStateSignals('device-id'); - expect(result.success).toBe(false); - if (!result.success && 'signals' in result) { - expect(result.signals).toEqual([]); - } + expect(result).toEqual({ signals: [], success: false }); }); }); From c723a1b8afeee74555cf905f9fe30b9b3012e520 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 2 Jul 2026 02:27:01 +0530 Subject: [PATCH 130/203] more changes --- app/lib/methods/getUsersPresence.test.ts | 7 +++--- app/lib/methods/getUsersPresence.ts | 2 +- app/lib/services/connect.ts | 2 +- app/lib/services/sdk.ts | 27 +++++++++++++++++------- app/sagas/deepLinking.js | 4 ++-- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/lib/methods/getUsersPresence.test.ts b/app/lib/methods/getUsersPresence.test.ts index 6465b7a3f9e..3fcf1611b14 100644 --- a/app/lib/methods/getUsersPresence.test.ts +++ b/app/lib/methods/getUsersPresence.test.ts @@ -20,7 +20,6 @@ jest.mock('../services/sdk', () => ({ __esModule: true, default: { subscribe: jest.fn(), - subscribeRaw: jest.fn(), get: jest.fn() } })); @@ -157,7 +156,7 @@ describe('subscribeUsersPresence', () => { describe('getUsersPresence', () => { beforeEach(() => { (sdk.get as jest.Mock).mockReset(); - (sdk.subscribeRaw as jest.Mock).mockReset(); + (sdk.subscribe as jest.Mock).mockReset(); (reduxStore.dispatch as jest.Mock).mockReset(); }); @@ -187,11 +186,11 @@ describe('getUsersPresence', () => { expect(sdk.get).toHaveBeenCalledWith('/v1/users.presence', {}); }); - it('subscribes stream-user-presence via subscribeRaw for server >= 4.1.0 on success', async () => { + it('subscribes stream-user-presence via subscribe for server >= 4.1.0 on success', async () => { setVersion('4.1.0', { settings: {} }); (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: [] }); await getUsersPresence(['uid1']); - expect(sdk.subscribeRaw).toHaveBeenCalledWith('stream-user-presence', ['', { added: ['uid1'] }]); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-user-presence', ['', { added: ['uid1'] }]); }); it('dispatches setActiveUsers with user presence data', async () => { diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index a4e59b6789d..9db8273a2ec 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -71,7 +71,7 @@ export async function getUsersPresence(usersParams: string[]) { const result = await sdk.get('/v1/users.presence', params); if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) { - sdk.subscribeRaw('stream-user-presence', ['', { added: usersParams }]); + sdk.subscribe('stream-user-presence', ['', { added: usersParams }]); } if (result.success) { diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 291042aa980..1873d4dd913 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -439,7 +439,7 @@ async function getWebsocketInfo({ return { success: false, - message: I18n.t('Invalid_URL') + message: err?.message || I18n.t('Invalid_URL') }; } finally { probeSdk?.connection.close(); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 0b2b0381c6a..0e7fb4ba6a6 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -193,11 +193,20 @@ class Sdk { if ([totpInvalid, totpRequired].includes(errorType)) { const { details } = isMethodCall ? normalized : normalized?.data; try { - await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); - return this.post(endpoint, params); + const totpResult = await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); + this.setHeaders({ + 'x-2fa-code': totpResult.twoFactorCode, + 'x-2fa-method': totpResult.twoFactorMethod + }); + return await this.post(endpoint, params); } catch { // twoFactor was canceled return {} as ResultFor<'POST', MatchPathPattern>; + } finally { + const next = { ...this.headers }; + delete next['x-2fa-code']; + delete next['x-2fa-method']; + this.headers = next; } } else { throw normalized; @@ -267,19 +276,25 @@ class Sdk { } const [method, ...params] = args; const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(this.code ? [this.code] : [])); + if (this.code) { + this.code = null; + } return result; } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { const { details } = e; try { this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' }); - return this.methodCall(...args); + const result = await this.methodCall(...args); + this.code = null; + return result; } catch { // twoFactor was canceled + this.code = null; return {}; } } else { - return Promise.reject(e); + throw e; } } } @@ -351,10 +366,6 @@ class Sdk { return this.current?.stream(...args); } - subscribeRaw(...args: Parameters) { - return this.current?.client.subscribe(...args); - } - /** * Re-subscribe to the per-user `stream-notify-user` channels needed for VoIP, * notifications, and presence. Used after `forceReopen()` — once the socket diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index a3c11a9cb96..883db056132 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -236,7 +236,7 @@ const handleOpen = function* handleOpen({ params }) { const hostAlreadyConnected = sdk.server === host; if (!hostAlreadyConnected) { yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); - yield put(serverInitAdd(server)); + yield put(serverInitAdd(host)); yield delay(1000); EventEmitter.emit('NewServer', { server: host }); } @@ -339,7 +339,7 @@ const handleClickCallPush = function* handleClickCallPush({ params }) { return; } yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); - yield put(serverInitAdd(server)); + yield put(serverInitAdd(host)); yield delay(1000); EventEmitter.emit('NewServer', { server: host }); if (params.token) { From 6acc6a32c6335e02a879f81c646df1bd7e5877ac Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 19:24:46 +0530 Subject: [PATCH 131/203] lockfile update --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ac77691f21..cfa4e560c5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,7 +97,7 @@ importers: version: 0.31.31 '@rocket.chat/mobile-crypto': specifier: RocketChat/rocket.chat-mobile-crypto#main - version: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/5a390be1271bfe3d4b4e8c7ae870249f2c9a8cb0(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@rocket.chat/ui-kit': specifier: ^0.39.0 version: 0.39.0(@rocket.chat/icons@0.47.0)(@types/node@25.0.3)(typescript@5.9.3) From 60b552bfafb51dabba06c9a8c7e37e3e39336bd0 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:19:53 +0530 Subject: [PATCH 132/203] fix: restore room reconnect/typing-cleanup via connection status listener onStreamData only forwards DDP collection messages, not connection lifecycle events, so 'connected'/'close' listeners in room.ts never fired after the ddp-client migration. - Add Sdk.onConnectionStatus() method that wraps ddp-client's connection-status emitter - Update RoomSubscription to use onConnectionStatus for 'connected'/ 'closed'/'disconnected' events instead of onStreamData - Add test verifying the wiring works end-to-end --- app/lib/methods/subscriptions/room.test.ts | 17 +++++++++++++++++ app/lib/methods/subscriptions/room.ts | 16 ++++++++++------ app/lib/services/sdk.ts | 9 +++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts index 97b4388c55c..fdb5df8cf14 100644 --- a/app/lib/methods/subscriptions/room.test.ts +++ b/app/lib/methods/subscriptions/room.test.ts @@ -3,6 +3,7 @@ import { loadMissedMessages } from '../loadMissedMessages'; import { clearUserTyping } from '../../../actions/usersTyping'; const mockSubscribeRoom = jest.fn, [string]>(() => Promise.resolve([])); +const mockOnConnectionStatus = jest.fn<() => void, [(status: string) => void]>(() => jest.fn()); const mockOnStreamData = jest.fn, [string, (...args: unknown[]) => void]>(() => Promise.resolve({ stop: jest.fn() }) ); @@ -10,6 +11,7 @@ jest.mock('../../services/sdk', () => ({ __esModule: true, default: { subscribeRoom: (rid: string) => mockSubscribeRoom(rid), + onConnectionStatus: (cb: (status: string) => void) => mockOnConnectionStatus(cb), onStreamData: (event: string, cb: (...args: unknown[]) => void) => mockOnStreamData(event, cb) } })); @@ -137,6 +139,21 @@ describe('RoomSubscription', () => { expect(mockSubscribeRoom).toHaveBeenCalledTimes(1); expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); }); + + it('wires handleConnected/handleClose through onConnectionStatus, not onStreamData', async () => { + await sub.subscribe(); + + expect(mockOnConnectionStatus).toHaveBeenCalledTimes(1); + const statusCallback = mockOnConnectionStatus.mock.calls[0][0]; + + mockSubscribeRoom.mockClear(); + statusCallback('connected'); + await Promise.resolve(); + expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); + + statusCallback('closed'); + expect(mockStoreDispatch).toHaveBeenCalledWith(clearUserTyping()); + }); }); describe('handleConnected', () => { diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index 4b042e10925..8530a43f558 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -34,8 +34,7 @@ export default class RoomSubscription { private rid: string; private isAlive: boolean; private promises?: Promise; - private connectedListener?: Promise; - private disconnectedListener?: Promise; + private connectionStatusListener?: () => void; private notifyRoomListener?: Promise; private messageReceivedListener?: Promise; @@ -51,8 +50,14 @@ export default class RoomSubscription { } this.promises = sdk.subscribeRoom(this.rid); - this.connectedListener = sdk.onStreamData('connected', this.handleConnected); - this.disconnectedListener = sdk.onStreamData('close', this.handleClose); + this.connectionStatusListener = sdk.onConnectionStatus(status => { + if (status === 'connected') { + this.handleConnected(); + } + if (['disconnected', 'closed'].includes(status)) { + this.handleClose(); + } + }); this.notifyRoomListener = sdk.onStreamData('stream-notify-room', this.handleNotifyRoomReceived); this.messageReceivedListener = sdk.onStreamData('stream-room-messages', this.handleMessageReceived); if (!this.isAlive) { @@ -76,8 +81,7 @@ export default class RoomSubscription { } } reduxStore.dispatch(clearUserTyping()); - this.removeListener(this.connectedListener); - this.removeListener(this.disconnectedListener); + this.connectionStatusListener?.(); this.removeListener(this.notifyRoomListener); this.removeListener(this.messageReceivedListener); }; diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 0e7fb4ba6a6..2d2f064d8cc 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -362,6 +362,15 @@ class Sdk { }); } + onConnectionStatus( + callback: (status: 'idle' | 'connecting' | 'connected' | 'failed' | 'closed' | 'disconnected' | 'reconnecting') => void + ): () => void { + if (!this.current) { + return () => {}; + } + return this.current.connection.on('connection', callback); + } + stream(...args: Parameters) { return this.current?.stream(...args); } From 78f590a8a25551fe62b4e5b78d3976e43610e2e5 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:26:46 +0530 Subject: [PATCH 133/203] fix: log subscribeRoom failures instead of swallowing them --- app/lib/services/sdk.test.ts | 17 +++++++++++++++++ app/lib/services/sdk.ts | 2 ++ 2 files changed, 19 insertions(+) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 02c7ece006a..544e2ed35de 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -23,6 +23,8 @@ jest.mock('../methods/userPreferences', () => ({ } })); +jest.mock('../methods/helpers/log', () => ({ __esModule: true, default: jest.fn() })); + jest.mock('@rocket.chat/ddp-client', () => ({ DDPSDK: { create: jest.fn(), @@ -828,6 +830,21 @@ describe('Sdk.subscribeRoom', () => { expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-room', 'rid-1/messagesRead'); expect(subs).toHaveLength(5); }); + + it('logs the error when subscribing throws, instead of swallowing it silently', async () => { + const log = jest.requireMock('../methods/helpers/log').default; + const fake = { + client: { + subscribe: jest.fn(() => { + throw new Error('socket not ready'); + }) + } + }; + setInternalSdk(fake); + const result = await sdk.subscribeRoom('room-1'); + expect(result).toEqual([]); + expect(log).toHaveBeenCalledWith(expect.any(Error)); + }); }); describe('Sdk.disconnect', () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 2d2f064d8cc..66e495fea05 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -5,6 +5,7 @@ import { type ClientStream, DDPSDK } from '@rocket.chat/ddp-client'; import { twoFactor } from './twoFactor'; import { store as reduxStore } from '../store/auxStore'; import { compareServerVersion, random } from '../methods/helpers'; +import log from '../methods/helpers/log'; import UserPreferences from '../methods/userPreferences'; import { headers as defaultHeaders } from '../methods/helpers/defaultHeaders'; import { BASIC_AUTH_KEY } from '../methods/helpers/fetch'; @@ -338,6 +339,7 @@ class Sdk { subscriptions.push(this.current.client.subscribe(topic, `${rid}/messagesRead`)); return Promise.resolve(subscriptions); } catch (e) { + log(e); return Promise.resolve(subscriptions); } } From 1639639ead28a9b99a0bf4921160d870a5f29291 Mon Sep 17 00:00:00 2001 From: Rohit Bansal <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:10:08 +0530 Subject: [PATCH 134/203] chore: add edge-to-edge support on Android (#7157) --- .github/actions/build-android/action.yml | 1 + .github/workflows/e2e-build-android.yml | 1 + .../tests/room/message-markdown-click.yaml | 4 +- android/app/src/main/res/values/styles.xml | 2 + android/gradle.properties | 2 +- app/containers/ActionSheet/ActionSheet.tsx | 8 +- .../ActionSheet/BottomSheetContent.tsx | 3 +- .../useActionSheetDetents.test.tsx | 9 +- .../ActionSheet/useActionSheetDetents.ts | 14 +- app/containers/EmojiPicker/EmojiCategory.tsx | 4 +- app/containers/FormContainer.tsx | 10 +- app/containers/List/ListContainer.tsx | 5 +- .../List/__snapshots__/List.test.tsx.snap | 154 +++++++++++++----- .../hooks/useEmojiKeyboard.tsx | 12 +- app/containers/markdown/Markdown.test.tsx | 4 + .../__snapshots__/Markdown.test.tsx.snap | 2 +- app/lib/hooks/withSafeAreaInsets.tsx | 15 ++ app/lib/methods/helpers/theme.ts | 4 +- app/views/AddExistingChannelView/index.tsx | 4 +- app/views/AutoTranslateView/index.tsx | 4 +- app/views/CannedResponsesListView/index.tsx | 3 + app/views/DefaultBrowserView/index.tsx | 8 +- app/views/DirectoryView/Options.tsx | 4 +- app/views/DirectoryView/index.tsx | 4 +- app/views/DirectoryView/styles.ts | 3 + app/views/DiscussionsView/index.tsx | 4 +- app/views/E2ESaveYourPasswordView.tsx | 4 +- app/views/JitsiMeetView/index.tsx | 9 +- app/views/LanguageView/index.tsx | 8 +- .../MediaAutoDownloadView/ListPicker.tsx | 4 +- app/views/MessagesView/index.tsx | 7 +- app/views/NewMessageView/index.tsx | 3 + app/views/NewServerView/index.tsx | 7 +- app/views/PickerView.tsx | 3 + app/views/ProfileView/index.tsx | 7 +- app/views/ReadReceiptView/index.tsx | 9 +- app/views/RoomInfoEditView/index.tsx | 4 +- app/views/RoomMembersView/index.tsx | 3 + app/views/RoomView/index.tsx | 15 +- .../RoomsListView/components/ServersList.tsx | 5 +- app/views/RoomsListView/index.tsx | 4 +- app/views/SearchMessagesView/index.tsx | 8 +- app/views/SelectListView.tsx | 8 +- app/views/SelectServerView.tsx | 8 +- app/views/SelectedUsersView/index.tsx | 4 +- app/views/ShareListView/index.tsx | 7 +- .../ClearAfterSheetContent.tsx | 4 +- .../DatePickerSheetContent.tsx | 4 +- app/views/StatusView/index.tsx | 3 + app/views/TeamChannelsView.tsx | 8 +- app/views/ThreadMessagesView/index.tsx | 8 +- .../ListPicker.tsx | 4 +- package.json | 2 +- pnpm-lock.yaml | 35 ++-- 54 files changed, 308 insertions(+), 175 deletions(-) create mode 100644 app/lib/hooks/withSafeAreaInsets.tsx diff --git a/.github/actions/build-android/action.yml b/.github/actions/build-android/action.yml index 3947acdc50f..86c531d52c9 100644 --- a/.github/actions/build-android/action.yml +++ b/.github/actions/build-android/action.yml @@ -53,6 +53,7 @@ runs: echo -e "reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64" >> ./gradle.properties echo -e "newArchEnabled=true" >> ./gradle.properties echo -e "hermesEnabled=true" >> ./gradle.properties + echo -e "edgeToEdgeEnabled=true" >> ./gradle.properties echo -e "VERSIONCODE=${{ inputs.BUILD_VERSION }}" >> ./gradle.properties echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties echo -e "BugsnagAPIKey=${{ inputs.BUGSNAG_KEY_OFFICIAL }}" >> ./gradle.properties diff --git a/.github/workflows/e2e-build-android.yml b/.github/workflows/e2e-build-android.yml index 02cb630fdc2..5fd11a44f80 100644 --- a/.github/workflows/e2e-build-android.yml +++ b/.github/workflows/e2e-build-android.yml @@ -106,6 +106,7 @@ jobs: echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties echo -e "newArchEnabled=true" >> ./gradle.properties echo -e "hermesEnabled=true" >> ./gradle.properties + echo -e "edgeToEdgeEnabled=true" >> ./gradle.properties echo -e "VERSIONCODE=999999" >> ./gradle.properties echo -e "KEYSTORE=release.keystore" >> ./gradle.properties echo -e 'KEYSTORE_PASSWORD=${{ secrets.KEYSTORE_OFFICIAL_PASSWORD }}' >> ./gradle.properties diff --git a/.maestro/tests/room/message-markdown-click.yaml b/.maestro/tests/room/message-markdown-click.yaml index caffeae2f2f..f0a746f2648 100644 --- a/.maestro/tests/room/message-markdown-click.yaml +++ b/.maestro/tests/room/message-markdown-click.yaml @@ -78,7 +78,7 @@ tags: platform: iOS commands: - tapOn: - point: 66%,66% + point: 66%,63% # Verify alert is shown with the link - extendedWaitUntil: @@ -106,7 +106,7 @@ tags: platform: iOS commands: - longPressOn: - point: 66%,66% + point: 66%,63% # Verify clipboard has the link alert - extendedWaitUntil: diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index a612e0f380b..74d1a1c5bd7 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -3,6 +3,7 @@ #aaaaaa false @drawable/rn_edit_text_material + false diff --git a/android/gradle.properties b/android/gradle.properties index ce8b83a17d2..48449f001cd 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -51,4 +51,4 @@ hermesEnabled=true # Use this property to enable edge-to-edge display support. # This allows your app to draw behind system bars for an immersive UI. # Note: Only works with ReactActivity and should not be used with custom Activity. -edgeToEdgeEnabled=false \ No newline at end of file +edgeToEdgeEnabled=true \ No newline at end of file diff --git a/app/containers/ActionSheet/ActionSheet.tsx b/app/containers/ActionSheet/ActionSheet.tsx index 52377a608aa..cf4719b5e7e 100644 --- a/app/containers/ActionSheet/ActionSheet.tsx +++ b/app/containers/ActionSheet/ActionSheet.tsx @@ -6,7 +6,6 @@ import { findNodeHandle, Keyboard, type LayoutChangeEvent, - Platform, useWindowDimensions, type View } from 'react-native'; @@ -35,10 +34,6 @@ const ActionSheet = memo( const [contentHeight, setContentHeight] = useState(0); const onCloseSnapshotRef = useRef(undefined); - // TrueSheet detects the bottom inset for Android 16 and iOS - // To avoid content hiding behind navigation bar on older Android versions - const isNewAndroid = isAndroid && Number(Platform.Version) >= 36; - const bottom = isIOS || isNewAndroid ? 0 : windowHeight * 0.03; const itemHeight = useActionSheetItemHeight(); const handleContentLayout = ({ nativeEvent: { layout } }: LayoutChangeEvent) => { @@ -108,7 +103,6 @@ const ActionSheet = memo( const { detents, maxHeight, scrollEnabled } = useActionSheetDetents({ windowHeight, - bottomInset: bottom, itemHeight, optionsLength: data?.options?.length || 0, snaps: effectiveSnaps, @@ -119,7 +113,7 @@ const ActionSheet = memo( const hasOptions = !!data?.options?.length; const hasSnaps = !!effectiveSnaps?.length; - const disableContentPanning = data?.enableContentPanningGesture === false || (!scrollEnabled && isAndroid); + const disableContentPanning = data?.enableContentPanningGesture === false; const isScrollable = hasOptions || (hasSnaps && !disableContentPanning); const contentMinHeight = diff --git a/app/containers/ActionSheet/BottomSheetContent.tsx b/app/containers/ActionSheet/BottomSheetContent.tsx index 8451db9cfda..ffd92507298 100644 --- a/app/containers/ActionSheet/BottomSheetContent.tsx +++ b/app/containers/ActionSheet/BottomSheetContent.tsx @@ -41,7 +41,6 @@ const BottomSheetContent = memo( const { colors } = useTheme(); const { bottom } = useSafeAreaInsets(); const height = useActionSheetItemHeight(); - const paddingBottom = isAndroid ? bottom + height : bottom; const minHeightStyle = isAndroid || !contentMinHeight ? undefined : { minHeight: contentMinHeight }; const renderFooter = () => @@ -68,7 +67,7 @@ const BottomSheetContent = memo( style={{ backgroundColor: colors.strokeExtraDark }} keyboardDismissMode='interactive' indicatorStyle='black' - contentContainerStyle={{ paddingBottom, backgroundColor: colors.surfaceLight }} + contentContainerStyle={{ paddingBottom: bottom, backgroundColor: colors.surfaceLight }} ItemSeparatorComponent={List.Separator} ListHeaderComponent={List.Separator} ListFooterComponent={renderFooter} diff --git a/app/containers/ActionSheet/useActionSheetDetents.test.tsx b/app/containers/ActionSheet/useActionSheetDetents.test.tsx index 63ddc6dddbc..2564b0cbd74 100644 --- a/app/containers/ActionSheet/useActionSheetDetents.test.tsx +++ b/app/containers/ActionSheet/useActionSheetDetents.test.tsx @@ -9,7 +9,6 @@ describe('useActionSheetDetents', () => { const { result } = renderHook(() => useActionSheetDetents({ windowHeight, - bottomInset: 0, itemHeight: 0, optionsLength: 0, snaps: [0.3, '80%', 2], @@ -26,7 +25,6 @@ describe('useActionSheetDetents', () => { const { result } = renderHook(() => useActionSheetDetents({ windowHeight, - bottomInset: 16, itemHeight: 50, optionsLength: 20, snaps: undefined, @@ -44,7 +42,6 @@ describe('useActionSheetDetents', () => { const { result } = renderHook(() => useActionSheetDetents({ windowHeight, - bottomInset: 10, itemHeight: 20, optionsLength: 3, snaps: undefined, @@ -54,14 +51,13 @@ describe('useActionSheetDetents', () => { }) ); - expect(result.current.detents).toEqual([0.108]); + expect(result.current.detents).toEqual([0.098]); }); it('computes detent from content height when there are no options', () => { const { result } = renderHook(() => useActionSheetDetents({ windowHeight, - bottomInset: 50, itemHeight: 0, optionsLength: 0, snaps: undefined, @@ -71,14 +67,13 @@ describe('useActionSheetDetents', () => { }) ); - expect(result.current.detents).toEqual([(300 + 50 + HANDLE_HEIGHT) / windowHeight]); + expect(result.current.detents).toEqual([(300 + HANDLE_HEIGHT) / windowHeight]); }); it('falls back to minimum height when no content or options', () => { const { result } = renderHook(() => useActionSheetDetents({ windowHeight, - bottomInset: 0, itemHeight: 0, optionsLength: 0, snaps: undefined, diff --git a/app/containers/ActionSheet/useActionSheetDetents.ts b/app/containers/ActionSheet/useActionSheetDetents.ts index 481919f57a7..5bc2b90abfa 100644 --- a/app/containers/ActionSheet/useActionSheetDetents.ts +++ b/app/containers/ActionSheet/useActionSheetDetents.ts @@ -23,7 +23,6 @@ function normalizeSnapsToDetents(snaps: (string | number)[]): number[] { type UseActionSheetDetentsParams = { windowHeight: number; - bottomInset: number; itemHeight: number; optionsLength?: number; snaps?: (string | number)[]; @@ -38,7 +37,6 @@ function heightToDetent(height: number, screenHeight: number): number { export function useActionSheetDetents({ windowHeight, - bottomInset, itemHeight, optionsLength = 0, snaps, @@ -51,10 +49,7 @@ export function useActionSheetDetents({ const hasOptions = optionsLength > 0; const maxSnap = hasOptions - ? Math.min( - (itemHeight + 0.5) * optionsLength + HANDLE_HEIGHT + headerHeight + bottomInset + (hasCancel ? itemHeight : 0), - maxHeight - ) + ? Math.min((itemHeight + 0.5) * optionsLength + HANDLE_HEIGHT + headerHeight + (hasCancel ? itemHeight : 0), maxHeight) : 0; let detents: SheetDetent[]; @@ -67,14 +62,13 @@ export function useActionSheetDetents({ detents = [0.5, ACTION_SHEET_MAX_HEIGHT_FRACTION]; scrollEnabled = true; } else { - const measuredHeight = - optionsLength * itemHeight + HANDLE_HEIGHT + headerHeight + bottomInset + (hasCancel ? itemHeight : 0); + const measuredHeight = optionsLength * itemHeight + HANDLE_HEIGHT + headerHeight + (hasCancel ? itemHeight : 0); scrollEnabled = false; detents = [heightToDetent(Math.round(measuredHeight), windowHeight)]; } } else if (contentHeight > 0) { - const rawContentDetent = (contentHeight + bottomInset + HANDLE_HEIGHT) / windowHeight; + const rawContentDetent = (contentHeight + HANDLE_HEIGHT) / windowHeight; const contentDetent = Math.min( ACTION_SHEET_MAX_HEIGHT_FRACTION, Math.max(ACTION_SHEET_MIN_HEIGHT_FRACTION, rawContentDetent) @@ -86,5 +80,5 @@ export function useActionSheetDetents({ } return { detents, maxHeight, scrollEnabled }; - }, [bottomInset, contentHeight, hasCancel, headerHeight, itemHeight, optionsLength, snaps, windowHeight]); + }, [contentHeight, hasCancel, headerHeight, itemHeight, optionsLength, snaps, windowHeight]); } diff --git a/app/containers/EmojiPicker/EmojiCategory.tsx b/app/containers/EmojiPicker/EmojiCategory.tsx index 54cb29ea1d2..d1e9250e8b0 100644 --- a/app/containers/EmojiPicker/EmojiCategory.tsx +++ b/app/containers/EmojiPicker/EmojiCategory.tsx @@ -1,6 +1,5 @@ import { memo, type ReactElement } from 'react'; import { FlatList } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { type ICustomEmojis, type IEmoji } from '../../definitions/IEmoji'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; @@ -55,7 +54,6 @@ const EmojiCategory = ({ bottomSheet = false }: IEmojiCategoryProps): ReactElement | null => { const items = useEmojis(category); - const { bottom } = useSafeAreaInsets(); if (!parentWidth) { return null; @@ -63,7 +61,7 @@ const EmojiCategory = ({ const numColumns = Math.trunc(parentWidth / EMOJI_BUTTON_SIZE); const marginHorizontal = (parentWidth % EMOJI_BUTTON_SIZE) / 2; - const contentPaddingBottom = bottomSheet ? Math.max(0, MIN_BOTTOM_SHEET_BREATHING_ROOM - bottom) : undefined; + const contentPaddingBottom = bottomSheet ? MIN_BOTTOM_SHEET_BREATHING_ROOM : undefined; const renderItem = ({ item }: { item: IEmoji }) => ; diff --git a/app/containers/FormContainer.tsx b/app/containers/FormContainer.tsx index 3449efafee2..df719c1b7a8 100644 --- a/app/containers/FormContainer.tsx +++ b/app/containers/FormContainer.tsx @@ -1,6 +1,7 @@ import { type ReactElement } from 'react'; import { type ScrollViewProps, StyleSheet, View } from 'react-native'; import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import sharedStyles from '../views/Styles'; import scrollPersistTaps from '../lib/methods/helpers/scrollPersistTaps'; @@ -17,7 +18,7 @@ interface IFormContainer extends ScrollViewProps { const styles = StyleSheet.create({ scrollView: { - minHeight: '100%' + flexGrow: 1 } }); @@ -27,19 +28,20 @@ export const FormContainerInner = ({ }: { children: (ReactElement | null)[]; accessibilityLabel?: string; -}) => ( +}): ReactElement => ( {children} ); -const FormContainer = ({ children, testID, showAppVersion = true, ...props }: IFormContainer) => { +const FormContainer = ({ children, testID, showAppVersion = true, ...props }: IFormContainer): ReactElement => { const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); return ( diff --git a/app/containers/List/ListContainer.tsx b/app/containers/List/ListContainer.tsx index fd007030536..4542f8be3d0 100644 --- a/app/containers/List/ListContainer.tsx +++ b/app/containers/List/ListContainer.tsx @@ -1,5 +1,6 @@ import { type ReactElement } from 'react'; import { ScrollView, type ScrollViewProps, StyleSheet } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps'; @@ -16,9 +17,11 @@ interface IListContainer extends ScrollViewProps { const ListContainer = ({ children, ...props }: IListContainer) => { 'use memo'; + const { bottom } = useSafeAreaInsets(); + return ( diff --git a/app/containers/List/__snapshots__/List.test.tsx.snap b/app/containers/List/__snapshots__/List.test.tsx.snap index ba7f78d3559..02ca3132ced 100644 --- a/app/containers/List/__snapshots__/List.test.tsx.snap +++ b/app/containers/List/__snapshots__/List.test.tsx.snap @@ -3,9 +3,14 @@ exports[`Story Snapshots: Alert should match snapshot 1`] = ` { const { bottom } = useSafeAreaInsets(); const { height } = useKeyboardAnimation(); - const initialHeight = Platform.OS === 'ios' ? bottom : 0; - const keyboardHeight = useSharedValue(initialHeight); - const previousHeight = useSharedValue(initialHeight); + const keyboardHeight = useSharedValue(bottom); + const previousHeight = useSharedValue(bottom); const updateHeight = (force: boolean = false) => { 'worklet'; @@ -93,9 +91,9 @@ export const useEmojiKeyboard = () => { ) { return; } - // On iOS, keyboard controller doesn't include bottom inset, so we add it when keyboard is closed - // On Android, keyboard controller already includes it, so we don't add it - const notch = Platform.OS === 'ios' && height.value === 0 ? bottom : 0; + // When keyboard is closed, add bottom safe area inset to ensure content is visible above + // navigation bars/home indicator. When keyboard is open, keyboard height already covers it. + const notch = height.value === 0 ? bottom : 0; keyboardHeight.value = height.value + notch; previousHeight.value = keyboardHeight.value; }; diff --git a/app/containers/markdown/Markdown.test.tsx b/app/containers/markdown/Markdown.test.tsx index 3beca437ef3..0372d0b5107 100644 --- a/app/containers/markdown/Markdown.test.tsx +++ b/app/containers/markdown/Markdown.test.tsx @@ -1,4 +1,8 @@ import { generateSnapshots } from '../../../.rnstorybook/generateSnapshots'; import * as stories from './Markdown.stories'; +beforeAll(() => { + jest.useFakeTimers({ now: new Date('2026-02-01T00:00:00.000Z').getTime() }); +}); + generateSnapshots(stories); diff --git a/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap b/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap index 2517ef4eb26..009e5615a36 100644 --- a/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap +++ b/app/containers/markdown/__snapshots__/Markdown.test.tsx.snap @@ -5882,7 +5882,7 @@ exports[`Story Snapshots: Timestamp should match snapshot 1`] = ` ] } > - 2 years ago + a year ago diff --git a/app/lib/hooks/withSafeAreaInsets.tsx b/app/lib/hooks/withSafeAreaInsets.tsx new file mode 100644 index 00000000000..3bc79af273c --- /dev/null +++ b/app/lib/hooks/withSafeAreaInsets.tsx @@ -0,0 +1,15 @@ +import { type ComponentType } from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import { withSafeAreaInsets as withSafeAreaInsetsLib, type WithSafeAreaInsetsProps } from 'react-native-safe-area-context'; + +import { type TNavigationOptions } from '../../definitions/navigationTypes'; + +export type { WithSafeAreaInsetsProps }; + +export function withSafeAreaInsets( + Component: ComponentType & TNavigationOptions +): ComponentType> & TNavigationOptions { + const WithInsets = withSafeAreaInsetsLib>(Component as any); + hoistNonReactStatics(WithInsets, Component); + return WithInsets as unknown as ComponentType> & TNavigationOptions; +} diff --git a/app/lib/methods/helpers/theme.ts b/app/lib/methods/helpers/theme.ts index 0e0f0317598..b54de5c4ba0 100644 --- a/app/lib/methods/helpers/theme.ts +++ b/app/lib/methods/helpers/theme.ts @@ -1,6 +1,6 @@ import { Appearance } from 'react-native'; -import * as NavigationBar from 'expo-navigation-bar'; import * as SystemUI from 'expo-system-ui'; +import { NavigationBar } from '@zoontek/react-native-navigation-bar'; import { type IThemePreference, type TThemeMode } from '../../../definitions/ITheme'; import { themes } from '../../constants/colors'; @@ -53,7 +53,7 @@ export const setNativeTheme = (themePreferences: IThemePreference) => { const isLightTheme = theme === 'light'; if (isAndroid) { try { - NavigationBar.setStyle(isLightTheme ? 'dark' : 'light'); + NavigationBar.setBarStyle(isLightTheme ? 'dark-content' : 'light-content'); } catch (error) { // Do nothing } diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index 4405a503b05..11546f34b28 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -3,6 +3,7 @@ import { type NativeStackNavigationOptions, type NativeStackNavigationProp } fro import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { FlatList } from 'react-native'; import { Q } from '@nozbe/watermelondb'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { textInputDebounceTime } from '../../lib/constants/debounceConfig'; import * as List from '../../containers/List'; @@ -38,6 +39,7 @@ const AddExistingChannelView = () => { const { params: { teamId } } = useRoute(); + const { bottom } = useSafeAreaInsets(); const { serverVersion, addTeamChannelPermission, moveRoomToTeamPermission } = useAppSelector(state => ({ serverVersion: state.server.version, @@ -175,7 +177,7 @@ const AddExistingChannelView = () => { ); }} ItemSeparatorComponent={List.Separator} - contentContainerStyle={{ backgroundColor: colors.surfaceRoom }} + contentContainerStyle={{ backgroundColor: colors.surfaceRoom, paddingBottom: bottom }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/AutoTranslateView/index.tsx b/app/views/AutoTranslateView/index.tsx index f8ed8f56611..4b2f410d0cf 100644 --- a/app/views/AutoTranslateView/index.tsx +++ b/app/views/AutoTranslateView/index.tsx @@ -2,6 +2,7 @@ import { useEffect, useLayoutEffect, useRef, useState, memo, type ReactElement } import { FlatList, StyleSheet, Switch } from 'react-native'; import { type Subscription } from 'rxjs'; import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as List from '../../containers/List'; import SafeAreaView from '../../containers/SafeAreaView'; @@ -24,6 +25,7 @@ const AutoTranslateView = (): ReactElement => { params: { rid, room } } = useRoute>(); const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); const [languages, setLanguages] = useState<{ language: string; name: string }[]>([]); const [selectedLanguage, setSelectedLanguage] = useState(room?.autoTranslateLanguage); @@ -131,7 +133,7 @@ const AutoTranslateView = (): ReactElement => { } ItemSeparatorComponent={List.Separator} ListFooterComponent={List.Separator} - contentContainerStyle={[List.styles.contentContainerStyleFlatList, styles.list]} + contentContainerStyle={[List.styles.contentContainerStyleFlatList, styles.list, { paddingBottom: bottom }]} /> ); diff --git a/app/views/CannedResponsesListView/index.tsx b/app/views/CannedResponsesListView/index.tsx index 51616395267..3dad6f8d3cf 100644 --- a/app/views/CannedResponsesListView/index.tsx +++ b/app/views/CannedResponsesListView/index.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'; import { FlatList } from 'react-native'; import { type RouteProp } from '@react-navigation/native'; import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import database from '../../lib/database'; import I18n from '../../i18n'; @@ -67,6 +68,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView const { theme } = useTheme(); const isMasterDetail = useMasterDetail(); + const { bottom } = useSafeAreaInsets(); const getRoomFromDb = async () => { const { rid } = route.params; @@ -271,6 +273,7 @@ const CannedResponsesListView = ({ navigation, route }: ICannedResponsesListView data={cannedResponsesScopeName} extraData={cannedResponsesScopeName} style={[styles.list, { backgroundColor: themes[theme].surfaceRoom }]} + contentContainerStyle={{ paddingBottom: bottom }} renderItem={({ item }) => ( { const [supported, setSupported] = useState([]); const navigation = useNavigation(); + const { bottom } = useSafeAreaInsets(); + + const paddingBottom = useMemo(() => Math.max(16, bottom), [bottom]); useLayoutEffect(() => { navigation.setOptions({ @@ -86,7 +90,7 @@ const DefaultBrowserView = () => { item.value} - contentContainerStyle={List.styles.contentContainerStyleFlatList} + contentContainerStyle={[List.styles.contentContainerStyleFlatList, { paddingBottom }]} renderItem={({ item }) => ( { const { colors } = useTheme(); - const insets = useSafeAreaInsets(); const renderItem = (itemType: string) => { let text = 'Users'; @@ -52,7 +50,7 @@ const DirectoryOptions = ({ }; return ( - + {renderItem('channels')} diff --git a/app/views/DirectoryView/index.tsx b/app/views/DirectoryView/index.tsx index 634b7453fad..c0e924bc325 100644 --- a/app/views/DirectoryView/index.tsx +++ b/app/views/DirectoryView/index.tsx @@ -1,6 +1,7 @@ import { useLayoutEffect, type ReactElement } from 'react'; import { FlatList, type ListRenderItem } from 'react-native'; import { shallowEqual } from 'react-redux'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type CompositeNavigationProp } from '@react-navigation/native'; @@ -36,6 +37,7 @@ interface IDirectoryViewProps { const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); const { showActionSheet, hideActionSheet } = useActionSheet(); const { isFederationEnabled, directoryDefaultView } = useAppSelector( @@ -198,7 +200,7 @@ const DirectoryView = ({ navigation }: IDirectoryViewProps): ReactElement => { item._id} renderItem={renderItem} diff --git a/app/views/DirectoryView/styles.ts b/app/views/DirectoryView/styles.ts index 588d8dda6a1..32a11d852b5 100644 --- a/app/views/DirectoryView/styles.ts +++ b/app/views/DirectoryView/styles.ts @@ -3,6 +3,9 @@ import { StyleSheet } from 'react-native'; import sharedStyles from '../Styles'; export default StyleSheet.create({ + container: { + paddingVertical: 0 + }, list: { flex: 1 }, diff --git a/app/views/DiscussionsView/index.tsx b/app/views/DiscussionsView/index.tsx index 24e72334263..fb4ad5401d2 100644 --- a/app/views/DiscussionsView/index.tsx +++ b/app/views/DiscussionsView/index.tsx @@ -2,6 +2,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { FlatList, StyleSheet } from 'react-native'; import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { textInputDebounceTime } from '../../lib/constants/debounceConfig'; import { type IMessageFromServer, type TThreadModel } from '../../definitions'; @@ -33,6 +34,7 @@ const styles = StyleSheet.create({ const DiscussionsView = () => { const navigation = useNavigation>(); const route = useRoute>(); + const { bottom } = useSafeAreaInsets(); const rid = route.params?.rid; const t = route.params?.t; @@ -176,7 +178,7 @@ const DiscussionsView = () => { renderItem={renderItem} keyExtractor={(item: any) => item._id} style={{ backgroundColor: colors.surfaceRoom }} - contentContainerStyle={styles.contentContainer} + contentContainerStyle={[styles.contentContainer, { paddingBottom: bottom }]} onEndReachedThreshold={0.5} removeClippedSubviews={isIOS} onEndReached={() => isSearching && offset.current < total.current && load()} diff --git a/app/views/E2ESaveYourPasswordView.tsx b/app/views/E2ESaveYourPasswordView.tsx index 1e46f979446..d5ea90ce809 100644 --- a/app/views/E2ESaveYourPasswordView.tsx +++ b/app/views/E2ESaveYourPasswordView.tsx @@ -4,6 +4,7 @@ import { ScrollView, StyleSheet, Text, View } from 'react-native'; import { useDispatch } from 'react-redux'; import { StackActions, useNavigation } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { encryptionSetBanner } from '../actions/encryption'; import Button from '../containers/Button'; @@ -64,6 +65,7 @@ const E2ESaveYourPasswordView = () => { const dispatch = useDispatch(); const [password, setPassword] = useState(''); const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); useLayoutEffect(() => { navigation.setOptions({ @@ -105,7 +107,7 @@ const E2ESaveYourPasswordView = () => { + contentContainerStyle={[sharedStyles.containerScrollView, { flexGrow: 1, paddingBottom: bottom }]}> {I18n.t('Save_Your_Encryption_Password_warning')} diff --git a/app/views/JitsiMeetView/index.tsx b/app/views/JitsiMeetView/index.tsx index 3f82a909040..036b39e711e 100644 --- a/app/views/JitsiMeetView/index.tsx +++ b/app/views/JitsiMeetView/index.tsx @@ -2,8 +2,9 @@ import CookieManager from '@react-native-cookies/cookies'; import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake'; import { useCallback, useEffect, useState, type ReactElement } from 'react'; -import { ActivityIndicator, Linking, SafeAreaView, StyleSheet, View } from 'react-native'; +import { ActivityIndicator, Linking, StyleSheet, View } from 'react-native'; import WebView, { type WebViewNavigation } from 'react-native-webview'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { userAgent } from '../../lib/constants/userAgent'; import { useAppSelector } from '../../lib/hooks/useAppSelector'; @@ -14,6 +15,7 @@ import { endVideoConfTimer, initVideoConfTimer } from '../../lib/methods/videoCo import { getUserSelector } from '../../selectors/login'; import { type ChatsStackParamList } from '../../stacks/types'; import JitsiAuthModal from './JitsiAuthModal'; +import SafeAreaView from '../../containers/SafeAreaView'; const JitsiMeetView = (): ReactElement => { const { @@ -22,6 +24,7 @@ const JitsiMeetView = (): ReactElement => { const { goBack } = useNavigation(); const user = useAppSelector(state => getUserSelector(state)); const serverUrl = useAppSelector(state => state.server.server); + const { bottom } = useSafeAreaInsets(); const [authModal, setAuthModal] = useState(false); const [cookiesSet, setCookiesSet] = useState(false); @@ -105,7 +108,7 @@ const JitsiMeetView = (): ReactElement => { return ( - {authModal && } + {authModal ? : null} {cookiesSet ? ( { }} onNavigationStateChange={onNavigationStateChange} onShouldStartLoadWithRequest={onNavigationStateChange} - style={styles.webviewContainer} + style={[styles.webviewContainer, { marginBottom: bottom }]} userAgent={userAgent} javaScriptEnabled domStorageEnabled diff --git a/app/views/LanguageView/index.tsx b/app/views/LanguageView/index.tsx index a196dedda43..96c46d04431 100644 --- a/app/views/LanguageView/index.tsx +++ b/app/views/LanguageView/index.tsx @@ -4,6 +4,7 @@ import RNRestart from 'react-native-restart'; import { useDispatch } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import ListRadio from '../../containers/List/ListRadio'; import { useAppSelector } from '../../lib/hooks/useAppSelector'; @@ -21,6 +22,8 @@ import log, { events, logEvent } from '../../lib/methods/helpers/log'; import { saveUserPreferences } from '../../lib/services/restApi'; const LanguageView = () => { + 'use memo'; + const { languageDefault, id } = useAppSelector(state => ({ languageDefault: getUserSelector(state).language, id: getUserSelector(state).id @@ -29,6 +32,9 @@ const LanguageView = () => { const dispatch = useDispatch(); const navigation = useNavigation>(); + const { bottom } = useSafeAreaInsets(); + + const paddingBottom = Math.max(16, bottom); useLayoutEffect(() => { navigation.setOptions({ @@ -95,7 +101,7 @@ const LanguageView = () => { keyExtractor={item => item.value} ListHeaderComponent={List.Separator} ListFooterComponent={List.Separator} - contentContainerStyle={List.styles.contentContainerStyleFlatList} + contentContainerStyle={[List.styles.contentContainerStyleFlatList, { paddingBottom }]} renderItem={({ item }) => ( { const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); - const insets = useSafeAreaInsets(); const option = OPTIONS.find(option => option.value === value) || OPTIONS[2]; const getOptions = (): ReactElement => ( - + {OPTIONS.map(i => ( diff --git a/app/views/MessagesView/index.tsx b/app/views/MessagesView/index.tsx index 5ac8fc574fd..442bf5b41de 100644 --- a/app/views/MessagesView/index.tsx +++ b/app/views/MessagesView/index.tsx @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { dequal } from 'dequal'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type CompositeNavigationProp, type RouteProp } from '@react-navigation/core'; +import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { type MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; import Message from '../../containers/message'; @@ -55,6 +56,7 @@ interface IMessagesViewProps { showActionSheet: (params: { options: string[]; hasCancel: boolean }) => void; useRealName: boolean; isMasterDetail: boolean; + insets: EdgeInsets; } interface IMessagesViewState { @@ -358,7 +360,7 @@ class MessagesView extends Component { render() { const { messages, loading } = this.state; - const { theme } = this.props; + const { theme, insets } = this.props; if (!loading && messages.length === 0) { return this.renderEmpty(); @@ -372,6 +374,7 @@ class MessagesView extends Component { style={[styles.list, { backgroundColor: themes[theme].surfaceRoom }]} keyExtractor={item => item._id} onEndReached={this.load} + contentContainerStyle={{ paddingBottom: insets.bottom }} ListFooterComponent={loading ? : null} /> @@ -386,4 +389,4 @@ const mapStateToProps = (state: IApplicationState) => ({ useRealName: state.settings.UI_Use_Real_Name }); -export default connect(mapStateToProps)(withTheme(withActionSheet(withMasterDetail(MessagesView)))); +export default connect(mapStateToProps)(withTheme(withActionSheet(withMasterDetail(withSafeAreaInsets(MessagesView))))); diff --git a/app/views/NewMessageView/index.tsx b/app/views/NewMessageView/index.tsx index f0929a2ba66..95a15220a33 100644 --- a/app/views/NewMessageView/index.tsx +++ b/app/views/NewMessageView/index.tsx @@ -4,6 +4,7 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react import { FlatList } from 'react-native'; import { shallowEqual } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import ActivityIndicator from '../../containers/ActivityIndicator'; import * as HeaderButton from '../../containers/Header/components/HeaderButton'; @@ -38,6 +39,7 @@ const NewMessageView = () => { const { colors } = useTheme(); const navigation = useNavigation>(); + const { bottom } = useSafeAreaInsets(); const { maxUsers, useRealName } = useAppSelector( state => ({ @@ -132,6 +134,7 @@ const NewMessageView = () => { ItemSeparatorComponent={List.Separator} ListFooterComponent={searching ? () => : List.Separator} style={{ backgroundColor: colors.surfaceTint }} + contentContainerStyle={{ paddingBottom: bottom }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/NewServerView/index.tsx b/app/views/NewServerView/index.tsx index 4e3565fe53b..a86ebc86675 100644 --- a/app/views/NewServerView/index.tsx +++ b/app/views/NewServerView/index.tsx @@ -53,7 +53,6 @@ const NewServerView = () => { const { submit } = useConnectServer({ workspaceUrl, certificate, previousServer }); const phoneMarginTop = previousServer ? 32 : 84; const marginTop = isTablet ? 0 : phoneMarginTop; - const formContainerStyle = previousServer ? { paddingBottom: 100 } : {}; const onChangeText = (text: string) => { setValue('workspaceUrl', text); @@ -148,11 +147,7 @@ const NewServerView = () => { }, [connecting, previousServer]); return ( - + { const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); const [data, setData] = useState(paramData); const [total, setTotal] = useState(paramTotal ?? 0); const [searchText, setSearchText] = useState(''); @@ -106,6 +108,7 @@ const PickerView = (): ReactElement => { onEndReachedThreshold={0.5} ItemSeparatorComponent={List.Separator} ListHeaderComponent={} + contentContainerStyle={{ paddingBottom: bottom }} ListFooterComponent={List.Separator} ListEmptyComponent={() => ( {I18n.t('No_results_found')} diff --git a/app/views/ProfileView/index.tsx b/app/views/ProfileView/index.tsx index 6b4366d58da..451fe06adc9 100644 --- a/app/views/ProfileView/index.tsx +++ b/app/views/ProfileView/index.tsx @@ -2,6 +2,7 @@ import { type NativeStackNavigationOptions, type NativeStackNavigationProp } fro import { type ReactElement, useCallback, useLayoutEffect, useRef, useState } from 'react'; import { Keyboard, ScrollView, View, type TextInput } from 'react-native'; import { useDispatch } from 'react-redux'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as yup from 'yup'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; @@ -58,6 +59,7 @@ const ProfileView = ({ navigation }: IProfileViewProps): ReactElement => { const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); const dispatch = useDispatch(); const { Accounts_AllowDeleteOwnAccount, @@ -292,7 +294,10 @@ const ProfileView = ({ navigation }: IProfileViewProps): ReactElement => { diff --git a/app/views/ReadReceiptView/index.tsx b/app/views/ReadReceiptView/index.tsx index 19544f1d206..b6a4115d1f6 100644 --- a/app/views/ReadReceiptView/index.tsx +++ b/app/views/ReadReceiptView/index.tsx @@ -3,8 +3,10 @@ import { dequal } from 'dequal'; import { connect } from 'react-redux'; import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { type RouteProp } from '@react-navigation/core'; +import { type EdgeInsets } from 'react-native-safe-area-context'; import { Component } from 'react'; +import { withSafeAreaInsets } from '../../lib/hooks/withSafeAreaInsets'; import dayjs from '../../lib/dayjs'; import * as List from '../../containers/List'; import Avatar from '../../containers/Avatar'; @@ -32,6 +34,7 @@ interface INavigationOption { interface IReadReceiptViewProps extends INavigationOption { Message_TimeAndDateFormat: string; theme: TSupportedThemes; + insets: EdgeInsets; } class ReadReceiptView extends Component { @@ -138,7 +141,7 @@ class ReadReceiptView extends Component @@ -147,7 +150,7 @@ class ReadReceiptView extends Component ({ Message_TimeAndDateFormat: state.settings.Message_TimeAndDateFormat as string }); -export default connect(mapStateToProps)(withTheme(ReadReceiptView)); +export default connect(mapStateToProps)(withTheme(withSafeAreaInsets(ReadReceiptView))); diff --git a/app/views/RoomInfoEditView/index.tsx b/app/views/RoomInfoEditView/index.tsx index 7fbc27dfa7f..b09642bab89 100644 --- a/app/views/RoomInfoEditView/index.tsx +++ b/app/views/RoomInfoEditView/index.tsx @@ -2,6 +2,7 @@ import { useLayoutEffect, useState } from 'react'; import { BlockContext } from '@rocket.chat/ui-kit'; import { dequal } from 'dequal'; import { AccessibilityInfo, Alert, Keyboard, ScrollView, Text, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useForm } from 'react-hook-form'; import { type SetValueConfig } from 'react-hook-form'; import * as yup from 'yup'; @@ -51,6 +52,7 @@ const schema = yup.object().shape({ const RoomInfoEditView = ({ navigation, route }: IRoomInfoEditViewProps) => { const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); const { encryptionEnabled, serverVersion } = useAppSelector(state => ({ serverVersion: state.server.version as string, encryptionEnabled: state.encryption.enabled @@ -336,7 +338,7 @@ const RoomInfoEditView = ({ navigation, route }: IRoomInfoEditViewProps) => { diff --git a/app/views/RoomMembersView/index.tsx b/app/views/RoomMembersView/index.tsx index 506495d3186..f448ed61cc7 100644 --- a/app/views/RoomMembersView/index.tsx +++ b/app/views/RoomMembersView/index.tsx @@ -2,6 +2,7 @@ import { type NavigationProp, type RouteProp, useNavigation, useRoute } from '@r import { type ReactElement, useCallback, useEffect, useReducer, useRef } from 'react'; import { FlatList, Text, View } from 'react-native'; import { shallowEqual } from 'react-redux'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { type TActionSheetOptionsItem, useActionSheet } from '../../containers/ActionSheet'; import { sendLoadingEvent } from '../../containers/Loading'; @@ -75,6 +76,7 @@ const RoomMembersView = (): ReactElement => { const { params } = useRoute>(); const navigation = useNavigation>(); + const { bottom } = useSafeAreaInsets(); const latestSearchRequest = useRef(0); @@ -434,6 +436,7 @@ const RoomMembersView = (): ReactElement => { )} style={styles.list} + contentContainerStyle={{ paddingBottom: bottom }} keyExtractor={item => item._id} ItemSeparatorComponent={List.Separator} ListHeaderComponent={ diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx index f2b3c882a1d..f01b818c42c 100644 --- a/app/views/RoomView/index.tsx +++ b/app/views/RoomView/index.tsx @@ -1526,14 +1526,15 @@ class RoomView extends Component { renderFooter = () => { const { joined, room, readOnly, loading } = this.state; - const { theme, airGappedRestrictionRemainingDays } = this.props; + const { theme, insets, airGappedRestrictionRemainingDays } = this.props; + const footerBottomInset = { paddingBottom: insets.bottom }; if (!this.rid) { return null; } if ('onHold' in room && room.onHold) { return ( - + {I18n.t('Chat_is_on_hold')} { } if (!joined) { return ( - + {I18n.t('You_are_in_preview_mode')} { } if (airGappedRestrictionRemainingDays !== undefined && airGappedRestrictionRemainingDays === 0) { return ( - + {I18n.t('AirGapped_workspace_read_only_title')} @@ -1575,14 +1576,14 @@ class RoomView extends Component { } if (readOnly) { return ( - + {I18n.t('This_room_is_read_only')} ); } if ('id' in room && isBlocked(room)) { return ( - + {I18n.t('This_room_is_blocked')} ); @@ -1593,7 +1594,7 @@ class RoomView extends Component { if (description) { return ( - + {description} ); diff --git a/app/views/RoomsListView/components/ServersList.tsx b/app/views/RoomsListView/components/ServersList.tsx index 1a15a4489dd..1eecc635086 100644 --- a/app/views/RoomsListView/components/ServersList.tsx +++ b/app/views/RoomsListView/components/ServersList.tsx @@ -1,6 +1,5 @@ import { memo, useLayoutEffect, useRef, useState } from 'react'; import { FlatList, Text, View } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { batch, useDispatch } from 'react-redux'; import { type Subscription } from 'rxjs'; @@ -38,7 +37,6 @@ const ServersList = () => { const server = useAppSelector(state => state.server.server); const isMasterDetail = useMasterDetail(); const { colors } = useTheme(); - const insets = useSafeAreaInsets(); useLayoutEffect(() => { const init = () => { @@ -125,8 +123,7 @@ const ServersList = () => { diff --git a/app/views/RoomsListView/index.tsx b/app/views/RoomsListView/index.tsx index 5c3e9c7f6e0..1873759c72b 100644 --- a/app/views/RoomsListView/index.tsx +++ b/app/views/RoomsListView/index.tsx @@ -1,7 +1,7 @@ import { useNavigation } from '@react-navigation/native'; import { memo, useContext, useEffect } from 'react'; import { BackHandler, FlatList, RefreshControl } from 'react-native'; -import { useSafeAreaFrame } from 'react-native-safe-area-context'; +import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context'; import { shallowEqual } from 'react-redux'; import ActivityIndicator from '../../containers/ActivityIndicator'; @@ -45,6 +45,7 @@ const RoomsListView = memo(function RoomsListView() { const isMasterDetail = useMasterDetail(); const navigation = useNavigation(); const { width } = useSafeAreaFrame(); + const { bottom } = useSafeAreaInsets(); const getItemLayout = useGetItemLayout(); const { subscriptions, loading } = useSubscriptions(); const subscribedRoom = useAppSelector(state => state.room.subscribedRoom); @@ -132,6 +133,7 @@ const RoomsListView = memo(function RoomsListView() { extraData={searchEnabled ? searchResults : subscriptions} keyExtractor={item => `${item.rid}-${searchEnabled}`} style={[styles.list, { backgroundColor: colors.surfaceRoom }]} + contentContainerStyle={{ paddingBottom: bottom }} renderItem={renderItem} ListHeaderComponent={ListHeader} ListFooterComponent={searching ? () => : undefined} diff --git a/app/views/SearchMessagesView/index.tsx b/app/views/SearchMessagesView/index.tsx index e931573cb7f..4f58b84e1df 100644 --- a/app/views/SearchMessagesView/index.tsx +++ b/app/views/SearchMessagesView/index.tsx @@ -4,8 +4,10 @@ import { FlatList, Text, View } from 'react-native'; import { Q } from '@nozbe/watermelondb'; import { connect } from 'react-redux'; import { dequal } from 'dequal'; +import { type EdgeInsets } from 'react-native-safe-area-context'; import { Component } from 'react'; +import { withSafeAreaInsets } from '../../lib/hooks/withSafeAreaInsets'; import { FormTextInput } from '../../containers/TextInput'; import ActivityIndicator from '../../containers/ActivityIndicator'; import Markdown from '../../containers/markdown'; @@ -78,6 +80,7 @@ interface ISearchMessagesViewProps extends INavigationOption { theme: TSupportedThemes; useRealName: boolean; isMasterDetail: boolean; + insets: EdgeInsets; } class SearchMessagesView extends Component { private offset: number; @@ -307,7 +310,7 @@ class SearchMessagesView extends Component { const { messages, loading, searchText } = this.state; - const { theme } = this.props; + const { theme, insets } = this.props; if (!loading && messages.length === 0 && searchText.length) { return this.renderEmpty(); @@ -318,6 +321,7 @@ class SearchMessagesView extends Component item._id} onEndReached={this.onEndReached} ListFooterComponent={loading ? : null} @@ -357,4 +361,4 @@ const mapStateToProps = (state: any) => ({ customEmojis: state.customEmojis }); -export default connect(mapStateToProps)(withTheme(withMasterDetail(SearchMessagesView))); +export default connect(mapStateToProps)(withTheme(withMasterDetail(withSafeAreaInsets(SearchMessagesView)))); diff --git a/app/views/SelectListView.tsx b/app/views/SelectListView.tsx index 44c259e1f4c..39f193bbfca 100644 --- a/app/views/SelectListView.tsx +++ b/app/views/SelectListView.tsx @@ -1,6 +1,7 @@ import { type NativeStackNavigationOptions, type NativeStackNavigationProp } from '@react-navigation/native-stack'; import { FlatList, StyleSheet, Text, View } from 'react-native'; import { type RouteProp } from '@react-navigation/native'; +import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { Component } from 'react'; import { type ChatsStackParamList } from '../stacks/types'; @@ -38,6 +39,7 @@ interface ISelectListViewProps { route: RouteProp; theme: TSupportedThemes; isMasterDetail: boolean; + insets: EdgeInsets; } class SelectListView extends Component { @@ -190,7 +192,7 @@ class SelectListView extends Component item.rid} renderItem={this.renderItem} ListHeaderComponent={this.isSearch ? this.renderSearch : this.renderInfoText} - contentContainerStyle={{ backgroundColor: themes[theme].surfaceRoom }} + contentContainerStyle={{ backgroundColor: themes[theme].surfaceRoom, paddingBottom: insets.bottom }} keyboardShouldPersistTaps='always' /> @@ -207,4 +209,4 @@ class SelectListView extends Component item.id; const SelectServerView = () => { + 'use memo'; + const [servers, setServers] = useState([]); const dispatch = useDispatch(); + const { bottom } = useSafeAreaInsets(); const server = useAppSelector(state => state.server.server); const navigation = useNavigation>(); const { fontScale } = useResponsiveLayout(); + const paddingBottom = Math.max(16, bottom); + const getItemLayout = useCallback( (_data: any, index: number) => { const height = PixelRatio.roundToNearestPixel(ROW_HEIGHT * fontScale); @@ -67,7 +73,7 @@ const SelectServerView = () => { keyExtractor={keyExtractor} getItemLayout={getItemLayout} ItemSeparatorComponent={List.Separator} - contentContainerStyle={List.styles.contentContainerStyleFlatList} + contentContainerStyle={[List.styles.contentContainerStyleFlatList, { paddingBottom }]} ListHeaderComponent={List.Separator} ListFooterComponent={List.Separator} removeClippedSubviews diff --git a/app/views/SelectedUsersView/index.tsx b/app/views/SelectedUsersView/index.tsx index 4cdd3fd4a7d..9ffcedcf323 100644 --- a/app/views/SelectedUsersView/index.tsx +++ b/app/views/SelectedUsersView/index.tsx @@ -6,6 +6,7 @@ import { shallowEqual, useDispatch } from 'react-redux'; import { type Subscription } from 'rxjs'; import { type RouteProp, useNavigation, useRoute } from '@react-navigation/native'; import { type NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { addUser, removeUser, reset } from '../../actions/selectedUsers'; import ActivityIndicator from '../../containers/ActivityIndicator'; @@ -47,6 +48,7 @@ const SelectedUsersView = () => { const { colors } = useTheme(); const dispatch = useDispatch(); + const { bottom } = useSafeAreaInsets(); const { users, loading, useRealName, user } = useAppSelector( state => ({ @@ -202,7 +204,7 @@ const SelectedUsersView = () => { ItemSeparatorComponent={List.Separator} ListFooterComponent={searching ? : } ListHeaderComponent={

} - contentContainerStyle={{ backgroundColor: colors.surfaceRoom }} + contentContainerStyle={{ backgroundColor: colors.surfaceRoom, paddingBottom: bottom }} keyboardShouldPersistTaps='always' /> diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx index e159da28701..effcd3e6ebb 100644 --- a/app/views/ShareListView/index.tsx +++ b/app/views/ShareListView/index.tsx @@ -6,6 +6,7 @@ import { connect } from 'react-redux'; import * as mime from 'react-native-mime-types'; import { dequal } from 'dequal'; import { Q } from '@nozbe/watermelondb'; +import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { Component } from 'react'; import database from '../../lib/database'; @@ -61,6 +62,7 @@ interface IShareListViewProps extends INavigationOption { airGappedRestrictionRemainingDays: number | undefined; shareExtensionParams: Record; dispatch: Dispatch; + insets: EdgeInsets; } const getItemLayout = (_data: any, index: number) => { @@ -448,7 +450,7 @@ class ShareListView extends Component { render = () => { const { chats, loading, searchResults, searching, serversCount } = this.state; - const { theme } = this.props; + const { theme, insets } = this.props; if (loading) { return ; @@ -489,6 +491,7 @@ class ShareListView extends Component { data={searching ? searchResults : chats} keyExtractor={keyExtractor} style={[styles.flatlist, { backgroundColor: themes[theme].surfaceHover }]} + contentContainerStyle={{ paddingBottom: insets.bottom }} renderItem={this.renderItem} getItemLayout={getItemLayout} ItemSeparatorComponent={List.Separator} @@ -516,4 +519,4 @@ const mapStateToProps = ({ login, server, share, settings }: IApplicationState) : undefined }); -export default connect(mapStateToProps)(withTheme(ShareListView)); +export default connect(mapStateToProps)(withTheme(withSafeAreaInsets(ShareListView))); diff --git a/app/views/StatusView/ClearAfterPicker/ClearAfterSheetContent.tsx b/app/views/StatusView/ClearAfterPicker/ClearAfterSheetContent.tsx index 5da52fb7cdc..d3a95c57c8b 100644 --- a/app/views/StatusView/ClearAfterPicker/ClearAfterSheetContent.tsx +++ b/app/views/StatusView/ClearAfterPicker/ClearAfterSheetContent.tsx @@ -1,7 +1,6 @@ import { DateTimePickerAndroid } from '@react-native-community/datetimepicker'; import { useState, Fragment, type ReactElement } from 'react'; import { View } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useActionSheet } from '../../../containers/ActionSheet'; import * as List from '../../../containers/List'; @@ -23,7 +22,6 @@ const ClearAfterSheetContent = ({ initialValue, initialDate, onConfirm }: IClear const [pendingValue, setPendingValue] = useState(initialValue); const { colors } = useTheme(); const { showActionSheet, hideActionSheet } = useActionSheet(); - const insets = useSafeAreaInsets(); const pendingDate = initialDate ?? new Date(); const openAndroidDateTimePicker = (baseDate: Date) => { @@ -69,7 +67,7 @@ const ClearAfterSheetContent = ({ initialValue, initialDate, onConfirm }: IClear }; return ( - + {CLEAR_AFTER_OPTIONS.map(option => ( diff --git a/app/views/StatusView/ClearAfterPicker/DatePickerSheetContent.tsx b/app/views/StatusView/ClearAfterPicker/DatePickerSheetContent.tsx index 7c8890f5188..9651e8681ff 100644 --- a/app/views/StatusView/ClearAfterPicker/DatePickerSheetContent.tsx +++ b/app/views/StatusView/ClearAfterPicker/DatePickerSheetContent.tsx @@ -1,7 +1,6 @@ import DateTimePicker from '@react-native-community/datetimepicker'; import { useState, type ReactElement } from 'react'; import { View } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useActionSheet } from '../../../containers/ActionSheet'; import Button from '../../../containers/Button'; @@ -20,10 +19,9 @@ const DatePickerSheetContent = ({ initialDate, onConfirm }: IDatePickerSheetCont const [pendingDate, setPendingDate] = useState(initialDate); const { colors } = useTheme(); const { hideActionSheet } = useActionSheet(); - const insets = useSafeAreaInsets(); return ( - + { const dispatch = useDispatch(); const { setOptions, goBack } = useNavigation(); const { colors } = useTheme(); + const { bottom } = useSafeAreaInsets(); const submit = async () => { const { status } = inputValues; @@ -244,6 +246,7 @@ const StatusView = (): ReactElement => { /> } style={{ backgroundColor: colors.surfaceTint }} + contentContainerStyle={{ paddingBottom: bottom }} /> ); diff --git a/app/views/TeamChannelsView.tsx b/app/views/TeamChannelsView.tsx index a74ea7b3c15..43fbdd2f148 100644 --- a/app/views/TeamChannelsView.tsx +++ b/app/views/TeamChannelsView.tsx @@ -2,6 +2,7 @@ import { Q } from '@nozbe/watermelondb'; import { type NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { Alert, FlatList, Keyboard, PixelRatio } from 'react-native'; import { connect } from 'react-redux'; +import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { Component } from 'react'; import { deleteRoom } from '../actions/room'; @@ -87,6 +88,7 @@ interface ITeamChannelsViewProps extends IBaseScreen void; showAvatar: boolean; displayMode: DisplayMode; + insets: EdgeInsets; } class TeamChannelsView extends Component { private teamId: string; @@ -549,6 +551,7 @@ class TeamChannelsView extends Component { const { loading, data, search, isSearching, searchText } = this.state; + const { insets } = this.props; if (loading) { return ; } @@ -570,6 +573,7 @@ class TeamChannelsView extends Component this.load()} onEndReachedThreshold={0.5} + contentContainerStyle={{ paddingBottom: insets.bottom }} ListFooterComponent={this.renderFooter} /> ); @@ -601,4 +605,6 @@ const mapStateToProps = (state: IApplicationState) => ({ displayMode: state.sortPreferences.displayMode }); -export default connect(mapStateToProps)(withDimensions(withTheme(withActionSheet(withMasterDetail(TeamChannelsView))))); +export default connect(mapStateToProps)( + withDimensions(withTheme(withActionSheet(withMasterDetail(withSafeAreaInsets(TeamChannelsView))))) +); diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 80f05939ea8..50f838344dd 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -4,6 +4,7 @@ import { Q } from '@nozbe/watermelondb'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { type NativeStackNavigationOptions } from '@react-navigation/native-stack'; import { type Observable, type Subscription } from 'rxjs'; +import { type EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context'; import { Component } from 'react'; import { showActionSheetRef } from '../../containers/ActionSheet'; @@ -66,6 +67,7 @@ interface IThreadMessagesViewProps extends IBaseScreen { @@ -491,7 +493,7 @@ class ThreadMessagesView extends Component { const { loading, messages, displayingThreads, currentFilter } = this.state; - const { theme } = this.props; + const { theme, insets } = this.props; if (!messages?.length || !displayingThreads?.length) { let text; if (currentFilter === Filter.Following) { @@ -510,7 +512,7 @@ class ThreadMessagesView extends Component ({ useRealName: state.settings.UI_Use_Real_Name as boolean }); -export default connect(mapStateToProps)(withTheme(withMasterDetail(ThreadMessagesView))); +export default connect(mapStateToProps)(withTheme(withMasterDetail(withSafeAreaInsets(ThreadMessagesView)))); diff --git a/app/views/UserNotificationPreferencesView/ListPicker.tsx b/app/views/UserNotificationPreferencesView/ListPicker.tsx index 815b22666bb..ec766ce7a99 100644 --- a/app/views/UserNotificationPreferencesView/ListPicker.tsx +++ b/app/views/UserNotificationPreferencesView/ListPicker.tsx @@ -1,5 +1,4 @@ import { StyleSheet, Text, View } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Fragment, type ReactElement } from 'react'; import * as List from '../../containers/List'; @@ -37,10 +36,9 @@ const ListPicker = ({ const { showActionSheet, hideActionSheet } = useActionSheet(); const { colors } = useTheme(); const option = value ? OPTIONS[preference].find(option => option.value === value) : OPTIONS[preference][0]; - const insets = useSafeAreaInsets(); const getOptions = (): ReactElement => ( - + {OPTIONS[preference].map(i => ( diff --git a/package.json b/package.json index 37b8c598ebd..cb488c56114 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto#main", "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", "@rocket.chat/ui-kit": "^0.39.0", + "@zoontek/react-native-navigation-bar": "^1.1.1", "axios": "0.30.3", "bytebuffer": "~5.0.1", "color2k": "1.2.4", @@ -70,7 +71,6 @@ "expo-image": "~3.0.11", "expo-keep-awake": "~15.0.8", "expo-local-authentication": "~17.0.8", - "expo-navigation-bar": "~5.0.10", "expo-notifications": "~0.32.15", "expo-status-bar": "~3.0.9", "expo-system-ui": "~6.0.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55b107cf318..eafb0f810ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: '@rocket.chat/ui-kit': specifier: ^0.39.0 version: 0.39.0(@rocket.chat/icons@0.47.0)(@types/node@25.0.3)(typescript@5.9.3) + '@zoontek/react-native-navigation-bar': + specifier: ^1.1.1 + version: 1.1.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) axios: specifier: 0.30.3 version: 0.30.3 @@ -150,9 +153,6 @@ importers: expo-local-authentication: specifier: ~17.0.8 version: 17.0.8(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) - expo-navigation-bar: - specifier: ~5.0.10 - version: 5.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-notifications: specifier: ~0.32.15 version: 0.32.15(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -3070,6 +3070,12 @@ packages: '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + '@zoontek/react-native-navigation-bar@1.1.2': + resolution: {integrity: sha512-ZEH516aS7Gvo6JDnpEBmH5geECnnzCCbh9jOGovywCjwMeOJxSlbcjkq3UFBWyqCeeQFsxTblhoszG/0nP0Wpw==} + peerDependencies: + react: '*' + react-native: '*' + abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} deprecated: Use your platform's native atob() and btoa() methods instead @@ -4481,13 +4487,6 @@ packages: react: '*' react-native: '*' - expo-navigation-bar@5.0.10: - resolution: {integrity: sha512-r9rdLw8mY6GPMQmVVOY/r1NBBw74DZefXHF60HxhRsdNI2kjc1wLdfWfR2rk4JVdOvdMDujnGrc9HQmqM3n8Jg==} - peerDependencies: - expo: '*' - react: '*' - react-native: '*' - expo-notifications@0.32.15: resolution: {integrity: sha512-gnJcauheC2S0Wl0RuJaFkaBRVzCG011j5hlG0TEbsuOCPBuB/F30YEk8yurK8Psv+zHkVfeiJ5AC+nL0LWk0WA==} peerDependencies: @@ -11165,6 +11164,11 @@ snapshots: '@yarnpkg/lockfile@1.1.0': {} + '@zoontek/react-native-navigation-bar@1.1.2(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + dependencies: + react: 19.1.0 + react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) + abab@2.0.6: {} abort-controller@3.0.0: @@ -12741,17 +12745,6 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - expo-navigation-bar@5.0.10(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): - dependencies: - '@react-native/normalize-colors': 0.81.5 - debug: 4.4.3 - expo: 54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-native: 0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - transitivePeerDependencies: - - supports-color - expo-notifications@0.32.15(expo@54.0.30(@babel/core@7.25.9)(react-native-webview@13.16.1(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@expo/image-utils': 0.8.8 From d6427cd41293cde6ae22b4fbb7de932deeb45e2a Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:17:15 +0530 Subject: [PATCH 135/203] fix: catch inquiry subscription rejection instead of letting it go unhandled --- app/ee/omnichannel/lib/index.test.ts | 37 +++++++++++++++++++ app/ee/omnichannel/lib/index.ts | 7 +++- .../lib/subscriptions/inquiry.test.ts | 2 +- .../omnichannel/lib/subscriptions/inquiry.ts | 2 +- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/ee/omnichannel/lib/index.test.ts b/app/ee/omnichannel/lib/index.test.ts index 756bd1079c9..d19b692e3bc 100644 --- a/app/ee/omnichannel/lib/index.test.ts +++ b/app/ee/omnichannel/lib/index.test.ts @@ -45,13 +45,24 @@ jest.mock('../../../lib/services/restApi', () => ({ getAgentDepartments: jest.fn().mockResolvedValue({ success: true, departments: [] }) })); +jest.mock('../../../lib/methods/helpers/log', () => ({ + __esModule: true, + default: jest.fn() +})); + import sdk from '../../../lib/services/sdk'; import { store } from '../../../lib/store/auxStore'; import { compareServerVersion } from '../../../lib/methods/helpers'; +import EventEmitter from '../../../lib/methods/helpers/events'; +import subscribeInquiry from './subscriptions/inquiry'; +import log from '../../../lib/methods/helpers/log'; const mockSdkPost = sdk.post as jest.Mock; const mockMethodCallWrapper = sdk.methodCallWrapper as jest.Mock; const mockCompareServerVersion = compareServerVersion as jest.Mock; +const mockAddEventListener = EventEmitter.addEventListener as jest.Mock; +const mockSubscribeInquiry = subscribeInquiry as jest.Mock; +const mockLog = log as jest.Mock; beforeEach(() => { jest.clearAllMocks(); @@ -145,3 +156,29 @@ describe('isOmnichannelStatusAvailable', () => { expect(isOmnichannelStatusAvailable!(undefined)).toBe(false); }); }); + +describe('Omnichannel.subscribeInquiry', () => { + it('does not produce an unhandled rejection when subscribeInquiry() rejects', async () => { + const rejection = new Error('inquiry: @subscribeInquiry user.id not found'); + mockSubscribeInquiry.mockRejectedValueOnce(rejection); + + jest.isolateModules(() => { + require('./index'); + }); + + const subscribeListener = mockAddEventListener.mock.calls.find(([event]) => event === 'INQUIRY_SUBSCRIBE')?.[1]; + expect(subscribeListener).toBeDefined(); + + const unhandled = jest.fn(); + process.on('unhandledRejection', unhandled); + + subscribeListener(); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + + process.off('unhandledRejection', unhandled); + expect(unhandled).not.toHaveBeenCalled(); + expect(mockLog).toHaveBeenCalledWith(rejection); + }); +}); diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 4fc1a82dcb2..66a87415a5f 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -3,6 +3,7 @@ import { compareServerVersion } from '../../../lib/methods/helpers'; import EventEmitter from '../../../lib/methods/helpers/events'; import { store as reduxStore } from '../../../lib/store/auxStore'; import subscribeInquiry from './subscriptions/inquiry'; +import log from '../../../lib/methods/helpers/log'; export const isOmnichannelStatusAvailable = (statusLivechat: string | undefined): boolean => statusLivechat === 'available'; @@ -55,7 +56,11 @@ class Omnichannel { subscribeInquiry = async () => { console.log('[RCRN] Subscribing to inquiry'); - this.inquirySub = await subscribeInquiry(); + try { + this.inquirySub = await subscribeInquiry(); + } catch (e) { + log(e); + } }; unsubscribeInquiry = () => { diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts index 44f1d23e535..d4429b47869 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts @@ -132,7 +132,7 @@ describe('subscribeInquiry', () => { login: { user: {} }, inquiry: { queued: [] } }); - await expect(subscribeInquiry() as any).rejects.toBeUndefined(); + await expect(subscribeInquiry() as any).rejects.toThrow('inquiry: @subscribeInquiry user.id not found'); }); it('queue handler dispatches inquiryQueueAdd for new queued items', () => { diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index 124d54931d4..abc37480495 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -115,6 +115,6 @@ export default function subscribeInquiry() { }; } catch (e) { log(e); - return Promise.reject(); + return Promise.reject(e instanceof Error ? e : new Error('subscribeInquiry failed')); } } From 44276751f8a129ee11349e611099c29a65394b35 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:42:52 +0530 Subject: [PATCH 136/203] fix: stop double-nesting stream-user-presence subscribe params ClientStream.subscribe(name, ...params) spreads its arguments into the DDP call; passing a single array wrapped the payload one level too deep, so the server silently ignored the subscription. --- app/lib/methods/getUsersPresence.test.ts | 9 ++++++++- app/lib/methods/getUsersPresence.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/lib/methods/getUsersPresence.test.ts b/app/lib/methods/getUsersPresence.test.ts index 3fcf1611b14..943ee9e4ac3 100644 --- a/app/lib/methods/getUsersPresence.test.ts +++ b/app/lib/methods/getUsersPresence.test.ts @@ -190,7 +190,14 @@ describe('getUsersPresence', () => { setVersion('4.1.0', { settings: {} }); (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: [] }); await getUsersPresence(['uid1']); - expect(sdk.subscribe).toHaveBeenCalledWith('stream-user-presence', ['', { added: ['uid1'] }]); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-user-presence', '', { added: ['uid1'] }); + }); + + it('spreads the subscribe params instead of nesting them in one array', async () => { + setVersion('4.1.0', { settings: {} }); + (sdk.get as jest.Mock).mockResolvedValue({ success: true, users: [] }); + await getUsersPresence(['user-1', 'user-2']); + expect(sdk.subscribe).toHaveBeenCalledWith('stream-user-presence', '', { added: ['user-1', 'user-2'] }); }); it('dispatches setActiveUsers with user presence data', async () => { diff --git a/app/lib/methods/getUsersPresence.ts b/app/lib/methods/getUsersPresence.ts index 9db8273a2ec..cf6d36957fd 100644 --- a/app/lib/methods/getUsersPresence.ts +++ b/app/lib/methods/getUsersPresence.ts @@ -71,7 +71,7 @@ export async function getUsersPresence(usersParams: string[]) { const result = await sdk.get('/v1/users.presence', params); if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '4.1.0')) { - sdk.subscribe('stream-user-presence', ['', { added: usersParams }]); + sdk.subscribe('stream-user-presence', '', { added: usersParams }); } if (result.success) { From 21fedd6ef2654ac40d411ae6cacbd11a7044ac64 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:02:34 +0530 Subject: [PATCH 137/203] fix: check /v1/login response via status field, not success /v1/login is a special-cased Rocket.Chat endpoint that returns {status, data}, not {success, data} like other v1 endpoints. The old check meant loginResult.success was always undefined, so every real login would have rejected with 'Invalid response from server'. Casts the post() result to the documented {status, data} shape since the generic REST result types assume the success/failure convention that this endpoint doesn't follow. --- app/definitions/rest/v1/auth.ts | 10 ++++++++++ app/lib/services/sdk.test.ts | 20 ++++++++++---------- app/lib/services/sdk.ts | 13 ++++++++++--- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/app/definitions/rest/v1/auth.ts b/app/definitions/rest/v1/auth.ts index c49499f0af7..3924518c5ac 100644 --- a/app/definitions/rest/v1/auth.ts +++ b/app/definitions/rest/v1/auth.ts @@ -13,6 +13,16 @@ export interface ILoginMeResponse { roles: string[]; settings: IUserSettings; avatarUrl: string; + language?: string; + statusText?: string; + customFields?: { + [key: string]: any; + }; + statusLivechat?: string; + avatarETag?: string; + bio?: string; + nickname?: string; + requirePasswordChange?: boolean; } export interface ILoginDataResponse { diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 544e2ed35de..0b5af723e05 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -182,7 +182,7 @@ describe('Sdk.login', () => { it('calls loginWithToken with the authToken from the REST response', async () => { const fake = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } }); setInternalSdk(fake); @@ -191,8 +191,8 @@ describe('Sdk.login', () => { expect(result.authToken).toBe('tok-abc'); }); - it('rejects when the REST response has success: false', async () => { - const fake = buildFakeSdkWithLogin({ success: false }); + it('rejects when the REST response has status: error', async () => { + const fake = buildFakeSdkWithLogin({ status: 'error' }); setInternalSdk(fake); await expect(sdk.login({ user: 'john', password: 'wrong' })).rejects.toThrow('Invalid response from server'); }); @@ -208,7 +208,7 @@ describe('Sdk.login', () => { it('sets X-Auth-Token and X-User-Id in getHeaders after successful login', async () => { const fake = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } }); setInternalSdk(fake); @@ -220,7 +220,7 @@ describe('Sdk.login', () => { it('clears X-Auth-Token and X-User-Id from getHeaders after logout', async () => { const fake = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } }); setInternalSdk(fake); @@ -235,7 +235,7 @@ describe('Sdk.login', () => { // Deep link logins reach sdk.login with `{ resume }` instead of `{ user, password }`, // but the header centralization reads authToken/userId from the same /v1/login response. const fake = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-deep', userId: 'uid-deep', me: { username: 'jane' } } }); setInternalSdk(fake); @@ -255,7 +255,7 @@ describe('Sdk.login', () => { // Login on server1 → token1/user1; re-initialize for server2 → headers cleared; // login on server2 → token2/user2 (server1 credentials never leak). const server1Sdk = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-1', userId: 'uid-1', me: { username: 'user1' } } }); (DDPSDK.create as jest.Mock).mockReturnValueOnce(server1Sdk); @@ -265,7 +265,7 @@ describe('Sdk.login', () => { expect(sdk.getHeaders()['X-User-Id']).toBe('uid-1'); const server2Sdk = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-2', userId: 'uid-2', me: { username: 'user2' } } }); (DDPSDK.create as jest.Mock).mockReturnValueOnce(server2Sdk); @@ -282,7 +282,7 @@ describe('Sdk.login', () => { it('switches to the deep link server credentials when already logged in on another server', async () => { // Already authenticated on server1. const server1Sdk = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-1', userId: 'uid-1', me: { username: 'user1' } } }); (DDPSDK.create as jest.Mock).mockReturnValueOnce(server1Sdk); @@ -292,7 +292,7 @@ describe('Sdk.login', () => { // A deep link targeting server2 re-initializes on the new host, then logs in with the resume token. const server2Sdk = buildFakeSdkWithLogin({ - success: true, + status: 'success', data: { authToken: 'tok-2', userId: 'uid-2', me: { username: 'user2' } } }); (DDPSDK.create as jest.Mock).mockReturnValueOnce(server2Sdk); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 66e495fea05..ceb1ff06b8f 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -16,6 +16,7 @@ import { type PathFor, type ResultFor } from '../../definitions/rest/helpers'; +import { type ILoginDataResponse } from '../../definitions/rest/v1/auth'; export async function normalizeResponseError(response: Response): Promise<{ status: number; data: any }> { try { @@ -248,10 +249,16 @@ class Sdk { return result.twoFactorCode; } - async login(credentials: any): Promise { + async login(credentials: any): Promise { try { - const loginResult = await this.post('/v1/login', credentials); - if (!loginResult?.success) { + // /v1/login is a special-cased Rocket.Chat endpoint: it replies with { status, data } + // instead of the { success, data } convention the generic REST types assume, so the + // inferred result type doesn't match the real shape here — cast to the documented one. + const loginResult = (await this.post('/v1/login', credentials)) as unknown as { + status: string; + data: ILoginDataResponse; + }; + if (loginResult?.status !== 'success' || !loginResult.data) { return Promise.reject(new Error('Invalid response from server')); } await this.current?.account.loginWithToken(loginResult.data.authToken); From 310d9218e05aad8166951456f717c5776ef610c3 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:28:13 +0530 Subject: [PATCH 138/203] fix: stop 2FA retry headers from leaking into concurrent requests this.headers was mutated in place during a 2FA retry, so any other in-flight get/post/delete call would pick up the transient x-2fa-code/x-2fa-method meant for an unrelated request. Extracts a private postWithHeaders helper that merges the 2FA headers into a per-call object without touching the shared this.headers. The retry recurses into postWithHeaders (not post), preserving support for a retry that itself gets challenged again (nested 2FA), which existing test coverage relies on. --- app/lib/services/sdk.test.ts | 30 ++++++++++++++++++++++++++++++ app/lib/services/sdk.ts | 25 +++++++++++++++++-------- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 0b5af723e05..48dbd586e4a 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -478,6 +478,36 @@ describe('Sdk.post', () => { expect(twoFactor).toHaveBeenNthCalledWith(2, { method: 'totp', invalid: true }); expect(result).toEqual({ success: true }); }); + + it('does not leak x-2fa-code into a concurrent request while a 2FA retry is in flight', async () => { + // The retried call is held open on this gate so we can read shared headers while it is still in flight. + let releaseRetry!: (value: any) => void; + const retryGate = new Promise(resolve => { + releaseRetry = resolve; + }); + + const post = jest.fn(); + post.mockRejectedValueOnce({ data: { errorType: 'totp-required', details: { method: 'totp' } } }); + post.mockImplementationOnce(() => retryGate); + + const fake = buildFakeSdkWithRest({ rest: { post } }); + setInternalSdk(fake); + (twoFactor as jest.Mock).mockResolvedValueOnce({ twoFactorCode: '123456', twoFactorMethod: 'totp' }); + + const retryPromise = (sdk as any).post('/v1/rooms.info', {}); + + // Let the retry run up to the point where it has fired its retried network call (still pending on retryGate). + await new Promise(resolve => setTimeout(resolve, 0)); + + // A concurrent, unrelated call reads the shared headers while the 2FA retry is still in flight. + const concurrentHeaders = sdk.getHeaders(); + + releaseRetry({ success: true }); + const result = await retryPromise; + + expect(result).toEqual({ success: true }); + expect(concurrentHeaders['x-2fa-code']).toBeUndefined(); + }); }); describe('Sdk.methodCall', () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index ceb1ff06b8f..5b5efa3e8d2 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -165,12 +165,25 @@ class Sdk { > ? void : Serialized>> + ): Promise>> { + return this.postWithHeaders(endpoint, params, {}); + } + + /** + * Same as `post`, but merges `extraHeaders` into a per-call headers object instead of mutating the shared + * `this.headers`. Used for the 2FA retry so the transient `x-2fa-code`/`x-2fa-method` headers never become + * visible to any other `get`/`post`/`delete` call that might be in flight at the same time. + */ + private async postWithHeaders>( + endpoint: TPath, + params: any, + extraHeaders: Record ): Promise>> { const isMethodCall = !!endpoint?.includes('/v1/method.call'); try { const sdk = this.ensureInitialized(); // @ts-ignore - const result = await sdk.rest.post(endpoint, params, { headers: this.headers }); + const result = await sdk.rest.post(endpoint, params, { headers: { ...this.headers, ...extraHeaders } }); /** * if API_Use_REST_For_DDP_Calls is enabled and it's a method call, @@ -196,19 +209,15 @@ class Sdk { const { details } = isMethodCall ? normalized : normalized?.data; try { const totpResult = await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); - this.setHeaders({ + // Recurse (not into `post`) so a retry that itself gets challenged again keeps using + // per-call headers, without ever touching the shared `this.headers`. + return await this.postWithHeaders(endpoint, params, { 'x-2fa-code': totpResult.twoFactorCode, 'x-2fa-method': totpResult.twoFactorMethod }); - return await this.post(endpoint, params); } catch { // twoFactor was canceled return {} as ResultFor<'POST', MatchPathPattern>; - } finally { - const next = { ...this.headers }; - delete next['x-2fa-code']; - delete next['x-2fa-method']; - this.headers = next; } } else { throw normalized; From a012e16143827002d983c901a76eea2706f94493 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:46:10 +0530 Subject: [PATCH 139/203] fix: stop the previous inquiry subscription before resubscribing connect.ts now dispatches inquiryRequest() on every reconnect for livechat roles; without stopping the prior subscription first, every reconnect leaked a queueListener and department listeners. --- app/ee/omnichannel/lib/index.test.ts | 26 ++++++++++++++++++++++++++ app/ee/omnichannel/lib/index.ts | 1 + 2 files changed, 27 insertions(+) diff --git a/app/ee/omnichannel/lib/index.test.ts b/app/ee/omnichannel/lib/index.test.ts index d19b692e3bc..9be360f8eb3 100644 --- a/app/ee/omnichannel/lib/index.test.ts +++ b/app/ee/omnichannel/lib/index.test.ts @@ -181,4 +181,30 @@ describe('Omnichannel.subscribeInquiry', () => { expect(unhandled).not.toHaveBeenCalled(); expect(mockLog).toHaveBeenCalledWith(rejection); }); + + it('stops the previous inquiry subscription before creating a new one on repeated INQUIRY_SUBSCRIBE', async () => { + const firstStop = jest.fn(); + const secondStop = jest.fn(); + mockSubscribeInquiry.mockResolvedValueOnce({ stop: firstStop }).mockResolvedValueOnce({ stop: secondStop }); + + jest.isolateModules(() => { + require('./index'); + }); + + const subscribeListener = mockAddEventListener.mock.calls.find(([event]) => event === 'INQUIRY_SUBSCRIBE')?.[1]; + expect(subscribeListener).toBeDefined(); + + subscribeListener(); + await Promise.resolve(); + await Promise.resolve(); + + expect(firstStop).not.toHaveBeenCalled(); + + subscribeListener(); + await Promise.resolve(); + await Promise.resolve(); + + expect(firstStop).toHaveBeenCalledTimes(1); + expect(secondStop).not.toHaveBeenCalled(); + }); }); diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 66a87415a5f..a1ea2648302 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -56,6 +56,7 @@ class Omnichannel { subscribeInquiry = async () => { console.log('[RCRN] Subscribing to inquiry'); + this.unsubscribeInquiry(); try { this.inquirySub = await subscribeInquiry(); } catch (e) { From e2487a37ba09c1c24a4fbdea9e359912c63d5866 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:47:23 +0530 Subject: [PATCH 140/203] chore: remove vestigial user param from sendAttachments Auth is sourced from sdk.getHeaders() since the ddp-client migration; sendFileMessage stopped taking a user param but sendAttachments and its two callers still threaded one through unused. - Remove user param from sendAttachments type signature - Remove unused IUser import from sendAttachments.ts - Remove user param from MessageComposer call site - Remove user param from ShareView call site - Remove unused user variable from MessageComposer (was only used for sendAttachments) - Remove unused getUserSelector import from MessageComposer User variable is still used in ShareView for sendMessage call, so no unused variable lint errors appear. --- app/containers/MessageComposer/MessageComposer.tsx | 3 --- app/lib/methods/sendFileMessage/sendAttachments.ts | 3 +-- app/views/ShareView/index.tsx | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/containers/MessageComposer/MessageComposer.tsx b/app/containers/MessageComposer/MessageComposer.tsx index f9367890e4d..6becf9c2a19 100644 --- a/app/containers/MessageComposer/MessageComposer.tsx +++ b/app/containers/MessageComposer/MessageComposer.tsx @@ -30,7 +30,6 @@ import EmojiPicker from '../EmojiPicker'; import { MessageComposerContent } from './components/MessageComposerContent'; import { useTheme } from '../../theme'; import { useAppSelector } from '../../lib/hooks/useAppSelector'; -import { getUserSelector } from '../../selectors/login'; import { sendAttachments } from '../../lib/methods/sendFileMessage/sendAttachments'; import { useAltTextSupported } from '../../lib/hooks/useAltTextSupported'; @@ -61,7 +60,6 @@ export const MessageComposer = ({ const recordingAudio = useRecordingAudio(); const { formatShortnameToUnicode } = useShortnameToUnicode(); const { colors } = useTheme(); - const user = useAppSelector(state => getUserSelector(state)); const server = useAppSelector(state => state.server.server); const altTextSupported = useAltTextSupported(); const attachments = useComposerAttachments(); @@ -132,7 +130,6 @@ export const MessageComposer = ({ rid, tmid, server, - user: { id: user.id, token: user.token }, altTextSupported, getMsg: ({ description }, index) => (index === 0 ? description || quotedMessage || textFromInput : description) }); diff --git a/app/lib/methods/sendFileMessage/sendAttachments.ts b/app/lib/methods/sendFileMessage/sendAttachments.ts index 0bd64d6990b..2036ef773ac 100644 --- a/app/lib/methods/sendFileMessage/sendAttachments.ts +++ b/app/lib/methods/sendFileMessage/sendAttachments.ts @@ -1,4 +1,4 @@ -import { type IShareAttachment, type IUser } from '../../../definitions'; +import { type IShareAttachment } from '../../../definitions'; import { sendFileMessage } from './index'; const ROTATED_ORIENTATIONS = ['5', '6', '7', '8']; @@ -15,7 +15,6 @@ export const sendAttachments = ({ rid: string; tmid: string | undefined; server: string; - user: Partial>; altTextSupported: boolean; getMsg: (attachment: IShareAttachment, index: number) => string | undefined; }): Promise => diff --git a/app/views/ShareView/index.tsx b/app/views/ShareView/index.tsx index f6cf1436956..26de759a919 100644 --- a/app/views/ShareView/index.tsx +++ b/app/views/ShareView/index.tsx @@ -278,7 +278,6 @@ class ShareView extends Component { rid: room.rid, tmid: this.getThreadId(thread), server, - user: { id: user.id, token: user.token }, altTextSupported: isAltTextSupported, getMsg: ({ description }) => (isAltTextSupported ? description || msg : msg) }); From 07117256f8db583655846eebf385ba28d4c00632 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:47:31 +0530 Subject: [PATCH 141/203] fix: don't destroy the local upload record when mediaConfirm fails --- .../methods/sendFileMessage/sendFileMessageV2.test.ts | 10 ++++++++++ app/lib/methods/sendFileMessage/sendFileMessageV2.ts | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts index 5eb37749576..428487a383f 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.test.ts @@ -169,4 +169,14 @@ describe('sendFileMessageV2', () => { }) ); }); + + it('does not destroy the local upload record when mediaConfirm fails', async () => { + (fetch as jest.Mock).mockResolvedValueOnce({ ok: false, status: 500 }); + const { createUploadRecord } = require('./utils'); + + await expect(sendFileMessageV2('rid1', baseFile, undefined, 'https://server.com')).rejects.toThrow(); + + const [, mockUploadRecord] = await createUploadRecord.mock.results[0].value; + expect(mockUploadRecord.destroyPermanently).not.toHaveBeenCalled(); + }); }); diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts index 8fefbe75acc..4265d0b6412 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts @@ -61,7 +61,7 @@ export async function sendFileMessageV2( if (getContent) { content = await getContent(response.file._id, response.file.url); } - await fetch(`${server}/api/v1/rooms.mediaConfirm/${rid}/${response.file._id}`, { + const confirmResponse = await fetch(`${server}/api/v1/rooms.mediaConfirm/${rid}/${response.file._id}`, { method: 'POST', headers: { ...headers, @@ -75,6 +75,9 @@ export async function sendFileMessageV2( content }) }); + if (!confirmResponse.ok) { + throw new Error(`mediaConfirm failed with status ${confirmResponse.status}`); + } await db.write(async () => { await uploadRecord?.destroyPermanently(); }); From e94dea83bc13b379bf856194ec4f7ab83567721f Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:47:32 +0530 Subject: [PATCH 142/203] fix: log when Sdk.logout() times out instead of completing silently account.logout() races against a 5s timer in logout(); if the timer wins, the function previously returned normally with no signal that the server-side session may still be valid. Now logs an Error via the existing log() helper so it surfaces in crash reporting. --- app/lib/services/sdk.test.ts | 16 ++++++++++++++++ app/lib/services/sdk.ts | 9 ++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 48dbd586e4a..36103fd586d 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -308,6 +308,22 @@ describe('Sdk.login', () => { }); }); +describe('Sdk.logout', () => { + it('logs when the account.logout() call times out instead of completing', async () => { + jest.useFakeTimers(); + const log = jest.requireMock('../methods/helpers/log').default; + const fake = { account: { logout: jest.fn(() => new Promise(() => {})) } }; // never resolves + setInternalSdk(fake); + + const logoutPromise = sdk.logout(); + jest.advanceTimersByTime(5000); + await logoutPromise; + + expect(log).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining('timed out') })); + jest.useRealTimers(); + }); +}); + describe('normalizeResponseError', () => { it('parses a JSON body and returns { status, data }', async () => { const response = new Response(JSON.stringify({ error: 'not-found' }), { status: 404 }); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 5b5efa3e8d2..7201836d621 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -429,7 +429,14 @@ class Sdk { async logout(): Promise { if (this.current?.account) { - await Promise.race([this.current.account.logout(), new Promise(resolve => setTimeout(resolve, 5000))]); + const TIMEOUT = Symbol('logout-timeout'); + const result = await Promise.race([ + this.current.account.logout(), + new Promise(resolve => setTimeout(() => resolve(TIMEOUT), 5000)) + ]); + if (result === TIMEOUT) { + log(new Error('Sdk.logout(): account.logout() timed out after 5s; server session may still be valid')); + } } this.setHeaders({ 'X-Auth-Token': '', From 5ef4e801ecc8efa36b14de86ecc0aabbb5836839 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:52:59 +0530 Subject: [PATCH 143/203] test: cover the patched Connection reconnect/zombie-socket logic probe/forceReopen/checkAndReopen and the stale-onclose guard were previously only exercised through mocks; this drives the real patched ConnectionImpl class with a fake WebSocket and the package's own MinimalDDPClient. --- .../__tests__/ddpConnectionPatch.test.ts | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 app/lib/services/__tests__/ddpConnectionPatch.test.ts diff --git a/app/lib/services/__tests__/ddpConnectionPatch.test.ts b/app/lib/services/__tests__/ddpConnectionPatch.test.ts new file mode 100644 index 00000000000..617ce485309 --- /dev/null +++ b/app/lib/services/__tests__/ddpConnectionPatch.test.ts @@ -0,0 +1,159 @@ +/** + * Behavioral tests for the hand-patched `ConnectionImpl` class shipped in + * `patches/@rocket.chat+ddp-client+0.3.51.patch`. + * + * `sdk.test.ts` / `connect.test.ts` only assert delegation to a *mocked* + * `connection.probe` / `forceReopen` / `checkAndReopen` — none of the real + * patched logic runs under test. This file imports the real compiled + * `ConnectionImpl` (the deep `dist/Connection` path, since it isn't part of + * the package's public API surface) and drives it with a fake `WebSocket` and + * the package's own `MinimalDDPClient`, so a future edit to the patch (or a + * `ddp-client` version bump that silently drops it) is caught by a real + * regression rather than a mock. + * + * Patched behaviors under test: + * - stale-`onclose` guard: `ws.onclose` bails out with `if (ws !== this.ws) return;` + * so a late close event from a socket that `forceReopen()` already replaced + * cannot clobber the live connection's status. + * - `forceReopen()` in-flight dedup: concurrent calls share one `reopenInFlight` + * promise instead of tearing the socket down twice. + */ +import { ConnectionImpl } from '@rocket.chat/ddp-client/dist/Connection'; +import { MinimalDDPClient } from '@rocket.chat/ddp-client/dist/MinimalDDPClient'; + +// Minimal fake WebSocket. ConnectionImpl receives the constructor directly +// (`new this.WS(url)`) rather than reading a global, so no `global.WebSocket` +// stubbing is needed — we just pass this class in as the `WS` constructor arg. +class FakeWebSocket { + static instances: FakeWebSocket[] = []; + + static readonly CONNECTING = 0; + + static readonly OPEN = 1; + + static readonly CLOSING = 2; + + static readonly CLOSED = 3; + + readonly CONNECTING = 0; + + readonly OPEN = 1; + + readonly CLOSING = 2; + + readonly CLOSED = 3; + + readyState = FakeWebSocket.CONNECTING; + + onopen?: () => void; + + onclose?: (ev: { code: number; reason: string }) => void; + + onmessage?: (ev: { data: string }) => void; + + onerror?: (ev: unknown) => void; + + constructor(public url: string) { + FakeWebSocket.instances.push(this); + } + + send = jest.fn(); + + close = jest.fn(() => { + this.readyState = FakeWebSocket.CLOSED; + }); + + // Test helper (not part of the WebSocket surface): simulate the transport + // finishing its handshake and firing `onopen`. + open() { + this.readyState = FakeWebSocket.OPEN; + this.onopen?.(); + } +} + +// Test helper: simulate the server replying to the DDP `connect` message with +// `connected`, which is what makes `ConnectionImpl.connect()`'s promise +// resolve and its status flip to 'connected'. Must be called after `open()` +// since `onmessage` is only wired up inside the real `onopen` handler. +const completeHandshake = (ws: FakeWebSocket, session: string) => { + ws.onmessage?.({ data: JSON.stringify({ msg: 'connected', session }) }); +}; + +const buildConnection = () => { + const client = new MinimalDDPClient(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const connection = new (ConnectionImpl as any)('wss://example.com', FakeWebSocket, client, { + retryCount: 0, + retryTime: 1000 + }); + return { client, connection }; +}; + +describe('patched ConnectionImpl (patches/@rocket.chat+ddp-client+0.3.51.patch)', () => { + beforeEach(() => { + FakeWebSocket.instances = []; + }); + + it('ignores a stale onclose from a socket that forceReopen() already replaced', async () => { + const { connection } = buildConnection(); + + const connectPromise = connection.connect(); + const staleSocket = FakeWebSocket.instances[0]; + staleSocket.open(); + completeHandshake(staleSocket, 'session-1'); + await expect(connectPromise).resolves.toBe(true); + expect(connection.status).toBe('connected'); + + // Capture the stale socket's onclose handler *before* forceReopen (via + // close()) severs it, to stand in for a close event that was already + // in flight on the transport at the moment the socket got replaced — + // the real-world race the `ws !== this.ws` guard defends against. + const staleOnClose = staleSocket.onclose; + expect(typeof staleOnClose).toBe('function'); + + const reopenPromise = connection.forceReopen(); + expect(FakeWebSocket.instances).toHaveLength(2); + const freshSocket = FakeWebSocket.instances[1]; + expect(freshSocket).not.toBe(staleSocket); + + freshSocket.open(); + completeHandshake(freshSocket, 'session-2'); + await expect(reopenPromise).resolves.toBe(true); + expect(connection.status).toBe('connected'); + expect(connection.ws).toBe(freshSocket); + + // The stale socket's close event arrives late. Without the guard this + // would set status to 'disconnected' and emit a 'disconnected' event + // against what is, from the app's perspective, a perfectly live + // connection. + staleOnClose?.({ code: 1006, reason: 'late close' }); + + expect(connection.status).toBe('connected'); + expect(connection.ws).toBe(freshSocket); + }); + + it('dedupes concurrent forceReopen() calls into a single in-flight reopen', async () => { + const { connection } = buildConnection(); + + const connectPromise = connection.connect(); + FakeWebSocket.instances[0].open(); + completeHandshake(FakeWebSocket.instances[0], 'session-1'); + await connectPromise; + + const first = connection.forceReopen(); + const second = connection.forceReopen(); + + // The second call must return the exact same in-flight promise rather + // than tearing the socket down again. + expect(second).toBe(first); + expect(FakeWebSocket.instances).toHaveLength(2); + + const freshSocket = FakeWebSocket.instances[1]; + freshSocket.open(); + completeHandshake(freshSocket, 'session-2'); + + await expect(Promise.all([first, second])).resolves.toEqual([true, true]); + // Still only one extra socket was ever opened for the two concurrent calls. + expect(FakeWebSocket.instances).toHaveLength(2); + }); +}); From 0dfdcb5b53cca6175b6362732407f338e4edf6e7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:51:26 +0530 Subject: [PATCH 144/203] fix: add missing url field to IInvite and stop discarding its type findOrCreateInvite's only caller reads invite.url, which wasn't on the type; an : any return annotation was hiding the gap. --- app/definitions/IInvite.ts | 1 + app/lib/services/restApi.test.ts | 26 +++++++++++++++++++++++++- app/lib/services/restApi.ts | 4 ++-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/definitions/IInvite.ts b/app/definitions/IInvite.ts index 038cc985cdf..bfdb769e0fa 100644 --- a/app/definitions/IInvite.ts +++ b/app/definitions/IInvite.ts @@ -8,4 +8,5 @@ export interface IInvite extends IRocketChatRecord { createdAt: Date; expires: Date | null; uses: number; + url: string; } diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index c07a43cbba4..94de505a17f 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -1,7 +1,7 @@ import type { ServerMediaSignal } from '@rocket.chat/media-signaling'; import { Platform } from 'react-native'; -import { mediaCallsStateSignals } from './restApi'; +import { findOrCreateInvite, mediaCallsStateSignals } from './restApi'; const mockSdkGet = jest.fn(); const mockSdkPost = jest.fn(); @@ -118,6 +118,30 @@ describe('mediaCallsStateSignals', () => { }); }); +describe('findOrCreateInvite', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns an invite with a url field', async () => { + mockSdkPost.mockResolvedValueOnce({ + success: true, + _id: 'inv1', + days: 1, + maxUses: 0, + rid: 'room1', + url: 'https://example.com/invite/abc' + }); + + const invite = await findOrCreateInvite({ rid: 'room1', days: 1, maxUses: 0 }); + + expect(mockSdkPost).toHaveBeenCalledWith('/v1/findOrCreateInvite', { rid: 'room1', days: 1, maxUses: 0 }); + expect(invite.success).toBe(true); + if (!invite.success) return; + expect(invite.url).toBe('https://example.com/invite/abc'); + }); +}); + describe('registerPushToken', () => { const platformOsAtSuiteStart = Platform.OS; diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 45780c84a40..ab391382112 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -978,11 +978,11 @@ export const getSupportedLanguagesAutoTranslate = (): Promise<{ language: string export const translateMessage = (messageId: string, targetLanguage: string) => sdk.post('/v1/autotranslate.translateMessage', { messageId, targetLanguage }); -export const findOrCreateInvite = ({ rid, days, maxUses }: { rid: string; days: number; maxUses: number }): any => +export const findOrCreateInvite = ({ rid, days, maxUses }: { rid: string; days: number; maxUses: number }) => // RC 2.4.0 sdk.post('/v1/findOrCreateInvite', { rid, days, maxUses }); -export const validateInviteToken = (token: string): any => +export const validateInviteToken = (token: string) => // RC 2.4.0 sdk.post('/v1/validateInviteToken', { token }); From fc662b63e78221a5b3d3df4f107adeb659d63b7b Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:54:47 +0530 Subject: [PATCH 145/203] chore: remove stray fix me comment above getDepartmentInfo No known real gap tied to this function; the comment was contextless noise left over from an earlier pass. --- app/lib/services/restApi.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index ab391382112..b8940337e42 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -458,7 +458,6 @@ export const forwardLivechat = (transferData: any) => { return sdk.methodCallWrapper('livechat:transfer', transferData); }; -// fix me export const getDepartmentInfo = (departmentId: string) => // RC 2.2.0 sdk.get(`/v1/livechat/department/${departmentId}?includeAgents=false`); From d9b02f0886c6e74eee701f3f9c02405d4512b5ce Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:54:39 +0530 Subject: [PATCH 146/203] fix: guard optional cursor field and remove stale @ts-ignores chat.syncMessages's cursor is optional per its own type; deletedNext/ updatedNext crashed when the server omitted it. Also removed two @ts-ignores left over from before these endpoints were typed. --- app/lib/methods/getSlashCommands.ts | 1 - app/lib/methods/loadMissedMessages.test.ts | 46 ++++++++++++++++++++++ app/lib/methods/loadMissedMessages.ts | 22 ++++++----- 3 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 app/lib/methods/loadMissedMessages.test.ts diff --git a/app/lib/methods/getSlashCommands.ts b/app/lib/methods/getSlashCommands.ts index 39b387b7b2e..3123876f0d1 100644 --- a/app/lib/methods/getSlashCommands.ts +++ b/app/lib/methods/getSlashCommands.ts @@ -16,7 +16,6 @@ export function getSlashCommands() { if (!result.success) { return resolve(); } - // @ts-ignore const { commands } = result; if (commands && commands.length) { await db.write(async () => { diff --git a/app/lib/methods/loadMissedMessages.test.ts b/app/lib/methods/loadMissedMessages.test.ts new file mode 100644 index 00000000000..33040e66c6c --- /dev/null +++ b/app/lib/methods/loadMissedMessages.test.ts @@ -0,0 +1,46 @@ +import { loadMissedMessages } from './loadMissedMessages'; +import sdk from '../services/sdk'; +import { getSubscriptionByRoomId } from '../database/services/Subscription'; +import updateMessages from './updateMessages'; +import { store } from '../store/auxStore'; + +jest.mock('../services/sdk', () => ({ + __esModule: true, + default: { + get: jest.fn() + } +})); + +jest.mock('../database/services/Subscription', () => ({ + getSubscriptionByRoomId: jest.fn(() => Promise.resolve(null)) +})); + +jest.mock('../store/auxStore', () => ({ + store: { + getState: jest.fn(() => ({ + server: { version: '7.1.0' } + })) + } +})); + +jest.mock('./updateMessages', () => jest.fn()); + +const mockSdkGet = sdk.get as jest.MockedFunction; +const mockedGetSubscriptionByRoomId = getSubscriptionByRoomId as jest.MockedFunction; +const mockedUpdateMessages = updateMessages as jest.MockedFunction; +const mockedGetState = store.getState as jest.MockedFunction; + +describe('loadMissedMessages', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockedGetSubscriptionByRoomId.mockResolvedValue(null as any); + mockedUpdateMessages.mockResolvedValue(0 as any); + mockedGetState.mockReturnValue({ server: { version: '7.1.0' } } as any); + }); + + it('does not throw when the server omits cursor on a syncMessages response', async () => { + mockSdkGet.mockResolvedValueOnce({ success: true, result: { updated: [], deleted: [] } } as any); // no cursor field at all + + await expect(loadMissedMessages({ rid: 'room-1', updatedNext: 100 })).resolves.toBeUndefined(); + }); +}); diff --git a/app/lib/methods/loadMissedMessages.ts b/app/lib/methods/loadMissedMessages.ts index fc11c55d0c0..1030233e4f2 100644 --- a/app/lib/methods/loadMissedMessages.ts +++ b/app/lib/methods/loadMissedMessages.ts @@ -8,9 +8,11 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; const count = 50; const syncMessages = async ({ roomId, next, type }: { roomId: string; next: number; type: 'UPDATED' | 'DELETED' }) => { - // @ts-ignore // this method dont have type - const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, next, count, type }); - return result; + const response = await sdk.get('/v1/chat.syncMessages', { roomId, next, count, type }); + if (!response.success) { + return undefined; + } + return response.result; }; const getSyncMessagesFromCursor = async ( @@ -35,9 +37,9 @@ const getSyncMessagesFromCursor = async ( const [updatedMessages, deletedMessages] = await Promise.all(promises); return { deleted: deletedMessages?.deleted ?? [], - deletedNext: deletedMessages?.cursor.next, + deletedNext: deletedMessages?.cursor?.next, updated: updatedMessages?.updated ?? [], - updatedNext: updatedMessages?.cursor.next + updatedNext: updatedMessages?.cursor?.next }; }; @@ -81,9 +83,11 @@ async function load({ lastOpenISOString = lastUpdate?.toISOString(); } // RC 0.60.0 - // @ts-ignore // this method dont have type - const { result } = await sdk.get('/v1/chat.syncMessages', { roomId, lastUpdate: lastOpenISOString }); - return result; + const response = await sdk.get('/v1/chat.syncMessages', { roomId, lastUpdate: lastOpenISOString }); + if (!response.success) { + return undefined; + } + return response.result; } export async function loadMissedMessages(args: { @@ -104,7 +108,7 @@ export async function loadMissedMessages(args: { updatedNext, deleted, deletedNext - }: { updated: ILastMessage[]; deleted: ILastMessage[]; updatedNext: number | null; deletedNext: number | null } = data; + }: { updated: ILastMessage[]; deleted: ILastMessage[]; updatedNext?: number | null; deletedNext?: number | null } = data; // @ts-ignore // TODO: remove loaderItem obligatoriness await updateMessages({ rid: args.rid, update: updated, remove: deleted }); From 10266f83f7d881f5d08b5cea5885b931fa7a93a8 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:45:17 +0530 Subject: [PATCH 147/203] test: exercise logout() itself, not just removeServer logout.test.ts previously only tested a sibling export; the actual logout() function (subscription teardown, sdk.logout, disconnect) had no coverage. --- app/lib/methods/logout.test.ts | 86 ++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/app/lib/methods/logout.test.ts b/app/lib/methods/logout.test.ts index 4dea28e3651..cd7642fa00f 100644 --- a/app/lib/methods/logout.test.ts +++ b/app/lib/methods/logout.test.ts @@ -57,19 +57,23 @@ jest.mock('../services/restApi', () => ({ })); jest.mock('./subscriptions/rooms', () => ({ - roomsSubscription: null + roomsSubscription: { + stop: jest.fn() + } })); jest.mock('./getUsersPresence', () => ({ - _activeUsersSubTimeout: { activeUsersSubTimeout: false } + _activeUsersSubTimeout: { activeUsersSubTimeout: undefined } })); import { DDPSDK } from '@rocket.chat/ddp-client'; -import { removeServer } from './logout'; +import { removeServer, logout } from './logout'; import UserPreferences from './userPreferences'; import { getDeviceToken } from '../notifications'; import log from './helpers/log'; +import { disconnect } from '../services/connect'; +import sdk from '../services/sdk'; describe('removeServer', () => { beforeEach(() => { @@ -174,3 +178,79 @@ describe('removeServer', () => { expect(close).toHaveBeenCalled(); }); }); + +describe('logout', () => { + beforeEach(() => { + jest.clearAllMocks(); + // Reset sdk.current to undefined before each test + (sdk as any).current = undefined; + }); + + afterEach(() => { + // Restore roomsSubscription to its original state to prevent test isolation issues + const mockRoomsSubscription = jest.requireMock('./subscriptions/rooms'); + if (!mockRoomsSubscription.roomsSubscription || !mockRoomsSubscription.roomsSubscription.stop) { + mockRoomsSubscription.roomsSubscription = { + stop: jest.fn() + }; + } + }); + + it('stops the rooms subscription, clears the presence timer, logs out of the sdk, and disconnects', async () => { + // Setup + (sdk as any).current = {}; // Make sdk.current truthy so disconnect() is called + const mockRoomsSubscription = jest.requireMock('./subscriptions/rooms'); + const mockConnect = jest.requireMock('../services/connect'); + const mockSdk = jest.requireMock('../services/sdk').default; + const mockActiveUsersTimeout = jest.requireMock('./getUsersPresence')._activeUsersSubTimeout; + + // Set up a timeout ID to be cleared + mockActiveUsersTimeout.activeUsersSubTimeout = 123; + + // Execute + await logout({ server: 'https://example.com' }); + + // Assert + expect(mockRoomsSubscription.roomsSubscription.stop).toHaveBeenCalled(); + expect(mockSdk.logout).toHaveBeenCalled(); + expect(mockConnect.disconnect).toHaveBeenCalled(); + // Verify the timeout was cleared + expect(mockActiveUsersTimeout.activeUsersSubTimeout).toBe(false); + }); + + it('handles missing roomsSubscription gracefully', async () => { + // Setup + (sdk as any).current = {}; + const mockRoomsSubscription = jest.requireMock('./subscriptions/rooms'); + const mockConnect = jest.requireMock('../services/connect'); + const mockSdk = jest.requireMock('../services/sdk').default; + + // Set roomsSubscription to null to test the optional chaining + mockRoomsSubscription.roomsSubscription = null; + + // Execute + await logout({ server: 'https://example.com' }); + + // Assert - should not throw and disconnect should still be called + expect(mockSdk.logout).toHaveBeenCalled(); + expect(mockConnect.disconnect).toHaveBeenCalled(); + }); + + it('handles missing timeout gracefully', async () => { + // Setup + (sdk as any).current = {}; + const mockSdk = jest.requireMock('../services/sdk').default; + const mockConnect = jest.requireMock('../services/connect'); + const mockActiveUsersTimeout = jest.requireMock('./getUsersPresence')._activeUsersSubTimeout; + + // Set activeUsersSubTimeout to undefined + mockActiveUsersTimeout.activeUsersSubTimeout = undefined; + + // Execute + await logout({ server: 'https://example.com' }); + + // Assert - should not throw + expect(mockSdk.logout).toHaveBeenCalled(); + expect(mockConnect.disconnect).toHaveBeenCalled(); + }); +}); From 0e6bbb1bef7a76ed4e186302aa872f6628f8c88e Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 7 Jul 2026 03:50:30 +0530 Subject: [PATCH 148/203] fix: remove unused disconnect import left by an incomplete amend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Task 10 fix subagent's report claimed this import was removed via git commit --amend, but the amend never actually ran — the edit was left uncommitted in that worktree and the merge brought in the stale, still-broken commit. Removing it here directly; also corrected in the source branch/worktree so its own history is consistent. --- app/lib/methods/logout.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/lib/methods/logout.test.ts b/app/lib/methods/logout.test.ts index cd7642fa00f..404cf0d21f6 100644 --- a/app/lib/methods/logout.test.ts +++ b/app/lib/methods/logout.test.ts @@ -72,7 +72,6 @@ import { removeServer, logout } from './logout'; import UserPreferences from './userPreferences'; import { getDeviceToken } from '../notifications'; import log from './helpers/log'; -import { disconnect } from '../services/connect'; import sdk from '../services/sdk'; describe('removeServer', () => { From 71c58fbdeca6f32b0ed5c4aaf57d8320a9fc9d52 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:27:25 +0530 Subject: [PATCH 149/203] fix: bail out of connect() if a newer call already switched servers Without the old SDK's abort(), a connect(A) call could resume after connect(B) took over and wire up listeners/dispatch actions against B's connection using A's stale state. --- app/lib/services/connect.test.ts | 33 +++++++++++++++++++++++++++++++- app/lib/services/connect.ts | 6 ++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index e33d6e7a13b..bdd3186cdd0 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -36,7 +36,12 @@ jest.mock('./sdk', () => { return state.server; }, disconnect: (...args: any[]) => mockSdkDisconnect(...args), - initialize: (s: string) => mockSdkInitialize(s), + // Mirrors the real sdk.ts: initialize() synchronously assigns the server before + // resolving, so `sdk.server` reflects the in-flight connect() call's target. + initialize: (s: string) => { + state.server = s; + return mockSdkInitialize(s); + }, onCollection: (...args: any[]) => mockSdkOnCollection(...args), login: (...args: any[]) => mockSdkLogin(...args), get current() { @@ -480,6 +485,30 @@ describe('connect — connection status handler', () => { expect(mockSdkInitialize).not.toHaveBeenCalled(); }); + it('bails out if a newer connect() call switched servers while getSettings() was in flight', async () => { + const getSettingsMock = jest.requireMock('../methods/getSettings').getSettings as jest.Mock; + let resolveSettingsA: () => void; + getSettingsMock.mockImplementationOnce( + () => + new Promise(resolve => { + resolveSettingsA = resolve; + }) + ); + + sdkMock.__setServer(undefined); + const connectAPromise = connect({ server: 'https://a.example.com' }); + await Promise.resolve(); // let connect(A) reach the getSettings() await + + // Simulate connect(B) having already taken over. + sdkMock.__setServer('https://b.example.com'); + + resolveSettingsA!(); + await connectAPromise; + + // connect(A)'s continuation must not have registered a connection listener for the stale call. + expect(mockConnectionOn).not.toHaveBeenCalled(); + }); + it('dispatches connectSuccess and loginRequest(resume) on reconnect when user has a token', async () => { await connect({ server: SERVER }); const listener = getCapturedConnectionListener(); @@ -632,6 +661,7 @@ describe('checkAndReopen', () => { describe('connect — rooms subscription guard reset on close', () => { beforeEach(() => { jest.clearAllMocks(); + sdkMock.__setServer(undefined); mockStoreGetState.mockReturnValue({ meteor: { connected: false }, login: { user: null, isAuthenticated: false }, @@ -672,6 +702,7 @@ describe('connect — stream-notify-logged updateAvatar', () => { beforeEach(async () => { jest.clearAllMocks(); + sdkMock.__setServer(undefined); mockStoreGetState.mockReturnValue({ meteor: { connected: false }, login: { user: null, isAuthenticated: false }, diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 1873d4dd913..67f380b7eb5 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -55,6 +55,12 @@ async function connect({ server, logoutOnError = false }: { server: string; logo await sdk.initialize(server); await getSettings(); + // A newer connect() call may have switched servers while getSettings() was in flight — + // bail out rather than wiring up listeners/dispatching against the wrong sdk instance. + if (sdk.server !== server) { + return; + } + // Tracks a real disconnect so the next `'connected'` can drain hangups the user tapped while // the WebSocket was unhealthy. Local to the closure so it resets per `connect()` call. let pendingHangupsDrainArmed = false; From c88fe767b4f7b31c2be71bb8c0271a447a17f03a Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:35:08 +0530 Subject: [PATCH 150/203] test: actually invoke the registered 2FA challenge handler Previously only asserted handleTwoFactorChallenge was called with a function, never that the function itself resolves/rejects correctly. This is the only 2FA path for GET/DELETE requests (POST's retry loop is covered separately). --- app/lib/services/sdk.test.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 36103fd586d..a4b23eddf92 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -812,6 +812,32 @@ describe('Sdk.initialize', () => { expect(handleTwoFactorChallenge).toHaveBeenCalledWith(expect.any(Function)); }); + it('resolves the registered 2FA challenge handler with the code from twoFactor() — covers GET/DELETE 2FA', async () => { + const handleTwoFactorChallenge = jest.fn(); + (DDPSDK.create as jest.Mock).mockReturnValue({ rest: { handleTwoFactorChallenge } }); + sdk.initialize('https://example.com'); + const registeredHandler = handleTwoFactorChallenge.mock.calls[0][0]; + + (twoFactor as jest.Mock).mockResolvedValueOnce({ twoFactorCode: '654321', twoFactorMethod: 'totp' }); + + const code = await registeredHandler({ method: 'totp', invalidAttempt: false }); + + expect(code).toBe('654321'); + expect(twoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: false }); + }); + + it('propagates a cancellation from the registered 2FA challenge handler', async () => { + const handleTwoFactorChallenge = jest.fn(); + (DDPSDK.create as jest.Mock).mockReturnValue({ rest: { handleTwoFactorChallenge } }); + sdk.initialize('https://example.com'); + const registeredHandler = handleTwoFactorChallenge.mock.calls[0][0]; + + (twoFactor as jest.Mock).mockRejectedValueOnce(new Error('cancelled')); + + await expect(registeredHandler({ method: 'totp', invalidAttempt: true })).rejects.toThrow('cancelled'); + expect(twoFactor).toHaveBeenCalledWith({ method: 'totp', invalid: true }); + }); + it('resets headers to defaults — does not carry Authorization across server switches', () => { (sdk as any).serverUrl = 'https://old'; sdk.setBasicAuth('dXNlcjpwYXNz'); From e47a1af66fe3d4f58e6fdfe7d3fda97b1d23620b Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:43:35 +0530 Subject: [PATCH 151/203] docs: fix stale JSDoc and document non-obvious auth/2FA/logout behavior Update awaitDdpLoggedIn's JSDoc to describe the current connect() listener mechanism instead of the removed ddp.loggedIn/connectedListener references, and add rationale comments for the 2FA code clear in methodCall, the logout() timeout race, and the dual DDP+REST auth bookkeeping in login(). --- app/lib/services/connect.ts | 10 ++++++---- app/lib/services/sdk.ts | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 67f380b7eb5..7b39fe3e755 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -390,10 +390,12 @@ function checkAndReopen(): Promise { /** * Resolves when the current session is fully logged in (or `timeoutMs` elapses). - * Trusts redux state rather than `ddp.loggedIn`, which isn't cleared on socket - * close and can read true for a stale session. Redux resets to - * `isAuthenticated=false` on `LOGIN.REQUEST` (dispatched by the connectedListener) - * and back to true on `LOGIN.SUCCESS`; `meteor.connected` covers the handshake. + * Trusts redux state rather than the ddp-client's own connection/account state, + * which isn't cleared on socket close and can appear valid for a stale session. + * Redux resets to `isAuthenticated=false` on `LOGIN.REQUEST` (dispatched from the + * `connection.on('connection', ...)` handler in `connect()` when `status === + * 'connected'`) and back to true on `LOGIN.SUCCESS`; `meteor.connected` covers + * the handshake. */ async function awaitDdpLoggedIn(timeoutMs: number = 5000): Promise { const isReady = () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 7201836d621..1e271b3c6bc 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -270,6 +270,8 @@ class Sdk { if (loginResult?.status !== 'success' || !loginResult.data) { return Promise.reject(new Error('Invalid response from server')); } + // Auth is tracked in two places, both required: loginWithToken() sets the DDP-level session used + // by methodCall()/subscribe(), while setHeaders() below sets the REST-level auth used by get/post/delete. await this.current?.account.loginWithToken(loginResult.data.authToken); this.setHeaders({ @@ -293,6 +295,7 @@ class Sdk { } const [method, ...params] = args; const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(this.code ? [this.code] : [])); + // Clear the 2FA code after use — a stale trailing arg breaks typed method signatures on the next call. if (this.code) { this.code = null; } @@ -430,6 +433,7 @@ class Sdk { async logout(): Promise { if (this.current?.account) { const TIMEOUT = Symbol('logout-timeout'); + // account.logout() can hang indefinitely on a dead socket; cap it so app-level logout always completes. const result = await Promise.race([ this.current.account.logout(), new Promise(resolve => setTimeout(() => resolve(TIMEOUT), 5000)) From 04ac6a5bfd83e797bacfc814a7b740e8f55cf221 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 7 Jul 2026 18:48:31 +0530 Subject: [PATCH 152/203] fix: use the project's log() helper instead of console.log/error in connect.ts console.log/console.error never reach Bugsnag in production, hiding real connection/SSO-fetch failures. --- app/lib/services/connect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 7b39fe3e755..d27da0e7600 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -257,7 +257,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo sdk.onCollection('stream-force_logout', () => store.dispatch(logout(true))); } catch (e) { - console.error('Connection error:', e); + log(e); throw e; } } @@ -478,7 +478,7 @@ async function getLoginServices(server: string) { store.dispatch(setLoginServices({})); } } catch (error) { - console.log(error); + log(error); store.dispatch(setLoginServices({})); } } From 80f98725c561b9f28c019f3bd94888a31b27cb45 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:01:02 +0530 Subject: [PATCH 153/203] fix: guard stream-notify-all/logged and stream-roles against fieldless payloads protectedFunction only catches synchronous throws; an async handler destructuring ddpMessage.fields without a guard would produce an unhandled rejection instead, unlike stream-user-presence which already guards this. --- app/lib/services/connect.test.ts | 40 ++++++++++++++++++++++++++++++++ app/lib/services/connect.ts | 17 +++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index bdd3186cdd0..47d2c243759 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -4,6 +4,7 @@ import { pendingHangups } from './voip/pendingHangups'; import { unsubscribeRooms } from '../methods/subscribeRooms'; import { setUser } from '../../actions/login'; import database from '../database'; +import { onRolesChanged } from '../methods/getRoles'; jest.mock('./voip/MediaSessionInstance', () => ({ mediaSessionInstance: { reset: jest.fn(), drainPendingHangups: jest.fn() } @@ -969,3 +970,42 @@ describe('connection status handler (Fix 4 regression)', () => { expect(actions).not.toContain('INQUIRY_REQUEST'); }); }); + +// Regression: stream-notify-all/stream-roles/stream-notify-logged handlers are wrapped in +// protectedFunction, which only catches synchronous throws. A fieldless payload reaching an +// async handler that destructures `ddpMessage.fields` without a guard would reject instead of +// throw, producing an unhandled promise rejection. Each handler must bail out early instead. +describe('connect — collection handlers guard against fieldless payloads', () => { + beforeEach(() => { + jest.clearAllMocks(); + sdkMock.__setServer(undefined); + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + }); + + it('ignores a stream-notify-all message with no fields instead of throwing', async () => { + await connect({ server: 'https://example.com' }); + const [handler] = getHandlersByEvent('stream-notify-all'); + + await expect(handler({ msg: 'added' })).resolves.toBeUndefined(); + }); + + it('ignores a stream-roles message with no fields instead of throwing', async () => { + await connect({ server: 'https://example.com' }); + const [handler] = getHandlersByEvent('stream-roles'); + + expect(() => handler({ msg: 'added' })).not.toThrow(); + expect(onRolesChanged).not.toHaveBeenCalled(); + }); + + it('ignores a stream-notify-logged message with no fields instead of throwing', async () => { + await connect({ server: 'https://example.com' }); + const [handler] = getHandlersByEvent('stream-notify-logged'); + + await expect(handler({ msg: 'added' })).resolves.toBeUndefined(); + }); +}); diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index d27da0e7600..33e410c9911 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -106,7 +106,10 @@ async function connect({ server, logoutOnError = false }: { server: string; logo sdk.onCollection( 'stream-notify-all', - protectedFunction(async (ddpMessage: { fields: { args?: any; eventName: string } }) => { + protectedFunction(async (ddpMessage: { fields?: { args?: any; eventName: string } }) => { + if (!ddpMessage.fields) { + return; + } const { eventName } = ddpMessage.fields; if (/public-settings-changed/.test(eventName)) { const { _id, value } = ddpMessage.fields.args[1]; @@ -142,7 +145,12 @@ async function connect({ server, logoutOnError = false }: { server: string; logo sdk.onCollection( 'stream-roles', - protectedFunction((ddpMessage: any) => onRolesChanged(ddpMessage)) + protectedFunction((ddpMessage: any) => { + if (!ddpMessage?.fields) { + return; + } + onRolesChanged(ddpMessage); + }) ); // RC 4.1 @@ -166,7 +174,10 @@ async function connect({ server, logoutOnError = false }: { server: string; logo sdk.onCollection( 'stream-notify-logged', - protectedFunction(async (ddpMessage: { fields: { args?: any; eventName?: any } }) => { + protectedFunction(async (ddpMessage: { fields?: { args?: any; eventName?: any } }) => { + if (!ddpMessage.fields) { + return; + } const { eventName } = ddpMessage.fields; // `user-status` event is deprecated after RC 4.1 in favor of `stream-user-presence/${uid}` From a768f3eb935fdc9d24908f1ba1931c4a5c722530 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:36:22 +0530 Subject: [PATCH 154/203] lint fix --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 1e271b3c6bc..620b7b0cff2 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -155,7 +155,7 @@ class Sdk { } } - async post>( + post>( endpoint: TPath, params: void extends OperationParams<'POST', MatchPathPattern> ? void From 72661f4793f85d07a4de4d46131721461ff28719 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:03:40 +0530 Subject: [PATCH 155/203] Changes --- app/lib/methods/getRoles.ts | 94 ++++++++--------- app/lib/methods/getSingleMessage.ts | 19 ++-- app/lib/methods/getSlashCommands.ts | 114 ++++++++++----------- app/lib/services/connect.ts | 4 +- app/lib/services/restApi.ts | 1 - app/views/AddExistingChannelView/index.tsx | 3 +- 6 files changed, 110 insertions(+), 125 deletions(-) diff --git a/app/lib/methods/getRoles.ts b/app/lib/methods/getRoles.ts index aaaa7d71fbf..015ceaecf28 100644 --- a/app/lib/methods/getRoles.ts +++ b/app/lib/methods/getRoles.ts @@ -69,63 +69,59 @@ export async function onRolesChanged(ddpMessage: IRolesChanged): Promise { } } -export function getRoles(): Promise { +export async function getRoles(): Promise { const db = database.active; - return new Promise(async resolve => { - try { - // RC 0.70.0 - const result = await sdk.get('/v1/roles.list'); + try { + // RC 0.70.0 + const result = await sdk.get('/v1/roles.list'); - if (!result.success) { - return resolve(); - } + if (!result.success) { + return; + } - const { roles } = result; + const { roles } = result; - if (roles && roles.length) { - await db.write(async () => { - const rolesCollections = db.get('roles'); - const allRolesRecords = await rolesCollections.query().fetch(); + if (roles && roles.length) { + await db.write(async () => { + const rolesCollections = db.get('roles'); + const allRolesRecords = await rolesCollections.query().fetch(); - // filter roles - const filteredRolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id)); - const filteredRolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id)); + // filter roles + const filteredRolesToCreate = roles.filter(i1 => !allRolesRecords.find(i2 => i1._id === i2.id)); + const filteredRolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id)); - // Create - const rolesToCreate = filteredRolesToCreate.map(role => - rolesCollections.prepareCreate( - protectedFunction((r: TRoleModel) => { - r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema); - Object.assign(r, role); - }) - ) - ); + // Create + const rolesToCreate = filteredRolesToCreate.map(role => + rolesCollections.prepareCreate( + protectedFunction((r: TRoleModel) => { + r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema); + Object.assign(r, role); + }) + ) + ); - // Update - const rolesToUpdate = filteredRolesToUpdate.map(role => { - const newRole = roles.find(r => r._id === role.id); - return role.prepareUpdate( - protectedFunction((r: TRoleModel) => { - Object.assign(r, newRole); - }) - ); - }); + // Update + const rolesToUpdate = filteredRolesToUpdate.map(role => { + const newRole = roles.find(r => r._id === role.id); + return role.prepareUpdate( + protectedFunction((r: TRoleModel) => { + Object.assign(r, newRole); + }) + ); + }); - const allRecords: Model[] = [...rolesToCreate, ...rolesToUpdate]; + const allRecords: Model[] = [...rolesToCreate, ...rolesToUpdate]; - try { - await db.batch(allRecords); - } catch (e) { - log(e); - } - setRoles(); - return allRecords.length; - }); - return resolve(); - } - } catch (e) { - log(e); - return resolve(); + try { + await db.batch(allRecords); + } catch (e) { + log(e); + } + setRoles(); + return allRecords.length; + }); } - }); + } catch (e) { + log(e); + } } diff --git a/app/lib/methods/getSingleMessage.ts b/app/lib/methods/getSingleMessage.ts index b0ac1e9a88f..2b4600fcc28 100644 --- a/app/lib/methods/getSingleMessage.ts +++ b/app/lib/methods/getSingleMessage.ts @@ -1,17 +1,12 @@ import { getSingleMessage as getSingleMessageService } from '../services/restApi'; import { type IMessage } from '../../definitions'; -const getSingleMessage = (messageId: string): Promise => - new Promise(async (resolve, reject) => { - try { - const result = await getSingleMessageService(messageId); - if (result.success) { - return resolve(result.message); - } - return reject(); - } catch (e) { - return reject(e); - } - }); +const getSingleMessage = async (messageId: string): Promise => { + const result = await getSingleMessageService(messageId); + if (result.success) { + return result.message; + } + throw new Error('Failed to fetch message'); +}; export default getSingleMessage; diff --git a/app/lib/methods/getSlashCommands.ts b/app/lib/methods/getSlashCommands.ts index 3123876f0d1..56211702a2c 100644 --- a/app/lib/methods/getSlashCommands.ts +++ b/app/lib/methods/getSlashCommands.ts @@ -6,72 +6,68 @@ import protectedFunction from './helpers/protectedFunction'; import { type ISlashCommandResult, type TSlashCommandModel } from '../../definitions'; import sdk from '../services/sdk'; -export function getSlashCommands() { +export async function getSlashCommands(): Promise { const db = database.active; - return new Promise(async resolve => { - try { - // RC 0.60.2 - const result = await sdk.get('/v1/commands.list'); + try { + // RC 0.60.2 + const result = await sdk.get('/v1/commands.list'); - if (!result.success) { - return resolve(); - } - const { commands } = result; - if (commands && commands.length) { - await db.write(async () => { - const slashCommandsCollection = db.get('slash_commands'); - const allSlashCommandsRecords = await slashCommandsCollection.query().fetch(); + if (!result.success) { + return; + } + const { commands } = result; + if (commands && commands.length) { + await db.write(async () => { + const slashCommandsCollection = db.get('slash_commands'); + const allSlashCommandsRecords = await slashCommandsCollection.query().fetch(); - // filter slash commands - const filteredSlashCommandsToCreate = commands.filter( - (i1: ISlashCommandResult) => !allSlashCommandsRecords.find(i2 => i1.command === i2.id) - ); - const filteredSlashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => - commands.find((i2: ISlashCommandResult) => i1.id === i2.command) - ); - const filteredSlashCommandsToDelete = allSlashCommandsRecords.filter( - i1 => - !filteredSlashCommandsToCreate.find((i2: ISlashCommandResult) => i2.command === i1.id) && - !filteredSlashCommandsToUpdate.find(i2 => i2.id === i1.id) - ); + // filter slash commands + const filteredSlashCommandsToCreate = commands.filter( + (i1: ISlashCommandResult) => !allSlashCommandsRecords.find(i2 => i1.command === i2.id) + ); + const filteredSlashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => + commands.find((i2: ISlashCommandResult) => i1.id === i2.command) + ); + const filteredSlashCommandsToDelete = allSlashCommandsRecords.filter( + i1 => + !filteredSlashCommandsToCreate.find((i2: ISlashCommandResult) => i2.command === i1.id) && + !filteredSlashCommandsToUpdate.find(i2 => i2.id === i1.id) + ); - // Create - const slashCommandsToCreate = filteredSlashCommandsToCreate.map((command: ISlashCommandResult) => - slashCommandsCollection.prepareCreate( - protectedFunction((s: TSlashCommandModel) => { - s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema); - Object.assign(s, command); - }) - ) - ); + // Create + const slashCommandsToCreate = filteredSlashCommandsToCreate.map((command: ISlashCommandResult) => + slashCommandsCollection.prepareCreate( + protectedFunction((s: TSlashCommandModel) => { + s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema); + Object.assign(s, command); + }) + ) + ); - // Update - const slashCommandsToUpdate = filteredSlashCommandsToUpdate.map(command => { - const newCommand = commands.find((s: ISlashCommandResult) => s.command === command.id); - return command.prepareUpdate( - protectedFunction((s: TSlashCommandModel) => { - Object.assign(s, newCommand); - }) - ); - }); + // Update + const slashCommandsToUpdate = filteredSlashCommandsToUpdate.map(command => { + const newCommand = commands.find((s: ISlashCommandResult) => s.command === command.id); + return command.prepareUpdate( + protectedFunction((s: TSlashCommandModel) => { + Object.assign(s, newCommand); + }) + ); + }); - // Delete - const slashCommandsToDelete = filteredSlashCommandsToDelete.map(command => command.prepareDestroyPermanently()); + // Delete + const slashCommandsToDelete = filteredSlashCommandsToDelete.map(command => command.prepareDestroyPermanently()); - const allRecords = [...slashCommandsToCreate, ...slashCommandsToUpdate, ...slashCommandsToDelete]; + const allRecords = [...slashCommandsToCreate, ...slashCommandsToUpdate, ...slashCommandsToDelete]; - try { - await db.batch(allRecords); - } catch (e) { - log(e); - } - return allRecords.length; - }); - } - return resolve(); - } catch (e) { - log(e); - return resolve(); + try { + await db.batch(allRecords); + } catch (e) { + log(e); + } + return allRecords.length; + }); } - }); + } catch (e) { + log(e); + } } diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 33e410c9911..ecf4bb52240 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -102,7 +102,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo }); await sdk.current?.connection.connect(); - sdk.onCollection('users', (ddpMessage: any) => _setUser(ddpMessage)); + sdk.onCollection('users', (ddpMessage: unknown) => _setUser(ddpMessage as IActiveUsers)); sdk.onCollection( 'stream-notify-all', @@ -117,7 +117,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo const settingsCollection = db.get('settings'); // Check if the _id exists in defaultSettings - if (defaultSettings.hasOwnProperty(_id)) { + if (Object.hasOwn(defaultSettings, _id)) { try { const settingsRecord = await settingsCollection.find(_id); // @ts-ignore diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index b8940337e42..b2f390ea070 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1050,7 +1050,6 @@ export const editMediaMessage = async ( const response = await fetch(`${serverUrl}/api/v1/rooms.mediaConfirm/${rid}/${fileId}`, { method: 'POST', headers: { - ...sdk.getHeaders(), 'Content-Type': 'application/json' }, body: JSON.stringify({ diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index 1a15ec8a110..4bdcff117e8 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -145,8 +145,7 @@ const AddExistingChannelView = () => { } } catch (e: any) { logEvent(events.CT_ADD_ROOM_TO_TEAM_F); - const errorKey = e?.data?.error ?? 'error-unknown'; - showErrorAlert(I18n.t(errorKey), I18n.t('Add_Existing_Channel'), () => {}); + showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {}); log(e); } finally { sendLoadingEvent({ visible: false }); From eec64f67421676e5d175c350bc41eaf141c0963c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Thu, 9 Jul 2026 22:27:36 +0530 Subject: [PATCH 156/203] remove custom id and token extend --- app/lib/methods/uploadAvatar/uploadAvatar.test.ts | 2 +- app/lib/methods/uploadAvatar/uploadAvatar.ts | 5 +---- jest.config.js | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/lib/methods/uploadAvatar/uploadAvatar.test.ts b/app/lib/methods/uploadAvatar/uploadAvatar.test.ts index 09b13157c7d..1ba3f011550 100644 --- a/app/lib/methods/uploadAvatar/uploadAvatar.test.ts +++ b/app/lib/methods/uploadAvatar/uploadAvatar.test.ts @@ -23,7 +23,7 @@ jest.mock('../sendFileMessage/utils', () => ({ jest.mock('../../services/sdk', () => ({ __esModule: true, default: { - getHeaders: () => ({ 'X-Custom': 'custom' }) + getHeaders: () => ({ 'X-Custom': 'custom', 'X-Auth-Token': 'tok1', 'X-User-Id': 'uid1' }) } })); diff --git a/app/lib/methods/uploadAvatar/uploadAvatar.ts b/app/lib/methods/uploadAvatar/uploadAvatar.ts index 72635683e4b..1d14b6bf297 100644 --- a/app/lib/methods/uploadAvatar/uploadAvatar.ts +++ b/app/lib/methods/uploadAvatar/uploadAvatar.ts @@ -6,14 +6,11 @@ import sdk from '../../services/sdk'; export const uploadUserAvatarMultipart = async (localUri: string, mimeType: string, filename: string): Promise => { const { server } = reduxStore.getState().server; - const { id, token } = reduxStore.getState().login.user; const filePath = await copyFileToCacheDirectoryIfNeeded(localUri, filename); const formData: IFormData[] = [{ name: 'image', uri: filePath, type: mimeType, filename }]; const headers: Record = { ...sdk.getHeaders(), - 'Content-Type': 'multipart/form-data', - ...(token ? { 'X-Auth-Token': token } : {}), - ...(id ? { 'X-User-Id': id } : {}) + 'Content-Type': 'multipart/form-data' }; const upload = new FileUpload(`${server}/api/v1/users.setAvatar`, headers, formData); await upload.send(); diff --git a/jest.config.js b/jest.config.js index b0b20cedc3c..02fee07b89e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -2,7 +2,7 @@ module.exports = { modulePathIgnorePatterns: ['/.*worktrees/'], testPathIgnorePatterns: ['e2e', 'node_modules', '/.*worktrees/'], transformIgnorePatterns: [ - 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|@rocket.chat/ui-kit|tiny-events)' + 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|@rocket.chat/ui-kit|@rocket.chat/ddp-client|tiny-events)' ], preset: './jest.preset.js', cacheDirectory: '/.jest-cache', From 478f7643f4a5c942d959ba29ab7151316be6cf05 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:17:48 +0530 Subject: [PATCH 157/203] changes --- app/ee/omnichannel/lib/index.ts | 7 +------ app/lib/methods/getSingleMessage.ts | 21 ++++++++++++------- .../sendFileMessage/sendFileMessageV2.ts | 5 +---- app/lib/methods/subscriptions/room.ts | 2 +- app/sagas/videoConf.ts | 6 +----- app/views/RoomInfoView/index.tsx | 1 - app/views/ThreadMessagesView/index.tsx | 2 -- .../UserNotificationPreferencesView/index.tsx | 4 ++-- 8 files changed, 19 insertions(+), 29 deletions(-) diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index a1ea2648302..609cbe65c75 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -56,12 +56,7 @@ class Omnichannel { subscribeInquiry = async () => { console.log('[RCRN] Subscribing to inquiry'); - this.unsubscribeInquiry(); - try { - this.inquirySub = await subscribeInquiry(); - } catch (e) { - log(e); - } + this.inquirySub = await subscribeInquiry(); }; unsubscribeInquiry = () => { diff --git a/app/lib/methods/getSingleMessage.ts b/app/lib/methods/getSingleMessage.ts index 2b4600fcc28..1cecd9be7d9 100644 --- a/app/lib/methods/getSingleMessage.ts +++ b/app/lib/methods/getSingleMessage.ts @@ -1,12 +1,17 @@ -import { getSingleMessage as getSingleMessageService } from '../services/restApi'; import { type IMessage } from '../../definitions'; +import { getSingleMessage as getSingleMessageService } from '../services/restApi'; -const getSingleMessage = async (messageId: string): Promise => { - const result = await getSingleMessageService(messageId); - if (result.success) { - return result.message; - } - throw new Error('Failed to fetch message'); -}; +const getSingleMessage = (messageId: string): Promise => + new Promise(async (resolve, reject) => { + try { + const result = await getSingleMessageService(messageId); + if (result.success) { + return resolve(result.message); + } + return reject(); + } catch (e) { + return reject(e); + } + }); export default getSingleMessage; diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts index 4265d0b6412..8fefbe75acc 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts @@ -61,7 +61,7 @@ export async function sendFileMessageV2( if (getContent) { content = await getContent(response.file._id, response.file.url); } - const confirmResponse = await fetch(`${server}/api/v1/rooms.mediaConfirm/${rid}/${response.file._id}`, { + await fetch(`${server}/api/v1/rooms.mediaConfirm/${rid}/${response.file._id}`, { method: 'POST', headers: { ...headers, @@ -75,9 +75,6 @@ export async function sendFileMessageV2( content }) }); - if (!confirmResponse.ok) { - throw new Error(`mediaConfirm failed with status ${confirmResponse.status}`); - } await db.write(async () => { await uploadRecord?.destroyPermanently(); }); diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index 3fcad9b7c7d..3bf4612dc99 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -52,7 +52,7 @@ export default class RoomSubscription { this.connectionStatusListener = sdk.onConnectionStatus(status => { if (status === 'connected') { - this.handleConnected(); + this.handleLogin(); } if (['disconnected', 'closed'].includes(status)) { this.handleClose(); diff --git a/app/sagas/videoConf.ts b/app/sagas/videoConf.ts index ad560ae9c2d..60b5d62ea38 100644 --- a/app/sagas/videoConf.ts +++ b/app/sagas/videoConf.ts @@ -179,7 +179,7 @@ function* initCall({ payload: { mic, cam, direct, rid } }: { payload: TCallProps if (isServer5OrNewer) { try { const videoConfResponse = yield* call(videoConferenceStart, rid); - if (videoConfResponse.success && videoConfResponse.data) { + if (videoConfResponse.success) { if (direct && videoConfResponse.data.type === 'direct') { yield call(callUser, { rid, uid: videoConfResponse.data.calleeId, callId: videoConfResponse.data.callId }); } else { @@ -187,10 +187,6 @@ function* initCall({ payload: { mic, cam, direct, rid } }: { payload: TCallProps yield call(hideActionSheetRef); yield put(setCalling(false)); } - } else { - yield put(setCalling(false)); - yield call(hideActionSheetRef); - showErrorAlert(i18n.t('error-init-video-conf')); } } catch (e) { yield put(setCalling(false)); diff --git a/app/views/RoomInfoView/index.tsx b/app/views/RoomInfoView/index.tsx index 1dc08957c6a..1e8b14ea772 100644 --- a/app/views/RoomInfoView/index.tsx +++ b/app/views/RoomInfoView/index.tsx @@ -236,7 +236,6 @@ const RoomInfoView = (): ReactElement => { try { const result = await createDirectMessage(roomUser.username); if (result.success) return resolve({ ...roomUser, rid: result.room.rid }); - return resolve(); } catch (e) { reject(e); } diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index da3a9a83572..50f838344dd 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -351,8 +351,6 @@ class ThreadMessagesView extends Component { try { const result = await getUserPreferences(); if (result.success) { + setLoading(false); setPreferences(result.preferences); } } catch (error) { - log(error); - } finally { setLoading(false); + log(error); } } getPreferences(); From 83226a4572714e439f44e3daa1e5bc7cc4de055a Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:55:21 +0530 Subject: [PATCH 158/203] test fix --- app/ee/omnichannel/lib/index.test.ts | 33 ++----------------- app/ee/omnichannel/lib/index.ts | 1 - .../sendFileMessage/sendFileMessageV2.ts | 5 ++- app/lib/methods/subscriptions/room.test.ts | 26 ++++++++------- app/lib/methods/subscriptions/room.ts | 6 ++-- app/lib/services/sdk.test.ts | 30 +++++++++++++++++ app/lib/services/sdk.ts | 16 +++++++++ 7 files changed, 70 insertions(+), 47 deletions(-) diff --git a/app/ee/omnichannel/lib/index.test.ts b/app/ee/omnichannel/lib/index.test.ts index 9be360f8eb3..e23aae4253c 100644 --- a/app/ee/omnichannel/lib/index.test.ts +++ b/app/ee/omnichannel/lib/index.test.ts @@ -55,14 +55,12 @@ import { store } from '../../../lib/store/auxStore'; import { compareServerVersion } from '../../../lib/methods/helpers'; import EventEmitter from '../../../lib/methods/helpers/events'; import subscribeInquiry from './subscriptions/inquiry'; -import log from '../../../lib/methods/helpers/log'; const mockSdkPost = sdk.post as jest.Mock; const mockMethodCallWrapper = sdk.methodCallWrapper as jest.Mock; const mockCompareServerVersion = compareServerVersion as jest.Mock; const mockAddEventListener = EventEmitter.addEventListener as jest.Mock; const mockSubscribeInquiry = subscribeInquiry as jest.Mock; -const mockLog = log as jest.Mock; beforeEach(() => { jest.clearAllMocks(); @@ -158,31 +156,7 @@ describe('isOmnichannelStatusAvailable', () => { }); describe('Omnichannel.subscribeInquiry', () => { - it('does not produce an unhandled rejection when subscribeInquiry() rejects', async () => { - const rejection = new Error('inquiry: @subscribeInquiry user.id not found'); - mockSubscribeInquiry.mockRejectedValueOnce(rejection); - - jest.isolateModules(() => { - require('./index'); - }); - - const subscribeListener = mockAddEventListener.mock.calls.find(([event]) => event === 'INQUIRY_SUBSCRIBE')?.[1]; - expect(subscribeListener).toBeDefined(); - - const unhandled = jest.fn(); - process.on('unhandledRejection', unhandled); - - subscribeListener(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - - process.off('unhandledRejection', unhandled); - expect(unhandled).not.toHaveBeenCalled(); - expect(mockLog).toHaveBeenCalledWith(rejection); - }); - - it('stops the previous inquiry subscription before creating a new one on repeated INQUIRY_SUBSCRIBE', async () => { + it('subscribes a fresh inquiry on each INQUIRY_SUBSCRIBE event', async () => { const firstStop = jest.fn(); const secondStop = jest.fn(); mockSubscribeInquiry.mockResolvedValueOnce({ stop: firstStop }).mockResolvedValueOnce({ stop: secondStop }); @@ -198,13 +172,12 @@ describe('Omnichannel.subscribeInquiry', () => { await Promise.resolve(); await Promise.resolve(); - expect(firstStop).not.toHaveBeenCalled(); - subscribeListener(); await Promise.resolve(); await Promise.resolve(); - expect(firstStop).toHaveBeenCalledTimes(1); + expect(mockSubscribeInquiry).toHaveBeenCalledTimes(2); + expect(firstStop).not.toHaveBeenCalled(); expect(secondStop).not.toHaveBeenCalled(); }); }); diff --git a/app/ee/omnichannel/lib/index.ts b/app/ee/omnichannel/lib/index.ts index 609cbe65c75..4fc1a82dcb2 100644 --- a/app/ee/omnichannel/lib/index.ts +++ b/app/ee/omnichannel/lib/index.ts @@ -3,7 +3,6 @@ import { compareServerVersion } from '../../../lib/methods/helpers'; import EventEmitter from '../../../lib/methods/helpers/events'; import { store as reduxStore } from '../../../lib/store/auxStore'; import subscribeInquiry from './subscriptions/inquiry'; -import log from '../../../lib/methods/helpers/log'; export const isOmnichannelStatusAvailable = (statusLivechat: string | undefined): boolean => statusLivechat === 'available'; diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts index 8fefbe75acc..4265d0b6412 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts @@ -61,7 +61,7 @@ export async function sendFileMessageV2( if (getContent) { content = await getContent(response.file._id, response.file.url); } - await fetch(`${server}/api/v1/rooms.mediaConfirm/${rid}/${response.file._id}`, { + const confirmResponse = await fetch(`${server}/api/v1/rooms.mediaConfirm/${rid}/${response.file._id}`, { method: 'POST', headers: { ...headers, @@ -75,6 +75,9 @@ export async function sendFileMessageV2( content }) }); + if (!confirmResponse.ok) { + throw new Error(`mediaConfirm failed with status ${confirmResponse.status}`); + } await db.write(async () => { await uploadRecord?.destroyPermanently(); }); diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts index 20a5200d5e1..d52dc6abec1 100644 --- a/app/lib/methods/subscriptions/room.test.ts +++ b/app/lib/methods/subscriptions/room.test.ts @@ -4,6 +4,7 @@ import { clearUserTyping } from '../../../actions/usersTyping'; const mockSubscribeRoom = jest.fn, [string]>(() => Promise.resolve([])); const mockOnConnectionStatus = jest.fn<() => void, [(status: string) => void]>(() => jest.fn()); +const mockOnLogin = jest.fn<() => void, [() => void]>(() => jest.fn()); const mockOnStreamData = jest.fn, [string, (...args: unknown[]) => void]>(() => Promise.resolve({ stop: jest.fn() }) ); @@ -12,6 +13,7 @@ jest.mock('../../services/sdk', () => ({ default: { subscribeRoom: (rid: string) => mockSubscribeRoom(rid), onConnectionStatus: (cb: (status: string) => void) => mockOnConnectionStatus(cb), + onLogin: (cb: () => void) => mockOnLogin(cb), onStreamData: (event: string, cb: (...args: unknown[]) => void) => mockOnStreamData(event, cb) } })); @@ -140,19 +142,26 @@ describe('RoomSubscription', () => { expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); }); - it('wires handleConnected/handleClose through onConnectionStatus, not onStreamData', async () => { + it('wires handleClose through onConnectionStatus, not onStreamData', async () => { await sub.subscribe(); expect(mockOnConnectionStatus).toHaveBeenCalledTimes(1); const statusCallback = mockOnConnectionStatus.mock.calls[0][0]; + statusCallback('closed'); + expect(mockStoreDispatch).toHaveBeenCalledWith(clearUserTyping()); + }); + + it('wires handleLogin through onLogin, not the pre-auth onConnectionStatus "connected" status', async () => { + await sub.subscribe(); + + expect(mockOnLogin).toHaveBeenCalledWith(sub.handleLogin); + mockSubscribeRoom.mockClear(); + const statusCallback = mockOnConnectionStatus.mock.calls[0][0]; statusCallback('connected'); await Promise.resolve(); - expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); - - statusCallback('closed'); - expect(mockStoreDispatch).toHaveBeenCalledWith(clearUserTyping()); + expect(mockSubscribeRoom).not.toHaveBeenCalled(); }); }); @@ -249,13 +258,6 @@ describe('RoomSubscription', () => { expect(mockOnStreamData).not.toHaveBeenCalled(); }); - it('re-subscribes on the authenticated "login" event, not the pre-auth "connected" event', async () => { - await sub.subscribe(); - - expect(mockOnStreamData).toHaveBeenCalledWith('login', sub.handleLogin); - expect(mockOnStreamData).not.toHaveBeenCalledWith('connected', expect.anything()); - }); - it('survives a poisoned subscription array (undefined entry from a rejected sub) and still re-subscribes', async () => { // A pre-auth subscribe rejected by the server (nosub) resolves to undefined inside Promise.all. const freshSub = { unsubscribe: jest.fn(() => Promise.resolve()) }; diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index 3bf4612dc99..55779b3abd7 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -35,6 +35,7 @@ export default class RoomSubscription { private isAlive: boolean; private promises?: Promise; private connectionStatusListener?: () => void; + private loginListener?: () => void; private notifyRoomListener?: Promise; private messageReceivedListener?: Promise; @@ -51,13 +52,11 @@ export default class RoomSubscription { this.promises = sdk.subscribeRoom(this.rid); this.connectionStatusListener = sdk.onConnectionStatus(status => { - if (status === 'connected') { - this.handleLogin(); - } if (['disconnected', 'closed'].includes(status)) { this.handleClose(); } }); + this.loginListener = sdk.onLogin(this.handleLogin); this.notifyRoomListener = sdk.onStreamData('stream-notify-room', this.handleNotifyRoomReceived); this.messageReceivedListener = sdk.onStreamData('stream-room-messages', this.handleMessageReceived); if (!this.isAlive) { @@ -82,6 +81,7 @@ export default class RoomSubscription { } reduxStore.dispatch(clearUserTyping()); this.connectionStatusListener?.(); + this.loginListener?.(); this.removeListener(this.notifyRoomListener); this.removeListener(this.messageReceivedListener); }; diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index a4b23eddf92..1ed09b978d4 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -787,6 +787,36 @@ describe('Sdk.onStreamData', () => { }); }); +describe('Sdk.onLogin', () => { + it('returns a noop unsubscribe when sdk.current is undefined', () => { + setInternalSdk(undefined); + const stop = sdk.onLogin(jest.fn()); + expect(typeof stop).toBe('function'); + expect(() => stop()).not.toThrow(); + }); + + it('subscribes via account.on("uid", ...) and exposes the handle as stop', () => { + const stopHandle = jest.fn(); + const on = jest.fn().mockReturnValue(stopHandle); + setInternalSdk({ account: { on } }); + const stop = sdk.onLogin(jest.fn()); + expect(on).toHaveBeenCalledWith('uid', expect.any(Function)); + expect(stop).toBe(stopHandle); + }); + + it('invokes the callback only when uid is set (skips the logout emission)', () => { + const callback = jest.fn(); + const on = jest.fn((_: string, handler: (uid?: string) => void) => { + handler(undefined); + handler('u1'); + return jest.fn(); + }); + setInternalSdk({ account: { on } }); + sdk.onLogin(callback); + expect(callback).toHaveBeenCalledTimes(1); + }); +}); + describe('Sdk.initialize', () => { it('calls DDPSDK.create with the server URL and stores it', () => { const handleTwoFactorChallenge = jest.fn(); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 620b7b0cff2..b7cf050d230 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -392,6 +392,22 @@ class Sdk { return this.current.connection.on('connection', callback); } + /** + * Fires once re-authentication actually completes (uid set), not on raw socket 'connected' - + * ddp-client's own reconnect handler calls account.loginWithToken() without awaiting it, so + * subscribing on 'connected' races the login and gets rejected (nosub) by the server. + */ + onLogin(callback: () => void): () => void { + if (!this.current) { + return () => {}; + } + return this.current.account.on('uid', uid => { + if (uid) { + callback(); + } + }); + } + stream(...args: Parameters) { return this.current?.stream(...args); } From cd87f222b6f39403374fe0f04f015d36447a2f1c Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:28:17 +0530 Subject: [PATCH 159/203] remove another unnecessary change --- app/views/AddExistingChannelView/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/AddExistingChannelView/index.tsx b/app/views/AddExistingChannelView/index.tsx index 4bdcff117e8..11546f34b28 100644 --- a/app/views/AddExistingChannelView/index.tsx +++ b/app/views/AddExistingChannelView/index.tsx @@ -141,13 +141,12 @@ const AddExistingChannelView = () => { logEvent(events.CT_ADD_ROOM_TO_TEAM); const result = await addRoomsToTeam({ rooms: selected, teamId }); if (result.success) { + sendLoadingEvent({ visible: false }); Navigation.resetTo(); } } catch (e: any) { logEvent(events.CT_ADD_ROOM_TO_TEAM_F); showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {}); - log(e); - } finally { sendLoadingEvent({ visible: false }); } }; From f3adec53940ac92dfd70fd7f55d58e4f54976783 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:21:22 +0530 Subject: [PATCH 160/203] test improve --- app/lib/services/connect.test.ts | 58 ++++++++++++++++++++++++++------ app/lib/services/connect.ts | 2 +- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 47d2c243759..f8f23a37811 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -29,31 +29,44 @@ const mockSdkLogin = jest.fn(); const mockAccountUser: { value: any } = { value: undefined }; jest.mock('./sdk', () => { - const state: { server: string | undefined; currentEnabled: boolean } = { server: undefined, currentEnabled: true }; + const state: { + server: string | undefined; + currentEnabled: boolean; + connection: { on: any; connect: any; checkAndReopen: any } | undefined; + } = { + server: undefined, + currentEnabled: true, + connection: undefined + }; + const makeConnection = () => ({ + on: (event: string, cb: any) => mockConnectionOn(event, cb), + connect: () => mockConnectionConnect(), + checkAndReopen: () => mockConnectionCheckAndReopen() + }); return { __esModule: true, default: { get server() { return state.server; }, - disconnect: (...args: any[]) => mockSdkDisconnect(...args), - // Mirrors the real sdk.ts: initialize() synchronously assigns the server before - // resolving, so `sdk.server` reflects the in-flight connect() call's target. + disconnect: (...args: any[]) => { + state.connection = undefined; + return mockSdkDisconnect(...args); + }, + // Mirrors the real sdk.ts: initialize() yields a brand-new connection instance each + // call, so handlers attached by a previous connect() are discarded with the old instance. initialize: (s: string) => { state.server = s; + state.connection = makeConnection(); return mockSdkInitialize(s); }, onCollection: (...args: any[]) => mockSdkOnCollection(...args), login: (...args: any[]) => mockSdkLogin(...args), get current() { - if (!state.currentEnabled) return undefined; + if (!state.currentEnabled || !state.connection) return undefined; return { account: { user: mockAccountUser.value }, - connection: { - on: (event: string, cb: any) => mockConnectionOn(event, cb), - connect: () => mockConnectionConnect(), - checkAndReopen: () => mockConnectionCheckAndReopen() - } + connection: state.connection }; } }, @@ -62,7 +75,8 @@ jest.mock('./sdk', () => { }, __setCurrentEnabled: (v: boolean) => { state.currentEnabled = v; - } + }, + __getCurrentConnection: () => state.connection }; }); @@ -78,6 +92,7 @@ jest.mock('../../ee/omnichannel/actions/inquiry', () => ({ const sdkMock = jest.requireMock('./sdk') as { __setServer: (v: string | undefined) => void; __setCurrentEnabled: (v: boolean) => void; + __getCurrentConnection: () => { on: any; connect: any; checkAndReopen: any } | undefined; }; // --- Store mock --- @@ -636,6 +651,27 @@ describe('connect — pendingHangups drain on reconnect', () => { }); }); +describe('connect — listener lifecycle across reconnects', () => { + it('attaches handlers to a fresh connection instance each connect, so old handlers are dropped (no leak)', async () => { + sdkMock.__setServer(undefined); + await connect({ server: 'https://a.example.com' }); + const connectionA = sdkMock.__getCurrentConnection(); + + sdkMock.__setServer(undefined); + await connect({ server: 'https://b.example.com' }); + const connectionB = sdkMock.__getCurrentConnection(); + + // Each connect() renders its connection.on('connection', …) on its own instance. + // Because the new SDK creates a fresh connection per connect() and disconnect() drops + // the previous one, connectionA (and the handlers attached to it) is unreachable after + // the second connect — exactly why the old `*.then(stopListener)` teardown is unnecessary. + expect(connectionA).toBeDefined(); + expect(connectionB).toBeDefined(); + expect(connectionA).not.toBe(connectionB); + expect(sdkMock.__getCurrentConnection()).toBe(connectionB); + }); +}); + // ───────────────────────────────────────────────────────────────────────────── describe('checkAndReopen', () => { afterEach(() => { diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index ecf4bb52240..3b2bd29507a 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -117,7 +117,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo const settingsCollection = db.get('settings'); // Check if the _id exists in defaultSettings - if (Object.hasOwn(defaultSettings, _id)) { + if (defaultSettings.hasOwnProperty(_id)) { try { const settingsRecord = await settingsCollection.find(_id); // @ts-ignore From 8cb18e74ff24632be88a04b1e78bb3b5db599f46 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 01:51:35 +0530 Subject: [PATCH 161/203] some changes --- .../__tests__/ddpConnectionPatch.test.ts | 93 +++++++++++++++++++ app/lib/services/connect.test.ts | 41 ++++++++ app/lib/services/connect.ts | 5 - app/lib/services/restApi.test.ts | 50 ++++++++++ app/lib/services/restApi.ts | 18 +++- app/lib/services/sdk.test.ts | 14 ++- app/lib/services/sdk.ts | 8 +- 7 files changed, 213 insertions(+), 16 deletions(-) diff --git a/app/lib/services/__tests__/ddpConnectionPatch.test.ts b/app/lib/services/__tests__/ddpConnectionPatch.test.ts index 617ce485309..830b05f19f6 100644 --- a/app/lib/services/__tests__/ddpConnectionPatch.test.ts +++ b/app/lib/services/__tests__/ddpConnectionPatch.test.ts @@ -156,4 +156,97 @@ describe('patched ConnectionImpl (patches/@rocket.chat+ddp-client+0.3.51.patch)' // Still only one extra socket was ever opened for the two concurrent calls. expect(FakeWebSocket.instances).toHaveLength(2); }); + + describe('probe()', () => { + it('resolves true when a pong is received', async () => { + const { client, connection } = buildConnection(); + const connectPromise = connection.connect(); + const ws = FakeWebSocket.instances[0]; + ws.open(); + completeHandshake(ws, 'session-1'); + await connectPromise; + + let pongCb: ((data: unknown) => void) | undefined; + (client as any).on = jest.fn((ev: string, cb: (data: unknown) => void) => { + if (ev === 'pong') pongCb = cb; + return () => {}; + }); + (client as any).ping = jest.fn(() => { + pongCb?.({ id: 'x' }); + }); + + await expect(connection.probe()).resolves.toBe(true); + }); + + it('resolves false when no pong arrives within the timeout', async () => { + const { client, connection } = buildConnection(); + const connectPromise = connection.connect(); + const ws = FakeWebSocket.instances[0]; + ws.open(); + completeHandshake(ws, 'session-1'); + await connectPromise; + + jest.useFakeTimers(); + (client as any).on = jest.fn(() => () => {}); + (client as any).ping = jest.fn(); + const p = connection.probe(50); + jest.advanceTimersByTime(100); + await expect(p).resolves.toBe(false); + jest.useRealTimers(); + }); + + it('resolves false when the client has no ping/on', async () => { + const { client, connection } = buildConnection(); + const connectPromise = connection.connect(); + const ws = FakeWebSocket.instances[0]; + ws.open(); + completeHandshake(ws, 'session-1'); + await connectPromise; + + (client as any).ping = undefined; + (client as any).on = undefined; + + await expect(connection.probe()).resolves.toBe(false); + }); + }); + + describe('checkAndReopen()', () => { + it('forces a reopen when status is not connected', async () => { + const { connection } = buildConnection(); + // status defaults to 'idle' + const reopen = jest.spyOn(connection, 'forceReopen').mockResolvedValue(true); + + await expect(connection.checkAndReopen()).resolves.toBe(true); + expect(reopen).toHaveBeenCalled(); + }); + + it('returns true when the probe reports the socket alive', async () => { + const { client, connection } = buildConnection(); + const connectPromise = connection.connect(); + const ws = FakeWebSocket.instances[0]; + ws.open(); + completeHandshake(ws, 'session-1'); + await connectPromise; + + const probe = jest.spyOn(connection, 'probe').mockResolvedValue(true); + + await expect(connection.checkAndReopen()).resolves.toBe(true); + expect(probe).toHaveBeenCalled(); + }); + + it('forces a reopen when the probe reports the socket dead', async () => { + const { client, connection } = buildConnection(); + const connectPromise = connection.connect(); + const ws = FakeWebSocket.instances[0]; + ws.open(); + completeHandshake(ws, 'session-1'); + await connectPromise; + + jest.spyOn(connection, 'probe').mockResolvedValue(false); + const reopen = jest.spyOn(connection, 'forceReopen').mockResolvedValue(true); + + await expect(connection.checkAndReopen()).resolves.toBe(true); + expect(reopen).toHaveBeenCalled(); + }); + }); }); diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index f8f23a37811..71dbf04492c 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -80,6 +80,11 @@ jest.mock('./sdk', () => { }; }); +const mockCreateAndConnect = jest.fn(); +jest.mock('@rocket.chat/ddp-client', () => ({ + DDPSDK: { createAndConnect: (...args: unknown[]) => mockCreateAndConnect(...args) } +})); + const mockTwoFactor = jest.fn(); jest.mock('./twoFactor', () => ({ twoFactor: (...args: any[]) => mockTwoFactor(...args) @@ -1045,3 +1050,39 @@ describe('connect — collection handlers guard against fieldless payloads', () await expect(handler({ msg: 'added' })).resolves.toBeUndefined(); }); }); + +describe('getWebsocketInfo', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns success when the probe socket connects', async () => { + const { getWebsocketInfo } = require('./connect'); + mockCreateAndConnect.mockResolvedValue({ connection: { close: jest.fn() } }); + + const result = await getWebsocketInfo({ server: 'https://example.com' }); + + expect(result).toEqual({ success: true }); + expect(mockCreateAndConnect).toHaveBeenCalledWith('https://example.com'); + }); + + it('returns a disabled message when the server rejects with 400', async () => { + const { getWebsocketInfo } = require('./connect'); + mockCreateAndConnect.mockRejectedValue(new Error('Error: 400 bad request')); + + const result = await getWebsocketInfo({ server: 'https://example.com' }); + + expect(result.success).toBe(false); + expect(result.message).toMatch(/disabled/i); + }); + + it('falls back to the error message for non-400 failures', async () => { + const { getWebsocketInfo } = require('./connect'); + mockCreateAndConnect.mockRejectedValue(new Error('boom')); + + const result = await getWebsocketInfo({ server: 'https://example.com' }); + + expect(result.success).toBe(false); + expect(result.message).toBe('boom'); + }); +}); diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 3b2bd29507a..e7360c9740e 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -273,10 +273,6 @@ async function connect({ server, logoutOnError = false }: { server: string; logo } } -function stopListener(listener: any): boolean { - return listener && listener.stop(); -} - async function login(credentials: ICredentials): Promise { const result = await sdk.login(credentials); const { me } = result; @@ -530,7 +526,6 @@ export { connect, disconnect, getWebsocketInfo, - stopListener, getLoginServices, determineAuthType }; diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index 94de505a17f..44147e57546 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -10,6 +10,8 @@ let mockSdkCurrent: unknown = {}; jest.mock('./sdk', () => ({ __esModule: true, default: { + server: 'https://example.com', + getHeaders: () => ({}), get: (...args: unknown[]) => mockSdkGet(...args), post: (...args: unknown[]) => mockSdkPost(...args), get current() { @@ -288,3 +290,51 @@ describe('registerPushToken', () => { expect(Object.prototype.hasOwnProperty.call(payload, 'voipToken')).toBe(false); }); }); + +describe('usersAutoComplete', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('JSON-stringifies the selector before sending it', () => { + const { usersAutoComplete } = require('./restApi'); + usersAutoComplete({ term: 'abc', exceptions: [] }); + expect(mockSdkGet).toHaveBeenCalledWith('/v1/users.autocomplete', { + selector: JSON.stringify({ term: 'abc', exceptions: [] }) + }); + }); +}); + +describe('removePushToken', () => { + let fetchMock: jest.Mock; + + beforeEach(() => { + fetchMock = jest.fn().mockResolvedValue({ ok: true } as Response); + // @ts-ignore + global.fetch = fetchMock; + (require('../notifications').getDeviceToken as jest.Mock).mockReturnValue('device-token'); + }); + + afterEach(() => { + // @ts-ignore + delete global.fetch; + }); + + it('sends a DELETE with the token in the request body', async () => { + const { removePushToken } = require('./restApi'); + const result = await removePushToken(); + expect(result).toBe(true); + expect(fetchMock).toHaveBeenCalledWith('https://example.com/api/v1/push.token', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token: 'device-token' }) + }); + }); + + it('returns undefined and does not fetch when there is no device token', async () => { + (require('../notifications').getDeviceToken as jest.Mock).mockReturnValue(undefined); + const { removePushToken } = require('./restApi'); + expect(await removePushToken()).toBeUndefined(); + expect(fetchMock).not.toHaveBeenCalled(); + }); +}); diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index b2f390ea070..5034078eb06 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -477,7 +477,7 @@ export const getDepartments = (args?: { count: number; offset: number; text: str export const usersAutoComplete = (selector: any) => // RC 2.4.0 - sdk.get('/v1/users.autocomplete', { selector }); + sdk.get('/v1/users.autocomplete', { selector: JSON.stringify(selector) }); export const getRoutingConfig = async (): Promise<{ previewRoom: boolean; @@ -1160,16 +1160,24 @@ export const removePushToken = async (): Promise => { if (token) { lastToken = ''; lastVoipToken = ''; + const serverUrl = sdk.server; + if (!serverUrl) { + return false; + } try { - // RC 0.60.0 - const response = await sdk.delete('/v1/push.token', { token }); - return response?.success === true; + // RC 0.60.0 — api-client's `delete` drops the request body, but the server + // requires `token` in the body, so send it via the local fetch helper (cf. editMediaMessage). + const response = await fetch(`${serverUrl}/api/v1/push.token`, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ token }) + }); + return response.ok; } catch (e) { log(e); return false; } } - return false; }; // RC 6.6.0 diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index 1ed09b978d4..c0eeb6cda1e 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -156,6 +156,16 @@ describe('Sdk.subscribeNotifyUser', () => { expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-user', 'override-user/message'); expect(fake.__subscribe).not.toHaveBeenCalledWith('stream-notify-user', 'logged-in-user/message'); }); + + it('uses the explicit userId even when no user is logged in', () => { + // Guards the reconnect/forceReopen timing gap: callers pass userId explicitly + // so subscriptions are established even before account.user is re-populated. + const fake = buildFakeSdkWithSubscribe(undefined); + setInternalSdk(fake); + const handles = sdk.subscribeNotifyUser('explicit-user'); + expect(handles).toHaveLength(NOTIFY_USER_EVENTS.length); + expect(fake.__subscribe).toHaveBeenCalledWith('stream-notify-user', 'explicit-user/message'); + }); }); describe('Sdk.login', () => { @@ -227,8 +237,8 @@ describe('Sdk.login', () => { await sdk.login({ user: 'john', password: 'secret' }); await sdk.logout(); const headers = sdk.getHeaders(); - expect(headers['X-Auth-Token']).toBe(''); - expect(headers['X-User-Id']).toBe(''); + expect(headers).not.toHaveProperty('X-Auth-Token'); + expect(headers).not.toHaveProperty('X-User-Id'); }); it('sets the auth headers from a resume-token (deep link) login response', async () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index b7cf050d230..9a2b36f86e1 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -458,10 +458,10 @@ class Sdk { log(new Error('Sdk.logout(): account.logout() timed out after 5s; server session may still be valid')); } } - this.setHeaders({ - 'X-Auth-Token': '', - 'X-User-Id': '' - }); + const next = { ...this.headers }; + delete next['X-Auth-Token']; + delete next['X-User-Id']; + this.headers = next; } } From 3104d5dd2f07673c4b80784ee671f0a84f1c160e Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 01:55:45 +0530 Subject: [PATCH 162/203] k one more patch this time --- app/lib/services/restApi.test.ts | 27 +++++--------------- app/lib/services/restApi.ts | 15 +++-------- patches/@rocket.chat+api-client+0.2.56.patch | 20 +++++++++++++++ 3 files changed, 30 insertions(+), 32 deletions(-) create mode 100644 patches/@rocket.chat+api-client+0.2.56.patch diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index 44147e57546..c7b3c69b333 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -5,15 +5,15 @@ import { findOrCreateInvite, mediaCallsStateSignals } from './restApi'; const mockSdkGet = jest.fn(); const mockSdkPost = jest.fn(); +const mockSdkDelete = jest.fn(); let mockSdkCurrent: unknown = {}; jest.mock('./sdk', () => ({ __esModule: true, default: { - server: 'https://example.com', - getHeaders: () => ({}), get: (...args: unknown[]) => mockSdkGet(...args), post: (...args: unknown[]) => mockSdkPost(...args), + delete: (...args: unknown[]) => mockSdkDelete(...args), get current() { return mockSdkCurrent; } @@ -306,35 +306,22 @@ describe('usersAutoComplete', () => { }); describe('removePushToken', () => { - let fetchMock: jest.Mock; - beforeEach(() => { - fetchMock = jest.fn().mockResolvedValue({ ok: true } as Response); - // @ts-ignore - global.fetch = fetchMock; + mockSdkDelete.mockResolvedValue({ success: true }); (require('../notifications').getDeviceToken as jest.Mock).mockReturnValue('device-token'); }); - afterEach(() => { - // @ts-ignore - delete global.fetch; - }); - - it('sends a DELETE with the token in the request body', async () => { + it('sends a DELETE to /v1/push.token with the token in the body', async () => { const { removePushToken } = require('./restApi'); const result = await removePushToken(); expect(result).toBe(true); - expect(fetchMock).toHaveBeenCalledWith('https://example.com/api/v1/push.token', { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ token: 'device-token' }) - }); + expect(mockSdkDelete).toHaveBeenCalledWith('/v1/push.token', { token: 'device-token' }); }); - it('returns undefined and does not fetch when there is no device token', async () => { + it('returns undefined and does not call delete when there is no device token', async () => { (require('../notifications').getDeviceToken as jest.Mock).mockReturnValue(undefined); const { removePushToken } = require('./restApi'); expect(await removePushToken()).toBeUndefined(); - expect(fetchMock).not.toHaveBeenCalled(); + expect(mockSdkDelete).not.toHaveBeenCalled(); }); }); diff --git a/app/lib/services/restApi.ts b/app/lib/services/restApi.ts index 5034078eb06..c642f97e9d1 100644 --- a/app/lib/services/restApi.ts +++ b/app/lib/services/restApi.ts @@ -1160,19 +1160,10 @@ export const removePushToken = async (): Promise => { if (token) { lastToken = ''; lastVoipToken = ''; - const serverUrl = sdk.server; - if (!serverUrl) { - return false; - } try { - // RC 0.60.0 — api-client's `delete` drops the request body, but the server - // requires `token` in the body, so send it via the local fetch helper (cf. editMediaMessage). - const response = await fetch(`${serverUrl}/api/v1/push.token`, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ token }) - }); - return response.ok; + // RC 0.60.0 + const response = await sdk.delete('/v1/push.token', { token }); + return response?.success === true; } catch (e) { log(e); return false; diff --git a/patches/@rocket.chat+api-client+0.2.56.patch b/patches/@rocket.chat+api-client+0.2.56.patch new file mode 100644 index 00000000000..d4777907be4 --- /dev/null +++ b/patches/@rocket.chat+api-client+0.2.56.patch @@ -0,0 +1,20 @@ +--- a/node_modules/@rocket.chat/api-client/dist/index.js ++++ b/node_modules/@rocket.chat/api-client/dist/index.js +@@ -128,9 +128,14 @@ + return response.json(); + }); + } +- delete(endpoint_1, _params_1) { +- return __awaiter(this, arguments, void 0, function* (endpoint, _params, options = {}) { +- const response = yield this.send(endpoint, 'DELETE', options !== null && options !== void 0 ? options : {}); ++ delete(endpoint_1, params_1) { ++ return __awaiter(this, arguments, void 0, function* (endpoint, params, _a = {}) { ++ var { headers } = _a, options = __rest(_a, ["headers"]); ++ const isFormData = checkIfIsFormData(params); ++ const response = yield this.send(endpoint, 'DELETE', Object.assign({ body: isFormData ? buildFormData(params) : JSON.stringify(params), headers: Object.assign(Object.assign({ Accept: 'application/json' }, (!isFormData && { 'Content-Type': 'application/json' })), headers) }, options)); ++ if (response.status === 204) { ++ return {}; ++ } + return response.json(); + }); + } From 0a03fd047cd58f2838a51588b4920568cd876752 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 01:57:53 +0530 Subject: [PATCH 163/203] test fix --- app/lib/services/restApi.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/lib/services/restApi.test.ts b/app/lib/services/restApi.test.ts index c7b3c69b333..ffe8951a9f4 100644 --- a/app/lib/services/restApi.test.ts +++ b/app/lib/services/restApi.test.ts @@ -306,13 +306,17 @@ describe('usersAutoComplete', () => { }); describe('removePushToken', () => { + let removePushToken: () => Promise; + beforeEach(() => { + jest.resetModules(); + mockSdkDelete.mockClear(); mockSdkDelete.mockResolvedValue({ success: true }); (require('../notifications').getDeviceToken as jest.Mock).mockReturnValue('device-token'); + removePushToken = require('./restApi').removePushToken; }); it('sends a DELETE to /v1/push.token with the token in the body', async () => { - const { removePushToken } = require('./restApi'); const result = await removePushToken(); expect(result).toBe(true); expect(mockSdkDelete).toHaveBeenCalledWith('/v1/push.token', { token: 'device-token' }); @@ -320,7 +324,6 @@ describe('removePushToken', () => { it('returns undefined and does not call delete when there is no device token', async () => { (require('../notifications').getDeviceToken as jest.Mock).mockReturnValue(undefined); - const { removePushToken } = require('./restApi'); expect(await removePushToken()).toBeUndefined(); expect(mockSdkDelete).not.toHaveBeenCalled(); }); From ba9bb949b03cd85affd19a32d9fd79669ea02210 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 03:45:09 +0530 Subject: [PATCH 164/203] remove main pointing --- package.json | 2 +- pnpm-lock.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 959ba4b3cb7..45a899a7203 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@rocket.chat/core-typings": "^8.3.2", "@rocket.chat/ddp-client": "^0.3.50", "@rocket.chat/message-parser": "0.31.31", - "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto#main", + "@rocket.chat/mobile-crypto": "RocketChat/rocket.chat-mobile-crypto", "@rocket.chat/ui-kit": "^0.39.0", "@zoontek/react-native-navigation-bar": "^1.1.1", "axios": "0.30.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cde3dd6e25..f55e37e7cf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,7 +96,7 @@ importers: specifier: 0.31.31 version: 0.31.31 '@rocket.chat/mobile-crypto': - specifier: RocketChat/rocket.chat-mobile-crypto#main + specifier: RocketChat/rocket.chat-mobile-crypto version: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c(react-native@0.81.5(@babel/core@7.25.9)(@react-native-community/cli@20.0.0(typescript@5.9.3))(@react-native/metro-config@0.81.5(@babel/core@7.25.9))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@rocket.chat/ui-kit': specifier: ^0.39.0 @@ -2468,7 +2468,7 @@ packages: engines: {node: 22.16.0} '@rocket.chat/mobile-crypto@https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c': - resolution: {gitHosted: true, integrity: sha512-v3uaVCYlj9gGZkAZe3JWQkhlGaRyahmnagbMSKtSnl+6WuLNxaPOf0GjJKVuxBdqkXPPhr23JIihVlch3FVeCQ==, tarball: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c} + resolution: {gitHosted: true, tarball: https://codeload.github.com/RocketChat/rocket.chat-mobile-crypto/tar.gz/69a0a250dd7c6ff0808eb659d7202be1cae7fa1c} version: 0.4.0 peerDependencies: react: '*' From 1a4527e0125a920dfde086e190f6bd56085bb164 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 03:47:03 +0530 Subject: [PATCH 165/203] more changes --- app/definitions/rest/v1/auth.ts | 10 +-- .../__tests__/ddpConnectionPatch.test.ts | 4 +- app/lib/services/sdk.ts | 72 ++++++++----------- app/views/ThreadMessagesView/index.tsx | 2 + 4 files changed, 41 insertions(+), 47 deletions(-) diff --git a/app/definitions/rest/v1/auth.ts b/app/definitions/rest/v1/auth.ts index 3924518c5ac..32ce4fe7ecf 100644 --- a/app/definitions/rest/v1/auth.ts +++ b/app/definitions/rest/v1/auth.ts @@ -31,11 +31,13 @@ export interface ILoginDataResponse { me: ILoginMeResponse; } +export interface ILoginResponse { + status: string; + data: ILoginDataResponse; +} + export type AuthEndpoints = { login: { - POST: (params: { user?: string; password?: string; resume?: string; code?: string }) => { - status: string; - data: ILoginDataResponse; - }; + POST: (params: { user?: string; password?: string; resume?: string; code?: string }) => ILoginResponse; }; }; diff --git a/app/lib/services/__tests__/ddpConnectionPatch.test.ts b/app/lib/services/__tests__/ddpConnectionPatch.test.ts index 830b05f19f6..8f22946b947 100644 --- a/app/lib/services/__tests__/ddpConnectionPatch.test.ts +++ b/app/lib/services/__tests__/ddpConnectionPatch.test.ts @@ -221,7 +221,7 @@ describe('patched ConnectionImpl (patches/@rocket.chat+ddp-client+0.3.51.patch)' }); it('returns true when the probe reports the socket alive', async () => { - const { client, connection } = buildConnection(); + const { connection } = buildConnection(); const connectPromise = connection.connect(); const ws = FakeWebSocket.instances[0]; ws.open(); @@ -235,7 +235,7 @@ describe('patched ConnectionImpl (patches/@rocket.chat+ddp-client+0.3.51.patch)' }); it('forces a reopen when the probe reports the socket dead', async () => { - const { client, connection } = buildConnection(); + const { connection } = buildConnection(); const connectPromise = connection.connect(); const ws = FakeWebSocket.instances[0]; ws.open(); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 9a2b36f86e1..a8126133e22 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -16,7 +16,7 @@ import { type PathFor, type ResultFor } from '../../definitions/rest/helpers'; -import { type ILoginDataResponse } from '../../definitions/rest/v1/auth'; +import { type ILoginDataResponse, type ILoginResponse } from '../../definitions/rest/v1/auth'; export async function normalizeResponseError(response: Response): Promise<{ status: number; data: any }> { try { @@ -27,6 +27,10 @@ export async function normalizeResponseError(response: Response): Promise<{ stat } } +// Mirrors @rocket.chat/ddp-client's internal `ConnectionStatus`. It isn't exported by the package yet, +// so we keep a single source of truth here instead of repeating the union inline. +export type ConnectionStatus = 'idle' | 'connecting' | 'connected' | 'failed' | 'closed' | 'disconnected' | 'reconnecting'; + export const NOTIFY_USER_EVENTS = [ 'message', 'notification', @@ -155,7 +159,7 @@ class Sdk { } } - post>( + async post>( endpoint: TPath, params: void extends OperationParams<'POST', MatchPathPattern> ? void @@ -164,20 +168,8 @@ class Sdk { MatchPathPattern > ? void - : Serialized>> - ): Promise>> { - return this.postWithHeaders(endpoint, params, {}); - } - - /** - * Same as `post`, but merges `extraHeaders` into a per-call headers object instead of mutating the shared - * `this.headers`. Used for the 2FA retry so the transient `x-2fa-code`/`x-2fa-method` headers never become - * visible to any other `get`/`post`/`delete` call that might be in flight at the same time. - */ - private async postWithHeaders>( - endpoint: TPath, - params: any, - extraHeaders: Record + : Serialized>>, + extraHeaders: Record = {} ): Promise>> { const isMethodCall = !!endpoint?.includes('/v1/method.call'); try { @@ -209,9 +201,9 @@ class Sdk { const { details } = isMethodCall ? normalized : normalized?.data; try { const totpResult = await twoFactor({ method: details?.method, invalid: errorType === totpInvalid }); - // Recurse (not into `post`) so a retry that itself gets challenged again keeps using - // per-call headers, without ever touching the shared `this.headers`. - return await this.postWithHeaders(endpoint, params, { + // Recurse so a retry that itself gets challenged again keeps using per-call headers, + // without ever touching the shared `this.headers`. + return await this.post(endpoint, params, { 'x-2fa-code': totpResult.twoFactorCode, 'x-2fa-method': totpResult.twoFactorMethod }); @@ -260,13 +252,7 @@ class Sdk { async login(credentials: any): Promise { try { - // /v1/login is a special-cased Rocket.Chat endpoint: it replies with { status, data } - // instead of the { success, data } convention the generic REST types assume, so the - // inferred result type doesn't match the real shape here — cast to the documented one. - const loginResult = (await this.post('/v1/login', credentials)) as unknown as { - status: string; - data: ILoginDataResponse; - }; + const loginResult = (await this.post('/v1/login', credentials)) as unknown as ILoginResponse; if (loginResult?.status !== 'success' || !loginResult.data) { return Promise.reject(new Error('Invalid response from server')); } @@ -383,9 +369,7 @@ class Sdk { }); } - onConnectionStatus( - callback: (status: 'idle' | 'connecting' | 'connected' | 'failed' | 'closed' | 'disconnected' | 'reconnecting') => void - ): () => void { + onConnectionStatus(callback: (status: ConnectionStatus) => void): () => void { if (!this.current) { return () => {}; } @@ -447,21 +431,27 @@ class Sdk { } async logout(): Promise { - if (this.current?.account) { - const TIMEOUT = Symbol('logout-timeout'); - // account.logout() can hang indefinitely on a dead socket; cap it so app-level logout always completes. - const result = await Promise.race([ - this.current.account.logout(), - new Promise(resolve => setTimeout(() => resolve(TIMEOUT), 5000)) - ]); - if (result === TIMEOUT) { - log(new Error('Sdk.logout(): account.logout() timed out after 5s; server session may still be valid')); - } - } const next = { ...this.headers }; delete next['X-Auth-Token']; delete next['X-User-Id']; - this.headers = next; + try { + if (this.current?.account) { + const TIMEOUT = Symbol('logout-timeout'); + // account.logout() can hang indefinitely on a dead socket; cap it so app-level logout always completes. + const result = await Promise.race([ + this.current.account.logout(), + new Promise(resolve => setTimeout(() => resolve(TIMEOUT), 5000)) + ]); + if (result === TIMEOUT) { + log(new Error('Sdk.logout(): account.logout() timed out after 5s; server session may still be valid')); + } + } + } catch (e) { + log(e); + } finally { + // Always clear auth headers, even if logout throws — otherwise stale X-Auth-Token/X-User-Id leak into later calls. + this.headers = next; + } } } diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index 50f838344dd..da3a9a83572 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -351,6 +351,8 @@ class ThreadMessagesView extends Component Date: Tue, 14 Jul 2026 21:05:43 +0530 Subject: [PATCH 166/203] prevent token from being send in header for cross origin url --- app/containers/Avatar/Avatar.tsx | 4 +- app/lib/methods/helpers/fetch.test.ts | 19 ++++++++ app/lib/methods/helpers/fetch.ts | 4 +- .../methods/helpers/getAuthHeaders.test.ts | 45 +++++++++++++++++++ app/lib/methods/helpers/getAuthHeaders.ts | 19 ++++++++ app/lib/methods/helpers/isSameOrigin.test.ts | 24 ++++++++++ app/lib/methods/helpers/isSameOrigin.ts | 11 +++++ 7 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 app/lib/methods/helpers/getAuthHeaders.test.ts create mode 100644 app/lib/methods/helpers/getAuthHeaders.ts create mode 100644 app/lib/methods/helpers/isSameOrigin.test.ts create mode 100644 app/lib/methods/helpers/isSameOrigin.ts diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index 616201bacb0..ab063d3a218 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -4,11 +4,11 @@ import { Image } from 'expo-image'; import Emoji from '../markdown/components/emoji/Emoji'; import { getAvatarURL } from '../../lib/methods/helpers/getAvatarUrl'; +import { getAuthHeaders } from '../../lib/methods/helpers/getAuthHeaders'; import { SubscriptionType } from '../../definitions'; import { type IAvatar } from './interfaces'; import I18n from '../../i18n'; import Touch from '../Touch'; -import sdk from '../../lib/services/sdk'; const Avatar = memo( ({ @@ -81,7 +81,7 @@ const Avatar = memo( style={avatarStyle} source={{ uri, - headers: sdk.getHeaders() + headers: uri ? getAuthHeaders(uri) : undefined }} priority='high' /> diff --git a/app/lib/methods/helpers/fetch.test.ts b/app/lib/methods/helpers/fetch.test.ts index 83a715f52a0..97731889fb1 100644 --- a/app/lib/methods/helpers/fetch.test.ts +++ b/app/lib/methods/helpers/fetch.test.ts @@ -2,6 +2,7 @@ jest.mock('../../services/sdk', () => ({ __esModule: true, default: { + server: 'https://open.rocket.chat', getHeaders: jest.fn(), setBasicAuth: jest.fn() } @@ -65,6 +66,24 @@ describe('helpers/fetch', () => { fetchMock.mockResolvedValueOnce(expected); await expect(customFetch('/api/v1/test')).resolves.toBe(expected); }); + + it('attaches session headers (X-Auth-Token/X-User-Id) for same-origin requests', async () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'X-Auth-Token': 'tok', 'X-User-Id': 'uid' }); + await customFetch('https://open.rocket.chat/api/v1/settings'); + const call = fetchMock.mock.calls[0][1]; + expect(call.headers['X-Auth-Token']).toBe('tok'); + expect(call.headers['X-User-Id']).toBe('uid'); + }); + + it('omits session headers but keeps default headers for cross-origin requests', async () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'User-Agent': 'RC Mobile', 'X-Auth-Token': 'tok', 'X-User-Id': 'uid' }); + await customFetch('https://releases.rocket.chat/v2/server/supportedVersions'); + const call = fetchMock.mock.calls[0][1]; + expect(call.headers['X-Auth-Token']).toBeUndefined(); + expect(call.headers['X-User-Id']).toBeUndefined(); + expect(typeof call.headers['User-Agent']).toBe('string'); + expect(call.headers['User-Agent']).toMatch(/RC Mobile/); + }); }); describe('helpers/fetch setBasicAuth', () => { diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index a27fc5a96e7..2d430c6c320 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -1,5 +1,6 @@ import sdk from '../../services/sdk'; import { type DefaultHeaders, headers } from './defaultHeaders'; +import { getAuthHeaders } from './getAuthHeaders'; export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; @@ -19,6 +20,7 @@ export const setBasicAuth = (basicAuth: string | null): void => { export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; export default (url: string, options: IOptions = {}): Promise => { - const customOptions = { ...options, headers: { ...sdk.getHeaders(), ...(options.headers || {}) } }; + const authHeaders = getAuthHeaders(url); + const customOptions = { ...options, headers: { ...authHeaders, ...(options.headers || {}) } }; return fetch(url, customOptions); }; diff --git a/app/lib/methods/helpers/getAuthHeaders.test.ts b/app/lib/methods/helpers/getAuthHeaders.test.ts new file mode 100644 index 00000000000..833c160b9b8 --- /dev/null +++ b/app/lib/methods/helpers/getAuthHeaders.test.ts @@ -0,0 +1,45 @@ +/* eslint-disable import/first */ +jest.mock('../../services/sdk', () => ({ + __esModule: true, + default: { + server: 'https://open.rocket.chat', + getHeaders: jest.fn() + } +})); + +import { getAuthHeaders } from './getAuthHeaders'; +import sdk from '../../services/sdk'; + +describe('getAuthHeaders', () => { + beforeEach(() => { + (sdk.getHeaders as jest.Mock).mockReset(); + }); + + it('returns full headers including the session token for same-origin URLs', () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'User-Agent': 'RC Mobile', 'X-Auth-Token': 'tok', 'X-User-Id': 'uid' }); + const headers = getAuthHeaders('https://open.rocket.chat/api/v1/settings'); + expect(headers['X-Auth-Token']).toBe('tok'); + expect(headers['X-User-Id']).toBe('uid'); + expect(headers['User-Agent']).toBe('RC Mobile'); + }); + + it('strips only the session token for cross-origin URLs, keeping other headers', () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ + 'User-Agent': 'RC Mobile', + Authorization: 'Basic abc', + 'X-Auth-Token': 'tok', + 'X-User-Id': 'uid' + }); + const headers = getAuthHeaders('https://releases.rocket.chat/v2/server/supportedVersions'); + expect(headers['X-Auth-Token']).toBeUndefined(); + expect(headers['X-User-Id']).toBeUndefined(); + expect(headers['User-Agent']).toBe('RC Mobile'); + expect(headers.Authorization).toBe('Basic abc'); + }); + + it('treats relative URLs as same-origin', () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'X-Auth-Token': 'tok' }); + const headers = getAuthHeaders('/api/v1/x'); + expect(headers['X-Auth-Token']).toBe('tok'); + }); +}); diff --git a/app/lib/methods/helpers/getAuthHeaders.ts b/app/lib/methods/helpers/getAuthHeaders.ts new file mode 100644 index 00000000000..e962fc468e1 --- /dev/null +++ b/app/lib/methods/helpers/getAuthHeaders.ts @@ -0,0 +1,19 @@ +import sdk from '../../services/sdk'; +import { isSameOrigin } from './isSameOrigin'; + +/** + * Returns the request headers for a given URL. + * On same-origin requests the full header set is sent (including the session token). + * On cross-origin requests only the session token (X-Auth-Token/X-User-Id) is stripped, + * keeping non-sensitive headers such as User-Agent (and Basic auth) — matching the + * pre-migration behavior where only User-Agent/Basic auth were ever sent off-origin. + */ +export const getAuthHeaders = (url: string): Record => { + if (isSameOrigin(url, sdk.server)) { + return sdk.getHeaders(); + } + const headers = { ...sdk.getHeaders() }; + delete headers['X-Auth-Token']; + delete headers['X-User-Id']; + return headers; +}; diff --git a/app/lib/methods/helpers/isSameOrigin.test.ts b/app/lib/methods/helpers/isSameOrigin.test.ts new file mode 100644 index 00000000000..fae6638579c --- /dev/null +++ b/app/lib/methods/helpers/isSameOrigin.test.ts @@ -0,0 +1,24 @@ +import { isSameOrigin } from './isSameOrigin'; + +describe('isSameOrigin', () => { + test('returns true for same-origin absolute URLs', () => { + expect(isSameOrigin('https://open.rocket.chat/api/v1/x', 'https://open.rocket.chat')).toBe(true); + }); + + test('returns false for cross-origin absolute URLs', () => { + expect(isSameOrigin('https://releases.rocket.chat/v2/server/supportedVersions', 'https://open.rocket.chat')).toBe(false); + expect(isSameOrigin('https://external.provider.com/avatar/user', 'https://open.rocket.chat')).toBe(false); + }); + + test('returns false when scheme differs', () => { + expect(isSameOrigin('http://open.rocket.chat/avatar/x', 'https://open.rocket.chat')).toBe(false); + }); + + test('returns true for relative URLs', () => { + expect(isSameOrigin('/api/v1/x', 'https://open.rocket.chat')).toBe(true); + }); + + test('returns true when origin is undefined', () => { + expect(isSameOrigin('https://releases.rocket.chat/x')).toBe(true); + }); +}); diff --git a/app/lib/methods/helpers/isSameOrigin.ts b/app/lib/methods/helpers/isSameOrigin.ts new file mode 100644 index 00000000000..5598a9e81a1 --- /dev/null +++ b/app/lib/methods/helpers/isSameOrigin.ts @@ -0,0 +1,11 @@ +export const isSameOrigin = (url: string, origin?: string): boolean => { + if (!origin) { + return true; + } + try { + return new URL(url).origin === new URL(origin).origin; + } catch { + // Relative/internal URL — safe to attach auth headers. + return true; + } +}; From 8dbff828d7be9a9e3b300399788be6e7098c8b2d Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:09:20 +0530 Subject: [PATCH 167/203] added server guard before sdk login --- app/lib/services/sdk.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index a8126133e22..f878864dcba 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -252,12 +252,18 @@ class Sdk { async login(credentials: any): Promise { try { + const { server } = this; const loginResult = (await this.post('/v1/login', credentials)) as unknown as ILoginResponse; if (loginResult?.status !== 'success' || !loginResult.data) { return Promise.reject(new Error('Invalid response from server')); } // Auth is tracked in two places, both required: loginWithToken() sets the DDP-level session used // by methodCall()/subscribe(), while setHeaders() below sets the REST-level auth used by get/post/delete. + // Another server may have been selected while the REST login was in flight — applying this token + // to the wrong sdk instance would authenticate server A's credentials on server B's connection. + if (this.server !== server) { + return Promise.reject(new Error('Server switched during login')); + } await this.current?.account.loginWithToken(loginResult.data.authToken); this.setHeaders({ From 5928660952ce622558efa0a5072a650b04c4fb06 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:22:44 +0530 Subject: [PATCH 168/203] check server before listners start --- app/lib/services/connect.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index e7360c9740e..2ef7e01c07f 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -102,6 +102,12 @@ async function connect({ server, logoutOnError = false }: { server: string; logo }); await sdk.current?.connection.connect(); + // A newer connect() may have switched servers while connect() was in flight — + // bail before wiring the collection listeners onto the wrong sdk instance. + if (sdk.server !== server) { + return; + } + sdk.onCollection('users', (ddpMessage: unknown) => _setUser(ddpMessage as IActiveUsers)); sdk.onCollection( From e14854c9f580436b15e5b2f9c28bbecc905a3ef7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:25:37 +0530 Subject: [PATCH 169/203] inquiry improvement --- .../lib/subscriptions/inquiry.test.ts | 25 +++++++++++++++++++ .../omnichannel/lib/subscriptions/inquiry.ts | 8 +++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts index d4429b47869..4dd06f6aabb 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.test.ts @@ -127,6 +127,31 @@ describe('subscribeInquiry', () => { expect(sdk.subscribe).not.toHaveBeenCalled(); }); + it('does not let a stale invocation push department subscriptions after being superseded by a newer one', async () => { + let resolveFirstDepts: (v: any) => void; + (getAgentDepartments as jest.Mock) + .mockReturnValueOnce( + new Promise(resolve => { + resolveFirstDepts = resolve; + }) + ) + .mockResolvedValueOnce({ success: true, departments: [] }); + + // first invocation: getAgentDepartments left pending + subscribeInquiry(); + + // second invocation supersedes the first (its own getAgentDepartments resolves immediately) + subscribeInquiry(); + await flushPromises(); + (sdk.subscribe as jest.Mock).mockClear(); + + // the first (stale) invocation's promise resolves late - it must be ignored + resolveFirstDepts!({ success: true, departments: [{ departmentId: 'stale-dept' }] }); + await flushPromises(); + + expect(sdk.subscribe).not.toHaveBeenCalledWith('stream-livechat-inquiry-queue-observer', 'department/stale-dept'); + }); + it('rejects when the logged-in user has no id', async () => { (store.getState as jest.Mock).mockReturnValue({ login: { user: {} }, diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.ts b/app/ee/omnichannel/lib/subscriptions/inquiry.ts index abc37480495..6c16f48b8ec 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.ts +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.ts @@ -24,12 +24,14 @@ const removeListener = (listener: any) => listener.stop(); let queueListener: any; let departmentListeners: any[] = []; -let stopped = false; +let activeGeneration = 0; const streamTopic = 'stream-livechat-inquiry-queue-observer'; export default function subscribeInquiry() { - stopped = false; + const generation = ++activeGeneration; + let stopped = false; + const isStale = () => stopped || generation !== activeGeneration; const handleQueueMessageReceived = (ddpMessage: IDdpMessage) => { const [{ type, ...sub }] = ddpMessage.fields.args; @@ -87,7 +89,7 @@ export default function subscribeInquiry() { getAgentDepartments(user.id) .then(result => { - if (stopped) return; + if (isStale()) return; if (result.success) { const { departments } = result; From a21b6589524dd9419cf0634a598e84e3bc7ff4cb Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:49:26 +0530 Subject: [PATCH 170/203] register all listners before connect --- app/lib/services/connect.test.ts | 33 ++++++++++++++++++++++++++++++++ app/lib/services/connect.ts | 12 ++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 71dbf04492c..f3ca2e7bf69 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -656,6 +656,39 @@ describe('connect — pendingHangups drain on reconnect', () => { }); }); +describe('connect — collection listeners register independently of connection.connect() outcome', () => { + const SERVER = 'https://example.com'; + + beforeEach(() => { + jest.clearAllMocks(); + sdkMock.__setServer(undefined); + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + }); + + it('registers collection listeners even when connection.connect() rejects', async () => { + mockConnectionConnect.mockRejectedValueOnce(new Error('socket error')); + + await expect(connect({ server: SERVER })).rejects.toThrow('socket error'); + + expect(mockSdkOnCollection).toHaveBeenCalledWith('stream-force_logout', expect.any(Function)); + }); + + it('registers collection listeners before a hung connection.connect() ever settles', async () => { + mockConnectionConnect.mockReturnValueOnce(new Promise(() => {})); + + // Intentionally not awaited: connection.connect() never resolves/rejects. + connect({ server: SERVER }); + await flushMicrotasks(); + + expect(mockSdkOnCollection).toHaveBeenCalledWith('stream-force_logout', expect.any(Function)); + }); +}); + describe('connect — listener lifecycle across reconnects', () => { it('attaches handlers to a fresh connection instance each connect, so old handlers are dropped (no leak)', async () => { sdkMock.__setServer(undefined); diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 2ef7e01c07f..fccab25689e 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -100,14 +100,8 @@ async function connect({ server, logoutOnError = false }: { server: string; logo store.dispatch(disconnectAction()); } }); - await sdk.current?.connection.connect(); - - // A newer connect() may have switched servers while connect() was in flight — - // bail before wiring the collection listeners onto the wrong sdk instance. - if (sdk.server !== server) { - return; - } - + // Registered before connect() resolves: a rejected or hung connect() must not leave the + // client without listeners once the underlying socket connects or recovers on its own. sdk.onCollection('users', (ddpMessage: unknown) => _setUser(ddpMessage as IActiveUsers)); sdk.onCollection( @@ -273,6 +267,8 @@ async function connect({ server, logoutOnError = false }: { server: string; logo ); sdk.onCollection('stream-force_logout', () => store.dispatch(logout(true))); + + await sdk.current?.connection.connect(); } catch (e) { log(e); throw e; From 0b7808b01907174dd8098eb9ca55bb894063e662 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 22:57:55 +0530 Subject: [PATCH 171/203] more changes --- app/lib/methods/getSettings.ts | 4 ++-- app/lib/services/connect.test.ts | 27 +++++++++++++++++++++++++++ app/lib/services/connect.ts | 9 ++++++++- app/lib/services/sdk.ts | 2 +- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/app/lib/methods/getSettings.ts b/app/lib/methods/getSettings.ts index 1740c934671..ee3fd3227b9 100644 --- a/app/lib/methods/getSettings.ts +++ b/app/lib/methods/getSettings.ts @@ -149,7 +149,7 @@ export function subscribeSettings() { type IData = ISettingsIcon | IPreparedSettings; -export async function getSettings(): Promise { +export async function getSettings(signal?: AbortSignal): Promise { try { const db = database.active; const settingsParams = Object.keys(defaultSettings).filter(key => !loginSettings.includes(key)); @@ -165,7 +165,7 @@ export async function getSettings(): Promise { do { // TODO: why is no-await-in-loop enforced in the first place? /* eslint-disable no-await-in-loop */ - const response = await fetch(`${url}&offset=${offset}`); + const response = await fetch(`${url}&offset=${offset}`, { signal }); const result = await response.json(); if (!result.success) { diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index f3ca2e7bf69..030c1949164 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -530,6 +530,33 @@ describe('connect — connection status handler', () => { expect(mockConnectionOn).not.toHaveBeenCalled(); }); + it('aborts the previous connect() in-flight getSettings when a newer call supersedes it', async () => { + const getSettingsMock = jest.requireMock('../methods/getSettings').getSettings as jest.Mock; + let resolveSettingsA: () => void; + let capturedSignal: AbortSignal | undefined; + getSettingsMock.mockImplementationOnce((signal?: AbortSignal) => { + capturedSignal = signal; + return new Promise(resolve => { + resolveSettingsA = resolve; + }); + }); + + sdkMock.__setServer(undefined); + const connectAPromise = connect({ server: 'https://a.example.com' }); + await Promise.resolve(); // let connect(A) reach the getSettings() await + + // connect(B) supersedes connect(A) while getSettings(A) is still in flight. + sdkMock.__setServer('https://b.example.com'); + await connect({ server: 'https://b.example.com' }); + + // The previous call's REST request must have been signalled to abort so it + // can't write server A's data into the now-active database B. + expect(capturedSignal?.aborted).toBe(true); + + resolveSettingsA!(); + await connectAPromise; + }); + it('dispatches connectSuccess and loginRequest(resume) on reconnect when user has a token', async () => { await connect({ server: SERVER }); const listener = getCapturedConnectionListener(); diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index fccab25689e..6cd9ecbe53a 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -39,12 +39,18 @@ interface IServices { service: string; } +let connectAbortController: AbortController | null = null; + async function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise { if (sdk.server === server) { return; } try { + connectAbortController?.abort(); + connectAbortController = new AbortController(); + const { signal } = connectAbortController; + disconnect(); database.setActiveDB(server); @@ -53,7 +59,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo EventEmitter.emit('INQUIRY_UNSUBSCRIBE'); await sdk.initialize(server); - await getSettings(); + await getSettings(signal); // A newer connect() call may have switched servers while getSettings() was in flight — // bail out rather than wiring up listeners/dispatching against the wrong sdk instance. @@ -430,6 +436,7 @@ async function awaitDdpLoggedIn(timeoutMs: number = 5000): Promise { } function disconnect() { + connectAbortController?.abort(); const result = sdk.disconnect(); mediaSessionInstance.reset(); return result; diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index f878864dcba..10d976ae3d2 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -331,7 +331,7 @@ class Sdk { } subscribe(...args: Parameters) { - return this.current?.client.subscribe(...args); + return this.ensureInitialized().client.subscribe(...args); } subscribeRoom(rid: string): Promise { From 74f5ff4d340bce42d7134e27b89ad68f50348799 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:03:31 +0530 Subject: [PATCH 172/203] handle other active user presence updates --- app/lib/services/connect.test.ts | 54 ++++++++++++++++++++++++++++++++ app/lib/services/connect.ts | 1 + 2 files changed, 55 insertions(+) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 030c1949164..4102329c078 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -5,6 +5,7 @@ import { unsubscribeRooms } from '../methods/subscribeRooms'; import { setUser } from '../../actions/login'; import database from '../database'; import { onRolesChanged } from '../methods/getRoles'; +import { STATUSES } from '../../definitions'; jest.mock('./voip/MediaSessionInstance', () => ({ mediaSessionInstance: { reset: jest.fn(), drainPendingHangups: jest.fn() } @@ -1111,6 +1112,59 @@ describe('connect — collection handlers guard against fieldless payloads', () }); }); +describe('connect — stream-user-presence updates active users', () => { + const SERVER = 'https://example.com'; + + beforeEach(() => { + jest.clearAllMocks(); + sdkMock.__setServer(undefined); + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: null, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + }); + + const fireUserPresence = async (uid: string, status: number, statusText: string) => { + const [handler] = getHandlersByEvent('stream-user-presence'); + handler({ + msg: 'changed', + fields: { uid, args: [[uid, status, statusText, undefined, undefined]] } + }); + }; + + it('dispatches setActiveUsers with the presence for a user who is not the logged-in user', async () => { + await connect({ server: SERVER }); + const setActiveUsersMock = jest.requireMock('../../actions/activeUsers').setActiveUsers; + + await fireUserPresence('other-user-1', 1, 'online'); + + expect(setActiveUsersMock).toHaveBeenCalledWith({ + 'other-user-1': { status: STATUSES[1], statusText: 'online', statusSource: undefined, statusExpiresAt: undefined } + }); + }); + + it('still dispatches setActiveUsers for the logged-in user (in addition to setUser)', async () => { + mockStoreGetState.mockReturnValue({ + meteor: { connected: false }, + login: { user: { id: 'me' }, isAuthenticated: false }, + settings: {}, + server: { version: '6.0.0' } + }); + await connect({ server: SERVER }); + const setActiveUsersMock = jest.requireMock('../../actions/activeUsers').setActiveUsers; + const setUserMock = jest.requireMock('../../actions/login').setUser; + + await fireUserPresence('me', 1, 'online'); + + expect(setActiveUsersMock).toHaveBeenCalledWith({ + me: { status: STATUSES[1], statusText: 'online', statusSource: undefined, statusExpiresAt: undefined } + }); + expect(setUserMock).toHaveBeenCalledWith({ status: STATUSES[1], statusText: 'online', statusSource: undefined, statusExpiresAt: undefined }); + }); +}); + describe('getWebsocketInfo', () => { beforeEach(() => { jest.clearAllMocks(); diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 6cd9ecbe53a..19f5eba6830 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -170,6 +170,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo const [, status, statusText, statusSource, statusExpiresAtRaw] = userStatus; const statusExpiresAt = normalizeStatusExpiresAt(statusExpiresAtRaw); const newStatus = { status: STATUSES[status], statusText, statusSource, statusExpiresAt }; + store.dispatch(setActiveUsers({ [uid]: newStatus })); const { user: loggedUser } = store.getState().login; if (loggedUser && loggedUser.id === uid) { From 279e483ffad2b7c3513637ee4c15aac05ca9eebb Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:03:41 +0530 Subject: [PATCH 173/203] lint fix --- app/lib/services/connect.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 4102329c078..240e21c0e70 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -1126,7 +1126,7 @@ describe('connect — stream-user-presence updates active users', () => { }); }); - const fireUserPresence = async (uid: string, status: number, statusText: string) => { + const fireUserPresence = (uid: string, status: number, statusText: string) => { const [handler] = getHandlersByEvent('stream-user-presence'); handler({ msg: 'changed', @@ -1138,7 +1138,7 @@ describe('connect — stream-user-presence updates active users', () => { await connect({ server: SERVER }); const setActiveUsersMock = jest.requireMock('../../actions/activeUsers').setActiveUsers; - await fireUserPresence('other-user-1', 1, 'online'); + fireUserPresence('other-user-1', 1, 'online'); expect(setActiveUsersMock).toHaveBeenCalledWith({ 'other-user-1': { status: STATUSES[1], statusText: 'online', statusSource: undefined, statusExpiresAt: undefined } @@ -1156,7 +1156,7 @@ describe('connect — stream-user-presence updates active users', () => { const setActiveUsersMock = jest.requireMock('../../actions/activeUsers').setActiveUsers; const setUserMock = jest.requireMock('../../actions/login').setUser; - await fireUserPresence('me', 1, 'online'); + fireUserPresence('me', 1, 'online'); expect(setActiveUsersMock).toHaveBeenCalledWith({ me: { status: STATUSES[1], statusText: 'online', statusSource: undefined, statusExpiresAt: undefined } From b6a636675c340a2e83a09b6783df353df643f5ba Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:04:09 +0530 Subject: [PATCH 174/203] lint fix --- app/lib/services/connect.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 240e21c0e70..997c63c5448 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -1161,7 +1161,12 @@ describe('connect — stream-user-presence updates active users', () => { expect(setActiveUsersMock).toHaveBeenCalledWith({ me: { status: STATUSES[1], statusText: 'online', statusSource: undefined, statusExpiresAt: undefined } }); - expect(setUserMock).toHaveBeenCalledWith({ status: STATUSES[1], statusText: 'online', statusSource: undefined, statusExpiresAt: undefined }); + expect(setUserMock).toHaveBeenCalledWith({ + status: STATUSES[1], + statusText: 'online', + statusSource: undefined, + statusExpiresAt: undefined + }); }); }); From f463753cce96452b423bb62244ea07f9e9369508 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:13:12 +0530 Subject: [PATCH 175/203] check for user login before switching server --- app/sagas/selectServer.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index 00975655bdb..eedde4797a1 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -138,9 +138,12 @@ const getServerInfoSaga = function* getServerInfoSaga({ server, raiseError = tru const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }: ISelectServerAction) { try { if (sdk.server === server) { - yield put(appStart({ root: RootEnum.ROOT_INSIDE })); - yield put(selectServerCancel()); - return; + const isLoggedIn = yield* appSelector(state => state.login.isAuthenticated && state.meteor.connected); + if (isLoggedIn) { + yield put(appStart({ root: RootEnum.ROOT_INSIDE })); + yield put(selectServerCancel()); + return; + } } // SSL Pinning - Read certificate alias and set it to be used by network requests const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`); From b9e427fbefc5b453e3edb1a5fe6685a89f1a3cdb Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:39:06 +0530 Subject: [PATCH 176/203] normalized response --- app/lib/services/sdk.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 10d976ae3d2..b366cfbbd0e 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -191,9 +191,11 @@ class Sdk { } return result; } catch (e: any) { - // @rocket.chat/api-client rejects with the raw fetch Response on REST errors. - // Normalize to { status, data } so callers can read e.data.* - const normalized = !isMethodCall && e instanceof Response ? await normalizeResponseError(e) : e; + // @rocket.chat/api-client rejects with the raw fetch Response on transport failures (network/5xx), + // for both REST and method calls. Normalize it so callers can read e.data.* (REST) or e.error/e.reason + // (method call, which is flat, matching the shape already thrown for method-call business errors above). + const normalizedResponse = e instanceof Response ? await normalizeResponseError(e) : null; + const normalized = isMethodCall ? normalizedResponse?.data ?? e : normalizedResponse ?? e; const errorType = isMethodCall ? normalized?.error : normalized?.data?.errorType; const totpInvalid = 'totp-invalid'; const totpRequired = 'totp-required'; From d77a0dd9ecda742e0663cfb90f9618895dfb0289 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:51:50 +0530 Subject: [PATCH 177/203] user preference change for token --- app/lib/methods/helpers/fetch.test.ts | 9 +++++++-- app/lib/methods/helpers/fetch.ts | 4 ++-- app/lib/services/sdk.test.ts | 14 ++++++++++++++ app/lib/services/sdk.ts | 11 +++++------ app/sagas/selectServer.ts | 2 +- app/views/NewServerView/methods/basicAuth.ts | 2 +- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/app/lib/methods/helpers/fetch.test.ts b/app/lib/methods/helpers/fetch.test.ts index 97731889fb1..35e9184a6b1 100644 --- a/app/lib/methods/helpers/fetch.test.ts +++ b/app/lib/methods/helpers/fetch.test.ts @@ -89,11 +89,16 @@ describe('helpers/fetch', () => { describe('helpers/fetch setBasicAuth', () => { it('delegates to sdk.setBasicAuth', () => { setBasicAuth('dXNlcjpwYXNz'); - expect(sdk.setBasicAuth).toHaveBeenCalledWith('dXNlcjpwYXNz'); + expect(sdk.setBasicAuth).toHaveBeenCalledWith('dXNlcjpwYXNz', undefined); }); it('forwards null to clear basic auth', () => { setBasicAuth(null); - expect(sdk.setBasicAuth).toHaveBeenCalledWith(null); + expect(sdk.setBasicAuth).toHaveBeenCalledWith(null, undefined); + }); + + it('forwards an explicit server so the target-server key is used, not the sdk-tracked one', () => { + setBasicAuth('dXNlcjpwYXNz', 'https://new.example.com'); + expect(sdk.setBasicAuth).toHaveBeenCalledWith('dXNlcjpwYXNz', 'https://new.example.com'); }); }); diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index 2d430c6c320..96a47946ce3 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -13,8 +13,8 @@ interface IOptions { export { headers }; -export const setBasicAuth = (basicAuth: string | null): void => { - sdk.setBasicAuth(basicAuth); +export const setBasicAuth = (basicAuth: string | null, server?: string): void => { + sdk.setBasicAuth(basicAuth, server); }; export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index c0eeb6cda1e..fe40a98c697 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -764,6 +764,20 @@ describe('Sdk header lifecycle', () => { delete (headers as any).Authorization; // mutate the returned copy expect(sdk.getHeaders().Authorization).toBe('Basic dXNlcjpwYXNz'); }); + + it('persists setBasicAuth under an explicitly passed server, not the stale this.serverUrl (switch-server regression)', () => { + (sdk as any).serverUrl = 'https://old.example.com'; + sdk.setBasicAuth('dXNlcjpwYXNz', 'https://new.example.com'); + expect(UserPreferences.setString).toHaveBeenCalledWith('BASIC_AUTH_KEY-https://new.example.com', 'dXNlcjpwYXNz'); + expect(UserPreferences.setString).not.toHaveBeenCalledWith('BASIC_AUTH_KEY-https://old.example.com', expect.anything()); + }); + + it('clears setBasicAuth(null) under an explicitly passed server, not the stale this.serverUrl', () => { + (sdk as any).serverUrl = 'https://old.example.com'; + sdk.setBasicAuth(null, 'https://new.example.com'); + expect(UserPreferences.removeItem).toHaveBeenCalledWith('BASIC_AUTH_KEY-https://new.example.com'); + expect(UserPreferences.removeItem).not.toHaveBeenCalledWith('BASIC_AUTH_KEY-https://old.example.com'); + }); }); describe('Sdk.onStreamData', () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index b366cfbbd0e..067f393fffc 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -120,16 +120,15 @@ class Sdk { return { ...this.headers }; } - setBasicAuth(basicAuth: string | null): void { - const url = this.serverUrl; + setBasicAuth(basicAuth: string | null, server: string | undefined = this.serverUrl): void { if (basicAuth) { - if (url) { - UserPreferences.setString(`${BASIC_AUTH_KEY}-${url}`, basicAuth); + if (server) { + UserPreferences.setString(`${BASIC_AUTH_KEY}-${server}`, basicAuth); } this.setHeaders({ Authorization: `Basic ${basicAuth}` }); } else { - if (url) { - UserPreferences.removeItem(`${BASIC_AUTH_KEY}-${url}`); + if (server) { + UserPreferences.removeItem(`${BASIC_AUTH_KEY}-${server}`); } const next = { ...this.headers }; delete next.Authorization; diff --git a/app/sagas/selectServer.ts b/app/sagas/selectServer.ts index eedde4797a1..bc9d870648d 100644 --- a/app/sagas/selectServer.ts +++ b/app/sagas/selectServer.ts @@ -182,7 +182,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch } const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${server}`); - setBasicAuth(basicAuth); + setBasicAuth(basicAuth, server); if (user) { yield put(clearSettings()); diff --git a/app/views/NewServerView/methods/basicAuth.ts b/app/views/NewServerView/methods/basicAuth.ts index 80e8d2de8c7..e46999e01c0 100644 --- a/app/views/NewServerView/methods/basicAuth.ts +++ b/app/views/NewServerView/methods/basicAuth.ts @@ -10,7 +10,7 @@ const basicAuth = (server: string, text: string) => { if (parsedUrl.auth.length) { const credentials = Base64.encode(parsedUrl.auth); UserPreferences.setString(`${BASIC_AUTH_KEY}-${server}`, credentials); - setBasicAuth(credentials); + setBasicAuth(credentials, server); } } catch { // do nothing From f2b79bb24e36b64095f5c01aedabbd573ed88ca7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:59:15 +0530 Subject: [PATCH 178/203] remove a comment --- app/lib/services/sdk.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 067f393fffc..e5863fd128a 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -383,11 +383,6 @@ class Sdk { return this.current.connection.on('connection', callback); } - /** - * Fires once re-authentication actually completes (uid set), not on raw socket 'connected' - - * ddp-client's own reconnect handler calls account.loginWithToken() without awaiting it, so - * subscribing on 'connected' races the login and gets rejected (nosub) by the server. - */ onLogin(callback: () => void): () => void { if (!this.current) { return () => {}; From a0123435003a79c4fe24751e10289f9964038eb2 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 00:16:08 +0530 Subject: [PATCH 179/203] new test --- app/lib/services/sdk.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index fe40a98c697..b07581c8259 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -332,6 +332,20 @@ describe('Sdk.logout', () => { expect(log).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining('timed out') })); jest.useRealTimers(); }); + + it('still clears auth headers when account.logout() throws', async () => { + const log = jest.requireMock('../methods/helpers/log').default; + const fake = { account: { logout: jest.fn().mockRejectedValue(new Error('socket closed')) } }; + setInternalSdk(fake); + (sdk as any).headers = { ...(sdk as any).headers, 'X-Auth-Token': 'tok-abc', 'X-User-Id': 'uid-1' }; + + await sdk.logout(); + + expect(log).toHaveBeenCalledWith(expect.objectContaining({ message: 'socket closed' })); + const headers = sdk.getHeaders(); + expect(headers).not.toHaveProperty('X-Auth-Token'); + expect(headers).not.toHaveProperty('X-User-Id'); + }); }); describe('normalizeResponseError', () => { From d0819ed641ba410b622c6a25c37165f9499c3141 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 00:29:31 +0530 Subject: [PATCH 180/203] using stop in room sub --- app/lib/methods/subscriptions/room.test.ts | 24 +++++++++++----------- app/lib/methods/subscriptions/room.ts | 7 +++---- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts index d52dc6abec1..edab3f7bf14 100644 --- a/app/lib/methods/subscriptions/room.test.ts +++ b/app/lib/methods/subscriptions/room.test.ts @@ -212,21 +212,21 @@ describe('RoomSubscription', () => { }); it('tears down stale subscriptions on reconnect and tracks fresh ones for later cleanup', async () => { - const staleSub = { unsubscribe: jest.fn(() => Promise.resolve()) }; - const freshSub = { unsubscribe: jest.fn(() => Promise.resolve()) }; + const staleSub = { stop: jest.fn() }; + const freshSub = { stop: jest.fn() }; mockSubscribeRoom.mockResolvedValueOnce([staleSub]).mockResolvedValueOnce([freshSub]); await sub.subscribe(); await sub.handleLogin(); await sub.unsubscribe(); - expect(staleSub.unsubscribe).toHaveBeenCalledTimes(1); - expect(freshSub.unsubscribe).toHaveBeenCalledTimes(1); + expect(staleSub.stop).toHaveBeenCalledTimes(1); + expect(freshSub.stop).toHaveBeenCalledTimes(1); }); it('does not accumulate subscriptions across repeated handleLogin calls (simulates sequential reopen)', async () => { - const first = { unsubscribe: jest.fn(() => Promise.resolve()) }; - const second = { unsubscribe: jest.fn(() => Promise.resolve()) }; + const first = { stop: jest.fn() }; + const second = { stop: jest.fn() }; mockSubscribeRoom.mockResolvedValueOnce([first]).mockResolvedValueOnce([second]); await sub.subscribe(); @@ -235,18 +235,18 @@ describe('RoomSubscription', () => { // First reopen → tears down [first], creates [second] await sub.handleLogin(); expect(mockSubscribeRoom).toHaveBeenCalledTimes(2); - expect(first.unsubscribe).toHaveBeenCalledTimes(1); - expect(second.unsubscribe).not.toHaveBeenCalled(); + expect(first.stop).toHaveBeenCalledTimes(1); + expect(second.stop).not.toHaveBeenCalled(); // Second reopen → tears down [second], creates [] await sub.handleLogin(); expect(mockSubscribeRoom).toHaveBeenCalledTimes(3); - expect(second.unsubscribe).toHaveBeenCalledTimes(1); + expect(second.stop).toHaveBeenCalledTimes(1); // Final cleanup → empty batch, no more unsubscribes await sub.unsubscribe(); - expect(first.unsubscribe).toHaveBeenCalledTimes(1); - expect(second.unsubscribe).toHaveBeenCalledTimes(1); + expect(first.stop).toHaveBeenCalledTimes(1); + expect(second.stop).toHaveBeenCalledTimes(1); }); it('does not call onStreamData inside handleLogin (listeners persist across reopen)', async () => { @@ -260,7 +260,7 @@ describe('RoomSubscription', () => { it('survives a poisoned subscription array (undefined entry from a rejected sub) and still re-subscribes', async () => { // A pre-auth subscribe rejected by the server (nosub) resolves to undefined inside Promise.all. - const freshSub = { unsubscribe: jest.fn(() => Promise.resolve()) }; + const freshSub = { stop: jest.fn() }; mockSubscribeRoom.mockResolvedValueOnce([undefined as any]).mockResolvedValueOnce([freshSub]); await sub.subscribe(); diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index 55779b3abd7..8b50d029651 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -18,7 +18,6 @@ import { Encryption } from '../../encryption'; import { type IMessage, type TMessageModel, - type TSubscriptionModel, type TThreadMessageModel, type TThreadModel, type IDeleteMessageBulkParams @@ -33,7 +32,7 @@ import markMessagesRead from '../helpers/markMessagesRead'; export default class RoomSubscription { private rid: string; private isAlive: boolean; - private promises?: Promise; + private promises?: Promise; private connectionStatusListener?: () => void; private loginListener?: () => void; private notifyRoomListener?: Promise; @@ -74,7 +73,7 @@ export default class RoomSubscription { if (this.promises) { try { const subscriptions = (await this.promises) || []; - subscriptions.forEach(sub => sub?.unsubscribe?.().catch(() => console.log('unsubscribeRoom'))); + subscriptions.forEach(sub => sub?.stop?.()); } catch (e) { // do nothing } @@ -104,7 +103,7 @@ export default class RoomSubscription { try { if (this.promises) { const oldSubs = await this.promises; - oldSubs?.forEach(sub => sub?.unsubscribe?.().catch(() => {})); + oldSubs?.forEach(sub => sub?.stop?.()); } this.promises = sdk.subscribeRoom(this.rid); await this.promises; From 42cc9dfbf36b65519d10801cf79f6d0683f660c3 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 00:42:56 +0530 Subject: [PATCH 181/203] fix type --- app/lib/methods/subscriptions/room.ts | 4 ++-- app/lib/services/sdk.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index 8b50d029651..d2c405edebb 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -23,7 +23,7 @@ import { type IDeleteMessageBulkParams } from '../../../definitions'; import { type IDDPMessage } from '../../../definitions/IDDPMessage'; -import sdk from '../../services/sdk'; +import sdk, { type DDPSubscription } from '../../services/sdk'; import { readMessages } from '../readMessages'; import { loadMissedMessages } from '../loadMissedMessages'; import { updateLastOpen } from '../updateLastOpen'; @@ -32,7 +32,7 @@ import markMessagesRead from '../helpers/markMessagesRead'; export default class RoomSubscription { private rid: string; private isAlive: boolean; - private promises?: Promise; + private promises?: Promise; private connectionStatusListener?: () => void; private loginListener?: () => void; private notifyRoomListener?: Promise; diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index e5863fd128a..cdc0a64bc73 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -31,6 +31,8 @@ export async function normalizeResponseError(response: Response): Promise<{ stat // so we keep a single source of truth here instead of repeating the union inline. export type ConnectionStatus = 'idle' | 'connecting' | 'connected' | 'failed' | 'closed' | 'disconnected' | 'reconnecting'; +export type DDPSubscription = ReturnType; + export const NOTIFY_USER_EVENTS = [ 'message', 'notification', @@ -335,8 +337,8 @@ class Sdk { return this.ensureInitialized().client.subscribe(...args); } - subscribeRoom(rid: string): Promise { - const subscriptions: any[] = []; + subscribeRoom(rid: string): Promise { + const subscriptions: DDPSubscription[] = []; if (!this.current) { return Promise.resolve(subscriptions); } From ddc69230b95d91f9bea05cd353730f52f7ef0236 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 00:49:14 +0530 Subject: [PATCH 182/203] remove unwanted comment --- app/lib/methods/helpers/getAuthHeaders.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/lib/methods/helpers/getAuthHeaders.ts b/app/lib/methods/helpers/getAuthHeaders.ts index e962fc468e1..5ca48cec027 100644 --- a/app/lib/methods/helpers/getAuthHeaders.ts +++ b/app/lib/methods/helpers/getAuthHeaders.ts @@ -4,9 +4,7 @@ import { isSameOrigin } from './isSameOrigin'; /** * Returns the request headers for a given URL. * On same-origin requests the full header set is sent (including the session token). - * On cross-origin requests only the session token (X-Auth-Token/X-User-Id) is stripped, - * keeping non-sensitive headers such as User-Agent (and Basic auth) — matching the - * pre-migration behavior where only User-Agent/Basic auth were ever sent off-origin. + * On cross-origin requests only the session token (X-Auth-Token/X-User-Id) is stripped */ export const getAuthHeaders = (url: string): Record => { if (isSameOrigin(url, sdk.server)) { From 5271af8e51dbc31bcf7edc00218fa9bae07a57b6 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 01:01:21 +0530 Subject: [PATCH 183/203] return false on error and no origin provided --- app/lib/methods/helpers/isSameOrigin.test.ts | 2 +- app/lib/methods/helpers/isSameOrigin.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/lib/methods/helpers/isSameOrigin.test.ts b/app/lib/methods/helpers/isSameOrigin.test.ts index fae6638579c..be98aad34ff 100644 --- a/app/lib/methods/helpers/isSameOrigin.test.ts +++ b/app/lib/methods/helpers/isSameOrigin.test.ts @@ -19,6 +19,6 @@ describe('isSameOrigin', () => { }); test('returns true when origin is undefined', () => { - expect(isSameOrigin('https://releases.rocket.chat/x')).toBe(true); + expect(isSameOrigin('https://releases.rocket.chat/x')).toBe(false); }); }); diff --git a/app/lib/methods/helpers/isSameOrigin.ts b/app/lib/methods/helpers/isSameOrigin.ts index 5598a9e81a1..091a4424abd 100644 --- a/app/lib/methods/helpers/isSameOrigin.ts +++ b/app/lib/methods/helpers/isSameOrigin.ts @@ -1,11 +1,10 @@ export const isSameOrigin = (url: string, origin?: string): boolean => { if (!origin) { - return true; + return false; } try { - return new URL(url).origin === new URL(origin).origin; + return new URL(url, origin).origin === new URL(origin).origin; } catch { - // Relative/internal URL — safe to attach auth headers. - return true; + return false; } }; From 4e891c1e9df37c12850b8e46ee780a193974defe Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 01:44:51 +0530 Subject: [PATCH 184/203] change... --- app/lib/services/connect.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 19f5eba6830..a0fd1a7e60b 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -47,11 +47,10 @@ async function connect({ server, logoutOnError = false }: { server: string; logo } try { - connectAbortController?.abort(); + disconnect(); connectAbortController = new AbortController(); const { signal } = connectAbortController; - disconnect(); database.setActiveDB(server); unsubscribeRooms(); From 881da594c443ae72f3965a36d140be6abc50f6d7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 01:53:29 +0530 Subject: [PATCH 185/203] test update --- app/lib/services/connect.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 997c63c5448..5b02f204af5 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -547,7 +547,8 @@ describe('connect — connection status handler', () => { await Promise.resolve(); // let connect(A) reach the getSettings() await // connect(B) supersedes connect(A) while getSettings(A) is still in flight. - sdkMock.__setServer('https://b.example.com'); + // sdk.server is still 'a.example.com' here (set by connect(A)'s own initialize()) — + // connect(B) is what flips it to 'b.example.com' via its own initialize() call below. await connect({ server: 'https://b.example.com' }); // The previous call's REST request must have been signalled to abort so it From ed2ea3c6592476717590d42772b0ab1023bef313 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Wed, 15 Jul 2026 21:01:40 +0530 Subject: [PATCH 186/203] fix --- app/lib/methods/sendFileMessage/sendFileMessage.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/lib/methods/sendFileMessage/sendFileMessage.ts b/app/lib/methods/sendFileMessage/sendFileMessage.ts index e227633b5b5..96760cb62eb 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessage.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessage.ts @@ -1,23 +1,20 @@ -import { settings as RocketChatSettings } from '@rocket.chat/sdk'; - -import { type IUser, type TSendFileMessageFileInfo, type TUploadModel } from '../../../definitions'; +import { type TSendFileMessageFileInfo, type TUploadModel } from '../../../definitions'; import database from '../../database'; import FileUpload from '../helpers/fileUpload'; import { copyFileToCacheDirectoryIfNeeded, createUploadRecord, persistUploadError, uploadQueue } from './utils'; import { type IFormData } from '../helpers/fileUpload/definitions'; +import sdk from '../../services/sdk'; export async function sendFileMessage( rid: string, fileInfo: TSendFileMessageFileInfo, tmid: string | undefined, server: string, - user: Partial>, isForceTryAgain?: boolean ): Promise { let uploadPath: string | null = ''; let uploadRecord: TUploadModel | null; try { - const { id, token } = user; const uploadUrl = `${server}/api/v1/rooms.upload/${rid}`; fileInfo.rid = rid; @@ -60,10 +57,8 @@ export async function sendFileMessage( } const headers = { - ...RocketChatSettings.customHeaders, + ...sdk.getHeaders(), 'Content-Type': 'multipart/form-data', - 'X-Auth-Token': token, - 'X-User-Id': id }; uploadQueue[uploadPath] = new FileUpload(uploadUrl, headers, formData, async (loaded, total) => { From 5ae2868d75f31c6a3f91a029f725c61e1d404710 Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Wed, 15 Jul 2026 15:33:30 +0000 Subject: [PATCH 187/203] chore: format code and fix lint issues --- app/lib/methods/sendFileMessage/sendFileMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/methods/sendFileMessage/sendFileMessage.ts b/app/lib/methods/sendFileMessage/sendFileMessage.ts index 96760cb62eb..0afbfcf1055 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessage.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessage.ts @@ -58,7 +58,7 @@ export async function sendFileMessage( const headers = { ...sdk.getHeaders(), - 'Content-Type': 'multipart/form-data', + 'Content-Type': 'multipart/form-data' }; uploadQueue[uploadPath] = new FileUpload(uploadUrl, headers, formData, async (loaded, total) => { From ec73e95247fdd13296ffcde55eb35a24746f88e7 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:41:41 +0530 Subject: [PATCH 188/203] test fix --- .../sendFileMessage/sendFileMessage.test.ts | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/app/lib/methods/sendFileMessage/sendFileMessage.test.ts b/app/lib/methods/sendFileMessage/sendFileMessage.test.ts index 9d6bbf33965..75b8c3cb6c6 100644 --- a/app/lib/methods/sendFileMessage/sendFileMessage.test.ts +++ b/app/lib/methods/sendFileMessage/sendFileMessage.test.ts @@ -65,13 +65,17 @@ jest.mock('../helpers/fileUpload', () => ({ }) })); -jest.mock('./utils', () => ({ - copyFileToCacheDirectoryIfNeeded: jest.fn().mockImplementation((p: string) => Promise.resolve(p)), - getUploadPath: jest.fn(() => 'upload-path-1'), - persistUploadError: jest.fn().mockResolvedValue(undefined), - createUploadRecord: jest.fn().mockResolvedValue(['upload-path-1', { update: jest.fn(), destroyPermanently: jest.fn() }]), - uploadQueue: {} -})); +jest.mock('./utils', () => { + const uploadRecord = { update: jest.fn(), destroyPermanently: jest.fn() }; + return { + __uploadRecord: uploadRecord, + copyFileToCacheDirectoryIfNeeded: jest.fn().mockImplementation((p: string) => Promise.resolve(p)), + getUploadPath: jest.fn(() => 'upload-path-1'), + persistUploadError: jest.fn().mockResolvedValue(undefined), + createUploadRecord: jest.fn().mockResolvedValue(['upload-path-1', uploadRecord]), + uploadQueue: {} + }; +}); const mockEncryptFile = jest.fn().mockResolvedValue({ file: { path: '/p', name: 'n', type: 't' }, @@ -159,11 +163,12 @@ describe('sendFileMessage V1 headers', () => { undefined, 'https://x.com' ); - const dbMod = require('../../database'); + const { __uploadRecord } = require('./utils'); + // sendFileMessage updates the record returned by createUploadRecord, not the DB mock. await mockState.progressCb!(50, 100); - expect(dbMod.__update).toHaveBeenCalledWith(expect.any(Function)); + expect(__uploadRecord.update).toHaveBeenCalledWith(expect.any(Function)); // The update callback assigns u.progress = Math.floor((50/100)*100) = 50 - const updateFn = dbMod.__update.mock.calls[0][0]; + const updateFn = __uploadRecord.update.mock.calls[0][0]; const u: any = {}; updateFn(u); expect(u.progress).toBe(50); From 516e6d0b3919a23983e60eebd1b9ac460c554af9 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 19 Jul 2026 21:04:39 +0530 Subject: [PATCH 189/203] some improvements --- app/lib/methods/helpers/fetch.test.ts | 17 ++++ app/lib/methods/helpers/fetch.ts | 8 +- app/lib/methods/helpers/isSameOrigin.test.ts | 8 ++ app/lib/methods/helpers/isSameOrigin.ts | 9 +- app/lib/services/sdk.test.ts | 89 +++++++++++++++++--- app/lib/services/sdk.ts | 25 +++--- 6 files changed, 128 insertions(+), 28 deletions(-) diff --git a/app/lib/methods/helpers/fetch.test.ts b/app/lib/methods/helpers/fetch.test.ts index 35e9184a6b1..5563cb7624c 100644 --- a/app/lib/methods/helpers/fetch.test.ts +++ b/app/lib/methods/helpers/fetch.test.ts @@ -84,6 +84,23 @@ describe('helpers/fetch', () => { expect(typeof call.headers['User-Agent']).toBe('string'); expect(call.headers['User-Agent']).toMatch(/RC Mobile/); }); + + it('strips session headers for cross-origin requests even when the caller explicitly sets them', async () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'User-Agent': 'RC Mobile' }); + await customFetch('https://releases.rocket.chat/v2/server/supportedVersions', { + headers: { 'X-Auth-Token': 'caller-supplied-tok', 'X-User-Id': 'caller-supplied-uid' } as any + }); + const call = fetchMock.mock.calls[0][1]; + expect(call.headers['X-Auth-Token']).toBeUndefined(); + expect(call.headers['X-User-Id']).toBeUndefined(); + }); + + it('still lets caller-supplied headers win for same-origin requests', async () => { + (sdk.getHeaders as jest.Mock).mockReturnValue({ 'X-Auth-Token': 'sdk-tok' }); + await customFetch('https://open.rocket.chat/api/v1/settings', { headers: { 'X-Auth-Token': 'caller-tok' } as any }); + const call = fetchMock.mock.calls[0][1]; + expect(call.headers['X-Auth-Token']).toBe('caller-tok'); + }); }); describe('helpers/fetch setBasicAuth', () => { diff --git a/app/lib/methods/helpers/fetch.ts b/app/lib/methods/helpers/fetch.ts index 96a47946ce3..5140d97af0c 100644 --- a/app/lib/methods/helpers/fetch.ts +++ b/app/lib/methods/helpers/fetch.ts @@ -1,6 +1,7 @@ import sdk from '../../services/sdk'; import { type DefaultHeaders, headers } from './defaultHeaders'; import { getAuthHeaders } from './getAuthHeaders'; +import { isSameOrigin } from './isSameOrigin'; export type TMethods = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'post' | 'get' | 'delete' | 'put'; @@ -21,6 +22,11 @@ export const BASIC_AUTH_KEY = 'BASIC_AUTH_KEY'; export default (url: string, options: IOptions = {}): Promise => { const authHeaders = getAuthHeaders(url); - const customOptions = { ...options, headers: { ...authHeaders, ...(options.headers || {}) } }; + const mergedHeaders: Record = { ...authHeaders, ...(options.headers || {}) }; + if (!isSameOrigin(url, sdk.server)) { + delete mergedHeaders['X-Auth-Token']; + delete mergedHeaders['X-User-Id']; + } + const customOptions = { ...options, headers: mergedHeaders }; return fetch(url, customOptions); }; diff --git a/app/lib/methods/helpers/isSameOrigin.test.ts b/app/lib/methods/helpers/isSameOrigin.test.ts index be98aad34ff..a1aa526d140 100644 --- a/app/lib/methods/helpers/isSameOrigin.test.ts +++ b/app/lib/methods/helpers/isSameOrigin.test.ts @@ -21,4 +21,12 @@ describe('isSameOrigin', () => { test('returns true when origin is undefined', () => { expect(isSameOrigin('https://releases.rocket.chat/x')).toBe(false); }); + + test('returns false for a different path under a subpath-deployed server (same host, different workspace)', () => { + expect(isSameOrigin('https://example.com/rc-workspace-b/api/info', 'https://example.com/rc-workspace-a')).toBe(false); + }); + + test('returns true for a request under the same subpath-deployed server', () => { + expect(isSameOrigin('https://example.com/rc-workspace-a/api/v1/settings', 'https://example.com/rc-workspace-a')).toBe(true); + }); }); diff --git a/app/lib/methods/helpers/isSameOrigin.ts b/app/lib/methods/helpers/isSameOrigin.ts index 091a4424abd..d3f251b2209 100644 --- a/app/lib/methods/helpers/isSameOrigin.ts +++ b/app/lib/methods/helpers/isSameOrigin.ts @@ -3,7 +3,14 @@ export const isSameOrigin = (url: string, origin?: string): boolean => { return false; } try { - return new URL(url, origin).origin === new URL(origin).origin; + const target = new URL(url, origin); + const base = new URL(origin); + if (target.origin !== base.origin) { + return false; + } + const basePath = base.pathname.endsWith('/') ? base.pathname : `${base.pathname}/`; + const targetPath = target.pathname.endsWith('/') ? target.pathname : `${target.pathname}/`; + return targetPath.startsWith(basePath); } catch { return false; } diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index b07581c8259..d3e7792d17a 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -62,7 +62,6 @@ beforeEach(() => { (UserPreferences.removeItem as jest.Mock).mockReset(); (DDPSDK.create as jest.Mock).mockReset(); (DDPSDK.createAndConnect as jest.Mock).mockReset(); - (sdk as any).code = null; (sdk as any).serverUrl = undefined; }); @@ -289,6 +288,41 @@ describe('Sdk.login', () => { expect(sdk.getHeaders()['X-User-Id']).toBe('uid-2'); }); + it('does not write stale headers if the server switches while loginWithToken() is still in flight', async () => { + let resolveLoginWithToken: () => void = () => {}; + const loginWithToken = jest.fn( + () => + new Promise(resolve => { + resolveLoginWithToken = resolve; + }) + ); + const server1Sdk = buildFakeSdkWithLogin( + { status: 'success', data: { authToken: 'tok-1', userId: 'uid-1', me: { username: 'user1' } } }, + loginWithToken + ); + (DDPSDK.create as jest.Mock).mockReturnValueOnce(server1Sdk); + sdk.initialize('https://server1.com'); + + // server1's login suspends inside the loginWithToken() await. + const loginPromise = sdk.login({ user: 'user1', password: 'pass1' }); + + // The app switches to server2 while server1's login is still in flight. + const server2Sdk = buildFakeSdkWithLogin({ + status: 'success', + data: { authToken: 'tok-2', userId: 'uid-2', me: { username: 'user2' } } + }); + (DDPSDK.create as jest.Mock).mockReturnValueOnce(server2Sdk); + sdk.initialize('https://server2.com'); + await sdk.login({ user: 'user2', password: 'pass2' }); + expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-2'); + + // server1's suspended login now resumes — it must reject, not clobber server2's headers. + resolveLoginWithToken(); + await expect(loginPromise).rejects.toThrow('Server switched during login'); + expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-2'); + expect(sdk.getHeaders()['X-User-Id']).toBe('uid-2'); + }); + it('switches to the deep link server credentials when already logged in on another server', async () => { // Already authenticated on server1. const server1Sdk = buildFakeSdkWithLogin({ @@ -565,12 +599,50 @@ describe('Sdk.methodCall', () => { expect(result).toEqual({ result: 'ok' }); }); - it('appends stored TOTP code to params when present', async () => { - const callAsync = jest.fn().mockResolvedValue({}); + it('appends the resolved TOTP code to params when a retry is in flight', async () => { + const callAsync = jest + .fn() + .mockRejectedValueOnce({ error: 'totp-required', details: { method: 'totp' } }) + .mockResolvedValueOnce({}); setInternalSdk(buildFakeSdkWithMethod(callAsync)); - (sdk as any).code = '654321'; + (twoFactor as jest.Mock).mockResolvedValue({ twoFactorCode: '654321', twoFactorMethod: 'totp' }); await (sdk as any).methodCall('myMethod', 'arg1'); - expect(callAsync).toHaveBeenCalledWith('myMethod', {}, 'arg1', '654321'); + expect(callAsync).toHaveBeenNthCalledWith(2, 'myMethod', {}, 'arg1', { twoFactorCode: '654321', twoFactorMethod: 'totp' }); + }); + + it('does not leak a pending 2FA code into a concurrent, unrelated methodCall() that never needed 2FA', async () => { + const createDeferred = () => { + let resolve!: (value: T) => void; + let reject!: (reason?: any) => void; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; + }; + + const attempt1A = createDeferred(); + const retryA = createDeferred(); + const challengeA = createDeferred(); + const callAsync = jest + .fn() + .mockImplementationOnce(() => attempt1A.promise) // methodA attempt 1 + .mockImplementationOnce(() => retryA.promise) // methodA retry (kept pending) + .mockImplementationOnce((_method: string, _opts: any, ...rest: any[]) => Promise.resolve({ methodCArgs: rest })); // methodC attempt 1 + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + (twoFactor as jest.Mock).mockReturnValueOnce(challengeA.promise); + + const callAPromise = (sdk as any).methodCall('methodA'); + attempt1A.reject({ error: 'totp-required', details: { method: 'totp' } }); + await Promise.resolve().then(() => Promise.resolve()); // let the catch handler call twoFactor() + challengeA.resolve({ twoFactorCode: 'a-code', twoFactorMethod: 'totp' }); + await Promise.resolve().then(() => Promise.resolve()); // let this.code-equivalent retry kick off (now pending on retryA) + + const callCResult = await (sdk as any).methodCall('methodC'); // unrelated, never hits a 2FA error + expect(callCResult.methodCArgs).toEqual([]); + + retryA.resolve({ ok: true }); + await callAPromise; }); it('handles totp-required by prompting twoFactor and retrying', async () => { @@ -923,13 +995,6 @@ describe('Sdk.initialize', () => { sdk.initialize('https://example.com'); expect(sdk.getHeaders().Authorization).toBe('Basic cmVzdW1lOnRva2Vu'); }); - - it('clears any stored TOTP code on initialize', () => { - (sdk as any).code = '123456'; - (DDPSDK.create as jest.Mock).mockReturnValue({ rest: { handleTwoFactorChallenge: jest.fn() } }); - sdk.initialize('https://example.com'); - expect((sdk as any).code).toBeNull(); - }); }); describe('Sdk.subscribeRoom', () => { diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index cdc0a64bc73..894aa6c80b9 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -60,11 +60,9 @@ declare module '@rocket.chat/ddp-client' { class Sdk { private sdk: DDPSDK | undefined; private serverUrl: string | undefined; - private code: any = null; private headers: Record = { ...defaultHeaders } as Record; initialize(server: string): DDPSDK { - this.code = null; this.headers = { ...defaultHeaders } as Record; this.sdk = DDPSDK.create(server); @@ -269,6 +267,9 @@ class Sdk { } await this.current?.account.loginWithToken(loginResult.data.authToken); + if (this.server !== server) { + return Promise.reject(new Error('Server switched during login')); + } this.setHeaders({ 'X-Auth-Token': loginResult.data.authToken, 'X-User-Id': loginResult.data.userId @@ -283,29 +284,25 @@ class Sdk { } } - async methodCall(...args: any[]): Promise { + methodCall(...args: any[]): Promise { + return this.callMethod(args); + } + + private async callMethod(args: any[], code?: { twoFactorCode: string; twoFactorMethod: string }): Promise { try { if (!this.current || !this.current.client) { throw new Error('SDK not initialized'); } const [method, ...params] = args; - const result = await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(this.code ? [this.code] : [])); - // Clear the 2FA code after use — a stale trailing arg breaks typed method signatures on the next call. - if (this.code) { - this.code = null; - } - return result; + return await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(code ? [code] : [])); } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { const { details } = e; try { - this.code = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' }); - const result = await this.methodCall(...args); - this.code = null; - return result; + const totpResult = await twoFactor({ method: details?.method, invalid: e.error === 'totp-invalid' }); + return await this.callMethod(args, totpResult); } catch { // twoFactor was canceled - this.code = null; return {}; } } else { From 3505eb8c03dfecdcb95630fb22da05657c972667 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 20 Jul 2026 03:29:02 +0530 Subject: [PATCH 190/203] minor changes --- app/lib/services/connect.ts | 2 +- app/sagas/deepLinking.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index a0fd1a7e60b..96af3b3220a 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -99,7 +99,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo store.dispatch(inquiryRequest()); } } - if (['disconnected', 'closed'].includes(status)) { + if (['disconnected', 'closed', 'failed'].includes(status)) { unsubscribeRooms(); pendingHangupsDrainArmed = true; store.dispatch(disconnectAction()); diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 883db056132..a3c11a9cb96 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -236,7 +236,7 @@ const handleOpen = function* handleOpen({ params }) { const hostAlreadyConnected = sdk.server === host; if (!hostAlreadyConnected) { yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); - yield put(serverInitAdd(host)); + yield put(serverInitAdd(server)); yield delay(1000); EventEmitter.emit('NewServer', { server: host }); } @@ -339,7 +339,7 @@ const handleClickCallPush = function* handleClickCallPush({ params }) { return; } yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); - yield put(serverInitAdd(host)); + yield put(serverInitAdd(server)); yield delay(1000); EventEmitter.emit('NewServer', { server: host }); if (params.token) { From 9e8f672d32a8ebee325ca46046ef3fa38394b423 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:38:00 +0530 Subject: [PATCH 191/203] some improvements --- app/lib/methods/actions.test.ts | 6 +- app/lib/methods/actions.ts | 7 +-- app/lib/services/sdk.test.ts | 80 ++++++++++++++++++++++++++ app/views/ThreadMessagesView/index.tsx | 2 +- 4 files changed, 88 insertions(+), 7 deletions(-) diff --git a/app/lib/methods/actions.test.ts b/app/lib/methods/actions.test.ts index 47613ea410f..4659e4451cb 100644 --- a/app/lib/methods/actions.test.ts +++ b/app/lib/methods/actions.test.ts @@ -102,11 +102,13 @@ describe('actions', () => { expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ - 'X-Auth-Token': 'auth-token', - 'X-User-Id': 'user-id' + 'Content-Type': 'application/json' }) }) ); + const headersArg = mockedFetch.mock.calls[0][1]?.headers as Record; + expect(headersArg['X-Auth-Token']).toBeUndefined(); + expect(headersArg['X-User-Id']).toBeUndefined(); }); it('handles modal.update response', async () => { diff --git a/app/lib/methods/actions.ts b/app/lib/methods/actions.ts index a173beb9670..6d26ad54b37 100644 --- a/app/lib/methods/actions.ts +++ b/app/lib/methods/actions.ts @@ -108,7 +108,6 @@ export async function triggerAction({ const payload = rest.payload ?? rest.value; try { - const { userId, authToken } = sdk.currentLogin; const host = sdk.server; if (!host) { throw new Error('SDK not initialized'); @@ -130,12 +129,12 @@ export async function triggerAction({ }); // we need to use fetch because this.sdk.post add /v1 to url + // fetch() already attaches the session headers ambiently for same-origin requests + // (host === sdk.server), so we don't rebuild them here. const result = await fetch(`${host}/api/apps/ui.interaction/${appId}/`, { method: 'POST', headers: { - 'Content-Type': 'application/json', - 'X-Auth-Token': authToken, - 'X-User-Id': userId + 'Content-Type': 'application/json' }, body: JSON.stringify(interaction) }); diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index d3e7792d17a..a42dd29b613 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -200,6 +200,27 @@ describe('Sdk.login', () => { expect(result.authToken).toBe('tok-abc'); }); + it('rejects instead of hanging forever if loginWithToken() never resolves (e.g. socket torn down mid-login)', async () => { + jest.useFakeTimers(); + const loginWithToken = jest.fn(() => new Promise(() => {})); // never resolves + const fake = buildFakeSdkWithLogin( + { status: 'success', data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } }, + loginWithToken + ); + setInternalSdk(fake); + + const loginPromise = sdk.login({ user: 'john', password: 'secret' }); + // Attach the rejection expectation before advancing the fake timer: login() awaits its own + // REST call before reaching the timeout race, and advanceTimersByTimeAsync flushes pending + // microtasks as it advances — it can settle loginPromise before the next line would + // otherwise attach a handler, which Node reports as an unhandled rejection. + // eslint-disable-next-line jest/valid-expect + const assertion = expect(loginPromise).rejects.toThrow('timed out'); + await jest.advanceTimersByTimeAsync(20000); + await assertion; + jest.useRealTimers(); + }); + it('rejects when the REST response has status: error', async () => { const fake = buildFakeSdkWithLogin({ status: 'error' }); setInternalSdk(fake); @@ -323,6 +344,54 @@ describe('Sdk.login', () => { expect(sdk.getHeaders()['X-User-Id']).toBe('uid-2'); }); + it('rejects a login() superseded by a newer login() to the SAME server, without clobbering its headers', async () => { + // Double-tapping login, or a flapping connection re-dispatching loginRequest, can fire two + // concurrent login() calls at the same server. The generation guard must let only the + // newer one win, regardless of which loginWithToken() call happens to resolve last. + const resolvers: Array<() => void> = []; + const loginWithToken = jest.fn(() => new Promise(resolve => resolvers.push(resolve))); + const fake = buildFakeSdkWithLogin( + { status: 'success', data: { authToken: 'tok-first', userId: 'uid-first', me: { username: 'user1' } } }, + loginWithToken + ); + setInternalSdk(fake); + + const firstLoginPromise = sdk.login({ user: 'user1', password: 'pass1' }); + + // Let the first login clear its own (pre-loginWithToken) generation check and reach + // loginWithToken() before the second login starts — otherwise the second login's + // generation bump would make the first fail its *early* check instead of exercising + // the race this test is actually after (both suspended inside loginWithToken()). + for (let i = 0; i < 10 && resolvers.length < 1; i++) { + // eslint-disable-next-line no-await-in-loop + await Promise.resolve(); + } + expect(resolvers).toHaveLength(1); + + fake.__post.mockResolvedValueOnce({ + status: 'success', + data: { authToken: 'tok-second', userId: 'uid-second', me: { username: 'user1' } } + }); + const secondLoginPromise = sdk.login({ user: 'user1', password: 'pass1' }); + + for (let i = 0; i < 10 && resolvers.length < 2; i++) { + // eslint-disable-next-line no-await-in-loop + await Promise.resolve(); + } + expect(resolvers).toHaveLength(2); + + // The second (newer) login resolves first and wins. + resolvers[1](); + await secondLoginPromise; + expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-second'); + + // The first (now-superseded) login resumes — it must reject rather than clobber it. + resolvers[0](); + await expect(firstLoginPromise).rejects.toThrow('Superseded by a newer login attempt'); + expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-second'); + expect(sdk.getHeaders()['X-User-Id']).toBe('uid-second'); + }); + it('switches to the deep link server credentials when already logged in on another server', async () => { // Already authenticated on server1. const server1Sdk = buildFakeSdkWithLogin({ @@ -599,6 +668,17 @@ describe('Sdk.methodCall', () => { expect(result).toEqual({ result: 'ok' }); }); + it('rejects instead of hanging forever if callAsyncWithOptions never resolves (e.g. socket torn down mid-call)', async () => { + jest.useFakeTimers(); + const callAsync = jest.fn(() => new Promise(() => {})); // never resolves, as if the socket died mid-call + setInternalSdk(buildFakeSdkWithMethod(callAsync)); + + const callPromise = (sdk as any).methodCall('myMethod'); + jest.advanceTimersByTime(20000); + await expect(callPromise).rejects.toThrow('timed out'); + jest.useRealTimers(); + }); + it('appends the resolved TOTP code to params when a retry is in flight', async () => { const callAsync = jest .fn() diff --git a/app/views/ThreadMessagesView/index.tsx b/app/views/ThreadMessagesView/index.tsx index da3a9a83572..eeba863c92d 100644 --- a/app/views/ThreadMessagesView/index.tsx +++ b/app/views/ThreadMessagesView/index.tsx @@ -352,7 +352,7 @@ class ThreadMessagesView extends Component Date: Mon, 20 Jul 2026 20:21:59 +0530 Subject: [PATCH 192/203] test fix --- app/lib/services/sdk.test.ts | 80 ------------------------------------ 1 file changed, 80 deletions(-) diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index a42dd29b613..d3e7792d17a 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -200,27 +200,6 @@ describe('Sdk.login', () => { expect(result.authToken).toBe('tok-abc'); }); - it('rejects instead of hanging forever if loginWithToken() never resolves (e.g. socket torn down mid-login)', async () => { - jest.useFakeTimers(); - const loginWithToken = jest.fn(() => new Promise(() => {})); // never resolves - const fake = buildFakeSdkWithLogin( - { status: 'success', data: { authToken: 'tok-abc', userId: 'uid-1', me: { username: 'john' } } }, - loginWithToken - ); - setInternalSdk(fake); - - const loginPromise = sdk.login({ user: 'john', password: 'secret' }); - // Attach the rejection expectation before advancing the fake timer: login() awaits its own - // REST call before reaching the timeout race, and advanceTimersByTimeAsync flushes pending - // microtasks as it advances — it can settle loginPromise before the next line would - // otherwise attach a handler, which Node reports as an unhandled rejection. - // eslint-disable-next-line jest/valid-expect - const assertion = expect(loginPromise).rejects.toThrow('timed out'); - await jest.advanceTimersByTimeAsync(20000); - await assertion; - jest.useRealTimers(); - }); - it('rejects when the REST response has status: error', async () => { const fake = buildFakeSdkWithLogin({ status: 'error' }); setInternalSdk(fake); @@ -344,54 +323,6 @@ describe('Sdk.login', () => { expect(sdk.getHeaders()['X-User-Id']).toBe('uid-2'); }); - it('rejects a login() superseded by a newer login() to the SAME server, without clobbering its headers', async () => { - // Double-tapping login, or a flapping connection re-dispatching loginRequest, can fire two - // concurrent login() calls at the same server. The generation guard must let only the - // newer one win, regardless of which loginWithToken() call happens to resolve last. - const resolvers: Array<() => void> = []; - const loginWithToken = jest.fn(() => new Promise(resolve => resolvers.push(resolve))); - const fake = buildFakeSdkWithLogin( - { status: 'success', data: { authToken: 'tok-first', userId: 'uid-first', me: { username: 'user1' } } }, - loginWithToken - ); - setInternalSdk(fake); - - const firstLoginPromise = sdk.login({ user: 'user1', password: 'pass1' }); - - // Let the first login clear its own (pre-loginWithToken) generation check and reach - // loginWithToken() before the second login starts — otherwise the second login's - // generation bump would make the first fail its *early* check instead of exercising - // the race this test is actually after (both suspended inside loginWithToken()). - for (let i = 0; i < 10 && resolvers.length < 1; i++) { - // eslint-disable-next-line no-await-in-loop - await Promise.resolve(); - } - expect(resolvers).toHaveLength(1); - - fake.__post.mockResolvedValueOnce({ - status: 'success', - data: { authToken: 'tok-second', userId: 'uid-second', me: { username: 'user1' } } - }); - const secondLoginPromise = sdk.login({ user: 'user1', password: 'pass1' }); - - for (let i = 0; i < 10 && resolvers.length < 2; i++) { - // eslint-disable-next-line no-await-in-loop - await Promise.resolve(); - } - expect(resolvers).toHaveLength(2); - - // The second (newer) login resolves first and wins. - resolvers[1](); - await secondLoginPromise; - expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-second'); - - // The first (now-superseded) login resumes — it must reject rather than clobber it. - resolvers[0](); - await expect(firstLoginPromise).rejects.toThrow('Superseded by a newer login attempt'); - expect(sdk.getHeaders()['X-Auth-Token']).toBe('tok-second'); - expect(sdk.getHeaders()['X-User-Id']).toBe('uid-second'); - }); - it('switches to the deep link server credentials when already logged in on another server', async () => { // Already authenticated on server1. const server1Sdk = buildFakeSdkWithLogin({ @@ -668,17 +599,6 @@ describe('Sdk.methodCall', () => { expect(result).toEqual({ result: 'ok' }); }); - it('rejects instead of hanging forever if callAsyncWithOptions never resolves (e.g. socket torn down mid-call)', async () => { - jest.useFakeTimers(); - const callAsync = jest.fn(() => new Promise(() => {})); // never resolves, as if the socket died mid-call - setInternalSdk(buildFakeSdkWithMethod(callAsync)); - - const callPromise = (sdk as any).methodCall('myMethod'); - jest.advanceTimersByTime(20000); - await expect(callPromise).rejects.toThrow('timed out'); - jest.useRealTimers(); - }); - it('appends the resolved TOTP code to params when a retry is in flight', async () => { const callAsync = jest .fn() From a4b35d9a83db1da8504770f81b7c6cf014e1d07d Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 20 Jul 2026 21:15:24 +0530 Subject: [PATCH 193/203] api-client patch update --- patches/@rocket.chat+api-client+0.2.56.patch | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/patches/@rocket.chat+api-client+0.2.56.patch b/patches/@rocket.chat+api-client+0.2.56.patch index d4777907be4..a746d699219 100644 --- a/patches/@rocket.chat+api-client+0.2.56.patch +++ b/patches/@rocket.chat+api-client+0.2.56.patch @@ -1,5 +1,14 @@ --- a/node_modules/@rocket.chat/api-client/dist/index.js +++ b/node_modules/@rocket.chat/api-client/dist/index.js +@@ -33,7 +33,7 @@ + } + if (typeof data === 'object' && !(data instanceof File)) { + Object.keys(data).forEach((key) => { +- buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key); ++ buildFormData(data[key], formData, parentKey ? `${parentKey}[${key}]` : key); + }); + } + else { @@ -128,9 +128,14 @@ return response.json(); }); @@ -11,7 +20,7 @@ + return __awaiter(this, arguments, void 0, function* (endpoint, params, _a = {}) { + var { headers } = _a, options = __rest(_a, ["headers"]); + const isFormData = checkIfIsFormData(params); -+ const response = yield this.send(endpoint, 'DELETE', Object.assign({ body: isFormData ? buildFormData(params) : JSON.stringify(params), headers: Object.assign(Object.assign({ Accept: 'application/json' }, (!isFormData && { 'Content-Type': 'application/json' })), headers) }, options)); ++ const response = yield this.send(endpoint, 'DELETE', Object.assign({ body: isFormData ? buildFormData(params) : JSON.stringify(params), headers: Object.assign(Object.assign({ Accept: 'application/json' }, (!isFormData && params !== undefined && { 'Content-Type': 'application/json' })), headers) }, options)); + if (response.status === 204) { + return {}; + } From 50fe9c5627104cd4941fb7599091e593b7ba7b29 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 25 Jul 2026 00:17:08 +0530 Subject: [PATCH 194/203] improve --- app/lib/services/connect.ts | 2 +- app/lib/services/sdk.ts | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 96af3b3220a..c3fd33dbe77 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -107,7 +107,7 @@ async function connect({ server, logoutOnError = false }: { server: string; logo }); // Registered before connect() resolves: a rejected or hung connect() must not leave the // client without listeners once the underlying socket connects or recovers on its own. - sdk.onCollection('users', (ddpMessage: unknown) => _setUser(ddpMessage as IActiveUsers)); + sdk.onCollection('users', protectedFunction((ddpMessage: unknown) => _setUser(ddpMessage as IActiveUsers))); sdk.onCollection( 'stream-notify-all', diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 894aa6c80b9..a2c979eed9d 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -78,7 +78,7 @@ class Sdk { } private ensureInitialized(): DDPSDK { - if (!this.current) { + if (!this.current || !this.current.client) { throw new Error('SDK not initialized'); } return this.current; @@ -290,9 +290,7 @@ class Sdk { private async callMethod(args: any[], code?: { twoFactorCode: string; twoFactorMethod: string }): Promise { try { - if (!this.current || !this.current.client) { - throw new Error('SDK not initialized'); - } + this.ensureInitialized(); const [method, ...params] = args; return await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(code ? [code] : [])); } catch (e: any) { From dcc7534ffea10e929420d8803d9535df7075e49c Mon Sep 17 00:00:00 2001 From: Rohit3523 Date: Sat, 25 Jul 2026 13:02:47 +0000 Subject: [PATCH 195/203] chore: format code and fix lint issues --- app/lib/services/connect.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index c3fd33dbe77..0679a5f88db 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -107,7 +107,10 @@ async function connect({ server, logoutOnError = false }: { server: string; logo }); // Registered before connect() resolves: a rejected or hung connect() must not leave the // client without listeners once the underlying socket connects or recovers on its own. - sdk.onCollection('users', protectedFunction((ddpMessage: unknown) => _setUser(ddpMessage as IActiveUsers))); + sdk.onCollection( + 'users', + protectedFunction((ddpMessage: unknown) => _setUser(ddpMessage as IActiveUsers)) + ); sdk.onCollection( 'stream-notify-all', From 15fbc6278c806a512d6ee77b85700292c9a2aa0e Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sat, 25 Jul 2026 20:28:18 +0530 Subject: [PATCH 196/203] code rabnit suggestions --- app/lib/methods/helpers/isSameOrigin.test.ts | 2 +- app/lib/services/connect.test.ts | 31 ++++++++++++++++---- app/lib/services/connect.ts | 6 ++-- app/lib/services/sdk.ts | 4 +-- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/app/lib/methods/helpers/isSameOrigin.test.ts b/app/lib/methods/helpers/isSameOrigin.test.ts index a1aa526d140..e32c122741e 100644 --- a/app/lib/methods/helpers/isSameOrigin.test.ts +++ b/app/lib/methods/helpers/isSameOrigin.test.ts @@ -18,7 +18,7 @@ describe('isSameOrigin', () => { expect(isSameOrigin('/api/v1/x', 'https://open.rocket.chat')).toBe(true); }); - test('returns true when origin is undefined', () => { + test('returns false when origin is undefined', () => { expect(isSameOrigin('https://releases.rocket.chat/x')).toBe(false); }); diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 5b02f204af5..9b0ccf758d4 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -33,16 +33,21 @@ jest.mock('./sdk', () => { const state: { server: string | undefined; currentEnabled: boolean; - connection: { on: any; connect: any; checkAndReopen: any } | undefined; + connection: { on: any; connect: any; checkAndReopen: any; status: string } | undefined; + status: string; } = { server: undefined, currentEnabled: true, - connection: undefined + connection: undefined, + status: 'idle' }; const makeConnection = () => ({ on: (event: string, cb: any) => mockConnectionOn(event, cb), connect: () => mockConnectionConnect(), - checkAndReopen: () => mockConnectionCheckAndReopen() + checkAndReopen: () => mockConnectionCheckAndReopen(), + get status() { + return state.status; + } }); return { __esModule: true, @@ -52,6 +57,7 @@ jest.mock('./sdk', () => { }, disconnect: (...args: any[]) => { state.connection = undefined; + state.status = 'idle'; return mockSdkDisconnect(...args); }, // Mirrors the real sdk.ts: initialize() yields a brand-new connection instance each @@ -59,6 +65,7 @@ jest.mock('./sdk', () => { initialize: (s: string) => { state.server = s; state.connection = makeConnection(); + state.status = 'connecting'; return mockSdkInitialize(s); }, onCollection: (...args: any[]) => mockSdkOnCollection(...args), @@ -77,6 +84,9 @@ jest.mock('./sdk', () => { __setCurrentEnabled: (v: boolean) => { state.currentEnabled = v; }, + __setConnectionStatus: (v: string) => { + state.status = v; + }, __getCurrentConnection: () => state.connection }; }); @@ -98,7 +108,8 @@ jest.mock('../../ee/omnichannel/actions/inquiry', () => ({ const sdkMock = jest.requireMock('./sdk') as { __setServer: (v: string | undefined) => void; __setCurrentEnabled: (v: boolean) => void; - __getCurrentConnection: () => { on: any; connect: any; checkAndReopen: any } | undefined; + __setConnectionStatus: (v: string) => void; + __getCurrentConnection: () => { on: any; connect: any; checkAndReopen: any; status: string } | undefined; }; // --- Store mock --- @@ -493,6 +504,7 @@ describe('connect — connection status handler', () => { beforeEach(() => { jest.clearAllMocks(); sdkMock.__setServer(undefined); + sdkMock.__setConnectionStatus('idle'); mockStoreGetState.mockReturnValue({ meteor: { connected: false }, login: { user: null, isAuthenticated: false }, @@ -502,11 +514,20 @@ describe('connect — connection status handler', () => { }); it('returns early without initializing when server is already active', async () => { - sdkMock.__setServer(SERVER); + await connect({ server: SERVER }); + mockSdkInitialize.mockClear(); await connect({ server: SERVER }); expect(mockSdkInitialize).not.toHaveBeenCalled(); }); + it('re-initializes when sdk.server matches but the SDK never finished initializing (e.g. a prior failed connect())', async () => { + // sdk.server is set synchronously inside initialize(), before loadBasicAuth()/handleTwoFactorChallenge() + // run — a throw there leaves sdk.server pointing at this server with no live connection behind it. + sdkMock.__setServer(SERVER); + await connect({ server: SERVER }); + expect(mockSdkInitialize).toHaveBeenCalledWith(SERVER); + }); + it('bails out if a newer connect() call switched servers while getSettings() was in flight', async () => { const getSettingsMock = jest.requireMock('../methods/getSettings').getSettings as jest.Mock; let resolveSettingsA: () => void; diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index 0679a5f88db..b2050c229b7 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -10,7 +10,7 @@ import database from '../database'; import { twoFactor } from './twoFactor'; import { store } from '../store/auxStore'; import { loginRequest, logout, setLoginServices, setUser } from '../../actions/login'; -import sdk from './sdk'; +import sdk, { type ConnectionStatus } from './sdk'; import { mediaSessionInstance } from './voip/MediaSessionInstance'; import { pendingHangups } from './voip/pendingHangups'; import I18n from '../../i18n'; @@ -41,8 +41,10 @@ interface IServices { let connectAbortController: AbortController | null = null; +const ACTIVE_CONNECTION_STATUSES: ConnectionStatus[] = ['connecting', 'connected', 'reconnecting']; + async function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise { - if (sdk.server === server) { + if (sdk.server === server && sdk.current && ACTIVE_CONNECTION_STATUSES.includes(sdk.current.connection.status)) { return; } diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index a2c979eed9d..3144e22dcbc 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -290,9 +290,9 @@ class Sdk { private async callMethod(args: any[], code?: { twoFactorCode: string; twoFactorMethod: string }): Promise { try { - this.ensureInitialized(); + const sdk = this.ensureInitialized(); const [method, ...params] = args; - return await this.current.client.callAsyncWithOptions(method, {}, ...params, ...(code ? [code] : [])); + return await sdk.client.callAsyncWithOptions(method, {}, ...params, ...(code ? [code] : [])); } catch (e: any) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { const { details } = e; From 52fc781f01d3944889ce29f04ae39f447746b31b Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 2 Aug 2026 18:02:40 +0530 Subject: [PATCH 197/203] new changes to align with new patch --- app/lib/methods/subscriptions/room.test.ts | 2 +- .../__tests__/ddpConnectionPatch.test.ts | 190 ++++++++---------- app/lib/services/connect.test.ts | 38 ++-- app/lib/services/connect.ts | 16 +- app/lib/services/sdk.test.ts | 24 +-- app/lib/services/sdk.ts | 6 +- patches/@rocket.chat+ddp-client+0.3.51.patch | 167 ++++++++------- 7 files changed, 232 insertions(+), 211 deletions(-) diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts index 3902251a6cd..3f19453c4d0 100644 --- a/app/lib/methods/subscriptions/room.test.ts +++ b/app/lib/methods/subscriptions/room.test.ts @@ -199,7 +199,7 @@ describe('RoomSubscription', () => { }); }); - describe('DDP subscription recovery after forceReopen', () => { + describe('DDP subscription recovery after reconnect', () => { it('handleLogin re-subscribes the room to restore lost DDP subscriptions', async () => { await sub.subscribe(); mockSubscribeRoom.mockClear(); diff --git a/app/lib/services/__tests__/ddpConnectionPatch.test.ts b/app/lib/services/__tests__/ddpConnectionPatch.test.ts index 8f22946b947..1d0138993d7 100644 --- a/app/lib/services/__tests__/ddpConnectionPatch.test.ts +++ b/app/lib/services/__tests__/ddpConnectionPatch.test.ts @@ -3,20 +3,22 @@ * `patches/@rocket.chat+ddp-client+0.3.51.patch`. * * `sdk.test.ts` / `connect.test.ts` only assert delegation to a *mocked* - * `connection.probe` / `forceReopen` / `checkAndReopen` — none of the real - * patched logic runs under test. This file imports the real compiled - * `ConnectionImpl` (the deep `dist/Connection` path, since it isn't part of - * the package's public API surface) and drives it with a fake `WebSocket` and - * the package's own `MinimalDDPClient`, so a future edit to the patch (or a - * `ddp-client` version bump that silently drops it) is caught by a real - * regression rather than a mock. + * `connection.probe` / `reopenNow` — none of the real patched logic runs + * under test. This file imports the real compiled `ConnectionImpl` (the deep + * `dist/Connection` path, since it isn't part of the package's public API + * surface) and drives it with a fake `WebSocket` and the package's own + * `MinimalDDPClient`, so a future edit to the patch (or a `ddp-client` + * version bump that silently drops it) is caught by a real regression rather + * than a mock. * * Patched behaviors under test: * - stale-`onclose` guard: `ws.onclose` bails out with `if (ws !== this.ws) return;` - * so a late close event from a socket that `forceReopen()` already replaced + * so a late close event from a socket that `reopenNow()` already replaced * cannot clobber the live connection's status. - * - `forceReopen()` in-flight dedup: concurrent calls share one `reopenInFlight` + * - `reopenNow()` in-flight dedup: concurrent calls share one `reopenInFlight` * promise instead of tearing the socket down twice. + * - `probe()`: only resolves true for a pong received on an open socket after + * the probe started (lastPing guard). */ import { ConnectionImpl } from '@rocket.chat/ddp-client/dist/Connection'; import { MinimalDDPClient } from '@rocket.chat/ddp-client/dist/MinimalDDPClient'; @@ -89,36 +91,39 @@ const buildConnection = () => { return { client, connection }; }; +const connectUp = async (connection: any) => { + const connectPromise = connection.connect(); + const ws = FakeWebSocket.instances[0]; + ws.open(); + completeHandshake(ws, 'session-1'); + await connectPromise; + return ws; +}; + describe('patched ConnectionImpl (patches/@rocket.chat+ddp-client+0.3.51.patch)', () => { beforeEach(() => { FakeWebSocket.instances = []; }); - it('ignores a stale onclose from a socket that forceReopen() already replaced', async () => { + it('ignores a stale onclose from a socket that reopenNow() already replaced', async () => { const { connection } = buildConnection(); + const staleSocket = await connectUp(connection); - const connectPromise = connection.connect(); - const staleSocket = FakeWebSocket.instances[0]; - staleSocket.open(); - completeHandshake(staleSocket, 'session-1'); - await expect(connectPromise).resolves.toBe(true); - expect(connection.status).toBe('connected'); - - // Capture the stale socket's onclose handler *before* forceReopen (via - // close()) severs it, to stand in for a close event that was already - // in flight on the transport at the moment the socket got replaced — - // the real-world race the `ws !== this.ws` guard defends against. + // Capture the stale socket's onclose handler *before* reopenNow severs + // it, to stand in for a close event that was already in flight on the + // transport at the moment the socket got replaced — the real-world race + // the `ws !== this.ws` guard defends against. const staleOnClose = staleSocket.onclose; expect(typeof staleOnClose).toBe('function'); - const reopenPromise = connection.forceReopen(); + const reopenPromise = connection.reopenNow(); expect(FakeWebSocket.instances).toHaveLength(2); const freshSocket = FakeWebSocket.instances[1]; expect(freshSocket).not.toBe(staleSocket); freshSocket.open(); completeHandshake(freshSocket, 'session-2'); - await expect(reopenPromise).resolves.toBe(true); + await expect(reopenPromise).resolves.toBeUndefined(); expect(connection.status).toBe('connected'); expect(connection.ws).toBe(freshSocket); @@ -132,16 +137,12 @@ describe('patched ConnectionImpl (patches/@rocket.chat+ddp-client+0.3.51.patch)' expect(connection.ws).toBe(freshSocket); }); - it('dedupes concurrent forceReopen() calls into a single in-flight reopen', async () => { + it('dedupes concurrent reopenNow() calls into a single in-flight reopen', async () => { const { connection } = buildConnection(); + await connectUp(connection); - const connectPromise = connection.connect(); - FakeWebSocket.instances[0].open(); - completeHandshake(FakeWebSocket.instances[0], 'session-1'); - await connectPromise; - - const first = connection.forceReopen(); - const second = connection.forceReopen(); + const first = connection.reopenNow(); + const second = connection.reopenNow(); // The second call must return the exact same in-flight promise rather // than tearing the socket down again. @@ -152,101 +153,82 @@ describe('patched ConnectionImpl (patches/@rocket.chat+ddp-client+0.3.51.patch)' freshSocket.open(); completeHandshake(freshSocket, 'session-2'); - await expect(Promise.all([first, second])).resolves.toEqual([true, true]); + await expect(Promise.all([first, second])).resolves.toEqual([undefined, undefined]); // Still only one extra socket was ever opened for the two concurrent calls. expect(FakeWebSocket.instances).toHaveLength(2); }); + it('tears down the replaced socket without emitting a status event', async () => { + const { connection } = buildConnection(); + const staleSocket = await connectUp(connection); + + const statusEvents: string[] = []; + connection.on('connection', (status: string) => statusEvents.push(status)); + const disconnected = jest.fn(); + connection.on('disconnected', disconnected); + + connection.reopenNow(); + expect(staleSocket.onopen).toBeNull(); + expect(staleSocket.onmessage).toBeNull(); + expect(staleSocket.onclose).toBeNull(); + expect(disconnected).toHaveBeenCalledTimes(1); + // No 'disconnected' status event from the swap: the app's redux state is + // only flipped by the fresh 'connecting'/'connected' that follows, + // avoiding a spurious disconnect clobber. + expect(statusEvents).toEqual(['connecting']); + + // Complete the fresh handshake so the reconnect bailout timer is cleared. + const freshSocket = FakeWebSocket.instances[1]; + freshSocket.open(); + completeHandshake(freshSocket, 'session-2'); + await new Promise(r => setTimeout(r, 0)); + }); + describe('probe()', () => { - it('resolves true when a pong is received', async () => { - const { client, connection } = buildConnection(); - const connectPromise = connection.connect(); - const ws = FakeWebSocket.instances[0]; - ws.open(); - completeHandshake(ws, 'session-1'); - await connectPromise; - - let pongCb: ((data: unknown) => void) | undefined; - (client as any).on = jest.fn((ev: string, cb: (data: unknown) => void) => { - if (ev === 'pong') pongCb = cb; - return () => {}; - }); - (client as any).ping = jest.fn(() => { - pongCb?.({ id: 'x' }); - }); - - await expect(connection.probe()).resolves.toBe(true); + it('resolves true when a pong is received on an open socket', async () => { + const { connection } = buildConnection(); + const ws = await connectUp(connection); + + const p = connection.probe(200); + // Pongs arrive over the wire, so deliver it after the probe started — + // never synchronously, or it would land in the same millisecond as the + // probe's lastPing snapshot and be filtered by the stale-pong guard. + setTimeout(() => { + ws.onmessage?.({ data: JSON.stringify({ msg: 'pong' }) }); + }, 5); + + await expect(p).resolves.toBe(true); }); it('resolves false when no pong arrives within the timeout', async () => { - const { client, connection } = buildConnection(); - const connectPromise = connection.connect(); - const ws = FakeWebSocket.instances[0]; - ws.open(); - completeHandshake(ws, 'session-1'); - await connectPromise; + const { connection } = buildConnection(); + await connectUp(connection); jest.useFakeTimers(); - (client as any).on = jest.fn(() => () => {}); - (client as any).ping = jest.fn(); const p = connection.probe(50); jest.advanceTimersByTime(100); await expect(p).resolves.toBe(false); jest.useRealTimers(); }); - it('resolves false when the client has no ping/on', async () => { - const { client, connection } = buildConnection(); - const connectPromise = connection.connect(); - const ws = FakeWebSocket.instances[0]; - ws.open(); - completeHandshake(ws, 'session-1'); - await connectPromise; - - (client as any).ping = undefined; - (client as any).on = undefined; - - await expect(connection.probe()).resolves.toBe(false); - }); - }); - - describe('checkAndReopen()', () => { - it('forces a reopen when status is not connected', async () => { - const { connection } = buildConnection(); - // status defaults to 'idle' - const reopen = jest.spyOn(connection, 'forceReopen').mockResolvedValue(true); - - await expect(connection.checkAndReopen()).resolves.toBe(true); - expect(reopen).toHaveBeenCalled(); - }); - - it('returns true when the probe reports the socket alive', async () => { + it('resolves false when the socket is not open', async () => { const { connection } = buildConnection(); - const connectPromise = connection.connect(); - const ws = FakeWebSocket.instances[0]; - ws.open(); - completeHandshake(ws, 'session-1'); - await connectPromise; - - const probe = jest.spyOn(connection, 'probe').mockResolvedValue(true); - - await expect(connection.checkAndReopen()).resolves.toBe(true); - expect(probe).toHaveBeenCalled(); + await expect(connection.probe()).resolves.toBe(false); }); - it('forces a reopen when the probe reports the socket dead', async () => { + it('ignores pongs that arrived before the probe started', async () => { const { connection } = buildConnection(); - const connectPromise = connection.connect(); - const ws = FakeWebSocket.instances[0]; - ws.open(); - completeHandshake(ws, 'session-1'); - await connectPromise; + const ws = await connectUp(connection); - jest.spyOn(connection, 'probe').mockResolvedValue(false); - const reopen = jest.spyOn(connection, 'forceReopen').mockResolvedValue(true); + // A heartbeat pong lands just before the probe begins; only a pong + // that advances lastPing past the probe start should count. + ws.onmessage?.({ data: JSON.stringify({ msg: 'pong' }) }); - await expect(connection.checkAndReopen()).resolves.toBe(true); - expect(reopen).toHaveBeenCalled(); + jest.useFakeTimers(); + const p = connection.probe(50); + jest.advanceTimersByTime(100); + await expect(p).resolves.toBe(false); + jest.useRealTimers(); }); }); }); diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 9b0ccf758d4..9277ae4e0e3 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -21,7 +21,8 @@ jest.mock('../methods/helpers/deviceInfo', () => ({ // All event-driven tests invoke getCapturedConnectionListener() to drive it. const mockConnectionOn = jest.fn(); const mockConnectionConnect = jest.fn().mockResolvedValue(undefined); -const mockConnectionCheckAndReopen = jest.fn().mockResolvedValue(true); +const mockConnectionProbe = jest.fn().mockResolvedValue(true); +const mockConnectionReopenNow = jest.fn().mockResolvedValue(undefined); const mockSdkInitialize = jest.fn().mockResolvedValue(undefined); const mockSdkOnCollection = jest.fn(); const mockSdkDisconnect = jest.fn(); @@ -33,7 +34,7 @@ jest.mock('./sdk', () => { const state: { server: string | undefined; currentEnabled: boolean; - connection: { on: any; connect: any; checkAndReopen: any; status: string } | undefined; + connection: { on: any; connect: any; probe: any; reopenNow: any; status: string } | undefined; status: string; } = { server: undefined, @@ -44,7 +45,8 @@ jest.mock('./sdk', () => { const makeConnection = () => ({ on: (event: string, cb: any) => mockConnectionOn(event, cb), connect: () => mockConnectionConnect(), - checkAndReopen: () => mockConnectionCheckAndReopen(), + probe: () => mockConnectionProbe(), + reopenNow: () => mockConnectionReopenNow(), get status() { return state.status; } @@ -109,7 +111,7 @@ const sdkMock = jest.requireMock('./sdk') as { __setServer: (v: string | undefined) => void; __setCurrentEnabled: (v: boolean) => void; __setConnectionStatus: (v: string) => void; - __getCurrentConnection: () => { on: any; connect: any; checkAndReopen: any; status: string } | undefined; + __getCurrentConnection: () => { on: any; connect: any; probe: any; reopenNow: any; status: string } | undefined; }; // --- Store mock --- @@ -764,6 +766,7 @@ describe('connect — listener lifecycle across reconnects', () => { describe('checkAndReopen', () => { afterEach(() => { sdkMock.__setCurrentEnabled(true); + jest.clearAllMocks(); }); it('resolves false when sdk.current is undefined', async () => { @@ -771,15 +774,27 @@ describe('checkAndReopen', () => { await expect(checkAndReopen()).resolves.toBe(false); }); - it('delegates to connection.checkAndReopen() and returns its result', async () => { - mockConnectionCheckAndReopen.mockResolvedValueOnce(true); + it('reopens when the connection status is not connected', async () => { + sdkMock.__setConnectionStatus('disconnected'); await expect(checkAndReopen()).resolves.toBe(true); - expect(mockConnectionCheckAndReopen).toHaveBeenCalledTimes(1); + expect(mockConnectionReopenNow).toHaveBeenCalledTimes(1); + expect(mockConnectionProbe).not.toHaveBeenCalled(); }); - it('forwards false when connection.checkAndReopen() resolves false', async () => { - mockConnectionCheckAndReopen.mockResolvedValueOnce(false); - await expect(checkAndReopen()).resolves.toBe(false); + it('probes when connected and skips the reopen when the socket is alive', async () => { + sdkMock.__setConnectionStatus('connected'); + mockConnectionProbe.mockResolvedValueOnce(true); + await expect(checkAndReopen()).resolves.toBe(true); + expect(mockConnectionProbe).toHaveBeenCalledTimes(1); + expect(mockConnectionReopenNow).not.toHaveBeenCalled(); + }); + + it('reopens when the probe reports the socket dead', async () => { + sdkMock.__setConnectionStatus('connected'); + mockConnectionProbe.mockResolvedValueOnce(false); + await expect(checkAndReopen()).resolves.toBe(true); + expect(mockConnectionProbe).toHaveBeenCalledTimes(1); + expect(mockConnectionReopenNow).toHaveBeenCalledTimes(1); }); }); @@ -796,8 +811,7 @@ describe('connect — rooms subscription guard reset on close', () => { }); // Regression: a long background marks the DDP socket stale, so foregrounding triggers - // `checkAndReopen` → `forceReopen`, which wipes the SDK subscriptions and emits 'close' while - // bypassing `connect()`. The rooms-list `stream-notify-user` feed only re-subscribes when the + // `checkAndReopen` → `reopenNow`, which closes the stale socket and reconnects. The rooms-list `stream-notify-user` feed only re-subscribes when the // module-level guard in `subscribeRooms` is clear, and `unsubscribeRooms()` is what clears it. // If the 'close' handler stops calling `unsubscribeRooms()`, the guard stays set after reconnect // and the rooms list silently stops updating (subscriptions/favorites/reads). diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index b2050c229b7..15d998bbc59 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -404,8 +404,20 @@ async function loginOAuthOrSso(params: ICredentials) { store.dispatch(loginRequest({ resume: result.token }, false)); } -function checkAndReopen(): Promise { - return sdk.current?.connection.checkAndReopen() ?? Promise.resolve(false); +async function checkAndReopen(): Promise { + const connection = sdk.current?.connection; + if (!connection) { + return false; + } + if (connection.status !== 'connected') { + await connection.reopenNow(); + return true; + } + const alive = await connection.probe(); + if (!alive) { + await connection.reopenNow(); + } + return true; } /** diff --git a/app/lib/services/sdk.test.ts b/app/lib/services/sdk.test.ts index d3e7792d17a..0d73b158c18 100644 --- a/app/lib/services/sdk.test.ts +++ b/app/lib/services/sdk.test.ts @@ -43,9 +43,9 @@ const setInternalSdk = (value: any) => { (sdk as unknown as { sdk: any }).sdk = value; }; -const buildFakeConnection = (overrides: Partial<{ probe: jest.Mock; forceReopen: jest.Mock; close: jest.Mock }> = {}) => ({ +const buildFakeConnection = (overrides: Partial<{ probe: jest.Mock; reopenNow: jest.Mock; close: jest.Mock }> = {}) => ({ probe: jest.fn().mockResolvedValue(true), - forceReopen: jest.fn().mockResolvedValue(true), + reopenNow: jest.fn().mockResolvedValue(undefined), close: jest.fn(), ...overrides }); @@ -90,23 +90,17 @@ describe('Sdk.probe', () => { }); }); -describe('Sdk.forceReopen', () => { - it('returns false when sdk.current is undefined', async () => { +describe('Sdk.reopenNow', () => { + it('resolves when sdk.current is undefined', async () => { setInternalSdk(undefined); - await expect(sdk.forceReopen()).resolves.toBe(false); + await expect(sdk.reopenNow()).resolves.toBeUndefined(); }); - it('delegates to connection.forceReopen()', async () => { + it('delegates to connection.reopenNow()', async () => { const connection = buildFakeConnection(); setInternalSdk({ connection }); - await expect(sdk.forceReopen()).resolves.toBe(true); - expect(connection.forceReopen).toHaveBeenCalledTimes(1); - }); - - it('returns the result from connection.forceReopen()', async () => { - const connection = buildFakeConnection({ forceReopen: jest.fn().mockResolvedValue(false) }); - setInternalSdk({ connection }); - await expect(sdk.forceReopen()).resolves.toBe(false); + await expect(sdk.reopenNow()).resolves.toBeUndefined(); + expect(connection.reopenNow).toHaveBeenCalledTimes(1); }); }); @@ -157,7 +151,7 @@ describe('Sdk.subscribeNotifyUser', () => { }); it('uses the explicit userId even when no user is logged in', () => { - // Guards the reconnect/forceReopen timing gap: callers pass userId explicitly + // Guards the reconnect/reopenNow timing gap: callers pass userId explicitly // so subscriptions are established even before account.user is re-populated. const fake = buildFakeSdkWithSubscribe(undefined); setInternalSdk(fake); diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 3144e22dcbc..5069c14de8d 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -101,8 +101,8 @@ class Sdk { return this.current?.connection.probe(timeoutMs) ?? Promise.resolve(false); } - forceReopen(): Promise { - return this.current?.connection.forceReopen() ?? Promise.resolve(false); + reopenNow(): Promise { + return this.current?.connection.reopenNow() ?? Promise.resolve(); } private loadBasicAuth(): void { @@ -397,7 +397,7 @@ class Sdk { /** * Re-subscribe to the per-user `stream-notify-user` channels needed for VoIP, - * notifications, and presence. Used after `forceReopen()` — once the socket + * notifications, and presence. Used after `reopenNow()` — once the socket * is rebuilt, any prior subscriptions are server-side stale and must be * re-established or the user receives no signals (incoming call answers, * messages, etc.) until the next full saga re-run. diff --git a/patches/@rocket.chat+ddp-client+0.3.51.patch b/patches/@rocket.chat+ddp-client+0.3.51.patch index 47b736d67c0..86706879c78 100644 --- a/patches/@rocket.chat+ddp-client+0.3.51.patch +++ b/patches/@rocket.chat+ddp-client+0.3.51.patch @@ -1,59 +1,27 @@ ---- a/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts -+++ b/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts -@@ -21,6 +21,26 @@ - connect(): Promise; - reconnect(): Promise; - close(): void; -+ /** -+ * Active liveness check: sends a raw DDP `ping` and resolves true if `pong` -+ * arrives within `timeoutMs`. Useful for detecting zombie sockets that -+ * remain in `status === 'connected'` but whose underlying transport is dead -+ * (e.g. mobile NAT timeouts that don't fire `onclose`). -+ */ -+ probe(timeoutMs?: number): Promise; -+ /** -+ * Tear down the current socket and reconnect from scratch. Concurrent -+ * callers share one in-flight promise so parallel calls don't race each -+ * other. Resolves true once the new socket is connected; false on failure. -+ */ -+ forceReopen(): Promise; -+ /** -+ * Ensure the socket is usable. If `status !== 'connected'`, force-reopens. -+ * If status is `connected`, runs a `probe()` to detect zombie sockets; on -+ * a dead probe, force-reopens. Resolves true when the socket is healthy or -+ * was successfully reopened. -+ */ -+ checkAndReopen(probeTimeoutMs?: number): Promise; - } - interface WebSocketConstructor { - new (url: string | URL, protocols?: string | string[]): WebSocket; -@@ -43,11 +63,15 @@ - ws: WebSocket | undefined; - retryCount: number; - queue: Set; -+ private reopenInFlight; - constructor(url: string, WS: WebSocketConstructor, client: DDPClient, retryOptions?: RetryOptions); - private emitStatus; - reconnect(): Promise; - connect(): Promise; - close(): void; -+ probe(timeoutMs?: number): Promise; -+ forceReopen(): Promise; -+ checkAndReopen(probeTimeoutMs?: number): Promise; - static create(url: string, webSocketImpl: WebSocketConstructor, client: DDPClient, retryOptions?: RetryOptions): ConnectionImpl; - } - export {}; +diff --git a/node_modules/@rocket.chat/ddp-client/dist/Connection.js b/node_modules/@rocket.chat/ddp-client/dist/Connection.js +index c6c3edf..15cd6be 100644 --- a/node_modules/@rocket.chat/ddp-client/dist/Connection.js +++ b/node_modules/@rocket.chat/ddp-client/dist/Connection.js -@@ -11,6 +11,7 @@ +@@ -11,6 +11,8 @@ class ConnectionImpl extends emitter_1.Emitter { this.status = 'idle'; this.retryCount = 0; this.queue = new Set(); ++ this.lastPing = Date.now(); + this.reopenInFlight = null; this.ssl = url.startsWith('https') || url.startsWith('wss'); this.url = url.replace(/^https?:\/\//, '').replace(/^wss?:\/\//, ''); this.client.onDispatchMessage((message) => { -@@ -91,6 +92,12 @@ +@@ -51,7 +53,9 @@ class ConnectionImpl extends emitter_1.Emitter { + this.ws = ws; + return new Promise((resolve, reject) => { + ws.onopen = () => { ++ this.lastPing = Date.now(); + ws.onmessage = (event) => { ++ this.lastPing = Date.now(); + this.client.handleMessage(String(event.data)); + }; + // The server may send an initial message which is a JSON object lacking a msg key. If so, the client should ignore it. The client does not have to wait for this message. +@@ -91,6 +95,12 @@ class ConnectionImpl extends emitter_1.Emitter { }); }; ws.onclose = () => { @@ -66,7 +34,7 @@ clearTimeout(this.retryOptions.retryTimer); if (this.status === 'closed') { return; -@@ -109,11 +116,75 @@ +@@ -109,11 +119,97 @@ class ConnectionImpl extends emitter_1.Emitter { }); } close() { @@ -91,56 +59,107 @@ } + probe(timeoutMs = 2000) { + return new Promise((resolve) => { -+ const ddp = this.client; -+ if (!ddp || typeof ddp.ping !== 'function' || typeof ddp.on !== 'function') { ++ if (!this.ws || this.ws.readyState !== this.ws.OPEN) { + resolve(false); + return; + } ++ const lastPingAtStart = this.lastPing; + let settled = false; + let off; -+ const finish = (alive) => { ++ const cleanup = () => { + if (settled) return; + settled = true; -+ clearTimeout(timer); + if (off) off(); -+ resolve(alive); ++ clearTimeout(timer); ++ }; ++ const onPong = () => { ++ if (this.lastPing <= lastPingAtStart) return; ++ cleanup(); ++ resolve(true); + }; -+ const timer = setTimeout(() => finish(false), timeoutMs); ++ off = this.client.on('pong', onPong); ++ const timer = setTimeout(() => { ++ cleanup(); ++ resolve(false); ++ }, timeoutMs); + try { -+ off = ddp.on('pong', () => finish(true)); -+ ddp.ping(); ++ this.ws.send(JSON.stringify({ msg: 'ping' })); + } + catch (_a) { -+ finish(false); ++ cleanup(); ++ resolve(false); + } + }); + } -+ forceReopen() { ++ reopenNow() { + if (this.reopenInFlight) return this.reopenInFlight; -+ try { -+ this.close(); -+ } -+ catch (_a) { -+ // ignore — we're rebuilding anyway ++ clearTimeout(this.retryOptions.retryTimer); ++ this.emit('disconnected'); ++ if (this.ws) { ++ try { ++ this.ws.onopen = null; ++ this.ws.onmessage = null; ++ this.ws.onerror = null; ++ this.ws.onclose = null; ++ } ++ catch (_a) { ++ // some WebSocket impls throw on null assignment — safe to ignore ++ } ++ try { ++ this.ws.close(); ++ } ++ catch (_b) { ++ // ignore — we're rebuilding anyway ++ } + } -+ const p = this.reconnect() -+ .then((v) => Boolean(v)) -+ .catch(() => false); ++ this.status = 'disconnected'; ++ const p = new Promise((resolve) => { ++ let settled = false; ++ const finish = () => { ++ if (settled) return; ++ settled = true; ++ clearTimeout(timer); ++ resolve(); ++ }; ++ const timer = setTimeout(finish, 10000); ++ this.connect().then(finish, finish); ++ }); + this.reopenInFlight = p; + const clear = () => { + if (this.reopenInFlight === p) this.reopenInFlight = null; + }; + p.then(clear, clear); + return p; -+ } -+ async checkAndReopen(probeTimeoutMs = 2000) { -+ if (this.status !== 'connected') { -+ return this.forceReopen(); -+ } -+ const alive = await this.probe(probeTimeoutMs); -+ if (alive) return true; -+ return this.forceReopen(); + } static create(url, webSocketImpl, client, retryOptions = { retryCount: 0, retryTime: 1000 }) { return new ConnectionImpl(url, webSocketImpl, client, retryOptions); } +diff --git a/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts b/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts +index 5b312fa..fabff3e 100644 +--- a/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts ++++ b/node_modules/@rocket.chat/ddp-client/dist/Connection.d.ts +@@ -21,6 +21,8 @@ export interface Connection extends Emitter<{ + connect(): Promise; + reconnect(): Promise; + close(): void; ++ probe(timeoutMs?: number): Promise; ++ reopenNow(): Promise; + } + interface WebSocketConstructor { + new (url: string | URL, protocols?: string | string[]): WebSocket; +@@ -43,11 +45,15 @@ export declare class ConnectionImpl extends Emitter<{ + ws: WebSocket | undefined; + retryCount: number; + queue: Set; ++ private lastPing; ++ private reopenInFlight; + constructor(url: string, WS: WebSocketConstructor, client: DDPClient, retryOptions?: RetryOptions); + private emitStatus; + reconnect(): Promise; + connect(): Promise; + close(): void; ++ probe(timeoutMs?: number): Promise; ++ reopenNow(): Promise; + static create(url: string, webSocketImpl: WebSocketConstructor, client: DDPClient, retryOptions?: RetryOptions): ConnectionImpl; + } + export {}; From 4586623008250abb8501aab22920f4a7c6d97673 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:15:24 +0530 Subject: [PATCH 198/203] rename handleLogin to handleConnection --- app/lib/methods/subscriptions/room.test.ts | 36 +++++++++++----------- app/lib/methods/subscriptions/room.ts | 4 +-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/lib/methods/subscriptions/room.test.ts b/app/lib/methods/subscriptions/room.test.ts index 206e6ec1e95..ca25e60f62a 100644 --- a/app/lib/methods/subscriptions/room.test.ts +++ b/app/lib/methods/subscriptions/room.test.ts @@ -158,10 +158,10 @@ describe('RoomSubscription', () => { expect(mockStoreDispatch).toHaveBeenCalledWith(clearUserTyping()); }); - it('wires handleLogin through onLogin, not the pre-auth onConnectionStatus "connected" status', async () => { + it('wires handleConnection through onLogin, not the pre-auth onConnectionStatus "connected" status', async () => { await sub.subscribe(); - expect(mockOnLogin).toHaveBeenCalledWith(sub.handleLogin); + expect(mockOnLogin).toHaveBeenCalledWith(sub.handleConnection); mockSubscribeRoom.mockClear(); const statusCallback = mockOnConnectionStatus.mock.calls[0][0]; @@ -171,9 +171,9 @@ describe('RoomSubscription', () => { }); }); - describe('handleLogin', () => { + describe('handleConnection', () => { it('calls subscribeRoom, dispatches clearUserTyping, loads missed messages, and reads', async () => { - await sub.handleLogin(); + await sub.handleConnection(); expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); expect(mockStoreDispatch).toHaveBeenCalledWith(clearUserTyping()); @@ -183,7 +183,7 @@ describe('RoomSubscription', () => { it('handles subscribeRoom rejection gracefully', async () => { mockSubscribeRoom.mockRejectedValueOnce(new Error('boom')); - await expect(sub.handleLogin()).resolves.toBeUndefined(); + await expect(sub.handleConnection()).resolves.toBeUndefined(); }); }); @@ -198,11 +198,11 @@ describe('RoomSubscription', () => { }); describe('DDP subscription recovery after reconnect', () => { - it('handleLogin re-subscribes the room to restore lost DDP subscriptions', async () => { + it('handleConnection re-subscribes the room to restore lost DDP subscriptions', async () => { await sub.subscribe(); mockSubscribeRoom.mockClear(); - await sub.handleLogin(); + await sub.handleConnection(); expect(mockSubscribeRoom).toHaveBeenCalledTimes(1); expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); @@ -223,14 +223,14 @@ describe('RoomSubscription', () => { mockSubscribeRoom.mockResolvedValueOnce([staleSub]).mockResolvedValueOnce([freshSub]); await sub.subscribe(); - await sub.handleLogin(); + await sub.handleConnection(); await sub.unsubscribe(); expect(staleSub.stop).toHaveBeenCalledTimes(1); expect(freshSub.stop).toHaveBeenCalledTimes(1); }); - it('does not accumulate subscriptions across repeated handleLogin calls (simulates sequential reopen)', async () => { + it('does not accumulate subscriptions across repeated handleConnection calls (simulates sequential reopen)', async () => { const first = { stop: jest.fn() }; const second = { stop: jest.fn() }; mockSubscribeRoom.mockResolvedValueOnce([first]).mockResolvedValueOnce([second]); @@ -239,13 +239,13 @@ describe('RoomSubscription', () => { expect(mockSubscribeRoom).toHaveBeenCalledTimes(1); // First reopen → tears down [first], creates [second] - await sub.handleLogin(); + await sub.handleConnection(); expect(mockSubscribeRoom).toHaveBeenCalledTimes(2); expect(first.stop).toHaveBeenCalledTimes(1); expect(second.stop).not.toHaveBeenCalled(); // Second reopen → tears down [second], creates [] - await sub.handleLogin(); + await sub.handleConnection(); expect(mockSubscribeRoom).toHaveBeenCalledTimes(3); expect(second.stop).toHaveBeenCalledTimes(1); @@ -255,11 +255,11 @@ describe('RoomSubscription', () => { expect(second.stop).toHaveBeenCalledTimes(1); }); - it('does not call onStreamData inside handleLogin (listeners persist across reopen)', async () => { + it('does not call onStreamData inside handleConnection (listeners persist across reopen)', async () => { await sub.subscribe(); mockOnStreamData.mockClear(); - await sub.handleLogin(); + await sub.handleConnection(); expect(mockOnStreamData).not.toHaveBeenCalled(); }); @@ -272,30 +272,30 @@ describe('RoomSubscription', () => { await sub.subscribe(); mockSubscribeRoom.mockClear(); - await expect(sub.handleLogin()).resolves.toBeUndefined(); + await expect(sub.handleConnection()).resolves.toBeUndefined(); expect(mockSubscribeRoom).toHaveBeenCalledTimes(1); expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); }); }); describe('isAlive guard', () => { - it('handleLogin does nothing once the subscription is no longer alive (race with unsubscribe)', async () => { + it('handleConnection does nothing once the subscription is no longer alive (race with unsubscribe)', async () => { await sub.subscribe(); await sub.unsubscribe(); jest.clearAllMocks(); - await sub.handleLogin(); + await sub.handleConnection(); expect(mockSubscribeRoom).not.toHaveBeenCalled(); expect(loadMissedMessages).not.toHaveBeenCalled(); expect(mockStoreDispatch).not.toHaveBeenCalled(); }); - it('handleLogin re-subscribes while the subscription is still alive', async () => { + it('handleConnection re-subscribes while the subscription is still alive', async () => { await sub.subscribe(); mockSubscribeRoom.mockClear(); - await sub.handleLogin(); + await sub.handleConnection(); expect(mockSubscribeRoom).toHaveBeenCalledWith(rid); }); diff --git a/app/lib/methods/subscriptions/room.ts b/app/lib/methods/subscriptions/room.ts index 41a83e48237..f19aa324f42 100644 --- a/app/lib/methods/subscriptions/room.ts +++ b/app/lib/methods/subscriptions/room.ts @@ -54,7 +54,7 @@ export default class RoomSubscription { this.handleClose(); } }); - this.loginListener = sdk.onLogin(this.handleLogin); + this.loginListener = sdk.onLogin(this.handleConnection); this.notifyRoomListener = sdk.onStreamData('stream-notify-room', this.handleNotifyRoomReceived); this.messageReceivedListener = sdk.onStreamData('stream-room-messages', this.handleMessageReceived); if (!this.isAlive) { @@ -94,7 +94,7 @@ export default class RoomSubscription { } }; - handleLogin = async () => { + handleConnection = async () => { if (!this.isAlive) { return; } From 9de06cdfd188fa8776acaef74cfb78ca36bdd67a Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:15:54 +0530 Subject: [PATCH 199/203] remame rawSubscriptionHandles to subscriptionHandles --- app/lib/methods/subscriptions/rooms.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts index 750c807de45..43533b98ef0 100644 --- a/app/lib/methods/subscriptions/rooms.ts +++ b/app/lib/methods/subscriptions/rooms.ts @@ -42,7 +42,7 @@ let streamListener: Promise | false; let subServer: string; let queue: { [key: string]: ISubscription | IRoom } = {}; let subTimer: ReturnType | null | false = null; -let rawSubscriptionHandles: { stop: () => void }[] = []; +let subscriptionHandles: { stop: () => void }[] = []; const WINDOW_TIME = 500; export let roomsSubscription: { stop: () => void } | null = null; @@ -428,14 +428,14 @@ export default function subscribeRooms() { streamListener.then(removeListener); streamListener = false; } - rawSubscriptionHandles.forEach(handle => { + subscriptionHandles.forEach(handle => { try { handle.stop(); } catch (e) { log(e); } }); - rawSubscriptionHandles = []; + subscriptionHandles = []; queue = {}; if (subTimer) { clearTimeout(subTimer); @@ -449,7 +449,7 @@ export default function subscribeRooms() { try { // set the server that started this task subServer = sdk.current?.connection.url || ''; - rawSubscriptionHandles = sdk.subscribeNotifyUser(); + subscriptionHandles = sdk.subscribeNotifyUser(); roomsSubscription = { stop: () => stop() }; return null; } catch (e) { From d4ff6b6a6b63b2df0e2d2858d322cca1544b0ed1 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:16:58 +0530 Subject: [PATCH 200/203] using handleConnection in test... --- app/lib/methods/subscriptions/room.resumeSync.test.ts | 4 ++-- app/lib/methods/subscriptions/roomCloseCursor.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/lib/methods/subscriptions/room.resumeSync.test.ts b/app/lib/methods/subscriptions/room.resumeSync.test.ts index 05e73f46716..4cf66f6958c 100644 --- a/app/lib/methods/subscriptions/room.resumeSync.test.ts +++ b/app/lib/methods/subscriptions/room.resumeSync.test.ts @@ -67,7 +67,7 @@ describe('RoomSubscription resume sync', () => { const persistedCursor = new Date(Date.UTC(2024, 0, 1, 11, 0, 0)); mockedGetSubscriptionByRoomId.mockResolvedValue({ lastOpen: persistedCursor } as never); - await new RoomSubscription(RID).handleLogin(); + await new RoomSubscription(RID).handleConnection(); expect(mockedSdkGet).toHaveBeenCalledWith( '/v1/chat.syncMessages', @@ -84,7 +84,7 @@ describe('RoomSubscription resume sync', () => { it('fetches nothing for a room without a sync cursor (null lastOpen): RoomView owns the initial load', async () => { mockedGetSubscriptionByRoomId.mockResolvedValue({ lastOpen: null, t: 'c' } as never); - await new RoomSubscription(RID).handleLogin(); + await new RoomSubscription(RID).handleConnection(); expect(mockedSdkGet).not.toHaveBeenCalled(); }); diff --git a/app/lib/methods/subscriptions/roomCloseCursor.test.ts b/app/lib/methods/subscriptions/roomCloseCursor.test.ts index 801a7494ec3..c4192ae3686 100644 --- a/app/lib/methods/subscriptions/roomCloseCursor.test.ts +++ b/app/lib/methods/subscriptions/roomCloseCursor.test.ts @@ -117,7 +117,7 @@ describe('closing a room while offline must not advance the sync cursor', () => await new RoomSubscription(RID).unsubscribe(); await Promise.resolve(); - await new RoomSubscription(RID).handleLogin(); + await new RoomSubscription(RID).handleConnection(); expect(mockedUpdateMessages).toHaveBeenCalledWith( expect.objectContaining({ From 313bc77cd6495abddf5a7cc43bd8cbc3a4b70846 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:24:57 +0530 Subject: [PATCH 201/203] remove unwanted comments --- app/lib/services/sdk.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index 5069c14de8d..ae6120c2f6c 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -150,7 +150,7 @@ class Sdk { const sdk = this.ensureInitialized(); try { // @ts-ignore - return await sdk.rest.get(endpoint, params, { + return sdk.rest.get(endpoint, params, { headers: this.headers }); } catch (e: any) { @@ -190,9 +190,6 @@ class Sdk { } return result; } catch (e: any) { - // @rocket.chat/api-client rejects with the raw fetch Response on transport failures (network/5xx), - // for both REST and method calls. Normalize it so callers can read e.data.* (REST) or e.error/e.reason - // (method call, which is flat, matching the shape already thrown for method-call business errors above). const normalizedResponse = e instanceof Response ? await normalizeResponseError(e) : null; const normalized = isMethodCall ? normalizedResponse?.data ?? e : normalizedResponse ?? e; const errorType = isMethodCall ? normalized?.error : normalized?.data?.errorType; @@ -258,10 +255,6 @@ class Sdk { if (loginResult?.status !== 'success' || !loginResult.data) { return Promise.reject(new Error('Invalid response from server')); } - // Auth is tracked in two places, both required: loginWithToken() sets the DDP-level session used - // by methodCall()/subscribe(), while setHeaders() below sets the REST-level auth used by get/post/delete. - // Another server may have been selected while the REST login was in flight — applying this token - // to the wrong sdk instance would authenticate server A's credentials on server B's connection. if (this.server !== server) { return Promise.reject(new Error('Server switched during login')); } @@ -395,16 +388,6 @@ class Sdk { return this.current?.stream(...args); } - /** - * Re-subscribe to the per-user `stream-notify-user` channels needed for VoIP, - * notifications, and presence. Used after `reopenNow()` — once the socket - * is rebuilt, any prior subscriptions are server-side stale and must be - * re-established or the user receives no signals (incoming call answers, - * messages, etc.) until the next full saga re-run. - * - * Ports the old SDK's `subscribeNotifyUser()` helper. Returns an empty array - * when there is no current SDK or no logged-in user. - */ subscribeNotifyUser(userId?: string) { const client = this.current?.client; const uid = userId ?? this.current?.account.user?.id; From dab8e41c75c67f81313ffba91071e854536262d9 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 3 Aug 2026 01:08:08 +0530 Subject: [PATCH 202/203] remove unwanted checkAndReopen --- app/lib/services/connect.test.ts | 39 ++------------------------------ app/lib/services/connect.ts | 17 -------------- 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/app/lib/services/connect.test.ts b/app/lib/services/connect.test.ts index 93be3ca7996..6409d70416e 100644 --- a/app/lib/services/connect.test.ts +++ b/app/lib/services/connect.test.ts @@ -1,4 +1,4 @@ -import { checkAndReopen, connect, determineAuthType, disconnect, login, loginTOTP } from './connect'; +import { connect, determineAuthType, disconnect, login, loginTOTP } from './connect'; import { mediaSessionInstance } from './voip/MediaSessionInstance'; import { pendingHangups } from './voip/pendingHangups'; import { unsubscribeRooms } from '../methods/subscribeRooms'; @@ -804,41 +804,6 @@ describe('connect — listener lifecycle across reconnects', () => { }); // ───────────────────────────────────────────────────────────────────────────── -describe('checkAndReopen', () => { - afterEach(() => { - sdkMock.__setCurrentEnabled(true); - jest.clearAllMocks(); - }); - - it('resolves false when sdk.current is undefined', async () => { - sdkMock.__setCurrentEnabled(false); - await expect(checkAndReopen()).resolves.toBe(false); - }); - - it('reopens when the connection status is not connected', async () => { - sdkMock.__setConnectionStatus('disconnected'); - await expect(checkAndReopen()).resolves.toBe(true); - expect(mockConnectionReopenNow).toHaveBeenCalledTimes(1); - expect(mockConnectionProbe).not.toHaveBeenCalled(); - }); - - it('probes when connected and skips the reopen when the socket is alive', async () => { - sdkMock.__setConnectionStatus('connected'); - mockConnectionProbe.mockResolvedValueOnce(true); - await expect(checkAndReopen()).resolves.toBe(true); - expect(mockConnectionProbe).toHaveBeenCalledTimes(1); - expect(mockConnectionReopenNow).not.toHaveBeenCalled(); - }); - - it('reopens when the probe reports the socket dead', async () => { - sdkMock.__setConnectionStatus('connected'); - mockConnectionProbe.mockResolvedValueOnce(false); - await expect(checkAndReopen()).resolves.toBe(true); - expect(mockConnectionProbe).toHaveBeenCalledTimes(1); - expect(mockConnectionReopenNow).toHaveBeenCalledTimes(1); - }); -}); - describe('connect — rooms subscription guard reset on close', () => { beforeEach(() => { jest.clearAllMocks(); @@ -852,7 +817,7 @@ describe('connect — rooms subscription guard reset on close', () => { }); // Regression: a long background marks the DDP socket stale, so foregrounding triggers - // `checkAndReopen` → `reopenNow`, which closes the stale socket and reconnects. The rooms-list `stream-notify-user` feed only re-subscribes when the + // `recoverSocket` → `reopenNow` (socketHealth.ts), which closes the stale socket and reconnects. The rooms-list `stream-notify-user` feed only re-subscribes when the // module-level guard in `subscribeRooms` is clear, and `unsubscribeRooms()` is what clears it. // If the 'close' handler stops calling `unsubscribeRooms()`, the guard stays set after reconnect // and the rooms list silently stops updating (subscriptions/favorites/reads). diff --git a/app/lib/services/connect.ts b/app/lib/services/connect.ts index c4ce1f7e5fd..a8f6a9ec19a 100644 --- a/app/lib/services/connect.ts +++ b/app/lib/services/connect.ts @@ -405,22 +405,6 @@ async function loginOAuthOrSso(params: ICredentials) { store.dispatch(loginRequest({ resume: result.token }, false)); } -async function checkAndReopen(): Promise { - const connection = sdk.current?.connection; - if (!connection) { - return false; - } - if (connection.status !== 'connected') { - await connection.reopenNow(); - return true; - } - const alive = await connection.probe(); - if (!alive) { - await connection.reopenNow(); - } - return true; -} - function disconnect() { connectAbortController?.abort(); const result = sdk.disconnect(); @@ -516,7 +500,6 @@ export { loginTOTP, loginWithPassword, loginOAuthOrSso, - checkAndReopen, connect, disconnect, getWebsocketInfo, From 98197fabfb652be15f417b9cf36c44683bc0c755 Mon Sep 17 00:00:00 2001 From: Rohit <40559587+Rohit3523@users.noreply.github.com> Date: Mon, 3 Aug 2026 02:02:31 +0530 Subject: [PATCH 203/203] added await --- app/lib/services/sdk.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/services/sdk.ts b/app/lib/services/sdk.ts index ae6120c2f6c..31ef2d06570 100644 --- a/app/lib/services/sdk.ts +++ b/app/lib/services/sdk.ts @@ -150,7 +150,7 @@ class Sdk { const sdk = this.ensureInitialized(); try { // @ts-ignore - return sdk.rest.get(endpoint, params, { + return await sdk.rest.get(endpoint, params, { headers: this.headers }); } catch (e: any) {