diff --git a/package.json b/package.json
index 7ac885d..e010a9b 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@vueuse/core": "^4.4.0",
"bulma": "^0.9.2",
"core-js": "^3.9.0",
+ "date-fns": "^2.21.1",
"markdown-it": "^12.0.4",
"markdown-it-block-embed": "^0.0.3",
"nanoid": "^3.1.22",
diff --git a/src/components/Authorize.vue b/src/components/Authorize.vue
new file mode 100644
index 0000000..6583b0a
--- /dev/null
+++ b/src/components/Authorize.vue
@@ -0,0 +1,44 @@
+
+
+
An error occured when sign in...
+
+
+
+
diff --git a/src/components/SignInGithub.vue b/src/components/SignInGithub.vue
new file mode 100644
index 0000000..994163e
--- /dev/null
+++ b/src/components/SignInGithub.vue
@@ -0,0 +1,41 @@
+
+
+
+ Sign in with
+
+
+
+
+
+
+
+
diff --git a/src/components/WelcomeWorld.vue b/src/components/WelcomeWorld.vue
index d87c1e4..c3a7f63 100644
--- a/src/components/WelcomeWorld.vue
+++ b/src/components/WelcomeWorld.vue
@@ -3,14 +3,20 @@
Lite Note
-
Get started
+
+ Get started
+ about
+
+
@@ -90,8 +96,6 @@
rel="noopener noreferrer"
>Julien
- |
- about
@@ -102,8 +106,10 @@ import { defineComponent } from 'vue'
import { useForm } from '@/hooks/useForm.hook'
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { useFavoriteRepos } from '@/modules/repo/hooks/useFavoriteRepos.hook'
+import SignInGithub from '@/components/SignInGithub.vue'
export default defineComponent({
+ components: { SignInGithub },
name: 'WelcomeWord',
setup() {
const { isLogged, username } = useGitHubLogin()
@@ -119,7 +125,9 @@ export default defineComponent({
padding: 1rem;
margin: auto;
display: flex;
+ flex: 1;
flex-direction: column;
+ justify-content: space-between;
.get-started {
margin: center;
@@ -130,6 +138,10 @@ export default defineComponent({
h4 {
text-align: center;
}
+
+ .github-login {
+ margin-top: 1rem;
+ }
}
footer {
diff --git a/src/data/models/GithubAccessToken.ts b/src/data/models/GithubAccessToken.ts
index d58931b..4e0ebe8 100644
--- a/src/data/models/GithubAccessToken.ts
+++ b/src/data/models/GithubAccessToken.ts
@@ -3,5 +3,10 @@ import { Model } from '@/data/models/Model'
export interface GithubAccessToken extends Model
{
username: string
- personalAccessToken: string
+ token: string
+ expirationDate: string
+ expiresIn: number
+ refreshToken: string
+ refreshTokenExpiresIn: number
+ refreshTokenExpirationDate: string
}
diff --git a/src/hooks/useGitHubLogin.hook.ts b/src/hooks/useGitHubLogin.hook.ts
index b74a4ac..7f76d13 100644
--- a/src/hooks/useGitHubLogin.hook.ts
+++ b/src/hooks/useGitHubLogin.hook.ts
@@ -1,51 +1,35 @@
import { computed, ref } from 'vue'
-import { DataType } from '@/data/DataType.enum'
-import { GithubAccessToken } from '@/data/models/GithubAccessToken'
-import { data } from '@/data/data'
import { confirmMessage } from '@/utils/notif'
+import { GithubToken } from '@/modules/user/interfaces/GithubToken'
+import { getAccessToken, saveAccessToken } from '@/modules/user/service/signIn'
-const personalAccessTokenId = 'PAT'
const username = ref(null)
const accessToken = ref(null)
let init = true
export const useGitHubLogin = () => {
- const getAccessToken = async () => {
- const response = await data.get<
- DataType.GithubAccessToken,
- GithubAccessToken
- >(data.generateId(DataType.GithubAccessToken, personalAccessTokenId))
+ const saveAccessTokenToLocal = async () => {
+ const response = await getAccessToken()
username.value = response?.username || ''
- accessToken.value = response?.personalAccessToken || ''
-
- return response
+ accessToken.value = response?.token || ''
}
if (init) {
init = false
- getAccessToken()
+ saveAccessTokenToLocal()
}
- const saveCredentials = async (username: string, token: string) => {
- const actualPAT = await getAccessToken()
+ const saveCredentials = async (githubToken: GithubToken) => {
+ const accessToken = await saveAccessToken(githubToken)
- const personalAccessToken: GithubAccessToken = {
- ...actualPAT,
- _id: data.generateId(DataType.GithubAccessToken, personalAccessTokenId),
- $type: DataType.GithubAccessToken,
- username,
- personalAccessToken: token
- }
-
- await data.add(personalAccessToken)
- getAccessToken()
- confirmMessage('token saved!')
+ await saveAccessTokenToLocal()
+ confirmMessage(`${accessToken.username} is logged in!`)
}
return {
- isLogged: !!username.value && !!accessToken.value,
+ isLogged: !!accessToken.value,
isReady: computed(() => accessToken.value !== null),
username,
accessToken,
diff --git a/src/hooks/useRepos.hook.ts b/src/hooks/useRepos.hook.ts
index cbbc643..36fbf91 100644
--- a/src/hooks/useRepos.hook.ts
+++ b/src/hooks/useRepos.hook.ts
@@ -1,7 +1,7 @@
-import { Octokit } from '@octokit/rest'
import { RepoBase } from '@/modules/repo/interfaces/RepoBase'
import { useAsyncState } from '@vueuse/core'
import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
+import { getOctokit } from '@/modules/repo/services/octo'
export const useRepos = () => {
const { username, accessToken } = useGitHubLogin()
@@ -10,9 +10,7 @@ export const useRepos = () => {
return []
}
- const octokit = new Octokit({
- auth: accessToken.value
- })
+ const octokit = await getOctokit()
const repoList = await octokit.request('GET /search/repositories', {
q: `user:${username.value}`,
diff --git a/src/modules/repo/services/octo.ts b/src/modules/repo/services/octo.ts
new file mode 100644
index 0000000..c0df59f
--- /dev/null
+++ b/src/modules/repo/services/octo.ts
@@ -0,0 +1,37 @@
+import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
+import { needToRefreshToken, refreshToken } from '@/modules/user/service/signIn'
+import { Octokit } from '@octokit/rest'
+
+let refreshingToken = false
+let octokit = new Octokit()
+
+const sleep = async (ms: number) => {
+ return new Promise((resolve) => setTimeout(resolve, ms))
+}
+
+export const getOctokit = async (): Promise => {
+ const { accessToken } = useGitHubLogin()
+ octokit = new Octokit({
+ auth: accessToken.value
+ })
+
+ if (refreshingToken) {
+ await sleep(100)
+ return getOctokit()
+ }
+
+ if (!refreshingToken) {
+ refreshingToken = true
+ if (await needToRefreshToken()) {
+ const accessToken = await refreshToken()
+ if (accessToken) {
+ octokit = new Octokit({
+ auth: accessToken?.token
+ })
+ }
+ }
+ refreshingToken = false
+ }
+
+ return octokit
+}
diff --git a/src/modules/repo/services/repo.ts b/src/modules/repo/services/repo.ts
index bb8605c..676ac35 100644
--- a/src/modules/repo/services/repo.ts
+++ b/src/modules/repo/services/repo.ts
@@ -1,9 +1,8 @@
-import { useGitHubLogin } from '@/hooks/useGitHubLogin.hook'
import { useMarkdown } from '@/hooks/useMarkdown.hook'
import { useNoteCache } from '@/modules/note/hooks/useNoteCache'
import { RepoFile } from '@/modules/repo/interfaces/RepoFile'
import { UserSettings } from '@/modules/repo/interfaces/UserSettings'
-import { Octokit } from '@octokit/rest'
+import { getOctokit } from '@/modules/repo/services/octo'
export const getFiles = async (
owner: string,
@@ -12,12 +11,7 @@ export const getFiles = async (
if (!owner || !repo) {
return []
}
-
- const { accessToken } = useGitHubLogin()
-
- const octokit = new Octokit({
- auth: accessToken.value
- })
+ const octokit = await getOctokit()
const commits = await octokit.request('GET /repos/{owner}/{repo}/commits', {
owner,
@@ -47,16 +41,14 @@ export const getMainReadme = async (owner: string, repo: string) => {
if (!owner || !repo) {
return null
}
+
const { render } = useMarkdown()
const { getCachedNote, saveCacheNote } = useNoteCache('README')
const cachedReadme = await getCachedNote()
try {
- const { accessToken } = useGitHubLogin()
- const octokit = new Octokit({
- auth: accessToken.value
- })
+ const octokit = await getOctokit()
const README = await octokit.repos.getReadme({
owner,
@@ -101,11 +93,7 @@ export const getFileContent = async (
repo: string,
sha: string
) => {
- const { accessToken } = useGitHubLogin()
-
- const octokit = new Octokit({
- auth: accessToken.value
- })
+ const octokit = await getOctokit()
if (!user || !repo) {
null
diff --git a/src/modules/repo/store/userRepo.store.ts b/src/modules/repo/store/userRepo.store.ts
index f4d844a..63f3bc8 100644
--- a/src/modules/repo/store/userRepo.store.ts
+++ b/src/modules/repo/store/userRepo.store.ts
@@ -28,10 +28,8 @@ export const useUserRepoStore = defineStore({
async setUserRepo(newUser: string, newRepo: string) {
this.user = newUser
this.repo = newRepo
- const [readme, files] = await Promise.all([
- getMainReadme(newUser, newRepo),
- getFiles(newUser, newRepo)
- ])
+ const readme = await getMainReadme(newUser, newRepo)
+ const files = await getFiles(newUser, newRepo)
this.userSettings = await getUserSettingsContent(newUser, newRepo, files)
this.readme = readme
diff --git a/src/modules/user/interfaces/GithubToken.ts b/src/modules/user/interfaces/GithubToken.ts
new file mode 100644
index 0000000..83919bc
--- /dev/null
+++ b/src/modules/user/interfaces/GithubToken.ts
@@ -0,0 +1,8 @@
+export interface GithubToken {
+ access_token: string
+ expires_in: number
+ refresh_token: string
+ refresh_token_expires_in: number
+ scope: string
+ token_type: 'bearer'
+}
diff --git a/src/modules/user/interfaces/GithubTokenError.ts b/src/modules/user/interfaces/GithubTokenError.ts
new file mode 100644
index 0000000..5b43646
--- /dev/null
+++ b/src/modules/user/interfaces/GithubTokenError.ts
@@ -0,0 +1,5 @@
+export interface GithubTokenError {
+ error: string
+ error_description: string
+ error_uri: string
+}
diff --git a/src/modules/user/service/signIn.ts b/src/modules/user/service/signIn.ts
new file mode 100644
index 0000000..c9c9cf2
--- /dev/null
+++ b/src/modules/user/service/signIn.ts
@@ -0,0 +1,118 @@
+import { data } from '@/data/data'
+import { DataType } from '@/data/DataType.enum'
+import { GithubAccessToken } from '@/data/models/GithubAccessToken'
+import { GithubToken } from '@/modules/user/interfaces/GithubToken'
+import { GithubTokenError } from '@/modules/user/interfaces/GithubTokenError'
+import { Octokit } from '@octokit/rest'
+import { addSeconds } from 'date-fns'
+
+const AUTHENTICATION_SERVER = 'https://litenote.li212.fr'
+const personalTokenId = 'token'
+
+export const signIn = async (
+ code: string
+): Promise => {
+ const authenticationServerURL = new URL(AUTHENTICATION_SERVER)
+ authenticationServerURL.searchParams.set('code', code)
+
+ const response = await fetch(authenticationServerURL.toString())
+ const body = (await response.json()) as GithubToken | GithubTokenError
+
+ return body
+}
+
+export const needToRefreshToken = async () => {
+ const accessToken = await data.get<
+ DataType.GithubAccessToken,
+ GithubAccessToken
+ >(data.generateId(DataType.GithubAccessToken, personalTokenId))
+
+ if (!accessToken) {
+ return false
+ }
+
+ return new Date(accessToken.expirationDate) <= new Date()
+}
+
+export const refreshToken = async () => {
+ const accessToken = await data.get<
+ DataType.GithubAccessToken,
+ GithubAccessToken
+ >(data.generateId(DataType.GithubAccessToken, personalTokenId))
+
+ if (!accessToken) {
+ return null
+ }
+
+ console.log(accessToken.refreshToken)
+
+ if (await needToRefreshToken()) {
+ const authenticationServerURL = new URL(AUTHENTICATION_SERVER)
+ authenticationServerURL.searchParams.set('type', 'refresh')
+ authenticationServerURL.searchParams.set('code', accessToken.refreshToken)
+
+ const response = await fetch(authenticationServerURL.toString())
+ const githubToken = (await response.json()) as
+ | GithubToken
+ | GithubTokenError
+
+ console.log(githubToken)
+
+ if ('error' in githubToken) {
+ return null
+ }
+
+ return await saveAccessToken(githubToken)
+ }
+
+ return accessToken
+}
+
+export const getAccessToken = async () => {
+ const response = await data.get<
+ DataType.GithubAccessToken,
+ GithubAccessToken
+ >(data.generateId(DataType.GithubAccessToken, personalTokenId))
+
+ return response
+}
+
+export const saveAccessToken = async (githubToken: GithubToken) => {
+ const actualPAT = await getAccessToken()
+
+ const expirationDate = addSeconds(
+ new Date(),
+ githubToken.expires_in
+ ).toISOString()
+
+ const refreshTokenExpirationDate = addSeconds(
+ new Date(),
+ githubToken.refresh_token_expires_in
+ ).toISOString()
+
+ const accessToken: GithubAccessToken = {
+ ...actualPAT,
+ _id: data.generateId(DataType.GithubAccessToken, personalTokenId),
+ $type: DataType.GithubAccessToken,
+ token: githubToken.access_token,
+ expiresIn: githubToken.expires_in,
+ expirationDate,
+ refreshToken: githubToken.refresh_token,
+ refreshTokenExpiresIn: githubToken.refresh_token_expires_in,
+ refreshTokenExpirationDate,
+ username: ''
+ }
+
+ console.log(accessToken)
+
+ const octokit = new Octokit({
+ auth: accessToken?.token
+ })
+
+ const user = await octokit.request('GET /user')
+ accessToken.username = user.data.login
+
+ await data.add(accessToken)
+
+ return accessToken
+}
diff --git a/src/views/About.vue b/src/views/About.vue
index 305a9d4..c9d9f25 100644
--- a/src/views/About.vue
+++ b/src/views/About.vue
@@ -1,24 +1,22 @@
-
Lite Note
-
Work in progress:
- - header note for quick actions ⏳
- - offline notes
- - full path resolver between notes
- - draft & fleeting notes folders
- - login with GitHub "Personal Access Token" tutorial
- - private & public notes tutorial
+ - header note for quick actions ✅
+ - offline notes ✅
+ - full path resolver between notes ✅
+ - draft & fleeting notes folders ✅
+ - private & public notes tutorial ✅
-
custom settings:
- - light & dark modes
- - font families
+ - light & dark modes ✅
+ - font families ✅
+ - Share PDF section of a note (and its subnotes) ⏳
diff --git a/src/views/Home.vue b/src/views/Home.vue
index 9b12707..058c720 100644
--- a/src/views/Home.vue
+++ b/src/views/Home.vue
@@ -1,5 +1,6 @@
@@ -10,6 +11,7 @@
import { defineComponent, defineAsyncComponent, computed } from 'vue'
import { useQueryStackedNotes } from '@/hooks/useQueryStackedNotes.hook'
import NewVersion from '@/components/NewVersion.vue'
+import Authorize from '@/components/Authorize.vue'
const FluxNote = defineAsyncComponent(() => import('@/components/FluxNote.vue'))
@@ -22,7 +24,8 @@ export default defineComponent({
components: {
WelcomeWorld,
FluxNote,
- NewVersion
+ NewVersion,
+ Authorize
},
props: {
user: { type: String, required: false, default: '' },
@@ -47,7 +50,13 @@ export default defineComponent({
align-items: center;
.new-version {
- margin-top: 1rem;
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
}
}
+
+.authorize {
+ margin: auto;
+}
diff --git a/yarn.lock b/yarn.lock
index bb41fd0..c536bf1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4355,6 +4355,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
+date-fns@^2.21.1:
+ version "2.21.1"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.21.1.tgz#679a4ccaa584c0706ea70b3fa92262ac3009d2b0"
+ integrity sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA==
+
deasync@^0.1.15:
version "0.1.20"
resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.20.tgz#546fd2660688a1eeed55edce2308c5cf7104f9da"