Skip to content

Commit ae8e8f0

Browse files
authored
Use fetch helpers instead of fetch (#27026)
WIP because: - [x] Some calls set a `content-type` but send no body, can likely remove the header - [x] Need to check whether `charset=utf-8` has any significance on the webauthn calls, I assume not as it is the default for json content. - [x] Maybe `no-restricted-globals` is better for eslint, but will require a lot of duplication in the yaml or moving eslint config to a `.js` extension. - [x] Maybe export `request` as `fetch`, shadowing the global.
1 parent 8099238 commit ae8e8f0

17 files changed

+70
-98
lines changed

.eslintrc.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ overrides:
4646
- files: ["*.config.*"]
4747
rules:
4848
import/no-unused-modules: [0]
49+
- files: ["web_src/js/modules/fetch.js", "web_src/js/standalone/**/*"]
50+
rules:
51+
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
4952

5053
rules:
5154
"@eslint-community/eslint-comments/disable-enable-pair": [2]
@@ -420,7 +423,7 @@ rules:
420423
no-restricted-exports: [0]
421424
no-restricted-globals: [2, addEventListener, blur, close, closed, confirm, defaultStatus, defaultstatus, error, event, external, find, focus, frameElement, frames, history, innerHeight, innerWidth, isFinite, isNaN, length, location, locationbar, menubar, moveBy, moveTo, name, onblur, onerror, onfocus, onload, onresize, onunload, open, opener, opera, outerHeight, outerWidth, pageXOffset, pageYOffset, parent, print, removeEventListener, resizeBy, resizeTo, screen, screenLeft, screenTop, screenX, screenY, scroll, scrollbars, scrollBy, scrollTo, scrollX, scrollY, self, status, statusbar, stop, toolbar, top, __dirname, __filename]
422425
no-restricted-imports: [0]
423-
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
426+
no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression, {selector: "CallExpression[callee.name='fetch']", message: "use modules/fetch.js instead"}]
424427
no-return-assign: [0]
425428
no-script-url: [2]
426429
no-self-assign: [2, {props: true}]

docs/content/contributing/guidelines-frontend.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Some lint rules and IDEs also have warnings if the returned Promise is not handl
9595
### Fetching data
9696

9797
To fetch data, use the wrapper functions `GET`, `POST` etc. from `modules/fetch.js`. They
98-
accept a `data` option for the content, will automatically set CSFR token and return a
98+
accept a `data` option for the content, will automatically set CSRF token and return a
9999
Promise for a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
100100

101101
### HTML Attributes and `dataset`

