Sync behaviour improvments

This commit is contained in:
tapframe 2025-09-18 15:32:05 +05:30
parent 62defd4773
commit abfccd0e36
3 changed files with 57 additions and 14 deletions

View file

@ -10,6 +10,7 @@ type AccountContextValue = {
signIn: (email: string, password: string) => Promise<string | null>;
signUp: (email: string, password: string) => Promise<string | null>;
signOut: () => Promise<void>;
refreshCurrentUser: () => Promise<void>;
updateProfile: (partial: { avatarUrl?: string; displayName?: string }) => Promise<string | null>;
};
@ -47,16 +48,21 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
// Auth state listener
const { data: subscription } = supabase.auth.onAuthStateChange(async (_event, session) => {
const fullUser = session?.user ? await accountService.getCurrentUser() : null;
setUser(fullUser);
if (fullUser) {
await syncService.migrateLocalScopeToUser();
await syncService.subscribeRealtime();
// Pull first to hydrate local state, then push to avoid wiping server with empty local
await syncService.fullPull();
await syncService.fullPush();
} else {
syncService.unsubscribeRealtime();
setLoading(true);
try {
const fullUser = session?.user ? await accountService.getCurrentUser() : null;
setUser(fullUser);
if (fullUser) {
await syncService.migrateLocalScopeToUser();
await syncService.subscribeRealtime();
// Pull first to hydrate local state, then push to avoid wiping server with empty local
await syncService.fullPull();
await syncService.fullPush();
} else {
syncService.unsubscribeRealtime();
}
} finally {
setLoading(false);
}
});
@ -81,6 +87,15 @@ export const AccountProvider: React.FC<{ children: React.ReactNode }> = ({ child
await accountService.signOut();
setUser(null);
},
refreshCurrentUser: async () => {
setLoading(true);
try {
const u = await accountService.getCurrentUser();
setUser(u);
} finally {
setLoading(false);
}
},
updateProfile: async (partial) => {
const err = await accountService.updateProfile(partial);
if (!err) {

View file

@ -231,7 +231,7 @@ const SettingsScreen: React.FC = () => {
const { isAuthenticated, userProfile, refreshAuthStatus } = useTraktContext();
const { currentTheme } = useTheme();
const insets = useSafeAreaInsets();
const { user, signOut, loading: accountLoading } = useAccount();
const { user, signOut, loading: accountLoading, refreshCurrentUser } = useAccount();
// Tablet-specific state
const [selectedCategory, setSelectedCategory] = useState('account');
@ -248,10 +248,12 @@ const SettingsScreen: React.FC = () => {
if (__DEV__) console.log('SettingsScreen focused, refreshing auth status. Current state:', { isAuthenticated, userProfile: userProfile?.username });
}
refreshAuthStatus();
// Also refresh account user in case we returned from auth flow
refreshCurrentUser();
});
return unsubscribe;
}, [navigation, isAuthenticated, userProfile, refreshAuthStatus]);
}, [navigation, isAuthenticated, userProfile, refreshAuthStatus, refreshCurrentUser]);
// States for dynamic content
const [addonCount, setAddonCount] = useState<number>(0);

View file

@ -693,6 +693,8 @@ class SyncService {
(stremioService as any).addonOrder = order;
await (stremioService as any).saveInstalledAddons();
await (stremioService as any).saveAddonOrder();
// Mark addons initialized for this user to prevent destructive merges on first push
try { await AsyncStorage.setItem(`@user:${userId}:addons_initialized`, 'true'); } catch {}
// Push merged order to server to preserve across devices
try {
const rows = order.map((addonId: string, idx: number) => ({
@ -848,9 +850,27 @@ class SyncService {
const user = await accountService.getCurrentUser();
if (!user) return;
const userId = user.id;
const addons = await stremioService.getInstalledAddonsAsync();
let addons = await stremioService.getInstalledAddonsAsync();
logger.log(`[Sync] push installed_addons count=${addons.length}`);
const order = (stremioService as any).addonOrder as string[];
let order = (stremioService as any).addonOrder as string[];
// Safety: if this is a first-time push and local addons are fewer than remote, pull before pushing
try {
const initialized = (await AsyncStorage.getItem(`@user:${userId}:addons_initialized`)) === 'true';
const { data: remoteBefore } = await supabase
.from('installed_addons')
.select('addon_id')
.eq('user_id', userId);
const remoteCount = (remoteBefore || []).length;
if (!initialized && remoteCount > addons.length) {
logger.log('[Sync] addons not initialized and local smaller than remote → pulling before push');
await this.pullAddonsSnapshot(userId);
// refresh local state after pull
addons = await stremioService.getInstalledAddonsAsync();
order = (stremioService as any).addonOrder as string[];
}
} catch {}
const rows = addons.map((a: any) => ({
user_id: userId,
addon_id: a.id,
@ -863,12 +883,17 @@ class SyncService {
manifest_data: a,
}));
// Delete remote addons that no longer exist locally (excluding pre-installed to be safe)
// Guard: do not perform deletions on first-time merge when remote has more addons
try {
const { data: remote, error: rErr } = await supabase
.from('installed_addons')
.select('addon_id')
.eq('user_id', userId);
if (!rErr && remote) {
const initialized = (await AsyncStorage.getItem(`@user:${userId}:addons_initialized`)) === 'true';
if (!initialized && (remote as any[]).length > addons.length) {
logger.log('[Sync] skipping deletions during first-time addon merge');
} else {
const localIds = new Set(addons.map((a: any) => a.id));
const toDeletePromises = (remote as any[])
.map(r => r.addon_id as string)
@ -894,6 +919,7 @@ class SyncService {
.in('addon_id', toDelete);
if (del.error && __DEV__) console.warn('[SyncService] delete addons error', del.error);
}
}
}
} catch (e) {
if (__DEV__) console.warn('[SyncService] deletion sync for addons failed', e);