chore: Refactor RestService usage into a singleton pattern and remove redundant JWT token dependency
This commit is contained in:
@@ -12,16 +12,14 @@ import {
|
||||
Linking,
|
||||
Modal,
|
||||
} from "react-native";
|
||||
import {S3File, RestService} from "@/src/services/RestService";
|
||||
import {S3File, restService} from "@/src/services/RestService";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import {useColors} from "@/src/hooks/useColors";
|
||||
import {useAuth} from "@/src/stores/authStore";
|
||||
import {MaterialIcons} from '@expo/vector-icons';
|
||||
import { useMatrixStore } from "@/src/stores";
|
||||
import SaveToMatrixButton from "@/src/components/SaveToMatrixButton";
|
||||
|
||||
export default function ImageScreen() {
|
||||
const {token} = useAuth();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [files, setFiles] = useState<S3File[]>([]);
|
||||
const [showFiles, setShowFiles] = useState(false);
|
||||
@@ -35,7 +33,7 @@ export default function ImageScreen() {
|
||||
const fetchStoredFiles = async () => {
|
||||
setLoadingFiles(true);
|
||||
try {
|
||||
const response = await new RestService(token).getStoredFiles();
|
||||
const response = await restService.getStoredFiles();
|
||||
if (response.ok && response.data) {
|
||||
setFiles(response.data);
|
||||
setShowFiles(true);
|
||||
@@ -68,7 +66,7 @@ export default function ImageScreen() {
|
||||
formData.append('image', fileBlob, fileName);
|
||||
console.log("Web-Plattform: Blob angehängt mit Namen:", fileName);
|
||||
|
||||
const response = await new RestService(token).uploadFile(formData);
|
||||
const response = await restService.uploadFile(formData);
|
||||
|
||||
if (response.ok) {
|
||||
console.log("Datei erfolgreich hochgeladen");
|
||||
@@ -117,7 +115,7 @@ export default function ImageScreen() {
|
||||
|
||||
const viewFile = async (objectKey: string) => {
|
||||
try {
|
||||
const response = await new RestService(token).getFileUrl(objectKey);
|
||||
const response = await restService.getFileUrl(objectKey);
|
||||
if (response.ok && response.data.url) {
|
||||
const url = response.data.url;
|
||||
|
||||
@@ -137,7 +135,7 @@ export default function ImageScreen() {
|
||||
|
||||
const deleteFile = async (objectKey: string) => {
|
||||
try {
|
||||
const response = await new RestService(token).deleteFile(objectKey);
|
||||
const response = await restService.deleteFile(objectKey);
|
||||
if (response.ok) {
|
||||
console.log("Datei erfolgreich gelöscht");
|
||||
fetchStoredFiles();
|
||||
|
||||
+4
-10
@@ -4,7 +4,7 @@ import ThemedBackground from "@/src/components/themed/ThemedBackground";
|
||||
import ChangePasswordFeature from "@/src/components/ChangePasswordFeature";
|
||||
import ThemeToggleButton from "@/src/components/ThemeToggleButton";
|
||||
import SpotifyAuthButton from "@/src/components/SpotifyAuthButton";
|
||||
import {RestService, Token} from "@/src/services/RestService";
|
||||
import {restService, Token} from "@/src/services/RestService";
|
||||
|
||||
import {useAuth} from "@/src/stores/authStore";
|
||||
import {View, Text} from "react-native";
|
||||
@@ -12,7 +12,7 @@ import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import {useRouter} from "expo-router";
|
||||
|
||||
export default function SettingsScreen() {
|
||||
const {token: jwtToken, authenticatedUser, logout, refreshUser} = useAuth();
|
||||
const {authenticatedUser, logout, refreshUser} = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
const handleAuthSuccess = (token: Token) => {
|
||||
@@ -23,7 +23,7 @@ export default function SettingsScreen() {
|
||||
expirationDate: new Date(Date.now() + token.expires_in * 1000),
|
||||
};
|
||||
|
||||
new RestService(jwtToken).updateSelfSpotifyConfig(spotifyConfig).then((result) => {
|
||||
restService.updateSelfSpotifyConfig(spotifyConfig).then((result) => {
|
||||
console.log("Spotify Token gespeichert");
|
||||
console.log(result);
|
||||
|
||||
@@ -38,7 +38,6 @@ export default function SettingsScreen() {
|
||||
Hallo, {authenticatedUser?.name}
|
||||
</ThemedHeader>
|
||||
|
||||
{/* Erscheinungsbild Section */}
|
||||
<View className="bg-surface dark:bg-surface-dark rounded-2xl p-5">
|
||||
<Text className="text-base font-semibold text-onSurface dark:text-onSurface-dark mb-4">
|
||||
Erscheinungsbild
|
||||
@@ -51,7 +50,6 @@ export default function SettingsScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Konto Section */}
|
||||
<View className="bg-surface dark:bg-surface-dark rounded-2xl p-5">
|
||||
<Text className="text-base font-semibold text-onSurface dark:text-onSurface-dark mb-4">
|
||||
Konto
|
||||
@@ -61,7 +59,6 @@ export default function SettingsScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Integrationen Section */}
|
||||
<View className="bg-surface dark:bg-surface-dark rounded-2xl p-5">
|
||||
<Text className="text-base font-semibold text-onSurface dark:text-onSurface-dark mb-4">
|
||||
Integrationen
|
||||
@@ -69,7 +66,6 @@ export default function SettingsScreen() {
|
||||
<View className="gap-3">
|
||||
<SpotifyAuthButton
|
||||
onAuthSuccess={handleAuthSuccess}
|
||||
jwtToken={jwtToken}
|
||||
disabled={!!authenticatedUser?.spotifyConfig}
|
||||
/>
|
||||
{!!authenticatedUser?.spotifyConfig && (
|
||||
@@ -77,8 +73,7 @@ export default function SettingsScreen() {
|
||||
mode="outlined"
|
||||
title="Spotify trennen"
|
||||
onPress={() => {
|
||||
const rest = new RestService(jwtToken);
|
||||
rest.removeSpotifyConfig().then((result) => {
|
||||
restService.removeSpotifyConfig().then((result) => {
|
||||
console.log("Spotify Login entfernt");
|
||||
console.log(result);
|
||||
refreshUser();
|
||||
@@ -89,7 +84,6 @@ export default function SettingsScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Logout am Ende */}
|
||||
<View className="mt-auto pb-4">
|
||||
<ThemedButton
|
||||
mode="outlined"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, {useRef, useState} from "react";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { ApiResponse, RestService } from "@/src/services/RestService";
|
||||
import { useAuth } from "@/src/stores/authStore";
|
||||
import { ApiResponse, restService } from "@/src/services/RestService";
|
||||
import PasswordInput from "@/src/components/PasswordInput";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import { Text } from "react-native-paper";
|
||||
@@ -13,7 +12,6 @@ interface ChangePasswordFormProps {
|
||||
}
|
||||
|
||||
export default function ChangePasswordForm({ onSuccess, onCancel }: ChangePasswordFormProps) {
|
||||
const { token: jwtToken } = useAuth();
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [apiResponse, setApiResponse] = useState<ApiResponse<{ message: string }> | null>(null);
|
||||
@@ -30,7 +28,7 @@ export default function ChangePasswordForm({ onSuccess, onCancel }: ChangePasswo
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await new RestService(jwtToken).changeSelfPassword(password, confirmPassword);
|
||||
const response = await restService.changeSelfPassword(password, confirmPassword);
|
||||
setApiResponse(response);
|
||||
|
||||
if (response.ok) {
|
||||
|
||||
@@ -2,8 +2,7 @@ import React, { useState } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import { useMatrixStore } from "@/src/stores";
|
||||
import { useAuth } from "@/src/stores/authStore";
|
||||
import { RestService } from "@/src/services/RestService";
|
||||
import { restService } from "@/src/services/RestService";
|
||||
import { MatrixState } from "@/src/model/User";
|
||||
|
||||
interface SaveToMatrixButtonProps {
|
||||
@@ -12,7 +11,6 @@ interface SaveToMatrixButtonProps {
|
||||
}
|
||||
|
||||
export default function SaveToMatrixButton({ mode, className }: SaveToMatrixButtonProps) {
|
||||
const { token } = useAuth();
|
||||
const setGlobalMode = useMatrixStore((s) => s.setGlobalMode);
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -29,7 +27,7 @@ export default function SaveToMatrixButton({ mode, className }: SaveToMatrixButt
|
||||
|
||||
const updatedState = useMatrixStore.getState().matrixState;
|
||||
|
||||
const response = await new RestService(token).updateLastState(updatedState);
|
||||
const response = await restService.updateLastState(updatedState);
|
||||
|
||||
if (response.ok) {
|
||||
setFeedback({ type: 'success', message: 'Gespeichert!' });
|
||||
|
||||
@@ -5,12 +5,11 @@ import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
|
||||
interface SpotifyAuthButtonProps {
|
||||
onAuthSuccess: (token: Token) => void;
|
||||
jwtToken: string | null;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const SpotifyAuthButton = ({onAuthSuccess, jwtToken, disabled}: SpotifyAuthButtonProps) => {
|
||||
const {promptAuth, isReady, error} = useSpotifyAuth(onAuthSuccess, jwtToken);
|
||||
const SpotifyAuthButton = ({onAuthSuccess, disabled}: SpotifyAuthButtonProps) => {
|
||||
const {promptAuth, isReady, error} = useSpotifyAuth(onAuthSuccess);
|
||||
|
||||
if (error) {
|
||||
console.error('Spotify Auth Error:', error);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useEffect} from "react";
|
||||
import {makeRedirectUri, useAuthRequest} from "expo-auth-session";
|
||||
import {RestService, Token} from "@/src/services/RestService";
|
||||
import {restService, Token} from "@/src/services/RestService";
|
||||
|
||||
const discovery = {
|
||||
authorizationEndpoint: 'https://accounts.spotify.com/authorize',
|
||||
@@ -15,7 +15,6 @@ interface UseSpotifyAuthResult {
|
||||
|
||||
export const useSpotifyAuth = (
|
||||
onAuthSuccess: (token: Token) => void,
|
||||
jwtToken: string | null,
|
||||
): UseSpotifyAuthResult => {
|
||||
const [request, response, promptAsync] = useAuthRequest(
|
||||
{
|
||||
@@ -35,7 +34,7 @@ export const useSpotifyAuth = (
|
||||
if (response?.type === 'success') {
|
||||
try {
|
||||
const {code} = response.params;
|
||||
const res = (await new RestService(jwtToken).exchangeSpotifyCodeForToken(code));
|
||||
const res = (await restService.exchangeSpotifyCodeForToken(code));
|
||||
if (!res.ok) {
|
||||
console.log("Fehler beim token abrufen");
|
||||
return;
|
||||
|
||||
+24
-12
@@ -26,13 +26,18 @@ export interface S3File {
|
||||
size: number;
|
||||
}
|
||||
|
||||
// Token provider function type - will be set from authStore
|
||||
type TokenProvider = () => string | null;
|
||||
let tokenProvider: TokenProvider = () => null;
|
||||
|
||||
export const setTokenProvider = (provider: TokenProvider) => {
|
||||
tokenProvider = provider;
|
||||
};
|
||||
|
||||
class RestService {
|
||||
private readonly jwtToken: string | null;
|
||||
private api: AxiosInstance;
|
||||
|
||||
constructor(jwtToken: string | null) {
|
||||
this.jwtToken = jwtToken;
|
||||
|
||||
constructor() {
|
||||
this.api = axios.create({
|
||||
baseURL: API_URL,
|
||||
timeout: 10000, // Set a timeout for requests
|
||||
@@ -41,25 +46,29 @@ class RestService {
|
||||
|
||||
this.api.interceptors.request.use(
|
||||
(config) => {
|
||||
if (this.jwtToken) {
|
||||
config.headers.Authorization = `Bearer ${this.jwtToken}`;
|
||||
// Only use token for non-web platforms
|
||||
if (Platform.OS !== 'web') {
|
||||
const token = tokenProvider();
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
console.log('Request Config:', config);
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Request Error:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
this.api.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log('Response Data:', response.data);
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
console.error('Response Error:', error.response?.data || error.message);
|
||||
// Only log non-connection errors to avoid spam when backend is down
|
||||
if (error.code !== 'ECONNREFUSED' && error.code !== 'ERR_NETWORK') {
|
||||
console.error('Response Error:', error.response?.data || error.message);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
@@ -207,10 +216,13 @@ class RestService {
|
||||
);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error during request:', error);
|
||||
// Silently throw connection errors to avoid log spam
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {RestService};
|
||||
// Singleton instance
|
||||
const restService = new RestService();
|
||||
|
||||
export { RestService, restService };
|
||||
|
||||
+16
-16
@@ -3,7 +3,7 @@ import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { Platform } from 'react-native';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { User } from '@/src/model/User';
|
||||
import { RestService } from '@/src/services/RestService';
|
||||
import { restService, setTokenProvider } from '@/src/services/RestService';
|
||||
import { useMatrixStore } from './matrixStore';
|
||||
|
||||
const authStorage = {
|
||||
@@ -69,14 +69,12 @@ export const useAuthStore = create<AuthState>()(
|
||||
checkAuthStatus: async () => {
|
||||
const state = get();
|
||||
try {
|
||||
const token = Platform.OS === 'web' ? null : state.token;
|
||||
|
||||
if (Platform.OS !== 'web' && !token) {
|
||||
if (Platform.OS !== 'web' && !state.token) {
|
||||
set({ isAuthenticated: false, loading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await fetchUserAndInitMatrix(token);
|
||||
const user = await fetchUserAndInitMatrix();
|
||||
set({
|
||||
isAuthenticated: !!user,
|
||||
authenticatedUser: user,
|
||||
@@ -102,7 +100,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
set({ loading: true, error: null });
|
||||
|
||||
try {
|
||||
const response = await new RestService(null).login(username, password, stayLoggedIn);
|
||||
const response = await restService.login(username, password, stayLoggedIn);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Login failed:", response.data);
|
||||
@@ -118,12 +116,13 @@ export const useAuthStore = create<AuthState>()(
|
||||
let token: string | null = null;
|
||||
if (Platform.OS !== 'web') {
|
||||
token = response.data.token!;
|
||||
// Set token in state first so the token provider has access to it
|
||||
set({ token });
|
||||
}
|
||||
|
||||
const user = await fetchUserAndInitMatrix(token);
|
||||
const user = await fetchUserAndInitMatrix();
|
||||
|
||||
set({
|
||||
token,
|
||||
error: null,
|
||||
isAuthenticated: !!user,
|
||||
authenticatedUser: user,
|
||||
@@ -141,7 +140,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
logout: async () => {
|
||||
try {
|
||||
if (Platform.OS === 'web') {
|
||||
await new RestService(null).logout();
|
||||
await restService.logout();
|
||||
}
|
||||
} finally {
|
||||
useMatrixStore.getState().resetToDefaults();
|
||||
@@ -158,10 +157,9 @@ export const useAuthStore = create<AuthState>()(
|
||||
const state = get();
|
||||
console.log("refreshUser");
|
||||
|
||||
const token = Platform.OS === 'web' ? null : state.token;
|
||||
if (Platform.OS !== 'web' && !token) return;
|
||||
if (Platform.OS !== 'web' && !state.token) return;
|
||||
|
||||
const user = await fetchUserAndInitMatrix(token);
|
||||
const user = await fetchUserAndInitMatrix();
|
||||
if (user) {
|
||||
set({ authenticatedUser: user });
|
||||
}
|
||||
@@ -173,6 +171,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
partialize: (state) => ({ token: state.token }),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
if (state) {
|
||||
// Set the token provider so RestService can access the token
|
||||
setTokenProvider(() => useAuthStore.getState().token);
|
||||
state.setHydrated();
|
||||
state.checkAuthStatus();
|
||||
}
|
||||
@@ -181,16 +181,16 @@ export const useAuthStore = create<AuthState>()(
|
||||
)
|
||||
);
|
||||
|
||||
async function fetchUser(token: string | null): Promise<User | null> {
|
||||
const response = await new RestService(token).getSelf();
|
||||
async function fetchUser(): Promise<User | null> {
|
||||
const response = await restService.getSelf();
|
||||
if (!response.ok || !response.data) {
|
||||
return null;
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function fetchUserAndInitMatrix(token: string | null): Promise<User | null> {
|
||||
const user = await fetchUser(token);
|
||||
async function fetchUserAndInitMatrix(): Promise<User | null> {
|
||||
const user = await fetchUser();
|
||||
if (user?.lastState) {
|
||||
useMatrixStore.getState().initializeFromUser(user.lastState);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user