Stackedit/src/services/providers/helpers/giteeHelper.js
2022-10-29 15:46:57 +08:00

439 lines
11 KiB
JavaScript

import utils from '../../utils';
import networkSvc from '../../networkSvc';
import store from '../../../store';
import userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc';
import constants from '../../../data/constants';
const tokenExpirationMargin = 5 * 60 * 1000;
const appDataRepo = 'stackedit-app-data';
const request = (token, options) => networkSvc.request({
...options,
headers: {
...options.headers || {},
},
params: {
...options.params || {},
t: Date.now(), // Prevent from caching
access_token: token.accessToken,
},
});
const repoRequest = (token, owner, repo, options) => request(token, {
...options,
url: `https://gitee.com/api/v5/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${options.url}`,
})
.then(res => res.body);
const getCommitMessage = (name, path) => {
const message = store.getters['data/computedSettings'].git[name];
return message.replace(/{{path}}/g, path);
};
/**
* Getting a user from its userId is not feasible with API v3.
* Using an undocumented endpoint...
*/
const subPrefix = 'ge';
userSvc.setInfoResolver('gitee', subPrefix, async (sub) => {
try {
const user = (await networkSvc.request({
url: `https://gitee.com/api/v5/users/${sub}`,
params: {
t: Date.now(), // Prevent from caching
},
})).body;
if (user.avatar_url && user.avatar_url.endsWith('.png') && !user.avatar_url.endsWith('no_portrait.png')) {
user.avatar_url = `${user.avatar_url}!avatar60`;
}
return {
id: `${subPrefix}:${user.login}`,
name: user.login,
imageUrl: user.avatar_url || '',
};
} catch (err) {
if (err.status !== 404) {
throw new Error('RETRY');
}
throw err;
}
});
export default {
subPrefix,
/**
* https://developer.gitee.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/
*/
async startOauth2(lastToken, silent = false, isMain, randomClientId) {
let tokenBody;
if (!silent) {
const clientId = (await networkSvc.request({
method: 'GET',
url: 'giteeClientId',
params: { random: randomClientId },
})).body;
// Get an OAuth2 code
const { code } = await networkSvc.startOauth2(
'https://gitee.com/oauth/authorize',
{
client_id: clientId,
scope: 'projects pull_requests',
response_type: 'code',
},
silent,
);
// Exchange code with token
tokenBody = (await networkSvc.request({
method: 'GET',
url: 'oauth2/giteeToken',
params: {
clientId,
code,
oauth2RedirectUri: constants.oauth2RedirectUri,
},
})).body;
} else {
// grant_type=refresh_token&refresh_token={refresh_token}
tokenBody = (await networkSvc.request({
method: 'POST',
url: 'https://gitee.com/oauth/token',
params: {
grant_type: 'refresh_token',
refresh_token: lastToken.refreshToken,
},
})).body;
}
const accessToken = tokenBody.access_token;
// Call the user info endpoint
let user = null;
try {
user = (await networkSvc.request({
method: 'GET',
url: 'https://gitee.com/api/v5/user',
params: {
access_token: accessToken,
},
})).body;
} catch (err) {
if (err.status === 401) {
this.startOauth2(null, false, isMain, 1);
}
throw err;
}
if (user.avatar_url && user.avatar_url.endsWith('.png') && !user.avatar_url.endsWith('no_portrait.png')) {
user.avatar_url = `${user.avatar_url}!avatar60`;
}
userSvc.addUserInfo({
id: `${subPrefix}:${user.login}`,
name: user.login,
imageUrl: user.avatar_url || '',
});
// 获取同一个用户的登录token
const existingToken = store.getters['data/giteeTokensBySub'][user.login];
// Build token object including sub 在token失效后刷新token 如果刷新失败则触发重新授权
const token = {
accessToken,
// 主文档空间的登录 标识登录
isLogin: !!isMain || (existingToken && !!existingToken.isLogin),
refreshToken: tokenBody.refresh_token,
expiresOn: Date.now() + (tokenBody.expires_in * 1000),
name: user.login,
sub: `${user.login}`,
};
if (isMain) {
// 检查 stackedit-app-data 仓库是否已经存在 如果不存在则创建该仓库
await this.checkAndCreateRepo(token);
}
// Add token to gitee tokens
store.dispatch('data/addGiteeToken', token);
return token;
},
// 刷新token
async refreshToken(token) {
const { sub } = token;
const lastToken = store.getters['data/giteeTokensBySub'][sub];
// 兼容旧的没有过期时间
if (!lastToken.expiresOn) {
await store.dispatch('modal/open', {
type: 'providerRedirection',
name: 'Gitee',
});
return this.startOauth2();
}
// lastToken is not expired
if (lastToken.expiresOn > Date.now() + tokenExpirationMargin) {
return lastToken;
}
// existing token is about to expire.
// Try to get a new token in background
try {
return await this.startOauth2(lastToken, true);
} catch (err) {
// If it fails try to popup a window
if (store.state.offline) {
throw err;
}
await store.dispatch('modal/open', {
type: 'providerRedirection',
name: 'Gitee',
});
return this.startOauth2();
}
},
signin() {
return this.startOauth2(null, false, true);
},
async addAccount() {
const token = await this.startOauth2();
badgeSvc.addBadge('addGiteeAccount');
return token;
},
/**
* https://developer.gitee.com/v3/repos/commits/#get-a-single-commit
* https://developer.gitee.com/v3/git/trees/#get-a-tree
*/
async getTree({
token,
owner,
repo,
branch,
}) {
try {
const refreshedToken = await this.refreshToken(token);
const { commit } = await repoRequest(refreshedToken, owner, repo, {
url: `commits/${encodeURIComponent(branch)}`,
});
const { tree, truncated } = await repoRequest(refreshedToken, owner, repo, {
url: `git/trees/${encodeURIComponent(commit.tree.sha)}?recursive=1`,
});
if (truncated) {
throw new Error('Git tree too big. Please remove some files in the repository.');
}
return tree;
} catch (err) {
if (err.status === 401) {
this.startOauth2(null, false, null, 1);
}
throw err;
}
},
async checkAndCreateRepo(token) {
const url = `https://gitee.com/api/v5/repos/${encodeURIComponent(token.name)}/${encodeURIComponent(appDataRepo)}`;
try {
await request(token, { url });
} catch (err) {
// 不存在则创建
if (err.status === 404) {
await request(token, {
method: 'POST',
url: 'https://gitee.com/api/v5/user/repos',
params: {
name: appDataRepo,
auto_init: true,
},
});
} else {
throw err;
}
}
},
/**
* https://developer.gitee.com/v3/repos/commits/#list-commits-on-a-repository
*/
async getCommits({
token,
owner,
repo,
sha,
path,
}) {
const refreshedToken = await this.refreshToken(token);
return repoRequest(refreshedToken, owner, repo, {
url: 'commits',
params: { sha, path },
});
},
/**
* https://developer.gitee.com/v3/repos/contents/#create-a-file
* https://developer.gitee.com/v3/repos/contents/#update-a-file
*/
async uploadFile({
token,
owner,
repo,
branch,
path,
content,
sha,
isImg,
commitMessage,
}) {
let uploadContent = content;
if (isImg && typeof content !== 'string') {
uploadContent = await utils.encodeFiletoBase64(content);
}
const refreshedToken = await this.refreshToken(token);
return repoRequest(refreshedToken, owner, repo, {
method: sha ? 'PUT' : 'POST',
url: `contents/${encodeURIComponent(path)}`,
body: {
message: commitMessage || getCommitMessage(sha ? 'updateFileMessage' : 'createFileMessage', path),
content: isImg ? uploadContent : utils.encodeBase64(content || ' '),
sha,
branch,
},
});
},
/**
* https://developer.gitee.com/v3/repos/contents/#delete-a-file
*/
async removeFile({
token,
owner,
repo,
branch,
path,
sha,
}) {
const refreshedToken = await this.refreshToken(token);
return repoRequest(refreshedToken, owner, repo, {
method: 'DELETE',
url: `contents/${encodeURIComponent(path)}`,
body: {
message: getCommitMessage('deleteFileMessage', path),
sha,
branch,
},
});
},
/**
* https://developer.gitee.com/v3/repos/contents/#get-contents
*/
async downloadFile({
token,
owner,
repo,
branch,
path,
isImg,
}) {
const refreshedToken = await this.refreshToken(token);
const { sha, content } = await repoRequest(refreshedToken, owner, repo, {
url: `contents/${encodeURIComponent(path)}`,
params: { ref: branch },
});
if (sha) {
const data = !isImg ? utils.decodeBase64(content) : content;
return {
sha,
data: data === ' ' ? '' : data,
};
}
return {};
},
/**
* https://developer.gitee.com/v3/gists/#create-a-gist
* https://developer.gitee.com/v3/gists/#edit-a-gist
*/
async uploadGist({
token,
description,
filename,
content,
isPublic,
gistId,
}) {
const refreshedToken = await this.refreshToken(token);
const { body } = await request(refreshedToken, gistId ? {
method: 'PATCH',
url: `https://gitee.com/api/v5/gists/${gistId}`,
body: {
description,
files: {
[filename]: {
content,
},
},
},
} : {
method: 'POST',
url: 'https://gitee.com/api/v5/gists',
body: {
description,
files: {
[filename]: {
content,
},
},
public: isPublic,
},
});
return body;
},
/**
* https://developer.gitee.com/v3/gists/#get-a-single-gist
*/
async downloadGist({
token,
gistId,
filename,
}) {
const refreshedToken = await this.refreshToken(token);
const result = (await request(refreshedToken, {
url: `https://gitee.com/api/v5/gists/${gistId}`,
})).body.files[filename];
if (!result) {
throw new Error('Gist file not found.');
}
return result.content;
},
/**
* https://developer.gitee.com/v3/gists/#list-gist-commits
*/
async getGistCommits({
token,
gistId,
}) {
const refreshedToken = await this.refreshToken(token);
const { body } = await request(refreshedToken, {
url: `https://gitee.com/api/v5/gists/${gistId}/commits`,
});
return body;
},
/**
* https://developer.gitee.com/v3/gists/#get-a-specific-revision-of-a-gist
*/
async downloadGistRevision({
token,
gistId,
filename,
sha,
}) {
const refreshedToken = await this.refreshToken(token);
const result = (await request(refreshedToken, {
url: `https://gitee.com/api/v5/gists/${gistId}/${sha}`,
})).body.files[filename];
if (!result) {
throw new Error('Gist file not found.');
}
return result.content;
},
};