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:
parent
8099238618
commit
ae8e8f055e
17 changed files with 70 additions and 98 deletions
|
@ -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} = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(url, {
|
const headersMerged = new Headers({
|
||||||
headers: {
|
...(!safeMethods.has(method.toUpperCase()) && {'x-csrf-token': csrfToken}),
|
||||||
'x-csrf-token': csrfToken,
|
|
||||||
...(contentType && {'content-type': contentType}),
|
...(contentType && {'content-type': contentType}),
|
||||||
...headers,
|
});
|
||||||
},
|
|
||||||
|
for (const [name, value] of Object.entries(headers)) {
|
||||||
|
headersMerged.set(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: headersMerged,
|
||||||
...(body && {body}),
|
...(body && {body}),
|
||||||
...other,
|
...other,
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue