From 4a290cf1a3b3f46d28429f491ecee78114e19f3b Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Mon, 23 Mar 2026 16:15:52 -0300 Subject: [PATCH 1/8] fix: updateStatusText not broadcasting presence.status --- apps/meteor/app/apps/server/bridges/users.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/meteor/app/apps/server/bridges/users.ts b/apps/meteor/app/apps/server/bridges/users.ts index 3c27eab902521..a6a66862e974c 100644 --- a/apps/meteor/app/apps/server/bridges/users.ts +++ b/apps/meteor/app/apps/server/bridges/users.ts @@ -9,6 +9,7 @@ import { Random } from '@rocket.chat/random'; import { checkUsernameAvailability } from '../../../lib/server/functions/checkUsernameAvailability'; import { deleteUser } from '../../../lib/server/functions/deleteUser'; import { getUserCreatedByApp } from '../../../lib/server/functions/getUserCreatedByApp'; +import { setStatusText } from '../../../lib/server/functions/setStatusText'; import { setUserActiveStatus } from '../../../lib/server/functions/setUserActiveStatus'; import { setUserAvatar } from '../../../lib/server/functions/setUserAvatar'; import { notifyOnUserChange, notifyOnUserChangeById } from '../../../lib/server/lib/notifyListener'; @@ -134,6 +135,21 @@ export class AppUserBridge extends UserBridge { const { status } = fields; delete fields.status; + if (!status && typeof fields.statusText === 'string') { + await setStatusText( + { + _id: user.id, + username: user.username, + name: user.name, + status: user.status as UserStatus, + roles: user.roles, + statusText: user.statusText, + }, + fields.statusText, + ); + delete fields.statusText; + } + if (status) { await Presence.setStatus(user.id, status as UserStatus, fields.statusText); } From 01cca4ee30fa2cd110385ff6d345025335ed1fc9 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 23 Mar 2026 16:54:56 -0300 Subject: [PATCH 2/8] small refactor --- apps/meteor/app/apps/server/bridges/users.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/users.ts b/apps/meteor/app/apps/server/bridges/users.ts index a6a66862e974c..08d2c1d79477c 100644 --- a/apps/meteor/app/apps/server/bridges/users.ts +++ b/apps/meteor/app/apps/server/bridges/users.ts @@ -132,10 +132,13 @@ export class AppUserBridge extends UserBridge { return true; } - const { status } = fields; + const { status, statusText } = fields; + delete fields.statusText; delete fields.status; - if (!status && typeof fields.statusText === 'string') { + if (status) { + await Presence.setStatus(user.id, status as UserStatus, statusText); + } else if (typeof statusText === 'string') { await setStatusText( { _id: user.id, @@ -145,13 +148,8 @@ export class AppUserBridge extends UserBridge { roles: user.roles, statusText: user.statusText, }, - fields.statusText, + statusText, ); - delete fields.statusText; - } - - if (status) { - await Presence.setStatus(user.id, status as UserStatus, fields.statusText); } await Users.updateOne({ _id: user.id }, { $set: fields as any }); From 2331dd261a69d3a7ae7a4c8f6eba1bdbb67c5b16 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Mon, 23 Mar 2026 16:58:50 -0300 Subject: [PATCH 3/8] apply suggestion from ancestral review --- apps/meteor/app/apps/server/bridges/users.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/users.ts b/apps/meteor/app/apps/server/bridges/users.ts index 08d2c1d79477c..41916b9921c1c 100644 --- a/apps/meteor/app/apps/server/bridges/users.ts +++ b/apps/meteor/app/apps/server/bridges/users.ts @@ -132,9 +132,7 @@ export class AppUserBridge extends UserBridge { return true; } - const { status, statusText } = fields; - delete fields.statusText; - delete fields.status; + const { status, statusText, ...updateFields } = fields; if (status) { await Presence.setStatus(user.id, status as UserStatus, statusText); @@ -152,9 +150,9 @@ export class AppUserBridge extends UserBridge { ); } - await Users.updateOne({ _id: user.id }, { $set: fields as any }); + await Users.updateOne({ _id: user.id }, { $set: updateFields as any }); - void notifyOnUserChange({ clientAction: 'updated', id: user.id, diff: fields }); + void notifyOnUserChange({ clientAction: 'updated', id: user.id, diff: updateFields }); return true; } From 35daadc94884e0465a4fcd122aaa88bb6f193a11 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Mon, 23 Mar 2026 20:12:19 -0300 Subject: [PATCH 4/8] chore: changeset --- .changeset/serious-eggs-type.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/serious-eggs-type.md diff --git a/.changeset/serious-eggs-type.md b/.changeset/serious-eggs-type.md new file mode 100644 index 0000000000000..1a2758a28fe65 --- /dev/null +++ b/.changeset/serious-eggs-type.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes an issue where the apps-engine updateStatusText method isn't updating the app user status text properly From 261b1d6060173f279d1c28f8c065e758bdc91b45 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 24 Mar 2026 12:18:21 -0300 Subject: [PATCH 5/8] test: add update-status-test app and its tests --- .../tests/data/apps/app-packages/README.md | 113 ++++++++++++++++++ .../tests/data/apps/app-packages/index.ts | 2 + .../app-packages/update-status-test_0.0.1.zip | Bin 0 -> 24385 bytes .../end-to-end/apps/update-status-text.ts | 95 +++++++++++++++ 4 files changed, 210 insertions(+) create mode 100644 apps/meteor/tests/data/apps/app-packages/update-status-test_0.0.1.zip create mode 100644 apps/meteor/tests/end-to-end/apps/update-status-text.ts diff --git a/apps/meteor/tests/data/apps/app-packages/README.md b/apps/meteor/tests/data/apps/app-packages/README.md index 0d4caae341d16..bdb3f214bafbe 100644 --- a/apps/meteor/tests/data/apps/app-packages/README.md +++ b/apps/meteor/tests/data/apps/app-packages/README.md @@ -180,3 +180,116 @@ export class NestedRequestsApp extends App implements IPostMessageSent { ``` + +#### Update Status Test + +File name: `update-status-test_0.0.1.zip` + +An app that provides two public API endpoints to test the `updateStatus` and `updateStatusText` bridge methods. A `username` parameter is required to specify the target user. + +**Endpoints:** + +- `POST /update-status` — Calls `updateStatus(user, statusText, status)`. Expects `{ username: string, status: string, statusText?: string }`. +- `POST /update-status-text` — Calls `updateStatusText(user, statusText)`. Expects `{ username: string, statusText: string }`. + +
+App source code + +**UpdateStatusTestApp.ts** +```typescript +import { + IAppAccessors, + IConfigurationExtend, + ILogger, +} from '@rocket.chat/apps-engine/definition/accessors'; +import { ApiSecurity, ApiVisibility } from '@rocket.chat/apps-engine/definition/api'; +import { App } from '@rocket.chat/apps-engine/definition/App'; +import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; +import { UpdateStatusEndpoint } from './endpoints/UpdateStatusEndpoint'; +import { UpdateStatusTextEndpoint } from './endpoints/UpdateStatusTextEndpoint'; + +export class UpdateStatusTestApp extends App { + constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) { + super(info, logger, accessors); + } + + protected async extendConfiguration(configuration: IConfigurationExtend): Promise { + await configuration.api.provideApi({ + visibility: ApiVisibility.PUBLIC, + security: ApiSecurity.UNSECURE, + endpoints: [ + new UpdateStatusEndpoint(this), + new UpdateStatusTextEndpoint(this), + ], + }); + } +} +``` + +**endpoints/UpdateStatusEndpoint.ts** +```typescript +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { ApiEndpoint, IApiEndpointInfo, IApiRequest, IApiResponse } from '@rocket.chat/apps-engine/definition/api'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; + +export class UpdateStatusEndpoint extends ApiEndpoint { + public path = 'update-status'; + + public async post(request: IApiRequest, endpoint: IApiEndpointInfo, read: IRead, modify: IModify, http: IHttp, persis: IPersistence): Promise { + const { status, statusText = '', username } = request.content || {}; + + if (!status) { + return { status: 400, content: 'status is required' }; + } + + if (!username) { + return { status: 400, content: 'username is required' }; + } + + const user = await read.getUserReader().getByUsername(username) as IUser; + + if (!user) { + return { status: 404, content: 'User not found' }; + } + + await modify.getUpdater().getUserUpdater().updateStatus(user, statusText, status); + + return this.success(JSON.stringify({ status, statusText })); + } +} +``` + +**endpoints/UpdateStatusTextEndpoint.ts** +```typescript +import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { ApiEndpoint, IApiEndpointInfo, IApiRequest, IApiResponse } from '@rocket.chat/apps-engine/definition/api'; +import { IUser } from '@rocket.chat/apps-engine/definition/users'; + +export class UpdateStatusTextEndpoint extends ApiEndpoint { + public path = 'update-status-text'; + + public async post(request: IApiRequest, endpoint: IApiEndpointInfo, read: IRead, modify: IModify, http: IHttp, persis: IPersistence): Promise { + const { statusText, username } = request.content || {}; + + if (typeof statusText !== 'string') { + return { status: 400, content: 'statusText is required' }; + } + + if (!username) { + return { status: 400, content: 'username is required' }; + } + + const user = await read.getUserReader().getByUsername(username) as IUser; + + if (!user) { + return { status: 404, content: 'User not found' }; + } + + await modify.getUpdater().getUserUpdater().updateStatusText(user, statusText); + + return this.success(JSON.stringify({ statusText })); + } +} +``` + +
diff --git a/apps/meteor/tests/data/apps/app-packages/index.ts b/apps/meteor/tests/data/apps/app-packages/index.ts index 968480262ba6e..4fa2e5d719f76 100644 --- a/apps/meteor/tests/data/apps/app-packages/index.ts +++ b/apps/meteor/tests/data/apps/app-packages/index.ts @@ -5,3 +5,5 @@ export const appImplementsIPreFileUpload = path.resolve(__dirname, './file-uploa export const appAPIParameterTest = path.resolve(__dirname, './api-parameter-test_0.0.1.zip'); export const appCausingNestedRequests = path.resolve(__dirname, './nested-requests_0.0.1.zip'); + +export const appUpdateStatusTest = path.resolve(__dirname, './update-status-test_0.0.1.zip'); diff --git a/apps/meteor/tests/data/apps/app-packages/update-status-test_0.0.1.zip b/apps/meteor/tests/data/apps/app-packages/update-status-test_0.0.1.zip new file mode 100644 index 0000000000000000000000000000000000000000..9eacbf93a375c3819cdba75b76ce8bcb8e56744b GIT binary patch literal 24385 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�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 Se;y;h92qY^D@y;*-Twjh=`l0_ literal 0 HcmV?d00001 diff --git a/apps/meteor/tests/end-to-end/apps/update-status-text.ts b/apps/meteor/tests/end-to-end/apps/update-status-text.ts new file mode 100644 index 0000000000000..415879931bbfe --- /dev/null +++ b/apps/meteor/tests/end-to-end/apps/update-status-text.ts @@ -0,0 +1,95 @@ +import type { App } from '@rocket.chat/core-typings'; +import { expect } from 'chai'; +import { after, before, describe, it } from 'mocha'; + +import { getCredentials, request, credentials } from '../../data/api-data'; +import { appUpdateStatusTest } from '../../data/apps/app-packages'; +import { apps } from '../../data/apps/apps-data'; +import { cleanupApps, installLocalTestPackage } from '../../data/apps/helper'; +import { getUserByUsername } from '../../data/users.helper'; +import { IS_EE } from '../../e2e/config/constants'; + +const APP_USERNAME = 'update-status-test.bot'; + +(IS_EE ? describe : describe.skip)('Apps - Update App User Status', () => { + let app: App; + + before((done) => getCredentials(done)); + + before(async () => { + await cleanupApps(); + app = await installLocalTestPackage(appUpdateStatusTest); + }); + + after(() => cleanupApps()); + + describe('[updateStatusText]', () => { + it('should return 400 when statusText is missing', async () => { + await request + .post(apps(`/public/${app.id}/update-status-text`)) + .set(credentials) + .send({ username: APP_USERNAME }) + .expect(400); + }); + + it('should update the app user statusText', async () => { + const statusText = `test-status-${Date.now()}`; + + await request + .post(apps(`/public/${app.id}/update-status-text`)) + .set(credentials) + .send({ username: APP_USERNAME, statusText }) + .expect(200); + + const appUser = await getUserByUsername(APP_USERNAME); + expect(appUser.statusText).to.be.equal(statusText); + }); + + it('should clear the app user statusText', async () => { + await request + .post(apps(`/public/${app.id}/update-status-text`)) + .set(credentials) + .send({ username: APP_USERNAME, statusText: '' }) + .expect(200); + + const appUser = await getUserByUsername(APP_USERNAME); + expect(appUser.statusText).to.be.equal(''); + }); + }); + + describe('[updateStatus]', () => { + it('should return 400 when status is missing', async () => { + await request + .post(apps(`/public/${app.id}/update-status`)) + .set(credentials) + .send({ username: APP_USERNAME }) + .expect(400); + }); + + it('should update the app user statusText when status and statusText is provided', async () => { + const statusText = `busy-status-${Date.now()}`; + + await request + .post(apps(`/public/${app.id}/update-status`)) + .set(credentials) + .send({ username: APP_USERNAME, status: 'busy', statusText }) + .expect(200); + + const appUser = await getUserByUsername(APP_USERNAME); + expect(appUser.statusText).to.be.equal(statusText); + }); + + it('should update status without changing statusText', async () => { + const userBefore = await getUserByUsername(APP_USERNAME); + + await request + .post(apps(`/public/${app.id}/update-status`)) + .set(credentials) + .send({ username: APP_USERNAME, status: 'away' }) + .expect(200); + + const appUser = await getUserByUsername(APP_USERNAME); + expect(appUser.statusText).to.be.equal(userBefore.statusText); + }); + }); +}); From c8d83ec264927492e943a1f5bcb652b648049173 Mon Sep 17 00:00:00 2001 From: Douglas Fabris Date: Tue, 24 Mar 2026 17:18:34 -0300 Subject: [PATCH 6/8] test: remove 400 error tests for updateStatusText and updateStatus --- .../tests/end-to-end/apps/update-status-text.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/apps/meteor/tests/end-to-end/apps/update-status-text.ts b/apps/meteor/tests/end-to-end/apps/update-status-text.ts index 415879931bbfe..dd3f0617d65c7 100644 --- a/apps/meteor/tests/end-to-end/apps/update-status-text.ts +++ b/apps/meteor/tests/end-to-end/apps/update-status-text.ts @@ -24,14 +24,6 @@ const APP_USERNAME = 'update-status-test.bot'; after(() => cleanupApps()); describe('[updateStatusText]', () => { - it('should return 400 when statusText is missing', async () => { - await request - .post(apps(`/public/${app.id}/update-status-text`)) - .set(credentials) - .send({ username: APP_USERNAME }) - .expect(400); - }); - it('should update the app user statusText', async () => { const statusText = `test-status-${Date.now()}`; @@ -58,14 +50,6 @@ const APP_USERNAME = 'update-status-test.bot'; }); describe('[updateStatus]', () => { - it('should return 400 when status is missing', async () => { - await request - .post(apps(`/public/${app.id}/update-status`)) - .set(credentials) - .send({ username: APP_USERNAME }) - .expect(400); - }); - it('should update the app user statusText when status and statusText is provided', async () => { const statusText = `busy-status-${Date.now()}`; From 97cb2c6f959e50ef26cf09209ddf31268b868442 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 24 Mar 2026 18:31:51 -0300 Subject: [PATCH 7/8] refactor: clarify why the user status can't be checked on tests --- apps/meteor/tests/end-to-end/apps/update-status-text.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/meteor/tests/end-to-end/apps/update-status-text.ts b/apps/meteor/tests/end-to-end/apps/update-status-text.ts index dd3f0617d65c7..8b3deece0692b 100644 --- a/apps/meteor/tests/end-to-end/apps/update-status-text.ts +++ b/apps/meteor/tests/end-to-end/apps/update-status-text.ts @@ -60,6 +60,10 @@ const APP_USERNAME = 'update-status-test.bot'; .expect(200); const appUser = await getUserByUsername(APP_USERNAME); + + // We can't test the status value because the Presence service will override it with OFFLINE + // when the user doesn't have an active session/connection + // expect(appUser.status).to.equal(status); expect(appUser.statusText).to.be.equal(statusText); }); @@ -73,6 +77,10 @@ const APP_USERNAME = 'update-status-test.bot'; .expect(200); const appUser = await getUserByUsername(APP_USERNAME); + + // We can't test the status value because the Presence service will override it with OFFLINE + // when the user doesn't have an active session/connection + // expect(appUser.status).to.equal(status); expect(appUser.statusText).to.be.equal(userBefore.statusText); }); }); From 34feb50a570ea4c6d66a2a46c25f90c5c4a1b656 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 24 Mar 2026 18:32:31 -0300 Subject: [PATCH 8/8] fix: incorrect validation placement --- apps/meteor/app/apps/server/bridges/users.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/users.ts b/apps/meteor/app/apps/server/bridges/users.ts index 41916b9921c1c..43cd7f9ea89e1 100644 --- a/apps/meteor/app/apps/server/bridges/users.ts +++ b/apps/meteor/app/apps/server/bridges/users.ts @@ -128,10 +128,6 @@ export class AppUserBridge extends UserBridge { throw new Error('User not provided'); } - if (!Object.keys(fields).length) { - return true; - } - const { status, statusText, ...updateFields } = fields; if (status) { @@ -150,6 +146,10 @@ export class AppUserBridge extends UserBridge { ); } + if (!Object.keys(updateFields).length) { + return true; + } + await Users.updateOne({ _id: user.id }, { $set: updateFields as any }); void notifyOnUserChange({ clientAction: 'updated', id: user.id, diff: updateFields });