@adventurelabs/scout-core 1.0.22 → 1.0.24

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.
@@ -1,3 +1,3 @@
1
1
  import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
- export declare function useScoutDbListener(supabaseClient: SupabaseClient<Database>): void;
3
+ export declare function useScoutDbListener(scoutSupabase: SupabaseClient<Database>): void;
@@ -2,38 +2,38 @@
2
2
  import { useAppDispatch } from "../store/hooks";
3
3
  import { useEffect, useRef } from "react";
4
4
  import { addDevice, addPlan, addTag, deleteDevice, deletePlan, deleteTag, updateDevice, updatePlan, updateTag, } from "../store/scout";
5
- import { get_device_by_id } from "../helpers/devices";
6
- import { EnumWebResponse } from "../types/requests";
7
- export function useScoutDbListener(supabaseClient) {
8
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "";
9
- const anon_key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
5
+ export function useScoutDbListener(scoutSupabase) {
10
6
  const supabase = useRef(null);
7
+ const channels = useRef([]);
11
8
  const dispatch = useAppDispatch();
12
- // Add instance tracking
13
- const instanceId = useRef(Math.random().toString(36).substr(2, 9));
14
- console.log(`[DB Listener] Hook initialized - Instance ID: ${instanceId.current}`);
15
9
  function handleTagInserts(payload) {
16
- console.log(`[DB Listener] Tag INSERT received (Instance ${instanceId.current}):`, payload.new);
10
+ console.log("[DB Listener] Tag INSERT received:", payload.new);
17
11
  dispatch(addTag(payload.new));
18
12
  }
19
13
  function handleTagDeletes(payload) {
20
- console.log(`[DB Listener] Tag DELETE received (Instance ${instanceId.current}):`, payload.old);
14
+ console.log("[DB Listener] Tag DELETE received:", payload.old);
15
+ console.log("[DB Listener] Tag DELETE - payload structure:", {
16
+ hasOld: !!payload.old,
17
+ oldId: payload.old?.id,
18
+ oldEventId: payload.old?.event_id,
19
+ oldClassName: payload.old?.class_name,
20
+ fullPayload: payload,
21
+ });
22
+ if (!payload.old || !payload.old.id) {
23
+ console.error("[DB Listener] Tag DELETE - Invalid payload, missing tag data");
24
+ return;
25
+ }
26
+ console.log("[DB Listener] Tag DELETE - Dispatching deleteTag action with ID:", payload.old.id);
21
27
  dispatch(deleteTag(payload.old));
22
28
  }
23
29
  function handleTagUpdates(payload) {
24
- console.log(`[DB Listener] Tag UPDATE received (Instance ${instanceId.current}):`, payload.new);
30
+ console.log("[DB Listener] Tag UPDATE received:", payload.new);
25
31
  dispatch(updateTag(payload.new));
26
32
  }
27
33
  async function handleDeviceInserts(payload) {
28
34
  console.log("[DB Listener] Device INSERT received:", payload.new);
29
- const response_device = await get_device_by_id(payload.new.id);
30
- if (response_device.status == EnumWebResponse.SUCCESS &&
31
- response_device.data) {
32
- dispatch(addDevice(response_device.data));
33
- }
34
- else {
35
- console.error("error getting device by id", payload);
36
- }
35
+ // For now, just dispatch the raw payload since we don't have the device helper
36
+ dispatch(addDevice(payload.new));
37
37
  }
38
38
  function handleDeviceDeletes(payload) {
39
39
  console.log("[DB Listener] Device DELETE received:", payload.old);
@@ -41,14 +41,8 @@ export function useScoutDbListener(supabaseClient) {
41
41
  }
42
42
  async function handleDeviceUpdates(payload) {
43
43
  console.log("[DB Listener] Device UPDATE received:", payload.new);
44
- const response_device = await get_device_by_id(payload.new.id);
45
- if (response_device.status == EnumWebResponse.SUCCESS &&
46
- response_device.data) {
47
- dispatch(updateDevice(response_device.data));
48
- }
49
- else {
50
- console.error("error getting device by id", payload);
51
- }
44
+ // For now, just dispatch the raw payload since we don't have the device helper
45
+ dispatch(updateDevice(payload.new));
52
46
  }
53
47
  function handlePlanInserts(payload) {
54
48
  console.log("[DB Listener] Plan INSERT received:", payload.new);
@@ -63,43 +57,99 @@ export function useScoutDbListener(supabaseClient) {
63
57
  dispatch(updatePlan(payload.new));
64
58
  }
65
59
  useEffect(() => {
66
- console.log(`[DB Listener] Using provided Supabase client - Instance ${instanceId.current}`);
67
- supabaseClient
68
- .channel("plans_insert")
60
+ console.log("=== SCOUT DB LISTENER DEBUG ===");
61
+ console.log("[DB Listener] Using shared Supabase client from ScoutRefreshProvider context");
62
+ if (!scoutSupabase) {
63
+ console.error("[DB Listener] No Supabase client available from ScoutRefreshProvider context");
64
+ return;
65
+ }
66
+ supabase.current = scoutSupabase;
67
+ // Test authentication first
68
+ const testAuth = async () => {
69
+ try {
70
+ const { data: { user }, error, } = await scoutSupabase.auth.getUser();
71
+ console.log("[DB Listener] Auth test - User:", user ? "authenticated" : "anonymous");
72
+ console.log("[DB Listener] Auth test - Error:", error);
73
+ }
74
+ catch (err) {
75
+ console.error("[DB Listener] Auth test failed:", err);
76
+ }
77
+ };
78
+ testAuth();
79
+ // Create a single channel for all operations with unique name
80
+ const channelName = `scout_realtime_${Date.now()}`;
81
+ console.log("[DB Listener] Creating channel:", channelName);
82
+ const mainChannel = scoutSupabase.channel(channelName);
83
+ // Subscribe to all events
84
+ mainChannel
69
85
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "plans" }, handlePlanInserts)
70
- .subscribe();
71
- supabaseClient
72
- .channel("plans_delete")
73
86
  .on("postgres_changes", { event: "DELETE", schema: "public", table: "plans" }, handlePlanDeletes)
74
- .subscribe();
75
- supabaseClient
76
- .channel("plans_update")
77
87
  .on("postgres_changes", { event: "UPDATE", schema: "public", table: "plans" }, handlePlanUpdates)
78
- .subscribe();
79
- supabaseClient
80
- .channel("devices_delete")
81
- .on("postgres_changes", { event: "DELETE", schema: "public", table: "devices" }, handleDeviceDeletes)
82
- .subscribe();
83
- supabaseClient
84
- .channel("devices_insert")
85
88
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "devices" }, handleDeviceInserts)
86
- .subscribe();
87
- supabaseClient
88
- .channel("devices_update")
89
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "devices" }, handleDeviceDeletes)
89
90
  .on("postgres_changes", { event: "UPDATE", schema: "public", table: "devices" }, handleDeviceUpdates)