web_src/js/components/DashboardRepoList.vue

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import {createApp, nextTick} from 'vue';
33
import $ from 'jquery';
44
import {SvgIcon} from '../svg.js';
5+
import {GET} from '../modules/fetch.js';
56
67
const {appSubUrl, assetUrlPrefix, pageData} = window.config;
78
@@ -233,11 +234,11 @@ const sfc = {
233234
try {
234235
if (!this.reposTotalCount) {
235236
const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
236-
response = await fetch(totalCountSearchURL);
237+
response = await GET(totalCountSearchURL);
237238
this.reposTotalCount = response.headers.get('X-Total-Count');
238239
}
239240
240-
response = await fetch(searchedURL);
241+
response = await GET(searchedURL);
241242
json = await response.json();
242243
} catch {
243244
if (searchedURL === this.searchURL) {

web_src/js/components/DiffCommitSelector.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script>
22
import {SvgIcon} from '../svg.js';
3+
import {GET} from '../modules/fetch.js';
34
45
export default {
56
components: {SvgIcon},
@@ -123,7 +124,7 @@ export default {
123124
},
124125
/** Load the commits to show in this dropdown */
125126
async fetchCommits() {
126-
const resp = await fetch(`${this.issueLink}/commits/list`);
127+
const resp = await GET(`${this.issueLink}/commits/list`);
127128
const results = await resp.json();
128129
this.commits.push(...results.commits.map((x) => {
129130
x.hovered = false;

web_src/js/components/RepoBranchTagSelector.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import $ from 'jquery';
44
import {SvgIcon} from '../svg.js';
55
import {pathEscapeSegments} from '../utils/url.js';
66
import {showErrorToast} from '../modules/toast.js';
7+
import {GET} from '../modules/fetch.js';
78
89
const sfc = {
910
components: {SvgIcon},
@@ -190,8 +191,7 @@ const sfc = {
190191
}
191192
this.isLoading = true;
192193
try {
193-
const reqUrl = `${this.repoLink}/${this.mode}/list`;
194-
const resp = await fetch(reqUrl);
194+
const resp = await GET(`${this.repoLink}/${this.mode}/list`);
195195
const {results} = await resp.json();
196196
for (const result of results) {
197197
let selected = false;

web_src/js/features/common-global.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {htmlEscape} from 'escape-goat';
1111
import {showTemporaryTooltip} from '../modules/tippy.js';
1212
import {confirmModal} from './comp/ConfirmModal.js';
1313
import {showErrorToast} from '../modules/toast.js';
14+
import {request} from '../modules/fetch.js';
1415

1516
const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
1617

@@ -81,7 +82,7 @@ function fetchActionDoRedirect(redirect) {
8182

8283
async function fetchActionDoRequest(actionElem, url, opt) {
8384
try {
84-
const resp = await fetch(url, opt);
85+
const resp = await request(url, opt);
8586
if (resp.status === 200) {
8687
let {redirect} = await resp.json();
8788
redirect = redirect || actionElem.getAttribute('data-redirect');
@@ -127,7 +128,7 @@ async function formFetchAction(e) {
127128
}
128129

129130
let reqUrl = formActionUrl;
130-
const reqOpt = {method: formMethod.toUpperCase(), headers: {'X-Csrf-Token': csrfToken}};
131+
const reqOpt = {method: formMethod.toUpperCase()};
131132
if (formMethod.toLowerCase() === 'get') {
132133
const params = new URLSearchParams();
133134
for (const [key, value] of formData) {
@@ -264,7 +265,7 @@ async function linkAction(e) {
264265
const url = el.getAttribute('data-url');
265266
const doRequest = async () => {
266267
el.disabled = true;
267-
await fetchActionDoRequest(el, url, {method: 'POST', headers: {'X-Csrf-Token': csrfToken}});
268+
await fetchActionDoRequest(el, url, {method: 'POST'});
268269
el.disabled = false;
269270
};
270271

web_src/js/features/common-issue-list.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import $ from 'jquery';
22
import {isElemHidden, onInputDebounce, toggleElem} from '../utils/dom.js';
3-
const {appSubUrl} = window.config;
3+
import {GET} from '../modules/fetch.js';
44

5+
const {appSubUrl} = window.config;
56
const reIssueIndex = /^(\d+)$/; // eg: "123"
67
const reIssueSharpIndex = /^#(\d+)$/; // eg: "#123"
78
const reIssueOwnerRepoIndex = /^([-.\w]+)\/([-.\w]+)#(\d+)$/; // eg: "{owner}/{repo}#{index}"
@@ -54,7 +55,7 @@ export function initCommonIssueListQuickGoto() {
5455
// try to check whether the parsed goto link is valid
5556
let targetUrl = parseIssueListQuickGotoLink(repoLink, searchText);
5657
if (targetUrl) {
57-
const res = await fetch(`${targetUrl}/info`);
58+
const res = await GET(`${targetUrl}/info`);
5859
if (res.status !== 200) targetUrl = '';
5960
}
6061

web_src/js/features/comp/ImagePaste.js

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import $ from 'jquery';
2-
3-
const {csrfToken} = window.config;
2+
import {POST} from '../../modules/fetch.js';
43

54
async function uploadFile(file, uploadUrl) {
65
const formData = new FormData();
76
formData.append('file', file, file.name);
87

9-
const res = await fetch(uploadUrl, {
10-
method: 'POST',
11-
headers: {'X-Csrf-Token': csrfToken},
12-
body: formData,
13-
});
8+
const res = await POST(uploadUrl, {data: formData});
149
return await res.json();
1510
}
1611

web_src/js/features/comp/ReactionSelector.js

+3-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import $ from 'jquery';
2-
3-
const {csrfToken} = window.config;
2+
import {POST} from '../../modules/fetch.js';
43

54
export function initCompReactionSelector($parent) {
65
$parent.find(`.select-reaction .item.reaction, .comment-reaction-button`).on('click', async function (e) {
@@ -12,15 +11,8 @@ export function initCompReactionSelector($parent) {
1211
const reactionContent = $(this).attr('data-reaction-content');
1312
const hasReacted = $(this).closest('.ui.segment.reactions').find(`a[data-reaction-content="${reactionContent}"]`).attr('data-has-reacted') === 'true';
1413

15-
const res = await fetch(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, {
16-
method: 'POST',
17-
headers: {
18-
'content-type': 'application/x-www-form-urlencoded',
19-
},
20-
body: new URLSearchParams({
21-
_csrf: csrfToken,
22-
content: reactionContent,
23-
}),
14+
const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, {
15+
data: new URLSearchParams({content: reactionContent}),
2416
});
2517

2618
const data = await res.json();

web_src/js/features/copycontent.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {clippie} from 'clippie';
22
import {showTemporaryTooltip} from '../modules/tippy.js';
33
import {convertImage} from '../utils.js';
4+
import {GET} from '../modules/fetch.js';
45

56
const {i18n} = window.config;
67

@@ -20,7 +21,7 @@ export function initCopyContent() {
2021
if (link) {
2122
btn.classList.add('is-loading', 'small-loading-icon');
2223
try {
23-
const res = await fetch(link, {credentials: 'include', redirect: 'follow'});
24+
const res = await GET(link, {credentials: 'include', redirect: 'follow'});
2425
const contentType = res.headers.get('content-type');
2526

2627
if (contentType.startsWith('image/') && !contentType.startsWith('image/svg')) {

web_src/js/features/install.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import $ from 'jquery';
22
import {hideElem, showElem} from '../utils/dom.js';
3+
import {GET} from '../modules/fetch.js';
34

45
export function initInstall() {
56
const $page = $('.page-content.install');
@@ -111,7 +112,7 @@ function initPostInstall() {
111112
const targetUrl = el.getAttribute('href');
112113
let tid = setInterval(async () => {
113114
try {
114-
const resp = await fetch(targetUrl);
115+
const resp = await GET(targetUrl);
115116
if (tid && resp.status === 200) {
116117
clearInterval(tid);
117118
tid = null;

web_src/js/features/pull-view-file.js

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {diffTreeStore} from '../modules/stores.js';
22
import {setFileFolding} from './file-fold.js';
3+
import {POST} from '../modules/fetch.js';
34

4-
const {csrfToken, pageData} = window.config;
5+
const {pageData} = window.config;
56
const prReview = pageData.prReview || {};
67
const viewedStyleClass = 'viewed-file-checked-form';
78
const viewedCheckboxSelector = '.viewed-file-form'; // Selector under which all "Viewed" checkbox forms can be found
@@ -68,11 +69,7 @@ export function initViewedCheckboxListenerFor() {
6869
const data = {files};
6970
const headCommitSHA = form.getAttribute('data-headcommit');
7071
if (headCommitSHA) data.headCommitSHA = headCommitSHA;
71-
fetch(form.getAttribute('data-link'), {
72-
method: 'POST',
73-
headers: {'X-Csrf-Token': csrfToken},
74-
body: JSON.stringify(data),
75-
});
72+
POST(form.getAttribute('data-link'), {data});
7673

7774
// Fold the file accordingly
7875
const parentBox = form.closest('.diff-file-header');

web_src/js/features/repo-diff-commit.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {hideElem, showElem, toggleElem} from '../utils/dom.js';
2+
import {GET} from '../modules/fetch.js';
23

34
async function loadBranchesAndTags(area, loadingButton) {
45
loadingButton.classList.add('disabled');
56
try {
6-
const res = await fetch(loadingButton.getAttribute('data-fetch-url'));
7+
const res = await GET(loadingButton.getAttribute('data-fetch-url'));
78
const data = await res.json();
89
hideElem(loadingButton);
910
addTags(area, data.tags);

web_src/js/features/repo-issue-list.js

+3-15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {htmlEscape} from 'escape-goat';
55
import {confirmModal} from './comp/ConfirmModal.js';
66
import {showErrorToast} from '../modules/toast.js';
77
import {createSortable} from '../modules/sortable.js';
8+
import {DELETE, POST} from '../modules/fetch.js';
89

910
function initRepoIssueListCheckboxes() {
1011
const $issueSelectAll = $('.issue-checkbox-all');
@@ -146,13 +147,7 @@ function initPinRemoveButton() {
146147
const id = Number(el.getAttribute('data-issue-id'));
147148

148149
// Send the unpin request
149-
const response = await fetch(el.getAttribute('data-unpin-url'), {
150-
method: 'delete',
151-
headers: {
152-
'X-Csrf-Token': window.config.csrfToken,
153-
'Content-Type': 'application/json',
154-
},
155-
});
150+
const response = await DELETE(el.getAttribute('data-unpin-url'));
156151
if (response.ok) {
157152
// Delete the tooltip
158153
el._tippy.destroy();
@@ -166,14 +161,7 @@ function initPinRemoveButton() {
166161
async function pinMoveEnd(e) {
167162
const url = e.item.getAttribute('data-move-url');
168163
const id = Number(e.item.getAttribute('data-issue-id'));
169-
await fetch(url, {
170-
method: 'post',
171-
body: JSON.stringify({id, position: e.newIndex + 1}),
172-
headers: {
173-
'X-Csrf-Token': window.config.csrfToken,
174-
'Content-Type': 'application/json',
175-
},
176-
});
164+
await POST(url, {data: {id, position: e.newIndex + 1}});
177165
}
178166

179167
async function initIssuePinSort() {

web_src/js/features/repo-migrate.js

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import $ from 'jquery';
22
import {hideElem, showElem} from '../utils/dom.js';
3+
import {GET, POST} from '../modules/fetch.js';
34

4-
const {appSubUrl, csrfToken} = window.config;
5+
const {appSubUrl} = window.config;
56

67
export function initRepoMigrationStatusChecker() {
78
const $repoMigrating = $('#repo_migrating');
@@ -13,7 +14,7 @@ export function initRepoMigrationStatusChecker() {
1314

1415
// returns true if the refresh still need to be called after a while
1516
const refresh = async () => {
16-
const res = await fetch(`${appSubUrl}/user/task/${task}`);
17+
const res = await GET(`${appSubUrl}/user/task/${task}`);
1718
if (res.status !== 200) return true; // continue to refresh if network error occurs
1819

1920
const data = await res.json();
@@ -58,12 +59,6 @@ export function initRepoMigrationStatusChecker() {
5859
}
5960

6061
async function doMigrationRetry(e) {
61-
await fetch($(e.target).attr('data-migrating-task-retry-url'), {
62-
method: 'post',
63-
headers: {
64-
'X-Csrf-Token': csrfToken,
65-
'Content-Type': 'application/json',
66-
},
67-
});
62+
await POST($(e.target).attr('data-migrating-task-retry-url'));
6863
window.location.reload();
6964
}

0 commit comments

Comments
 (0)