@adventurelabs/scout-core 1.0.29 → 1.0.30

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,7 +1,7 @@
1
1
  "use client";
2
2
  import { useAppDispatch } from "../store/hooks";
3
3
  import { useEffect, useRef } from "react";
4
- import { addDevice, addPlan, addTag, deleteDevice, deletePlan, deleteTag, updateDevice, updatePlan, updateTag, } from "../store/scout";
4
+ import { addDevice, addPlan, addTag, addSessionToStore, deleteDevice, deletePlan, deleteSessionFromStore, deleteTag, updateDevice, updatePlan, updateSessionInStore, updateTag, } from "../store/scout";
5
5
  export function useScoutDbListener(scoutSupabase) {
6
6
  const supabase = useRef(null);
7
7
  const channels = useRef([]);
@@ -56,6 +56,33 @@ export function useScoutDbListener(scoutSupabase) {
56
56
  console.log("[DB Listener] Plan UPDATE received:", payload.new);
57
57
  dispatch(updatePlan(payload.new));
58
58
  }
59
+ function handleSessionInserts(payload) {
60
+ console.log("[DB Listener] Session INSERT received:", payload.new);
61
+ dispatch(addSessionToStore(payload.new));
62
+ }
63
+ function handleSessionDeletes(payload) {
64
+ console.log("[DB Listener] Session DELETE received:", payload.old);
65
+ dispatch(deleteSessionFromStore(payload.old));
66
+ }
67
+ function handleSessionUpdates(payload) {
68
+ console.log("[DB Listener] Session UPDATE received:", payload.new);
69
+ dispatch(updateSessionInStore(payload.new));
70
+ }
71
+ function handleConnectivityInserts(payload) {
72
+ console.log("[DB Listener] Connectivity INSERT received:", payload.new);
73
+ // For now, we'll just log connectivity changes since they're related to sessions
74
+ // In the future, we might want to update session connectivity data
75
+ }
76
+ function handleConnectivityDeletes(payload) {
77
+ console.log("[DB Listener] Connectivity DELETE received:", payload.old);
78
+ // For now, we'll just log connectivity changes since they're related to sessions
79
+ // In the future, we might want to update session connectivity data
80
+ }
81
+ function handleConnectivityUpdates(payload) {
82
+ console.log("[DB Listener] Connectivity UPDATE received:", payload.new);
83
+ // For now, we'll just log connectivity changes since they're related to sessions
84
+ // In the future, we might want to update session connectivity data
85
+ }
59
86
  useEffect(() => {
60
87
  console.log("=== SCOUT DB LISTENER DEBUG ===");
61
88
  console.log("[DB Listener] Using shared Supabase client from ScoutRefreshProvider context");
@@ -91,12 +118,12 @@ export function useScoutDbListener(scoutSupabase) {
91
118
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
92
119
  .on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
93
120
  .on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
94
- .on("postgres_changes", { event: "INSERT", schema: "public", table: "connectivity" }, handleTagUpdates)
95
- .on("postgres_changes", { event: "DELETE", schema: "public", table: "connectivity" }, handleTagUpdates)
96
- .on("postgres_changes", { event: "UPDATE", schema: "public", table: "connectivity" }, handleTagUpdates)
97
- .on("postgres_changes", { event: "INSERT", schema: "public", table: "sessions" }, handleTagUpdates)
98
- .on("postgres_changes", { event: "DELETE", schema: "public", table: "sessions" }, handleTagUpdates)
99
- .on("postgres_changes", { event: "UPDATE", schema: "public", table: "sessions" }, handleTagUpdates)
121
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "connectivity" }, handleConnectivityInserts)
122
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "connectivity" }, handleConnectivityDeletes)
123
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "connectivity" }, handleConnectivityUpdates)
124
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "sessions" }, handleSessionInserts)
125
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "sessions" }, handleSessionDeletes)
126
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "sessions" }, handleSessionUpdates)
100
127
  .subscribe((status) => {
101
128
  console.log("[DB Listener] Subscription status:", status);
102
129
  if (status === "SUBSCRIBED") {
@@ -93,11 +93,23 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
93
93
  payload: any;
94
94
  type: string;
95
95
  }) => void;
96
+ addSessionToStore: (state: import("immer").WritableDraft<ScoutState>, action: {
97
+ payload: any;
98
+ type: string;
99
+ }) => void;
100
+ deleteSessionFromStore: (state: import("immer").WritableDraft<ScoutState>, action: {
101
+ payload: any;
102
+ type: string;
103
+ }) => void;
104
+ updateSessionInStore: (state: import("immer").WritableDraft<ScoutState>, action: {
105
+ payload: any;
106
+ type: string;
107
+ }) => void;
96
108
  setUser: (state: import("immer").WritableDraft<ScoutState>, action: {
97
109
  payload: any;
98
110
  type: string;
99
111
  }) => void;
100
112
  }, "scout", "scout", import("@reduxjs/toolkit").SliceSelectors<ScoutState>>;
