1window.Bridge = { 2 // 1. READ: Get the current user based on the URL (?user=scooby_doo) 3 getInitialSession: async function() { 4 const params = new URLSearchParams(window.location.search); 5 let userId = params.get('user'); 6 7 if (!userId && typeof _supabase !== 'undefined') { 8 const { data: { session } } = await _supabase.auth.getSession(); 9 userId = session?.user?.id; 10 } 11 12 if (!userId) return null; 13 14 console.log("[Bridge] High-Integrity Fetch for: " + userId); 15 16 // --- CLOCK SYNC START --- 17 try { 18 const { data: sTime, error: sError } = await _supabase.rpc('get_server_time'); 19 if (sTime) { 20 const serverNow = new Date(sTime).getTime(); 21 timeOffset = serverNow - Date.now(); 22 this.currentServerTime = sTime; 23 console.log("[Clock-Sync] Offset adjusted: " + timeOffset + "ms"); 24 } 25 } catch (e) { 26 //console.warn("Sync failed catch:", e); 27 supaHaltingMessage = 'Server time notice'; 28 return null; 29 } 30 // --- CLOCK SYNC END --- 31 32 const response = await _supabase 33 .from('fluffians') 34 .select('*, guardians(*)') 35 .eq('id', userId) 36 .single(); 37 38 const { data, error } = response; 39 40 if (error || !data) { 41 //console.error("[Bridge] Cloud Fetch Failed:", error); 42 supaHaltingMessage = 'Table join notice'; 43 return null; 44 } 45 46 const { data: interest } = await _supabase 47 .from('md_interest') 48 .select('id') 49 .eq('user_id', userId) 50 .maybeSingle(); 51 52 // --- PROTEGE FETCH START --- 53 const { data: protegeRecords } = await _supabase 54 .from('guardians') 55 .select('id, fluffians(*)') 56 .eq('contact_email', data.email); 57 // --- PROTEGE FETCH END --- 58 59 const user = { ...data };
60 user.subscription = await this.getSubscription(userId); 61 user.sTime = this.currentServerTime; 62 user.guardians = data.guardians || []; 63 64 user.proteges = protegeRecords ? protegeRecords.map(r => ({ 65 relationshipId: r.id, 66 ...r.fluffians 67 })).filter(p => p.id) : []; 68 69 user.is_MDinterested = !!interest; 70 return user; 71 }, 72 73 saveMDInterest: async function() { 74 console.log('[Bridge] saveMDInterest called'); 75 76 // 1. Check Global Scope 77 if (typeof activeUser === 'undefined') { 78 console.error('[Bridge] ERROR: activeUser is NOT DEFINED in this scope.'); 79 return false; 80 } 81 82 if (!activeUser || !activeUser.id) { 83 console.error('[Bridge] ERROR: activeUser is missing or has no ID.', activeUser); 84 return false; 85 } 86 87 var client = _supabase; 88 if (!client) { 89 console.error('[Bridge] ERROR: _supabase client is missing from window.'); 90 return false; 91 } 92 93 console.log('[Bridge] Attempting insert for:', activeUser.email, 'ID:', activeUser.id); 94 95 try { 96 var result = await client 97 .from('md_interest') 98 .insert([{ 99 user_id: activeUser.id, 100 email: activeUser.email || '[email protected]' 101 }]); 102 103 // 2. Handle Supabase response 104 if (result.error) { 105 // Check for the 409 Spam Shield (code 23505) 106 if (result.error.code === '23505') { 107 console.info('[Bridge] Unique Constraint: User already on list (Success).'); 108 return true; 109 } 110 console.error('[Bridge] Supabase Insert Error:', result.error.message, result.error.code); 111 return false; 112 } 113 114 console.log('[Bridge] Save Successful.'); 115 return true; 116 117 } catch (err) { 118 console.error('[Bridge] Unexpected Exception:', err.message); 119 return false; 120 } 121 }, 122 123 // 2. READ: Get all guardians belonging to a specific user 124 getGuardians: async function(userId) { 125 if (!userId) return []; 126 127 128 // Fetch ALL rows for this user 129 const { data, error } = await _supabase 130 .from('guardians') 131 .select('*') 132 .eq('fluffian_id', userId); 133 134 if (error) { 135 console.error("[Bridge] Guardian Fetch Failed:", error); 136 return []; 137 } 138 139 // JS Filter: Only exclude those explicitly set to true 140 // This handles NULL, undefined, and false correctly 141 const activeGuardians = (data || []).filter(g => g.is_unsubscribed !== true); 142 143 return activeGuardians; 144 }, 145 146 147 // 1. UPDATE: Specifically update the feeding timestamp using Server Time 148 updateLastFedAt: async function(userId) { 149 // --- STEP A: Fetch the "True" Server Time --- 150 const { data: sTime, error: sError } = await _supabase.rpc('get_server_time'); 151 152 if (sError || !sTime) { 153 console.error("[Bridge] Failed to get server time, falling back to local:", sError); 154 } 155 156 // --- STEP B: Use Server Time if available, otherwise fallback to local --- 157 const finalTime = sTime ? new Date(sTime).getTime() : Date.now(); 158 159 const { error } = await _supabase 160 .from('fluffians') 161 .update({ last_fed_at: finalTime }) 162 .eq('id', userId); 163 164 if (error) { 165 console.error("[Bridge] Update Last Fed Failed:", error); 166 return false; 167 } 168 169 console.log("[Bridge] Fluffy Fed at (Sync):", new Date(finalTime).toISOString()); 170 return true; 171 }, 172 173 174 // 2. UPDATE: Specifically update the check-in frequency (e.g., '24h', '48h') 175 updateCheckInInterval: async function(userId, interval) { 176 const { error } = await _supabase 177 .from('fluffians') 178 .update({ check_in_interval: interval }) 179 .eq('id', userId); 180 181 if (error) { 182 console.error("[Bridge] Update Interval Failed:", error); 183 return false; 184 } 185 return true; 186 }, 187 188 // 3. UPDATE: Specifically acknowledge/clear a triggered alarm 189 clearAlarm: async function(userId) { 190 const { error } = await _supabase 191 .from('fluffians') 192 .update({ alarm_cleared: true }) 193 .eq('id', userId); 194 195 if (error) { 196 console.error("[Bridge] Clear Alarm Failed:", error); 197 return false;
198 } 199 return true; 200 }, 201 202 203 // 4. WRITE: Add a new guardian (Cloud-First) 204 addGuardian: async function(userId, email) { 205 const { data, error } = await _supabase 206 .from('guardians') 207 .insert([{ 208 fluffian_id: userId, 209 contact_email: email 210 }]) 211 .select() // Get the new UUID and row back 212 .single(); 213 214 if (error) { 215 console.error("[Bridge] Add Guardian Failed:", error.message); 216 return { success: false, message: error.message }; 217 } 218 219 console.log(`[Bridge] Guardian Added: ${data.id}`); 220 return { success: true, data: data }; // Returns the full object inside a success wrapper 221 }, 222 223 224 // 5. WRITE: Remove a guardian from the cloud 225 deleteGuardian: async function(guardianId) { 226 const { error } = await _supabase 227 .from('guardians') 228 .delete() 229 .eq('id', guardianId); 230 231 if (error) { 232 console.error("[Bridge] Delete Guardian Failed:", error); 233 return false; 234 } 235 236 console.log(`[Bridge] Guardian Deleted: ${guardianId}`); 237 return true; 238 }, 239 240 // 3. ATOMIC SYNC: Reset the timer and set the interval using Server Time 241 activateWatch: async function(userId, interval) { 242 // --- STEP A: Fetch the "True" Server Time --- 243 const { data: sTime, error: sError } = await _supabase.rpc('get_server_time'); 244 245 if (sError || !sTime) { 246 console.warn("[Bridge] Server sync failed during activation, falling back to local."); 247 } 248 249 // Use server time if available, otherwise local clock 250 const finalTime = sTime ? new Date(sTime).getTime() : Date.now(); 251 252 const { error } = await _supabase 253 .from('fluffians') 254 .update({ 255 check_in_interval: interval, 256 last_fed_at: finalTime, 257 alarm_cleared: true // Ensure state is clean 258 }) 259 .eq('id', userId); 260 261 if (error) { 262 console.error("[Bridge] Activate Watch Failed:", error); 263 return false; 264 } 265 266 console.log("[Bridge] Watch Activated (Sync):", interval + " @ " + new Date(finalTime).toISOString()); 267 return true; 268 }, 269 270 271 getSubscription: async function(userId) { 272 // Real DB Fetch: Get the single latest record for this user 273 const { data, error } = await _supabase 274 .from("subscriptions") 275 .select("tier, status, expires_at, starts_at") 276 .eq("user_id", userId) 277 .order("starts_at", { ascending: false }) 278 .limit(1) 279 .maybeSingle(); 280 281 const now = new Date(); 282 const sub = data; 283 284 // Logic: Is it 'active' AND not expired? 285 if (sub && sub.status === "active" && new Date(sub.expires_at) > now) { 286 console.log("[Bridge] Active Subscription Found: " + sub.tier.toLowerCase()); 287 return { 288 ...sub, 289 tier: sub.tier.toLowerCase() 290 }; 291 } 292 293 // Fallback: Return a consistent 'Free' object 294 return { 295 user_id: userId, 296 tier: "free", 297 status: "expired", 298 expires_at: sub ? sub.expires_at : null 299 }; 300 }, 301 302 // Add this to your Bridge object 303 blockProtege: async function(relationshipId, protegeId, guardianEmail) { 304 console.log(`[Bridge] Blocking Protege: ${protegeId} for ${guardianEmail}`); 305 306 // 1. ADD SUPPRESSION 307 const { error: blockError } = await _supabase 308 .from('email_suppressions') 309 .insert([ 310 { 311 email: guardianEmail, 312 scope: 'USER', 313 source_user_id: protegeId 314 } 315 ]); 316 317 if (blockError) { 318 console.error("[Bridge] Block Suppression Failed:", blockError); 319 return false; 320 } 321 322 // 2. DELETE RELATIONSHIP (Reuse existing logic) 323 return await this.deleteGuardian(relationshipId); 324 }, 325 326 327 deleteUserAccount: async function(userId) { 328 // This hits a custom Postgres function to wipe all rows linked to this ID 329 const { error } = await _supabase.rpc('delete_own_user_data'); 330 331 if (error) { 332 console.error("[Bridge] Delete Error:", error); 333 return false;
334 } 335 336 // Final Step: Sign the user out locally so the session is destroyed 337 await _supabase.auth.signOut(); 338 return true; 339 } 340 341 342 343}; 344
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.