@adventurelabs/scout-core 1.2.1 → 1.2.4

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.
@@ -22,3 +22,4 @@ export * from "./operators";
22
22
  export * from "./components";
23
23
  export * from "./versions_software";
24
24
  export * from "./artifacts";
25
+ export * from "./pins";
@@ -22,3 +22,4 @@ export * from "./operators";
22
22
  export * from "./components";
23
23
  export * from "./versions_software";
24
24
  export * from "./artifacts";
25
+ export * from "./pins";
@@ -0,0 +1,11 @@
1
+ import { Database } from "../types/supabase";
2
+ import { IPin, PinInsert } from "../types/db";
3
+ import { IWebResponseCompatible } from "../types/requests";
4
+ import { SupabaseClient } from "@supabase/supabase-js";
5
+ export declare function get_pins_for_herd(client: SupabaseClient<Database>, herd_id: number): Promise<IWebResponseCompatible<IPin[]>>;
6
+ export declare function get_pin_by_id(client: SupabaseClient<Database>, pin_id: number): Promise<IWebResponseCompatible<IPin | null>>;
7
+ export declare function create_pin(client: SupabaseClient<Database>, newPin: PinInsert): Promise<IWebResponseCompatible<IPin | null>>;
8
+ export declare function update_pin(client: SupabaseClient<Database>, pin_id: number, updatedPin: Partial<PinInsert>): Promise<IWebResponseCompatible<IPin | null>>;
9
+ export declare function delete_pin(client: SupabaseClient<Database>, pin_id: number): Promise<IWebResponseCompatible<IPin | null>>;
10
+ export declare function get_pins_by_created_by(client: SupabaseClient<Database>, user_id: string): Promise<IWebResponseCompatible<IPin[]>>;
11
+ export declare function get_pins_by_color(client: SupabaseClient<Database>, herd_id: number, color: string): Promise<IWebResponseCompatible<IPin[]>>;
@@ -0,0 +1,142 @@
1
+ import { IWebResponse } from "../types/requests";
2
+ export async function get_pins_for_herd(client, herd_id) {
3
+ // Call get_pins_for_herd with rpc
4
+ const { data, error } = await client.rpc("get_pins_for_herd", {
5
+ herd_id_caller: herd_id,
6
+ });
7
+ if (error) {
8
+ return IWebResponse.error(error.message).to_compatible();
9
+ }
10
+ if (!data) {
11
+ return IWebResponse.error("No pins found for herd").to_compatible();
12
+ }
13
+ return IWebResponse.success(data).to_compatible();
14
+ }
15
+ export async function get_pin_by_id(client, pin_id) {
16
+ const { data, error } = await client
17
+ .from("pins")
18
+ .select("*")
19
+ .eq("id", pin_id)
20
+ .single();
21
+ if (error) {
22
+ return IWebResponse.error(error.message).to_compatible();
23
+ }
24
+ if (!data) {
25
+ return IWebResponse.error("Pin not found").to_compatible();
26
+ }
27
+ // Convert to pretty location format with coordinates
28
+ const pinWithCoords = {
29
+ ...data,
30
+ latitude: data.latitude ? parseFloat(data.latitude.toString()) : 0,
31
+ longitude: data.longitude ? parseFloat(data.longitude.toString()) : 0,
32
+ };
33
+ return IWebResponse.success(pinWithCoords).to_compatible();
34
+ }
35
+ export async function create_pin(client, newPin) {
36
+ const { data, error } = await client
37
+ .from("pins")
38
+ .insert([newPin])
39
+ .select("*")
40
+ .single();
41
+ if (error) {
42
+ return IWebResponse.error(error.message).to_compatible();
43
+ }
44
+ if (!data) {
45
+ return IWebResponse.error("Failed to create pin").to_compatible();
46
+ }
47
+ // Convert to pretty location format with coordinates
48
+ const pinWithCoords = {
49
+ ...data,
50
+ latitude: data.latitude ? parseFloat(data.latitude.toString()) : 0,
51
+ longitude: data.longitude ? parseFloat(data.longitude.toString()) : 0,
52
+ };
53
+ return IWebResponse.success(pinWithCoords).to_compatible();
54
+ }
55
+ export async function update_pin(client, pin_id, updatedPin) {
56
+ // Remove fields that shouldn't be updated
57
+ const updateData = { ...updatedPin };
58
+ delete updateData.id;
59
+ delete updateData.created_at;
60
+ delete updateData.created_by; // RLS handles permissions
61
+ const { data, error } = await client
62
+ .from("pins")
63
+ .update(updateData)
64
+ .eq("id", pin_id)
65
+ .select("*")
66
+ .single();
67
+ if (error) {
68
+ return IWebResponse.error(error.message).to_compatible();
69
+ }
70
+ if (!data) {
71
+ return IWebResponse.error("Pin not found or update failed").to_compatible();
72
+ }
73
+ // Convert to pretty location format with coordinates
74
+ const pinWithCoords = {
75
+ ...data,
76
+ latitude: data.latitude ? parseFloat(data.latitude.toString()) : 0,
77
+ longitude: data.longitude ? parseFloat(data.longitude.toString()) : 0,
78
+ };
79
+ return IWebResponse.success(pinWithCoords).to_compatible();
80
+ }
81
+ export async function delete_pin(client, pin_id) {
82
+ const { data, error } = await client
83
+ .from("pins")
84
+ .delete()
85
+ .eq("id", pin_id)
86
+ .select("*")
87
+ .single();
88
+ if (error) {
89
+ return IWebResponse.error(error.message).to_compatible();
90
+ }
91
+ if (!data) {
92
+ return IWebResponse.error("Pin not found or deletion failed").to_compatible();
93
+ }
94
+ // Convert to pretty location format with coordinates
95
+ const pinWithCoords = {
96
+ ...data,
97
+ latitude: data.latitude ? parseFloat(data.latitude.toString()) : 0,
98
+ longitude: data.longitude ? parseFloat(data.longitude.toString()) : 0,
99
+ };
100
+ return IWebResponse.success(pinWithCoords).to_compatible();
101
+ }
102
+ export async function get_pins_by_created_by(client, user_id) {
103
+ const { data, error } = await client
104
+ .from("pins")
105
+ .select("*")
106
+ .eq("created_by", user_id)
107
+ .order("created_at", { ascending: false });
108
+ if (error) {
109
+ return IWebResponse.error(error.message).to_compatible();
110
+ }
111
+ if (!data) {
112
+ return IWebResponse.error("No pins found for user").to_compatible();
113
+ }
114
+ // Convert to pretty location format with coordinates
115
+ const pinsWithCoords = data.map((pin) => ({
116
+ ...pin,
117
+ latitude: pin.latitude ? parseFloat(pin.latitude.toString()) : 0,
118
+ longitude: pin.longitude ? parseFloat(pin.longitude.toString()) : 0,
119
+ }));
120
+ return IWebResponse.success(pinsWithCoords).to_compatible();
121
+ }
122
+ export async function get_pins_by_color(client, herd_id, color) {
123
+ const { data, error } = await client
124
+ .from("pins")
125
+ .select("*")
126
+ .eq("herd_id", herd_id)
127
+ .eq("color", color)
128
+ .order("created_at", { ascending: false });
129
+ if (error) {
130
+ return IWebResponse.error(error.message).to_compatible();
131
+ }
132
+ if (!data) {
133
+ return IWebResponse.error(`No pins found with color: ${color}`).to_compatible();
134
+ }
135
+ // Convert to pretty location format with coordinates
136
+ const pinsWithCoords = data.map((pin) => ({
137
+ ...pin,
138
+ latitude: pin.latitude ? parseFloat(pin.latitude.toString()) : 0,
139
+ longitude: pin.longitude ? parseFloat(pin.longitude.toString()) : 0,
140
+ }));
141
+ return IWebResponse.success(pinsWithCoords).to_compatible();
142
+ }
@@ -2,3 +2,7 @@ export { useScoutRefresh, type UseScoutRefreshOptions, } from "./useScoutRefresh
2
2
  export { useScoutRealtimeConnectivity } from "./useScoutRealtimeConnectivity";