101
- export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">;
113
+ export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, addSessionToStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addSessionToStore">, deleteSessionFromStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteSessionFromStore">, updateSessionInStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionInStore">;
102
114
  declare const _default: import("redux").Reducer<ScoutState>;
103
115
  export default _default;
@@ -192,11 +192,38 @@ export const scoutSlice = createSlice({
192
192
  herd_module.events_page_index = new_page_index;
193
193
  }
194
194
  },
195
+ addSessionToStore: (state, action) => {
196
+ const session = action.payload;
197
+ // Find the herd module that contains the device for this session
198
+ for (const herd_module of state.herd_modules) {
199
+ const device = herd_module.devices.find((d) => d.id === session.device_id);
200
+ if (device) {
201
+ herd_module.sessions = [session, ...herd_module.sessions];
202
+ return;
203
+ }
204
+ }
205
+ },
206
+ deleteSessionFromStore: (state, action) => {
207
+ const sessionId = action.payload.id;
208
+ for (const herd_module of state.herd_modules) {
209
+ herd_module.sessions = herd_module.sessions.filter((session) => session.id !== sessionId);
210
+ }
211
+ },
212
+ updateSessionInStore: (state, action) => {
213
+ const updatedSession = action.payload;
214
+ for (const herd_module of state.herd_modules) {
215
+ const sessionIndex = herd_module.sessions.findIndex((session) => session.id === updatedSession.id);
216
+ if (sessionIndex !== -1) {
217
+ herd_module.sessions[sessionIndex] = updatedSession;
218
+ return;
219
+ }
220
+ }
221
+ },
195
222
  setUser: (state, action) => {
196
223
  state.user = action.payload;
197
224
  },
198
225
  },
199
226
  });
200
227
  // Action creators are generated for each case reducer function
201
- export const { setHerdModules, setStatus, setActiveHerdId, setActiveDeviceId, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, } = scoutSlice.actions;
228
+ export const { setHerdModules, setStatus, setActiveHerdId, setActiveDeviceId, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, } = scoutSlice.actions;
202
229
  export default scoutSlice.reducer;
@@ -1,10 +1,11 @@
1
1
  import { SupabaseClient } from "@supabase/supabase-js";
2
- import { IDevice, IEventWithTags, IHerd, IPlan, IUserAndRole, IZoneWithActions } from "../types/db";
2
+ import { IDevice, IEventWithTags, IHerd, IPlan, IUserAndRole, IZoneWithActions, ISessionWithCoordinates } from "../types/db";
3
3
  export declare class HerdModule {
4
4
  herd: IHerd;
5
5
  devices: IDevice[];
6
6
  events: IEventWithTags[];
7
7
  zones: IZoneWithActions[];
8
+ sessions: ISessionWithCoordinates[];
8
9
  timestamp_last_refreshed: number;
9
10
  user_roles: IUserAndRole[] | null;
10
11
  events_page_index: number;
@@ -12,7 +13,7 @@ export declare class HerdModule {
12
13
  total_events_with_filters: number;
13
14
  labels: string[];
14
15
  plans: IPlan[];
15
- constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[]);
16
+ constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], sessions?: ISessionWithCoordinates[]);
16
17
  to_serializable(): IHerdModule;
