Browse Source

Replace `CancelToken` to `AbortSignal` (#19352)

Yamagishi Kazutoshi 1 year ago
parent
commit
219c38b921

+ 24 - 22
app/javascript/mastodon/actions/compose.js

@@ -1,18 +1,20 @@
-import api from '../api';
-import { CancelToken, isCancel } from 'axios';
+import { isCancel } from 'axios';
 import { throttle } from 'lodash';
-import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
-import { tagHistory } from '../settings';
+import { defineMessages } from 'react-intl';
+import api from 'mastodon/api';
+import { search as emojiSearch } from 'mastodon/features/emoji/emoji_mart_search_light';
+import { tagHistory } from 'mastodon/settings';
+import resizeImage from 'mastodon/utils/resize_image';
+import { showAlert, showAlertForError } from './alerts';
 import { useEmoji } from './emojis';
-import resizeImage from '../utils/resize_image';
 import { importFetchedAccounts } from './importer';
-import { updateTimeline } from './timelines';
-import { showAlertForError } from './alerts';
-import { showAlert } from './alerts';
 import { openModal } from './modal';
-import { defineMessages } from 'react-intl';
+import { updateTimeline } from './timelines';
 
-let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags;
+/** @type {AbortController | undefined} */
+let fetchComposeSuggestionsAccountsController;
+/** @type {AbortController | undefined} */
+let fetchComposeSuggestionsTagsController;
 
 export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE';
 export const COMPOSE_SUBMIT_REQUEST  = 'COMPOSE_SUBMIT_REQUEST';
@@ -433,8 +435,8 @@ export function undoUploadCompose(media_id) {
 };
 
 export function clearComposeSuggestions() {
-  if (cancelFetchComposeSuggestionsAccounts) {
-    cancelFetchComposeSuggestionsAccounts();
+  if (fetchComposeSuggestionsAccountsController) {
+    fetchComposeSuggestionsAccountsController.abort();
   }
   return {
     type: COMPOSE_SUGGESTIONS_CLEAR,
@@ -442,14 +444,14 @@ export function clearComposeSuggestions() {
 };
 
 const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
-  if (cancelFetchComposeSuggestionsAccounts) {
-    cancelFetchComposeSuggestionsAccounts();
+  if (fetchComposeSuggestionsAccountsController) {
+    fetchComposeSuggestionsAccountsController.abort();
   }
 
+  fetchComposeSuggestionsAccountsController = new AbortController();
+
   api(getState).get('/api/v1/accounts/search', {
-    cancelToken: new CancelToken(cancel => {
-      cancelFetchComposeSuggestionsAccounts = cancel;
-    }),
+    signal: fetchComposeSuggestionsAccountsController.signal,
 
     params: {
       q: token.slice(1),
@@ -472,16 +474,16 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
 };
 
 const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
-  if (cancelFetchComposeSuggestionsTags) {
-    cancelFetchComposeSuggestionsTags();
+  if (fetchComposeSuggestionsTagsController) {
+    fetchComposeSuggestionsTagsController.abort();
   }
 
   dispatch(updateSuggestionTags(token));
 
+  fetchComposeSuggestionsTagsController = new AbortController();
+
   api(getState).get('/api/v2/search', {
-    cancelToken: new CancelToken(cancel => {
-      cancelFetchComposeSuggestionsTags = cancel;
-    }),
+    signal: fetchComposeSuggestionsTagsController.signal,
 
     params: {
       type: 'hashtags',

+ 38 - 15
app/javascript/mastodon/api.js

@@ -1,20 +1,31 @@
+// @ts-check
+
 import axios from 'axios';
 import LinkHeader from 'http-link-header';
 import ready from './ready';
 
+/**
+ * @param {import('axios').AxiosResponse} response
+ * @returns {LinkHeader}
+ */
 export const getLinks = response => {
   const value = response.headers.link;
 
   if (!value) {
-    return { refs: [] };
+    return new LinkHeader();
   }
 
   return LinkHeader.parse(value);
 };
 
+/** @type {import('axios').RawAxiosRequestHeaders} */
 const csrfHeader = {};
 
+/**
+ * @returns {void}
+ */
 const setCSRFHeader = () => {
+  /** @type {HTMLMetaElement | null} */
   const csrfToken = document.querySelector('meta[name=csrf-token]');
 
   if (csrfToken) {
@@ -24,6 +35,10 @@ const setCSRFHeader = () => {
 
 ready(setCSRFHeader);
 
+/**
+ * @param {() => import('immutable').Map} getState
+ * @returns {import('axios').RawAxiosRequestHeaders}
+ */
 const authorizationHeaderFromState = getState => {
   const accessToken = getState && getState().getIn(['meta', 'access_token'], '');
 
@@ -36,17 +51,25 @@ const authorizationHeaderFromState = getState => {
   };
 };
 
-export default getState => axios.create({
-  headers: {
-    ...csrfHeader,
-    ...authorizationHeaderFromState(getState),
-  },
-
-  transformResponse: [function (data) {
-    try {
-      return JSON.parse(data);
-    } catch(Exception) {
-      return data;
-    }
-  }],
-});
+/**
+ * @param {() => import('immutable').Map} getState
+ * @returns {import('axios').AxiosInstance}
+ */
+export default function api(getState) {
+  return axios.create({
+    headers: {
+      ...csrfHeader,
+      ...authorizationHeaderFromState(getState),
+    },
+
+    transformResponse: [
+      function (data) {
+        try {
+          return JSON.parse(data);
+        } catch {
+          return data;
+        }
+      },
+    ],
+  });
+}

+ 1 - 0
app/javascript/mastodon/extra_polyfills.js

@@ -1,3 +1,4 @@
+import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';
 import 'intersection-observer';
 import 'requestidlecallback';
 import objectFitImages from 'object-fit-images';

+ 1 - 0
app/javascript/mastodon/load_polyfills.js

@@ -26,6 +26,7 @@ function loadPolyfills() {
   // Edge does not have requestIdleCallback and object-fit CSS property.
   // This avoids shipping them all the polyfills.
   const needsExtraPolyfills = !(
+    window.AbortController &&
     window.IntersectionObserver &&
     window.IntersectionObserverEntry &&
     'isIntersecting' in IntersectionObserverEntry.prototype &&

+ 22 - 0
jsconfig.json

@@ -0,0 +1,22 @@
+{
+  "compilerOptions": {
+    "allowJs": true,
+    "baseUrl": "./app/javascript",
+    "checkJs": false,
+    "experimentalDecorators": true,
+    "jsx": "react",
+    "lib": ["DOM", "DOM.Iterable", "ES2022"],
+    "module": "ES2022",
+    "moduleResolution": "node",
+    "noEmit": true,
+    "resolveJsonModule": true,
+    "strict": false,
+    "target": "ES2022"
+  },
+  "exclude": [
+    "**/build/*",
+    "**/node_modules/*",
+    "**/public/*",
+    "**/vendor/*"
+  ]
+}

+ 1 - 0
package.json

@@ -34,6 +34,7 @@
     "@gamestdio/websocket": "^0.3.2",
     "@github/webauthn-json": "^0.5.7",
     "@rails/ujs": "^6.1.7",
+    "abortcontroller-polyfill": "^1.7.5",
     "array-includes": "^3.1.5",
     "arrow-key-navigation": "^1.2.0",
     "autoprefixer": "^9.8.8",

+ 5 - 0
yarn.lock

@@ -2203,6 +2203,11 @@ abab@^2.0.6:
   resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
   integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
 
+abortcontroller-polyfill@^1.7.5:
+  version "1.7.5"
+  resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed"
+  integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==
+
 accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
   version "1.3.8"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"