From f8eaba06ccf1c970f6c71ff5fb77a538749be5a6 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 17 Oct 2023 12:20:52 -0600 Subject: [PATCH 1/3] Switched away from %% based http requests in favor of templated strings --- src/components/ACLModal/ACLModal.tsx | 8 +- src/components/BlockPlayer/BlockPlayer.ts | 6 +- .../ChallengeModal/ChallengeModal.tsx | 4 +- .../GameAcceptModal/GameAcceptModal.tsx | 2 +- .../IncidentReportTracker.tsx | 14 +-- src/components/ModerateUser/ModerateUser.tsx | 2 +- .../Notifications/Notifications.tsx | 4 +- src/components/SeekGraph/SeekGraph.tsx | 2 +- src/lib/rengo_balancer.ts | 4 +- src/lib/rengo_utils.ts | 10 +- src/lib/report_manager.tsx | 6 +- src/lib/requests.ts | 91 +++++-------------- src/models/games.d.ts | 2 +- .../AnnouncementCenter/AnnouncementCenter.tsx | 2 +- .../ChallengeLinkLanding.tsx | 4 +- .../ForceUsernameChange.tsx | 2 +- src/views/Game/Game.tsx | 6 +- src/views/Game/GameDock.tsx | 4 +- src/views/Game/PlayControls.test.tsx | 2 +- src/views/Group/Group.tsx | 26 +++--- src/views/Ladder/Ladder.tsx | 12 +-- src/views/LadderList/LadderList.tsx | 2 +- src/views/LibraryPlayer/LibraryPlayer.tsx | 14 ++- src/views/Overview/ChallengesList.tsx | 4 +- src/views/Overview/InviteList.tsx | 2 +- src/views/Play/Play.tsx | 2 +- src/views/Puzzle/Puzzle.tsx | 6 +- src/views/Puzzle/PuzzleEditing.ts | 6 +- src/views/ReportsCenter/ReportedGame.tsx | 4 +- src/views/ReportsCenter/ViewReport.tsx | 4 +- src/views/Settings/AccountSettings.tsx | 6 +- src/views/Tournament/Tournament.tsx | 28 +++--- src/views/User/AvatarCard.tsx | 4 +- src/views/User/ModTools.tsx | 2 +- src/views/User/User.tsx | 8 +- 35 files changed, 127 insertions(+), 178 deletions(-) diff --git a/src/components/ACLModal/ACLModal.tsx b/src/components/ACLModal/ACLModal.tsx index e3bef81812..2c5578a5be 100644 --- a/src/components/ACLModal/ACLModal.tsx +++ b/src/components/ACLModal/ACLModal.tsx @@ -47,13 +47,13 @@ export class ACLModal extends Modal { if ("game_id" in props) { this.url = `games/${props.game_id}/acl`; - this.del_url = `games/acl/%%`; + this.del_url = `games/acl/`; } else if ("review_id" in props) { this.url = `reviews/${props.review_id}/acl`; - this.del_url = `reviews/acl/%%`; + this.del_url = `reviews/acl/`; } else if ("puzzle_collection_id" in props) { this.url = `puzzles/collections/${props.puzzle_collection_id}/acl`; - this.del_url = `puzzles/collections/acl/%%`; + this.del_url = `puzzles/collections/acl/`; } else { throw new Error(`ACLModal created with invalid parameters`); } @@ -79,7 +79,7 @@ export class ACLModal extends Modal { } this.setState({ acl: new_acl }); - del(this.del_url, obj.id) + del(this.del_url + obj.id) .then(this.refresh) .catch((e) => { this.refresh(); diff --git a/src/components/BlockPlayer/BlockPlayer.ts b/src/components/BlockPlayer/BlockPlayer.ts index 67dfdac0a3..21fcc376bb 100644 --- a/src/components/BlockPlayer/BlockPlayer.ts +++ b/src/components/BlockPlayer/BlockPlayer.ts @@ -50,7 +50,7 @@ export function setIgnore(player_id: number, tf: boolean) { block_states[player_id] = new BlockState(player_id); } block_states[player_id].block_chat = tf; - put("players/%%/block", player_id, { block_chat: tf ? 1 : 0 }) + put(`players/${player_id}/block`, { block_chat: tf ? 1 : 0 }) .then(() => { ITC.send("update-blocks", true); }) @@ -64,7 +64,7 @@ export function setGameBlock(player_id: number, tf: boolean) { block_states[player_id] = new BlockState(player_id); } block_states[player_id].block_games = tf; - put("players/%%/block", player_id, { block_games: tf ? 1 : 0 }) + put(`players/${player_id}/block`, { block_games: tf ? 1 : 0 }) .then(() => { ITC.send("update-blocks", true); }) @@ -78,7 +78,7 @@ export function setAnnouncementBlock(player_id: number, tf: boolean) { block_states[player_id] = new BlockState(player_id); } block_states[player_id].block_announcements = tf; - put("players/%%/block", player_id, { block_announcements: tf ? 1 : 0 }) + put(`players/${player_id}/block`, { block_announcements: tf ? 1 : 0 }) .then(() => { ITC.send("update-blocks", true); }) diff --git a/src/components/ChallengeModal/ChallengeModal.tsx b/src/components/ChallengeModal/ChallengeModal.tsx index 077374e934..6e9191fc2b 100644 --- a/src/components/ChallengeModal/ChallengeModal.tsx +++ b/src/components/ChallengeModal/ChallengeModal.tsx @@ -625,7 +625,7 @@ export class ChallengeModal extends Modal this.saveSettings(); this.close(); - post(player_id ? "players/%%/challenge" : "challenges", player_id, challenge) + post(player_id ? `players/${player_id}/challenge` : "challenges", challenge) .then((res) => { // console.log("Challenge response: ", res); @@ -669,7 +669,7 @@ export class ChallengeModal extends Modal off(); if (accept) { // cancel challenge - void del("me/challenges/%%", challenge_id); + void del(`me/challenges/${challenge_id}`); } }) .catch(() => { diff --git a/src/components/GameAcceptModal/GameAcceptModal.tsx b/src/components/GameAcceptModal/GameAcceptModal.tsx index 047bcc3f83..768efe98d4 100644 --- a/src/components/GameAcceptModal/GameAcceptModal.tsx +++ b/src/components/GameAcceptModal/GameAcceptModal.tsx @@ -48,7 +48,7 @@ export class GameAcceptModal extends Modal { alert.close(); this.close(); diff --git a/src/components/IncidentReportTracker/IncidentReportTracker.tsx b/src/components/IncidentReportTracker/IncidentReportTracker.tsx index 119203281a..8ccd2e303e 100644 --- a/src/components/IncidentReportTracker/IncidentReportTracker.tsx +++ b/src/components/IncidentReportTracker/IncidentReportTracker.tsx @@ -52,12 +52,12 @@ export function IncidentReportTracker(): JSX.Element { const onReport = (report: Report) => { if (report.state !== "resolved") { report.unclaim = () => { - post("moderation/incident/%%", report.id, { id: report.id, action: "unclaim" }) + post(`moderation/incident/${report.id}`, { id: report.id, action: "unclaim" }) .then(ignore) .catch(errorAlerter); }; report.good_report = () => { - post("moderation/incident/%%", report.id, { + post(`moderation/incident/${report.id}`, { id: report.id, action: "resolve", was_helpful: true, @@ -66,7 +66,7 @@ export function IncidentReportTracker(): JSX.Element { .catch(errorAlerter); }; report.bad_report = () => { - post("moderation/incident/%%", report.id, { + post(`moderation/incident/${report.id}`, { id: report.id, action: "resolve", was_helpful: false, @@ -75,7 +75,7 @@ export function IncidentReportTracker(): JSX.Element { .catch(errorAlerter); }; report.steal = () => { - post("moderation/incident/%%", report.id, { id: report.id, action: "steal" }) + post(`moderation/incident/${report.id}`, { id: report.id, action: "steal" }) .then((res) => { if (res.vanished) { void alert.fire("Report was removed"); @@ -84,7 +84,7 @@ export function IncidentReportTracker(): JSX.Element { .catch(errorAlerter); }; report.claim = () => { - post("moderation/incident/%%", report.id, { id: report.id, action: "claim" }) + post(`moderation/incident/${report.id}`, { id: report.id, action: "claim" }) .then((res) => { if (res.vanished) { void alert.fire("Report was removed"); @@ -96,7 +96,7 @@ export function IncidentReportTracker(): JSX.Element { .catch(errorAlerter); }; report.cancel = () => { - post("moderation/incident/%%", report.id, { id: report.id, action: "cancel" }) + post(`moderation/incident/${report.id}`, { id: report.id, action: "cancel" }) .then(ignore) .catch(errorAlerter); }; @@ -110,7 +110,7 @@ export function IncidentReportTracker(): JSX.Element { }) .then(({ value: txt, isConfirmed }) => { if (isConfirmed) { - post("moderation/incident/%%", report.id, { + post(`moderation/incident/${report.id}`, { id: report.id, action: "note", note: txt, diff --git a/src/components/ModerateUser/ModerateUser.tsx b/src/components/ModerateUser/ModerateUser.tsx index 9321c78070..d1dc8f4bad 100644 --- a/src/components/ModerateUser/ModerateUser.tsx +++ b/src/components/ModerateUser/ModerateUser.tsx @@ -44,7 +44,7 @@ export class ModerateUser extends Modal { } componentDidMount() { - get("players/%%/full", this.props.playerId) + get(`players/${this.props.playerId}/full`) .then((result) => { console.log(result); this.setState( diff --git a/src/components/Notifications/Notifications.tsx b/src/components/Notifications/Notifications.tsx index 43b7355d07..7106e80be6 100644 --- a/src/components/Notifications/Notifications.tsx +++ b/src/components/Notifications/Notifications.tsx @@ -200,7 +200,7 @@ class NotificationEntry extends React.Component<{ notification }, any> { { this.setState({ message: _("Declining") }); - del("me/challenges/%%", notification.challenge_id) + del(`me/challenges/${notification.challenge_id}`) .then(this.del) .catch(this.onError); }} @@ -208,7 +208,7 @@ class NotificationEntry extends React.Component<{ notification }, any> { { this.setState({ message: _("Accepting") }); - post("me/challenges/%%/accept", notification.challenge_id, {}) + post(`me/challenges/${notification.challenge_id}/accept`, {}) .then(() => { this.del(); if ( diff --git a/src/components/SeekGraph/SeekGraph.tsx b/src/components/SeekGraph/SeekGraph.tsx index 360a94f6c8..b1cfa7cc09 100644 --- a/src/components/SeekGraph/SeekGraph.tsx +++ b/src/components/SeekGraph/SeekGraph.tsx @@ -804,7 +804,7 @@ export class SeekGraph extends TypedEventEmitter { .attr("title", _("Remove challenge")) .click(() => { //console.log("Remove"); - del("challenges/%%", C.challenge_id) + del(`challenges/${C.challenge_id}`) .then(() => e.html(_("Challenge removed"))) .catch(() => alert.fire(_("Error removing challenge"))); }), diff --git a/src/lib/rengo_balancer.ts b/src/lib/rengo_balancer.ts index 9d634d3b0a..d7c72c042c 100644 --- a/src/lib/rengo_balancer.ts +++ b/src/lib/rengo_balancer.ts @@ -169,14 +169,14 @@ export async function balanceTeams(challenge: Challenge): Promise { - return put("challenges/%%/team", challenge.challenge_id, { + return put(`challenges/${challenge.challenge_id}/team`, { unassign: challenge.rengo_participants, }); } diff --git a/src/lib/rengo_utils.ts b/src/lib/rengo_utils.ts index f58c002ee0..cbc5e07c19 100644 --- a/src/lib/rengo_utils.ts +++ b/src/lib/rengo_utils.ts @@ -32,7 +32,7 @@ export function nominateForRengoChallenge(c: Challenge): Promise { + return put(`challenges/${c.challenge_id}/join`, {}).then((res) => { alert.close(); return res; }); @@ -50,7 +50,7 @@ export function assignToTeam( ? "assign_white" : "unassign"; - return put("challenges/%%/team", challenge.challenge_id, { + return put(`challenges/${challenge.challenge_id}/team`, { [assignment]: [player_id], // back end expects an array of changes, but we only ever send one at a time. }); } @@ -70,13 +70,13 @@ export function startOwnRengoChallenge(the_challenge: Challenge): Promise allowEscapeKey: false, }); - return post("challenges/%%/start", the_challenge.challenge_id, {}).then(() => { + return post(`challenges/${the_challenge.challenge_id}/start`, {}).then(() => { alert.close(); }); } export function cancelRengoChallenge(the_challenge: Challenge): Promise { - return del("challenges/%%", the_challenge.challenge_id); + return del(`challenges/${the_challenge.challenge_id}`); } export function unNominate(the_challenge: Challenge): Promise { @@ -88,7 +88,7 @@ export function unNominate(the_challenge: Challenge): Promise { + return del(`challenges/${the_challenge.challenge_id}/join`, {}).then((res) => { alert.close(); return res; }); diff --git a/src/lib/report_manager.tsx b/src/lib/report_manager.tsx index 0a0cb2ed24..34b5ba774f 100644 --- a/src/lib/report_manager.tsx +++ b/src/lib/report_manager.tsx @@ -318,7 +318,7 @@ class ReportManager extends EventEmitter { public async close(report_id: number, helpful: boolean): Promise { delete this.active_incident_reports[report_id]; this.update(); - const res = await post("moderation/incident/%%", report_id, { + const res = await post(`moderation/incident/${report_id}`, { id: report_id, action: "resolve", was_helpful: helpful, @@ -337,7 +337,7 @@ class ReportManager extends EventEmitter { return res; } public async unclaim(report_id: number): Promise { - const res = await post("moderation/incident/%%", report_id, { + const res = await post(`moderation/incident/${report_id}`, { id: report_id, action: "unclaim", }); @@ -345,7 +345,7 @@ class ReportManager extends EventEmitter { return res; } public async claim(report_id: number): Promise { - const res = await post("moderation/incident/%%", report_id, { + const res = await post(`moderation/incident/${report_id}`, { id: report_id, action: "claim", }).then((res) => { diff --git a/src/lib/requests.ts b/src/lib/requests.ts index 7c8cb629c3..5ce5c9f311 100644 --- a/src/lib/requests.ts +++ b/src/lib/requests.ts @@ -16,7 +16,6 @@ */ import { deepCompare } from "misc"; -import { IdType } from "./types"; /** * If a non-absolute path is provided, appends "/api/v1/" to the input. @@ -61,10 +60,10 @@ function initialize() { } interface Request { - promise?: Promise; + method: Method; url: string; - type: Method; data: object; + promise?: Promise; request?: JQueryXHR; } @@ -73,54 +72,21 @@ let last_request_id = 0; type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; interface RequestFunction { - (url: string): Promise; - (url: string, id_or_data: IdType | object): Promise; - (url: string, id: IdType, data: object): Promise; + (url: string, data?: object): Promise; } function request(method: Method): RequestFunction { - return (url: string, ...rest: [] | [number | string | object] | [number | string, object]) => { + return (url: string, data?: object) => { initialize(); - - let id: IdType | undefined; - let data: object; - switch (typeof rest[0]) { - case "number": - case "string": - id = rest[0]; - data = rest[1] || {}; - break; - case "object": - id = undefined; - data = rest[0]; - break; - case "undefined": - id = undefined; - data = {}; - break; - } - if (url.indexOf("%%") < 0 && id !== undefined) { - console.warn("Url doesn't contain an id but one was given.", url, id); - console.trace(); - } - if (url.indexOf("%%") >= 0 && id === undefined) { - console.error("Url contains an id but none was given.", url); - console.trace(); - } - - const real_url: string = - (typeof id === "number" && isFinite(id)) || typeof id === "string" - ? url.replace("%%", id.toString()) - : url; - const real_data = data; + url = api1ify(url); for (const req_id in requests_in_flight) { const req = requests_in_flight[req_id]; if ( req.promise && - req.url === real_url && - method === req.type && - deepCompare(req.data, real_data) + req.url === url && + method === req.method && + deepCompare(req.data, data) ) { //console.log("Duplicate in flight request, chaining"); return req.promise; @@ -131,15 +97,15 @@ function request(method: Method): RequestFunction { const traceback = new Error(); requests_in_flight[request_id] = { - type: method, - url: real_url, - data: real_data, + method, + url, + data, }; requests_in_flight[request_id].promise = new Promise((resolve, reject) => { const opts: JQueryAjaxSettings = { - url: api1ify(real_url), - type: method, + method, + url, data: undefined, dataType: "json", contentType: "application/json", @@ -151,22 +117,19 @@ function request(method: Method): RequestFunction { delete requests_in_flight[request_id]; if (err.status !== 0) { /* Ignore aborts */ - console.warn(api1ify(real_url), err.status, err.statusText); + console.warn(url, err.status, err.statusText); console.warn(traceback.stack); } reject(err); }, }; - if (real_data) { - if ( - real_data instanceof Blob || - (Array.isArray(real_data) && real_data[0] instanceof Blob) - ) { + if (data) { + if (data instanceof Blob || (Array.isArray(data) && data[0] instanceof Blob)) { opts.data = new FormData(); - if (real_data instanceof Blob) { - opts.data.append("file", real_data); + if (data instanceof Blob) { + opts.data.append("file", data); } else { - for (const file of real_data as Array) { + for (const file of data as Array) { opts.data.append("file", file); } } @@ -174,9 +137,9 @@ function request(method: Method): RequestFunction { opts.contentType = false; } else { if (method === "GET") { - opts.data = real_data; + opts.data = data; } else { - opts.data = JSON.stringify(real_data); + opts.data = JSON.stringify(data); } } } @@ -208,8 +171,6 @@ export function getCookie(name: string) { * Fetches data using the GET method. * @param url the URL for the request. If a relative path is passed, /api/vi/ * will be appended. - * @param [id] providing an id is optional. It will be interpolated into the - * URL where "%%" appears. * @param [data] providing data is optional. This is used as the request payload * in JSON format. * @returns a Promise that resolves with the response payload. @@ -219,8 +180,6 @@ export const get = request("GET"); * Fetches data using the POST method. * @param url the URL for the request. If a relative path is passed, /api/vi/ * will be appended. - * @param [id] providing an id is optional. It will be interpolated into the - * URL where "%%" appears. * @param [data] providing data is optional. This is used as the request payload * in JSON format. * @returns a Promise that resolves with the response payload. @@ -230,8 +189,6 @@ export const post = request("POST"); * Fetches data using the PUT method. * @param url the URL for the request. If a relative path is passed, /api/vi/ * will be appended. - * @param [id] providing an id is optional. It will be interpolated into the - * URL where "%%" appears. * @param [data] providing data is optional. This is used as the request payload * in JSON format. * @returns a Promise that resolves with the response payload. @@ -241,8 +198,6 @@ export const put = request("PUT"); * Fetches data using the PATCH method. * @param url the URL for the request. If a relative path is passed, /api/vi/ * will be appended. - * @param [id] providing an id is optional. It will be interpolated into the - * URL where "%%" appears. * @param [data] providing data is optional. This is used as the request payload * in JSON format. * @returns a Promise that resolves with the response payload. @@ -252,8 +207,6 @@ export const patch = request("PATCH"); * Fetches data using the DELETE method. * @param url the URL for the request. If a relative path is passed, /api/vi/ * will be appended. - * @param [id] providing an id is optional. It will be interpolated into the - * URL where "%%" appears. * @param [data] providing data is optional. This is used as the request payload * in JSON format. * @returns a Promise that resolves with the response payload. @@ -269,7 +222,7 @@ export const del = request("DELETE"); export function abort_requests_in_flight(url: string, method?: Method): void { for (const id in requests_in_flight) { const req = requests_in_flight[id]; - if (req.url === url && (!method || method === req.type)) { + if (req.url === url && (!method || method === req.method)) { req.request.abort(); } } diff --git a/src/models/games.d.ts b/src/models/games.d.ts index 20734cf774..d2f751f58e 100644 --- a/src/models/games.d.ts +++ b/src/models/games.d.ts @@ -259,7 +259,7 @@ declare namespace rest_api { } /** - * The active_games list in the /players/%%/full + * The active_games list in the /players/${id}/full */ interface Game { black: game.Player; diff --git a/src/views/AnnouncementCenter/AnnouncementCenter.tsx b/src/views/AnnouncementCenter/AnnouncementCenter.tsx index 9a98b6cd5d..badda41ff9 100644 --- a/src/views/AnnouncementCenter/AnnouncementCenter.tsx +++ b/src/views/AnnouncementCenter/AnnouncementCenter.tsx @@ -106,7 +106,7 @@ export function AnnouncementCenter(): JSX.Element { .catch(errorAlerter); }; const deleteAnnouncement = (announcement) => { - del("announcements/%%", announcement.id).then(refresh).catch(errorAlerter); + del(`announcements/${announcement.id}`).then(refresh).catch(errorAlerter); }; const can_create = !!text; diff --git a/src/views/ChallengeLinkLanding/ChallengeLinkLanding.tsx b/src/views/ChallengeLinkLanding/ChallengeLinkLanding.tsx index 2c0212ca77..976f598abd 100644 --- a/src/views/ChallengeLinkLanding/ChallengeLinkLanding.tsx +++ b/src/views/ChallengeLinkLanding/ChallengeLinkLanding.tsx @@ -61,7 +61,7 @@ export function ChallengeLinkLanding(): JSX.Element { }); if (challenge.rengo) { - put("challenges/%%/join", challenge.challenge_id, {}) + put(`challenges/${challenge.challenge_id}/join`, {}) .then(() => { alert.close(); if (challenge.invite_only) { @@ -77,7 +77,7 @@ export function ChallengeLinkLanding(): JSX.Element { errorAlerter(err); }); } else { - post("challenges/%%/accept", challenge.challenge_id, {}) + post(`challenges/${challenge.challenge_id}/accept`, {}) .then(() => { alert.close(); navigate(`/game/${challenge.game_id}`, { replace: true }); diff --git a/src/views/ForceUsernameChange/ForceUsernameChange.tsx b/src/views/ForceUsernameChange/ForceUsernameChange.tsx index 1471fae116..3148f7753a 100644 --- a/src/views/ForceUsernameChange/ForceUsernameChange.tsx +++ b/src/views/ForceUsernameChange/ForceUsernameChange.tsx @@ -36,7 +36,7 @@ export function ForceUsernameChange(): JSX.Element { }); function saveUsername() { - put("players/%%", user.id, { username }) + put(`players/${user.id}`, { username }) .then(() => { cached.refresh.config(() => window.location.reload()); }) diff --git a/src/views/Game/Game.tsx b/src/views/Game/Game.tsx index 591144e4f3..73ed371b09 100644 --- a/src/views/Game/Game.tsx +++ b/src/views/Game/Game.tsx @@ -646,7 +646,7 @@ export function Game(): JSX.Element { }) .then(({ value: accept }) => { if (accept) { - post("games/%%/reviews", game_id, {}) + post(`games/${game_id}/reviews`, {}) .then((res) => browserHistory.push(`/review/${res.id}`)) .catch(errorAlerter); } @@ -1271,7 +1271,7 @@ export function Game(): JSX.Element { } if (game_id) { - get("games/%%", game_id) + get(`games/${game_id}`) .then((game: rest_api.GameDetails) => { if (game.players.white.id) { player_cache.update(game.players.white, true); @@ -1345,7 +1345,7 @@ export function Game(): JSX.Element { } if (review_id) { - get("reviews/%%", review_id) + get(`reviews/${review_id}`) .then((review) => { if (review.game && review.game.historical_ratings) { set_historical_black(review.game.historical_ratings.black); diff --git a/src/views/Game/GameDock.tsx b/src/views/Game/GameDock.tsx index c174b97fc6..87f4a2d715 100644 --- a/src/views/Game/GameDock.tsx +++ b/src/views/Game/GameDock.tsx @@ -225,7 +225,7 @@ export function GameDock({ moderation_note = moderation_note.trim(); } while (moderation_note === ""); - post("games/%%/moderate", game_id, { + post(`games/${game_id}/moderate`, { decide: winner, moderation_note: moderation_note, }).catch(errorAlerter); @@ -248,7 +248,7 @@ export function GameDock({ moderation_note = moderation_note.trim(); } while (moderation_note === ""); - post("games/%%/moderate", game_id, { + post(`games/${game_id}/moderate`, { autoscore: true, moderation_note: moderation_note, }).catch(errorAlerter); diff --git a/src/views/Game/PlayControls.test.tsx b/src/views/Game/PlayControls.test.tsx index 5271116103..4191bbf5f6 100644 --- a/src/views/Game/PlayControls.test.tsx +++ b/src/views/Game/PlayControls.test.tsx @@ -290,7 +290,7 @@ test("Pause buttons show up", () => { , ); act(() => { - // It would be more realistic to mock the "game/%%/clock" socket event, + // It would be more realistic to mock the "game/${id}/clock" socket event, // but AdHocClock is a complicated object and I'm not sure what params // to use to get the goban to actually pause. goban.pause_control = { diff --git a/src/views/Group/Group.tsx b/src/views/Group/Group.tsx index c3ca53d4df..57cd23baf2 100644 --- a/src/views/Group/Group.tsx +++ b/src/views/Group/Group.tsx @@ -161,7 +161,7 @@ class _Group extends React.PureComponent { resolve(group_id: number) { const user = data.get("user"); - get("groups/%%", group_id) + get(`groups/${group_id}`) .then((group: GroupInfo) => { window.document.title = group.name; @@ -181,7 +181,7 @@ class _Group extends React.PureComponent { }); }) .catch(errorAlerter); - get("groups/%%/news/", group_id) + get(`groups/${group_id}/news/`) .then((news) => { this.setState({ news: news.results }); }) @@ -202,14 +202,14 @@ class _Group extends React.PureComponent { } leaveGroup = () => { - post("groups/%%/members", this.state.group_id, { delete: true }) + post(`groups/${this.state.group_id}/members`, { delete: true }) .then(() => { this.resolve(this.state.group_id); }) .catch(errorAlerter); }; joinGroup = () => { - post("groups/%%/members", this.state.group_id, {}) + post(`groups/${this.state.group_id}/members`, {}) .then((res) => { if (res.success) { this.resolve(this.state.group_id); @@ -245,7 +245,7 @@ class _Group extends React.PureComponent { }); image_resizer(files[0], 512, 512) .then((file: Blob) => { - put("group/%%/icon", this.state.group_id, file) + put(`group/${this.state.group_id}/icon`, file) .then((res) => { console.log("Upload successful", res); }) @@ -259,7 +259,7 @@ class _Group extends React.PureComponent { }); image_resizer(files[0], 2560, 512) .then((file: Blob) => { - put("group/%%/banner", this.state.group_id, file) + put(`group/${this.state.group_id}/banner`, file) .then((res) => { console.log("Upload successful", res); }) @@ -373,7 +373,7 @@ class _Group extends React.PureComponent { return; } this.toggleNewNewsPost(); - post("group/%%/news/", this.state.group_id, { + post(`group/${this.state.group_id}/news/`, { title: this.state.new_news_title, content: this.state.new_news_body, }) @@ -407,7 +407,7 @@ class _Group extends React.PureComponent { }) .then(({ value: accept }) => { if (accept) { - post("group/%%/news/", this.state.group_id, { + post(`group/${this.state.group_id}/news/`, { id: entry.id, delete: true, }) @@ -459,7 +459,7 @@ class _Group extends React.PureComponent { inviteUser = () => { const username = this.state.user_to_invite.username; - post("group/%%/members", this.state.group_id, { username }) + post(`group/${this.state.group_id}/members`, { username }) .then((res) => { console.log(res); this.setState({ @@ -1185,7 +1185,7 @@ class _Group extends React.PureComponent { }) .then(({ value: accept }) => { if (accept) { - del("groups/%%", this.state.group.id) + del(`groups/${this.state.group_id}`) .then(() => { browserHistory.push("/groups/"); }) @@ -1203,7 +1203,7 @@ class _Group extends React.PureComponent { }) .then(({ value: accept }) => { if (accept) { - put("groups/%%/members", this.state.group_id, { + put(`groups/${this.state.group_id}/members`, { player_id: player_id, is_admin: true, }) @@ -1222,7 +1222,7 @@ class _Group extends React.PureComponent { }) .then(({ value: accept }) => { if (accept) { - put("groups/%%/members", this.state.group_id, { + put(`groups/${this.state.group_id}/members`, { player_id: player_id, is_admin: false, }) @@ -1241,7 +1241,7 @@ class _Group extends React.PureComponent { }) .then(({ value: accept }) => { if (accept) { - post("groups/%%/members", this.state.group_id, { + post(`groups/${this.state.group_id}/members`, { delete: true, player_id: player_id, }) diff --git a/src/views/Ladder/Ladder.tsx b/src/views/Ladder/Ladder.tsx index dcd155eea6..3d66a63aaf 100644 --- a/src/views/Ladder/Ladder.tsx +++ b/src/views/Ladder/Ladder.tsx @@ -77,7 +77,7 @@ class _Ladder extends React.PureComponent { } resolve(ladder_id) { - get("ladders/%%", ladder_id) + get(`ladders/${ladder_id}`) .then((ladder) => { this.setState({ ladder: ladder, @@ -91,7 +91,7 @@ class _Ladder extends React.PureComponent { } join = () => { - post("ladders/%%/players", this.props.match.params.ladder_id, {}) + post(`ladders/${this.props.match.params.ladder_id}/players`, {}) .then(() => { this.invalidate(); this.resolve(this.props.match.params.ladder_id); @@ -112,7 +112,7 @@ class _Ladder extends React.PureComponent { }) .then(({ value: yes }) => { if (yes) { - del("ladders/%%/players", this.props.match.params.ladder_id) + del(`ladders/${this.props.match.params.ladder_id}/players`) .then(() => { this.invalidate(); this.resolve(this.props.match.params.ladder_id); @@ -472,8 +472,7 @@ export class LadderRow extends React.Component { if (new_rank) { put( - "ladders/%%/players/moderate", - this.props.ladder.props.match.params.ladder_id, + `ladders/${this.props.ladder.props.match.params.ladder_id}/players/moderate`, { moderation_note: "Adjusting ladder position", player_id: player.id, @@ -619,8 +618,7 @@ export class LadderRow extends React.Component { if (yes) { post( - "ladders/%%/players/challenge", - this.props.ladder.props.match.params.ladder_id, + `ladders/${this.props.ladder.props.match.params.ladder_id}/players/challenge`, { player_id: ladder_player.player.id, }, diff --git a/src/views/LadderList/LadderList.tsx b/src/views/LadderList/LadderList.tsx index 06a212df84..eb7b1a7b90 100644 --- a/src/views/LadderList/LadderList.tsx +++ b/src/views/LadderList/LadderList.tsx @@ -70,7 +70,7 @@ export function LadderList(): JSX.Element { } const join = (ladder_id: number) => { - post("ladders/%%/players", ladder_id, {}) + post(`ladders/${ladder_id}/players`, {}) .then(() => { fetchLadders(); }) diff --git a/src/views/LibraryPlayer/LibraryPlayer.tsx b/src/views/LibraryPlayer/LibraryPlayer.tsx index dddd6ee031..144a8fef07 100644 --- a/src/views/LibraryPlayer/LibraryPlayer.tsx +++ b/src/views/LibraryPlayer/LibraryPlayer.tsx @@ -128,7 +128,7 @@ class _LibraryPlayer extends React.PureComponent { @@ -256,9 +256,7 @@ class _LibraryPlayer extends React.PureComponent { if (parseInt(this.props.match.params.player_id) === data.get("user").id) { files = files.filter((file) => /.sgf$/i.test(file.name)); - Promise.all( - files.map((file) => post("me/games/sgf/%%", this.state.collection_id, file)), - ) + Promise.all(files.map((file) => post(`me/games/sgf/${this.state.collection_id}`, file))) .then(() => { this.refresh(this.props.match.params.player_id).then(ignore).catch(ignore); }) @@ -274,7 +272,7 @@ class _LibraryPlayer extends React.PureComponent { this.refresh(this.props.match.params.player_id).then(ignore).catch(ignore); }) @@ -306,7 +304,7 @@ class _LibraryPlayer extends React.PureComponent { - post("library/%%/collections", this.state.player_id, { + post(`library/${this.state.player_id}/collections`, { parent_id: this.state.collection_id, name: this.state.new_collection_name, private: this.state.new_collection_private ? 1 : 0, @@ -320,7 +318,7 @@ class _LibraryPlayer extends React.PureComponent { const parent = this.state.collections[this.state.collection_id].parent; - post("library/%%", this.state.player_id, { + post(`library/${this.state.player_id}`, { delete_collections: [this.state.collection_id], }) .then(() => { @@ -331,7 +329,7 @@ class _LibraryPlayer extends React.PureComponent { - post("library/%%", this.state.player_id, { + post(`library/${this.state.player_id}`, { delete_entries: Object.keys(this.state.games_checked), }) .then(() => { diff --git a/src/views/Overview/ChallengesList.tsx b/src/views/Overview/ChallengesList.tsx index 937c17afbb..be902acc1a 100644 --- a/src/views/Overview/ChallengesList.tsx +++ b/src/views/Overview/ChallengesList.tsx @@ -48,11 +48,11 @@ export class ChallengesList extends React.PureComponent<{ onAccept: () => void } }; deleteChallenge(challenge) { - del("me/challenges/%%", challenge.id).then(ignore).catch(ignore); + del(`me/challenges/${challenge.id}`).then(ignore).catch(ignore); this.setState({ challenges: this.state.challenges.filter((c) => c.id !== challenge.id) }); } acceptChallenge(challenge) { - post("me/challenges/%%/accept", challenge.id, {}) + post(`me/challenges/${challenge.id}/accept`, {}) .then((res) => { if (res.time_per_move > 0 && res.time_per_move < 1800) { browserHistory.push(`/game/${res.game}`); diff --git a/src/views/Overview/InviteList.tsx b/src/views/Overview/InviteList.tsx index 30265735c0..09b731af5c 100644 --- a/src/views/Overview/InviteList.tsx +++ b/src/views/Overview/InviteList.tsx @@ -104,7 +104,7 @@ export function InviteList(): JSX.Element { }; const deleteChallenge = (challenge: Challenge) => { - del("challenges/%%", challenge.challenge_id) + del(`challenges/${challenge.challenge_id}`) .then(() => { removeChallenge(challenge); }) diff --git a/src/views/Play/Play.tsx b/src/views/Play/Play.tsx index 1a0d257de2..71fc46b1ef 100644 --- a/src/views/Play/Play.tsx +++ b/src/views/Play/Play.tsx @@ -282,7 +282,7 @@ export class Play extends React.Component<{}, PlayState> { }; cancelOpenChallenge = (challenge: Challenge) => { - del("challenges/%%", challenge.challenge_id).catch(errorAlerter); + del(`challenges/${challenge.challenge_id}`).catch(errorAlerter); this.unfreezeChallenges(); }; diff --git a/src/views/Puzzle/Puzzle.tsx b/src/views/Puzzle/Puzzle.tsx index 4b85db5ca6..92135a0b15 100644 --- a/src/views/Puzzle/Puzzle.tsx +++ b/src/views/Puzzle/Puzzle.tsx @@ -371,7 +371,7 @@ export class _Puzzle extends React.Component { }; ratePuzzle = (value) => { - put("puzzles/%%/rate", +this.props.match.params.puzzle_id, { rating: value }) + put(`puzzles/${this.props.match.params.puzzle_id}/rate`, { rating: value }) .then(ignore) .catch(errorAlerter); this.setState({ @@ -424,7 +424,7 @@ export class _Puzzle extends React.Component { if (parseInt(this.props.match.params.puzzle_id)) { /* save */ - put("puzzles/%%", +this.props.match.params.puzzle_id, { puzzle: puzzle }) + put(`puzzles/${this.props.match.params.puzzle_id}`, { puzzle: puzzle }) .then(() => { window.location.reload(); }) @@ -616,7 +616,7 @@ export class _Puzzle extends React.Component { }) .then(({ value: accept }) => { if (accept) { - del("puzzles/%%", +this.props.match.params.puzzle_id) + del(`puzzles/${this.props.match.params.puzzle_id}`) .then(() => browserHistory.push( `/puzzle-collection/${this.state.puzzle.puzzle_collection}`, diff --git a/src/views/Puzzle/PuzzleEditing.ts b/src/views/Puzzle/PuzzleEditing.ts index 6c28b83799..9208fb89b2 100644 --- a/src/views/Puzzle/PuzzleEditing.ts +++ b/src/views/Puzzle/PuzzleEditing.ts @@ -145,9 +145,9 @@ export class PuzzleEditor { } Promise.all([ - get("puzzles/%%", puzzle_id), - get("puzzles/%%/collection_summary", puzzle_id), - get("puzzles/%%/rate", puzzle_id), + get(`puzzles/${puzzle_id}`), + get(`puzzles/${puzzle_id}/collection_summary`), + get(`puzzles/${puzzle_id}/rate`), ]) .then((arr: [rest_api.PuzzleDetail, any, any]) => { const rating = arr[2]; diff --git a/src/views/ReportsCenter/ReportedGame.tsx b/src/views/ReportsCenter/ReportedGame.tsx index add0889b40..c0ef23e3a0 100644 --- a/src/views/ReportsCenter/ReportedGame.tsx +++ b/src/views/ReportsCenter/ReportedGame.tsx @@ -75,7 +75,7 @@ export function ReportedGame({ game_id }: { game_id: number }): JSX.Element { moderation_note = moderation_note.trim(); } while (moderation_note === ""); - post("games/%%/moderate", game_id, { + post(`games/${game_id}/moderate`, { decide: winner, moderation_note: moderation_note, }).catch(errorAlerter); @@ -122,7 +122,7 @@ export function ReportedGame({ game_id }: { game_id: number }): JSX.Element { React.useEffect(() => { if (game_id) { - get("games/%%", game_id) + get(`games/${game_id}`) .then((game: rest_api.GameDetails) => { setGame(game); setAnnulled(game.annulled); diff --git a/src/views/ReportsCenter/ViewReport.tsx b/src/views/ReportsCenter/ViewReport.tsx index 193b680cf5..4491889679 100644 --- a/src/views/ReportsCenter/ViewReport.tsx +++ b/src/views/ReportsCenter/ViewReport.tsx @@ -144,7 +144,7 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J if (!report_note_update_timeout) { report_note_update_timeout = setTimeout(() => { - post("moderation/incident/%%", report.id, { + post(`moderation/incident/${report.id}`, { id: report.id, action: "note", note: report_note_text, @@ -164,7 +164,7 @@ export function ViewReport({ report_id, reports, onChange }: ViewReportProps): J const assignToModerator = React.useCallback( (id: number) => { setModeratorId(id); - post("moderation/incident/%%", report.id, { + post(`moderation/incident/${report.id}`, { id: report.id, action: "assign", moderator_id: id, diff --git a/src/views/Settings/AccountSettings.tsx b/src/views/Settings/AccountSettings.tsx index f26db869f5..26dce88747 100644 --- a/src/views/Settings/AccountSettings.tsx +++ b/src/views/Settings/AccountSettings.tsx @@ -230,7 +230,7 @@ export function AccountSettings(props: SettingGroupPageProps): JSX.Element { image_resizer(files[0], 512, 512) .then((file: Blob) => { - put("players/%%/icon", user.id, file) + put(`players/${user.id}/icon`, file) .then((res) => { console.log("Upload successful", res); user.icon = res.icon; @@ -245,7 +245,7 @@ export function AccountSettings(props: SettingGroupPageProps): JSX.Element { }; const clearIcon = () => { setNewIcon(null); - del("players/%%/icon", user.id) + del(`players/${user.id}/icon`) .then((res) => { console.log("Cleared icon", res); user.icon = res.icon; @@ -268,7 +268,7 @@ export function AccountSettings(props: SettingGroupPageProps): JSX.Element { email, website, }; - put("players/%%", user.id, data) + put(`players/${user.id}`, data) .then(() => { cached.refresh.config(() => window.location.reload()); toast({_("Account settings updated successfully!")}, 5000); diff --git a/src/views/Tournament/Tournament.tsx b/src/views/Tournament/Tournament.tsx index df6dfcbc7e..97a6f38541 100644 --- a/src/views/Tournament/Tournament.tsx +++ b/src/views/Tournament/Tournament.tsx @@ -200,7 +200,7 @@ export function Tournament(): JSX.Element { resolve(); } if (new_tournament_group_id) { - get("groups/%%", new_tournament_group_id) + get(`groups/${new_tournament_group_id}`) .then((group) => { tournament_ref.current.group = group; refresh(); @@ -222,7 +222,7 @@ export function Tournament(): JSX.Element { const resolve = () => { abort_requests(); - const tournament_info_promise = get("tournaments/%%", tournament_id).then((t) => { + const tournament_info_promise = get(`tournaments/${tournament_id}`).then((t) => { tournament_ref.current = t; setInfoLoaded(true); refresh(); @@ -231,7 +231,7 @@ export function Tournament(): JSX.Element { Promise.all([ tournament_info_promise, - get("tournaments/%%/rounds", tournament_id), + get(`tournaments/${tournament_id}/rounds`), refreshPlayerList(), ]) .then((res) => { @@ -280,7 +280,7 @@ export function Tournament(): JSX.Element { resolve(); }; const refreshPlayerList = () => { - const ret = get("tournaments/%%/players/all", tournament_id); + const ret = get(`tournaments/${tournament_id}/players/all`); ret.then((players) => { for (const id in players) { @@ -402,7 +402,7 @@ export function Tournament(): JSX.Element { }) .then(({ value: accept }) => { if (accept) { - post("tournaments/%%/start", tournament_ref.current.id, {}) + post(`tournaments/${tournament_ref.current.id}/start`, {}) .then(ignore) .catch(errorAlerter); } @@ -417,7 +417,7 @@ export function Tournament(): JSX.Element { }) .then(({ value: accept }) => { if (accept) { - del("tournaments/%%", tournament_ref.current.id) + del(`tournaments/${tournament_ref.current.id}`) .then(() => { browserHistory.push("/"); }) @@ -434,7 +434,7 @@ export function Tournament(): JSX.Element { }) .then(({ value: accept }) => { if (accept) { - post("tournaments/%%/end", tournament_ref.current.id, {}) + post(`tournaments/${tournament_ref.current.id}/end`, {}) .then(() => { reloadTournament(); }) @@ -448,7 +448,7 @@ export function Tournament(): JSX.Element { } const username = user_to_invite.username; - post("tournaments/%%/players", tournament_id, { username }) + post(`tournaments/${tournament_id}/players`, { username }) .then((res) => { console.log(res); setInviteResult(interpolate(_("Invited {{username}}"), { username })); @@ -465,14 +465,14 @@ export function Tournament(): JSX.Element { }); }; const joinTournament = () => { - post("tournaments/%%/players", tournament_id, {}) + post(`tournaments/${tournament_id}/players`, {}) .then(() => { setIsJoined(true); }) .catch(errorAlerter); }; const partTournament = () => { - post("tournaments/%%/players", tournament_id, { delete: true }) + post(`tournaments/${tournament_id}/players`, { delete: true }) .then(() => { setIsJoined(false); }) @@ -1373,7 +1373,7 @@ export function Tournament(): JSX.Element { }) .then(({ value: accept }) => { if (accept) { - post("tournaments/%%/players", tournament_ref.current.id, { + post(`tournaments/${tournament_ref.current.id}/players`, { delete: true, player_id: user.id, }) @@ -1422,7 +1422,7 @@ export function Tournament(): JSX.Element { const adjustments = {}; adjustments[user.id] = v; - put("tournaments/%%/players", tournament_ref.current.id, { + put(`tournaments/${tournament_ref.current.id}/players`, { adjust: adjustments, }) .then(ignore) @@ -1442,7 +1442,7 @@ export function Tournament(): JSX.Element { }) .then(({ value: accept }) => { if (accept) { - put("tournaments/%%/players", tournament_ref.current.id, { + put(`tournaments/${tournament_ref.current.id}/players`, { disqualify: user.id, }) .then(ignore) @@ -3236,7 +3236,7 @@ function OpenGothaTournamentUploadDownload({ } function uploadFile(files) { - put("tournaments/%%/opengotha", tournament.id, files[0]) + put(`tournaments/${tournament.id}/opengotha`, files[0]) .then((res) => { console.log("Upload successful", res); openMergeReportModal(res.merge_report); diff --git a/src/views/User/AvatarCard.tsx b/src/views/User/AvatarCard.tsx index 28f3b32fd1..98f868ce6a 100644 --- a/src/views/User/AvatarCard.tsx +++ b/src/views/User/AvatarCard.tsx @@ -157,7 +157,7 @@ export function AvatarCard({ image_resizer(files[0], 512, 512) .then((file: Blob) => { - put("players/%%/icon", user.id, file) + put(`players/${user.id}/icon`, file) .then((res) => { console.log("Upload successful", res); user.icon = res.icon; @@ -172,7 +172,7 @@ export function AvatarCard({ }; const clearIcon = () => { setNewIcon(null); - del("players/%%/icon", user.id) + del(`players/${user.id}/icon`) .then((res) => { console.log("Cleared icon", res); user.icon = res.icon; diff --git a/src/views/User/ModTools.tsx b/src/views/User/ModTools.tsx index 5530d6d3ea..78ed23ced1 100644 --- a/src/views/User/ModTools.tsx +++ b/src/views/User/ModTools.tsx @@ -69,7 +69,7 @@ export function ModTools(props: ModToolsProps): JSX.Element { React.useEffect(() => { if (props.collapse_same_users) { - get(`players/${props.user_id}/aliases/`, undefined, { page_size: 100 }) + get(`players/${props.user_id}/aliases/`, { page_size: 100 }) .then((data: any) => { const aliases = data.results; setAliases(aliases); diff --git a/src/views/User/User.tsx b/src/views/User/User.tsx index 490d569042..da0eabe50f 100644 --- a/src/views/User/User.tsx +++ b/src/views/User/User.tsx @@ -109,8 +109,8 @@ export function User(props: { user_id?: number }): JSX.Element { return; } - // Cheaper API calls provide partial profile data before players/%%/full - Promise.all([get("players/%%", user_id), get("/termination-api/player/%%", user_id)]) + // Cheaper API calls provide partial profile data before players/{user_id}/full + Promise.all([get(`players/${user_id}`), get(`/termination-api/player/${user_id}`)]) .then((responses: [rest_api.PlayerDetail, rest_api.termination_api.Player]) => { if (resolved) { return; @@ -138,7 +138,7 @@ export function User(props: { user_id?: number }): JSX.Element { }) .catch(console.log); - get("players/%%/full", user_id) + get(`players/${user_id}/full`) .then((response: rest_api.FullPlayerDetail) => { setResolved(true); try { @@ -184,7 +184,7 @@ export function User(props: { user_id?: number }): JSX.Element { }), ); if (user) { - put("players/%%", user.id, { + put(`players/${user.id}`, { ...profile_card_changes, about: user.about, }) From e6201f87e9df1dba7422a593375f5ced34860ecf Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Wed, 18 Oct 2023 09:24:25 -0600 Subject: [PATCH 2/3] Switched to using the fetch API from jquery XHR based requests --- .../PlayerAutocomplete/PlayerAutocomplete.tsx | 3 +- src/lib/misc.ts | 9 +- src/lib/requests.ts | 163 ++++++++++-------- src/main.tsx | 3 + src/views/Game/Game.tsx | 8 +- 5 files changed, 101 insertions(+), 85 deletions(-) diff --git a/src/components/PlayerAutocomplete/PlayerAutocomplete.tsx b/src/components/PlayerAutocomplete/PlayerAutocomplete.tsx index d29076e6d3..5f2002ddf0 100644 --- a/src/components/PlayerAutocomplete/PlayerAutocomplete.tsx +++ b/src/components/PlayerAutocomplete/PlayerAutocomplete.tsx @@ -164,8 +164,7 @@ function _PlayerAutocomplete(props: PlayerAutocompleteProperties, ref): JSX.Elem //complete(value); } }).catch((err) => { - if (err.status !== 0) { - // status === 0 is an abort + if (err.name !== "AbortError") { console.log(err); } }); diff --git a/src/lib/misc.ts b/src/lib/misc.ts index 40458695af..55a85b2579 100644 --- a/src/lib/misc.ts +++ b/src/lib/misc.ts @@ -248,6 +248,10 @@ export function getPrintableError(err) { } if (err instanceof Error) { + if (err.name === "AbortError") { + /* ignore aborted requests' */ + return; + } console.error(err.stack); return err.toString(); } @@ -279,11 +283,6 @@ export function getPrintableError(err) { } } - if (obj.status === 0 && obj.statusText === "abort") { - /* ignore aborted requests' */ - return; - } - /* if (typeof(obj) === "object") { if (obj.game) obj = obj.game; diff --git a/src/lib/requests.ts b/src/lib/requests.ts index 5ce5c9f311..492bea5f99 100644 --- a/src/lib/requests.ts +++ b/src/lib/requests.ts @@ -39,35 +39,16 @@ export function api1ify(path: string) { /** alias for api1ify */ export const api1 = api1ify; -let initialized = false; -function initialize() { - if (initialized) { - return; - } - initialized = true; - - function csrfSafeMethod(method) { - return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method); - } - $.ajaxSetup({ - crossDomain: false, // obviates need for sameOrigin test - beforeSend: (xhr, settings) => { - if (!csrfSafeMethod(settings.type)) { - xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); - } - }, - }); -} - -interface Request { +interface OgsRequest { method: Method; url: string; data: object; promise?: Promise; - request?: JQueryXHR; + controller: AbortController; + signal: AbortSignal; } -const requests_in_flight: { [id: string]: Request } = {}; +const requests_in_flight: { [id: string]: OgsRequest } = {}; let last_request_id = 0; type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; @@ -76,8 +57,10 @@ interface RequestFunction { } function request(method: Method): RequestFunction { - return (url: string, data?: object) => { - initialize(); + const csrf_safe = /^(GET|HEAD|OPTIONS|TRACE)$/.test(method); + const cacheable = /^(GET|HEAD|OPTIONS|TRACE)$/.test(method); + + return async (url: string, data?: object): Promise => { url = api1ify(url); for (const req_id in requests_in_flight) { @@ -88,7 +71,7 @@ function request(method: Method): RequestFunction { method === req.method && deepCompare(req.data, data) ) { - //console.log("Duplicate in flight request, chaining"); + //console.log(`Duplicate in flight request: ${url} , chaining`); return req.promise; } } @@ -96,77 +79,86 @@ function request(method: Method): RequestFunction { const request_id = ++last_request_id; const traceback = new Error(); + const controller = new AbortController(); + const signal = controller.signal; + requests_in_flight[request_id] = { method, url, data, + controller, + signal, }; requests_in_flight[request_id].promise = new Promise((resolve, reject) => { - const opts: JQueryAjaxSettings = { - method, - url, - data: undefined, - dataType: "json", - contentType: "application/json", - success: (res) => { - delete requests_in_flight[request_id]; - resolve(res); - }, - error: (err) => { - delete requests_in_flight[request_id]; - if (err.status !== 0) { - /* Ignore aborts */ - console.warn(url, err.status, err.statusText); - console.warn(traceback.stack); - } - reject(err); - }, - }; + let prepared_data: string | FormData | undefined; + const headers: Headers = new Headers(); + headers.append("Accept", "application/json"); + + if (!csrf_safe) { + headers.append("X-CSRFToken", getCookie("csrftoken")); + } + if (data) { if (data instanceof Blob || (Array.isArray(data) && data[0] instanceof Blob)) { - opts.data = new FormData(); + prepared_data = new FormData(); if (data instanceof Blob) { - opts.data.append("file", data); + prepared_data.append("file", data); } else { for (const file of data as Array) { - opts.data.append("file", file); + prepared_data.append("file", file); } } - opts.processData = false; - opts.contentType = false; } else { if (method === "GET") { - opts.data = data; + url += + (url.indexOf("?") >= 0 ? "&" : "?") + + Object.keys(data) + .map((k) => `${k}=` + encodeURIComponent(data[k])) + .join("&"); } else { - opts.data = JSON.stringify(data); + prepared_data = JSON.stringify(data); + headers.append("Content-Type", "application/json"); } } } - requests_in_flight[request_id].request = $.ajax(opts); + fetch(url, { + signal, + method, + credentials: "include", + keepalive: true, + mode: csrf_safe ? "no-cors" : "cors", + cache: cacheable ? "default" : "no-cache", + body: prepared_data as any, + headers, + }) + .then((res) => { + delete requests_in_flight[request_id]; + if (res.status >= 200 && res.status < 300) { + if (res.status === 204) { + resolve({}); + } else { + resolve(res.json()); + } + } else { + reject(res); + } + }) + .catch((err) => { + delete requests_in_flight[request_id]; + if (err.name !== "AbortError") { + console.warn(url, err.name); + console.warn(traceback.stack); + } + reject(err); + }); }); return requests_in_flight[request_id].promise; }; } -/** Returns the cookie value for a given key */ -export function getCookie(name: string) { - let cookieValue = null; - if (document.cookie && document.cookie !== "") { - const cookies = document.cookie.split(";"); - for (let i = 0; i < cookies.length; i++) { - const cookie = jQuery.trim(cookies[i]); - if (cookie.substring(0, name.length + 1) === name + "=") { - cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); - break; - } - } - } - return cookieValue; -} - /** * Fetches data using the GET method. * @param url the URL for the request. If a relative path is passed, /api/vi/ @@ -219,11 +211,34 @@ export const del = request("DELETE"); * @param [method] providing a method is optional. If no method is provided, * all requests to the URL will be cancelled. */ -export function abort_requests_in_flight(url: string, method?: Method): void { - for (const id in requests_in_flight) { - const req = requests_in_flight[id]; +export function abort_requests_in_flight(url: string, method?: Method): boolean { + let aborted = false; + url = api1ify(url); + + for (const request_id in requests_in_flight) { + const req = requests_in_flight[request_id]; if (req.url === url && (!method || method === req.method)) { - req.request.abort(); + console.log("Aborting request", url); + req.controller.abort(); + aborted = true; + delete requests_in_flight[request_id]; } } + + return aborted; +} + +export function getCookie(name: string) { + let cookieValue = null; + if (document.cookie && document.cookie !== "") { + const cookies = document.cookie.split(";"); + for (let i = 0; i < cookies.length; i++) { + const cookie = jQuery.trim(cookies[i]); + if (cookie.substring(0, name.length + 1) === name + "=") { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; } diff --git a/src/main.tsx b/src/main.tsx index a6046c2c02..8ffc08efda 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -312,3 +312,6 @@ window["data"] = data; window["preferences"] = preferences; window["player_cache"] = player_cache; window["GoMath"] = GoMath; + +import * as requests from "requests"; +window["requests"] = requests; diff --git a/src/views/Game/Game.tsx b/src/views/Game/Game.tsx index 73ed371b09..733c2120aa 100644 --- a/src/views/Game/Game.tsx +++ b/src/views/Game/Game.tsx @@ -1328,15 +1328,15 @@ export function Game(): JSX.Element { } }) .catch((e) => { - if (e.statusText === "abort") { - console.error("Error: abort", e); + if (e.name === "AbortError") { + //console.error("Error: abort", e); return; } - if (e.statusText === "Not Found") { + if (e.status === 404 || e.statusText === "Not Found") { console.error("Error: not found, handled 10s later by socket.ts", e); return; } - console.error(e); + console.error(e.name, e); void alert.fire({ title: "Failed to load game data: " + e.statusText, icon: "error", From dc7a3570ac76f6be2592fdce04e50c904c098916 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Tue, 24 Oct 2023 14:10:33 -0600 Subject: [PATCH 3/3] Fix CORS stuff when requesting other origins (ie, the AI servers) --- src/lib/requests.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/requests.ts b/src/lib/requests.ts index 492bea5f99..23f4ec09dc 100644 --- a/src/lib/requests.ts +++ b/src/lib/requests.ts @@ -123,12 +123,14 @@ function request(method: Method): RequestFunction { } } + const same_origin = url.indexOf("://") < 0 || url.indexOf(window.location.origin) === 0; + fetch(url, { signal, method, - credentials: "include", + credentials: same_origin ? "include" : undefined, keepalive: true, - mode: csrf_safe ? "no-cors" : "cors", + mode: same_origin ? (csrf_safe ? "no-cors" : "cors") : undefined, cache: cacheable ? "default" : "no-cache", body: prepared_data as any, headers,