chore: User presence relocation (#36704)
parent
1045010054
commit
83eeed0a8c
@ -0,0 +1,107 @@ |
||||
import type { IUser } from '@rocket.chat/core-typings'; |
||||
import { UserStatus } from '@rocket.chat/core-typings'; |
||||
import { useConnectionStatus, useIsLoggingIn, useMethod, useUser, useUserPreference } from '@rocket.chat/ui-contexts'; |
||||
import { useEffect } from 'react'; |
||||
|
||||
import { withDebouncing } from '../../lib/utils/highOrderFunctions'; |
||||
import { Users } from '../stores'; |
||||
|
||||
// TODO: merge this with the current React-based implementation of idle detection
|
||||
|
||||
export class UserPresence { |
||||
private user: IUser | undefined; |
||||
|
||||
private timer: ReturnType<typeof setTimeout> | undefined; |
||||
|
||||
private status: UserStatus | undefined; |
||||
|
||||
private awayTime: number | undefined = 60_000; |
||||
|
||||
private connected = true; |
||||
|
||||
private goOnline: () => Promise<boolean | undefined> = async () => undefined; |
||||
|
||||
private goAway: () => Promise<boolean | undefined> = async () => undefined; |
||||
|
||||
private storeUser: (doc: IUser) => void = () => undefined; |
||||
|
||||
private startTimer() { |
||||
this.stopTimer(); |
||||
if (!this.awayTime) return; |
||||
|
||||
this.timer = setTimeout(this.setAway, this.awayTime); |
||||
} |
||||
|
||||
private stopTimer() { |
||||
clearTimeout(this.timer); |
||||
} |
||||
|
||||
private readonly setOnline = () => this.setStatus(UserStatus.ONLINE); |
||||
|
||||
private readonly setAway = () => this.setStatus(UserStatus.AWAY); |
||||
|
||||
private readonly setStatus = withDebouncing({ wait: 1000 })(async (newStatus: UserStatus.ONLINE | UserStatus.AWAY) => { |
||||
if (!this.connected || newStatus === this.status) { |
||||
this.startTimer(); |
||||
return; |
||||
} |
||||
|
||||
if (this.user?.status !== newStatus && this.user?.statusDefault === newStatus) { |
||||
this.storeUser({ ...this.user, status: newStatus }); |
||||
} |
||||
|
||||
switch (newStatus) { |
||||
case UserStatus.ONLINE: |
||||
await this.goOnline(); |
||||
break; |
||||
|
||||
case UserStatus.AWAY: |
||||
await this.goAway(); |
||||
this.stopTimer(); |
||||
break; |
||||
} |
||||
|
||||
this.status = newStatus; |
||||
}); |
||||
|
||||
readonly use = () => { |
||||
const user = useUser() ?? undefined; |
||||
const { connected } = useConnectionStatus(); |
||||
const isLoggingIn = useIsLoggingIn(); |
||||
const enableAutoAway = useUserPreference<boolean>('enableAutoAway'); |
||||
const idleTimeLimit = useUserPreference<number>('idleTimeLimit') ?? 300; |
||||
|
||||
this.user = user; |
||||
this.connected = connected; |
||||
this.awayTime = enableAutoAway ? idleTimeLimit * 1000 : undefined; |
||||
this.goOnline = useMethod('UserPresence:online'); |
||||
this.goAway = useMethod('UserPresence:away'); |
||||
this.storeUser = Users.use((state) => state.store); |
||||
|
||||
useEffect(() => { |
||||
const documentEvents = ['mousemove', 'mousedown', 'touchend', 'keydown'] as const; |
||||
documentEvents.forEach((key) => document.addEventListener(key, this.setOnline)); |
||||
window.addEventListener('focus', this.setOnline); |
||||
|
||||
return () => { |
||||
documentEvents.forEach((key) => document.removeEventListener(key, this.setOnline)); |
||||
window.removeEventListener('focus', this.setOnline); |
||||
}; |
||||
}, []); |
||||
|
||||
useEffect(() => { |
||||
if (!user || !connected || isLoggingIn) return; |
||||
this.startTimer(); |
||||
}, [connected, isLoggingIn, user]); |
||||
|
||||
useEffect(() => { |
||||
if (connected) { |
||||
this.startTimer(); |
||||
this.status = UserStatus.ONLINE; |
||||
return; |
||||
} |
||||
this.stopTimer(); |
||||
this.status = UserStatus.OFFLINE; |
||||
}, [connected]); |
||||
}; |
||||
} |
||||
@ -1,14 +0,0 @@ |
||||
declare module 'meteor/rocketchat:user-presence' { |
||||
namespace UserPresenceMonitor { |
||||
function processUserSession(userSession: any, event: string): void; |
||||
function onSetUserStatus(callback: any): void; |
||||
} |
||||
|
||||
namespace UserPresence { |
||||
let awayTime: number | undefined; |
||||
function removeConnectionsByInstanceId(id: string): void; |
||||
function start(): void; |
||||
function stopTimer(): void; |
||||
function setDefaultStatus(userId: string, status: string): void; |
||||
} |
||||
} |
||||
@ -1 +0,0 @@ |
||||
.npm/ |
||||
@ -1,113 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Tracker } from 'meteor/tracker'; |
||||
import { check } from 'meteor/check'; |
||||
|
||||
import { debounce } from './utils'; |
||||
|
||||
let timer; |
||||
let status; |
||||
|
||||
export const UserPresence = { |
||||
awayTime: 60000, // 1 minute
|
||||
awayOnWindowBlur: false, |
||||
callbacks: [], |
||||
connected: true, |
||||
started: false, |
||||
userId: null, |
||||
|
||||
/** |
||||
* The callback will receive the following parameters: user, status |
||||
*/ |
||||
onSetUserStatus(callback) { |
||||
this.callbacks.push(callback); |
||||
}, |
||||
|
||||
runCallbacks(user, status) { |
||||
this.callbacks.forEach(function (callback) { |
||||
callback.call(null, user, status); |
||||
}); |
||||
}, |
||||
|
||||
startTimer() { |
||||
UserPresence.stopTimer(); |
||||
if (!UserPresence.awayTime) { |
||||
return; |
||||
} |
||||
timer = setTimeout(UserPresence.setAway, UserPresence.awayTime); |
||||
}, |
||||
stopTimer() { |
||||
clearTimeout(timer); |
||||
}, |
||||
restartTimer() { |
||||
UserPresence.startTimer(); |
||||
}, |
||||
// eslint-disable-next-line no-use-before-define
|
||||
setAway: () => setUserPresence('away'), |
||||
// eslint-disable-next-line no-use-before-define
|
||||
setOnline: () => setUserPresence('online'), |
||||
start(userId) { |
||||
// after first call overwrite start function to only call startTimer
|
||||
this.start = () => { |
||||
this.startTimer(); |
||||
}; |
||||
this.userId = userId; |
||||
|
||||
// register a tracker on connection status so we can setup the away timer again (on reconnect)
|
||||
Tracker.autorun(() => { |
||||
const { connected } = Meteor.status(); |
||||
this.connected = connected; |
||||
if (connected) { |
||||
this.startTimer(); |
||||
status = 'online'; |
||||
return; |
||||
} |
||||
this.stopTimer(); |
||||
status = 'offline'; |
||||
}); |
||||
|
||||
['mousemove', 'mousedown', 'touchend', 'keydown'].forEach((key) => document.addEventListener(key, this.setOnline)); |
||||
|
||||
window.addEventListener('focus', this.setOnline); |
||||
|
||||
if (this.awayOnWindowBlur === true) { |
||||
window.addEventListener('blur', this.setAway); |
||||
} |
||||
}, |
||||
}; |
||||
|
||||
const setUserPresence = debounce(async (newStatus) => { |
||||
if (!UserPresence.connected || newStatus === status) { |
||||
UserPresence.startTimer(); |
||||
return; |
||||
} |
||||
switch (newStatus) { |
||||
case 'online': |
||||
await Meteor.callAsync('UserPresence:online', UserPresence.userId); |
||||
break; |
||||
case 'away': |
||||
await Meteor.callAsync('UserPresence:away', UserPresence.userId); |
||||
UserPresence.stopTimer(); |
||||
break; |
||||
default: |
||||
return; |
||||
} |
||||
status = newStatus; |
||||
}, 1000); |
||||
|
||||
Meteor.methods({ |
||||
async 'UserPresence:setDefaultStatus'(status) { |
||||
check(status, String); |
||||
await Meteor.users.updateAsync({ _id: Meteor.userId() }, { $set: { status, statusDefault: status } }); |
||||
}, |
||||
async 'UserPresence:online'() { |
||||
const user = await Meteor.userAsync(); |
||||
if (user && user.status !== 'online' && user.statusDefault === 'online') { |
||||
await Meteor.users.updateAsync({ _id: Meteor.userId() }, { $set: { status: 'online' } }); |
||||
} |
||||
UserPresence.runCallbacks(user, 'online'); |
||||
}, |
||||
async 'UserPresence:away'() { |
||||
const user = await Meteor.userAsync(); |
||||
UserPresence.runCallbacks(user, 'away'); |
||||
}, |
||||
}); |
||||
@ -1,8 +0,0 @@ |
||||
export function debounce(func, wait) { |
||||
let timeout; |
||||
|
||||
return (...args) => { |
||||
if (timeout) clearTimeout(timeout); |
||||
timeout = setTimeout(() => func(...args), wait); |
||||
}; |
||||
} |
||||
@ -1,18 +0,0 @@ |
||||
Package.describe({ |
||||
name: 'rocketchat:user-presence', |
||||
summary: 'Track user status', |
||||
version: '2.6.3', |
||||
git: 'https://github.com/Konecty/meteor-user-presence', |
||||
}); |
||||
|
||||
Package.onUse(function (api) { |
||||
api.use('tracker'); |
||||
api.use('check'); |
||||
api.use('ecmascript'); |
||||
|
||||
api.mainModule('client/client.js', 'client'); |
||||
}); |
||||
|
||||
Npm.depends({ |
||||
colors: '1.3.2', |
||||
}); |
||||
Loading…
Reference in new issue