我需要实现一种方法来跟踪浏览器(Angular 应用程序)中的空闲状态/不活动状态,因此我尝试创建一个可观察对象,只要没有任何用户交互事件就会触发该可观察对象,以便客户端可以自动注销:
import {filter, from, fromEvent, mergeAll, Observable, of, repeat, timeout} from 'rxjs';
const timeoutDelay = 1000 * 60 * 5; // 5 minutes
const events = ['keypress', 'click', 'wheel', 'mousemove', 'ontouchstart'];
const $onInactive = from(events.map(e => fromEvent(document, e)))
.pipe(
mergeAll(),
timeout({each: timeoutDelay, with: () => of(undefined as any)}),
filter(a => !a),
repeat()
);
Run Code Online (Sandbox Code Playgroud)
虽然我能够使其与上面的代码一起工作,但我不确定对于这个特定任务来说,这是否正确或有效地完成,所以我只想在这里仔细检查。
客户端将使用上面的代码,如下所示:
$onInvactive.subscribe(() => {
// no user activity for 5 minutes
// if currently logged in, then log out
});
Run Code Online (Sandbox Code Playgroud)
在 Web 客户端的整个生命周期中,只要发生不活动,通知就会到达。
解决方案
根据已接受的答案,下面是一个完整的 Angular v15 服务:
import {Injectable} from '@angular/core';
import {
Observable,
fromEvent, interval, merge,
repeat, takeUntil, map,
} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class InactivityService {
readonly timeoutDelay = 1000 * 60 * 5; // 5 minutes
readonly $onIdle: Observable<void>;
constructor() {
const events = ['keypress', 'click', 'wheel', 'mousemove', 'ontouchstart'];
const $signal = merge(...events.map(eventName => fromEvent(document, eventName)));
this.$onIdle = interval(this.timeoutDelay).pipe(takeUntil($signal), map(() => undefined), repeat());
}
}
Run Code Online (Sandbox Code Playgroud)
因此任何组件或其他服务都可以轻松跟踪空闲状态:
constructor(private inactivity: InactivityService) {
this.inactivity.$onIdle.subscribe(() => {
// user is idle for 5 minutes;
// it is emitted repeatedly
});
}
Run Code Online (Sandbox Code Playgroud)
这实际上是一个非常好的问题!
您跟踪某些活动事件的方法很聪明,但最终它实际上取决于您对“不活动”的定义。如果您对当前的定义感到满意,那么您可能可以跟踪更多内容,例如滚动或调整大小。
另一个“不活动”定义可以是“只要选项卡处于活动状态,用户就处于活动状态”。在现代浏览器中,可以通过页面可见性 API进行跟踪,即监听visibilitychange文档中的事件。在窗口对象上使用窗口焦点和模糊事件也应该作为一种解决方法。
现在,谈到实施本身,我将建议一种简化的方法。
const browserInactive$ = (graceTime: number): Observable<Event> => {
const activityIndicatorEvents = ['click', 'mousemove', 'mousedown', 'scroll', 'keypress'];
return merge(...activityIndicatorEvents.map(eventName => fromEvent(document, eventName))).pipe(debounceTime(graceTime));
};
Run Code Online (Sandbox Code Playgroud)
我自己还没有测试过,但这个想法很简单。它遵循您的原始实现,但我们使用debounceTime作为一种机制来等待 GraceTime“过期”,并使用一个函数作为配置行为的便捷方法。
编辑 1:有一个想法可以让您的 Angular 组件或服务轻松使用 Observable
export const BROWSER_INACTIVE$ = new InjectionToken<Observable<Event>>('Browser inactivity detector', {
factory: () => {
const activityIndicatorEvents = ['click', 'mousemove', 'mousedown', 'scroll', 'keypress'];
const graceTime = 1000 * 60 * 5; // 5 minutes
return merge(...activityIndicatorEvents.map(eventName => fromEvent(document, eventName))).pipe(debounceTime(graceTime), share());
}
});
Run Code Online (Sandbox Code Playgroud)
然后可以像这样使用:
@Component({...})
export class SomeComponent {
constructor(
@Inject(BROWSER_INACTIVE$) private readonly browserInactive$: Observable<Event>
) {}
}
Run Code Online (Sandbox Code Playgroud)
编辑2:基于OP反馈。在这种情况下,我们可以使用稍微不同的方法:
const browserInactive$ = (graceTime: number): Observable<any> => {
const activityIndicatorEvents$ = merge(
...['click', 'mousemove', 'mousedown', 'scroll', 'keypress'].map(
(eventName) => fromEvent(document, eventName)
)
);
return interval(graceTime).pipe(
takeUntil(activityIndicatorEvents$),
repeat()
);
};
Run Code Online (Sandbox Code Playgroud)
现在的想法是,我们有某种计时器(interval实际上是计时器),除非检测到ActivityIndicatorgracePeriod事件,否则它将发出每个计时器。问题是这将完成流,所以我们需要使用它来保持它的活动:)takeUntilrepeat
| 归档时间: |
|
| 查看次数: |
686 次 |
| 最近记录: |