@trackunit/react-core-hooks 1.17.54-alpha-835605b395c.0 → 1.17.55
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.
- package/index.cjs.js +185 -63
- package/index.esm.js +184 -65
- package/package.json +4 -4
- package/src/access/ExtensionRouterContext.d.ts +6 -0
- package/src/access/useValidateAccessAsync.d.ts +25 -0
- package/src/featureFlags/useHostFeatureFlag.d.ts +34 -0
- package/src/index.d.ts +4 -0
package/index.cjs.js
CHANGED
|
@@ -1,11 +1,162 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var reactCoreContextsApi = require('@trackunit/react-core-contexts-api');
|
|
4
3
|
var react = require('react');
|
|
4
|
+
var reactCoreContextsApi = require('@trackunit/react-core-contexts-api');
|
|
5
5
|
var esToolkit = require('es-toolkit');
|
|
6
6
|
var irisAppRuntimeCore = require('@trackunit/iris-app-runtime-core');
|
|
7
7
|
var zod = require('zod');
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* This is a hook to use the FeatureFlagContext.
|
|
11
|
+
*
|
|
12
|
+
* @requires FeatureFlagContext
|
|
13
|
+
* @example
|
|
14
|
+
* import { useFeatureFlags } from "@trackunit/react-core-hooks";
|
|
15
|
+
* const { flags } = useFeatureFlags();
|
|
16
|
+
* // use flags to control visibility/access ...
|
|
17
|
+
* @see (@link FeatureFlagContext)
|
|
18
|
+
*/
|
|
19
|
+
const useFeatureFlags = () => {
|
|
20
|
+
const context = react.useContext(reactCoreContextsApi.FeatureFlagContext);
|
|
21
|
+
if (!context) {
|
|
22
|
+
throw new Error("useFeatureFlags must be used within a FeatureFlagContext");
|
|
23
|
+
}
|
|
24
|
+
return context;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* This is a hook to use the UserSubscriptionContext.
|
|
29
|
+
*
|
|
30
|
+
* @requires UserSubscriptionContext
|
|
31
|
+
* @example
|
|
32
|
+
* import { useUserSubscription } from "@trackunit/react-core-hooks";
|
|
33
|
+
* const { numberOfDaysWithAccessToHistoricalData, packageType } = useUserSubscription();
|
|
34
|
+
* // use it for something
|
|
35
|
+
* const data = fetchData(numberOfDaysWithAccessToHistoricalData)
|
|
36
|
+
* @see { UserSubscription}
|
|
37
|
+
*/
|
|
38
|
+
const useUserSubscription = () => {
|
|
39
|
+
const context = react.useContext(reactCoreContextsApi.UserSubscriptionContext);
|
|
40
|
+
if (!context) {
|
|
41
|
+
throw new Error("useUserSubscription must be used within the UserSubscriptionContext");
|
|
42
|
+
}
|
|
43
|
+
return context;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Hook providing the authenticated user's profile, account, and permission data
|
|
48
|
+
* from the CurrentUserContext.
|
|
49
|
+
*
|
|
50
|
+
* ### When to use
|
|
51
|
+
* Use `useCurrentUser` when you need the user's identity or account context — displaying
|
|
52
|
+
* the user's name, resolving the effective account ID, or branching logic based on
|
|
53
|
+
* `isAssuming` or `isTrackunitUser`.
|
|
54
|
+
*
|
|
55
|
+
* ### When not to use
|
|
56
|
+
* - If you only need a boolean check for a single permission — use `useUserPermission`.
|
|
57
|
+
* - To gate entire routes — prefer server-side access control or `hasAccessTo` from
|
|
58
|
+
* `useNavigateInHost`.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* import { useCurrentUser } from "@trackunit/react-core-hooks";
|
|
62
|
+
* const { assumedUser, accountId } = useCurrentUser();
|
|
63
|
+
*
|
|
64
|
+
* const { data, loading } = useGetAccountByIdQuery({
|
|
65
|
+
* variables: { accountId: assumedUser?.accountId || accountId || "" },
|
|
66
|
+
* });
|
|
67
|
+
* @see { CurrentUserContext}
|
|
68
|
+
*/
|
|
69
|
+
const useCurrentUser = () => {
|
|
70
|
+
const context = react.useContext(reactCoreContextsApi.CurrentUserContext);
|
|
71
|
+
if (!context) {
|
|
72
|
+
throw new Error("useCurrentUser must be used within the CurrentUserProvider");
|
|
73
|
+
}
|
|
74
|
+
return context;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Async wrapper around {@link useValidateAccess}. Each helper awaits until the
|
|
79
|
+
* underlying user, subscription, and feature-flag data has finished loading
|
|
80
|
+
* before resolving, so callers such as router guards can check access without
|
|
81
|
+
* having to observe the synchronous `loading` flag themselves.
|
|
82
|
+
*/
|
|
83
|
+
const useValidateAccessAsync = () => {
|
|
84
|
+
const { hasAccessTo, hasFeatureFlag, hasPermission, loading } = useValidateAccess();
|
|
85
|
+
const hasAccessToRef = react.useRef(hasAccessTo);
|
|
86
|
+
hasAccessToRef.current = hasAccessTo;
|
|
87
|
+
const hasFeatureFlagRef = react.useRef(hasFeatureFlag);
|
|
88
|
+
hasFeatureFlagRef.current = hasFeatureFlag;
|
|
89
|
+
const hasPermissionRef = react.useRef(hasPermission);
|
|
90
|
+
hasPermissionRef.current = hasPermission;
|
|
91
|
+
const loadingRef = react.useRef(loading);
|
|
92
|
+
loadingRef.current = loading;
|
|
93
|
+
return react.useMemo(() => {
|
|
94
|
+
const waitForLoading = async () => {
|
|
95
|
+
if (loadingRef.current) {
|
|
96
|
+
await new Promise(resolve => {
|
|
97
|
+
const intervalId = setInterval(() => {
|
|
98
|
+
if (!loadingRef.current) {
|
|
99
|
+
clearInterval(intervalId);
|
|
100
|
+
resolve();
|
|
101
|
+
}
|
|
102
|
+
}, 10);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
hasAccessTo: async (requiredAccessTo) => {
|
|
108
|
+
await waitForLoading();
|
|
109
|
+
return hasAccessToRef.current(requiredAccessTo);
|
|
110
|
+
},
|
|
111
|
+
hasFeatureFlag: async (flag) => {
|
|
112
|
+
await waitForLoading();
|
|
113
|
+
return hasFeatureFlagRef.current(flag);
|
|
114
|
+
},
|
|
115
|
+
hasPermission: async (permission) => {
|
|
116
|
+
await waitForLoading();
|
|
117
|
+
return hasPermissionRef.current(permission);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}, [loadingRef, hasAccessToRef, hasFeatureFlagRef, hasPermissionRef]);
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Synchronously evaluates the current user's access via subscription features,
|
|
124
|
+
* feature flags, and permissions. Returns predicate helpers plus a `loading`
|
|
125
|
+
* flag that stays true until user, subscription, and flag data are all present.
|
|
126
|
+
*/
|
|
127
|
+
const useValidateAccess = () => {
|
|
128
|
+
const { isAuthenticated, permissions: userPermissions } = useCurrentUser();
|
|
129
|
+
const { features, loading: featuresLoading } = useUserSubscription();
|
|
130
|
+
const { flags: allFlags } = useFeatureFlags();
|
|
131
|
+
const loading = react.useMemo(() => allFlags === null || featuresLoading || !userPermissions, [allFlags, featuresLoading, userPermissions]);
|
|
132
|
+
const hasFeatureFlag = react.useCallback((flag) => {
|
|
133
|
+
return allFlags?.find(({ key }) => key === flag)?.state ?? null;
|
|
134
|
+
}, [allFlags]);
|
|
135
|
+
const hasAccessTo = react.useCallback((requiredAccessTo) => {
|
|
136
|
+
if (isAuthenticated) {
|
|
137
|
+
return hasAccessToFeature(features, requiredAccessTo);
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}, [features, isAuthenticated]);
|
|
141
|
+
const hasPermission = react.useCallback((permission) => {
|
|
142
|
+
if (isAuthenticated) {
|
|
143
|
+
return userPermissions?.includes(permission) ?? false;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}, [isAuthenticated, userPermissions]);
|
|
147
|
+
return react.useMemo(() => {
|
|
148
|
+
return {
|
|
149
|
+
hasAccessTo,
|
|
150
|
+
hasFeatureFlag,
|
|
151
|
+
hasPermission,
|
|
152
|
+
loading,
|
|
153
|
+
};
|
|
154
|
+
}, [hasAccessTo, hasFeatureFlag, hasPermission, loading]);
|
|
155
|
+
};
|
|
156
|
+
const hasAccessToFeature = (myFeatures, feature) => {
|
|
157
|
+
return myFeatures?.some(x => x.id === feature) ?? null;
|
|
158
|
+
};
|
|
159
|
+
|
|
9
160
|
/**
|
|
10
161
|
* Hook to get the analytics context.
|
|
11
162
|
*
|
|
@@ -219,21 +370,39 @@ const useExportDataContext = () => {
|
|
|
219
370
|
};
|
|
220
371
|
|
|
221
372
|
/**
|
|
222
|
-
*
|
|
373
|
+
* Reads a host feature flag by key.
|
|
374
|
+
*
|
|
375
|
+
* Return values:
|
|
376
|
+
* - `null` while the host feature flags have not loaded yet.
|
|
377
|
+
* - the flag's boolean `state` when the flag exists in the loaded set.
|
|
378
|
+
* - `null` when the flags are loaded but the requested flag is not present,
|
|
379
|
+
* unless `options.defaultValueWhenMissing` is provided, in which case that
|
|
380
|
+
* value is returned.
|
|
381
|
+
*
|
|
382
|
+
* The hook is generic so callers can narrow the accepted key. Internal callers
|
|
383
|
+
* pass the strongly-typed `Flag` union for type safety, e.g.
|
|
384
|
+
* `useHostFeatureFlag<Flag>(Flags.SOME_FLAG, { defaultValueWhenMissing: true })`.
|
|
385
|
+
* External callers may pass any string.
|
|
386
|
+
*
|
|
387
|
+
* Must be used within a `FeatureFlagContext` (provided by `TrackunitProviders`).
|
|
223
388
|
*
|
|
224
|
-
* @requires FeatureFlagContext
|
|
225
389
|
* @example
|
|
226
|
-
* import {
|
|
227
|
-
* const {
|
|
228
|
-
*
|
|
229
|
-
* @see (@link FeatureFlagContext)
|
|
390
|
+
* import { useHostFeatureFlag } from "@trackunit/react-core-hooks";
|
|
391
|
+
* const isEnabled = useHostFeatureFlag("my_flag", { defaultValueWhenMissing: true });
|
|
392
|
+
* @see useFeatureFlags
|
|
230
393
|
*/
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
394
|
+
const useHostFeatureFlag = (flag, options) => {
|
|
395
|
+
const { flags } = useFeatureFlags();
|
|
396
|
+
return react.useMemo(() => {
|
|
397
|
+
const found = flags?.find(({ key }) => key === flag);
|
|
398
|
+
if (found) {
|
|
399
|
+
return found.state;
|
|
400
|
+
}
|
|
401
|
+
if (flags && options.defaultValueWhenMissing !== undefined) {
|
|
402
|
+
return options.defaultValueWhenMissing;
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}, [flags, flag, options.defaultValueWhenMissing]);
|
|
237
406
|
};
|
|
238
407
|
|
|
239
408
|
/**
|
|
@@ -896,25 +1065,6 @@ const useSiteRuntime = () => {
|
|
|
896
1065
|
return react.useMemo(() => ({ siteInfo, loading, error }), [siteInfo, loading, error]);
|
|
897
1066
|
};
|
|
898
1067
|
|
|
899
|
-
/**
|
|
900
|
-
* This is a hook to use the UserSubscriptionContext.
|
|
901
|
-
*
|
|
902
|
-
* @requires UserSubscriptionContext
|
|
903
|
-
* @example
|
|
904
|
-
* import { useUserSubscription } from "@trackunit/react-core-hooks";
|
|
905
|
-
* const { numberOfDaysWithAccessToHistoricalData, packageType } = useUserSubscription();
|
|
906
|
-
* // use it for something
|
|
907
|
-
* const data = fetchData(numberOfDaysWithAccessToHistoricalData)
|
|
908
|
-
* @see { UserSubscription}
|
|
909
|
-
*/
|
|
910
|
-
const useUserSubscription = () => {
|
|
911
|
-
const context = react.useContext(reactCoreContextsApi.UserSubscriptionContext);
|
|
912
|
-
if (!context) {
|
|
913
|
-
throw new Error("useUserSubscription must be used within the UserSubscriptionContext");
|
|
914
|
-
}
|
|
915
|
-
return context;
|
|
916
|
-
};
|
|
917
|
-
|
|
918
1068
|
/**
|
|
919
1069
|
* This is a hook to use the TimeRangeProvider.
|
|
920
1070
|
*
|
|
@@ -960,37 +1110,6 @@ const useToast = () => {
|
|
|
960
1110
|
return toastContext;
|
|
961
1111
|
};
|
|
962
1112
|
|
|
963
|
-
/**
|
|
964
|
-
* Hook providing the authenticated user's profile, account, and permission data
|
|
965
|
-
* from the CurrentUserContext.
|
|
966
|
-
*
|
|
967
|
-
* ### When to use
|
|
968
|
-
* Use `useCurrentUser` when you need the user's identity or account context — displaying
|
|
969
|
-
* the user's name, resolving the effective account ID, or branching logic based on
|
|
970
|
-
* `isAssuming` or `isTrackunitUser`.
|
|
971
|
-
*
|
|
972
|
-
* ### When not to use
|
|
973
|
-
* - If you only need a boolean check for a single permission — use `useUserPermission`.
|
|
974
|
-
* - To gate entire routes — prefer server-side access control or `hasAccessTo` from
|
|
975
|
-
* `useNavigateInHost`.
|
|
976
|
-
*
|
|
977
|
-
* @example
|
|
978
|
-
* import { useCurrentUser } from "@trackunit/react-core-hooks";
|
|
979
|
-
* const { assumedUser, accountId } = useCurrentUser();
|
|
980
|
-
*
|
|
981
|
-
* const { data, loading } = useGetAccountByIdQuery({
|
|
982
|
-
* variables: { accountId: assumedUser?.accountId || accountId || "" },
|
|
983
|
-
* });
|
|
984
|
-
* @see { CurrentUserContext}
|
|
985
|
-
*/
|
|
986
|
-
const useCurrentUser = () => {
|
|
987
|
-
const context = react.useContext(reactCoreContextsApi.CurrentUserContext);
|
|
988
|
-
if (!context) {
|
|
989
|
-
throw new Error("useCurrentUser must be used within the CurrentUserProvider");
|
|
990
|
-
}
|
|
991
|
-
return context;
|
|
992
|
-
};
|
|
993
|
-
|
|
994
1113
|
/**
|
|
995
1114
|
* This is a hook providing the Current User language.
|
|
996
1115
|
*
|
|
@@ -1429,6 +1548,7 @@ exports.useFeatureFlags = useFeatureFlags;
|
|
|
1429
1548
|
exports.useFilterBarContext = useFilterBarContext;
|
|
1430
1549
|
exports.useGeolocation = useGeolocation;
|
|
1431
1550
|
exports.useHasAccessTo = useHasAccessTo;
|
|
1551
|
+
exports.useHostFeatureFlag = useHostFeatureFlag;
|
|
1432
1552
|
exports.useImageUploader = useImageUploader;
|
|
1433
1553
|
exports.useIrisAppId = useIrisAppId;
|
|
1434
1554
|
exports.useIrisAppImage = useIrisAppImage;
|
|
@@ -1443,5 +1563,7 @@ exports.useToast = useToast;
|
|
|
1443
1563
|
exports.useToken = useToken;
|
|
1444
1564
|
exports.useUserPermission = useUserPermission;
|
|
1445
1565
|
exports.useUserSubscription = useUserSubscription;
|
|
1566
|
+
exports.useValidateAccess = useValidateAccess;
|
|
1567
|
+
exports.useValidateAccessAsync = useValidateAccessAsync;
|
|
1446
1568
|
exports.useWidgetConfig = useWidgetConfig;
|
|
1447
1569
|
exports.useWidgetConfigAsync = useWidgetConfigAsync;
|
package/index.esm.js
CHANGED
|
@@ -1,9 +1,160 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useContext, useMemo, useCallback, useRef, useState, useEffect, useReducer } from 'react';
|
|
2
|
+
import { FeatureFlagContext, UserSubscriptionContext, CurrentUserContext, AnalyticsContext, AssetSortingContext, ConfirmationDialogContext, EnvironmentContext, ErrorHandlingContext, ExportDataContext, FilterBarContext, GeolocationContext, TokenContext, ModalDialogContext, NavigationContext, OemBrandingContext, TimeRangeContext, ToastContext, CurrentUserPreferenceContext, WidgetConfigContext } from '@trackunit/react-core-contexts-api';
|
|
3
3
|
import { isEqual } from 'es-toolkit';
|
|
4
4
|
import { AssetRuntime, CustomerRuntime, EventRuntime, ParamsRuntime, SiteRuntime, WidgetConfigRuntime } from '@trackunit/iris-app-runtime-core';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* This is a hook to use the FeatureFlagContext.
|
|
9
|
+
*
|
|
10
|
+
* @requires FeatureFlagContext
|
|
11
|
+
* @example
|
|
12
|
+
* import { useFeatureFlags } from "@trackunit/react-core-hooks";
|
|
13
|
+
* const { flags } = useFeatureFlags();
|
|
14
|
+
* // use flags to control visibility/access ...
|
|
15
|
+
* @see (@link FeatureFlagContext)
|
|
16
|
+
*/
|
|
17
|
+
const useFeatureFlags = () => {
|
|
18
|
+
const context = useContext(FeatureFlagContext);
|
|
19
|
+
if (!context) {
|
|
20
|
+
throw new Error("useFeatureFlags must be used within a FeatureFlagContext");
|
|
21
|
+
}
|
|
22
|
+
return context;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This is a hook to use the UserSubscriptionContext.
|
|
27
|
+
*
|
|
28
|
+
* @requires UserSubscriptionContext
|
|
29
|
+
* @example
|
|
30
|
+
* import { useUserSubscription } from "@trackunit/react-core-hooks";
|
|
31
|
+
* const { numberOfDaysWithAccessToHistoricalData, packageType } = useUserSubscription();
|
|
32
|
+
* // use it for something
|
|
33
|
+
* const data = fetchData(numberOfDaysWithAccessToHistoricalData)
|
|
34
|
+
* @see { UserSubscription}
|
|
35
|
+
*/
|
|
36
|
+
const useUserSubscription = () => {
|
|
37
|
+
const context = useContext(UserSubscriptionContext);
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error("useUserSubscription must be used within the UserSubscriptionContext");
|
|
40
|
+
}
|
|
41
|
+
return context;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Hook providing the authenticated user's profile, account, and permission data
|
|
46
|
+
* from the CurrentUserContext.
|
|
47
|
+
*
|
|
48
|
+
* ### When to use
|
|
49
|
+
* Use `useCurrentUser` when you need the user's identity or account context — displaying
|
|
50
|
+
* the user's name, resolving the effective account ID, or branching logic based on
|
|
51
|
+
* `isAssuming` or `isTrackunitUser`.
|
|
52
|
+
*
|
|
53
|
+
* ### When not to use
|
|
54
|
+
* - If you only need a boolean check for a single permission — use `useUserPermission`.
|
|
55
|
+
* - To gate entire routes — prefer server-side access control or `hasAccessTo` from
|
|
56
|
+
* `useNavigateInHost`.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* import { useCurrentUser } from "@trackunit/react-core-hooks";
|
|
60
|
+
* const { assumedUser, accountId } = useCurrentUser();
|
|
61
|
+
*
|
|
62
|
+
* const { data, loading } = useGetAccountByIdQuery({
|
|
63
|
+
* variables: { accountId: assumedUser?.accountId || accountId || "" },
|
|
64
|
+
* });
|
|
65
|
+
* @see { CurrentUserContext}
|
|
66
|
+
*/
|
|
67
|
+
const useCurrentUser = () => {
|
|
68
|
+
const context = useContext(CurrentUserContext);
|
|
69
|
+
if (!context) {
|
|
70
|
+
throw new Error("useCurrentUser must be used within the CurrentUserProvider");
|
|
71
|
+
}
|
|
72
|
+
return context;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Async wrapper around {@link useValidateAccess}. Each helper awaits until the
|
|
77
|
+
* underlying user, subscription, and feature-flag data has finished loading
|
|
78
|
+
* before resolving, so callers such as router guards can check access without
|
|
79
|
+
* having to observe the synchronous `loading` flag themselves.
|
|
80
|
+
*/
|
|
81
|
+
const useValidateAccessAsync = () => {
|
|
82
|
+
const { hasAccessTo, hasFeatureFlag, hasPermission, loading } = useValidateAccess();
|
|
83
|
+
const hasAccessToRef = useRef(hasAccessTo);
|
|
84
|
+
hasAccessToRef.current = hasAccessTo;
|
|
85
|
+
const hasFeatureFlagRef = useRef(hasFeatureFlag);
|
|
86
|
+
hasFeatureFlagRef.current = hasFeatureFlag;
|
|
87
|
+
const hasPermissionRef = useRef(hasPermission);
|
|
88
|
+
hasPermissionRef.current = hasPermission;
|
|
89
|
+
const loadingRef = useRef(loading);
|
|
90
|
+
loadingRef.current = loading;
|
|
91
|
+
return useMemo(() => {
|
|
92
|
+
const waitForLoading = async () => {
|
|
93
|
+
if (loadingRef.current) {
|
|
94
|
+
await new Promise(resolve => {
|
|
95
|
+
const intervalId = setInterval(() => {
|
|
96
|
+
if (!loadingRef.current) {
|
|
97
|
+
clearInterval(intervalId);
|
|
98
|
+
resolve();
|
|
99
|
+
}
|
|
100
|
+
}, 10);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
hasAccessTo: async (requiredAccessTo) => {
|
|
106
|
+
await waitForLoading();
|
|
107
|
+
return hasAccessToRef.current(requiredAccessTo);
|
|
108
|
+
},
|
|
109
|
+
hasFeatureFlag: async (flag) => {
|
|
110
|
+
await waitForLoading();
|
|
111
|
+
return hasFeatureFlagRef.current(flag);
|
|
112
|
+
},
|
|
113
|
+
hasPermission: async (permission) => {
|
|
114
|
+
await waitForLoading();
|
|
115
|
+
return hasPermissionRef.current(permission);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}, [loadingRef, hasAccessToRef, hasFeatureFlagRef, hasPermissionRef]);
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Synchronously evaluates the current user's access via subscription features,
|
|
122
|
+
* feature flags, and permissions. Returns predicate helpers plus a `loading`
|
|
123
|
+
* flag that stays true until user, subscription, and flag data are all present.
|
|
124
|
+
*/
|
|
125
|
+
const useValidateAccess = () => {
|
|
126
|
+
const { isAuthenticated, permissions: userPermissions } = useCurrentUser();
|
|
127
|
+
const { features, loading: featuresLoading } = useUserSubscription();
|
|
128
|
+
const { flags: allFlags } = useFeatureFlags();
|
|
129
|
+
const loading = useMemo(() => allFlags === null || featuresLoading || !userPermissions, [allFlags, featuresLoading, userPermissions]);
|
|
130
|
+
const hasFeatureFlag = useCallback((flag) => {
|
|
131
|
+
return allFlags?.find(({ key }) => key === flag)?.state ?? null;
|
|
132
|
+
}, [allFlags]);
|
|
133
|
+
const hasAccessTo = useCallback((requiredAccessTo) => {
|
|
134
|
+
if (isAuthenticated) {
|
|
135
|
+
return hasAccessToFeature(features, requiredAccessTo);
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}, [features, isAuthenticated]);
|
|
139
|
+
const hasPermission = useCallback((permission) => {
|
|
140
|
+
if (isAuthenticated) {
|
|
141
|
+
return userPermissions?.includes(permission) ?? false;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}, [isAuthenticated, userPermissions]);
|
|
145
|
+
return useMemo(() => {
|
|
146
|
+
return {
|
|
147
|
+
hasAccessTo,
|
|
148
|
+
hasFeatureFlag,
|
|
149
|
+
hasPermission,
|
|
150
|
+
loading,
|
|
151
|
+
};
|
|
152
|
+
}, [hasAccessTo, hasFeatureFlag, hasPermission, loading]);
|
|
153
|
+
};
|
|
154
|
+
const hasAccessToFeature = (myFeatures, feature) => {
|
|
155
|
+
return myFeatures?.some(x => x.id === feature) ?? null;
|
|
156
|
+
};
|
|
157
|
+
|
|
7
158
|
/**
|
|
8
159
|
* Hook to get the analytics context.
|
|
9
160
|
*
|
|
@@ -217,21 +368,39 @@ const useExportDataContext = () => {
|
|
|
217
368
|
};
|
|
218
369
|
|
|
219
370
|
/**
|
|
220
|
-
*
|
|
371
|
+
* Reads a host feature flag by key.
|
|
372
|
+
*
|
|
373
|
+
* Return values:
|
|
374
|
+
* - `null` while the host feature flags have not loaded yet.
|
|
375
|
+
* - the flag's boolean `state` when the flag exists in the loaded set.
|
|
376
|
+
* - `null` when the flags are loaded but the requested flag is not present,
|
|
377
|
+
* unless `options.defaultValueWhenMissing` is provided, in which case that
|
|
378
|
+
* value is returned.
|
|
379
|
+
*
|
|
380
|
+
* The hook is generic so callers can narrow the accepted key. Internal callers
|
|
381
|
+
* pass the strongly-typed `Flag` union for type safety, e.g.
|
|
382
|
+
* `useHostFeatureFlag<Flag>(Flags.SOME_FLAG, { defaultValueWhenMissing: true })`.
|
|
383
|
+
* External callers may pass any string.
|
|
384
|
+
*
|
|
385
|
+
* Must be used within a `FeatureFlagContext` (provided by `TrackunitProviders`).
|
|
221
386
|
*
|
|
222
|
-
* @requires FeatureFlagContext
|
|
223
387
|
* @example
|
|
224
|
-
* import {
|
|
225
|
-
* const {
|
|
226
|
-
*
|
|
227
|
-
* @see (@link FeatureFlagContext)
|
|
388
|
+
* import { useHostFeatureFlag } from "@trackunit/react-core-hooks";
|
|
389
|
+
* const isEnabled = useHostFeatureFlag("my_flag", { defaultValueWhenMissing: true });
|
|
390
|
+
* @see useFeatureFlags
|
|
228
391
|
*/
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
392
|
+
const useHostFeatureFlag = (flag, options) => {
|
|
393
|
+
const { flags } = useFeatureFlags();
|
|
394
|
+
return useMemo(() => {
|
|
395
|
+
const found = flags?.find(({ key }) => key === flag);
|
|
396
|
+
if (found) {
|
|
397
|
+
return found.state;
|
|
398
|
+
}
|
|
399
|
+
if (flags && options.defaultValueWhenMissing !== undefined) {
|
|
400
|
+
return options.defaultValueWhenMissing;
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
}, [flags, flag, options.defaultValueWhenMissing]);
|
|
235
404
|
};
|
|
236
405
|
|
|
237
406
|
/**
|
|
@@ -894,25 +1063,6 @@ const useSiteRuntime = () => {
|
|
|
894
1063
|
return useMemo(() => ({ siteInfo, loading, error }), [siteInfo, loading, error]);
|
|
895
1064
|
};
|
|
896
1065
|
|
|
897
|
-
/**
|
|
898
|
-
* This is a hook to use the UserSubscriptionContext.
|
|
899
|
-
*
|
|
900
|
-
* @requires UserSubscriptionContext
|
|
901
|
-
* @example
|
|
902
|
-
* import { useUserSubscription } from "@trackunit/react-core-hooks";
|
|
903
|
-
* const { numberOfDaysWithAccessToHistoricalData, packageType } = useUserSubscription();
|
|
904
|
-
* // use it for something
|
|
905
|
-
* const data = fetchData(numberOfDaysWithAccessToHistoricalData)
|
|
906
|
-
* @see { UserSubscription}
|
|
907
|
-
*/
|
|
908
|
-
const useUserSubscription = () => {
|
|
909
|
-
const context = useContext(UserSubscriptionContext);
|
|
910
|
-
if (!context) {
|
|
911
|
-
throw new Error("useUserSubscription must be used within the UserSubscriptionContext");
|
|
912
|
-
}
|
|
913
|
-
return context;
|
|
914
|
-
};
|
|
915
|
-
|
|
916
1066
|
/**
|
|
917
1067
|
* This is a hook to use the TimeRangeProvider.
|
|
918
1068
|
*
|
|
@@ -958,37 +1108,6 @@ const useToast = () => {
|
|
|
958
1108
|
return toastContext;
|
|
959
1109
|
};
|
|
960
1110
|
|
|
961
|
-
/**
|
|
962
|
-
* Hook providing the authenticated user's profile, account, and permission data
|
|
963
|
-
* from the CurrentUserContext.
|
|
964
|
-
*
|
|
965
|
-
* ### When to use
|
|
966
|
-
* Use `useCurrentUser` when you need the user's identity or account context — displaying
|
|
967
|
-
* the user's name, resolving the effective account ID, or branching logic based on
|
|
968
|
-
* `isAssuming` or `isTrackunitUser`.
|
|
969
|
-
*
|
|
970
|
-
* ### When not to use
|
|
971
|
-
* - If you only need a boolean check for a single permission — use `useUserPermission`.
|
|
972
|
-
* - To gate entire routes — prefer server-side access control or `hasAccessTo` from
|
|
973
|
-
* `useNavigateInHost`.
|
|
974
|
-
*
|
|
975
|
-
* @example
|
|
976
|
-
* import { useCurrentUser } from "@trackunit/react-core-hooks";
|
|
977
|
-
* const { assumedUser, accountId } = useCurrentUser();
|
|
978
|
-
*
|
|
979
|
-
* const { data, loading } = useGetAccountByIdQuery({
|
|
980
|
-
* variables: { accountId: assumedUser?.accountId || accountId || "" },
|
|
981
|
-
* });
|
|
982
|
-
* @see { CurrentUserContext}
|
|
983
|
-
*/
|
|
984
|
-
const useCurrentUser = () => {
|
|
985
|
-
const context = useContext(CurrentUserContext);
|
|
986
|
-
if (!context) {
|
|
987
|
-
throw new Error("useCurrentUser must be used within the CurrentUserProvider");
|
|
988
|
-
}
|
|
989
|
-
return context;
|
|
990
|
-
};
|
|
991
|
-
|
|
992
1111
|
/**
|
|
993
1112
|
* This is a hook providing the Current User language.
|
|
994
1113
|
*
|
|
@@ -1404,4 +1523,4 @@ const useWidgetConfig = () => {
|
|
|
1404
1523
|
return result;
|
|
1405
1524
|
};
|
|
1406
1525
|
|
|
1407
|
-
export { fetchAssetBlobUrl, useAnalytics, useAssetRuntime, useAssetSorting, useConfirmationDialog, useCurrentUser, useCurrentUserFavoriteAdvancedSensors, useCurrentUserFavoriteInsights, useCurrentUserLanguage, useCurrentUserSystemOfMeasurement, useCurrentUserTimeFormat, useCurrentUserTimeZonePreference, useCustomerRuntime, useEnvironment, useErrorHandler, useErrorHandlerOrNull, useEventRuntime, useExportDataContext, useFeatureBranchQueryString, useFeatureFlags, useFilterBarContext, useGeolocation, useHasAccessTo, useImageUploader, useIrisAppId, useIrisAppImage, useIrisAppName, useModalDialogContext, useNavigateInHost, useOemBrandingContext, useSettingsUtils, useSiteRuntime, useTimeRange, useToast, useToken, useUserPermission, useUserSubscription, useWidgetConfig, useWidgetConfigAsync };
|
|
1526
|
+
export { fetchAssetBlobUrl, useAnalytics, useAssetRuntime, useAssetSorting, useConfirmationDialog, useCurrentUser, useCurrentUserFavoriteAdvancedSensors, useCurrentUserFavoriteInsights, useCurrentUserLanguage, useCurrentUserSystemOfMeasurement, useCurrentUserTimeFormat, useCurrentUserTimeZonePreference, useCustomerRuntime, useEnvironment, useErrorHandler, useErrorHandlerOrNull, useEventRuntime, useExportDataContext, useFeatureBranchQueryString, useFeatureFlags, useFilterBarContext, useGeolocation, useHasAccessTo, useHostFeatureFlag, useImageUploader, useIrisAppId, useIrisAppImage, useIrisAppName, useModalDialogContext, useNavigateInHost, useOemBrandingContext, useSettingsUtils, useSiteRuntime, useTimeRange, useToast, useToken, useUserPermission, useUserSubscription, useValidateAccess, useValidateAccessAsync, useWidgetConfig, useWidgetConfigAsync };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-core-hooks",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.55",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"es-toolkit": "^1.39.10",
|
|
11
|
-
"@trackunit/iris-app-runtime-core": "1.17.50
|
|
12
|
-
"@trackunit/iris-app-runtime-core-api": "1.16.49
|
|
13
|
-
"@trackunit/react-core-contexts-api": "1.17.49
|
|
11
|
+
"@trackunit/iris-app-runtime-core": "1.17.50",
|
|
12
|
+
"@trackunit/iris-app-runtime-core-api": "1.16.49",
|
|
13
|
+
"@trackunit/react-core-contexts-api": "1.17.49",
|
|
14
14
|
"zod": "^3.25.76"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { HasAccess, ValidateAccess } from "./useValidateAccessAsync";
|
|
2
|
+
export interface ExtensionRouterContext {
|
|
3
|
+
hasAccessToRoute: (requiredAccessTo: ValidateAccess) => Promise<HasAccess>;
|
|
4
|
+
hasFeatureFlag: (flag: string) => Promise<HasAccess>;
|
|
5
|
+
hasPermission: (permission: string) => Promise<HasAccess>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LegacyFeature } from "@trackunit/iris-app-runtime-core-api";
|
|
2
|
+
export type HasAccess = boolean | null;
|
|
3
|
+
export type ValidateAccess = LegacyFeature;
|
|
4
|
+
/**
|
|
5
|
+
* Async wrapper around {@link useValidateAccess}. Each helper awaits until the
|
|
6
|
+
* underlying user, subscription, and feature-flag data has finished loading
|
|
7
|
+
* before resolving, so callers such as router guards can check access without
|
|
8
|
+
* having to observe the synchronous `loading` flag themselves.
|
|
9
|
+
*/
|
|
10
|
+
export declare const useValidateAccessAsync: () => {
|
|
11
|
+
hasAccessTo: (requiredAccessTo: ValidateAccess) => Promise<HasAccess>;
|
|
12
|
+
hasFeatureFlag: (flag: string) => Promise<HasAccess>;
|
|
13
|
+
hasPermission: (permission: string) => Promise<HasAccess>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Synchronously evaluates the current user's access via subscription features,
|
|
17
|
+
* feature flags, and permissions. Returns predicate helpers plus a `loading`
|
|
18
|
+
* flag that stays true until user, subscription, and flag data are all present.
|
|
19
|
+
*/
|
|
20
|
+
export declare const useValidateAccess: () => {
|
|
21
|
+
hasAccessTo: (requiredAccessTo: ValidateAccess) => HasAccess;
|
|
22
|
+
hasFeatureFlag: (flag: string) => HasAccess;
|
|
23
|
+
hasPermission: (permission: string) => HasAccess;
|
|
24
|
+
loading: boolean;
|
|
25
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface UseHostFeatureFlagOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Value returned when the feature flags have loaded but the requested flag is
|
|
4
|
+
* not present in the loaded set. When omitted, a missing flag resolves to
|
|
5
|
+
* `null` (the historical behaviour).
|
|
6
|
+
*
|
|
7
|
+
* Note: this only applies once flags are loaded. While flags are still
|
|
8
|
+
* loading the hook always returns `null` regardless of this option.
|
|
9
|
+
*/
|
|
10
|
+
defaultValueWhenMissing?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reads a host feature flag by key.
|
|
14
|
+
*
|
|
15
|
+
* Return values:
|
|
16
|
+
* - `null` while the host feature flags have not loaded yet.
|
|
17
|
+
* - the flag's boolean `state` when the flag exists in the loaded set.
|
|
18
|
+
* - `null` when the flags are loaded but the requested flag is not present,
|
|
19
|
+
* unless `options.defaultValueWhenMissing` is provided, in which case that
|
|
20
|
+
* value is returned.
|
|
21
|
+
*
|
|
22
|
+
* The hook is generic so callers can narrow the accepted key. Internal callers
|
|
23
|
+
* pass the strongly-typed `Flag` union for type safety, e.g.
|
|
24
|
+
* `useHostFeatureFlag<Flag>(Flags.SOME_FLAG, { defaultValueWhenMissing: true })`.
|
|
25
|
+
* External callers may pass any string.
|
|
26
|
+
*
|
|
27
|
+
* Must be used within a `FeatureFlagContext` (provided by `TrackunitProviders`).
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* import { useHostFeatureFlag } from "@trackunit/react-core-hooks";
|
|
31
|
+
* const isEnabled = useHostFeatureFlag("my_flag", { defaultValueWhenMissing: true });
|
|
32
|
+
* @see useFeatureFlags
|
|
33
|
+
*/
|
|
34
|
+
export declare const useHostFeatureFlag: <TFlag extends string = string>(flag: TFlag, options: UseHostFeatureFlagOptions) => boolean | null;
|
package/src/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export * from "./access/ExtensionRouterContext";
|
|
2
|
+
export { useValidateAccess, useValidateAccessAsync } from "./access/useValidateAccessAsync";
|
|
3
|
+
export type { ValidateAccess } from "./access/useValidateAccessAsync";
|
|
1
4
|
export * from "./analytics/useAnalytics";
|
|
2
5
|
export * from "./assetSorting/useAssetSorting";
|
|
3
6
|
export * from "./confirmationDialog/useConfirmationDialog";
|
|
@@ -5,6 +8,7 @@ export * from "./environment/useEnvironment";
|
|
|
5
8
|
export * from "./errorHandling/useErrorHandler";
|
|
6
9
|
export * from "./exportData/useExportDataContext";
|
|
7
10
|
export * from "./featureFlags/useFeatureFlags";
|
|
11
|
+
export * from "./featureFlags/useHostFeatureFlag";
|
|
8
12
|
export * from "./fetchAssetBlobUrl";
|
|
9
13
|
export * from "./filterBar/useFilterBarContext";
|
|
10
14
|
export * from "./geolocation/useGeolocation";
|