90
- .subscribe();
91
- supabaseClient
92
- .channel("tags_insert")
93
91
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
94
- .subscribe();
95
- supabaseClient
96
- .channel("tags_delete")
97
92
  .on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
98
- .subscribe();
99
- supabaseClient
100
- .channel("tags_update")
101
93
  .on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
102
- .subscribe();
103
- supabase.current = supabaseClient;
104
- }, [supabaseClient]);
94
+ .subscribe((status) => {
95
+ console.log("[DB Listener] Subscription status:", status);
96
+ if (status === "SUBSCRIBED") {
97
+ console.log("[DB Listener] ✅ Successfully subscribed to real-time updates");
98
+ }
99
+ else if (status === "CHANNEL_ERROR") {
100
+ console.error("[DB Listener] ❌ Channel error occurred");
101
+ }
102
+ else if (status === "TIMED_OUT") {
103
+ console.error("[DB Listener] ⏰ Subscription timed out");
104
+ }
105
+ else if (status === "CLOSED") {
106
+ console.log("[DB Listener] 🔒 Channel closed");
107
+ }
108
+ });
109
+ channels.current.push(mainChannel);
110
+ // Test the connection with system events
111
+ const testChannelName = `test_connection_${Date.now()}`;
112
+ console.log("[DB Listener] Creating test channel:", testChannelName);
113
+ const testChannel = scoutSupabase.channel(testChannelName);
114
+ testChannel
115
+ .on("system", { event: "disconnect" }, () => {
116
+ console.log("[DB Listener] 🔌 Disconnected from Supabase");
117
+ })
118
+ .on("system", { event: "reconnect" }, () => {
119
+ console.log("[DB Listener] 🔗 Reconnected to Supabase");
120
+ })
121
+ .on("system", { event: "error" }, (error) => {
122
+ console.error("[DB Listener] ❌ System error:", error);
123
+ })
124
+ .subscribe((status) => {
125
+ console.log("[DB Listener] Test channel status:", status);
126
+ });
127
+ channels.current.push(testChannel);
128
+ // Test a simple database query to verify connection
129
+ const testDbConnection = async () => {
130
+ try {
131
+ const { data, error } = await scoutSupabase
132
+ .from("tags")
133
+ .select("count")
134
+ .limit(1);
135
+ console.log("[DB Listener] DB connection test - Success:", !!data);
136
+ console.log("[DB Listener] DB connection test - Error:", error);
137
+ }
138
+ catch (err) {
139
+ console.error("[DB Listener] DB connection test failed:", err);
140
+ }
141
+ };
142
+ testDbConnection();
143
+ console.log("=== END SCOUT DB LISTENER DEBUG ===");
144
+ // Cleanup function
145
+ return () => {
146
+ console.log("[DB Listener] 🧹 Cleaning up channels");
147
+ channels.current.forEach((channel) => {
148
+ if (channel) {
149
+ scoutSupabase.removeChannel(channel);
150
+ }
151
+ });
152
+ channels.current = [];
153
+ };
154
+ }, [scoutSupabase, dispatch]);
105
155
  }
