diff --git a/package.json b/package.json index eb2b673f..cdb2a2fc 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@react-navigation/stack": "^5.12.3", "@reduxjs/toolkit": "^1.4.0", "expo": "~39.0.4", - "expo-app-auth": "~9.2.0", + "expo-auth-session": "~2.0.0", "expo-av": "~8.6.0", "expo-image-picker": "~9.1.1", "expo-secure-store": "~9.2.0", diff --git a/src/@types/store.d.ts b/src/@types/app.d.ts similarity index 75% rename from src/@types/store.d.ts rename to src/@types/app.d.ts index 8e6dce7c..7c16c5a6 100644 --- a/src/@types/store.d.ts +++ b/src/@types/app.d.ts @@ -1,10 +1,4 @@ -declare namespace store { - type InstanceInfoState = { - local: string - localToken: string - remote: string - } - +declare namespace App { type Pages = | 'Following' | 'Local' diff --git a/src/@types/mastodon.d.ts b/src/@types/mastodon.d.ts index dadfab99..5676fc92 100644 --- a/src/@types/mastodon.d.ts +++ b/src/@types/mastodon.d.ts @@ -1,4 +1,4 @@ -declare namespace mastodon { +declare namespace Mastodon { type Account = { // Base id: string @@ -184,6 +184,14 @@ declare namespace mastodon { emojis: Emoji[] } + type Preferences = { + 'posting:default:visibility'?: 'public' | 'unlisted' | 'private' | 'direct' + 'posting:default:sensitive'?: boolean + 'posting:default:language'?: string + 'reading:expand:media'?: 'default' | 'show_all' | 'hide_all' + 'reading:expand:spoilers'?: boolean + } + type Status = { // Base id: string diff --git a/src/api/client.ts b/src/api/client.ts index 08c548f0..67c3057b 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -22,8 +22,9 @@ const client = async ({ } body?: FormData }): Promise => { - const state: RootState['instanceInfo'] = store.getState().instanceInfo - const url = instanceUrl || store.getState().instanceInfo[instance] + const state: RootState['instances'] = store.getState().instances + const url = + instance === 'remote' ? instanceUrl || state.remote.url : state.local.url let response // try { @@ -35,7 +36,7 @@ const client = async ({ 'Content-Type': 'application/json', ...headers, ...(instance === 'local' && { - Authorization: `Bearer ${state.localToken}` + Authorization: `Bearer ${state.local.token}` }) }, ...(body && { body: body }), diff --git a/src/components/ParseContent.tsx b/src/components/ParseContent.tsx index 192283ca..bf5c5cb8 100644 --- a/src/components/ParseContent.tsx +++ b/src/components/ParseContent.tsx @@ -15,7 +15,7 @@ const renderNode = ({ node: HTMLViewNode index: number navigation: any - mentions?: mastodon.Mention[] + mentions?: Mastodon.Mention[] showFullLink: boolean }) => { if (node.name == 'a') { @@ -80,9 +80,9 @@ const renderNode = ({ export interface Props { content: string - emojis?: mastodon.Emoji[] + emojis?: Mastodon.Emoji[] emojiSize?: number - mentions?: mastodon.Mention[] + mentions?: Mastodon.Mention[] showFullLink?: boolean linesTruncated?: number } diff --git a/src/components/Status/Actioned.tsx b/src/components/Status/Actioned.tsx index 61209830..03afeb0b 100644 --- a/src/components/Status/Actioned.tsx +++ b/src/components/Status/Actioned.tsx @@ -7,7 +7,7 @@ import Emojis from './Emojis' export interface Props { action: 'favourite' | 'follow' | 'mention' | 'poll' | 'reblog' name?: string - emojis?: mastodon.Emoji[] + emojis?: Mastodon.Emoji[] notification?: boolean } diff --git a/src/components/Status/ActionsStatus.tsx b/src/components/Status/ActionsStatus.tsx index 12cfd549..2cddddf4 100644 --- a/src/components/Status/ActionsStatus.tsx +++ b/src/components/Status/ActionsStatus.tsx @@ -10,10 +10,11 @@ import { } from 'react-native' import Toast from 'react-native-toast-message' import { useMutation, useQueryCache } from 'react-query' -import { useSelector } from 'react-redux' import { Feather } from '@expo/vector-icons' import client from 'src/api/client' +import { getLocalAccountId } from 'src/stacks/common/instancesSlice' +import store from 'src/stacks/common/store' const fireMutation = async ({ id, @@ -106,12 +107,12 @@ const fireMutation = async ({ } export interface Props { - queryKey: store.QueryKey - status: mastodon.Status + queryKey: App.QueryKey + status: Mastodon.Status } const ActionsStatus: React.FC = ({ queryKey, status }) => { - const localAccountId = useSelector(state => state.instanceInfo.localAccountId) + const localAccountId = getLocalAccountId(store.getState()) const [modalVisible, setModalVisible] = useState(false) const queryCache = useQueryCache() @@ -129,7 +130,7 @@ const ActionsStatus: React.FC = ({ queryKey, status }) => { // oldData && // oldData.map((paging: any) => { // paging.toots.map( - // (status: mastodon.Status | mastodon.Notification, i: number) => { + // (status: Mastodon.Status | Mastodon.Notification, i: number) => { // if (status.id === newData.id) { // paging.toots[i] = newData // } diff --git a/src/components/Status/Attachment.tsx b/src/components/Status/Attachment.tsx index bfc3fea8..860c555e 100644 --- a/src/components/Status/Attachment.tsx +++ b/src/components/Status/Attachment.tsx @@ -5,7 +5,7 @@ import AttachmentImage from './Attachment/AttachmentImage' import AttachmentVideo from './Attachment/AttachmentVideo' export interface Props { - media_attachments: mastodon.Attachment[] + media_attachments: Mastodon.Attachment[] sensitive: boolean width: number } diff --git a/src/components/Status/Attachment/AttachmentImage.tsx b/src/components/Status/Attachment/AttachmentImage.tsx index baaaba61..ce8437b6 100644 --- a/src/components/Status/Attachment/AttachmentImage.tsx +++ b/src/components/Status/Attachment/AttachmentImage.tsx @@ -3,7 +3,7 @@ import { Button, Image, Modal, StyleSheet, Pressable, View } from 'react-native' import ImageViewer from 'react-native-image-zoom-viewer' export interface Props { - media_attachments: mastodon.Attachment[] + media_attachments: Mastodon.Attachment[] sensitive: boolean width: number } diff --git a/src/components/Status/Attachment/AttachmentVideo.tsx b/src/components/Status/Attachment/AttachmentVideo.tsx index 8fa0c110..5fc1ee1c 100644 --- a/src/components/Status/Attachment/AttachmentVideo.tsx +++ b/src/components/Status/Attachment/AttachmentVideo.tsx @@ -4,7 +4,7 @@ import { Video } from 'expo-av' import { Feather } from '@expo/vector-icons' export interface Props { - media_attachments: mastodon.Attachment[] + media_attachments: Mastodon.Attachment[] sensitive: boolean width: number } diff --git a/src/components/Status/Card.tsx b/src/components/Status/Card.tsx index 7f824169..c21d0c7f 100644 --- a/src/components/Status/Card.tsx +++ b/src/components/Status/Card.tsx @@ -3,7 +3,7 @@ import { Image, Pressable, StyleSheet, Text, View } from 'react-native' import { useNavigation } from '@react-navigation/native' export interface Props { - card: mastodon.Card + card: Mastodon.Card } const Card: React.FC = ({ card }) => { diff --git a/src/components/Status/Content.tsx b/src/components/Status/Content.tsx index a1426591..2b19138e 100644 --- a/src/components/Status/Content.tsx +++ b/src/components/Status/Content.tsx @@ -6,8 +6,8 @@ import ParseContent from 'src/components/ParseContent' export interface Props { content: string - emojis: mastodon.Emoji[] - mentions: mastodon.Mention[] + emojis: Mastodon.Emoji[] + mentions: Mastodon.Mention[] spoiler_text?: string } diff --git a/src/components/Status/Emojis.tsx b/src/components/Status/Emojis.tsx index 3b639822..e1644d2e 100644 --- a/src/components/Status/Emojis.tsx +++ b/src/components/Status/Emojis.tsx @@ -5,7 +5,7 @@ const regexEmoji = new RegExp(/(:[a-z0-9_]+:)/) export interface Props { content: string - emojis: mastodon.Emoji[] + emojis: Mastodon.Emoji[] dimension: number } diff --git a/src/components/Status/Header.tsx b/src/components/Status/Header.tsx index 90931e7b..0d3e598a 100644 --- a/src/components/Status/Header.tsx +++ b/src/components/Status/Header.tsx @@ -9,6 +9,11 @@ import Emojis from './Emojis' import relativeTime from 'src/utils/relativeTime' import client from 'src/api/client' import { useSelector } from 'react-redux' +import { + getLocalAccountId, + getLocalUrl +} from 'src/stacks/common/instancesSlice' +import store from 'src/stacks/common/store' const fireMutation = async ({ id, @@ -116,14 +121,14 @@ const fireMutation = async ({ } export interface Props { - queryKey: store.QueryKey + queryKey: App.QueryKey accountId: string domain: string name: string - emojis?: mastodon.Emoji[] + emojis?: Mastodon.Emoji[] account: string created_at: string - application?: mastodon.Application + application?: Mastodon.Application } const Header: React.FC = ({ @@ -137,8 +142,8 @@ const Header: React.FC = ({ application }) => { const navigation = useNavigation() - const localAccountId = useSelector(state => state.instanceInfo.localAccountId) - const localDomain = useSelector(state => state.instanceInfo.local) + const localAccountId = getLocalAccountId(store.getState()) + const localDomain = getLocalUrl(store.getState()) const [since, setSince] = useState(relativeTime(created_at)) const [modalVisible, setModalVisible] = useState(false) @@ -158,7 +163,7 @@ const Header: React.FC = ({ // oldData && // oldData.map((paging: any) => { // paging.toots.map( - // (status: mastodon.Status | mastodon.Notification, i: number) => { + // (status: Mastodon.Status | Mastodon.Notification, i: number) => { // if (status.id === newData.id) { // paging.toots[i] = newData // } diff --git a/src/components/Status/Poll.tsx b/src/components/Status/Poll.tsx index a3c78b53..4ae0a302 100644 --- a/src/components/Status/Poll.tsx +++ b/src/components/Status/Poll.tsx @@ -4,7 +4,7 @@ import { StyleSheet, Text, View } from 'react-native' import Emojis from './Emojis' export interface Props { - poll: mastodon.Poll + poll: Mastodon.Poll } // When haven't voted, result should not be shown but intead let people vote const Poll: React.FC = ({ poll }) => { diff --git a/src/components/StatusInNotifications.tsx b/src/components/StatusInNotifications.tsx index 8d38d715..ec8534da 100644 --- a/src/components/StatusInNotifications.tsx +++ b/src/components/StatusInNotifications.tsx @@ -12,8 +12,8 @@ import Card from './Status/Card' import ActionsStatus from './Status/ActionsStatus' export interface Props { - notification: mastodon.Notification - queryKey: store.QueryKey + notification: Mastodon.Notification + queryKey: App.QueryKey } const TootNotification: React.FC = ({ notification, queryKey }) => { diff --git a/src/components/StatusInTimeline.tsx b/src/components/StatusInTimeline.tsx index f162d670..1dc783bb 100644 --- a/src/components/StatusInTimeline.tsx +++ b/src/components/StatusInTimeline.tsx @@ -12,8 +12,8 @@ import Card from './Status/Card' import ActionsStatus from './Status/ActionsStatus' export interface Props { - status: mastodon.Status - queryKey: store.QueryKey + status: Mastodon.Status + queryKey: App.QueryKey } const StatusInTimeline: React.FC = ({ status, queryKey }) => { diff --git a/src/stacks/Me/Authentication/Instance.tsx b/src/stacks/Me/Authentication/Instance.tsx index 9268b830..90c459db 100644 --- a/src/stacks/Me/Authentication/Instance.tsx +++ b/src/stacks/Me/Authentication/Instance.tsx @@ -1,16 +1,23 @@ -import React, { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { Button, Text, TextInput, View } from 'react-native' -import { StackNavigationProp } from '@react-navigation/stack' import { useQuery } from 'react-query' import { debounce } from 'lodash' import { instanceFetch } from 'src/stacks/common/instanceFetch' -import { ScreenMeAuthentication } from '../Authentication' import client from 'src/api/client' -import * as AppAuth from 'expo-app-auth' +import * as AuthSession from 'expo-auth-session' +import { useDispatch } from 'react-redux' +import { updateLocal } from 'src/stacks/common/instancesSlice' +import { useNavigation } from '@react-navigation/native' const Instance: React.FC = () => { + const navigation = useNavigation() + const dispatch = useDispatch() const [instance, setInstance] = useState('') + const [applicationData, setApplicationData] = useState<{ + clientId: string + clientSecret: string + }>() const { isSuccess, refetch, data } = useQuery( ['Instance', { instance }], @@ -25,6 +32,7 @@ const Instance: React.FC = () => { debounce( text => { setInstance(text) + setApplicationData(undefined) refetch() }, 1000, @@ -35,26 +43,10 @@ const Instance: React.FC = () => { [] ) - const signInAsync = async (id: string) => { - let authState = await AppAuth.authAsync({ - issuer: `https://${instance}`, - scopes: ['read', 'write', 'follow', 'push'], - clientId: id, - redirectUrl: 'exp://127.0.0.1:19000', - serviceConfiguration: { - authorizationEndpoint: `https://${instance}/oauth/authorize`, - revocationEndpoint: `https://${instance}/oauth/revoke`, - tokenEndpoint: `https://${instance}/oauth/token` - }, - additionalParameters: { - response_type: 'code' - } - }) - console.log(authState) - return authState - } - - const oauthCreateApplication = async () => { + const createApplication = async () => { + if (applicationData) { + return Promise.resolve() + } const formData = new FormData() formData.append('client_name', 'test_dudu') formData.append('redirect_uris', 'exp://127.0.0.1:19000') @@ -68,20 +60,60 @@ const Instance: React.FC = () => { body: formData }) if (res.body?.client_id.length > 0) { - return Promise.resolve(res.body) + setApplicationData({ + clientId: res.body.client_id, + clientSecret: res.body.client_secret + }) + return Promise.resolve() } else { return Promise.reject() } } - const oauthFlow = async () => { - const applicationData = await oauthCreateApplication() - if (applicationData.client_id.length > 0) { - await signInAsync(applicationData.client_id) - } else { - console.error('Application data error') + const [request, response, promptAsync] = AuthSession.useAuthRequest( + { + clientId: applicationData?.clientId!, + clientSecret: applicationData?.clientSecret, + scopes: ['read', 'write', 'follow', 'push'], + redirectUri: 'exp://127.0.0.1:19000' + // usePKCE: false + }, + { + authorizationEndpoint: `https://${instance}/oauth/authorize` } - } + ) + + useEffect(() => { + ;(async () => { + if (request?.clientId) { + await promptAsync() + } + })() + }, [request]) + + useEffect(() => { + ;(async () => { + if (response?.type === 'success') { + const { accessToken } = await AuthSession.exchangeCodeAsync( + { + clientId: applicationData?.clientId!, + clientSecret: applicationData?.clientSecret, + scopes: ['read', 'write', 'follow', 'push'], + redirectUri: 'exp://127.0.0.1:19000', + code: response.params.code, + extraParams: { + grant_type: 'authorization_code' + } + }, + { + tokenEndpoint: `https://${instance}/oauth/token` + } + ) + dispatch(updateLocal({ url: instance, token: accessToken })) + navigation.navigate('Local') + } + })() + }, [response]) return ( @@ -99,7 +131,7 @@ const Instance: React.FC = () => {