strapi-plugin-magic-sessionmanager 2.0.0 → 2.0.2

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 (52) hide show
  1. package/admin/jsconfig.json +10 -0
  2. package/admin/src/components/Initializer.jsx +11 -0
  3. package/admin/src/components/LicenseGuard.jsx +591 -0
  4. package/admin/src/components/OnlineUsersWidget.jsx +208 -0
  5. package/admin/src/components/PluginIcon.jsx +8 -0
  6. package/admin/src/components/SessionDetailModal.jsx +445 -0
  7. package/admin/src/components/SessionInfoCard.jsx +151 -0
  8. package/admin/src/components/SessionInfoPanel.jsx +375 -0
  9. package/admin/src/components/index.jsx +5 -0
  10. package/admin/src/hooks/useLicense.js +103 -0
  11. package/admin/src/index.js +137 -0
  12. package/admin/src/pages/ActiveSessions.jsx +12 -0
  13. package/admin/src/pages/Analytics.jsx +735 -0
  14. package/admin/src/pages/App.jsx +12 -0
  15. package/admin/src/pages/HomePage.jsx +1248 -0
  16. package/admin/src/pages/License.jsx +603 -0
  17. package/admin/src/pages/Settings.jsx +1497 -0
  18. package/admin/src/pages/SettingsNew.jsx +1204 -0
  19. package/admin/src/pages/index.jsx +3 -0
  20. package/admin/src/pluginId.js +3 -0
  21. package/admin/src/translations/de.json +20 -0
  22. package/admin/src/translations/en.json +20 -0
  23. package/admin/src/utils/getTranslation.js +5 -0
  24. package/admin/src/utils/index.js +2 -0
  25. package/admin/src/utils/parseUserAgent.js +79 -0
  26. package/package.json +3 -1
  27. package/server/jsconfig.json +10 -0
  28. package/server/src/bootstrap.js +297 -0
  29. package/server/src/config/index.js +20 -0
  30. package/server/src/content-types/index.js +9 -0
  31. package/server/src/content-types/session/schema.json +76 -0
  32. package/server/src/controllers/controller.js +11 -0
  33. package/server/src/controllers/index.js +11 -0
  34. package/server/src/controllers/license.js +266 -0
  35. package/server/src/controllers/session.js +362 -0
  36. package/server/src/controllers/settings.js +122 -0
  37. package/server/src/destroy.js +18 -0
  38. package/server/src/index.js +21 -0
  39. package/server/src/middlewares/index.js +5 -0
  40. package/server/src/middlewares/last-seen.js +56 -0
  41. package/server/src/policies/index.js +3 -0
  42. package/server/src/register.js +32 -0
  43. package/server/src/routes/admin.js +149 -0
  44. package/server/src/routes/content-api.js +51 -0
  45. package/server/src/routes/index.js +9 -0
  46. package/server/src/services/geolocation.js +180 -0
  47. package/server/src/services/index.js +13 -0
  48. package/server/src/services/license-guard.js +308 -0
  49. package/server/src/services/notifications.js +319 -0
  50. package/server/src/services/service.js +7 -0
  51. package/server/src/services/session.js +345 -0
  52. package/server/src/utils/getClientIp.js +118 -0
