Files
upstream-rocketchat/apps/meteor/client/lib/UserAction.ts
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

179 lines
4.9 KiB
TypeScript

import type { IExtras, IRoomActivity, IUser } from '@rocket.chat/core-typings';
import { Emitter } from '@rocket.chat/emitter';
import { debounce } from 'lodash';
import { sdk } from './SDKClient';
import { settings } from './settings';
import { getUser, getUserId } from './user';
import { Users } from '../stores';
const TIMEOUT = 15000;
const RENEW = TIMEOUT / 3;
const USER_ACTIVITY = 'user-activity';
export const USER_ACTIVITIES = {
USER_RECORDING: 'user-recording',
USER_TYPING: 'user-typing',
USER_UPLOADING: 'user-uploading',
USER_PLAYING: 'user-playing',
};
const activityTimeouts = new Map();
const activityRenews = new Map();
const continuingIntervals = new Map();
const roomActivities = new Map<string, Set<string>>();
const rooms = new Map<string, (username: string, activityType: string[], extras?: object) => void>();
const performingUsers = new Map<string, IRoomActivity>();
const performingUsersEmitter = new Emitter<{ changed: void }>();
const shownName = function (user: IUser | null | undefined): string | undefined {
if (!user) {
return;
}
if (settings.peek('UI_Use_Real_Name')) {
return user.name;
}
return user.username;
};
const emitActivities = debounce(async (rid: string, extras: IExtras): Promise<void> => {
const activities = roomActivities.get(extras?.tmid || rid) || new Set();
sdk.publish('notify-room', [`${rid}/${USER_ACTIVITY}`, shownName(getUser()), [...activities], extras]);
}, 500);
function handleStreamAction(rid: string, username: string, activityTypes: string[], extras?: IExtras): void {
rid = extras?.tmid || rid;
const roomActivities = { ...performingUsers.get(rid) };
for (const [, activity] of Object.entries(USER_ACTIVITIES)) {
roomActivities[activity] = roomActivities[activity] || new Map();
const users = roomActivities[activity];
const timeout = users[username];
if (timeout) {
clearTimeout(timeout);
}
if (activityTypes.includes(activity)) {
activityTypes.splice(activityTypes.indexOf(activity), 1);
users[username] = setTimeout(() => handleStreamAction(rid, username, activityTypes, extras), TIMEOUT);
} else {
delete users[username];
}
}
performingUsers.set(rid, roomActivities);
performingUsersEmitter.emit('changed');
}
export const UserAction = new (class {
addStream(rid: string): () => void {
if (rooms.get(rid)) {
throw new Error('UserAction - addStream should only be called once per room');
}
const handler = function (username: string, activityType: string[], extras?: object): void {
const uid = getUserId();
const user = uid ? Users.state.get(uid) : undefined;
if (username === shownName(user)) {
return;
}
handleStreamAction(rid, username, activityType, extras);
};
rooms.set(rid, handler);
const { stop } = sdk.stream('notify-room', [`${rid}/${USER_ACTIVITY}`], handler);
return () => {
if (!rooms.get(rid)) {
return;
}
stop();
rooms.delete(rid);
};
}
performContinuously(rid: string, activityType: string, extras: IExtras = {}): void {
const trid = extras?.tmid || rid;
const key = `${activityType}-${trid}`;
if (continuingIntervals.get(key)) {
return;
}
this.start(rid, activityType, extras);
continuingIntervals.set(
key,
setInterval(() => {
this.start(rid, activityType, extras);
}, RENEW),
);
}
start(rid: string, activityType: string, extras: IExtras = {}): void {
const trid = extras?.tmid || rid;
const key = `${activityType}-${trid}`;
if (activityRenews.get(key)) {
return;
}
activityRenews.set(
key,
setTimeout(() => {
clearTimeout(activityRenews.get(key));
activityRenews.delete(key);
}, RENEW),
);
const activities = roomActivities.get(trid) || new Set();
activities.add(activityType);
roomActivities.set(trid, activities);
void emitActivities(rid, extras);
if (activityTimeouts.get(key)) {
clearTimeout(activityTimeouts.get(key));
activityTimeouts.delete(key);
}
activityTimeouts.set(
key,
setTimeout(() => this.stop(trid, activityType, extras), TIMEOUT),
);
activityTimeouts.get(key);
}
stop(rid: string, activityType: string, extras: IExtras): void {
const trid = extras?.tmid || rid;
const key = `${activityType}-${trid}`;
if (activityTimeouts.get(key)) {
clearTimeout(activityTimeouts.get(key));
activityTimeouts.delete(key);
}
if (activityRenews.get(key)) {
clearTimeout(activityRenews.get(key));
activityRenews.delete(key);
}
if (continuingIntervals.get(key)) {
clearInterval(continuingIntervals.get(key));
continuingIntervals.delete(key);
}
const activities = roomActivities.get(trid) || new Set();
activities.delete(activityType);
roomActivities.set(trid, activities);
void emitActivities(rid, extras);
}
get(roomId: string): IRoomActivity | undefined {
return performingUsers.get(roomId);
}
subscribe(onChanged: () => void): () => void {
return performingUsersEmitter.on('changed', onChanged);
}
})();