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
179 lines
4.9 KiB
TypeScript
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);
|
|
}
|
|
})();
|