17
18
  static from_herd(herd: IHerd, client: SupabaseClient): Promise<HerdModule>;
18
19
  }
@@ -28,4 +29,5 @@ export interface IHerdModule {
28
29
  labels: string[];
29
30
  plans: IPlan[];
30
31
  zones: IZoneWithActions[];
32
+ sessions: ISessionWithCoordinates[];
31
33
  }
@@ -7,8 +7,9 @@ import { server_get_users_with_herd_access } from "../helpers/users";
7
7
  import { EnumWebResponse } from "./requests";
8
8
  import { server_get_more_zones_and_actions_for_herd } from "../helpers/zones";
9
9
  import { server_list_api_keys } from "../api_keys/actions";
10
+ import { getSessionsByHerdId } from "../helpers/sessions";
10
11
  export class HerdModule {
11
- constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = []) {
12
+ constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = []) {
12
13
  this.user_roles = null;
13
14
  this.events_page_index = 0;
14
15
  this.total_events = 0;
@@ -26,6 +27,7 @@ export class HerdModule {
26
27
  this.labels = labels;
27
28
  this.plans = plans;
28
29
  this.zones = zones;
30
+ this.sessions = sessions;
29
31
  }
30
32
  to_serializable() {
31
33
  return {
@@ -40,43 +42,103 @@ export class HerdModule {
40
42
  labels: this.labels,
41
43
  plans: this.plans,
42
44
  zones: this.zones,
45
+ sessions: this.sessions,
43
46
  };
44
47
  }
45
48
  static async from_herd(herd, client) {
46
- // load devices
47
- let response_new_devices = await get_devices_by_herd(herd.id, client);
48
- if (response_new_devices.status == EnumWebResponse.ERROR ||
49
- !response_new_devices.data) {
50
- console.warn("No devices found for herd");
51
- return new HerdModule(herd, [], [], Date.now());
52
- }
53
- const new_devices = response_new_devices.data;
54
- // get api keys for each device... run requests in parallel
55
- if (new_devices.length > 0) {
56
- let api_keys_promises = new_devices.map((device) => server_list_api_keys(device.id?.toString() ?? ""));
57
- let api_keys = await Promise.all(api_keys_promises);
58
- for (let i = 0; i < new_devices.length; i++) {
59
- new_devices[i].api_keys_scout = api_keys[i];
49
+ try {
50
+ // load devices
51
+ let response_new_devices = await get_devices_by_herd(herd.id, client);
52
+ if (response_new_devices.status == EnumWebResponse.ERROR ||
53
+ !response_new_devices.data) {
54
+ console.warn("No devices found for herd");
55
+ return new HerdModule(herd, [], [], Date.now());
60
56
  }
61
- }
62
- // get recent events for each device... run requests in parallel
63
- let recent_events_promises = new_devices.map((device) => server_get_events_and_tags_for_device(device.id ?? 0));
64
- // Run all requests in parallel
65
- const [recent_events, res_zones, res_user_roles, total_event_count, res_plans,] = await Promise.all([
66
- Promise.all(recent_events_promises),
67
- server_get_more_zones_and_actions_for_herd(herd.id, 0, 10),
68
- server_get_users_with_herd_access(herd.id),
69
- server_get_total_events_by_herd(herd.id),
70
- server_get_plans_by_herd(herd.id),
71
- ]);
72
- for (let i = 0; i < new_devices.length; i++) {
73
- let x = recent_events[i].data;
74
- if (recent_events[i].status == EnumWebResponse.SUCCESS && x) {
75
- new_devices[i].recent_events = x;
57
+ const new_devices = response_new_devices.data;
58
+ // get api keys for each device... run requests in parallel
59
+ if (new_devices.length > 0) {
60
+ try {
61
+ let api_keys_promises = new_devices.map((device) => server_list_api_keys(device.id?.toString() ?? "").catch((error) => {
62
+ console.warn(`Failed to get API keys for device ${device.id}:`, error);
63
+ return undefined;
64
+ }));
65
+ let api_keys = await Promise.all(api_keys_promises);
66
+ for (let i = 0; i < new_devices.length; i++) {
67
+ new_devices[i].api_keys_scout = api_keys[i];
68
+ }
69
+ }
70
+ catch (error) {
71
+ console.warn("Failed to load API keys for devices:", error);
72
+ // Continue without API keys
73
+ }
76
74
  }
75
+ // get recent events for each device... run requests in parallel
76
+ let recent_events_promises = new_devices.map((device) => server_get_events_and_tags_for_device(device.id ?? 0).catch((error) => {
77
+ console.warn(`Failed to get events for device ${device.id}:`, error);
78
+ return { status: EnumWebResponse.ERROR, data: null };
79
+ }));
80
+ // Run all requests in parallel with individual error handling
81
+ const [recent_events, res_zones, res_user_roles, total_event_count, res_plans, res_sessions,] = await Promise.allSettled([
82
+ Promise.all(recent_events_promises),
83
+ server_get_more_zones_and_actions_for_herd(herd.id, 0, 10).catch((error) => {
84
+ console.warn("Failed to get zones and actions:", error);
85
+ return { status: EnumWebResponse.ERROR, data: null };
86
+ }),
87
+ server_get_users_with_herd_access(herd.id).catch((error) => {
88
+ console.warn("Failed to get user roles:", error);
89
+ return { status: EnumWebResponse.ERROR, data: null };
90
+ }),
91
+ server_get_total_events_by_herd(herd.id).catch((error) => {
92
+ console.warn("Failed to get total events count:", error);
93
+ return { status: EnumWebResponse.ERROR, data: null };
94
+ }),
95
+ server_get_plans_by_herd(herd.id).catch((error) => {
96
+ console.warn("Failed to get plans:", error);
97
+ return { status: EnumWebResponse.ERROR, data: null };
98
+ }),
99
+ getSessionsByHerdId(client, herd.id).catch((error) => {
100
+ console.warn("Failed to get sessions:", error);
101
+ return [];
102
+ }),
103
+ ]);
104
+ // Process recent events with error handling
105
+ if (recent_events.status === "fulfilled") {
106
+ for (let i = 0; i < new_devices.length; i++) {
107
+ try {
108
+ let x = recent_events.value[i]?.data;
109
+ if (recent_events.value[i]?.status == EnumWebResponse.SUCCESS &&
110
+ x) {
111
+ new_devices[i].recent_events = x;
112
+ }
113
+ }
114
+ catch (error) {
115
+ console.warn(`Failed to process events for device ${new_devices[i].id}:`, error);
116
+ }
117
+ }
118
+ }
119
+ // Extract data with safe fallbacks
120
+ const zones = res_zones.status === "fulfilled" && res_zones.value?.data
121
+ ? res_zones.value.data
122
+ : [];
123
+ const user_roles = res_user_roles.status === "fulfilled" && res_user_roles.value?.data
124
+ ? res_user_roles.value.data
125
+ : null;
126
+ const total_events = total_event_count.status === "fulfilled" &&
127
+ total_event_count.value?.data
128
+ ? total_event_count.value.data
129
+ : 0;
130
+ const plans = res_plans.status === "fulfilled" && res_plans.value?.data
131
+ ? res_plans.value.data
132
+ : [];
133
+ const sessions = res_sessions.status === "fulfilled" ? res_sessions.value : [];
134
+ // TODO: store in DB and retrieve on load?
135
+ const newLabels = LABELS;
136
+ return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions);
137
+ }
138
+ catch (error) {
139
+ console.error("Critical error in HerdModule.from_herd:", error);
140
+ // Return a minimal but valid HerdModule instance to prevent complete failure
141
+ return new HerdModule(herd, [], [], Date.now());
77
142
  }
78
- // TODO: store in DB and retrieve on load?
79
- const newLabels = LABELS;
80
- return new HerdModule(herd, new_devices, [], Date.now(), res_user_roles.data, 0, total_event_count.data ? total_event_count.data : 0, total_event_count.data ? total_event_count.data : 0, newLabels, res_plans.data ? res_plans.data : [], res_zones.data ? res_zones.data : []);
81
143
  }
82
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
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",