Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use EDU activity to determine user idleness #1152

Merged
merged 5 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/1152.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Determine user activeness based off presence, typing and read receipts when kicking idle users.
51 changes: 50 additions & 1 deletion src/bridge/IrcBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
PrometheusMetrics,
MembershipCache,
AgeCounters,
EphemeralEvent,
} from "matrix-appservice-bridge";
import { IrcAction } from "../models/IrcAction";
import { DataStore } from "../datastore/DataStore";
Expand All @@ -46,6 +47,12 @@ const DELAY_TIME_MS = 10 * 1000;
const DELAY_FETCH_ROOM_LIST_MS = 3 * 1000;
const DEAD_TIME_MS = 5 * 60 * 1000;
const TXN_SIZE_DEFAULT = 10000000 // 10MB

/**
* How old can a receipt be before we treat
* it as stale.
*/
const RECEIPT_CUTOFF_TIME_MS = 60000;
export const METRIC_ACTIVE_USERS = "active_users";

export class IrcBridge {
Expand Down Expand Up @@ -118,6 +125,10 @@ export class IrcBridge {
};
}
this.membershipCache = new MembershipCache();
if (!this.registration.pushEphemeral) {
log.info("Sending ephemeral events to the bridge is currently disabled in the registration file," +
" so user activity will not be captured");
}
this.bridge = new Bridge({
registration: this.registration,
homeserverUrl: this.config.homeserver.url,
Expand All @@ -129,7 +140,7 @@ export class IrcBridge {
onAliasQueried: this.onAliasQueried ?
this.onAliasQueried.bind(this) : undefined,
onLog: this.onLog.bind(this),

onEphemeralEvent: this.activityTracker ? this.onEphemeralEvent.bind(this) : undefined,
thirdPartyLookup: {
protocols: ["irc"],
getProtocol: this.getThirdPartyProtocol.bind(this),
Expand Down Expand Up @@ -803,6 +814,44 @@ export class IrcBridge {
request.outcomeFrom(this._onEvent(request));
}

private onEphemeralEvent(request: Request<EphemeralEvent>) {
// If we see one of these events over federation, bump the
// last active time for those users.
const event = request.getData();
let userIds: string[]|undefined = undefined;
if (!this.activityTracker) {
return;
}
if (event.type === "m.presence" && event.content.presence === "online") {
userIds = [event.sender];
}
else if (event.type === "m.receipt") {
userIds = [];
const currentTime = Date.now();
// The homeserver will send us a map of all userIDs => ts for each event.
// We are only interested in recent receipts though.
for (const eventData of Object.values(event.content).map((v) => v["m.read"])) {
for (const [userId, { ts }] of Object.entries(eventData)) {
if (currentTime - ts <= RECEIPT_CUTOFF_TIME_MS) {
userIds.push(userId);
}
}
}
}
else if (event.type === "m.typing") {
userIds = event.content.user_ids;
}

if (userIds) {
for (const userId of userIds) {
this.activityTracker.bumpLastActiveTime(userId);
this.dataStore.updateLastSeenTimeForUser(userId).catch((ex) => {
log.warn(`Failed to bump last active time for ${userId} in database`, ex);
});
}
}
}

private async _onEvent (baseRequest: BridgeRequestEvent): Promise<BridgeRequestErr|undefined> {
const event = baseRequest.getData();
let updatePromise: Promise<void>|null = null;
Expand Down
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export function generateRegistration(reg: AppServiceRegistration, config: Bridge
// connect, for example on startup.
reg.setRateLimited(false);

// Needed to detect activity of users on the bridge.
reg.pushEphemeral = true;

// Set protocols to IRC, so that the bridge appears in the list of
// thirdparty protocols
reg.setProtocols(["irc"]);
Expand Down