@@ -112,22 +112,22 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
112
112
  location: unknown | null;
113
113
  media_type: Database["public"]["Enums"]["media_type"];
114
114
  media_url: string | null;
115
- message: string;
115
+ message: string | null;
116
116
  timestamp_observation: string;
117
117
  };
118
118
  Insert: {
119
- altitude: number;
119
+ altitude?: number;
120
120
  device_id: number;
121
121
  earthranger_url?: string | null;
122
122
  file_path?: string | null;
123
- heading: number;
123
+ heading?: number;
124
124
  id?: number;
125
125
  inserted_at?: string;
126
126
  is_public?: boolean;
127
127
  location?: unknown | null;
128
128
  media_type?: Database["public"]["Enums"]["media_type"];
129
129
  media_url?: string | null;
130
- message: string;
130
+ message?: string | null;
131
131
  timestamp_observation?: string;
132
132
  };
133
133
  Update: {
@@ -142,7 +142,7 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
142
142
  location?: unknown | null;
143
143
  media_type?: Database["public"]["Enums"]["media_type"];
144
144
  media_url?: string | null;
145
- message?: string;
145
+ message?: string | null;
146
146
  timestamp_observation?: string;
147
147
  };
148
148
  Relationships: [{
@@ -505,7 +505,7 @@ export declare function useSupabase(): SupabaseClient<Database, "public", {
505
505
  location: unknown | null;
506
506
  media_type: Database["public"]["Enums"]["media_type"];
507
507
  media_url: string | null;
508
- message: string;
508
+ message: string | null;
509
509
  timestamp_observation: string;
510
510
  }[];
511
511
  };
@@ -76,19 +76,25 @@ export const scoutSlice = createSlice({
76
76
  }
77
77
  },
78
78
  deleteTag(state, action) {
79
+ console.log("[Redux] deleteTag action called with:", action.payload);
80
+ console.log("[Redux] deleteTag - Looking for tag ID:", action.payload.id);
79
81
  for (const herd_module of state.herd_modules) {
80
82
  for (const event of herd_module.events) {
81
83
  if (!event.tags) {
82
84
  continue;
83
85
  }
86
+ console.log(`[Redux] deleteTag - Checking event ${event.id}, has ${event.tags.length} tags`);
84
87
  for (const tag of event.tags) {
85
88
  if (tag.id === action.payload.id) {
89
+ console.log(`[Redux] deleteTag - Found tag ${tag.id} in event ${event.id}, removing it`);
86
90
  event.tags = event.tags.filter((t) => t.id !== tag.id);
91
+ console.log(`[Redux] deleteTag - After removal, event ${event.id} has ${event.tags.length} tags`);
87
92
  return;
88
93
  }
89
94
  }
90
95
  }
91
96
  }
97
+ console.log("[Redux] deleteTag - Tag not found in any event");
92
98
  },
93
99
  updateTag(state, action) {
94
100
  for (const herd_module of state.herd_modules) {
@@ -110,22 +110,22 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
110
110
  location: unknown | null;
111
111
  media_type: Database["public"]["Enums"]["media_type"];
112
112
  media_url: string | null;
113
- message: string;
113
+ message: string | null;
114
114
  timestamp_observation: string;
115
115
  };
116
116
  Insert: {
117
- altitude: number;
117
+ altitude?: number;
118
118
  device_id: number;
119
119
  earthranger_url?: string | null;
120
120
  file_path?: string | null;
121
- heading: number;
121
+ heading?: number;
122
122
  id?: number;
123
123
  inserted_at?: string;
124
124
  is_public?: boolean;
125
125
  location?: unknown | null;
126
126
  media_type?: Database["public"]["Enums"]["media_type"];
127
127
  media_url?: string | null;
128
- message: string;
128
+ message?: string | null;
129
129
  timestamp_observation?: string;
130
130
  };
131
131
  Update: {
@@ -140,7 +140,7 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
140
140
  location?: unknown | null;
141
141
  media_type?: Database["public"]["Enums"]["media_type"];
142
142
  media_url?: string | null;
143
- message?: string;
143
+ message?: string | null;
144
144
  timestamp_observation?: string;
145
145
  };
146
146
  Relationships: [{
@@ -503,7 +503,7 @@ export declare function newServerClient(): Promise<import("@supabase/supabase-js
503
503
  location: unknown | null;
504
504
  media_type: Database["public"]["Enums"]["media_type"];
505
505
  media_url: string | null;
506
- message: string;
506
+ message: string | null;
507
507
  timestamp_observation: string;
508
508
  }[];
509
509
  };
@@ -144,22 +144,22 @@ export type Database = {
144
144
  location: unknown | null;
145
145
  media_type: Database["public"]["Enums"]["media_type"];
146
146
  media_url: string | null;
147
- message: string;
147
+ message: string | null;
148
148
  timestamp_observation: string;
149
149
  };
150
150
  Insert: {
151
- altitude: number;
151
+ altitude?: number;
152
152
  device_id: number;
153
153
  earthranger_url?: string | null;
154
154
  file_path?: string | null;
155
- heading: number;
155
+ heading?: number;
156
156
  id?: number;
157
157
  inserted_at?: string;
158
158
  is_public?: boolean;
159
159
  location?: unknown | null;
160
160
  media_type?: Database["public"]["Enums"]["media_type"];
161
161
  media_url?: string | null;
162
- message: string;
162
+ message?: string | null;
163
163
  timestamp_observation?: string;
164
164
  };
165
165
  Update: {
@@ -174,7 +174,7 @@ export type Database = {
174
174
  location?: unknown | null;
175
175
  media_type?: Database["public"]["Enums"]["media_type"];
176
176
  media_url?: string | null;
177
- message?: string;
177
+ message?: string | null;
178
178
  timestamp_observation?: string;
179
179
  };
180
180
  Relationships: [
@@ -556,7 +556,7 @@ export type Database = {
556
556
  location: unknown | null;
557
557
  media_type: Database["public"]["Enums"]["media_type"];
558
558
  media_url: string | null;
559
- message: string;
559
+ message: string | null;
560
560
  timestamp_observation: string;
561
561
  }[];
562
562
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",