[READ ONLY MIRROR] Open Source TikTok alternative built on AT Protocol github.com/sprksocial/client
flutter atproto video dart
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

secure store e coisa

+116 -76
+5
.prettierrc
··· 1 + { 2 + "singleQuote": true, 3 + "semi": false, 4 + "printWidth": 130 5 + }
+4 -4
api/pdsAuth.ts
··· 52 52 active: data.active ?? false, 53 53 }; 54 54 55 - sessionManager.setSession(agent, session); 55 + await sessionManager.setSession(agent, session); 56 56 57 57 return data; 58 58 } catch (error) { ··· 88 88 active: true, 89 89 }; 90 90 91 - sessionManager.setSession(agent, session); 91 + await sessionManager.setSession(agent, session); 92 92 } catch (error) { 93 93 console.error("Register failed:", error); 94 94 throw error; ··· 98 98 /** 99 99 * Logout the current user 100 100 */ 101 - export const pdsLogout = () => { 102 - sessionManager.clearSession(); 101 + export const pdsLogout = async () => { 102 + await sessionManager.clearSession(); 103 103 }; 104 104 105 105 /**
+57 -35
api/sessionManager.ts
··· 1 1 import EventEmitter from "events"; 2 2 import { Agent, AtpSessionData, CredentialSession } from "@atproto/api"; 3 + import * as SecureStore from 'expo-secure-store'; 3 4 4 5 export enum SessionEventType { 5 6 CREATE = "session:create", ··· 21 22 22 23 constructor() { 23 24 super(); 24 - this.loadSession(); 25 + // Immediately invoke an async function to handle async initialization 26 + (async () => { 27 + await this.loadSession(); 28 + })().catch(error => { 29 + console.error('Failed to load session during initialization:', error); 30 + }); 25 31 } 26 32 27 33 /** ··· 41 47 /** 42 48 * Store a new session 43 49 */ 44 - setSession(agent: Agent, session: AtpSessionData): void { 50 + async setSession(agent: Agent, session: AtpSessionData): Promise<void> { 45 51 this.agent = agent; 46 52 this.sessionData = session; 47 53 this.pdsUrl = session.accessJwt ··· 50 56 "" 51 57 ) 52 58 : ""; 53 - this.persistSession(); 59 + await this.persistSession(); 54 60 // this.scheduleRefresh(); 55 61 this.emit(SessionEventType.CREATE, session); 56 62 } ··· 96 102 /** 97 103 * Clear the current session (logout) 98 104 */ 99 - clearSession(): void { 105 + async clearSession(): Promise<void> { 100 106 if (this.refreshTimeout) { 101 107 clearTimeout(this.refreshTimeout); 102 108 this.refreshTimeout = null; ··· 105 111 this.agent = null; 106 112 this.sessionData = null; 107 113 this.pdsUrl = ""; 108 - this.removePersistedSession(); 114 + await this.removePersistedSession(); 109 115 this.emit(SessionEventType.DELETE); 110 116 } 111 117 ··· 140 146 // } 141 147 142 148 /** 143 - * Persist session to storage 149 + * Persist session to storage using SecureStore 144 150 */ 145 - private persistSession(): void { 146 - if (typeof localStorage !== "undefined" && this.sessionData) { 147 - localStorage.setItem(this.persistKey, JSON.stringify(this.sessionData)); 151 + private async persistSession(): Promise<void> { 152 + if (this.sessionData) { 153 + try { 154 + await SecureStore.setItemAsync( 155 + this.persistKey, 156 + JSON.stringify(this.sessionData) 157 + ); 158 + } catch (error) { 159 + console.error('Error saving session to SecureStore:', error); 160 + } 148 161 } 149 162 } 150 163 151 164 /** 152 - * Remove persisted session from storage 165 + * Remove persisted session from SecureStore 153 166 */ 154 - private removePersistedSession(): void { 155 - if (typeof localStorage !== "undefined") { 156 - localStorage.removeItem(this.persistKey); 167 + private async removePersistedSession(): Promise<void> { 168 + try { 169 + await SecureStore.deleteItemAsync(this.persistKey); 170 + } catch (error) { 171 + console.error('Error removing session from SecureStore:', error); 157 172 } 158 173 } 159 174 160 175 /** 161 - * Load session from storage on startup 176 + * Load session from SecureStore on startup 162 177 */ 163 - private loadSession(): void { 164 - if (typeof localStorage !== "undefined") { 165 - try { 166 - const savedSession = localStorage.getItem(this.persistKey); 167 - if (savedSession) { 168 - this.sessionData = JSON.parse(savedSession); 178 + private async loadSession(): Promise<void> { 179 + try { 180 + const savedSession = await SecureStore.getItemAsync(this.persistKey); 169 181 170 - // Create a new agent with the saved data 171 - if (this.sessionData && this.sessionData.did) { 172 - if (!this.pdsUrl) { 173 - throw new Error("Could not find PDS URL for this account"); 174 - } 175 - const cred = new CredentialSession(new URL(this.pdsUrl)); 182 + if (savedSession) { 183 + this.sessionData = JSON.parse(savedSession); 176 184 177 - cred.resumeSession(this.sessionData); 178 - 179 - const agent = new Agent(cred); 185 + // Create a new agent with the saved data 186 + if (this.sessionData && this.sessionData.did) { 187 + // Extract the PDS URL from the access token 188 + this.pdsUrl = this.sessionData.accessJwt 189 + ? JSON.parse(atob(this.sessionData.accessJwt.split(".")[1])).aud.replace( 190 + "did:web:", 191 + "" 192 + ) 193 + : ""; 180 194 181 - // Restore the session 182 - this.agent = agent; 183 - // this.scheduleRefresh(); 195 + if (!this.pdsUrl) { 196 + throw new Error("Could not find PDS URL for this account"); 184 197 } 198 + const cred = new CredentialSession(new URL(`https://${this.pdsUrl}`)); 199 + cred.resumeSession(this.sessionData); 200 + 201 + const agent = new Agent(cred); 202 + 203 + // Restore the session 204 + this.agent = agent; 205 + // this.scheduleRefresh(); 185 206 } 186 - } catch (error) { 187 - console.error("Failed to load persisted session:", error); 188 - this.removePersistedSession(); 189 207 } 208 + } catch (error) { 209 + console.error('Error loading session from SecureStore:', error); 210 + // Clear any invalid or corrupted session data 211 + this.removePersistedSession(); 190 212 } 191 213 } 192 214 }
+2 -1
app.json
··· 35 35 } 36 36 ], 37 37 "expo-video", 38 - "react-native-video" 38 + "react-native-video", 39 + "expo-secure-store" 39 40 ], 40 41 "experiments": { 41 42 "typedRoutes": true
+8 -4
app/(tabs)/ProfileScreen.tsx
··· 185 185 } 186 186 187 187 // Handle logout 188 - const handleLogout = () => { 189 - logout(); 190 - // You might want to navigate to a different screen or refresh the UI 191 - console.log('User logged out'); 188 + const handleLogout = async () => { 189 + try { 190 + await logout(); 191 + // You might want to navigate to a different screen or refresh the UI 192 + console.log('User logged out'); 193 + } catch (error) { 194 + console.error('Error logging out:', error); 195 + } 192 196 }; 193 197 194 198 const styles = StyleSheet.create({
+21 -28
components/Image/ImageScreen.tsx
··· 1 - import React, { useState } from 'react'; 2 - import { View, StyleSheet, Image, ScrollView, Dimensions, NativeScrollEvent, NativeSyntheticEvent } from 'react-native'; 3 - import { BlurView } from 'expo-blur'; 4 - import { ThemedText } from '@/components/ThemedText'; 5 - import { ImageScreenProps } from '@/types/Interfaces'; 6 - import ImageIndex from './ImageIndex'; 7 - import VideoInfoOverlay from '../Video/VideoInfoOverlay'; 8 - import { LinearGradient } from 'expo-linear-gradient'; 1 + import React, { useState } from 'react' 2 + import { View, StyleSheet, Image, ScrollView, Dimensions, NativeScrollEvent, NativeSyntheticEvent } from 'react-native' 3 + import { BlurView } from 'expo-blur' 4 + import { ThemedText } from '@/components/ThemedText' 5 + import { ImageScreenProps } from '@/types/Interfaces' 6 + import ImageIndex from './ImageIndex' 7 + import VideoInfoOverlay from '../Video/VideoInfoOverlay' 8 + import { LinearGradient } from 'expo-linear-gradient' 9 9 10 10 export default function ImageScreen({ imageData }: ImageScreenProps) { 11 - const images = imageData?.embed?.images || []; 12 - const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); 13 - const TAB_BAR_HEIGHT = screenHeight * 0.1; 14 - const availableHeight = screenHeight - TAB_BAR_HEIGHT; 11 + const images = imageData?.embed?.images || [] 12 + const { width: screenWidth, height: screenHeight } = Dimensions.get('window') 13 + const TAB_BAR_HEIGHT = screenHeight * 0.1 14 + const availableHeight = screenHeight - TAB_BAR_HEIGHT 15 15 16 - const [currentIndex, setCurrentIndex] = useState(0); 16 + const [currentIndex, setCurrentIndex] = useState(0) 17 17 18 18 if (!images || images.length === 0) { 19 19 return ( 20 20 <View style={styles.noImageContainer}> 21 21 <ThemedText>No image available</ThemedText> 22 22 </View> 23 - ); 23 + ) 24 24 } 25 25 26 26 const handleScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => { 27 - const newIndex = Math.round(event.nativeEvent.contentOffset.x / screenWidth); 28 - setCurrentIndex(newIndex); 29 - }; 27 + const newIndex = Math.round(event.nativeEvent.contentOffset.x / screenWidth) 28 + setCurrentIndex(newIndex) 29 + } 30 30 31 31 return ( 32 32 <View style={{ ...styles.root, height: screenHeight - TAB_BAR_HEIGHT }}> 33 - 34 33 <VideoInfoOverlay videoData={imageData} /> 35 34 {images.length > 1 && ( 36 - 37 35 <View style={styles.pageIndexContainer}> 38 36 {images.map((_, index) => ( 39 37 <ImageIndex key={index} index={index} currentIndex={currentIndex} /> 40 38 ))} 41 - 42 39 </View> 43 - 44 40 )} 45 41 46 42 <ScrollView ··· 56 52 {images.map((img, index) => ( 57 53 <View key={index} style={[styles.pageContainer, { width: screenWidth }]}> 58 54 <Image source={{ uri: img.fullsize || '' }} style={styles.imageBg} resizeMode="cover" /> 59 - <BlurView intensity={50} style={styles.blurOverlay} tint="dark" /> 55 + <BlurView intensity={50} style={styles.blurOverlay} tint="dark" experimentalBlurMethod="dimezisBlurView" /> 60 56 <Image source={{ uri: img.fullsize || '' }} style={styles.image} resizeMode="contain" /> 61 57 </View> 62 58 ))} 63 59 64 - <LinearGradient 65 - colors={['transparent', 'rgba(0,0,0,0.8)']} 66 - style={styles.background} 67 - /> 60 + <LinearGradient colors={['transparent', 'rgba(0,0,0,0.8)']} style={styles.background} /> 68 61 </ScrollView> 69 62 </View> 70 - ); 63 + ) 71 64 } 72 65 73 66 const styles = StyleSheet.create({ ··· 129 122 bottom: 0, 130 123 height: 230, 131 124 }, 132 - }); 125 + })
+7 -3
hooks/useAtProto.ts
··· 73 73 }, []) 74 74 75 75 // Logout 76 - const logout = useCallback(() => { 77 - pdsLogout() 78 - }, []) 76 + const logout = useCallback(async () => { 77 + try { 78 + await pdsLogout(); 79 + } catch (err) { 80 + console.error('Logout failed:', err); 81 + } 82 + }, []); 79 83 80 84 return { 81 85 // State
+10
package-lock.json
··· 29 29 "expo-linking": "~7.0.4", 30 30 "expo-media-library": "~17.0.5", 31 31 "expo-router": "~4.0.16", 32 + "expo-secure-store": "~14.0.1", 32 33 "expo-splash-screen": "~0.29.20", 33 34 "expo-status-bar": "~2.0.1", 34 35 "expo-symbols": "~0.2.1", ··· 6953 6954 }, 6954 6955 "engines": { 6955 6956 "node": ">=10" 6957 + } 6958 + }, 6959 + "node_modules/expo-secure-store": { 6960 + "version": "14.0.1", 6961 + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-14.0.1.tgz", 6962 + "integrity": "sha512-QUS+j4+UG4jRQalgnpmTvvrFnMVLqPiUZRzYPnG3+JrZ5kwVW2w6YS3WWerPoR7C6g3y/a2htRxRSylsDs+TaQ==", 6963 + "license": "MIT", 6964 + "peerDependencies": { 6965 + "expo": "*" 6956 6966 } 6957 6967 }, 6958 6968 "node_modules/expo-splash-screen": {
+2 -1
package.json
··· 54 54 "react-native-video": "^6.10.0", 55 55 "react-native-videoeditorsdk": "^3.3.0", 56 56 "react-native-web": "~0.19.13", 57 - "react-native-webview": "13.12.5" 57 + "react-native-webview": "13.12.5", 58 + "expo-secure-store": "~14.0.1" 58 59 }, 59 60 "devDependencies": { 60 61 "@babel/core": "^7.25.2",