3
3
  export { useScoutRealtimeDevices } from "./useScoutRealtimeDevices";
4
4
  export { useScoutRealtimeVersionsSoftware } from "./useScoutRealtimeVersionsSoftware";
5
+ export { useScoutRealtimeEvents } from "./useScoutRealtimeEvents";
6
+ export { useScoutRealtimeTags } from "./useScoutRealtimeTags";
7
+ export { useScoutRealtimeSessions } from "./useScoutRealtimeSessions";
8
+ export { useScoutRealtimePlans } from "./useScoutRealtimePlans";
@@ -2,3 +2,7 @@ export { useScoutRefresh, } from "./useScoutRefresh";
2
2
  export { useScoutRealtimeConnectivity } from "./useScoutRealtimeConnectivity";
3
3
  export { useScoutRealtimeDevices } from "./useScoutRealtimeDevices";
4
4
  export { useScoutRealtimeVersionsSoftware } from "./useScoutRealtimeVersionsSoftware";
5
+ export { useScoutRealtimeEvents } from "./useScoutRealtimeEvents";
6
+ export { useScoutRealtimeTags } from "./useScoutRealtimeTags";
7
+ export { useScoutRealtimeSessions } from "./useScoutRealtimeSessions";
8
+ export { useScoutRealtimePlans } from "./useScoutRealtimePlans";
@@ -2,4 +2,4 @@ import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
3
  import { IConnectivityWithCoordinates } from "../types/db";
4
4
  import { RealtimeData } from "../types/realtime";
5
- export declare function useScoutRealtimeConnectivity(scoutSupabase: SupabaseClient<Database>): RealtimeData<IConnectivityWithCoordinates>[];
5
+ export declare function useScoutRealtimeConnectivity(scoutSupabase: SupabaseClient<Database>): [RealtimeData<IConnectivityWithCoordinates> | null, () => void];
@@ -4,7 +4,7 @@ import { useEffect, useRef, useCallback, useState, useMemo } from "react";
4
4
  import { EnumRealtimeOperation } from "../types/realtime";
5
5
  export function useScoutRealtimeConnectivity(scoutSupabase) {
6
6
  const channels = useRef([]);
7
- const [newConnectivityItems, setNewConnectivityItems] = useState([]);
7
+ const [latestConnectivityUpdate, setLatestConnectivityUpdate] = useState(null);
8
8
  const activeHerdId = useSelector((state) => state.scout.active_herd_id);
9
9
  const herdModules = useSelector((state) => state.scout.herd_modules);
10
10
  // Create stable reference for GPS device IDs to prevent unnecessary refetching
@@ -49,11 +49,11 @@ export function useScoutRealtimeConnectivity(scoutSupabase) {
49
49
  data: connectivityData,
50
50
  operation,
51
51
  };
52
- setNewConnectivityItems((prev) => [realtimeData, ...prev]);
52
+ setLatestConnectivityUpdate(realtimeData);
53
53
  }, []);
