@oxyhq/auth 1.0.0

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.
Files changed (119) hide show
  1. package/README.md +56 -0
  2. package/dist/cjs/WebOxyProvider.js +287 -0
  3. package/dist/cjs/hooks/mutations/index.js +23 -0
  4. package/dist/cjs/hooks/mutations/mutationFactory.js +126 -0
  5. package/dist/cjs/hooks/mutations/useAccountMutations.js +275 -0
  6. package/dist/cjs/hooks/mutations/useServicesMutations.js +149 -0
  7. package/dist/cjs/hooks/queries/index.js +35 -0
  8. package/dist/cjs/hooks/queries/queryKeys.js +82 -0
  9. package/dist/cjs/hooks/queries/useAccountQueries.js +141 -0
  10. package/dist/cjs/hooks/queries/useSecurityQueries.js +45 -0
  11. package/dist/cjs/hooks/queries/useServicesQueries.js +113 -0
  12. package/dist/cjs/hooks/queryClient.js +110 -0
  13. package/dist/cjs/hooks/useAssets.js +225 -0
  14. package/dist/cjs/hooks/useFileDownloadUrl.js +91 -0
  15. package/dist/cjs/hooks/useFileFiltering.js +81 -0
  16. package/dist/cjs/hooks/useFollow.js +159 -0
  17. package/dist/cjs/hooks/useFollow.types.js +4 -0
  18. package/dist/cjs/hooks/useQueryClient.js +16 -0
  19. package/dist/cjs/hooks/useSessionSocket.js +215 -0
  20. package/dist/cjs/hooks/useWebSSO.js +146 -0
  21. package/dist/cjs/index.js +115 -0
  22. package/dist/cjs/stores/accountStore.js +226 -0
  23. package/dist/cjs/stores/assetStore.js +192 -0
  24. package/dist/cjs/stores/authStore.js +47 -0
  25. package/dist/cjs/stores/followStore.js +154 -0
  26. package/dist/cjs/utils/authHelpers.js +154 -0
  27. package/dist/cjs/utils/avatarUtils.js +77 -0
  28. package/dist/cjs/utils/errorHandlers.js +128 -0
  29. package/dist/cjs/utils/sessionHelpers.js +90 -0
  30. package/dist/cjs/utils/storageHelpers.js +147 -0
  31. package/dist/esm/WebOxyProvider.js +282 -0
  32. package/dist/esm/hooks/mutations/index.js +10 -0
  33. package/dist/esm/hooks/mutations/mutationFactory.js +122 -0
  34. package/dist/esm/hooks/mutations/useAccountMutations.js +267 -0
  35. package/dist/esm/hooks/mutations/useServicesMutations.js +141 -0
  36. package/dist/esm/hooks/queries/index.js +14 -0
  37. package/dist/esm/hooks/queries/queryKeys.js +76 -0
  38. package/dist/esm/hooks/queries/useAccountQueries.js +131 -0
  39. package/dist/esm/hooks/queries/useSecurityQueries.js +40 -0
  40. package/dist/esm/hooks/queries/useServicesQueries.js +105 -0
  41. package/dist/esm/hooks/queryClient.js +104 -0
  42. package/dist/esm/hooks/useAssets.js +220 -0
  43. package/dist/esm/hooks/useFileDownloadUrl.js +86 -0
  44. package/dist/esm/hooks/useFileFiltering.js +78 -0
  45. package/dist/esm/hooks/useFollow.js +154 -0
  46. package/dist/esm/hooks/useFollow.types.js +3 -0
  47. package/dist/esm/hooks/useQueryClient.js +12 -0
  48. package/dist/esm/hooks/useSessionSocket.js +209 -0
  49. package/dist/esm/hooks/useWebSSO.js +143 -0
  50. package/dist/esm/index.js +48 -0
  51. package/dist/esm/stores/accountStore.js +219 -0
  52. package/dist/esm/stores/assetStore.js +180 -0
  53. package/dist/esm/stores/authStore.js +44 -0
  54. package/dist/esm/stores/followStore.js +151 -0
  55. package/dist/esm/utils/authHelpers.js +145 -0
  56. package/dist/esm/utils/avatarUtils.js +72 -0
  57. package/dist/esm/utils/errorHandlers.js +121 -0
  58. package/dist/esm/utils/sessionHelpers.js +84 -0
  59. package/dist/esm/utils/storageHelpers.js +108 -0
  60. package/dist/types/WebOxyProvider.d.ts +97 -0
  61. package/dist/types/hooks/mutations/index.d.ts +8 -0
  62. package/dist/types/hooks/mutations/mutationFactory.d.ts +75 -0
  63. package/dist/types/hooks/mutations/useAccountMutations.d.ts +68 -0
  64. package/dist/types/hooks/mutations/useServicesMutations.d.ts +22 -0
  65. package/dist/types/hooks/queries/index.d.ts +10 -0
  66. package/dist/types/hooks/queries/queryKeys.d.ts +64 -0
  67. package/dist/types/hooks/queries/useAccountQueries.d.ts +42 -0
  68. package/dist/types/hooks/queries/useSecurityQueries.d.ts +14 -0
  69. package/dist/types/hooks/queries/useServicesQueries.d.ts +31 -0
  70. package/dist/types/hooks/queryClient.d.ts +18 -0
  71. package/dist/types/hooks/useAssets.d.ts +34 -0
  72. package/dist/types/hooks/useFileDownloadUrl.d.ts +18 -0
  73. package/dist/types/hooks/useFileFiltering.d.ts +28 -0
  74. package/dist/types/hooks/useFollow.d.ts +61 -0
  75. package/dist/types/hooks/useFollow.types.d.ts +32 -0
  76. package/dist/types/hooks/useQueryClient.d.ts +6 -0
  77. package/dist/types/hooks/useSessionSocket.d.ts +13 -0
  78. package/dist/types/hooks/useWebSSO.d.ts +57 -0
  79. package/dist/types/index.d.ts +46 -0
  80. package/dist/types/stores/accountStore.d.ts +33 -0
  81. package/dist/types/stores/assetStore.d.ts +53 -0
  82. package/dist/types/stores/authStore.d.ts +16 -0
  83. package/dist/types/stores/followStore.d.ts +24 -0
  84. package/dist/types/utils/authHelpers.d.ts +98 -0
  85. package/dist/types/utils/avatarUtils.d.ts +33 -0
  86. package/dist/types/utils/errorHandlers.d.ts +34 -0
  87. package/dist/types/utils/sessionHelpers.d.ts +63 -0
  88. package/dist/types/utils/storageHelpers.d.ts +27 -0
  89. package/package.json +71 -0
  90. package/src/WebOxyProvider.tsx +372 -0
  91. package/src/global.d.ts +1 -0
  92. package/src/hooks/mutations/index.ts +25 -0
  93. package/src/hooks/mutations/mutationFactory.ts +215 -0
  94. package/src/hooks/mutations/useAccountMutations.ts +344 -0
  95. package/src/hooks/mutations/useServicesMutations.ts +164 -0
  96. package/src/hooks/queries/index.ts +36 -0
  97. package/src/hooks/queries/queryKeys.ts +88 -0
  98. package/src/hooks/queries/useAccountQueries.ts +152 -0
  99. package/src/hooks/queries/useSecurityQueries.ts +64 -0
  100. package/src/hooks/queries/useServicesQueries.ts +126 -0
  101. package/src/hooks/queryClient.ts +112 -0
  102. package/src/hooks/useAssets.ts +291 -0
  103. package/src/hooks/useFileDownloadUrl.ts +118 -0
  104. package/src/hooks/useFileFiltering.ts +115 -0
  105. package/src/hooks/useFollow.ts +175 -0
  106. package/src/hooks/useFollow.types.ts +33 -0
  107. package/src/hooks/useQueryClient.ts +17 -0
  108. package/src/hooks/useSessionSocket.ts +233 -0
  109. package/src/hooks/useWebSSO.ts +187 -0
  110. package/src/index.ts +144 -0
  111. package/src/stores/accountStore.ts +296 -0
  112. package/src/stores/assetStore.ts +281 -0
  113. package/src/stores/authStore.ts +63 -0
  114. package/src/stores/followStore.ts +181 -0
  115. package/src/utils/authHelpers.ts +183 -0
  116. package/src/utils/avatarUtils.ts +103 -0
  117. package/src/utils/errorHandlers.ts +194 -0
  118. package/src/utils/sessionHelpers.ts +151 -0
  119. package/src/utils/storageHelpers.ts +130 -0
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useFileDownloadUrl = exports.setOxyFileUrlInstance = void 0;
4
+ const react_1 = require("react");
5
+ let oxyInstance = null;
6
+ const setOxyFileUrlInstance = (instance) => {
7
+ oxyInstance = instance;
8
+ };
9
+ exports.setOxyFileUrlInstance = setOxyFileUrlInstance;
10
+ /**
11
+ * Hook to resolve a file's download URL asynchronously.
12
+ *
13
+ * Prefers `getFileDownloadUrlAsync` and falls back to the synchronous
14
+ * `getFileDownloadUrl` helper if the async call fails.
15
+ */
16
+ const useFileDownloadUrl = (fileId, options) => {
17
+ const [url, setUrl] = (0, react_1.useState)(null);
18
+ const [loading, setLoading] = (0, react_1.useState)(false);
19
+ const [error, setError] = (0, react_1.useState)(null);
20
+ (0, react_1.useEffect)(() => {
21
+ if (!fileId) {
22
+ setUrl(null);
23
+ setLoading(false);
24
+ setError(null);
25
+ return;
26
+ }
27
+ if (!oxyInstance) {
28
+ // Fail silently but don't crash the UI – caller can decide what to do with null URL.
29
+ setUrl(null);
30
+ setLoading(false);
31
+ setError(new Error('OxyServices instance not configured for useFileDownloadUrl'));
32
+ return;
33
+ }
34
+ let cancelled = false;
35
+ const load = async () => {
36
+ setLoading(true);
37
+ setError(null);
38
+ // Store instance in local variable for TypeScript null checking
39
+ const instance = oxyInstance;
40
+ if (!instance) {
41
+ setLoading(false);
42
+ setError(new Error('OxyServices instance not configured for useFileDownloadUrl'));
43
+ return;
44
+ }
45
+ try {
46
+ const { variant, expiresIn } = options || {};
47
+ let resolvedUrl = null;
48
+ if (typeof instance.getFileDownloadUrlAsync === 'function') {
49
+ resolvedUrl = await instance.getFileDownloadUrlAsync(fileId, variant, expiresIn);
50
+ }
51
+ if (!resolvedUrl && typeof instance.getFileDownloadUrl === 'function') {
52
+ resolvedUrl = instance.getFileDownloadUrl(fileId, variant, expiresIn);
53
+ }
54
+ if (!cancelled) {
55
+ setUrl(resolvedUrl || null);
56
+ }
57
+ }
58
+ catch (err) {
59
+ // Fallback to sync URL on error where possible
60
+ try {
61
+ if (typeof instance.getFileDownloadUrl === 'function') {
62
+ const { variant, expiresIn } = options || {};
63
+ const fallbackUrl = instance.getFileDownloadUrl(fileId, variant, expiresIn);
64
+ if (!cancelled) {
65
+ setUrl(fallbackUrl || null);
66
+ setError(err instanceof Error ? err : new Error(String(err)));
67
+ }
68
+ return;
69
+ }
70
+ }
71
+ catch {
72
+ // ignore secondary failure, we'll surface the original error below
73
+ }
74
+ if (!cancelled) {
75
+ setError(err instanceof Error ? err : new Error(String(err)));
76
+ }
77
+ }
78
+ finally {
79
+ if (!cancelled) {
80
+ setLoading(false);
81
+ }
82
+ }
83
+ };
84
+ load();
85
+ return () => {
86
+ cancelled = true;
87
+ };
88
+ }, [fileId, options?.variant, options?.expiresIn]);
89
+ return { url, loading, error };
90
+ };
91
+ exports.useFileDownloadUrl = useFileDownloadUrl;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useFileFiltering = useFileFiltering;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Hook for file filtering, sorting, and search functionality
7
+ * Extracts common file management logic for reuse across components
8
+ */
9
+ function useFileFiltering({ files, initialViewMode = 'all', initialSortBy = 'date', initialSortOrder = 'desc', }) {
10
+ const [viewMode, setViewMode] = (0, react_1.useState)(initialViewMode);
11
+ const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
12
+ const [sortBy, setSortBy] = (0, react_1.useState)(initialSortBy);
13
+ const [sortOrder, setSortOrder] = (0, react_1.useState)(initialSortOrder);
14
+ const toggleSortOrder = (0, react_1.useCallback)(() => {
15
+ setSortOrder((prev) => (prev === 'asc' ? 'desc' : 'asc'));
16
+ }, []);
17
+ const filteredFiles = (0, react_1.useMemo)(() => {
18
+ // Filter by view mode
19
+ let filteredByMode = files;
20
+ if (viewMode === 'photos') {
21
+ filteredByMode = files.filter((file) => file.contentType.startsWith('image/'));
22
+ }
23
+ else if (viewMode === 'videos') {
24
+ filteredByMode = files.filter((file) => file.contentType.startsWith('video/'));
25
+ }
26
+ else if (viewMode === 'documents') {
27
+ filteredByMode = files.filter((file) => file.contentType.includes('pdf') ||
28
+ file.contentType.includes('document') ||
29
+ file.contentType.includes('text') ||
30
+ file.contentType.includes('msword') ||
31
+ file.contentType.includes('excel') ||
32
+ file.contentType.includes('spreadsheet') ||
33
+ file.contentType.includes('presentation') ||
34
+ file.contentType.includes('powerpoint'));
35
+ }
36
+ else if (viewMode === 'audio') {
37
+ filteredByMode = files.filter((file) => file.contentType.startsWith('audio/'));
38
+ }
39
+ // Filter by search query
40
+ let filtered = filteredByMode;
41
+ if (searchQuery.trim()) {
42
+ const query = searchQuery.toLowerCase();
43
+ filtered = filteredByMode.filter((file) => file.filename.toLowerCase().includes(query) ||
44
+ file.contentType.toLowerCase().includes(query) ||
45
+ (file.metadata?.description &&
46
+ file.metadata.description.toLowerCase().includes(query)));
47
+ }
48
+ // Sort files
49
+ const sorted = [...filtered].sort((a, b) => {
50
+ let comparison = 0;
51
+ if (sortBy === 'date') {
52
+ const dateA = new Date(a.uploadDate || 0).getTime();
53
+ const dateB = new Date(b.uploadDate || 0).getTime();
54
+ comparison = dateA - dateB;
55
+ }
56
+ else if (sortBy === 'size') {
57
+ comparison = (a.length || 0) - (b.length || 0);
58
+ }
59
+ else if (sortBy === 'name') {
60
+ comparison = (a.filename || '').localeCompare(b.filename || '');
61
+ }
62
+ else if (sortBy === 'type') {
63
+ comparison = (a.contentType || '').localeCompare(b.contentType || '');
64
+ }
65
+ return sortOrder === 'asc' ? comparison : -comparison;
66
+ });
67
+ return sorted;
68
+ }, [files, searchQuery, viewMode, sortBy, sortOrder]);
69
+ return {
70
+ filteredFiles,
71
+ viewMode,
72
+ setViewMode,
73
+ searchQuery,
74
+ setSearchQuery,
75
+ sortBy,
76
+ setSortBy,
77
+ sortOrder,
78
+ setSortOrder,
79
+ toggleSortOrder,
80
+ };
81
+ }
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useFollowerCounts = exports.useFollow = void 0;
4
+ const react_1 = require("react");
5
+ const followStore_1 = require("../stores/followStore");
6
+ const WebOxyProvider_1 = require("../WebOxyProvider");
7
+ const useFollow = (userId) => {
8
+ const { oxyServices } = (0, WebOxyProvider_1.useWebOxy)();
9
+ const userIds = (0, react_1.useMemo)(() => (Array.isArray(userId) ? userId : userId ? [userId] : []), [userId]);
10
+ const isSingleUser = typeof userId === 'string';
11
+ // Zustand selectors
12
+ const followState = (0, followStore_1.useFollowStore)();
13
+ // Single user helpers
14
+ const isFollowing = isSingleUser && userId ? followState.followingUsers[userId] ?? false : false;
15
+ const isLoading = isSingleUser && userId ? followState.loadingUsers[userId] ?? false : false;
16
+ const error = isSingleUser && userId ? followState.errors[userId] ?? null : null;
17
+ // Follower count helpers
18
+ const followerCount = isSingleUser && userId ? followState.followerCounts[userId] ?? null : null;
19
+ const followingCount = isSingleUser && userId ? followState.followingCounts[userId] ?? null : null;
20
+ const isLoadingCounts = isSingleUser && userId ? followState.loadingCounts[userId] ?? false : false;
21
+ const toggleFollow = (0, react_1.useCallback)(async () => {
22
+ if (!isSingleUser || !userId)
23
+ throw new Error('toggleFollow is only available for single user mode');
24
+ await followState.toggleFollowUser(userId, oxyServices, isFollowing);
25
+ }, [isSingleUser, userId, followState, oxyServices, isFollowing]);
26
+ const setFollowStatus = (0, react_1.useCallback)((following) => {
27
+ if (!isSingleUser || !userId)
28
+ throw new Error('setFollowStatus is only available for single user mode');
29
+ followState.setFollowingStatus(userId, following);
30
+ }, [isSingleUser, userId, followState]);
31
+ const fetchStatus = (0, react_1.useCallback)(async () => {
32
+ if (!isSingleUser || !userId)
33
+ throw new Error('fetchStatus is only available for single user mode');
34
+ await followState.fetchFollowStatus(userId, oxyServices);
35
+ }, [isSingleUser, userId, followState, oxyServices]);
36
+ const clearError = (0, react_1.useCallback)(() => {
37
+ if (!isSingleUser || !userId)
38
+ throw new Error('clearError is only available for single user mode');
39
+ followState.clearFollowError(userId);
40
+ }, [isSingleUser, userId, followState]);
41
+ const fetchUserCounts = (0, react_1.useCallback)(async () => {
42
+ if (!isSingleUser || !userId)
43
+ throw new Error('fetchUserCounts is only available for single user mode');
44
+ await followState.fetchUserCounts(userId, oxyServices);
45
+ }, [isSingleUser, userId, followState, oxyServices]);
46
+ const setFollowerCount = (0, react_1.useCallback)((count) => {
47
+ if (!isSingleUser || !userId)
48
+ throw new Error('setFollowerCount is only available for single user mode');
49
+ followState.setFollowerCount(userId, count);
50
+ }, [isSingleUser, userId, followState]);
51
+ const setFollowingCount = (0, react_1.useCallback)((count) => {
52
+ if (!isSingleUser || !userId)
53
+ throw new Error('setFollowingCount is only available for single user mode');
54
+ followState.setFollowingCount(userId, count);
55
+ }, [isSingleUser, userId, followState]);
56
+ // Auto-fetch counts when hook is used for a single user and counts are missing.
57
+ (0, react_1.useEffect)(() => {
58
+ if (!isSingleUser || !userId)
59
+ return;
60
+ // If either count is not set and we're not already loading counts, trigger a fetch.
61
+ if ((followerCount === null || followingCount === null) && !isLoadingCounts) {
62
+ fetchUserCounts().catch((err) => console.warn('useFollow: fetchUserCounts failed', err));
63
+ }
64
+ }, [isSingleUser, userId, followerCount, followingCount, isLoadingCounts, fetchUserCounts]);
65
+ // Multiple user helpers
66
+ const followData = (0, react_1.useMemo)(() => {
67
+ const data = {};
68
+ userIds.forEach(uid => {
69
+ data[uid] = {
70
+ isFollowing: followState.followingUsers[uid] ?? false,
71
+ isLoading: followState.loadingUsers[uid] ?? false,
72
+ error: followState.errors[uid] ?? null,
73
+ };
74
+ });
75
+ return data;
76
+ }, [userIds, followState.followingUsers, followState.loadingUsers, followState.errors]);
77
+ const toggleFollowForUser = (0, react_1.useCallback)(async (targetUserId) => {
78
+ const currentState = followState.followingUsers[targetUserId] ?? false;
79
+ await followState.toggleFollowUser(targetUserId, oxyServices, currentState);
80
+ }, [followState, oxyServices]);
81
+ const setFollowStatusForUser = (0, react_1.useCallback)((targetUserId, following) => {
82
+ followState.setFollowingStatus(targetUserId, following);
83
+ }, [followState]);
84
+ const fetchStatusForUser = (0, react_1.useCallback)(async (targetUserId) => {
85
+ await followState.fetchFollowStatus(targetUserId, oxyServices);
86
+ }, [followState, oxyServices]);
87
+ const fetchAllStatuses = (0, react_1.useCallback)(async () => {
88
+ await Promise.all(userIds.map(uid => followState.fetchFollowStatus(uid, oxyServices)));
89
+ }, [userIds, followState, oxyServices]);
90
+ const clearErrorForUser = (0, react_1.useCallback)((targetUserId) => {
91
+ followState.clearFollowError(targetUserId);
92
+ }, [followState]);
93
+ const updateCountsFromFollowAction = (0, react_1.useCallback)((targetUserId, action, counts) => {
94
+ const currentUserId = oxyServices.getCurrentUserId() || undefined;
95
+ followState.updateCountsFromFollowAction(targetUserId, action, counts, currentUserId);
96
+ }, [followState, oxyServices]);
97
+ // Aggregate helpers for multiple users
98
+ const isAnyLoading = userIds.some(uid => followState.loadingUsers[uid]);
99
+ const hasAnyError = userIds.some(uid => !!followState.errors[uid]);
100
+ const allFollowing = userIds.every(uid => followState.followingUsers[uid]);
101
+ const allNotFollowing = userIds.every(uid => !followState.followingUsers[uid]);
102
+ if (isSingleUser && userId) {
103
+ return {
104
+ isFollowing,
105
+ isLoading,
106
+ error,
107
+ toggleFollow,
108
+ setFollowStatus,
109
+ fetchStatus,
110
+ clearError,
111
+ // Follower count methods
112
+ followerCount,
113
+ followingCount,
114
+ isLoadingCounts,
115
+ fetchUserCounts,
116
+ setFollowerCount,
117
+ setFollowingCount,
118
+ };
119
+ }
120
+ return {
121
+ followData,
122
+ toggleFollowForUser,
123
+ setFollowStatusForUser,
124
+ fetchStatusForUser,
125
+ fetchAllStatuses,
126
+ clearErrorForUser,
127
+ isAnyLoading,
128
+ hasAnyError,
129
+ allFollowing,
130
+ allNotFollowing,
131
+ };
132
+ };
133
+ exports.useFollow = useFollow;
134
+ // Convenience hook for just follower counts
135
+ const useFollowerCounts = (userId) => {
136
+ const { oxyServices } = (0, WebOxyProvider_1.useWebOxy)();
137
+ const followState = (0, followStore_1.useFollowStore)();
138
+ const followerCount = followState.followerCounts[userId] ?? null;
139
+ const followingCount = followState.followingCounts[userId] ?? null;
140
+ const isLoadingCounts = followState.loadingCounts[userId] ?? false;
141
+ const fetchUserCounts = (0, react_1.useCallback)(async () => {
142
+ await followState.fetchUserCounts(userId, oxyServices);
143
+ }, [userId, followState, oxyServices]);
144
+ const setFollowerCount = (0, react_1.useCallback)((count) => {
145
+ followState.setFollowerCount(userId, count);
146
+ }, [userId, followState]);
147
+ const setFollowingCount = (0, react_1.useCallback)((count) => {
148
+ followState.setFollowingCount(userId, count);
149
+ }, [userId, followState]);
150
+ return {
151
+ followerCount,
152
+ followingCount,
153
+ isLoadingCounts,
154
+ fetchUserCounts,
155
+ setFollowerCount,
156
+ setFollowingCount,
157
+ };
158
+ };
159
+ exports.useFollowerCounts = useFollowerCounts;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // Type-only definition for the useFollow hook to allow context exposure without runtime import cycles.
3
+ // Expand this as needed to better reflect the real return type.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useQueryClient = void 0;
4
+ const react_query_1 = require("@tanstack/react-query");
5
+ /**
6
+ * Custom hook to access the QueryClient
7
+ * Provides type safety and ensures client is available
8
+ */
9
+ const useQueryClient = () => {
10
+ const queryClient = (0, react_query_1.useQueryClient)();
11
+ if (!queryClient) {
12
+ throw new Error('QueryClient is not available. Make sure OxyProvider is wrapping your app.');
13
+ }
14
+ return queryClient;
15
+ };
16
+ exports.useQueryClient = useQueryClient;
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.useSessionSocket = useSessionSocket;
7
+ const react_1 = require("react");
8
+ const socket_io_client_1 = __importDefault(require("socket.io-client"));
9
+ const sonner_1 = require("sonner");
10
+ const core_1 = require("@oxyhq/core");
11
+ const core_2 = require("@oxyhq/core");
12
+ const debug = (0, core_2.createDebugLogger)('SessionSocket');
13
+ function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, clearSessionState, baseURL, onRemoteSignOut, onSessionRemoved }) {
14
+ const socketRef = (0, react_1.useRef)(null);
15
+ const joinedRoomRef = (0, react_1.useRef)(null);
16
+ // Store callbacks in refs to avoid re-joining when they change
17
+ const refreshSessionsRef = (0, react_1.useRef)(refreshSessions);
18
+ const logoutRef = (0, react_1.useRef)(logout);
19
+ const clearSessionStateRef = (0, react_1.useRef)(clearSessionState);
20
+ const onRemoteSignOutRef = (0, react_1.useRef)(onRemoteSignOut);
21
+ const onSessionRemovedRef = (0, react_1.useRef)(onSessionRemoved);
22
+ const activeSessionIdRef = (0, react_1.useRef)(activeSessionId);
23
+ const currentDeviceIdRef = (0, react_1.useRef)(currentDeviceId);
24
+ // Update refs when callbacks change
25
+ (0, react_1.useEffect)(() => {
26
+ refreshSessionsRef.current = refreshSessions;
27
+ logoutRef.current = logout;
28
+ clearSessionStateRef.current = clearSessionState;
29
+ onRemoteSignOutRef.current = onRemoteSignOut;
30
+ onSessionRemovedRef.current = onSessionRemoved;
31
+ activeSessionIdRef.current = activeSessionId;
32
+ currentDeviceIdRef.current = currentDeviceId;
33
+ }, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId]);
34
+ (0, react_1.useEffect)(() => {
35
+ if (!userId || !baseURL) {
36
+ // Clean up if userId or baseURL becomes invalid
37
+ if (socketRef.current && joinedRoomRef.current) {
38
+ socketRef.current.emit('leave', { userId: joinedRoomRef.current });
39
+ joinedRoomRef.current = null;
40
+ }
41
+ return;
42
+ }
43
+ const roomId = `user:${userId}`;
44
+ // Only create socket if it doesn't exist
45
+ if (!socketRef.current) {
46
+ socketRef.current = (0, socket_io_client_1.default)(baseURL, {
47
+ transports: ['websocket'],
48
+ });
49
+ }
50
+ const socket = socketRef.current;
51
+ // Only join if we haven't already joined this room
52
+ if (joinedRoomRef.current !== roomId) {
53
+ // Leave previous room if switching users
54
+ if (joinedRoomRef.current) {
55
+ socket.emit('leave', { userId: joinedRoomRef.current });
56
+ }
57
+ socket.emit('join', { userId: roomId });
58
+ joinedRoomRef.current = roomId;
59
+ debug.log('Emitting join for room:', roomId);
60
+ }
61
+ // Set up event handlers (only once per socket instance)
62
+ const handleConnect = () => {
63
+ debug.log('Socket connected:', socket.id);
64
+ };
65
+ const handleSessionUpdate = async (data) => {
66
+ debug.log('Received session_update:', data);
67
+ const currentActiveSessionId = activeSessionIdRef.current;
68
+ const currentDeviceId = currentDeviceIdRef.current;
69
+ // Handle different event types
70
+ if (data.type === 'session_removed') {
71
+ // Track removed session
72
+ if (data.sessionId && onSessionRemovedRef.current) {
73
+ onSessionRemovedRef.current(data.sessionId);
74
+ }
75
+ // If the removed sessionId matches the current activeSessionId, immediately clear state
76
+ if (data.sessionId === currentActiveSessionId) {
77
+ if (onRemoteSignOutRef.current) {
78
+ onRemoteSignOutRef.current();
79
+ }
80
+ else {
81
+ sonner_1.toast.info('You have been signed out remotely.');
82
+ }
83
+ // Use clearSessionState since session was already removed server-side
84
+ // Await to ensure storage cleanup completes before continuing
85
+ try {
86
+ await clearSessionStateRef.current();
87
+ }
88
+ catch (error) {
89
+ if (__DEV__) {
90
+ core_1.logger.error('Failed to clear session state after session_removed', error instanceof Error ? error : new Error(String(error)), { component: 'useSessionSocket' });
91
+ }
92
+ }
93
+ }
94
+ else {
95
+ // Otherwise, just refresh the sessions list (with error handling)
96
+ refreshSessionsRef.current().catch((error) => {
97
+ // Silently handle errors from refresh - they're expected if sessions were removed
98
+ if (__DEV__) {
99
+ core_1.logger.debug('Failed to refresh sessions after session_removed', { component: 'useSessionSocket' }, error);
100
+ }
101
+ });
102
+ }
103
+ }
104
+ else if (data.type === 'device_removed') {
105
+ // Track all removed sessions from this device
106
+ if (data.sessionIds && onSessionRemovedRef.current) {
107
+ for (const sessionId of data.sessionIds) {
108
+ onSessionRemovedRef.current(sessionId);
109
+ }
110
+ }
111
+ // If the removed deviceId matches the current device, immediately clear state
112
+ if (data.deviceId && data.deviceId === currentDeviceId) {
113
+ if (onRemoteSignOutRef.current) {
114
+ onRemoteSignOutRef.current();
115
+ }
116
+ else {
117
+ sonner_1.toast.info('This device has been removed. You have been signed out.');
118
+ }
119
+ // Use clearSessionState since sessions were already removed server-side
120
+ // Await to ensure storage cleanup completes before continuing
121
+ try {
122
+ await clearSessionStateRef.current();
123
+ }
124
+ catch (error) {
125
+ if (__DEV__) {
126
+ core_1.logger.error('Failed to clear session state after device_removed', error instanceof Error ? error : new Error(String(error)), { component: 'useSessionSocket' });
127
+ }
128
+ }
129
+ }
130
+ else {
131
+ // Otherwise, refresh sessions and device list (with error handling)
132
+ refreshSessionsRef.current().catch((error) => {
133
+ // Silently handle errors from refresh - they're expected if sessions were removed
134
+ if (__DEV__) {
135
+ core_1.logger.debug('Failed to refresh sessions after device_removed', { component: 'useSessionSocket' }, error);
136
+ }
137
+ });
138
+ }
139
+ }
140
+ else if (data.type === 'sessions_removed') {
141
+ // Track all removed sessions
142
+ if (data.sessionIds && onSessionRemovedRef.current) {
143
+ for (const sessionId of data.sessionIds) {
144
+ onSessionRemovedRef.current(sessionId);
145
+ }
146
+ }
147
+ // If the current activeSessionId is in the removed sessionIds list, immediately clear state
148
+ if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
149
+ if (onRemoteSignOutRef.current) {
150
+ onRemoteSignOutRef.current();
151
+ }
152
+ else {
153
+ sonner_1.toast.info('You have been signed out remotely.');
154
+ }
155
+ // Use clearSessionState since sessions were already removed server-side
156
+ // Await to ensure storage cleanup completes before continuing
157
+ try {
158
+ await clearSessionStateRef.current();
159
+ }
160
+ catch (error) {
161
+ if (__DEV__) {
162
+ core_1.logger.error('Failed to clear session state after sessions_removed', error instanceof Error ? error : new Error(String(error)), { component: 'useSessionSocket' });
163
+ }
164
+ }
165
+ }
166
+ else {
167
+ // Otherwise, refresh sessions list (with error handling)
168
+ refreshSessionsRef.current().catch((error) => {
169
+ // Silently handle errors from refresh - they're expected if sessions were removed
170
+ if (__DEV__) {
171
+ core_1.logger.debug('Failed to refresh sessions after sessions_removed', { component: 'useSessionSocket' }, error);
172
+ }
173
+ });
174
+ }
175
+ }
176
+ else {
177
+ // For other event types (e.g., session_created), refresh sessions (with error handling)
178
+ refreshSessionsRef.current().catch((error) => {
179
+ // Log but don't throw - refresh errors shouldn't break the socket handler
180
+ if (__DEV__) {
181
+ core_1.logger.debug('Failed to refresh sessions after session_update', { component: 'useSessionSocket' }, error);
182
+ }
183
+ });
184
+ // If the current session was logged out (legacy behavior), handle it specially
185
+ if (data.sessionId === currentActiveSessionId) {
186
+ if (onRemoteSignOutRef.current) {
187
+ onRemoteSignOutRef.current();
188
+ }
189
+ else {
190
+ sonner_1.toast.info('You have been signed out remotely.');
191
+ }
192
+ // Use clearSessionState since session was already removed server-side
193
+ // Await to ensure storage cleanup completes before continuing
194
+ try {
195
+ await clearSessionStateRef.current();
196
+ }
197
+ catch (error) {
198
+ debug.error('Failed to clear session state after session_update:', error);
199
+ }
200
+ }
201
+ }
202
+ };
203
+ socket.on('connect', handleConnect);
204
+ socket.on('session_update', handleSessionUpdate);
205
+ return () => {
206
+ socket.off('connect', handleConnect);
207
+ socket.off('session_update', handleSessionUpdate);
208
+ // Only leave on unmount if we're still in this room
209
+ if (joinedRoomRef.current === roomId) {
210
+ socket.emit('leave', { userId: roomId });
211
+ joinedRoomRef.current = null;
212
+ }
213
+ };
214
+ }, [userId, baseURL]); // Only depend on userId and baseURL - callbacks are in refs
215
+ }