@@ -0,0 +1,151 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Box, Typography, Flex, Badge } from '@strapi/design-system';
3
+ import { useFetchClient } from '@strapi/strapi/admin';
4
+
5
+ /**
6
+ * Session Info Card - shows user's session status and history
7
+ * Injected into Content Manager edit view for Users
8
+ */
9
+ const SessionInfoCard = ({ id, model }) => {
10
+ const { get } = useFetchClient();
11
+ const [sessions, setSessions] = useState([]);
12
+ const [loading, setLoading] = useState(true);
13
+ const [isUserModel, setIsUserModel] = useState(false);
14
+
15
+ useEffect(() => {
16
+ // Only show for User content type
17
+ if (model !== 'plugin::users-permissions.user') {
18
+ setIsUserModel(false);
19
+ setLoading(false);
20
+ return;
21
+ }
22
+
23
+ setIsUserModel(true);
24
+
25
+ // Fetch user's sessions
26
+ const fetchSessions = async () => {
27
+ if (!id) {
28
+ setLoading(false);
29
+ return;
30
+ }
31
+
32
+ try {
33
+ const { data } = await get(`/api/magic-sessionmanager/user/${id}/sessions`);
34
+ setSessions(data.data || []);
35
+ } catch (err) {
36
+ console.error('[SessionInfoCard] Error fetching sessions:', err);
37
+ } finally {
38
+ setLoading(false);
39
+ }
40
+ };
41
+
42
+ fetchSessions();
43
+ }, [id, model, get]);
44
+
45
+ // Don't render anything if not a User model
46
+ if (!isUserModel) {
47
+ return null;
48
+ }
49
+
50
+ if (loading) {
51
+ return (
52
+ <Box padding={4} background="neutral100" borderRadius="4px">
53
+ <Typography variant="sigma" textColor="neutral600">
54
+ Loading sessions...
55
+ </Typography>
56
+ </Box>
57
+ );
58
+ }
59
+
60
+ const activeSessions = sessions.filter(s => s.isActive);
61
+ const isOnline = activeSessions.length > 0;
62
+
63
+ return (
64
+ <Box
65
+ padding={4}
66
+ background="neutral0"
67
+ shadow="tableShadow"
68
+ borderRadius="4px"
69
+ marginBottom={4}
70
+ >
71
+ <Flex direction="column" gap={3}>
72
+ {/* Header */}
73
+ <Flex justifyContent="space-between" alignItems="center">
74
+ <Typography variant="sigma" textColor="neutral600" textTransform="uppercase">
75
+ Session Status
76
+ </Typography>
77
+ {isOnline ? (
78
+ <Badge active backgroundColor="success500">
79
+ 🟢 Online
80
+ </Badge>
81
+ ) : (
82
+ <Badge backgroundColor="neutral150">
83
+ ⚫ Offline
84
+ </Badge>
85
+ )}
86
+ </Flex>
87
+
88
+ {/* Stats */}
89
+ <Box>
90
+ <Typography variant="omega" fontWeight="bold">
91
+ Active Sessions: {activeSessions.length}
92
+ </Typography>
93
+ <Typography variant="pi" textColor="neutral600">
94
+ Total Sessions: {sessions.length}
95
+ </Typography>
96
+ </Box>
97
+
98
+ {/* Active Sessions List */}
99
+ {activeSessions.length > 0 && (
100
+ <Box>
101
+ <Typography variant="pi" fontWeight="bold" marginBottom={2}>
102
+ Current Sessions:
103
+ </Typography>
104
+ {activeSessions.slice(0, 3).map((session) => (
105
+ <Box
106
+ key={session.id}
107
+ padding={2}
108
+ background="neutral100"
109
+ borderRadius="4px"
110
+ marginBottom={2}
111
+ >
112
+ <Typography variant="pi" textColor="neutral800">
113
+ 📱 {session.ipAddress}
114
+ </Typography>
115
+ <Typography variant="pi" textColor="neutral600" fontSize="11px">
116
+ {new Date(session.loginTime).toLocaleString()}
117
+ </Typography>
118
+ </Box>
119
+ ))}
120
+ {activeSessions.length > 3 && (
121
+ <Typography variant="pi" textColor="neutral600">
122
+ + {activeSessions.length - 3} more...
123
+ </Typography>
124
+ )}
125
+ </Box>
126
+ )}
127
+
128
+ {/* Last Activity */}
129
+ {sessions.length > 0 && (
130
+ <Box>
131
+ <Typography variant="pi" textColor="neutral600">
132
+ Last Activity: {new Date(sessions[0].lastActive).toLocaleString()}
133
+ </Typography>
134
+ </Box>
135
+ )}
136
+
137
+ {/* No sessions message */}
138
+ {sessions.length === 0 && (
139
+ <Box padding={2} background="neutral100" borderRadius="4px">
140
+ <Typography variant="pi" textColor="neutral600">
141
+ No session data available
142
+ </Typography>
143
+ </Box>
144
+ )}
145
+ </Flex>
146
+ </Box>
147
+ );
148
+ };
149
+
150
+ export default SessionInfoCard;
151
+
@@ -0,0 +1,375 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Box, Typography, Flex, Button, Badge, Divider } from '@strapi/design-system';
3
+ import { Check, Cross, Monitor, Phone, Server, Clock } from '@strapi/icons';
4
+ import { useFetchClient, useNotification } from '@strapi/strapi/admin';
5
+ import parseUserAgent from '../utils/parseUserAgent';
6
+
7
+ /**
8
+ * Session Info Panel - Native Strapi design
9
+ * Clean, professional sidebar panel for Content Manager
10
+ */
11
+ const SessionInfoPanel = ({ documentId, model, document }) => {
12
+ const [sessions, setSessions] = useState([]);
13
+ const [loading, setLoading] = useState(true);
14
+ const [isBlocked, setIsBlocked] = useState(false);
15
+ const [actionLoading, setActionLoading] = useState(false);
16
+ const { get, post: postRequest } = useFetchClient();
17
+ const { toggleNotification } = useNotification();
18
+
19
+ const userId = document?.id || documentId;
20
+
21
+ useEffect(() => {
22
+ if (model !== 'plugin::users-permissions.user' || !userId) {
23
+ setLoading(false);
24
+ return;
25
+ }
26
+
27
+ const fetchData = async () => {
28
+ try {
29
+ const { data } = await get(`/api/magic-sessionmanager/user/${userId}/sessions`);
30
+ // Filter by truly active (not just isActive, but also within timeout)
31
+ const activeSessions = (data.data || []).filter(s => s.isTrulyActive);
32
+ setSessions(activeSessions);
33
+
34
+ setIsBlocked(document?.blocked || false);
35
+ } catch (err) {
36
+ console.error('[SessionInfoPanel] Error:', err);
37
+ } finally {
38
+ setLoading(false);
39
+ }
40
+ };
41
+
42
+ fetchData();
43
+ }, [userId, model, document, get]);
44
+
45
+ const handleLogoutAll = async () => {
46
+ if (!userId) return;
47
+
48
+ setActionLoading(true);
49
+ try {
50
+ const response = await postRequest(`/magic-sessionmanager/user/${userId}/terminate-all`);
51
+
52
+ if (response.data?.success) {
53
+ toggleNotification({
54
+ type: 'success',
55
+ message: 'All sessions terminated successfully',
56
+ });
57
+ setSessions([]);
58
+ }
59
+ } catch (error) {
60
+ toggleNotification({
61
+ type: 'warning',
62
+ message: 'Failed to terminate sessions',
63
+ });
64
+ console.error('[SessionInfoPanel] Logout all error:', error);
65
+ } finally {
66
+ setActionLoading(false);
67
+ }
68
+ };
69
+
70
+ const handleToggleBlock = async () => {
71
+ if (!userId) return;
72
+
73
+ setActionLoading(true);
74
+ try {
75
+ const response = await postRequest(`/magic-sessionmanager/user/${userId}/toggle-block`);
76
+
77
+ if (response.data?.success) {
78
+ const newBlockedStatus = response.data.blocked;
79
+ setIsBlocked(newBlockedStatus);
80
+
81
+ toggleNotification({
82
+ type: 'success',
83
+ message: newBlockedStatus ? 'User blocked successfully' : 'User unblocked successfully',
84
+ });
85
+
86
+ if (newBlockedStatus) {
87
+ setSessions([]);
88
+ }
89
+ }
90
+ } catch (error) {
91
+ toggleNotification({
92
+ type: 'warning',
93
+ message: 'Failed to update user status',
94
+ });
95
+ console.error('[SessionInfoPanel] Toggle block error:', error);
96
+ } finally {
97
+ setActionLoading(false);
98
+ }
99
+ };
100
+
101
+ const getDeviceIcon = (deviceType) => {
102
+ if (deviceType === 'Mobile' || deviceType === 'Tablet') return Phone;
103
+ if (deviceType === 'Desktop' || deviceType === 'Laptop') return Monitor;
104
+ return Server;
105
+ };
106
+
107
+ // ONLY show for User content type - hide completely for others
108
+ if (model !== 'plugin::users-permissions.user') {
109
+ return null;
110
+ }
111
+
112
+ if (loading) {
113
+ return {
114
+ title: 'Session Info',
115
+ content: (
116
+ <Box padding={4} background="neutral0">
117
+ <Typography variant="pi" textColor="neutral600">Loading...</Typography>
118
+ </Box>
119
+ ),
120
+ };
121
+ }
122
+
123
+ const isOnline = sessions.length > 0;
124
+
125
+ return {
126
+ title: 'Session Info',
127
+ content: (
128
+ <Box style={{ width: '100%' }}>
129
+ <Flex direction="column" gap={4} alignItems="stretch">
130
+ {/* Status Bar */}
131
+ <Box
132
+ padding={5}
133
+ background={isOnline ? 'success100' : 'neutral150'}
134
+ hasRadius
135
+ style={{
136
+ border: isOnline ? '1px solid #c6f6d5' : '1px solid #eaeaef',
137
+ transition: 'all 0.2s ease'
138
+ }}
139
+ >
140
+ <Flex direction="column" gap={3} alignItems="center">
141
+ <Badge
142
+ backgroundColor={isOnline ? 'success600' : 'neutral600'}
143
+ textColor="neutral0"
144
+ size="M"
145
+ style={{ fontSize: '14px', padding: '6px 12px' }}
146
+ >
147
+ {isOnline ? '🟢 ACTIVE' : '⚫ OFFLINE'}
148
+ </Badge>
149
+ <Typography variant="omega" fontWeight="semiBold" textColor={isOnline ? 'success700' : 'neutral700'}>
150
+ {sessions.length} active session{sessions.length !== 1 ? 's' : ''}
151
+ </Typography>
152
+ </Flex>
153
+ </Box>
154
+
155
+ {/* User Blocked Warning */}
156
+ {isBlocked && (
157
+ <Box
158
+ padding={4}
159
+ background="danger100"
160
+ hasRadius
161
+ >
162
+ <Typography variant="omega" fontWeight="semiBold" textColor="danger700" marginBottom={1}>
163
+ User is blocked
164
+ </Typography>
165
+ <Typography variant="pi" textColor="danger600">
166
+ Authentication disabled
167
+ </Typography>
168
+ </Box>
169
+ )}
170
+
171
+ {/* Active Sessions List */}
172
+ {sessions.length > 0 ? (
173
+ <Flex direction="column" gap={3} alignItems="stretch">
174
+ <Typography variant="sigma" textColor="neutral600" textTransform="uppercase" style={{
175
+ textAlign: 'left',
176
+ letterSpacing: '0.5px',
177
+ fontSize: '12px'
178
+ }}>
179
+ Active Sessions
180
+ </Typography>
181
+
182
+ {sessions.slice(0, 5).map((session) => {
183
+ const deviceInfo = parseUserAgent(session.userAgent);
184
+ const DeviceIcon = getDeviceIcon(deviceInfo.device);
185
+
186
+ return (
187
+ <Box
188
+ key={session.id}
189
+ padding={4}
190
+ background="neutral0"
191
+ hasRadius
192
+ style={{
193
+ border: '1px solid #e3e8ef',
194
+ boxShadow: '0 1px 3px rgba(0, 0, 0, 0.04)',
195
+ transition: 'all 0.2s ease'
196
+ }}
197
+ onMouseEnter={(e) => {
198
+ e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.08)';
199
+ e.currentTarget.style.borderColor = '#4945FF';
200
+ }}
201
+ onMouseLeave={(e) => {
202
+ e.currentTarget.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.04)';
203
+ e.currentTarget.style.borderColor = '#e3e8ef';
204
+ }}
205
+ >
206
+ <Flex direction="column" gap={2} alignItems="flex-start">
207
+ {/* Device Name with Icon */}
208
+ <Flex gap={2} alignItems="center">
209
+ <DeviceIcon width="20px" height="20px" />
210
+ <Typography variant="omega" fontWeight="bold" textColor="neutral800">
211
+ {deviceInfo.device}
212
+ </Typography>
213
+ </Flex>
214
+
215
+ {/* Status Badge */}
216
+ <Badge
217
+ backgroundColor="success600"
218
+ textColor="neutral0"
219
+ size="S"
220
+ >
221
+ Active
222
+ </Badge>
223
+
224
+ {/* Browser & OS */}
225
+ <Typography variant="pi" textColor="neutral600">
226
+ {deviceInfo.browser} on {deviceInfo.os}
227
+ </Typography>
228
+
229
+ <Divider />
230
+
231
+ {/* IP Address */}
232
+ <Flex gap={2} alignItems="center">
233
+ <Server width="14px" height="14px" />
234
+ <Typography variant="pi" textColor="neutral600">
235
+ {session.ipAddress}
236
+ </Typography>
237
+ </Flex>
238
+
239
+ {/* Login Time */}
240
+ <Flex gap={2} alignItems="center">
241
+ <Clock width="14px" height="14px" />
242
+ <Typography variant="pi" textColor="neutral600">
243
+ {new Date(session.loginTime).toLocaleString('en-US', {
244
+ month: 'short',
245
+ day: 'numeric',
246
+ hour: '2-digit',
247
+ minute: '2-digit'
248
+ })}
249
+ </Typography>
250
+ </Flex>
251
+
252
+ {/* Show minutes since last activity */}
253
+ {session.minutesSinceActive !== undefined && session.minutesSinceActive < 60 && (
254
+ <Typography variant="pi" textColor="success600" fontWeight="semiBold">
255
+ Active {session.minutesSinceActive === 0 ? 'now' : `${session.minutesSinceActive} min ago`}
256
+ </Typography>
257
+ )}
258
+ </Flex>
259
+ </Box>
260
+ );
261
+ })}
262
+
263
+ {sessions.length > 5 && (
264
+ <Box padding={3} background="primary100" hasRadius textAlign="center">
265
+ <Typography variant="pi" textColor="primary600" fontWeight="semiBold">
266
+ +{sessions.length - 5} more session{sessions.length - 5 !== 1 ? 's' : ''}
267
+ </Typography>
268
+ </Box>
269
+ )}
270
+ </Flex>
271
+ ) : (
272
+ <Box
273
+ padding={6}
274
+ background="neutral100"
275
+ hasRadius
276
+ style={{
277
+ border: '1px dashed #dcdce4',
278
+ textAlign: 'center'
279
+ }}
280
+ >
281
+ <Flex direction="column" alignItems="center" gap={2}>
282
+ <Typography
283
+ variant="pi"
284
+ textColor="neutral600"
285
+ style={{ fontSize: '32px', marginBottom: '8px' }}
286
+ >
287
+ 💤
288
+ </Typography>
289
+ <Typography variant="omega" fontWeight="semiBold" textColor="neutral700">
290
+ No active sessions
291
+ </Typography>
292
+ <Typography variant="pi" textColor="neutral500" style={{ fontSize: '13px' }}>
293
+ User has not logged in yet
294
+ </Typography>
295
+ </Flex>
296
+ </Box>
297
+ )}
298
+
299
+ {/* Action Buttons - Always at the bottom */}
300
+ <Divider />
301
+
302
+ <Flex direction="column" gap={3} alignItems="stretch">
303
+ <Typography variant="sigma" textColor="neutral600" textTransform="uppercase" style={{
304
+ textAlign: 'left',
305
+ letterSpacing: '0.5px',
306
+ fontSize: '12px'
307
+ }}>
308
+ Actions
309
+ </Typography>
310
+
311
+ <Button
312
+ variant="secondary"
313
+ size="M"
314
+ fullWidth
315
+ onClick={handleLogoutAll}
316
+ disabled={actionLoading || sessions.length === 0}
317
+ startIcon={<Cross />}
318
+ style={{
319
+ border: '1px solid #dc2626',
320
+ color: '#dc2626',
321
+ backgroundColor: 'transparent',
322
+ transition: 'all 0.2s ease',
323
+ }}
324
+ onMouseEnter={(e) => {
325
+ if (!actionLoading && sessions.length > 0) {
326
+ e.currentTarget.style.backgroundColor = '#dc2626';
327
+ e.currentTarget.style.color = 'white';
328
+ }
329
+ }}
330
+ onMouseLeave={(e) => {
331
+ if (!actionLoading && sessions.length > 0) {
332
+ e.currentTarget.style.backgroundColor = 'transparent';
333
+ e.currentTarget.style.color = '#dc2626';
334
+ }
335
+ }}
336
+ >
337
+ Terminate All Sessions
338
+ </Button>
339
+
340
+ <Button
341
+ variant="secondary"
342
+ size="M"
343
+ fullWidth
344
+ onClick={handleToggleBlock}
345
+ disabled={actionLoading}
346
+ startIcon={isBlocked ? <Check /> : <Cross />}
347
+ style={{
348
+ border: isBlocked ? '1px solid #16a34a' : '1px solid #dc2626',
349
+ color: isBlocked ? '#16a34a' : '#dc2626',
350
+ backgroundColor: 'transparent',
351
+ transition: 'all 0.2s ease',
352
+ }}
353
+ onMouseEnter={(e) => {
354
+ if (!actionLoading) {
355
+ e.currentTarget.style.backgroundColor = isBlocked ? '#16a34a' : '#dc2626';
356
+ e.currentTarget.style.color = 'white';
357
+ }
358
+ }}
359
+ onMouseLeave={(e) => {
360
+ if (!actionLoading) {
361
+ e.currentTarget.style.backgroundColor = 'transparent';
362
+ e.currentTarget.style.color = isBlocked ? '#16a34a' : '#dc2626';
363
+ }
364
+ }}
365
+ >
366
+ {isBlocked ? 'Unblock User' : 'Block User'}
367
+ </Button>
368
+ </Flex>
369
+ </Flex>
370
+ </Box>
371
+ ),
372
+ };
373
+ };
374
+
375
+ export default SessionInfoPanel;
@@ -0,0 +1,5 @@
1
+ export { default as Initializer } from './Initializer';
2
+ export { default as PluginIcon } from './PluginIcon';
3
+ export { default as SessionInfoCard } from './SessionInfoCard';
4
+ export { default as SessionInfoPanel } from './SessionInfoPanel';
5
+ export { default as OnlineUsersWidget } from './OnlineUsersWidget';
@@ -0,0 +1,103 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { useFetchClient } from '@strapi/strapi/admin';
3
+ import pluginId from '../pluginId';
4
+
5
+ /**
6
+ * Hook to check license status
7
+ * Returns: { isPremium, loading, error }
8
+ */
9
+ export const useLicense = () => {
10
+ const { get } = useFetchClient();
11
+ const [isPremium, setIsPremium] = useState(false);
12
+ const [isAdvanced, setIsAdvanced] = useState(false);
13
+ const [isEnterprise, setIsEnterprise] = useState(false);
14
+ const [loading, setLoading] = useState(true);
15
+ const [error, setError] = useState(null);
16
+ const [licenseData, setLicenseData] = useState(null);
17
+
18
+ useEffect(() => {
19
+ checkLicense();
20
+
21
+ // Auto-refresh every 1 hour to detect license changes (silent background check)
22
+ const interval = setInterval(() => {
23
+ checkLicense(true); // Silent refresh - user merkt nichts
24
+ }, 60 * 60 * 1000); // 1 hour
25
+
26
+ return () => clearInterval(interval);
27
+ }, []);
28
+
29
+ const checkLicense = async (silent = false) => {
30
+ if (!silent) {
31
+ setLoading(true);
32
+ }
33
+
34
+ try {
35
+ const response = await get(`/${pluginId}/license/status`);
36
+
37
+ // Debug logging with plugin name
38
+ if (!silent) {
39
+ console.log('[magic-sessionmanager/useLicense] Full API Response:', response.data);
40
+ console.log('[magic-sessionmanager/useLicense] License Details:', {
41
+ valid: response.data?.valid,
42
+ demo: response.data?.demo,
43
+ licenseKey: response.data?.data?.licenseKey?.substring(0, 13) + '...',
44
+ email: response.data?.data?.email,
45
+ features: response.data?.data?.features,
46
+ rawFeaturePremium: response.data?.data?.features?.premium,
47
+ });
48
+ }
49
+
50
+ // Check if license is valid AND has premium feature enabled
51
+ const isValid = response.data?.valid || false;
52
+ const hasPremiumFeature = response.data?.data?.features?.premium || false;
53
+ const hasAdvancedFeature = response.data?.data?.features?.advanced || false;
54
+ const hasEnterpriseFeature = response.data?.data?.features?.enterprise || false;
55
+ const newIsPremium = isValid && hasPremiumFeature;
56
+ const newIsAdvanced = isValid && hasAdvancedFeature;
57
+ const newIsEnterprise = isValid && hasEnterpriseFeature;
58
+
59
+ // Log with plugin name
60
+ if ((newIsPremium !== isPremium || !silent) && !silent) {
61
+ console.log(`[magic-sessionmanager/useLicense] Premium Status: ${newIsPremium} (valid: ${isValid}, featurePremium: ${hasPremiumFeature})`);
62
+ if (!newIsPremium && isValid) {
63
+ console.warn('[magic-sessionmanager/useLicense] ⚠️ License is valid but Premium feature is not enabled!');
64
+ }
65
+ }
66
+
67
+ setIsPremium(newIsPremium);
68
+ setIsAdvanced(newIsAdvanced);
69
+ setIsEnterprise(newIsEnterprise);
70
+ setLicenseData(response.data?.data || null);
71
+ setError(null);
72
+ } catch (err) {
73
+ if (!silent) {
74
+ console.error('[useLicense] Error checking license:', err);
75
+ }
76
+ setIsPremium(false);
77
+ setIsAdvanced(false);
78
+ setIsEnterprise(false);
79
+ setLicenseData(null);
80
+ setError(err);
81
+ } finally {
82
+ if (!silent) {
83
+ setLoading(false);
84
+ }
85
+ }
86
+ };
87
+
88
+ return {
89
+ isPremium,
90
+ isAdvanced,
91
+ isEnterprise,
92
+ loading,
93
+ error,
94
+ licenseData,
95
+ features: {
96
+ premium: isPremium,
97
+ advanced: isAdvanced,
98
+ enterprise: isEnterprise,
99
+ },
100
+ refetch: checkLicense
101
+ };
102
+ };
103
+