54
- // Clear new items when gps device IDs change (herd change)
55
- const clearNewItems = useCallback(() => {
56
- setNewConnectivityItems([]);
54
+ // Clear latest update
55
+ const clearLatestUpdate = useCallback(() => {
56
+ setLatestConnectivityUpdate(null);
57
57
  }, []);
58
58
  useEffect(() => {
59
59
  if (!scoutSupabase || gpsDeviceIds === "")
@@ -61,8 +61,8 @@ export function useScoutRealtimeConnectivity(scoutSupabase) {
61
61
  // Clean up existing channels
62
62
  channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
63
63
  channels.current = [];
64
- // Clear previous items when switching herds
65
- clearNewItems();
64
+ // Clear previous update when switching herds
65
+ clearLatestUpdate();
66
66
  // Create connectivity channel
67
67
  const channel = scoutSupabase
68
68
  .channel(`${activeHerdId}-connectivity`, { config: { private: true } })
@@ -78,7 +78,7 @@ export function useScoutRealtimeConnectivity(scoutSupabase) {
78
78
  gpsDeviceIds,
79
79
  activeHerdId,
80
80
  handleConnectivityBroadcast,
81
- clearNewItems,
81
+ clearLatestUpdate,
82
82
  ]);
83
- return newConnectivityItems;
83
+ return [latestConnectivityUpdate, clearLatestUpdate];
84
84
  }
@@ -2,4 +2,4 @@ import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
3
  import { IDevicePrettyLocation } from "../types/db";
4
4
  import { RealtimeData } from "../types/realtime";
5
- export declare function useScoutRealtimeDevices(scoutSupabase: SupabaseClient<Database>): RealtimeData<IDevicePrettyLocation>[];
5
+ export declare function useScoutRealtimeDevices(scoutSupabase: SupabaseClient<Database>): [RealtimeData<IDevicePrettyLocation> | null, () => void];
@@ -7,7 +7,7 @@ import { EnumRealtimeOperation } from "../types/realtime";
7
7
  export function useScoutRealtimeDevices(scoutSupabase) {
8
8
  const channels = useRef([]);
9
9
  const dispatch = useAppDispatch();
10
- const [newDeviceItems, setNewDeviceItems] = useState([]);
10
+ const [latestDeviceUpdate, setLatestDeviceUpdate] = useState(null);
11
11
  const activeHerdId = useSelector((state) => state.scout.active_herd_id);
12
12
  // Device broadcast handler
13
13
  const handleDeviceBroadcast = useCallback((payload) => {
@@ -45,11 +45,11 @@ export function useScoutRealtimeDevices(scoutSupabase) {
45
45
  operation,
46
46
  };
47
47
  console.log(`[scout-core realtime] DEVICE ${data.operation} received:`, JSON.stringify(realtimeData));
48
- setNewDeviceItems((prev) => [realtimeData, ...prev]);
48
+ setLatestDeviceUpdate(realtimeData);
49
49
  }, [dispatch]);
50
- // Clear new items when herd changes
51
- const clearNewItems = useCallback(() => {
52
- setNewDeviceItems([]);
50
+ // Clear latest update
51
+ const clearLatestUpdate = useCallback(() => {
52
+ setLatestDeviceUpdate(null);
53
53
  }, []);
54
54
  const cleanupChannels = () => {
55
55
  channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
@@ -70,14 +70,14 @@ export function useScoutRealtimeDevices(scoutSupabase) {
70
70
  };
71
71
  useEffect(() => {
72
72
  cleanupChannels();
73
- // Clear previous items when switching herds
74
- clearNewItems();
73
+ // Clear previous update when switching herds
74
+ clearLatestUpdate();
75
75
  // Create devices channel for active herd
76
76
  if (activeHerdId) {
77
77
  const channel = createDevicesChannel(activeHerdId);
78
78
  channels.current.push(channel);
79
79
  }
80
80
  return cleanupChannels;
81
- }, [activeHerdId, clearNewItems]);
82
- return newDeviceItems;
81
+ }, [activeHerdId, clearLatestUpdate]);
82
+ return [latestDeviceUpdate, clearLatestUpdate];
83
83
  }
@@ -0,0 +1,5 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ import { IEventAndTagsPrettyLocation } from "../types/db";
4
+ import { RealtimeData } from "../types/realtime";
5
+ export declare function useScoutRealtimeEvents(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges: boolean): [RealtimeData<IEventAndTagsPrettyLocation> | null, () => void];
@@ -0,0 +1,96 @@
1
+ "use client";
2
+ import { useAppDispatch } from "../store/hooks";
3
+ import { useSelector } from "react-redux";
4
+ import { useEffect, useRef, useCallback, useState } from "react";
5
+ import { updateEventValuesForHerdModule } from "../store/scout";
6
+ import { EnumRealtimeOperation } from "../types/realtime";
7
+ export function useScoutRealtimeEvents(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
8
+ const channels = useRef([]);
9
+ const dispatch = useAppDispatch();
10
+ const [latestEventUpdate, setLatestEventUpdate] = useState(null);
11
+ const activeHerdId = useSelector((state) => state.scout.active_herd_id);
12
+ // Event broadcast handler
13
+ const handleEventBroadcast = useCallback((payload) => {
14
+ console.log("[Events] Broadcast received:", payload.payload.operation);
15
+ const data = payload.payload;
16
+ const eventData = data.record || data.old_record;
17
+ if (!eventData)
18
+ return;
19
+ let operation;
20
+ // TODO: UNCOMMENT GLOBAL STORE OPERATIONS IF OKAY WITH FREQUENT
21
+ switch (data.operation) {
22
+ case "INSERT":
23
+ operation = EnumRealtimeOperation.INSERT;
24
+ if (data.record && activeHerdId && shouldUpdateGlobalStateOnChanges) {
25
+ console.log("[Events] New event received:", data.record);
26
+ // For events, we need to update the event values in the herd module
27
+ dispatch(updateEventValuesForHerdModule({
28
+ herd_id: activeHerdId,
29
+ events: [data.record],
30
+ }));
31
+ }
32
+ break;
33
+ case "UPDATE":
34
+ operation = EnumRealtimeOperation.UPDATE;
35
+ if (data.record && activeHerdId && shouldUpdateGlobalStateOnChanges) {
36
+ console.log("[Events] Event updated:", data.record);
37
+ dispatch(updateEventValuesForHerdModule({
38
+ herd_id: activeHerdId,
39
+ events: [data.record],
40
+ }));
41
+ }
42
+ break;
43
+ case "DELETE":
44
+ operation = EnumRealtimeOperation.DELETE;
45
+ if (data.old_record &&
46
+ activeHerdId &&
47
+ shouldUpdateGlobalStateOnChanges) {
48
+ console.log("[Events] Event deleted:", data.old_record);
49
+ // TODO: WRITE DELETION STORE ACTION
50
+ console.log("[Events] Event deletion detected - manual refresh may be needed");
51
+ }
52
+ break;
53
+ default:
54
+ return;
55
+ }
56
+ const realtimeData = {
57
+ data: eventData,
58
+ operation,
59
+ };
60
+ console.log(`[scout-core realtime] EVENT ${data.operation} received:`, JSON.stringify(realtimeData));
61
+ setLatestEventUpdate(realtimeData);
62
+ }, [dispatch, activeHerdId]);
63
+ // Clear latest update
64
+ const clearLatestUpdate = useCallback(() => {
65
+ setLatestEventUpdate(null);
66
+ }, []);
67
+ const cleanupChannels = () => {
68
+ channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
69
+ channels.current = [];
70
+ };
71
+ const createEventsChannel = (herdId) => {
72
+ return scoutSupabase
73
+ .channel(`${herdId}-events`, { config: { private: true } })
74
+ .on("broadcast", { event: "*" }, handleEventBroadcast)
75
+ .subscribe((status) => {
76
+ if (status === "SUBSCRIBED") {
77
+ console.log(`[Events] ✅ Connected to herd ${herdId}`);
78
+ }
79
+ else if (status === "CHANNEL_ERROR") {
80
+ console.warn(`[Events] 🟡 Failed to connect to herd ${herdId}`);
81
+ }
82
+ });
83
+ };
84
+ useEffect(() => {
85
+ cleanupChannels();
86
+ // Clear previous update when switching herds
87
+ clearLatestUpdate();
88
+ // Create events channel for active herd
89
+ if (activeHerdId) {
90
+ const channel = createEventsChannel(activeHerdId);
91
+ channels.current.push(channel);
92
+ }
93
+ return cleanupChannels;
94
+ }, [activeHerdId, clearLatestUpdate]);
95
+ return [latestEventUpdate, clearLatestUpdate];
96
+ }
@@ -0,0 +1,5 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ import { IPin } from "../types/db";
4
+ import { RealtimeData } from "../types/realtime";
5
+ export declare function useScoutRealtimePins(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges?: boolean): [RealtimeData<IPin> | null, () => void];
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { useSelector } from "react-redux";
3
+ import { useEffect, useRef, useCallback, useState } from "react";
4
+ import { EnumRealtimeOperation } from "../types/realtime";
5
+ export function useScoutRealtimePins(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
6
+ const channels = useRef([]);
7
+ const [latestPinUpdate, setLatestPinUpdate] = useState(null);
8
+ const activeHerdId = useSelector((state) => state.scout.active_herd_id);
9
+ // Pin broadcast handler
10
+ const handlePinBroadcast = useCallback((payload) => {
11
+ console.log("[Pins] Broadcast received:", payload.payload.operation);
12
+ const data = payload.payload;
13
+ const pinData = data.record || data.old_record;
14
+ if (!pinData)
15
+ return;
16
+ let operation;
17
+ switch (data.operation) {
18
+ case "INSERT":
19
+ operation = EnumRealtimeOperation.INSERT;
20
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
21
+ console.log("[Pins] New pin received:", data.record);
22
+ // TODO: dispatch(addPinToStore(data.record));
23
+ }
24
+ break;
25
+ case "UPDATE":
26
+ operation = EnumRealtimeOperation.UPDATE;
27
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
28
+ // TODO: dispatch(updatePinInStore(data.record));
29
+ }
30
+ break;
31
+ case "DELETE":
32
+ operation = EnumRealtimeOperation.DELETE;
33
+ if (data.old_record && shouldUpdateGlobalStateOnChanges) {
34
+ // TODO: dispatch(deletePinFromStore(data.old_record));
35
+ }
36
+ break;
37
+ default:
38
+ return;
39
+ }
40
+ const realtimeData = {
41
+ data: pinData,
42
+ operation,
43
+ };
44
+ console.log(`[scout-core realtime] PIN ${data.operation} received for pin "${pinData.name}" (${pinData.id}):`, JSON.stringify(realtimeData));
45
+ setLatestPinUpdate(realtimeData);
46
+ }, [shouldUpdateGlobalStateOnChanges]);
47
+ // Clear latest update
48
+ const clearLatestUpdate = useCallback(() => {
49
+ setLatestPinUpdate(null);
50
+ }, []);
51
+ const cleanupChannels = () => {
52
+ channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
53
+ channels.current = [];
54
+ };
55
+ const createPinsChannel = (herdId) => {
56
+ return scoutSupabase
57
+ .channel(`${herdId}-pins`, { config: { private: true } })
58
+ .on("broadcast", { event: "*" }, handlePinBroadcast)
59
+ .subscribe((status) => {
60
+ if (status === "SUBSCRIBED") {
61
+ console.log(`[Pins] ✅ Connected to herd ${herdId} pins broadcasts`);
62
+ }
63
+ else if (status === "CHANNEL_ERROR") {
64
+ console.warn(`[Pins] 🟡 Failed to connect to herd ${herdId} pins broadcasts`);
65
+ }
66
+ });
67
+ };
68
+ useEffect(() => {
69
+ cleanupChannels();
70
+ // Clear previous update when switching herds
71
+ clearLatestUpdate();
72
+ // Create pins channel for active herd
73
+ if (activeHerdId) {
74
+ const channel = createPinsChannel(activeHerdId);
75
+ channels.current.push(channel);
76
+ }
77
+ return cleanupChannels;
78
+ }, [activeHerdId, clearLatestUpdate]);
79
+ return [latestPinUpdate, clearLatestUpdate];
80
+ }
@@ -0,0 +1,5 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ import { IPlan } from "../types/db";
4
+ import { RealtimeData } from "../types/realtime";
5
+ export declare function useScoutRealtimePlans(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges: boolean): [RealtimeData<IPlan> | null, () => void];
@@ -0,0 +1,83 @@
1
+ "use client";
2
+ import { useAppDispatch } from "../store/hooks";
3
+ import { useSelector } from "react-redux";
4
+ import { useEffect, useRef, useCallback, useState } from "react";
5
+ import { addPlan, deletePlan, updatePlan } from "../store/scout";
6
+ import { EnumRealtimeOperation } from "../types/realtime";
7
+ export function useScoutRealtimePlans(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
8
+ const channels = useRef([]);
9
+ const dispatch = useAppDispatch();
10
+ const [latestPlanUpdate, setLatestPlanUpdate] = useState(null);
11
+ const activeHerdId = useSelector((state) => state.scout.active_herd_id);
12
+ // Plan broadcast handler
13
+ const handlePlanBroadcast = useCallback((payload) => {
14
+ console.log("[Plans] Broadcast received:", payload.payload.operation);
15
+ const data = payload.payload;
16
+ const planData = data.record || data.old_record;
17
+ if (!planData)
18
+ return;
19
+ let operation;
20
+ switch (data.operation) {
21
+ case "INSERT":
22
+ operation = EnumRealtimeOperation.INSERT;
23
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
24
+ console.log("[Plans] New plan received:", data.record);
25
+ dispatch(addPlan(data.record));
26
+ }
27
+ break;
28
+ case "UPDATE":
29
+ operation = EnumRealtimeOperation.UPDATE;
30
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
31
+ dispatch(updatePlan(data.record));
32
+ }
33
+ break;
34
+ case "DELETE":
35
+ operation = EnumRealtimeOperation.DELETE;
36
+ if (data.old_record && shouldUpdateGlobalStateOnChanges) {
37
+ dispatch(deletePlan(data.old_record));
38
+ }
39
+ break;
40
+ default:
41
+ return;
42
+ }
43
+ const realtimeData = {
44
+ data: planData,
45
+ operation,
46
+ };
47
+ console.log(`[scout-core realtime] PLAN ${data.operation} received:`, JSON.stringify(realtimeData));
48
+ setLatestPlanUpdate(realtimeData);
49
+ }, [dispatch]);
50
+ // Clear latest update
51
+ const clearLatestUpdate = useCallback(() => {
52
+ setLatestPlanUpdate(null);
53
+ }, []);
54
+ const cleanupChannels = () => {
55
+ channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
56
+ channels.current = [];
57
+ };
58
+ const createPlansChannel = (herdId) => {
59
+ return scoutSupabase
60
+ .channel(`${herdId}-plans`, { config: { private: true } })
61
+ .on("broadcast", { event: "*" }, handlePlanBroadcast)
62
+ .subscribe((status) => {
63
+ if (status === "SUBSCRIBED") {
64
+ console.log(`[Plans] ✅ Connected to herd ${herdId}`);
65
+ }
66
+ else if (status === "CHANNEL_ERROR") {
67
+ console.warn(`[Plans] 🟡 Failed to connect to herd ${herdId}`);
68
+ }
69
+ });
70
+ };
71
+ useEffect(() => {
72
+ cleanupChannels();
73
+ // Clear previous update when switching herds
74
+ clearLatestUpdate();
75
+ // Create plans channel for active herd
76
+ if (activeHerdId) {
77
+ const channel = createPlansChannel(activeHerdId);
78
+ channels.current.push(channel);
79
+ }
80
+ return cleanupChannels;
81
+ }, [activeHerdId, clearLatestUpdate]);
82
+ return [latestPlanUpdate, clearLatestUpdate];
83
+ }
@@ -0,0 +1,5 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ import { ISessionWithCoordinates } from "../types/db";
4
+ import { RealtimeData } from "../types/realtime";
5
+ export declare function useScoutRealtimeSessions(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges: boolean): [RealtimeData<ISessionWithCoordinates> | null, () => void];
@@ -0,0 +1,83 @@
1
+ "use client";
2
+ import { useAppDispatch } from "../store/hooks";
3
+ import { useSelector } from "react-redux";
4
+ import { useEffect, useRef, useCallback, useState } from "react";
5
+ import { addSessionToStore, deleteSessionFromStore, updateSessionInStore, } from "../store/scout";
6
+ import { EnumRealtimeOperation } from "../types/realtime";
7
+ export function useScoutRealtimeSessions(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
8
+ const channels = useRef([]);
9
+ const dispatch = useAppDispatch();
10
+ const [latestSessionUpdate, setLatestSessionUpdate] = useState(null);
11
+ const activeHerdId = useSelector((state) => state.scout.active_herd_id);
12
+ // Session broadcast handler
13
+ const handleSessionBroadcast = useCallback((payload) => {
14
+ console.log("[Sessions] Broadcast received:", payload.payload.operation);
15
+ const data = payload.payload;
16
+ const sessionData = data.record || data.old_record;
17
+ if (!sessionData)
18
+ return;
19
+ let operation;
20
+ switch (data.operation) {
21
+ case "INSERT":
22
+ operation = EnumRealtimeOperation.INSERT;
23
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
24
+ console.log("[Sessions] New session received:", data.record);
25
+ dispatch(addSessionToStore(data.record));
26
+ }
27
+ break;
28
+ case "UPDATE":
29
+ operation = EnumRealtimeOperation.UPDATE;
30
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
31
+ dispatch(updateSessionInStore(data.record));
32
+ }
33
+ break;
34
+ case "DELETE":
35
+ operation = EnumRealtimeOperation.DELETE;
36
+ if (data.old_record && shouldUpdateGlobalStateOnChanges) {
37
+ dispatch(deleteSessionFromStore(data.old_record));
38
+ }
39
+ break;
40
+ default:
41
+ return;
42
+ }
43
+ const realtimeData = {
44
+ data: sessionData,
45
+ operation,
46
+ };
47
+ console.log(`[scout-core realtime] SESSION ${data.operation} received:`, JSON.stringify(realtimeData));
48
+ setLatestSessionUpdate(realtimeData);
49
+ }, [dispatch]);
50
+ // Clear latest update
51
+ const clearLatestUpdate = useCallback(() => {
52
+ setLatestSessionUpdate(null);
53
+ }, []);
54
+ const cleanupChannels = () => {
55
+ channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
56
+ channels.current = [];
57
+ };
58
+ const createSessionsChannel = (herdId) => {
59
+ return scoutSupabase
60
+ .channel(`${herdId}-sessions`, { config: { private: true } })
61
+ .on("broadcast", { event: "*" }, handleSessionBroadcast)
62
+ .subscribe((status) => {
63
+ if (status === "SUBSCRIBED") {
64
+ console.log(`[Sessions] ✅ Connected to herd ${herdId}`);
65
+ }
66
+ else if (status === "CHANNEL_ERROR") {
67
+ console.warn(`[Sessions] 🟡 Failed to connect to herd ${herdId}`);
68
+ }
69
+ });
70
+ };
71
+ useEffect(() => {
72
+ cleanupChannels();
73
+ // Clear previous update when switching herds
74
+ clearLatestUpdate();
75
+ // Create sessions channel for active herd
76
+ if (activeHerdId) {
77
+ const channel = createSessionsChannel(activeHerdId);
78
+ channels.current.push(channel);
79
+ }
80
+ return cleanupChannels;
81
+ }, [activeHerdId, clearLatestUpdate]);
82
+ return [latestSessionUpdate, clearLatestUpdate];
83
+ }
@@ -0,0 +1,5 @@
1
+ import { SupabaseClient } from "@supabase/supabase-js";
2
+ import { Database } from "../types/supabase";
3
+ import { ITagPrettyLocation } from "../types/db";
4
+ import { RealtimeData } from "../types/realtime";
5
+ export declare function useScoutRealtimeTags(scoutSupabase: SupabaseClient<Database>, shouldUpdateGlobalStateOnChanges: boolean): [RealtimeData<ITagPrettyLocation> | null, () => void];
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ import { useAppDispatch } from "../store/hooks";
3
+ import { useSelector } from "react-redux";
4
+ import { useEffect, useRef, useCallback, useState } from "react";
5
+ import { addTag, deleteTag, updateTag } from "../store/scout";
6
+ import { EnumRealtimeOperation } from "../types/realtime";
7
+ export function useScoutRealtimeTags(scoutSupabase, shouldUpdateGlobalStateOnChanges) {
8
+ const channels = useRef([]);
9
+ const dispatch = useAppDispatch();
10
+ const [latestTagUpdate, setLatestTagUpdate] = useState(null);
11
+ const activeHerdId = useSelector((state) => state.scout.active_herd_id);
12
+ // Tag broadcast handler
13
+ const handleTagBroadcast = useCallback((payload) => {
14
+ console.log("[Tags] Broadcast received:", payload.payload.operation);
15
+ const data = payload.payload;
16
+ const tagData = data.record || data.old_record;
17
+ if (!tagData)
18
+ return;
19
+ let operation;
20
+ switch (data.operation) {
21
+ case "INSERT":
22
+ operation = EnumRealtimeOperation.INSERT;
23
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
24
+ console.log("[Tags] New tag received:", data.record);
25
+ dispatch(addTag(data.record));
26
+ }
27
+ break;
28
+ case "UPDATE":
29
+ operation = EnumRealtimeOperation.UPDATE;
30
+ if (data.record && shouldUpdateGlobalStateOnChanges) {
31
+ console.log("[Tags] Tag updated:", data.record);
32
+ dispatch(updateTag(data.record));
33
+ }
34
+ break;
35
+ case "DELETE":
36
+ operation = EnumRealtimeOperation.DELETE;
37
+ if (data.old_record && shouldUpdateGlobalStateOnChanges) {
38
+ console.log("[Tags] Tag deleted:", data.old_record);
39
+ dispatch(deleteTag(data.old_record));
40
+ }
41
+ break;
42
+ default:
43
+ return;
44
+ }
45
+ const realtimeData = {
46
+ data: tagData,
47
+ operation,
48
+ };
49
+ console.log(`[scout-core realtime] TAG ${data.operation} received:`, JSON.stringify(realtimeData));
50
+ setLatestTagUpdate(realtimeData);
51
+ }, [dispatch]);
52
+ // Clear latest update
53
+ const clearLatestUpdate = useCallback(() => {
54
+ setLatestTagUpdate(null);
55
+ }, []);
56
+ const cleanupChannels = () => {
57
+ channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
58
+ channels.current = [];
59
+ };
60
+ const createTagsChannel = (herdId) => {
61
+ return scoutSupabase
62
+ .channel(`${herdId}-tags`, { config: { private: true } })
63
+ .on("broadcast", { event: "*" }, handleTagBroadcast)
64
+ .subscribe((status) => {
65
+ if (status === "SUBSCRIBED") {
66
+ console.log(`[Tags] ✅ Connected to herd ${herdId}`);
67
+ }
68
+ else if (status === "CHANNEL_ERROR") {
69
+ console.warn(`[Tags] 🟡 Failed to connect to herd ${herdId}`);
70
+ }
71
+ });
72
+ };
73
+ useEffect(() => {
74
+ cleanupChannels();
75
+ // Clear previous update when switching herds
76
+ clearLatestUpdate();
77
+ // Create tags channel for active herd
78
+ if (activeHerdId) {
79
+ const channel = createTagsChannel(activeHerdId);
80
+ channels.current.push(channel);
81
+ }
82
+ return cleanupChannels;
83
+ }, [activeHerdId, clearLatestUpdate]);
84
+ return [latestTagUpdate, clearLatestUpdate];
85
+ }
@@ -2,4 +2,4 @@ import { SupabaseClient } from "@supabase/supabase-js";
2
2
  import { Database } from "../types/supabase";
3
3
  import { IVersionsSoftware } from "../types/db";
4
4
  import { RealtimeData } from "../types/realtime";
5
- export declare function useScoutRealtimeVersionsSoftware(scoutSupabase: SupabaseClient<Database>): RealtimeData<IVersionsSoftware>[];
5
+ export declare function useScoutRealtimeVersionsSoftware(scoutSupabase: SupabaseClient<Database>): [RealtimeData<IVersionsSoftware> | null, () => void];
@@ -3,7 +3,7 @@ import { useEffect, useRef, useCallback, useState } from "react";
3
3
  import { EnumRealtimeOperation } from "../types/realtime";
4
4
  export function useScoutRealtimeVersionsSoftware(scoutSupabase) {
5
5
  const channels = useRef([]);
6
- const [newVersionsItems, setNewVersionsItems] = useState([]);
6
+ const [latestVersionUpdate, setLatestVersionUpdate] = useState(null);
7
7
  // Handle versions software broadcasts
8
8
  const handleVersionsSoftwareBroadcast = useCallback((payload) => {
9
9
  const { event, payload: data } = payload;
@@ -30,11 +30,11 @@ export function useScoutRealtimeVersionsSoftware(scoutSupabase) {
30
30
  data: versionData,
31
31
  operation,
32
32
  };
33
- setNewVersionsItems((prev) => [realtimeData, ...prev]);
33
+ setLatestVersionUpdate(realtimeData);
34
34
  }, []);
35
- // Clear new items
36
- const clearNewItems = useCallback(() => {
37
- setNewVersionsItems([]);
35
+ // Clear latest update
36
+ const clearLatestUpdate = useCallback(() => {
37
+ setLatestVersionUpdate(null);
38
38
  }, []);
39
39
  useEffect(() => {
40
40
  if (!scoutSupabase)
@@ -42,8 +42,8 @@ export function useScoutRealtimeVersionsSoftware(scoutSupabase) {
42
42
  // Clean up existing channels
43
43
  channels.current.forEach((channel) => scoutSupabase.removeChannel(channel));
44
44
  channels.current = [];
45
- // Clear previous items when setting up
46
- clearNewItems();
45
+ // Clear previous update when setting up
46
+ clearLatestUpdate();
47
47
  // Create versions_software channel
48
48
  const channel = scoutSupabase
49
49
  .channel("versions_software_changes", { config: { private: true } })
@@ -61,6 +61,6 @@ export function useScoutRealtimeVersionsSoftware(scoutSupabase) {
61
61
  channels.current.forEach((ch) => scoutSupabase.removeChannel(ch));
62
62
  channels.current = [];
63
63
  };
64
- }, [scoutSupabase, handleVersionsSoftwareBroadcast, clearNewItems]);
65
- return newVersionsItems;
64
+ }, [scoutSupabase, handleVersionsSoftwareBroadcast, clearLatestUpdate]);
65
+ return [latestVersionUpdate, clearLatestUpdate];
66
66
  }
package/dist/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from "./helpers/events";
21
21
  export * from "./helpers/gps";
22
22
  export * from "./helpers/herds";
23
23
  export * from "./helpers/location";
24
+ export * from "./helpers/pins";
24
25
  export * from "./helpers/plans";
25
26
  export * from "./helpers/layers";
26
27
  export * from "./helpers/sessions";
@@ -41,6 +42,10 @@ export * from "./helpers/components";
41
42
  export * from "./hooks/useScoutRealtimeConnectivity";
42
43
  export * from "./hooks/useScoutRealtimeDevices";
43
44
  export * from "./hooks/useScoutRealtimeVersionsSoftware";
45
+ export * from "./hooks/useScoutRealtimeEvents";
46
+ export * from "./hooks/useScoutRealtimeTags";
47
+ export * from "./hooks/useScoutRealtimeSessions";
48
+ export * from "./hooks/useScoutRealtimePlans";
44
49
  export * from "./hooks/useScoutRefresh";
45
50
  export * from "./providers";
46
51
  export * from "./store/scout";
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ export * from "./helpers/events";
24
24
  export * from "./helpers/gps";
25
25
  export * from "./helpers/herds";
26
26
  export * from "./helpers/location";
27
+ export * from "./helpers/pins";
27
28
  export * from "./helpers/plans";
28
29
  export * from "./helpers/layers";
29
30
  export * from "./helpers/sessions";
@@ -45,6 +46,10 @@ export * from "./helpers/components";
45
46
  export * from "./hooks/useScoutRealtimeConnectivity";
46
47
  export * from "./hooks/useScoutRealtimeDevices";
47
48
  export * from "./hooks/useScoutRealtimeVersionsSoftware";
49
+ export * from "./hooks/useScoutRealtimeEvents";
50
+ export * from "./hooks/useScoutRealtimeTags";
51
+ export * from "./hooks/useScoutRealtimeSessions";
52
+ export * from "./hooks/useScoutRealtimePlans";
48
53
  export * from "./hooks/useScoutRefresh";
49
54
  // Providers
50
55
  export * from "./providers";
@@ -547,6 +547,57 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
547
547
  referencedColumns: ["id"];
548
548
  }];
