mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
feat: handle API rate limit
This commit is contained in:
parent
68b78216c9
commit
6916850d85
4 changed files with 97 additions and 40 deletions
|
|
@ -89,7 +89,14 @@ export default class SettingsBar extends React.PureComponent {
|
|||
onAccessTokenChange(accessToken)
|
||||
this.setState({
|
||||
accessToken: '',
|
||||
accessTokenHint: <span><a href="#" onClick={() => window.location.reload()}>Reload</a> to activate!</span>,
|
||||
accessTokenHint: (
|
||||
<span>
|
||||
<a href="#" onClick={() => window.location.reload()}>
|
||||
Reload
|
||||
</a>{' '}
|
||||
to activate!
|
||||
</span>
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -126,38 +133,44 @@ export default class SettingsBar extends React.PureComponent {
|
|||
const { setCompressSingleton } = this.props
|
||||
setCompressSingleton(compress)
|
||||
this.setState({
|
||||
compressHint: <span>Saved, <a href="#" onClick={() => window.location.reload()}>reload</a> to apply.</span>,
|
||||
compressHint: (
|
||||
<span>
|
||||
Saved,{' '}
|
||||
<a href="#" onClick={() => window.location.reload()}>
|
||||
reload
|
||||
</a>{' '}
|
||||
to apply.
|
||||
</span>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { accessTokenHint, toggleShowSideBarShortcut, compressSingletonFolder, shortcutHint, accessToken, compressHint } = this.state
|
||||
const {
|
||||
accessTokenHint,
|
||||
toggleShowSideBarShortcut,
|
||||
compressSingletonFolder,
|
||||
shortcutHint,
|
||||
accessToken,
|
||||
compressHint,
|
||||
} = this.state
|
||||
const { toggleShowSettings, activated, accessToken: hasAccessToken } = this.props
|
||||
return (
|
||||
<div className={'gitako-settings-bar'}>
|
||||
{activated && (
|
||||
<React.Fragment>
|
||||
<h3 className={'gitako-settings-bar-title'}>
|
||||
Settings
|
||||
</h3>
|
||||
<h3 className={'gitako-settings-bar-title'}>Settings</h3>
|
||||
<div className={'gitako-settings-bar-content'}>
|
||||
<div className={'shadow-shelter'} />
|
||||
<div className={'gitako-settings-bar-content-section access-token'}>
|
||||
<h4>Access Token</h4>
|
||||
<span>With access token provided, Gitako can access more repositories.</span>
|
||||
<br />
|
||||
<a href="https://github.com/blog/1509-personal-api-tokens" target="_blank">
|
||||
Help: how to create access token?
|
||||
<a
|
||||
href="https://github.com/EnixCoda/Gitako/wiki/How-to-create-access-token-for-Gitako%3F"
|
||||
target="_blank"
|
||||
>
|
||||
Why & how to create it?
|
||||
</a>
|
||||
<br />
|
||||
<span>
|
||||
Gitako stores the token in
|
||||
<a href="https://developer.chrome.com/apps/storage" target="_blank">
|
||||
chrome local storage
|
||||
</a>
|
||||
locally and safely.
|
||||
</span>
|
||||
<br />
|
||||
<div className={'access-token-input-control'}>
|
||||
<input
|
||||
className={'access-token-input form-control'}
|
||||
|
|
@ -199,10 +212,18 @@ export default class SettingsBar extends React.PureComponent {
|
|||
<div className={'gitako-settings-bar-content-section singleton'}>
|
||||
<h4>
|
||||
Compress singleton folder
|
||||
<a href={wikiLinks.compressSingletonFolder} target={'_blank'}>(?)</a>
|
||||
<a href={wikiLinks.compressSingletonFolder} target={'_blank'}>
|
||||
(?)
|
||||
</a>
|
||||
</h4>
|
||||
<label htmlFor={'compress-singleton-folder'}>
|
||||
<input id={'compress-singleton-folder'} name={'compress-singleton-folder'} type={'checkbox'} onChange={this.setCompressSingletonFolder} checked={compressSingletonFolder} />
|
||||
<input
|
||||
id={'compress-singleton-folder'}
|
||||
name={'compress-singleton-folder'}
|
||||
type={'checkbox'}
|
||||
onChange={this.setCompressSingletonFolder}
|
||||
checked={compressSingletonFolder}
|
||||
/>
|
||||
{compressSingletonFolder ? 'enabled' : 'disabled'}
|
||||
</label>
|
||||
{compressHint && <div className={'hint'}>{compressHint}</div>}
|
||||
|
|
@ -220,7 +241,12 @@ export default class SettingsBar extends React.PureComponent {
|
|||
</React.Fragment>
|
||||
)}
|
||||
<div className={'placeholder-row'}>
|
||||
<a className={'version'} href={wikiLinks.changeLog} target={'_blank'} title={'Check out new features!'}>
|
||||
<a
|
||||
className={'version'}
|
||||
href={wikiLinks.changeLog}
|
||||
target={'_blank'}
|
||||
title={'Check out new features!'}
|
||||
>
|
||||
v{version}
|
||||
</a>
|
||||
{activated ? (
|
||||
|
|
@ -230,11 +256,7 @@ export default class SettingsBar extends React.PureComponent {
|
|||
onClick={toggleShowSettings}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
type={'gear'}
|
||||
className={'show-settings-icon'}
|
||||
onClick={toggleShowSettings}
|
||||
/>
|
||||
<Icon type={'gear'} className={'show-settings-icon'} onClick={toggleShowSettings} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -79,23 +79,46 @@ export default class Gitako extends React.PureComponent {
|
|||
<div className={'description'}>
|
||||
<h5>Access Denied</h5>
|
||||
<p>
|
||||
Gitako needs access token with proper scopes (recommended: repo) to access this
|
||||
repository. Please save it in the settings below.
|
||||
Due to{' '}
|
||||
<a target="_blank" href="https://developer.github.com/v3/#rate-limiting">
|
||||
limitation of GitHub
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
<a target="_blank" href="https://developer.github.com/v3/#authentication">
|
||||
auth needs
|
||||
</a>
|
||||
, Gitako needs access token to continue. Please follow the instructions in the settings
|
||||
panel below.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const { errorDueToAuth, metaData, treeData, showSettings, accessToken, compressSingletonFolder, toggleShowSettings } = this.props
|
||||
const {
|
||||
errorDueToAuth,
|
||||
metaData,
|
||||
treeData,
|
||||
showSettings,
|
||||
accessToken,
|
||||
compressSingletonFolder,
|
||||
toggleShowSettings,
|
||||
} = this.props
|
||||
return (
|
||||
<div className={'gitako-side-bar-content'}>
|
||||
{metaData && <MetaBar metaData={metaData} />}
|
||||
{errorDueToAuth
|
||||
? this.renderAccessDeniedError()
|
||||
: metaData &&
|
||||
<FileExplorer toggleShowSettings={toggleShowSettings} metaData={metaData} treeData={treeData} freeze={showSettings} accessToken={accessToken} compressSingletonFolder={compressSingletonFolder} />
|
||||
}
|
||||
: metaData && (
|
||||
<FileExplorer
|
||||
toggleShowSettings={toggleShowSettings}
|
||||
metaData={metaData}
|
||||
treeData={treeData}
|
||||
freeze={showSettings}
|
||||
accessToken={accessToken}
|
||||
compressSingletonFolder={compressSingletonFolder}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -119,7 +142,11 @@ export default class Gitako extends React.PureComponent {
|
|||
return (
|
||||
<div className={'gitako-side-bar'}>
|
||||
<Portal into={logoContainerElement}>
|
||||
<ToggleShowButton error={error} shouldShow={shouldShow} toggleShowSideBar={toggleShowSideBar} />
|
||||
<ToggleShowButton
|
||||
error={error}
|
||||
shouldShow={shouldShow}
|
||||
toggleShowSideBar={toggleShowSideBar}
|
||||
/>
|
||||
</Portal>
|
||||
<Resizable className={cx({ hidden: error || !shouldShow })} baseSize={baseSize}>
|
||||
<div className={'gitako-side-bar-body'}>
|
||||
|
|
@ -133,7 +160,7 @@ export default class Gitako extends React.PureComponent {
|
|||
compressSingletonFolder={compressSingletonFolder}
|
||||
toggleShowSideBarShortcut={toggleShowSideBarShortcut}
|
||||
setCompressSingleton={setCompressSingleton}
|
||||
/>
|
||||
/>
|
||||
</div>
|
||||
</Resizable>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import DOMHelper, { REPO_TYPE_PRIVATE } from 'utils/DOMHelper'
|
||||
import GitHubHelper, { NOT_FOUND, BAD_CREDENTIALS } from 'utils/GitHubHelper'
|
||||
import GitHubHelper, { NOT_FOUND, BAD_CREDENTIALS, API_RATE_LIMIT } from 'utils/GitHubHelper'
|
||||
import configHelper from 'utils/configHelper'
|
||||
import URLHelper from 'utils/URLHelper'
|
||||
import keyHelper from 'utils/keyHelper'
|
||||
|
|
@ -53,9 +53,9 @@ const init = dispatch => async () => {
|
|||
|
||||
const handleError = dispatch => async (err) => {
|
||||
// TODO: detect request time exceeds limit
|
||||
if (err.message === NOT_FOUND || err.message === BAD_CREDENTIALS) {
|
||||
if (err.message === NOT_FOUND || err.message === BAD_CREDENTIALS || err.message === API_RATE_LIMIT ) {
|
||||
const repoPageType = await DOMHelper.getRepoPageType()
|
||||
const errorDueToAuth = repoPageType === REPO_TYPE_PRIVATE || err.message === BAD_CREDENTIALS
|
||||
const errorDueToAuth = repoPageType === REPO_TYPE_PRIVATE || err.message === BAD_CREDENTIALS || err.message === API_RATE_LIMIT
|
||||
dispatch({ errorDueToAuth })
|
||||
dispatch(setShowSettings, true)
|
||||
dispatch(setShouldShow, errorDueToAuth)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import { raiseError } from 'analytics'
|
||||
export const NOT_FOUND = 'Repo Not Found'
|
||||
export const BAD_CREDENTIALS = 'Bad credentials'
|
||||
export const API_RATE_LIMIT = `API rate limit`
|
||||
|
||||
function apiRateLimitExceeded(content) {
|
||||
return content && content['documentation_url'] === 'https://developer.github.com/v3/#rate-limiting'
|
||||
}
|
||||
|
||||
async function request(url, { accessToken } = {}) {
|
||||
const headers = {}
|
||||
|
|
@ -11,9 +16,12 @@ async function request(url, { accessToken } = {}) {
|
|||
if (res.status === 200) return res.json()
|
||||
// for private repo, GitHub api also responses with 404 when unauthorized
|
||||
else if (res.status === 404) throw new Error(NOT_FOUND)
|
||||
else if (!res.ok) raiseError(new Error(`Got ${res.statusText} when requesting ${url}`))
|
||||
const content = await res.json()
|
||||
throw new Error(content && content.message)
|
||||
else {
|
||||
const content = await res.json()
|
||||
if (apiRateLimitExceeded(content)) throw new Error(API_RATE_LIMIT)
|
||||
else if (!res.ok) raiseError(new Error(`Got ${res.statusText} when requesting ${url}`))
|
||||
throw new Error(content && content.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function getRepoMeta({ userName, repoName, accessToken }) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue