ts: Convert `activity.js` module to TypeScript.

This commit is contained in:
Lalit 2023-10-18 23:31:05 +05:30 committed by Tim Abbott
parent 0ca6d58e23
commit b86022ea52
4 changed files with 69 additions and 20 deletions

View File

@ -1,10 +1,34 @@
import $ from "jquery"; import $ from "jquery";
import assert from "minimalistic-assert";
import {z} from "zod";
import * as channel from "./channel"; import * as channel from "./channel";
import {page_params} from "./page_params"; import {page_params} from "./page_params";
import * as presence from "./presence"; import * as presence from "./presence";
import * as watchdog from "./watchdog"; import * as watchdog from "./watchdog";
const post_presence_response_schema = z.object({
msg: z.string(),
result: z.string(),
server_timestamp: z.number().optional(),
zephyr_mirror_active: z.boolean().optional(),
presences: z
.record(
z.string(),
z.object({
active_timestamp: z.number(),
idle_timestamp: z.number(),
}),
)
.optional(),
});
/* Keep in sync with views.py:update_active_status_backend() */
export enum ActivityState {
ACTIVE = "active",
IDLE = "idle",
}
/* /*
Helpers for detecting user activity and managing user idle states Helpers for detecting user activity and managing user idle states
*/ */
@ -12,11 +36,6 @@ import * as watchdog from "./watchdog";
/* Broadcast "idle" to server after 5 minutes of local inactivity */ /* Broadcast "idle" to server after 5 minutes of local inactivity */
const DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1000; const DEFAULT_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
/* Keep in sync with views.py:update_active_status_backend() */
export const ACTIVE = "active";
export const IDLE = "idle";
// When you open Zulip in a new browser window, client_is_active // When you open Zulip in a new browser window, client_is_active
// should be true. When a server-initiated reload happens, however, // should be true. When a server-initiated reload happens, however,
// it should be initialized to false. We handle this with a check for // it should be initialized to false. We handle this with a check for
@ -30,22 +49,22 @@ export let client_is_active = document.hasFocus && document.hasFocus();
// server-initiated reload as user activity. // server-initiated reload as user activity.
export let new_user_input = true; export let new_user_input = true;
export function set_new_user_input(value) { export function set_new_user_input(value: boolean): void {
new_user_input = value; new_user_input = value;
} }
export function clear_for_testing() { export function clear_for_testing(): void {
client_is_active = false; client_is_active = false;
} }
export function mark_client_idle() { export function mark_client_idle(): void {
// When we become idle, we don't immediately send anything to the // When we become idle, we don't immediately send anything to the
// server; instead, we wait for our next periodic update, since // server; instead, we wait for our next periodic update, since
// this data is fundamentally not timely. // this data is fundamentally not timely.
client_is_active = false; client_is_active = false;
} }
export function compute_active_status() { export function compute_active_status(): ActivityState {
// The overall algorithm intent for the `status` field is to send // The overall algorithm intent for the `status` field is to send
// `ACTIVE` (aka green circle) if we know the user is at their // `ACTIVE` (aka green circle) if we know the user is at their
// computer, and IDLE (aka orange circle) if the user might not // computer, and IDLE (aka orange circle) if the user might not
@ -62,18 +81,18 @@ export function compute_active_status() {
window.electron_bridge.get_idle_on_system !== undefined window.electron_bridge.get_idle_on_system !== undefined
) { ) {
if (window.electron_bridge.get_idle_on_system()) { if (window.electron_bridge.get_idle_on_system()) {
return IDLE; return ActivityState.IDLE;
} }
return ACTIVE; return ActivityState.ACTIVE;
} }
if (client_is_active) { if (client_is_active) {
return ACTIVE; return ActivityState.ACTIVE;
} }
return IDLE; return ActivityState.IDLE;
} }
export function send_presence_to_server(redraw) { export function send_presence_to_server(redraw?: () => void): void {
// Zulip has 2 data feeds coming from the server to the client: // Zulip has 2 data feeds coming from the server to the client:
// The server_events data, and this presence feed. Data from // The server_events data, and this presence feed. Data from
// server_events is nicely serialized, but if we've been offline // server_events is nicely serialized, but if we've been offline
@ -96,7 +115,7 @@ export function send_presence_to_server(redraw) {
watchdog.check_for_unsuspend(); watchdog.check_for_unsuspend();
channel.post({ void channel.post({
url: "/json/users/me/presence", url: "/json/users/me/presence",
data: { data: {
status: compute_active_status(), status: compute_active_status(),
@ -104,7 +123,9 @@ export function send_presence_to_server(redraw) {
new_user_input, new_user_input,
slim_presence: true, slim_presence: true,
}, },
success(data) { success(response) {
const data = post_presence_response_schema.parse(response);
// Update Zephyr mirror activity warning // Update Zephyr mirror activity warning
if (data.zephyr_mirror_active === false) { if (data.zephyr_mirror_active === false) {
$("#zephyr-mirror-error").addClass("show"); $("#zephyr-mirror-error").addClass("show");
@ -115,6 +136,14 @@ export function send_presence_to_server(redraw) {
new_user_input = false; new_user_input = false;
if (redraw) { if (redraw) {
assert(
data.presences !== undefined,
"Presences should be present if not a ping only presence request",
);
assert(
data.server_timestamp !== undefined,
"Server timestamp should be present if not a ping only presence request",
);
presence.set_info(data.presences, data.server_timestamp); presence.set_info(data.presences, data.server_timestamp);
redraw(); redraw();
} }
@ -122,7 +151,7 @@ export function send_presence_to_server(redraw) {
}); });
} }
export function mark_client_active() { export function mark_client_active(): void {
// exported for testing // exported for testing
if (!client_is_active) { if (!client_is_active) {
client_is_active = true; client_is_active = true;
@ -130,7 +159,7 @@ export function mark_client_active() {
} }
} }
export function initialize() { export function initialize(): void {
$("html").on("mousemove", () => { $("html").on("mousemove", () => {
new_user_input = true; new_user_input = true;
}); });

14
web/src/global.d.ts vendored
View File

@ -14,6 +14,14 @@ type JQueryCaretRange = {
text: string; text: string;
}; };
type JQueryIdleOptions = Partial<{
idle: number;
events: string;
onIdle: () => void;
onActive: () => void;
keepTracking: boolean;
}>;
declare namespace JQueryValidation { declare namespace JQueryValidation {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface ValidationOptions { interface ValidationOptions {
@ -37,6 +45,12 @@ interface JQuery {
range(text: string): this; range(text: string): this;
selectAll(): this; selectAll(): this;
deselectAll(): this; deselectAll(): this;
// Types for jquery-idle plugin
idle(opts: JQueryIdleOptions): {
cancel: () => void;
reset: () => void;
};
} }
declare const ZULIP_VERSION: string; declare const ZULIP_VERSION: string;

View File

@ -167,7 +167,7 @@ export function update_info_from_event(
} }
export function set_info( export function set_info(
presences: Map<number, Omit<RawPresence, "server_timestamp">>, presences: Record<number, Omit<RawPresence, "server_timestamp">>,
server_timestamp: number, server_timestamp: number,
): void { ): void {
/* /*
@ -278,7 +278,7 @@ export function last_active_date(user_id: number): Date | undefined {
} }
export function initialize(params: { export function initialize(params: {
presences: Map<number, Omit<RawPresence, "server_timestamp">>; presences: Record<number, Omit<RawPresence, "server_timestamp">>;
server_timestamp: number; server_timestamp: number;
}): void { }): void {
set_info(params.presences, params.server_timestamp); set_info(params.presences, params.server_timestamp);

View File

@ -687,6 +687,9 @@ test("initialize", ({override, mock_template}) => {
payload.success({ payload.success({
zephyr_mirror_active: true, zephyr_mirror_active: true,
presences: {}, presences: {},
msg: "",
result: "success",
server_timestamp: 0,
}); });
$(window).trigger("focus"); $(window).trigger("focus");
clear(); clear();
@ -708,6 +711,9 @@ test("initialize", ({override, mock_template}) => {
payload.success({ payload.success({
zephyr_mirror_active: false, zephyr_mirror_active: false,
presences: {}, presences: {},
msg: "",
result: "success",
server_timestamp: 0,
}); });
assert.ok($("#zephyr-mirror-error").hasClass("show")); assert.ok($("#zephyr-mirror-error").hasClass("show"));