549
549
  };
550
+ pins: {
551
+ Row: {
552
+ altitude_relative_to_ground: number;
553
+ color: string;
554
+ created_at: string;
555
+ created_by: string | null;
556
+ description: string | null;
557
+ herd_id: number;
558
+ id: number;
559
+ latitude: unknown;
560
+ longitude: unknown;
561
+ name: string;
562
+ };
563
+ Insert: {
564
+ altitude_relative_to_ground: number;
565
+ color: string;
566
+ created_at?: string;
567
+ created_by?: string | null;
568
+ description?: string | null;
569
+ herd_id: number;
570
+ id?: number;
571
+ latitude: unknown;
572
+ longitude: unknown;
573
+ name: string;
574
+ };
575
+ Update: {
576
+ altitude_relative_to_ground?: number;
577
+ color?: string;
578
+ created_at?: string;
579
+ created_by?: string | null;
580
+ description?: string | null;
581
+ herd_id?: number;
582
+ id?: number;
583
+ latitude?: unknown;
584
+ longitude?: unknown;
585
+ name?: string;
586
+ };
587
+ Relationships: [{
588
+ foreignKeyName: "pins_created_by_fkey";
589
+ columns: ["created_by"];
590
+ isOneToOne: false;
591
+ referencedRelation: "users";
592
+ referencedColumns: ["id"];
593
+ }, {
594
+ foreignKeyName: "pins_herd_id_fkey";
595
+ columns: ["herd_id"];
596
+ isOneToOne: false;
597
+ referencedRelation: "herds";
598
+ referencedColumns: ["id"];
599
+ }];
600
+ };
550
601
  plans: {
551
602
  Row: {
552
603
  herd_id: number;
@@ -1265,6 +1316,18 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
1265
1316
  total_heartbeats: number;
1266
1317
  }[];
1267
1318
  };
