mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	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.
This commit is contained in:
		@@ -46,6 +46,9 @@ overrides:
 | 
				
			|||||||
  - files: ["*.config.*"]
 | 
					  - files: ["*.config.*"]
 | 
				
			||||||
    rules:
 | 
					    rules:
 | 
				
			||||||
      import/no-unused-modules: [0]
 | 
					      import/no-unused-modules: [0]
 | 
				
			||||||
 | 
					  - files: ["web_src/js/modules/fetch.js", "web_src/js/standalone/**/*"]
 | 
				
			||||||
 | 
					    rules:
 | 
				
			||||||
 | 
					      no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rules:
 | 
					rules:
 | 
				
			||||||
  "@eslint-community/eslint-comments/disable-enable-pair": [2]
 | 
					  "@eslint-community/eslint-comments/disable-enable-pair": [2]
 | 
				
			||||||
@@ -420,7 +423,7 @@ rules:
 | 
				
			|||||||
  no-restricted-exports: [0]
 | 
					  no-restricted-exports: [0]
 | 
				
			||||||
  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]
 | 
					  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]
 | 
				
			||||||
  no-restricted-imports: [0]
 | 
					  no-restricted-imports: [0]
 | 
				
			||||||
  no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression]
 | 
					  no-restricted-syntax: [2, WithStatement, ForInStatement, LabeledStatement, SequenceExpression, {selector: "CallExpression[callee.name='fetch']", message: "use modules/fetch.js instead"}]
 | 
				
			||||||
  no-return-assign: [0]
 | 
					  no-return-assign: [0]
 | 
				
			||||||
  no-script-url: [2]
 | 
					  no-script-url: [2]
 | 
				
			||||||
  no-self-assign: [2, {props: true}]
 | 
					  no-self-assign: [2, {props: true}]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -95,7 +95,7 @@ Some lint rules and IDEs also have warnings if the returned Promise is not handl
 | 
				
			|||||||
### Fetching data
 | 
					### Fetching data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To fetch data, use the wrapper functions `GET`, `POST` etc. from `modules/fetch.js`. They
 | 
					To fetch data, use the wrapper functions `GET`, `POST` etc. from `modules/fetch.js`. They
 | 
				
			||||||
accept a `data` option for the content, will automatically set CSFR token and return a
 | 
					accept a `data` option for the content, will automatically set CSRF token and return a
 | 
				
			||||||
Promise for a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
 | 
					Promise for a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### HTML Attributes and `dataset`
 | 
					### HTML Attributes and `dataset`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
import {createApp, nextTick} from 'vue';
 | 
					import {createApp, nextTick} from 'vue';
 | 
				
			||||||
import $ from 'jquery';
 | 
					import $ from 'jquery';
 | 
				
			||||||
