@adventurelabs/scout-core 1.0.55 → 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.
@@ -6,6 +6,11 @@ export function useScoutDbListener(scoutSupabase) {
6
6
  const supabase = useRef(null);
7
7
  const channels = useRef([]);
8
8
  const dispatch = useAppDispatch();
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
9
14
  function handleTagInserts(payload) {
10
15
  console.log("[DB Listener] Tag INSERT received:", payload.new);
11
16
  dispatch(addTag(payload.new));
@@ -71,15 +76,20 @@ export function useScoutDbListener(scoutSupabase) {
71
76
  function handleConnectivityUpdates(payload) {
72
77
  console.log("[DB Listener] Connectivity UPDATE received:", payload.new);
73
78
  }
74
- useEffect(() => {
75
- if (!scoutSupabase) {
76
- console.error("[DB Listener] No Supabase client available");
77
- return;
78
- }
79
- supabase.current = scoutSupabase;
80
- // Create a single channel for all operations
81
- const channelName = `scout_realtime_${Date.now()}`;
82
- const mainChannel = scoutSupabase.channel(channelName);
79
+ // Clean up all channels
80
+ const cleanupChannels = () => {
81
+ channels.current.forEach((channel) => {
82
+ if (channel) {
83
+ scoutSupabase.removeChannel(channel);
84
+ }
85
+ });
86
+ channels.current = [];
87
+ };
88
+ // Setup channel with event handlers
89
+ const setupChannel = () => {
90
+ if (!scoutSupabase)
91
+ return null;
92
+ const mainChannel = scoutSupabase.channel("schema_db_changes");
83
93
  // Subscribe to all events
84
94
  mainChannel
85
95
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "plans" }, handlePlanInserts)
@@ -101,27 +111,67 @@ export function useScoutDbListener(scoutSupabase) {
101
111
  console.log("[DB Listener] Subscription status:", status);
102
112
  if (status === "SUBSCRIBED") {
103
113
  console.log("[DB Listener] ✅ Successfully subscribed to real-time updates");
114
+ reconnectAttemptsRef.current = 0; // Reset reconnect attempts on successful connection
104
115
  }
105
116
  else if (status === "CHANNEL_ERROR") {
106
- console.error("[DB Listener] ❌ Channel error occurred");
117
+ console.error("[DB Listener] ❌ Channel error occurred. Reconnecting...");
118
+ handleReconnect();
107
119
  }
108
120
  else if (status === "TIMED_OUT") {
109
- console.error("[DB Listener] ⏰ Subscription timed out");
121
+ console.error("[DB Listener] ⏰ Subscription timed out. Reconnecting...");
122
+ handleReconnect();
110
123
  }
111
124
  else if (status === "CLOSED") {
112
- console.log("[DB Listener] 🔒 Channel closed");
125
+ console.log("[DB Listener] 🔒 Channel closed. Reconnecting...");
126
+ handleReconnect();
113
127
  }
114
128
  });
115
- channels.current.push(mainChannel);
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;
136
+ }
137
+ // Clear any existing timeout
138
+ if (reconnectTimeoutRef.current) {
139
+ clearTimeout(reconnectTimeoutRef.current);
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
+ };
152
+ useEffect(() => {
153
+ if (!scoutSupabase) {
154
+ console.error("[DB Listener] No Supabase client available");
155
+ return;
156
+ }
157
+ supabase.current = scoutSupabase;
158
+ // Initial channel setup
159
+ const mainChannel = setupChannel();
160
+ if (mainChannel) {
161
+ channels.current.push(mainChannel);
162
+ }
116
163
  // Cleanup function
117
164
  return () => {
118
165
  console.log("[DB Listener] 🧹 Cleaning up channels");
119
- channels.current.forEach((channel) => {
120
- if (channel) {
121
- scoutSupabase.removeChannel(channel);
122
- }
123
- });
124
- channels.current = [];
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
174
+ cleanupChannels();
125
175
  };
126
176
  }, [scoutSupabase, dispatch]);
127
177
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.55",
3
+ "version": "1.0.56",
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",