1319
+ get_pins_for_herd: {
1320
+ Args: {
1321
+ herd_id_caller: number;
1322
+ };
1323
+ Returns: Database["public"]["CompositeTypes"]["pins_pretty_location"][];
1324
+ SetofOptions: {
1325
+ from: "*";
1326
+ to: "pins_pretty_location";
1327
+ isOneToOne: false;
1328
+ isSetofReturn: true;
1329
+ };
1330
+ };
1268
1331
  get_sessions_with_coordinates: {
1269
1332
  Args: {
1270
1333
  herd_id_caller: number;
@@ -1484,6 +1547,18 @@ export declare function useSupabase(): SupabaseClient<Database, "public", "publi
1484
1547
  is_public: boolean | null;
1485
1548
  tags: Database["public"]["Tables"]["tags"]["Row"][] | null;
1486
1549
  };
1550
+ pins_pretty_location: {
1551
+ id: number | null;
1552
+ created_at: string | null;
1553
+ altitude_relative_to_ground: number | null;
1554
+ color: string | null;
1555
+ name: string | null;
1556
+ description: string | null;
1557
+ herd_id: number | null;
1558
+ created_by: string | null;
1559
+ latitude: number | null;
1560
+ longitude: number | null;
1561
+ };
1487
1562
  session_with_coordinates: {
1488
1563
  id: number | null;
1489
1564
  device_id: number | null;
@@ -9,6 +9,7 @@ export type IUser = User;
9
9
  export type IDevice = Database["public"]["CompositeTypes"]["device_pretty_location"] & {
10
10
  api_keys_scout?: IApiKeyScout[];
11
11
  };
12
+ export type IPin = Database["public"]["CompositeTypes"]["pins_pretty_location"];
12
13
  export type IEvent = Database["public"]["Tables"]["events"]["Row"];
13
14
  export type ITag = Database["public"]["Tables"]["tags"]["Row"];
14
15
  export type ITagPrettyLocation = Database["public"]["CompositeTypes"]["tags_pretty_location"];
@@ -32,6 +33,7 @@ export type IArtifactWithMediaUrl = IArtifact & {
32
33
  export type ComponentInsert = Database["public"]["Tables"]["components"]["Insert"];
33
34
  export type VersionsSoftwareInsert = Database["public"]["Tables"]["versions_software"]["Insert"];
34
35
  export type ArtifactInsert = Database["public"]["Tables"]["artifacts"]["Insert"];
36
+ export type PinInsert = Database["public"]["Tables"]["pins"]["Insert"];
35
37
  export type IEventWithTags = Database["public"]["CompositeTypes"]["event_with_tags"] & {
36
38
  earthranger_url: string | null;
37
39
  file_path: string | null;
@@ -574,6 +574,60 @@ export type Database = {
574
574
  }
575
575
  ];
576
576
  };
577
+ pins: {
578
+ Row: {
579
+ altitude_relative_to_ground: number;
580
+ color: string;
581
+ created_at: string;
582
+ created_by: string | null;
583
+ description: string | null;
584
+ herd_id: number;
585
+ id: number;
586
+ latitude: unknown;
587
+ longitude: unknown;
588
+ name: string;
589
+ };
590
+ Insert: {
591
+ altitude_relative_to_ground: number;
592
+ color: string;
593
+ created_at?: string;
594
+ created_by?: string | null;
595
+ description?: string | null;
596
+ herd_id: number;
597
+ id?: number;
598
+ latitude: unknown;
599
+ longitude: unknown;
600
+ name: string;
601
+ };
602
+ Update: {
603
+ altitude_relative_to_ground?: number;
604
+ color?: string;
605
+ created_at?: string;
606
+ created_by?: string | null;
607
+ description?: string | null;
608
+ herd_id?: number;
609
+ id?: number;
610
+ latitude?: unknown;
611
+ longitude?: unknown;
612
+ name?: string;
613
+ };
614
+ Relationships: [
615
+ {
616
+ foreignKeyName: "pins_created_by_fkey";
617
+ columns: ["created_by"];
618
+ isOneToOne: false;
619
+ referencedRelation: "users";
620
+ referencedColumns: ["id"];
621
+ },
622
+ {
623
+ foreignKeyName: "pins_herd_id_fkey";
624
+ columns: ["herd_id"];
625
+ isOneToOne: false;
626
+ referencedRelation: "herds";
627
+ referencedColumns: ["id"];
628
+ }
629
+ ];
630
+ };
577
631
  plans: {
578
632
  Row: {
579
633
  herd_id: number;
@@ -1317,6 +1371,18 @@ export type Database = {
1317
1371
  total_heartbeats: number;
1318
1372
  }[];
1319
1373
  };
1374
+ get_pins_for_herd: {
1375
+ Args: {
1376
+ herd_id_caller: number;
1377
+ };
1378
+ Returns: Database["public"]["CompositeTypes"]["pins_pretty_location"][];
1379
+ SetofOptions: {
1380
+ from: "*";
1381
+ to: "pins_pretty_location";
1382
+ isOneToOne: false;
1383
+ isSetofReturn: true;
1384
+ };
1385
+ };
1320
1386
  get_sessions_with_coordinates: {
1321
1387
  Args: {
1322
1388
  herd_id_caller: number;
@@ -1536,6 +1602,18 @@ export type Database = {
1536
1602
  is_public: boolean | null;
1537
1603
  tags: Database["public"]["Tables"]["tags"]["Row"][] | null;
1538
1604
  };
1605
+ pins_pretty_location: {
1606
+ id: number | null;
1607
+ created_at: string | null;
1608
+ altitude_relative_to_ground: number | null;
1609
+ color: string | null;
1610
+ name: string | null;
1611
+ description: string | null;
1612
+ herd_id: number | null;
1613
+ created_by: string | null;
1614
+ latitude: number | null;
1615
+ longitude: number | null;
1616
+ };
1539
1617
  session_with_coordinates: {
1540
1618
  id: number | null;
1541
1619
  device_id: number | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.2.1",
3
+ "version": "1.2.4",
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",