import {SvgIcon} from '../svg.js';
 | 
					import {SvgIcon} from '../svg.js';
 | 
				
			||||||
 | 
					import {GET} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {appSubUrl, assetUrlPrefix, pageData} = window.config;
 | 
					const {appSubUrl, assetUrlPrefix, pageData} = window.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -233,11 +234,11 @@ const sfc = {
 | 
				
			|||||||
      try {
 | 
					      try {
 | 
				
			||||||
        if (!this.reposTotalCount) {
 | 
					        if (!this.reposTotalCount) {
 | 
				
			||||||
          const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
 | 
					          const totalCountSearchURL = `${this.subUrl}/repo/search?count_only=1&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
 | 
				
			||||||
          response = await fetch(totalCountSearchURL);
 | 
					          response = await GET(totalCountSearchURL);
 | 
				
			||||||
          this.reposTotalCount = response.headers.get('X-Total-Count');
 | 
					          this.reposTotalCount = response.headers.get('X-Total-Count');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        response = await fetch(searchedURL);
 | 
					        response = await GET(searchedURL);
 | 
				
			||||||
        json = await response.json();
 | 
					        json = await response.json();
 | 
				
			||||||
      } catch {
 | 
					      } catch {
 | 
				
			||||||
        if (searchedURL === this.searchURL) {
 | 
					        if (searchedURL === this.searchURL) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
<script>
 | 
					<script>
 | 
				
			||||||
import {SvgIcon} from '../svg.js';
 | 
					import {SvgIcon} from '../svg.js';
 | 
				
			||||||
 | 
					import {GET} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  components: {SvgIcon},
 | 
					  components: {SvgIcon},
 | 
				
			||||||
@@ -123,7 +124,7 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    /** Load the commits to show in this dropdown */
 | 
					    /** Load the commits to show in this dropdown */
 | 
				
			||||||
    async fetchCommits() {
 | 
					    async fetchCommits() {
 | 
				
			||||||
      const resp = await fetch(`${this.issueLink}/commits/list`);
 | 
					      const resp = await GET(`${this.issueLink}/commits/list`);
 | 
				
			||||||
      const results = await resp.json();
 | 
					      const results = await resp.json();
 | 
				
			||||||
      this.commits.push(...results.commits.map((x) => {
 | 
					      this.commits.push(...results.commits.map((x) => {
 | 
				
			||||||
        x.hovered = false;
 | 
					        x.hovered = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import $ from 'jquery';
 | 
				
			|||||||
import {SvgIcon} from '../svg.js';
 | 
					import {SvgIcon} from '../svg.js';
 | 
				
			||||||
import {pathEscapeSegments} from '../utils/url.js';
 | 
					import {pathEscapeSegments} from '../utils/url.js';
 | 
				
			||||||
import {showErrorToast} from '../modules/toast.js';
 | 
					import {showErrorToast} from '../modules/toast.js';
 | 
				
			||||||
 | 
					import {GET} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sfc = {
 | 
					const sfc = {
 | 
				
			||||||
  components: {SvgIcon},
 | 
					  components: {SvgIcon},
 | 
				
			||||||
@@ -190,8 +191,7 @@ const sfc = {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      this.isLoading = true;
 | 
					      this.isLoading = true;
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const reqUrl = `${this.repoLink}/${this.mode}/list`;
 | 
					        const resp = await GET(`${this.repoLink}/${this.mode}/list`);
 | 
				
			||||||
        const resp = await fetch(reqUrl);
 | 
					 | 
				
			||||||
        const {results} = await resp.json();
 | 
					        const {results} = await resp.json();
 | 
				
			||||||
        for (const result of results) {
 | 
					        for (const result of results) {
 | 
				
			||||||
          let selected = false;
 | 
					          let selected = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import {htmlEscape} from 'escape-goat';
 | 
				
			|||||||
import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
					import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
				
			||||||
import {confirmModal} from './comp/ConfirmModal.js';
 | 
					import {confirmModal} from './comp/ConfirmModal.js';
 | 
				
			||||||
import {showErrorToast} from '../modules/toast.js';
 | 
					import {showErrorToast} from '../modules/toast.js';
 | 
				
			||||||
 | 
					import {request} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
 | 
					const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,7 +82,7 @@ function fetchActionDoRedirect(redirect) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
async function fetchActionDoRequest(actionElem, url, opt) {
 | 
					async function fetchActionDoRequest(actionElem, url, opt) {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const resp = await fetch(url, opt);
 | 
					    const resp = await request(url, opt);
 | 
				
			||||||
    if (resp.status === 200) {
 | 
					    if (resp.status === 200) {
 | 
				
			||||||
      let {redirect} = await resp.json();
 | 
					      let {redirect} = await resp.json();
 | 
				
			||||||
      redirect = redirect || actionElem.getAttribute('data-redirect');
 | 
					      redirect = redirect || actionElem.getAttribute('data-redirect');
 | 
				
			||||||
@@ -127,7 +128,7 @@ async function formFetchAction(e) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let reqUrl = formActionUrl;
 | 
					  let reqUrl = formActionUrl;
 | 
				
			||||||
  const reqOpt = {method: formMethod.toUpperCase(), headers: {'X-Csrf-Token': csrfToken}};
 | 
					  const reqOpt = {method: formMethod.toUpperCase()};
 | 
				
			||||||
  if (formMethod.toLowerCase() === 'get') {
 | 
					  if (formMethod.toLowerCase() === 'get') {
 | 
				
			||||||
    const params = new URLSearchParams();
 | 
					    const params = new URLSearchParams();
 | 
				
			||||||
    for (const [key, value] of formData) {
 | 
					    for (const [key, value] of formData) {
 | 
				
			||||||
@@ -264,7 +265,7 @@ async function linkAction(e) {
 | 
				
			|||||||
  const url = el.getAttribute('data-url');
 | 
					  const url = el.getAttribute('data-url');
 | 
				
			||||||
  const doRequest = async () => {
 | 
					  const doRequest = async () => {
 | 
				
			||||||
    el.disabled = true;
 | 
					    el.disabled = true;
 | 
				
			||||||
    await fetchActionDoRequest(el, url, {method: 'POST', headers: {'X-Csrf-Token': csrfToken}});
 | 
					    await fetchActionDoRequest(el, url, {method: 'POST'});
 | 
				
			||||||
    el.disabled = false;
 | 
					    el.disabled = false;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
import $ from 'jquery';
 | 
					import $ from 'jquery';
 | 
				
			||||||
import {isElemHidden, onInputDebounce, toggleElem} from '../utils/dom.js';
 | 
					import {isElemHidden, onInputDebounce, toggleElem} from '../utils/dom.js';
 | 
				
			||||||
const {appSubUrl} = window.config;
 | 
					import {GET} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const {appSubUrl} = window.config;
 | 
				
			||||||
const reIssueIndex = /^(\d+)$/; // eg: "123"
 | 
					const reIssueIndex = /^(\d+)$/; // eg: "123"
 | 
				
			||||||
const reIssueSharpIndex = /^#(\d+)$/; // eg: "#123"
 | 
					const reIssueSharpIndex = /^#(\d+)$/; // eg: "#123"
 | 
				
			||||||
const reIssueOwnerRepoIndex = /^([-.\w]+)\/([-.\w]+)#(\d+)$/;  // eg: "{owner}/{repo}#{index}"
 | 
					const reIssueOwnerRepoIndex = /^([-.\w]+)\/([-.\w]+)#(\d+)$/;  // eg: "{owner}/{repo}#{index}"
 | 
				
			||||||
@@ -54,7 +55,7 @@ export function initCommonIssueListQuickGoto() {
 | 
				
			|||||||
    // try to check whether the parsed goto link is valid
 | 
					    // try to check whether the parsed goto link is valid
 | 
				
			||||||
    let targetUrl = parseIssueListQuickGotoLink(repoLink, searchText);
 | 
					    let targetUrl = parseIssueListQuickGotoLink(repoLink, searchText);
 | 
				
			||||||
    if (targetUrl) {
 | 
					    if (targetUrl) {
 | 
				
			||||||
      const res = await fetch(`${targetUrl}/info`);
 | 
					      const res = await GET(`${targetUrl}/info`);
 | 
				
			||||||
      if (res.status !== 200) targetUrl = '';
 | 
					      if (res.status !== 200) targetUrl = '';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,11 @@
 | 
				
			|||||||
import $ from 'jquery';
 | 
					import $ from 'jquery';
 | 
				
			||||||
 | 
					import {POST} from '../../modules/fetch.js';
 | 
				
			||||||
const {csrfToken} = window.config;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function uploadFile(file, uploadUrl) {
 | 
					async function uploadFile(file, uploadUrl) {
 | 
				
			||||||
  const formData = new FormData();
 | 
					  const formData = new FormData();
 | 
				
			||||||
  formData.append('file', file, file.name);
 | 
					  formData.append('file', file, file.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const res = await fetch(uploadUrl, {
 | 
					  const res = await POST(uploadUrl, {data: formData});
 | 
				
			||||||
    method: 'POST',
 | 
					 | 
				
			||||||
    headers: {'X-Csrf-Token': csrfToken},
 | 
					 | 
				
			||||||
    body: formData,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  return await res.json();
 | 
					  return await res.json();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
import $ from 'jquery';
 | 
					import $ from 'jquery';
 | 
				
			||||||
 | 
					import {POST} from '../../modules/fetch.js';
 | 
				
			||||||
const {csrfToken} = window.config;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function initCompReactionSelector($parent) {
 | 
					export function initCompReactionSelector($parent) {
 | 
				
			||||||
  $parent.find(`.select-reaction .item.reaction, .comment-reaction-button`).on('click', async function (e) {
 | 
					  $parent.find(`.select-reaction .item.reaction, .comment-reaction-button`).on('click', async function (e) {
 | 
				
			||||||
@@ -12,15 +11,8 @@ export function initCompReactionSelector($parent) {
 | 
				
			|||||||
    const reactionContent = $(this).attr('data-reaction-content');
 | 
					    const reactionContent = $(this).attr('data-reaction-content');
 | 
				
			||||||
    const hasReacted = $(this).closest('.ui.segment.reactions').find(`a[data-reaction-content="${reactionContent}"]`).attr('data-has-reacted') === 'true';
 | 
					    const hasReacted = $(this).closest('.ui.segment.reactions').find(`a[data-reaction-content="${reactionContent}"]`).attr('data-has-reacted') === 'true';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const res = await fetch(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, {
 | 
					    const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, {
 | 
				
			||||||
      method: 'POST',
 | 
					      data: new URLSearchParams({content: reactionContent}),
 | 
				
			||||||
      headers: {
 | 
					 | 
				
			||||||
        'content-type': 'application/x-www-form-urlencoded',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      body: new URLSearchParams({
 | 
					 | 
				
			||||||
        _csrf: csrfToken,
 | 
					 | 
				
			||||||
        content: reactionContent,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const data = await res.json();
 | 
					    const data = await res.json();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import {clippie} from 'clippie';
 | 
					import {clippie} from 'clippie';
 | 
				
			||||||
import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
					import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
				
			||||||
import {convertImage} from '../utils.js';
 | 
					import {convertImage} from '../utils.js';
 | 
				
			||||||
 | 
					import {GET} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {i18n} = window.config;
 | 
					const {i18n} = window.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,7 +21,7 @@ export function initCopyContent() {
 | 
				
			|||||||
    if (link) {
 | 
					    if (link) {
 | 
				
			||||||
      btn.classList.add('is-loading', 'small-loading-icon');
 | 
					      btn.classList.add('is-loading', 'small-loading-icon');
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const res = await fetch(link, {credentials: 'include', redirect: 'follow'});
 | 
					        const res = await GET(link, {credentials: 'include', redirect: 'follow'});
 | 
				
			||||||
        const contentType = res.headers.get('content-type');
 | 
					        const contentType = res.headers.get('content-type');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (contentType.startsWith('image/') && !contentType.startsWith('image/svg')) {
 | 
					        if (contentType.startsWith('image/') && !contentType.startsWith('image/svg')) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import $ from 'jquery';
 | 
					import $ from 'jquery';
 | 
				
			||||||
import {hideElem, showElem} from '../utils/dom.js';
 | 
					import {hideElem, showElem} from '../utils/dom.js';
 | 
				
			||||||
 | 
					import {GET} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function initInstall() {
 | 
					export function initInstall() {
 | 
				
			||||||
  const $page = $('.page-content.install');
 | 
					  const $page = $('.page-content.install');
 | 
				
			||||||
@@ -111,7 +112,7 @@ function initPostInstall() {
 | 
				
			|||||||
  const targetUrl = el.getAttribute('href');
 | 
					  const targetUrl = el.getAttribute('href');
 | 
				
			||||||
  let tid = setInterval(async () => {
 | 
					  let tid = setInterval(async () => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const resp = await fetch(targetUrl);
 | 
					      const resp = await GET(targetUrl);
 | 
				
			||||||
      if (tid && resp.status === 200) {
 | 
					      if (tid && resp.status === 200) {
 | 
				
			||||||
        clearInterval(tid);
 | 
					        clearInterval(tid);
 | 
				
			||||||
        tid = null;
 | 
					        tid = null;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
import {diffTreeStore} from '../modules/stores.js';
 | 
					import {diffTreeStore} from '../modules/stores.js';
 | 
				
			||||||
import {setFileFolding} from './file-fold.js';
 | 
					import {setFileFolding} from './file-fold.js';
 | 
				
			||||||
 | 
					import {POST} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {csrfToken, pageData} = window.config;
 | 
					const {pageData} = window.config;
 | 
				
			||||||
const prReview = pageData.prReview || {};
 | 
					const prReview = pageData.prReview || {};
 | 
				
			||||||
const viewedStyleClass = 'viewed-file-checked-form';
 | 
					const viewedStyleClass = 'viewed-file-checked-form';
 | 
				
			||||||
const viewedCheckboxSelector = '.viewed-file-form'; // Selector under which all "Viewed" checkbox forms can be found
 | 
					const viewedCheckboxSelector = '.viewed-file-form'; // Selector under which all "Viewed" checkbox forms can be found
 | 
				
			||||||
@@ -68,11 +69,7 @@ export function initViewedCheckboxListenerFor() {
 | 
				
			|||||||
      const data = {files};
 | 
					      const data = {files};
 | 
				
			||||||
      const headCommitSHA = form.getAttribute('data-headcommit');
 | 
					      const headCommitSHA = form.getAttribute('data-headcommit');
 | 
				
			||||||
      if (headCommitSHA) data.headCommitSHA = headCommitSHA;
 | 
					      if (headCommitSHA) data.headCommitSHA = headCommitSHA;
 | 
				
			||||||
      fetch(form.getAttribute('data-link'), {
 | 
					      POST(form.getAttribute('data-link'), {data});
 | 
				
			||||||
        method: 'POST',
 | 
					 | 
				
			||||||
        headers: {'X-Csrf-Token': csrfToken},
 | 
					 | 
				
			||||||
        body: JSON.stringify(data),
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Fold the file accordingly
 | 
					      // Fold the file accordingly
 | 
				
			||||||
      const parentBox = form.closest('.diff-file-header');
 | 
					      const parentBox = form.closest('.diff-file-header');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
import {hideElem, showElem, toggleElem} from '../utils/dom.js';
 | 
					import {hideElem, showElem, toggleElem} from '../utils/dom.js';
 | 
				
			||||||
 | 
					import {GET} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function loadBranchesAndTags(area, loadingButton) {
 | 
					async function loadBranchesAndTags(area, loadingButton) {
 | 
				
			||||||
  loadingButton.classList.add('disabled');
 | 
					  loadingButton.classList.add('disabled');
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const res = await fetch(loadingButton.getAttribute('data-fetch-url'));
 | 
					    const res = await GET(loadingButton.getAttribute('data-fetch-url'));
 | 
				
			||||||
    const data = await res.json();
 | 
					    const data = await res.json();
 | 
				
			||||||
    hideElem(loadingButton);
 | 
					    hideElem(loadingButton);
 | 
				
			||||||
    addTags(area, data.tags);
 | 
					    addTags(area, data.tags);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import {htmlEscape} from 'escape-goat';
 | 
				
			|||||||
import {confirmModal} from './comp/ConfirmModal.js';
 | 
					import {confirmModal} from './comp/ConfirmModal.js';
 | 
				
			||||||
import {showErrorToast} from '../modules/toast.js';
 | 
					import {showErrorToast} from '../modules/toast.js';
 | 
				
			||||||
import {createSortable} from '../modules/sortable.js';
 | 
					import {createSortable} from '../modules/sortable.js';
 | 
				
			||||||
 | 
					import {DELETE, POST} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initRepoIssueListCheckboxes() {
 | 
					function initRepoIssueListCheckboxes() {
 | 
				
			||||||
  const $issueSelectAll = $('.issue-checkbox-all');
 | 
					  const $issueSelectAll = $('.issue-checkbox-all');
 | 
				
			||||||
@@ -146,13 +147,7 @@ function initPinRemoveButton() {
 | 
				
			|||||||
      const id = Number(el.getAttribute('data-issue-id'));
 | 
					      const id = Number(el.getAttribute('data-issue-id'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Send the unpin request
 | 
					      // Send the unpin request
 | 
				
			||||||
      const response = await fetch(el.getAttribute('data-unpin-url'), {
 | 
					      const response = await DELETE(el.getAttribute('data-unpin-url'));
 | 
				
			||||||
        method: 'delete',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          'X-Csrf-Token': window.config.csrfToken,
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      if (response.ok) {
 | 
					      if (response.ok) {
 | 
				
			||||||
        // Delete the tooltip
 | 
					        // Delete the tooltip
 | 
				
			||||||
        el._tippy.destroy();
 | 
					        el._tippy.destroy();
 | 
				
			||||||
@@ -166,14 +161,7 @@ function initPinRemoveButton() {
 | 
				
			|||||||
async function pinMoveEnd(e) {
 | 
					async function pinMoveEnd(e) {
 | 
				
			||||||
  const url = e.item.getAttribute('data-move-url');
 | 
					  const url = e.item.getAttribute('data-move-url');
 | 
				
			||||||
  const id = Number(e.item.getAttribute('data-issue-id'));
 | 
					  const id = Number(e.item.getAttribute('data-issue-id'));
 | 
				
			||||||
  await fetch(url, {
 | 
					  await POST(url, {data: {id, position: e.newIndex + 1}});
 | 
				
			||||||
    method: 'post',
 | 
					 | 
				
			||||||
    body: JSON.stringify({id, position: e.newIndex + 1}),
 | 
					 | 
				
			||||||
    headers: {
 | 
					 | 
				
			||||||
      'X-Csrf-Token': window.config.csrfToken,
 | 
					 | 
				
			||||||
      'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function initIssuePinSort() {
 | 
					async function initIssuePinSort() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
import $ from 'jquery';
 | 
					import $ from 'jquery';
 | 
				
			||||||
import {hideElem, showElem} from '../utils/dom.js';
 | 
					import {hideElem, showElem} from '../utils/dom.js';
 | 
				
			||||||
 | 
					import {GET, POST} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {appSubUrl, csrfToken} = window.config;
 | 
					const {appSubUrl} = window.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function initRepoMigrationStatusChecker() {
 | 
					export function initRepoMigrationStatusChecker() {
 | 
				
			||||||
  const $repoMigrating = $('#repo_migrating');
 | 
					  const $repoMigrating = $('#repo_migrating');
 | 
				
			||||||
@@ -13,7 +14,7 @@ export function initRepoMigrationStatusChecker() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // returns true if the refresh still need to be called after a while
 | 
					  // returns true if the refresh still need to be called after a while
 | 
				
			||||||
  const refresh = async () => {
 | 
					  const refresh = async () => {
 | 
				
			||||||
    const res = await fetch(`${appSubUrl}/user/task/${task}`);
 | 
					    const res = await GET(`${appSubUrl}/user/task/${task}`);
 | 
				
			||||||
    if (res.status !== 200) return true; // continue to refresh if network error occurs
 | 
					    if (res.status !== 200) return true; // continue to refresh if network error occurs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const data = await res.json();
 | 
					    const data = await res.json();
 | 
				
			||||||
@@ -58,12 +59,6 @@ export function initRepoMigrationStatusChecker() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function doMigrationRetry(e) {
 | 
					async function doMigrationRetry(e) {
 | 
				
			||||||
  await fetch($(e.target).attr('data-migrating-task-retry-url'), {
 | 
					  await POST($(e.target).attr('data-migrating-task-retry-url'));
 | 
				
			||||||
    method: 'post',
 | 
					 | 
				
			||||||
    headers: {
 | 
					 | 
				
			||||||
      'X-Csrf-Token': csrfToken,
 | 
					 | 
				
			||||||
      'Content-Type': 'application/json',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  window.location.reload();
 | 
					  window.location.reload();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.js';
 | 
					import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.js';
 | 
				
			||||||
import {showElem} from '../utils/dom.js';
 | 
					import {showElem} from '../utils/dom.js';
 | 
				
			||||||
 | 
					import {GET, POST} from '../modules/fetch.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {appSubUrl, csrfToken} = window.config;
 | 
					const {appSubUrl} = window.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function initUserAuthWebAuthn() {
 | 
					export async function initUserAuthWebAuthn() {
 | 
				
			||||||
  const elPrompt = document.querySelector('.user.signin.webauthn-prompt');
 | 
					  const elPrompt = document.querySelector('.user.signin.webauthn-prompt');
 | 
				
			||||||
@@ -13,7 +14,7 @@ export async function initUserAuthWebAuthn() {
 | 
				
			|||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const res = await fetch(`${appSubUrl}/user/webauthn/assertion`);
 | 
					  const res = await GET(`${appSubUrl}/user/webauthn/assertion`);
 | 
				
			||||||
  if (res.status !== 200) {
 | 
					  if (res.status !== 200) {
 | 
				
			||||||
    webAuthnError('unknown');
 | 
					    webAuthnError('unknown');
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
@@ -53,12 +54,8 @@ async function verifyAssertion(assertedCredential) {
 | 
				
			|||||||
  const sig = new Uint8Array(assertedCredential.response.signature);
 | 
					  const sig = new Uint8Array(assertedCredential.response.signature);
 | 
				
			||||||
  const userHandle = new Uint8Array(assertedCredential.response.userHandle);
 | 
					  const userHandle = new Uint8Array(assertedCredential.response.userHandle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const res = await fetch(`${appSubUrl}/user/webauthn/assertion`, {
 | 
					  const res = await POST(`${appSubUrl}/user/webauthn/assertion`, {
 | 
				
			||||||
    method: 'POST',
 | 
					    data: {
 | 
				
			||||||
    headers: {
 | 
					 | 
				
			||||||
      'Content-Type': 'application/json; charset=utf-8'
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    body: JSON.stringify({
 | 
					 | 
				
			||||||
      id: assertedCredential.id,
 | 
					      id: assertedCredential.id,
 | 
				
			||||||
      rawId: encodeURLEncodedBase64(rawId),
 | 
					      rawId: encodeURLEncodedBase64(rawId),
 | 
				
			||||||
      type: assertedCredential.type,
 | 
					      type: assertedCredential.type,
 | 
				
			||||||
@@ -69,7 +66,7 @@ async function verifyAssertion(assertedCredential) {
 | 
				
			|||||||
        signature: encodeURLEncodedBase64(sig),
 | 
					        signature: encodeURLEncodedBase64(sig),
 | 
				
			||||||
        userHandle: encodeURLEncodedBase64(userHandle),
 | 
					        userHandle: encodeURLEncodedBase64(userHandle),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }),
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  if (res.status === 500) {
 | 
					  if (res.status === 500) {
 | 
				
			||||||
    webAuthnError('unknown');
 | 
					    webAuthnError('unknown');
 | 
				
			||||||
@@ -88,13 +85,8 @@ async function webauthnRegistered(newCredential) {
 | 
				
			|||||||
  const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
 | 
					  const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
 | 
				
			||||||
  const rawId = new Uint8Array(newCredential.rawId);
 | 
					  const rawId = new Uint8Array(newCredential.rawId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const res = await fetch(`${appSubUrl}/user/settings/security/webauthn/register`, {
 | 
					  const res = await POST(`${appSubUrl}/user/settings/security/webauthn/register`, {
 | 
				
			||||||
    method: 'POST',
 | 
					    data: {
 | 
				
			||||||
    headers: {
 | 
					 | 
				
			||||||
      'X-Csrf-Token': csrfToken,
 | 
					 | 
				
			||||||
      'Content-Type': 'application/json; charset=utf-8',
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    body: JSON.stringify({
 | 
					 | 
				
			||||||
      id: newCredential.id,
 | 
					      id: newCredential.id,
 | 
				
			||||||
      rawId: encodeURLEncodedBase64(rawId),
 | 
					      rawId: encodeURLEncodedBase64(rawId),
 | 
				
			||||||
      type: newCredential.type,
 | 
					      type: newCredential.type,
 | 
				
			||||||
@@ -102,7 +94,7 @@ async function webauthnRegistered(newCredential) {
 | 
				
			|||||||
        attestationObject: encodeURLEncodedBase64(attestationObject),
 | 
					        attestationObject: encodeURLEncodedBase64(attestationObject),
 | 
				
			||||||
        clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
 | 
					        clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }),
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (res.status === 409) {
 | 
					  if (res.status === 409) {
 | 
				
			||||||
@@ -165,15 +157,11 @@ export function initUserAuthWebAuthnRegister() {
 | 
				
			|||||||
async function webAuthnRegisterRequest() {
 | 
					async function webAuthnRegisterRequest() {
 | 
				
			||||||
  const elNickname = document.getElementById('nickname');
 | 
					  const elNickname = document.getElementById('nickname');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const body = new FormData();
 | 
					  const formData = new FormData();
 | 
				
			||||||
  body.append('name', elNickname.value);
 | 
					  formData.append('name', elNickname.value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const res = await fetch(`${appSubUrl}/user/settings/security/webauthn/request_register`, {
 | 
					  const res = await POST(`${appSubUrl}/user/settings/security/webauthn/request_register`, {
 | 
				
			||||||
    method: 'POST',
 | 
					    data: formData,
 | 
				
			||||||
    headers: {
 | 
					 | 
				
			||||||
      'X-Csrf-Token': csrfToken,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    body,
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (res.status === 409) {
 | 
					  if (res.status === 409) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,17 +2,18 @@ import {isObject} from '../utils.js';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const {csrfToken} = window.config;
 | 
					const {csrfToken} = window.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// safe HTTP methods that don't need a csrf token
 | 
				
			||||||
 | 
					const safeMethods = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// fetch wrapper, use below method name functions and the `data` option to pass in data
 | 
					// fetch wrapper, use below method name functions and the `data` option to pass in data
 | 
				
			||||||
// which will automatically set an appropriate content-type header. For json content,
 | 
					// which will automatically set an appropriate headers. For json content, only object
 | 
				
			||||||
// only object and array types are currently supported.
 | 
					// and array types are currently supported.
 | 
				
			||||||
function request(url, {headers, data, body, ...other} = {}) {
 | 
					export function request(url, {method = 'GET', headers = {}, data, body, ...other} = {}) {
 | 
				
			||||||
  let contentType;
 | 
					  let contentType;
 | 
				
			||||||
  if (!body) {
 | 
					  if (!body) {
 | 
				
			||||||
    if (data instanceof FormData) {
 | 
					    if (data instanceof FormData) {
 | 
				
			||||||
      contentType = 'multipart/form-data';
 | 
					 | 
				
			||||||
      body = data;
 | 
					      body = data;
 | 
				
			||||||
    } else if (data instanceof URLSearchParams) {
 | 
					    } else if (data instanceof URLSearchParams) {
 | 
				
			||||||
      contentType = 'application/x-www-form-urlencoded';
 | 
					 | 
				
			||||||
      body = data;
 | 
					      body = data;
 | 
				
			||||||
    } else if (isObject(data) || Array.isArray(data)) {
 | 
					    } else if (isObject(data) || Array.isArray(data)) {
 | 
				
			||||||
      contentType = 'application/json';
 | 
					      contentType = 'application/json';
 | 
				
			||||||
@@ -20,12 +21,18 @@ function request(url, {headers, data, body, ...other} = {}) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const headersMerged = new Headers({
 | 
				
			||||||
 | 
					    ...(!safeMethods.has(method.toUpperCase()) && {'x-csrf-token': csrfToken}),
 | 
				
			||||||
 | 
					    ...(contentType && {'content-type': contentType}),
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const [name, value] of Object.entries(headers)) {
 | 
				
			||||||
 | 
					    headersMerged.set(name, value);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return fetch(url, {
 | 
					  return fetch(url, {
 | 
				
			||||||
    headers: {
 | 
					    method,
 | 
				
			||||||
      'x-csrf-token': csrfToken,
 | 
					    headers: headersMerged,
 | 
				
			||||||
      ...(contentType && {'content-type': contentType}),
 | 
					 | 
				
			||||||
      ...headers,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    ...(body && {body}),
 | 
					    ...(body && {body}),
 | 
				
			||||||
    ...other,
 | 
					    ...other,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user