Skip to content

Commit 83eeed0

Browse files
authored
chore: User presence relocation (RocketChat#36704)
1 parent 1045010 commit 83eeed0

10 files changed

Lines changed: 114 additions & 176 deletions

File tree

apps/meteor/.meteor/packages

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
# Meteor packages used by this project, one per line.
2-
32
#
4-
53
# 'meteor add' and 'meteor remove' will edit this file for you,
6-
74
# but you can also edit it by hand.
85

96
rocketchat:ddp
107
rocketchat:mongo-config
118
rocketchat:livechat
129
rocketchat:streamer
1310
rocketchat:version
14-
rocketchat:user-presence
1511

1612
accounts-base@3.1.1
1713
accounts-facebook@1.3.4

apps/meteor/.meteor/versions

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ rocketchat:ddp@0.0.1
7474
rocketchat:livechat@0.0.1
7575
rocketchat:mongo-config@0.0.1
7676
rocketchat:streamer@1.1.0
77-
rocketchat:user-presence@2.6.3
7877
rocketchat:version@1.0.0
7978
routepolicy@1.1.2
8079
service-configuration@1.3.5
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { IUser } from '@rocket.chat/core-typings';
2+
import { UserStatus } from '@rocket.chat/core-typings';
3+
import { useConnectionStatus, useIsLoggingIn, useMethod, useUser, useUserPreference } from '@rocket.chat/ui-contexts';
4+
import { useEffect } from 'react';
5+
6+
import { withDebouncing } from '../../lib/utils/highOrderFunctions';
7+
import { Users } from '../stores';
8+
9+
// TODO: merge this with the current React-based implementation of idle detection
10+
11+
export class UserPresence {
12+
private user: IUser | undefined;
13+
14+
private timer: ReturnType<typeof setTimeout> | undefined;
15+
16+
private status: UserStatus | undefined;
17+
18+
private awayTime: number | undefined = 60_000;
19+
20+
private connected = true;
21+
22+
private goOnline: () => Promise<boolean | undefined> = async () => undefined;
23+
24+
private goAway: () => Promise<boolean | undefined> = async () => undefined;
25+
26+
private storeUser: (doc: IUser) => void = () => undefined;
27+
28+
private startTimer() {
29+
this.stopTimer();
30+
if (!this.awayTime) return;
31+
32+
this.timer = setTimeout(this.setAway, this.awayTime);
33+
}
34+
35+
private stopTimer() {
36+
clearTimeout(this.timer);
37+
}
38+
39+
private readonly setOnline = () => this.setStatus(UserStatus.ONLINE);
40+
41+
private readonly setAway = () => this.setStatus(UserStatus.AWAY);
42+
43+
private readonly setStatus = withDebouncing({ wait: 1000 })(async (newStatus: UserStatus.ONLINE | UserStatus.AWAY) => {
44+
if (!this.connected || newStatus === this.status) {
45+
this.startTimer();
46+
return;
47+
}
48+
49+
if (this.user?.status !== newStatus && this.user?.statusDefault === newStatus) {
50+
this.storeUser({ ...this.user, status: newStatus });
51+
}
52+
53+
switch (newStatus) {
54+
case UserStatus.ONLINE:
55+
await this.goOnline();
56+
break;
57+
58+
case UserStatus.AWAY:
59+
await this.goAway();
60+
this.stopTimer();
61+
break;
62+
}
63+
64+
this.status = newStatus;
65+
});
66+
67+
readonly use = () => {
68+
const user = useUser() ?? undefined;
69+
const { connected } = useConnectionStatus();
70+
const isLoggingIn = useIsLoggingIn();
71+
const enableAutoAway = useUserPreference<boolean>('enableAutoAway');
72+
const idleTimeLimit = useUserPreference<number>('idleTimeLimit') ?? 300;
73+
74+
this.user = user;
75+
this.connected = connected;
76+
this.awayTime = enableAutoAway ? idleTimeLimit * 1000 : undefined;
77+
this.goOnline = useMethod('UserPresence:online');
78+
this.goAway = useMethod('UserPresence:away');
79+
this.storeUser = Users.use((state) => state.store);
80+
81+
useEffect(() => {
82+
const documentEvents = ['mousemove', 'mousedown', 'touchend', 'keydown'] as const;
83+
documentEvents.forEach((key) => document.addEventListener(key, this.setOnline));
84+
window.addEventListener('focus', this.setOnline);
85+
86+
return () => {
87+
documentEvents.forEach((key) => document.removeEventListener(key, this.setOnline));
88+
window.removeEventListener('focus', this.setOnline);
89+
};
90+
}, []);
91+
92+
useEffect(() => {
93+
if (!user || !connected || isLoggingIn) return;
94+
this.startTimer();
95+
}, [connected, isLoggingIn, user]);
96+
97+
useEffect(() => {
98+
if (connected) {
99+
this.startTimer();
100+
this.status = UserStatus.ONLINE;
101+
return;
102+
}
103+
this.stopTimer();
104+
this.status = UserStatus.OFFLINE;
105+
}, [connected]);
106+
};
107+
}

apps/meteor/client/providers/UserPresenceProvider.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import type { UserPresenceContextValue } from '@rocket.chat/ui-contexts';
22
import { useSetting, UserPresenceContext } from '@rocket.chat/ui-contexts';
3-
import type { ReactElement, ReactNode } from 'react';
3+
import type { ReactNode } from 'react';
44
import { useMemo, useEffect } from 'react';
55

66
import { Presence } from '../lib/presence';
7+
import { UserPresence } from '../lib/userPresence';
8+
9+
const userPresence = new UserPresence();
710

811
type UserPresenceProviderProps = {
912
children?: ReactNode;
1013
};
1114

12-
const UserPresenceProvider = ({ children }: UserPresenceProviderProps): ReactElement => {
15+
const UserPresenceProvider = ({ children }: UserPresenceProviderProps) => {
16+
userPresence.use();
17+
1318
const usePresenceDisabled = useSetting('Presence_broadcast_disabled', false);
1419

1520
useEffect(() => {

apps/meteor/client/startup/startup.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import type { UserStatus } from '@rocket.chat/core-typings';
22
import { Meteor } from 'meteor/meteor';
3-
import { UserPresence } from 'meteor/rocketchat:user-presence';
43
import { Tracker } from 'meteor/tracker';
54
import moment from 'moment';
65

7-
import { getUserPreference } from '../../app/utils/client';
86
import 'highlight.js/styles/github.css';
97
import { sdk } from '../../app/utils/client/lib/SDKClient';
108
import { synchronizeUserData, removeLocalUserData } from '../lib/userData';
@@ -13,9 +11,6 @@ import { fireGlobalEvent } from '../lib/utils/fireGlobalEvent';
1311
Meteor.startup(() => {
1412
fireGlobalEvent('startup', true);
1513

16-
window.lastMessageWindow = {};
17-
window.lastMessageWindowHistory = {};
18-
1914
let status: UserStatus | undefined = undefined;
2015
Tracker.autorun(async () => {
2116
const uid = Meteor.userId();
@@ -42,16 +37,6 @@ Meteor.startup(() => {
4237
sdk.call('userSetUtcOffset', utcOffset);
4338
}
4439

45-
if (getUserPreference(user, 'enableAutoAway')) {
46-
const idleTimeLimit = (getUserPreference(user, 'idleTimeLimit') as number | null | undefined) || 300;
47-
UserPresence.awayTime = idleTimeLimit * 1000;
48-
} else {
49-
delete UserPresence.awayTime;
50-
UserPresence.stopTimer();
51-
}
52-
53-
UserPresence.start();
54-
5540
if (user.status !== status) {
5641
status = user.status;
5742
fireGlobalEvent('status-changed', status);

apps/meteor/definition/externals/meteor/konecty-user-presence.d.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

apps/meteor/packages/meteor-user-presence/.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

apps/meteor/packages/meteor-user-presence/client/client.js

Lines changed: 0 additions & 113 deletions
This file was deleted.

apps/meteor/packages/meteor-user-presence/client/utils.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

apps/meteor/packages/meteor-user-presence/package.js

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)