@adventurelabs/scout-core 1.0.54 → 1.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/events.d.ts +0 -4
- package/dist/helpers/events.js +7 -85
- package/dist/helpers/tags.d.ts +0 -1
- package/dist/helpers/tags.js +0 -42
- package/dist/hooks/useScoutDbListener.d.ts +1 -16
- package/dist/hooks/useScoutDbListener.js +149 -142
- package/dist/providers/ScoutRefreshProvider.d.ts +7 -0
- package/dist/supabase/server.d.ts +7 -0
- package/dist/types/supabase.d.ts +7 -0
- package/package.json +1 -1
package/dist/helpers/events.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { IEvent } from "../types/db";
|
|
2
1
|
import { IWebResponseCompatible } from "../types/requests";
|
|
3
2
|
import { EnumSessionsVisibility } from "../types/events";
|
|
4
|
-
export declare function server_get_events_by_herd(herd_id: number, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<IEvent[]>>;
|
|
5
|
-
export declare function server_get_more_events_by_herd(herd_id: number, offset: number, page_count: number | undefined, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<IEvent[]>>;
|
|
6
3
|
export declare function server_get_total_events_by_herd(herd_id: number, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<number>>;
|
|
7
|
-
export declare function server_create_event(newEvent: any): Promise<IWebResponseCompatible<boolean>>;
|
package/dist/helpers/events.js
CHANGED
|
@@ -1,79 +1,16 @@
|
|
|
1
1
|
"use server";
|
|
2
2
|
import { newServerClient } from "../supabase/server";
|
|
3
3
|
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
4
|
-
import { addSignedUrlsToEvents } from "./storage";
|
|
5
4
|
import { EnumSessionsVisibility } from "../types/events";
|
|
6
|
-
export async function server_get_events_by_herd(herd_id, sessions_visibility) {
|
|
7
|
-
const supabase = await newServerClient();
|
|
8
|
-
// fetch events and include devices
|
|
9
|
-
// sort by timestamp
|
|
10
|
-
let query = supabase
|
|
11
|
-
.from("events")
|
|
12
|
-
.select(`
|
|
13
|
-
*,
|
|
14
|
-
devices: devices!inner(*)
|
|
15
|
-
`)
|
|
16
|
-
.eq("devices.herd_id", herd_id);
|
|
17
|
-
// Apply session filter based on sessions_visibility
|
|
18
|
-
if (sessions_visibility === EnumSessionsVisibility.Only) {
|
|
19
|
-
query = query.not("session_id", "is", null);
|
|
20
|
-
}
|
|
21
|
-
else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
|
|
22
|
-
query = query.is("session_id", null);
|
|
23
|
-
}
|
|
24
|
-
const { data } = await query.order("timestamp_observation", { ascending: false });
|
|
25
|
-
// Add signed URLs to events using the same client
|
|
26
|
-
const eventsWithSignedUrls = data
|
|
27
|
-
? await addSignedUrlsToEvents(data, supabase)
|
|
28
|
-
: [];
|
|
29
|
-
// TODO: DETERMINE WHEN TO PASS ERROR
|
|
30
|
-
let response = IWebResponse.success(eventsWithSignedUrls);
|
|
31
|
-
return response.to_compatible();
|
|
32
|
-
}
|
|
33
|
-
export async function server_get_more_events_by_herd(herd_id, offset, page_count = 10, sessions_visibility) {
|
|
34
|
-
const from = offset * page_count;
|
|
35
|
-
const to = from + page_count - 1;
|
|
36
|
-
const supabase = await newServerClient();
|
|
37
|
-
// fetch events and include devices
|
|
38
|
-
// sort by timestamp
|
|
39
|
-
let query = supabase
|
|
40
|
-
.from("events")
|
|
41
|
-
.select(`
|
|
42
|
-
*,
|
|
43
|
-
devices: devices!inner(*)
|
|
44
|
-
`)
|
|
45
|
-
.eq("devices.herd_id", herd_id)
|
|
46
|
-
.range(from, to);
|
|
47
|
-
// Apply session filter based on sessions_visibility
|
|
48
|
-
if (sessions_visibility === EnumSessionsVisibility.Only) {
|
|
49
|
-
query = query.not("session_id", "is", null);
|
|
50
|
-
}
|
|
51
|
-
else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
|
|
52
|
-
query = query.is("session_id", null);
|
|
53
|
-
}
|
|
54
|
-
const { data } = await query.order("timestamp_observation", { ascending: false });
|
|
55
|
-
// Add signed URLs to events using the same client
|
|
56
|
-
const eventsWithSignedUrls = data
|
|
57
|
-
? await addSignedUrlsToEvents(data, supabase)
|
|
58
|
-
: [];
|
|
59
|
-
// TODO: DETERMINE WHEN TO PASS ERROR
|
|
60
|
-
let response = IWebResponse.success(eventsWithSignedUrls);
|
|
61
|
-
return response.to_compatible();
|
|
62
|
-
}
|
|
63
5
|
// function to get total number of events for a herd
|
|
64
6
|
export async function server_get_total_events_by_herd(herd_id, sessions_visibility) {
|
|
65
7
|
const supabase = await newServerClient();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
|
|
74
|
-
query = query.is("session_id", null);
|
|
75
|
-
}
|
|
76
|
-
const { count, error } = await query;
|
|
8
|
+
// Convert sessions_visibility to exclude_session_events boolean
|
|
9
|
+
const exclude_session_events = sessions_visibility === EnumSessionsVisibility.Exclude;
|
|
10
|
+
const { data, error } = (await supabase.rpc("get_total_events_for_herd_with_session_filter", {
|
|
11
|
+
herd_id_caller: herd_id,
|
|
12
|
+
exclude_session_events: exclude_session_events,
|
|
13
|
+
}));
|
|
77
14
|
if (error) {
|
|
78
15
|
return {
|
|
79
16
|
status: EnumWebResponse.ERROR,
|
|
@@ -82,21 +19,6 @@ export async function server_get_total_events_by_herd(herd_id, sessions_visibili
|
|
|
82
19
|
};
|
|
83
20
|
}
|
|
84
21
|
else {
|
|
85
|
-
return IWebResponse.success(
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
export async function server_create_event(newEvent) {
|
|
89
|
-
const supabase = await newServerClient();
|
|
90
|
-
// strip id field from herd object
|
|
91
|
-
const { data, error } = await supabase.from("events").insert([newEvent]);
|
|
92
|
-
if (error) {
|
|
93
|
-
return {
|
|
94
|
-
status: EnumWebResponse.ERROR,
|
|
95
|
-
msg: error.message,
|
|
96
|
-
data: null,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
return IWebResponse.success(true).to_compatible();
|
|
22
|
+
return IWebResponse.success(data || 0).to_compatible();
|
|
101
23
|
}
|
|
102
24
|
}
|
package/dist/helpers/tags.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { IEventWithTags, ITag } from "../types/db";
|
|
2
2
|
import { IWebResponseCompatible } from "../types/requests";
|
|
3
|
-
export declare function test_event_loading(device_id: number): Promise<boolean>;
|
|
4
3
|
export declare function server_create_tags(tags: ITag[]): Promise<IWebResponseCompatible<ITag[]>>;
|
|
5
4
|
export declare function server_delete_tags_by_ids(tag_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
|
|
6
5
|
export declare function server_update_tags(tags: ITag[]): Promise<IWebResponseCompatible<ITag[]>>;
|
package/dist/helpers/tags.js
CHANGED
|
@@ -3,25 +3,6 @@
|
|
|
3
3
|
import { newServerClient } from "../supabase/server";
|
|
4
4
|
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
5
5
|
import { addSignedUrlsToEvents, addSignedUrlToEvent } from "./storage";
|
|
6
|
-
// Test function to verify individual event loading works
|
|
7
|
-
export async function test_event_loading(device_id) {
|
|
8
|
-
try {
|
|
9
|
-
console.log(`[Event Test] Testing individual event loading for device ${device_id}`);
|
|
10
|
-
const events_response = await server_get_events_and_tags_for_device(device_id, 1);
|
|
11
|
-
if (events_response.status === EnumWebResponse.SUCCESS) {
|
|
12
|
-
console.log(`[Event Test] Successfully loaded ${events_response.data?.length || 0} events for device ${device_id}`);
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
console.error(`[Event Test] Failed to load events for device ${device_id}:`, events_response.msg);
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
console.error(`[Event Test] Failed to load events for device ${device_id}:`, error);
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
6
|
export async function server_create_tags(tags) {
|
|
26
7
|
const supabase = await newServerClient();
|
|
27
8
|
// remove id key from tags
|
|
@@ -94,29 +75,6 @@ export async function server_update_tags(tags) {
|
|
|
94
75
|
}
|
|
95
76
|
return IWebResponse.success(updatedTags).to_compatible();
|
|
96
77
|
}
|
|
97
|
-
// export async function server_get_events_with_tags_by_herd(
|
|
98
|
-
// herd_id: number
|
|
99
|
-
// ): Promise<IWebResponseCompatible<IEventWithTags[]>> {
|
|
100
|
-
// const supabase = await newServerClient();
|
|
101
|
-
// const { data, error } = await supabase
|
|
102
|
-
// .from("events")
|
|
103
|
-
// .select(
|
|
104
|
-
// `
|
|
105
|
-
// *,
|
|
106
|
-
// tags: tags (*)
|
|
107
|
-
// `
|
|
108
|
-
// )
|
|
109
|
-
// .eq("devices.herd_id", herd_id)
|
|
110
|
-
// .order("timestamp_observation", { ascending: false });
|
|
111
|
-
// if (error) {
|
|
112
|
-
// return {
|
|
113
|
-
// status: EnumWebResponse.ERROR,
|
|
114
|
-
// msg: error.message,
|
|
115
|
-
// data: [],
|
|
116
|
-
// };
|
|
117
|
-
// }
|
|
118
|
-
// return IWebResponse.success(data).to_compatible();
|
|
119
|
-
// }
|
|
120
78
|
export async function server_get_more_events_with_tags_by_herd(herd_id, offset, page_count = 10) {
|
|
121
79
|
const from = offset * page_count;
|
|
122
80
|
const to = from + page_count - 1;
|
|
@@ -1,18 +1,3 @@
|
|
|
1
1
|
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
2
|
import { Database } from "../types/supabase";
|
|
3
|
-
declare
|
|
4
|
-
DISCONNECTED = "disconnected",
|
|
5
|
-
CONNECTING = "connecting",
|
|
6
|
-
CONNECTED = "connected",
|
|
7
|
-
ERROR = "error"
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Hook for listening to real-time database changes
|
|
11
|
-
*/
|
|
12
|
-
export declare function useScoutDbListener(scoutSupabase: SupabaseClient<Database>): {
|
|
13
|
-
connectionState: ConnectionState;
|
|
14
|
-
lastError: string | null;
|
|
15
|
-
isConnected: boolean;
|
|
16
|
-
isConnecting: boolean;
|
|
17
|
-
};
|
|
18
|
-
export {};
|
|
3
|
+
export declare function useScoutDbListener(scoutSupabase: SupabaseClient<Database>): void;
|
|
@@ -1,170 +1,177 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useAppDispatch } from "../store/hooks";
|
|
3
|
-
import { useEffect, useRef
|
|
4
|
-
import { addDevice, addPlan, addTag, deleteDevice, deletePlan, deleteTag, updateDevice, updatePlan, updateTag, } from "../store/scout";
|
|
5
|
-
// Connection state enum
|
|
6
|
-
var ConnectionState;
|
|
7
|
-
(function (ConnectionState) {
|
|
8
|
-
ConnectionState["DISCONNECTED"] = "disconnected";
|
|
9
|
-
ConnectionState["CONNECTING"] = "connecting";
|
|
10
|
-
ConnectionState["CONNECTED"] = "connected";
|
|
11
|
-
ConnectionState["ERROR"] = "error";
|
|
12
|
-
})(ConnectionState || (ConnectionState = {}));
|
|
13
|
-
/**
|
|
14
|
-
* Hook for listening to real-time database changes
|
|
15
|
-
*/
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { addDevice, addPlan, addTag, addSessionToStore, deleteDevice, deletePlan, deleteSessionFromStore, deleteTag, updateDevice, updatePlan, updateSessionInStore, updateTag, } from "../store/scout";
|
|
16
5
|
export function useScoutDbListener(scoutSupabase) {
|
|
6
|
+
const supabase = useRef(null);
|
|
17
7
|
const channels = useRef([]);
|
|
18
8
|
const dispatch = useAppDispatch();
|
|
19
|
-
const
|
|
20
|
-
const
|
|
9
|
+
const reconnectTimeoutRef = useRef(null);
|
|
10
|
+
const reconnectAttemptsRef = useRef(0);
|
|
11
|
+
const maxReconnectAttempts = 10;
|
|
12
|
+
const baseDelay = 1000; // 1 second
|
|
13
|
+
const maxDelay = 5000; // 5 seconds
|
|
14
|
+
function handleTagInserts(payload) {
|
|
15
|
+
console.log("[DB Listener] Tag INSERT received:", payload.new);
|
|
16
|
+
dispatch(addTag(payload.new));
|
|
17
|
+
}
|
|
18
|
+
function handleTagDeletes(payload) {
|
|
19
|
+
console.log("[DB Listener] Tag DELETE received:", payload.old);
|
|
20
|
+
if (!payload.old || !payload.old.id) {
|
|
21
|
+
console.error("[DB Listener] Tag DELETE - Invalid payload, missing tag data");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
dispatch(deleteTag(payload.old));
|
|
25
|
+
}
|
|
26
|
+
function handleTagUpdates(payload) {
|
|
27
|
+
console.log("[DB Listener] Tag UPDATE received:", payload.new);
|
|
28
|
+
dispatch(updateTag(payload.new));
|
|
29
|
+
}
|
|
30
|
+
function handleDeviceInserts(payload) {
|
|
31
|
+
console.log("[DB Listener] Device INSERT received:", payload.new);
|
|
32
|
+
dispatch(addDevice(payload.new));
|
|
33
|
+
}
|
|
34
|
+
function handleDeviceDeletes(payload) {
|
|
35
|
+
console.log("[DB Listener] Device DELETE received:", payload.old);
|
|
36
|
+
dispatch(deleteDevice(payload.old));
|
|
37
|
+
}
|
|
38
|
+
function handleDeviceUpdates(payload) {
|
|
39
|
+
console.log("[DB Listener] Device UPDATE received:", payload.new);
|
|
40
|
+
dispatch(updateDevice(payload.new));
|
|
41
|
+
}
|
|
42
|
+
function handlePlanInserts(payload) {
|
|
43
|
+
console.log("[DB Listener] Plan INSERT received:", payload.new);
|
|
44
|
+
dispatch(addPlan(payload.new));
|
|
45
|
+
}
|
|
46
|
+
function handlePlanDeletes(payload) {
|
|
47
|
+
console.log("[DB Listener] Plan DELETE received:", payload.old);
|
|
48
|
+
dispatch(deletePlan(payload.old));
|
|
49
|
+
}
|
|
50
|
+
function handlePlanUpdates(payload) {
|
|
51
|
+
console.log("[DB Listener] Plan UPDATE received:", payload.new);
|
|
52
|
+
dispatch(updatePlan(payload.new));
|
|
53
|
+
}
|
|
54
|
+
function handleSessionInserts(payload) {
|
|
55
|
+
console.log("[DB Listener] Session INSERT received:", payload.new);
|
|
56
|
+
dispatch(addSessionToStore(payload.new));
|
|
57
|
+
}
|
|
58
|
+
function handleSessionDeletes(payload) {
|
|
59
|
+
console.log("[DB Listener] Session DELETE received:", payload.old);
|
|
60
|
+
if (!payload.old || !payload.old.id) {
|
|
61
|
+
console.error("[DB Listener] Session DELETE - Invalid payload, missing session data");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
dispatch(deleteSessionFromStore(payload.old));
|
|
65
|
+
}
|
|
66
|
+
function handleSessionUpdates(payload) {
|
|
67
|
+
console.log("[DB Listener] Session UPDATE received:", payload.new);
|
|
68
|
+
dispatch(updateSessionInStore(payload.new));
|
|
69
|
+
}
|
|
70
|
+
function handleConnectivityInserts(payload) {
|
|
71
|
+
console.log("[DB Listener] Connectivity INSERT received:", payload.new);
|
|
72
|
+
}
|
|
73
|
+
function handleConnectivityDeletes(payload) {
|
|
74
|
+
console.log("[DB Listener] Connectivity DELETE received:", payload.old);
|
|
75
|
+
}
|
|
76
|
+
function handleConnectivityUpdates(payload) {
|
|
77
|
+
console.log("[DB Listener] Connectivity UPDATE received:", payload.new);
|
|
78
|
+
}
|
|
21
79
|
// Clean up all channels
|
|
22
80
|
const cleanupChannels = () => {
|
|
23
81
|
channels.current.forEach((channel) => {
|
|
24
|
-
if (channel
|
|
25
|
-
|
|
26
|
-
scoutSupabase.removeChannel(channel);
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
console.warn("[DB Listener] Error removing channel:", error);
|
|
30
|
-
}
|
|
82
|
+
if (channel) {
|
|
83
|
+
scoutSupabase.removeChannel(channel);
|
|
31
84
|
}
|
|
32
85
|
});
|
|
33
86
|
channels.current = [];
|
|
34
87
|
};
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
tags: {
|
|
38
|
-
INSERT: (payload) => {
|
|
39
|
-
if (payload.new)
|
|
40
|
-
dispatch(addTag(payload.new));
|
|
41
|
-
},
|
|
42
|
-
UPDATE: (payload) => {
|
|
43
|
-
if (payload.new)
|
|
44
|
-
dispatch(updateTag(payload.new));
|
|
45
|
-
},
|
|
46
|
-
DELETE: (payload) => {
|
|
47
|
-
if (payload.old)
|
|
48
|
-
dispatch(deleteTag(payload.old));
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
devices: {
|
|
52
|
-
INSERT: (payload) => {
|
|
53
|
-
if (payload.new)
|
|
54
|
-
dispatch(addDevice(payload.new));
|
|
55
|
-
},
|
|
56
|
-
UPDATE: (payload) => {
|
|
57
|
-
if (payload.new)
|
|
58
|
-
dispatch(updateDevice(payload.new));
|
|
59
|
-
},
|
|
60
|
-
DELETE: (payload) => {
|
|
61
|
-
if (payload.old)
|
|
62
|
-
dispatch(deleteDevice(payload.old));
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
plans: {
|
|
66
|
-
INSERT: (payload) => {
|
|
67
|
-
if (payload.new)
|
|
68
|
-
dispatch(addPlan(payload.new));
|
|
69
|
-
},
|
|
70
|
-
UPDATE: (payload) => {
|
|
71
|
-
if (payload.new)
|
|
72
|
-
dispatch(updatePlan(payload.new));
|
|
73
|
-
},
|
|
74
|
-
DELETE: (payload) => {
|
|
75
|
-
if (payload.old)
|
|
76
|
-
dispatch(deletePlan(payload.old));
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
sessions: {
|
|
80
|
-
INSERT: (payload) => console.log("[DB Listener] Session INSERT:", payload),
|
|
81
|
-
UPDATE: (payload) => console.log("[DB Listener] Session UPDATE:", payload),
|
|
82
|
-
DELETE: (payload) => console.log("[DB Listener] Session DELETE:", payload),
|
|
83
|
-
},
|
|
84
|
-
connectivity: {
|
|
85
|
-
INSERT: (payload) => console.log("[DB Listener] Connectivity INSERT:", payload),
|
|
86
|
-
UPDATE: (payload) => console.log("[DB Listener] Connectivity UPDATE:", payload),
|
|
87
|
-
DELETE: (payload) => console.log("[DB Listener] Connectivity DELETE:", payload),
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
// Set up channels
|
|
91
|
-
const setupChannels = async () => {
|
|
88
|
+
// Setup channel with event handlers
|
|
89
|
+
const setupChannel = () => {
|
|
92
90
|
if (!scoutSupabase)
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
|
|
119
|
-
setLastError(`Channel subscription failed: ${status}`);
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
channels.current.push(channel);
|
|
91
|
+
return null;
|
|
92
|
+
const mainChannel = scoutSupabase.channel("schema_db_changes");
|
|
93
|
+
// Subscribe to all events
|
|
94
|
+
mainChannel
|
|
95
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "plans" }, handlePlanInserts)
|
|
96
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "plans" }, handlePlanDeletes)
|
|
97
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "plans" }, handlePlanUpdates)
|
|
98
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "devices" }, handleDeviceInserts)
|
|
99
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "devices" }, handleDeviceDeletes)
|
|
100
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "devices" }, handleDeviceUpdates)
|
|
101
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
|
|
102
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
|
|
103
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
|
|
104
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "connectivity" }, handleConnectivityInserts)
|
|
105
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "connectivity" }, handleConnectivityDeletes)
|
|
106
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "connectivity" }, handleConnectivityUpdates)
|
|
107
|
+
.on("postgres_changes", { event: "INSERT", schema: "public", table: "sessions" }, handleSessionInserts)
|
|
108
|
+
.on("postgres_changes", { event: "DELETE", schema: "public", table: "sessions" }, handleSessionDeletes)
|
|
109
|
+
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "sessions" }, handleSessionUpdates)
|
|
110
|
+
.subscribe((status) => {
|
|
111
|
+
console.log("[DB Listener] Subscription status:", status);
|
|
112
|
+
if (status === "SUBSCRIBED") {
|
|
113
|
+
console.log("[DB Listener] ✅ Successfully subscribed to real-time updates");
|
|
114
|
+
reconnectAttemptsRef.current = 0; // Reset reconnect attempts on successful connection
|
|
123
115
|
}
|
|
124
|
-
|
|
125
|
-
console.error(
|
|
116
|
+
else if (status === "CHANNEL_ERROR") {
|
|
117
|
+
console.error("[DB Listener] ❌ Channel error occurred. Reconnecting...");
|
|
118
|
+
handleReconnect();
|
|
126
119
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// Initialize connection
|
|
131
|
-
const initializeConnection = async () => {
|
|
132
|
-
if (!scoutSupabase)
|
|
133
|
-
return;
|
|
134
|
-
setConnectionState(ConnectionState.CONNECTING);
|
|
135
|
-
try {
|
|
136
|
-
// Test database connection
|
|
137
|
-
const { error } = await scoutSupabase.from("tags").select("id").limit(1);
|
|
138
|
-
if (error) {
|
|
139
|
-
throw new Error("Database connection test failed");
|
|
120
|
+
else if (status === "TIMED_OUT") {
|
|
121
|
+
console.error("[DB Listener] ⏰ Subscription timed out. Reconnecting...");
|
|
122
|
+
handleReconnect();
|
|
140
123
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw new Error("Channel setup failed");
|
|
124
|
+
else if (status === "CLOSED") {
|
|
125
|
+
console.log("[DB Listener] 🔒 Channel closed. Reconnecting...");
|
|
126
|
+
handleReconnect();
|
|
145
127
|
}
|
|
128
|
+
});
|
|
129
|
+
return mainChannel;
|
|
130
|
+
};
|
|
131
|
+
// Handle reconnection with exponential backoff
|
|
132
|
+
const handleReconnect = () => {
|
|
133
|
+
if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
|
|
134
|
+
console.error("[DB Listener] 🚫 Max reconnection attempts reached");
|
|
135
|
+
return;
|
|
146
136
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
// Clear any existing timeout
|
|
138
|
+
if (reconnectTimeoutRef.current) {
|
|
139
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
150
140
|
}
|
|
141
|
+
const delay = Math.min(baseDelay * (reconnectAttemptsRef.current + 1), maxDelay);
|
|
142
|
+
console.log(`[DB Listener] 🔄 Attempting reconnection in ${delay}ms (attempt ${reconnectAttemptsRef.current + 1}/${maxReconnectAttempts})`);
|
|
143
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
144
|
+
reconnectAttemptsRef.current++;
|
|
145
|
+
cleanupChannels();
|
|
146
|
+
const newChannel = setupChannel();
|
|
147
|
+
if (newChannel) {
|
|
148
|
+
channels.current.push(newChannel);
|
|
149
|
+
}
|
|
150
|
+
}, delay);
|
|
151
151
|
};
|
|
152
|
-
// Main effect
|
|
153
152
|
useEffect(() => {
|
|
154
153
|
if (!scoutSupabase) {
|
|
155
|
-
|
|
156
|
-
setLastError("No Supabase client available");
|
|
154
|
+
console.error("[DB Listener] No Supabase client available");
|
|
157
155
|
return;
|
|
158
156
|
}
|
|
159
|
-
|
|
157
|
+
supabase.current = scoutSupabase;
|
|
158
|
+
// Initial channel setup
|
|
159
|
+
const mainChannel = setupChannel();
|
|
160
|
+
if (mainChannel) {
|
|
161
|
+
channels.current.push(mainChannel);
|
|
162
|
+
}
|
|
163
|
+
// Cleanup function
|
|
160
164
|
return () => {
|
|
165
|
+
console.log("[DB Listener] 🧹 Cleaning up channels");
|
|
166
|
+
// Clear any pending reconnection attempts
|
|
167
|
+
if (reconnectTimeoutRef.current) {
|
|
168
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
169
|
+
reconnectTimeoutRef.current = null;
|
|
170
|
+
}
|
|
171
|
+
// Reset reconnect attempts
|
|
172
|
+
reconnectAttemptsRef.current = 0;
|
|
173
|
+
// Clean up channels
|
|
161
174
|
cleanupChannels();
|
|
162
175
|
};
|
|
163
|
-
}, [scoutSupabase]);
|
|
164
|
-
return {
|
|
165
|
-
connectionState,
|
|
166
|
-
lastError,
|
|
167
|
-
isConnected: connectionState === ConnectionState.CONNECTED,
|
|
168
|
-
isConnecting: connectionState === ConnectionState.CONNECTING,
|
|
169
|
-
};
|
|
176
|
+
}, [scoutSupabase, dispatch]);
|
|
170
177
|
}
|
|
@@ -716,6 +716,13 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
|
|
|
716
716
|
};
|
|
717
717
|
Returns: number;
|
|
718
718
|
};
|
|
719
|
+
get_total_events_for_herd_with_session_filter: {
|
|
720
|
+
Args: {
|
|
721
|
+
herd_id_caller: number;
|
|
722
|
+
exclude_session_events: boolean;
|
|
723
|
+
};
|
|
724
|
+
Returns: number;
|
|
725
|
+
};
|
|
719
726
|
get_zones_and_actions_for_herd: {
|
|
720
727
|
Args: {
|
|
721
728
|
herd_id_caller: number;
|
|
@@ -707,6 +707,13 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
|
|
|
707
707
|
};
|
|
708
708
|
Returns: number;
|
|
709
709
|
};
|
|
710
|
+
get_total_events_for_herd_with_session_filter: {
|
|
711
|
+
Args: {
|
|
712
|
+
herd_id_caller: number;
|
|
713
|
+
exclude_session_events: boolean;
|
|
714
|
+
};
|
|
715
|
+
Returns: number;
|
|
716
|
+
};
|
|
710
717
|
get_zones_and_actions_for_herd: {
|
|
711
718
|
Args: {
|
|
712
719
|
herd_id_caller: number;
|
package/dist/types/supabase.d.ts
CHANGED
|
@@ -776,6 +776,13 @@ export type Database = {
|
|
|
776
776
|
};
|
|
777
777
|
Returns: number;
|
|
778
778
|
};
|
|
779
|
+
get_total_events_for_herd_with_session_filter: {
|
|
780
|
+
Args: {
|
|
781
|
+
herd_id_caller: number;
|
|
782
|
+
exclude_session_events: boolean;
|
|
783
|
+
};
|
|
784
|
+
Returns: number;
|
|
785
|
+
};
|
|
779
786
|
get_zones_and_actions_for_herd: {
|
|
780
787
|
Args: {
|
|
781
788
|
herd_id_caller: number;
|