diff --git a/components/account/AccountMe.client.vue b/components/account/AccountMe.client.vue
new file mode 100644
index 00000000..05979ead
--- /dev/null
+++ b/components/account/AccountMe.client.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/composables/client.ts b/composables/client.ts
index c1559d51..5568891b 100644
--- a/composables/client.ts
+++ b/composables/client.ts
@@ -1,5 +1,10 @@
import type { MastoClient } from 'masto'
+import type { AppStore } from '~~/plugins/store.client'
export function useMasto() {
return inject('masto') as Promise
}
+
+export function useAppStore() {
+ return inject('app-store') as AppStore
+}
diff --git a/composables/cookies.ts b/composables/cookies.ts
new file mode 100644
index 00000000..ae4ee63d
--- /dev/null
+++ b/composables/cookies.ts
@@ -0,0 +1,11 @@
+import { DEFAULT_SERVER } from '~/constants'
+
+export function useAppCookies() {
+ const server = useCookie('nuxtodon-server', { default: () => DEFAULT_SERVER })
+ const token = useCookie('nuxtodon-token')
+
+ return {
+ server,
+ token,
+ }
+}
diff --git a/constants/index.ts b/constants/index.ts
index 0090e756..d17d7f9a 100644
--- a/constants/index.ts
+++ b/constants/index.ts
@@ -4,3 +4,4 @@ export const HOST_DOMAIN = process.dev
? 'http://localhost:3000'
: 'https://nuxtodon.netlify.app'
+export const DEFAULT_SERVER = 'mas.to'
diff --git a/layouts/default.vue b/layouts/default.vue
index f155eec2..f361970a 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -10,7 +10,9 @@
diff --git a/package.json b/package.json
index 948ffd98..4caa26c7 100644
--- a/package.json
+++ b/package.json
@@ -7,12 +7,13 @@
"dev": "nuxi dev",
"start": "node .output/server/index.mjs",
"lint": "eslint .",
+ "register-apps": "esno ./scripts/registerApps.ts",
"postinstall": "nuxi prepare",
"generate": "nuxi generate"
},
"devDependencies": {
"@antfu/eslint-config": "^0.30.1",
- "@iconify-json/carbon": "^1.1.9",
+ "@iconify-json/carbon": "^1.1.10",
"@iconify-json/logos": "^1.1.18",
"@iconify-json/ri": "^1.1.3",
"@iconify-json/twemoji": "^1.1.5",
diff --git a/pages/login.vue b/pages/login.vue
deleted file mode 100644
index 90b3189b..00000000
--- a/pages/login.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/pages/login/callback.vue b/pages/login/callback.vue
new file mode 100644
index 00000000..a63dcfa6
--- /dev/null
+++ b/pages/login/callback.vue
@@ -0,0 +1,17 @@
+
+
+
+
+ Login...
+
+
diff --git a/pages/login/index.vue b/pages/login/index.vue
new file mode 100644
index 00000000..91e67ef5
--- /dev/null
+++ b/pages/login/index.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
diff --git a/plugins/masto.ts b/plugins/masto.ts
index 7f4c7616..820dacfb 100644
--- a/plugins/masto.ts
+++ b/plugins/masto.ts
@@ -1,19 +1,11 @@
import { login } from 'masto'
-export const DEFAULT_SERVER = 'mas.to'
-
export default defineNuxtPlugin((nuxt) => {
- const server = useCookie('nuxtodon-server')
- const token = useCookie('nuxtodon-token')
+ const { server, token } = useAppCookies()
const masto = login({
- url: `https://${server.value || DEFAULT_SERVER}`,
+ url: `https://${server.value}`,
accessToken: token.value,
})
nuxt.vueApp.provide('masto', masto)
-
- // Reload the page when the token changes
- watch(token, () => {
- location.reload()
- })
})
diff --git a/plugins/store.client.ts b/plugins/store.client.ts
new file mode 100644
index 00000000..aaba9e4f
--- /dev/null
+++ b/plugins/store.client.ts
@@ -0,0 +1,47 @@
+import { login as loginMasto } from 'masto'
+import type { UserLogin } from '~/types'
+
+function createStore() {
+ const { server, token } = useAppCookies()
+ const accounts = useLocalStorage('nuxtodon-accounts', [], { deep: true })
+ const currentIndex = useLocalStorage('nuxtodon-current-user', -1)
+ const currentUser = computed(() => accounts.value[currentIndex.value])
+
+ async function login(user: UserLogin) {
+ const existing = accounts.value.findIndex(u => u.server === user.server && u.token === user.token)
+ if (existing !== -1) {
+ if (currentIndex.value === existing)
+ return null
+ currentIndex.value = existing
+ server.value = user.server
+ token.value = user.token
+ return true
+ }
+
+ const masto = await loginMasto({
+ url: `https://${user.server}`,
+ accessToken: user.token,
+ })
+ const me = await masto.accounts.verifyCredentials()
+ user.account = me
+
+ accounts.value.push(user)
+ currentIndex.value = accounts.value.length
+ server.value = user.server
+ token.value = user.token
+
+ return true
+ }
+
+ return {
+ currentUser,
+ accounts,
+ login,
+ }
+}
+
+export type AppStore = ReturnType
+
+export default defineNuxtPlugin((nuxt) => {
+ nuxt.vueApp.provide('app-store', createStore())
+})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 15577bc0..9ba7587d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2,7 +2,7 @@ lockfileVersion: 5.4
specifiers:
'@antfu/eslint-config': ^0.30.1
- '@iconify-json/carbon': ^1.1.9
+ '@iconify-json/carbon': ^1.1.10
'@iconify-json/logos': ^1.1.18
'@iconify-json/ri': ^1.1.3
'@iconify-json/twemoji': ^1.1.5
@@ -26,7 +26,7 @@ specifiers:
devDependencies:
'@antfu/eslint-config': 0.30.1_rmayb2veg2btbq6mbmnyivgasy
- '@iconify-json/carbon': 1.1.9
+ '@iconify-json/carbon': 1.1.10
'@iconify-json/logos': 1.1.18
'@iconify-json/ri': 1.1.3
'@iconify-json/twemoji': 1.1.5
@@ -632,8 +632,8 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true
- /@iconify-json/carbon/1.1.9:
- resolution: {integrity: sha512-O3geRhhnE9dDDC4oT6qwBs7sdc37R9UqftiG7BP8YVDw8OcXv8i95J0ZEkIfdOXFj1Wb6kC/Uu/6VTlAqotVXg==}
+ /@iconify-json/carbon/1.1.10:
+ resolution: {integrity: sha512-k3/28wk+2CklUPdKBXWhPHQxquvLGiPYk1s6UWdQYR3YtneHRMuCp+0zc9yNjHYBzoSylMIECO0UPH+SdccguA==}
dependencies:
'@iconify/types': 2.0.0
dev: true
diff --git a/scripts/registerApps.ts b/scripts/registerApps.ts
index 9aaaf065..2d29045a 100644
--- a/scripts/registerApps.ts
+++ b/scripts/registerApps.ts
@@ -1,7 +1,7 @@
import fs from 'fs-extra'
-import type { Client } from 'masto'
import { $fetch } from 'ohmyfetch'
-import { APP_NAME } from '~~/constants'
+import { APP_NAME } from '~/constants'
+import type { AppInfo } from '~/types'
const KNOWN_SERVERS = [
'mastodon.social',
@@ -9,33 +9,38 @@ const KNOWN_SERVERS = [
'fosstodon.org',
]
+const KNOWN_DOMAINS = [
+ 'http://localhost:3000',
+ 'https://nuxtodon.netlify.app',
+]
+
const filename = 'public/registered-apps.json'
-let registeredApps: Record = {}
+let registeredApps: Record = {}
if (fs.existsSync(filename))
registeredApps = await fs.readJSON(filename)
for (const server of KNOWN_SERVERS) {
- if (registeredApps[server])
- continue
+ const redirect_uris = [
+ 'urn:ietf:wg:oauth:2.0:oob',
+ ...KNOWN_DOMAINS.map(d => `${d}/api/${server}/oauth`),
+ ].join('\n')
- const app = await $fetch(`https://${server}/api/v1/apps`, {
- method: 'POST',
- body: {
- client_name: APP_NAME,
- redirect_uris: [
- 'urn:ietf:wg:oauth:2.0:oob',
- 'http://localhost:3000/*',
- 'https://nuxtodon.netlify.app/*',
- ].join('\n'),
- scopes: 'read write follow push',
- },
- })
+ if (!registeredApps[server] || registeredApps[server].redirect_uri !== redirect_uris) {
+ const app = await $fetch(`https://${server}/api/v1/apps`, {
+ method: 'POST',
+ body: {
+ client_name: APP_NAME,
+ redirect_uris,
+ scopes: 'read write follow push',
+ },
+ })
- registeredApps[server] = app
+ registeredApps[server] = app
- console.log(`Registered app for ${server}`)
+ console.log(`Registered app for ${server}`)
+ }
}
await fs.writeJSON(filename, registeredApps, { spaces: 2, EOL: '\n' })
diff --git a/server/api/[server]/oauth.ts b/server/api/[server]/oauth.ts
index be3b876f..fcb42f88 100644
--- a/server/api/[server]/oauth.ts
+++ b/server/api/[server]/oauth.ts
@@ -1,29 +1,36 @@
import { getQuery } from 'ufo'
+import { stringifyQuery } from 'vue-router'
import { getApp } from '~/server/shared'
+import { HOST_DOMAIN } from '~/constants'
-export default defineEventHandler(async (event) => {
- const server = event.context.params.server
+export default defineEventHandler(async ({ context, req, res }) => {
+ const server = context.params.server
const app = await getApp(server)
if (!app) {
- event.res.statusCode = 400
+ res.statusCode = 400
return `App not registered for server: ${server}`
}
- const query = getQuery(event.req.url!)
+ const query = getQuery(req.url!)
const code = query.code
- const res = await $fetch(`https://${server}/oauth/token`, {
+ const result: any = await $fetch(`https://${server}/oauth/token`, {
method: 'POST',
body: {
client_id: app.client_id,
client_secret: app.client_secret,
- redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
+ redirect_uri: `${HOST_DOMAIN}/api/${server}/oauth`,
grant_type: 'authorization_code',
code,
scope: 'read write follow push',
},
})
- console.log({ res })
+ res.writeHead(302, {
+ Location: `${HOST_DOMAIN}/login/callback?${stringifyQuery({ server, token: result.access_token })}`,
+ })
+ res.end()
+
+ return result
})
diff --git a/server/shared.ts b/server/shared.ts
index 78ce395a..b848cbb1 100644
--- a/server/shared.ts
+++ b/server/shared.ts
@@ -1,19 +1,17 @@
import { $fetch } from 'ohmyfetch'
-
-export interface AppInfo {
- id: string
- name: string
- website: string | null
- redirect_uri: string
- client_id: string
- client_secret: string
- vapid_key: string
-}
+import type { AppInfo } from '~/types'
export const registeredApps: Record = {}
const promise = $fetch(process.env.APPS_JSON_URL || 'http://localhost:3000/registered-apps.json')
.then(r => Object.assign(registeredApps, r))
+ .catch((e) => {
+ if (process.dev)
+ console.error('Failed to fetch registered apps,\nyou may need to run `nr register-apps` first')
+ else
+ console.error('Failed to fetch registered apps')
+ console.error(e)
+ })
export async function getApp(server: string) {
await promise
diff --git a/types/index.ts b/types/index.ts
new file mode 100644
index 00000000..1a2dc8d2
--- /dev/null
+++ b/types/index.ts
@@ -0,0 +1,17 @@
+import type { AccountCredentials } from 'masto'
+
+export interface AppInfo {
+ id: string
+ name: string
+ website: string | null
+ redirect_uri: string
+ client_id: string
+ client_secret: string
+ vapid_key: string
+}
+
+export interface UserLogin {
+ server: string
+ token: string
+ account?: AccountCredentials
+}