mirror of https://github.com/zulip/zulip.git
reload: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
0be5cc232c
commit
97ffccb45f
|
@ -97,7 +97,7 @@ reload itself:
|
||||||
start looking for a good time to reload, based on when the user is
|
start looking for a good time to reload, based on when the user is
|
||||||
idle (ideally, we'd reload when they're not looking and restore
|
idle (ideally, we'd reload when they're not looking and restore
|
||||||
state so that the user never knew it happened!). The logic for
|
state so that the user never knew it happened!). The logic for
|
||||||
doing this is in `web/src/reload.js`; but regardless we'll reload
|
doing this is in `web/src/reload.ts`; but regardless we'll reload
|
||||||
within 30 minutes unconditionally.
|
within 30 minutes unconditionally.
|
||||||
|
|
||||||
An important detail in server-initiated reloads is that we
|
An important detail in server-initiated reloads is that we
|
||||||
|
|
|
@ -189,7 +189,7 @@ EXEMPT_FILES = make_set(
|
||||||
"web/src/realm_playground.ts",
|
"web/src/realm_playground.ts",
|
||||||
"web/src/realm_user_settings_defaults.ts",
|
"web/src/realm_user_settings_defaults.ts",
|
||||||
"web/src/recent_view_ui.ts",
|
"web/src/recent_view_ui.ts",
|
||||||
"web/src/reload.js",
|
"web/src/reload.ts",
|
||||||
"web/src/reload_setup.js",
|
"web/src/reload_setup.js",
|
||||||
"web/src/reminder.js",
|
"web/src/reminder.js",
|
||||||
"web/src/resize.ts",
|
"web/src/resize.ts",
|
||||||
|
|
|
@ -71,8 +71,6 @@ async function test_reload_hash(page: Page): Promise<void> {
|
||||||
const initial_hash = await page.evaluate(() => window.location.hash);
|
const initial_hash = await page.evaluate(() => window.location.hash);
|
||||||
|
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
// We haven't converted reload.js to TypeScript yet.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
||||||
zulip_test.initiate_reload({immediate: true});
|
zulip_test.initiate_reload({immediate: true});
|
||||||
});
|
});
|
||||||
await page.waitForNavigation();
|
await page.waitForNavigation();
|
||||||
|
|
|
@ -52,7 +52,7 @@ export let client_is_active = document.hasFocus();
|
||||||
|
|
||||||
// new_user_input is a more strict version of client_is_active used
|
// new_user_input is a more strict version of client_is_active used
|
||||||
// primarily for analytics. We initialize this to true, to count new
|
// primarily for analytics. We initialize this to true, to count new
|
||||||
// page loads, but set it to false in the onload function in reload.js
|
// page loads, but set it to false in the onload function in reload.ts
|
||||||
// if this was a server-initiated-reload to avoid counting a
|
// if this was a server-initiated-reload to avoid counting a
|
||||||
// server-initiated reload as user activity.
|
// server-initiated reload as user activity.
|
||||||
export let new_user_input = true;
|
export let new_user_input = true;
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
import assert from "minimalistic-assert";
|
||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
import * as blueslip from "./blueslip";
|
import * as blueslip from "./blueslip";
|
||||||
import * as compose_state from "./compose_state";
|
import * as compose_state from "./compose_state";
|
||||||
import {csrf_token} from "./csrf";
|
import {csrf_token} from "./csrf";
|
||||||
import * as drafts from "./drafts";
|
import * as drafts from "./drafts";
|
||||||
import * as hash_util from "./hash_util";
|
import * as hash_util from "./hash_util";
|
||||||
|
import type {LocalStorage} from "./localstorage";
|
||||||
import {localstorage} from "./localstorage";
|
import {localstorage} from "./localstorage";
|
||||||
import * as message_lists from "./message_lists";
|
import * as message_lists from "./message_lists";
|
||||||
import {page_params} from "./page_params";
|
import {page_params} from "./page_params";
|
||||||
|
@ -14,19 +17,21 @@ import * as util from "./util";
|
||||||
|
|
||||||
// Read https://zulip.readthedocs.io/en/latest/subsystems/hashchange-system.html
|
// Read https://zulip.readthedocs.io/en/latest/subsystems/hashchange-system.html
|
||||||
|
|
||||||
const reload_hooks = [];
|
const token_metadata_schema = z.object({url: z.string(), timestamp: z.number()});
|
||||||
|
|
||||||
export function add_reload_hook(hook) {
|
const reload_hooks: (() => void)[] = [];
|
||||||
|
|
||||||
|
export function add_reload_hook(hook: () => void): void {
|
||||||
reload_hooks.push(hook);
|
reload_hooks.push(hook);
|
||||||
}
|
}
|
||||||
|
|
||||||
function call_reload_hooks() {
|
function call_reload_hooks(): void {
|
||||||
for (const hook of reload_hooks) {
|
for (const hook of reload_hooks) {
|
||||||
hook();
|
hook();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function preserve_state(send_after_reload, save_compose) {
|
function preserve_state(send_after_reload: boolean, save_compose: boolean): void {
|
||||||
if (!localstorage.supported()) {
|
if (!localstorage.supported()) {
|
||||||
// If local storage is not supported by the browser, we can't
|
// If local storage is not supported by the browser, we can't
|
||||||
// save the browser's position across reloads (since there's
|
// save the browser's position across reloads (since there's
|
||||||
|
@ -46,13 +51,16 @@ function preserve_state(send_after_reload, save_compose) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = "#reload:send_after_reload=" + Number(send_after_reload);
|
let url = "#reload:send_after_reload=" + Number(send_after_reload);
|
||||||
|
assert(csrf_token !== undefined);
|
||||||
url += "+csrf_token=" + encodeURIComponent(csrf_token);
|
url += "+csrf_token=" + encodeURIComponent(csrf_token);
|
||||||
|
|
||||||
if (save_compose) {
|
if (save_compose) {
|
||||||
const msg_type = compose_state.get_message_type();
|
const msg_type = compose_state.get_message_type();
|
||||||
if (msg_type === "stream") {
|
if (msg_type === "stream") {
|
||||||
|
const stream_id = compose_state.stream_id();
|
||||||
|
assert(stream_id !== undefined);
|
||||||
url += "+msg_type=stream";
|
url += "+msg_type=stream";
|
||||||
url += "+stream_id=" + encodeURIComponent(compose_state.stream_id());
|
url += "+stream_id=" + encodeURIComponent(stream_id);
|
||||||
url += "+topic=" + encodeURIComponent(compose_state.topic());
|
url += "+topic=" + encodeURIComponent(compose_state.topic());
|
||||||
} else if (msg_type === "private") {
|
} else if (msg_type === "private") {
|
||||||
url += "+msg_type=private";
|
url += "+msg_type=private";
|
||||||
|
@ -94,7 +102,7 @@ function preserve_state(send_after_reload, save_compose) {
|
||||||
// TODO: Remove the now-unnecessary URL-encoding logic above and
|
// TODO: Remove the now-unnecessary URL-encoding logic above and
|
||||||
// just pass the actual data structures through local storage.
|
// just pass the actual data structures through local storage.
|
||||||
const token = util.random_int(0, 1024 * 1024 * 1024 * 1024);
|
const token = util.random_int(0, 1024 * 1024 * 1024 * 1024);
|
||||||
const metadata = {
|
const metadata: z.infer<typeof token_metadata_schema> = {
|
||||||
url,
|
url,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
};
|
};
|
||||||
|
@ -102,33 +110,39 @@ function preserve_state(send_after_reload, save_compose) {
|
||||||
window.location.replace("#reload:" + token);
|
window.location.replace("#reload:" + token);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function is_stale_refresh_token(token_metadata, now) {
|
export function is_stale_refresh_token(token_metadata: unknown, now: number): boolean {
|
||||||
|
const parsed = token_metadata_schema.safeParse(token_metadata);
|
||||||
// TODO/compatibility: the metadata was changed from a string
|
// TODO/compatibility: the metadata was changed from a string
|
||||||
// to a map containing the string and a timestamp. For now we'll
|
// to a map containing the string and a timestamp. For now we'll
|
||||||
// delete all tokens that only contain the url. Remove this
|
// delete all tokens that only contain the url. Remove this
|
||||||
// early return once you can no longer directly upgrade from
|
// early return once you can no longer directly upgrade from
|
||||||
// Zulip 5.x to the current version.
|
// Zulip 5.x to the current version.
|
||||||
if (!token_metadata.timestamp) {
|
if (!parsed.success) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
const {timestamp} = parsed.data;
|
||||||
|
|
||||||
// The time between reload token generation and use should usually be
|
// The time between reload token generation and use should usually be
|
||||||
// fewer than 30 seconds, but we keep tokens around for a week just in case
|
// fewer than 30 seconds, but we keep tokens around for a week just in case
|
||||||
// (e.g. a tab could fail to load and be refreshed a while later).
|
// (e.g. a tab could fail to load and be refreshed a while later).
|
||||||
const milliseconds_in_a_day = 1000 * 60 * 60 * 24;
|
const milliseconds_in_a_day = 1000 * 60 * 60 * 24;
|
||||||
const timedelta = now - token_metadata.timestamp;
|
const timedelta = now - timestamp;
|
||||||
const days_since_token_creation = timedelta / milliseconds_in_a_day;
|
const days_since_token_creation = timedelta / milliseconds_in_a_day;
|
||||||
return days_since_token_creation > 7;
|
return days_since_token_creation > 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
function delete_stale_tokens(ls) {
|
function delete_stale_tokens(ls: LocalStorage): void {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
ls.removeDataRegexWithCondition("reload:\\d+", (metadata) =>
|
ls.removeDataRegexWithCondition("reload:\\d+", (metadata) =>
|
||||||
is_stale_refresh_token(metadata, now),
|
is_stale_refresh_token(metadata, now),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function do_reload_app(send_after_reload, save_compose, message_html) {
|
function do_reload_app(
|
||||||
|
send_after_reload: boolean,
|
||||||
|
save_compose: boolean,
|
||||||
|
message_html: string,
|
||||||
|
): void {
|
||||||
if (reload_state.is_in_progress()) {
|
if (reload_state.is_in_progress()) {
|
||||||
blueslip.log("do_reload_app: Doing nothing since reload_in_progress");
|
blueslip.log("do_reload_app: Doing nothing since reload_in_progress");
|
||||||
return;
|
return;
|
||||||
|
@ -164,7 +178,7 @@ function do_reload_app(send_after_reload, save_compose, message_html) {
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
function retry_reload() {
|
function retry_reload(): void {
|
||||||
blueslip.log("Retrying page reload due to 30s timer");
|
blueslip.log("Retrying page reload due to 30s timer");
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
@ -184,7 +198,7 @@ export function initiate({
|
||||||
save_compose = true,
|
save_compose = true,
|
||||||
send_after_reload = false,
|
send_after_reload = false,
|
||||||
message_html = "Reloading ...",
|
message_html = "Reloading ...",
|
||||||
}) {
|
}): void {
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
do_reload_app(send_after_reload, save_compose, message_html);
|
do_reload_app(send_after_reload, save_compose, message_html);
|
||||||
}
|
}
|
||||||
|
@ -215,14 +229,13 @@ export function initiate({
|
||||||
// makes it simple to reason about: We know that reloads will be
|
// makes it simple to reason about: We know that reloads will be
|
||||||
// spread over at least 5 minutes in all cases.
|
// spread over at least 5 minutes in all cases.
|
||||||
|
|
||||||
let idle_control;
|
let idle_control: ReturnType<JQuery["idle"]>;
|
||||||
const random_variance = util.random_int(0, 1000 * 60 * 5);
|
const random_variance = util.random_int(0, 1000 * 60 * 5);
|
||||||
const unconditional_timeout = 1000 * 60 * 30 + random_variance;
|
const unconditional_timeout = 1000 * 60 * 30 + random_variance;
|
||||||
const composing_idle_timeout = 1000 * 60 * 7 + random_variance;
|
const composing_idle_timeout = 1000 * 60 * 7 + random_variance;
|
||||||
const basic_idle_timeout = 1000 * 60 * 1 + random_variance;
|
const basic_idle_timeout = 1000 * 60 * 1 + random_variance;
|
||||||
let compose_started_handler;
|
|
||||||
|
|
||||||
function reload_from_idle() {
|
function reload_from_idle(): void {
|
||||||
do_reload_app(false, save_compose, message_html);
|
do_reload_app(false, save_compose, message_html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,22 +245,22 @@ export function initiate({
|
||||||
// particularly disruptive.
|
// particularly disruptive.
|
||||||
setTimeout(reload_from_idle, unconditional_timeout);
|
setTimeout(reload_from_idle, unconditional_timeout);
|
||||||
|
|
||||||
const compose_done_handler = function () {
|
function compose_done_handler(): void {
|
||||||
// If the user sends their message or otherwise closes
|
// If the user sends their message or otherwise closes
|
||||||
// compose, we return them to the not-composing timeouts.
|
// compose, we return them to the not-composing timeouts.
|
||||||
idle_control.cancel();
|
idle_control.cancel();
|
||||||
idle_control = $(document).idle({idle: basic_idle_timeout, onIdle: reload_from_idle});
|
idle_control = $(document).idle({idle: basic_idle_timeout, onIdle: reload_from_idle});
|
||||||
$(document).off("compose_canceled.zulip compose_finished.zulip", compose_done_handler);
|
$(document).off("compose_canceled.zulip compose_finished.zulip", compose_done_handler);
|
||||||
$(document).on("compose_started.zulip", compose_started_handler);
|
$(document).on("compose_started.zulip", compose_started_handler);
|
||||||
};
|
}
|
||||||
compose_started_handler = function () {
|
function compose_started_handler(): void {
|
||||||
// If the user stops being idle and starts composing a
|
// If the user stops being idle and starts composing a
|
||||||
// message, switch to the compose-open timeouts.
|
// message, switch to the compose-open timeouts.
|
||||||
idle_control.cancel();
|
idle_control.cancel();
|
||||||
idle_control = $(document).idle({idle: composing_idle_timeout, onIdle: reload_from_idle});
|
idle_control = $(document).idle({idle: composing_idle_timeout, onIdle: reload_from_idle});
|
||||||
$(document).off("compose_started.zulip", compose_started_handler);
|
$(document).off("compose_started.zulip", compose_started_handler);
|
||||||
$(document).on("compose_canceled.zulip compose_finished.zulip", compose_done_handler);
|
$(document).on("compose_canceled.zulip compose_finished.zulip", compose_done_handler);
|
||||||
};
|
}
|
||||||
|
|
||||||
if (compose_state.composing()) {
|
if (compose_state.composing()) {
|
||||||
idle_control = $(document).idle({idle: composing_idle_timeout, onIdle: reload_from_idle});
|
idle_control = $(document).idle({idle: composing_idle_timeout, onIdle: reload_from_idle});
|
|
@ -2,7 +2,7 @@
|
||||||
We want his module to load pretty early in the process
|
We want his module to load pretty early in the process
|
||||||
of starting the app, so that people.js can load early.
|
of starting the app, so that people.js can load early.
|
||||||
All the heavy lifting for reload logic happens in
|
All the heavy lifting for reload logic happens in
|
||||||
reload.js, which has lots of UI dependencies. If we
|
reload.ts, which has lots of UI dependencies. If we
|
||||||
didn't split out this module, our whole dependency tree
|
didn't split out this module, our whole dependency tree
|
||||||
would be kind of upside down.
|
would be kind of upside down.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -10,7 +10,6 @@ export {get_by_user_id as get_person_by_user_id, get_user_id_from_name} from "./
|
||||||
export {last_visible as last_visible_row, id as row_id} from "./rows";
|
export {last_visible as last_visible_row, id as row_id} from "./rows";
|
||||||
export {cancel as cancel_compose} from "./compose_actions";
|
export {cancel as cancel_compose} from "./compose_actions";
|
||||||
export {page_params, page_params_parse_time} from "./base_page_params";
|
export {page_params, page_params_parse_time} from "./base_page_params";
|
||||||
// @ts-expect-error We haven't converted reload.js yet
|
|
||||||
export {initiate as initiate_reload} from "./reload";
|
export {initiate as initiate_reload} from "./reload";
|
||||||
export {page_load_time} from "./setup";
|
export {page_load_time} from "./setup";
|
||||||
export {current_user, realm} from "./state_data";
|
export {current_user, realm} from "./state_data";
|
||||||
|
|
|
@ -5,7 +5,7 @@ const {strict: assert} = require("assert");
|
||||||
const {zrequire} = require("./lib/namespace");
|
const {zrequire} = require("./lib/namespace");
|
||||||
const {run_test, noop} = require("./lib/test");
|
const {run_test, noop} = require("./lib/test");
|
||||||
|
|
||||||
// override file-level function call in reload.js
|
// override file-level function call in reload.ts
|
||||||
window.addEventListener = noop;
|
window.addEventListener = noop;
|
||||||
const reload = zrequire("reload");
|
const reload = zrequire("reload");
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue