diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 4df60dd60a..2ee51b5ada 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -68,7 +68,7 @@ EXEMPT_FILES = make_set( "web/src/compose.js", "web/src/compose_actions.ts", "web/src/compose_banner.ts", - "web/src/compose_call_ui.js", + "web/src/compose_call_ui.ts", "web/src/compose_closed_ui.ts", "web/src/compose_fade.ts", "web/src/compose_notifications.ts", diff --git a/web/src/compose_call_ui.js b/web/src/compose_call_ui.ts similarity index 62% rename from web/src/compose_call_ui.js rename to web/src/compose_call_ui.ts index e6b24f2949..befae7efce 100644 --- a/web/src/compose_call_ui.js +++ b/web/src/compose_call_ui.ts @@ -1,4 +1,5 @@ import $ from "jquery"; +import {z} from "zod"; import * as channel from "./channel"; import * as compose_call from "./compose_call"; @@ -10,38 +11,53 @@ import {current_user, realm} from "./state_data"; import * as ui_report from "./ui_report"; import * as util from "./util"; -export function update_audio_and_video_chat_button_display() { +const call_response_schema = z.object({ + msg: z.string(), + result: z.string(), + url: z.string(), +}); + +export function update_audio_and_video_chat_button_display(): void { update_audio_chat_button_display(); update_video_chat_button_display(); } -export function update_video_chat_button_display() { +export function update_video_chat_button_display(): void { const show_video_chat_button = compose_call.compute_show_video_chat_button(); $(".compose-control-buttons-container .video_link").toggle(show_video_chat_button); $(".message-edit-feature-group .video_link").toggle(show_video_chat_button); } -export function update_audio_chat_button_display() { +export function update_audio_chat_button_display(): void { const show_audio_chat_button = compose_call.compute_show_audio_chat_button(); $(".compose-control-buttons-container .audio_link").toggle(show_audio_chat_button); $(".message-edit-feature-group .audio_link").toggle(show_audio_chat_button); } -function insert_video_call_url(url, target_textarea) { +function insert_video_call_url( + url: string, + target_textarea: JQuery | undefined, +): void { const link_text = $t({defaultMessage: "Join video call."}); compose_ui.insert_syntax_and_focus(`[${link_text}](${url})`, target_textarea, "block", 1); } -function insert_audio_call_url(url, target_textarea) { +function insert_audio_call_url( + url: string, + target_textarea: JQuery | undefined, +): void { const link_text = $t({defaultMessage: "Join voice call."}); compose_ui.insert_syntax_and_focus(`[${link_text}](${url})`, target_textarea, "block", 1); } -export function generate_and_insert_audio_or_video_call_link($target_element, is_audio_call) { - let $target_textarea; - let edit_message_id; +export function generate_and_insert_audio_or_video_call_link( + $target_element: JQuery, + is_audio_call: boolean, +): void { + let $target_textarea: JQuery | undefined; + let edit_message_id: string | undefined; if ($target_element.parents(".message_edit_form").length === 1) { - edit_message_id = rows.id($target_element.parents(".message_row")); + edit_message_id = rows.id($target_element.parents(".message_row")).toString(); $target_textarea = $(`#edit_form_${CSS.escape(edit_message_id)} .message_edit_content`); } @@ -52,43 +68,46 @@ export function generate_and_insert_audio_or_video_call_link($target_element, is realm.realm_video_chat_provider === available_providers.zoom.id ) { compose_call.abort_video_callbacks(edit_message_id); - const key = edit_message_id || ""; + const key = edit_message_id ?? ""; const request = { is_video_call: !is_audio_call, }; - const make_zoom_call = () => { - compose_call.video_call_xhrs.set( - key, - channel.post({ - url: "/json/calls/zoom/create", - data: request, - success(res) { - compose_call.video_call_xhrs.delete(key); - if (is_audio_call) { - insert_audio_call_url(res.url, $target_textarea); - } else { - insert_video_call_url(res.url, $target_textarea); - } - }, - error(xhr, status) { - compose_call.video_call_xhrs.delete(key); - if ( - status === "error" && - xhr.responseJSON && - xhr.responseJSON.code === "INVALID_ZOOM_TOKEN" - ) { - current_user.has_zoom_token = false; - } - if (status !== "abort") { - ui_report.generic_embed_error( - $t_html({defaultMessage: "Failed to create video call."}), - ); - } - }, - }), - ); + const make_zoom_call: () => void = () => { + const xhr = channel.post({ + url: "/json/calls/zoom/create", + data: request, + success(res) { + const data = call_response_schema.parse(res); + compose_call.video_call_xhrs.delete(key); + if (is_audio_call) { + insert_audio_call_url(data.url, $target_textarea); + } else { + insert_video_call_url(data.url, $target_textarea); + } + }, + error(xhr, status) { + compose_call.video_call_xhrs.delete(key); + let parsed; + if ( + status === "error" && + (parsed = z.object({code: z.string()}).safeParse(xhr.responseJSON)) + .success && + parsed.data.code === "INVALID_ZOOM_TOKEN" + ) { + current_user.has_zoom_token = false; + } + if (status !== "abort") { + ui_report.generic_embed_error( + $t_html({defaultMessage: "Failed to create video call."}), + ); + } + }, + }); + if (xhr !== undefined) { + compose_call.video_call_xhrs.set(key, xhr); + } }; if (current_user.has_zoom_token) { @@ -110,13 +129,14 @@ export function generate_and_insert_audio_or_video_call_link($target_element, is return; } const meeting_name = get_recipient_label() + " meeting"; - channel.get({ + void channel.get({ url: "/json/calls/bigbluebutton/create", data: { meeting_name, }, success(response) { - insert_video_call_url(response.url, $target_textarea); + const data = call_response_schema.parse(response); + insert_video_call_url(data.url, $target_textarea); }, }); } else { diff --git a/web/tests/compose_video.test.js b/web/tests/compose_video.test.js index b7efcdab42..85927dee30 100644 --- a/web/tests/compose_video.test.js +++ b/web/tests/compose_video.test.js @@ -180,7 +180,11 @@ test("videos", ({override}) => { channel.post = (payload) => { assert.equal(payload.url, "/json/calls/zoom/create"); - payload.success({url: "example.zoom.com"}); + payload.success({ + result: "success", + msg: "", + url: "example.zoom.com", + }); return {abort() {}}; }; @@ -230,6 +234,8 @@ test("videos", ({override}) => { assert.equal(options.url, "/json/calls/bigbluebutton/create"); assert.equal(options.data.meeting_name, "a meeting"); options.success({ + result: "success", + msg: "", url: "/calls/bigbluebutton/join?meeting_id=%22zulip-1%22&password=%22AAAAAAAAAA%22&checksum=%2232702220bff2a22a44aee72e96cfdb4c4091752e%22", }); };