PageSourceSearch

https://feedfluffy.com/supa-bridge.js

js feedfluffy.com collected 2026-10-02 01:28:59 UTC 10,889 bytes, 344 lines download raw bytes

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.