@evoke-platform/context 1.2.0 → 1.3.0-dev.10

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/README.md CHANGED
@@ -17,6 +17,7 @@ available and no further installation is necessary.
17
17
 
18
18
  - [Working With Objects](#working-with-objects)
19
19
  - [REST API Calls](#rest-api-calls)
20
+ - [Authentication Context](#authentication-context)
20
21
  - [Notifications](#notifications)
21
22
 
22
23
  ### Working With Objects
@@ -222,6 +223,25 @@ absolute URL.
222
223
 
223
224
  ##### `delete(url, options)`
224
225
 
226
+ ### Authentication Context
227
+
228
+ - [useAuthenticationContext](#useauthenticationcontext)
229
+
230
+ #### `useAuthenticationContext()`
231
+
232
+ Hook to obtain the authentication context based on the current logged-in user.
233
+
234
+ The authentication context includes the following property and functions.
235
+
236
+ - `account` _[object]_
237
+ - The account of the currently logged-in user. This includes both the user's `id` and `name`.
238
+ - `logout()`
239
+ - A function that logs out the currently logged-in user. The user will be redirected to Evoke's logout page upon logout.
240
+ - `getAccessToken()`
241
+ - A function that returns an access token that is associated to the currently logged-in user. This token can be used to make API calls to Evoke's APIs to authenticate the API call.
242
+ - Note: As a general recommendation, the [ApiService](#class-apiservices) class should be used to make API calls as it will take care
243
+ of appending an access token to the call.
244
+
225
245
  ### Notifications
226
246
 
227
247
  - [useNofitication](#usenotification)
@@ -30,6 +30,8 @@ export type Widget = {
30
30
  pluginId: string;
31
31
  widgetKey: string;
32
32
  colSpan: number;
33
+ isSticky?: boolean;
34
+ noPadding?: boolean;
33
35
  properties: Record<string, unknown>;
34
36
  };
35
37
  export type NavigationLocation = 'side' | 'top' | 'none';
@@ -9,9 +9,11 @@ export type AuthenticationContext = {
9
9
  export type UserAccount = {
10
10
  id: string;
11
11
  name?: string;
12
+ lastLoginTime?: number;
13
+ activeMfaSession?: boolean;
12
14
  };
13
15
  export type AuthenticationContextProviderProps = {
14
- msal: IMsalContext;
16
+ msal?: IMsalContext;
15
17
  authRequest: AuthenticationRequest;
16
18
  children?: ReactNode;
17
19
  };
@@ -9,11 +9,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
11
  import { createContext, useCallback, useContext, useMemo } from 'react';
12
+ import { useAuth } from 'react-oidc-context';
12
13
  const Context = createContext(undefined);
13
14
  Context.displayName = 'AuthenticationContext';
14
15
  function AuthenticationContextProvider(props) {
16
+ // Auto-detect provider type based on presence of msal prop
17
+ if (props.msal) {
18
+ const { msal, authRequest, children } = props;
19
+ return (_jsx(MsalProvider, { msal: msal, authRequest: authRequest, children: children }));
20
+ }
21
+ else {
22
+ const { authRequest, children } = props;
23
+ return _jsx(OidcProvider, { authRequest: authRequest, children: children });
24
+ }
25
+ }
26
+ function MsalProvider({ msal, authRequest, children }) {
15
27
  var _a;
16
- const { msal, authRequest, children } = props;
28
+ if (!msal) {
29
+ throw new Error('MSAL instance is required for MsalProvider');
30
+ }
17
31
  const account = (_a = msal.instance.getActiveAccount()) !== null && _a !== void 0 ? _a : msal.instance.getAllAccounts()[0];
18
32
  const getAccessToken = useCallback(function () {
19
33
  return __awaiter(this, void 0, void 0, function* () {
@@ -26,19 +40,87 @@ function AuthenticationContextProvider(props) {
26
40
  return '';
27
41
  }
28
42
  });
29
- }, [msal, authRequest]);
30
- const context = useMemo(() => account
31
- ? {
32
- account: { id: account.localAccountId, name: account.name },
33
- logout: () => {
34
- msal.instance.logoutRedirect({
35
- account,
36
- postLogoutRedirectUri: `/logout?p=${encodeURIComponent(window.location.pathname + window.location.search)}`,
37
- });
38
- },
39
- getAccessToken,
40
- }
41
- : undefined, [account, msal, getAccessToken]);
43
+ }, [msal, authRequest, account]);
44
+ const context = useMemo(() => {
45
+ var _a, _b;
46
+ return account
47
+ ? {
48
+ account: {
49
+ id: account.localAccountId,
50
+ name: account.name,
51
+ lastLoginTime: (_a = account.idTokenClaims) === null || _a === void 0 ? void 0 : _a.last_login_time,
52
+ activeMfaSession: Boolean((_b = account.idTokenClaims) === null || _b === void 0 ? void 0 : _b.active_mfa_session),
53
+ },
54
+ logout: () => {
55
+ msal.instance.logoutRedirect({
56
+ account,
57
+ postLogoutRedirectUri: `/logout?p=${encodeURIComponent(window.location.pathname + window.location.search)}`,
58
+ });
59
+ },
60
+ getAccessToken,
61
+ }
62
+ : undefined;
63
+ }, [account, msal, getAccessToken, authRequest]);
64
+ return _jsx(Context.Provider, { value: context, children: children });
65
+ }
66
+ function OidcProvider({ authRequest, children }) {
67
+ var _a, _b;
68
+ // The authRequest for react-oidc is formatted slightly differently than msal.
69
+ const oidcAuthRequest = {
70
+ scope: (_b = (_a = authRequest.scopes) === null || _a === void 0 ? void 0 : _a.join(' ')) !== null && _b !== void 0 ? _b : 'openid profile email',
71
+ extraQueryParams: authRequest.extraQueryParameters,
72
+ state: authRequest.state,
73
+ };
74
+ const auth = useAuth();
75
+ const getAccessToken = useCallback(function () {
76
+ var _a, _b;
77
+ return __awaiter(this, void 0, void 0, function* () {
78
+ try {
79
+ // With automaticSilentRenew: true, oidc-client-ts will attempt to renew the token in the background before it expires.
80
+ // However, this is not guaranteed to be perfectly in sync with your API calls. Always check for expiration here and call signinSilent if needed
81
+ // to ensure you get a valid token on demand.
82
+ if (((_a = auth.user) === null || _a === void 0 ? void 0 : _a.access_token) && !auth.user.expired) {
83
+ return auth.user.access_token;
84
+ }
85
+ // Token is either missing or expired - attempt silent refresh.
86
+ const user = yield auth.signinSilent(oidcAuthRequest);
87
+ // If signinSilent returns null, it means silent login failed
88
+ if (!user) {
89
+ console.log('Silent login failed, redirecting to login');
90
+ auth.signinRedirect(oidcAuthRequest);
91
+ return '';
92
+ }
93
+ return ((_b = auth.user) === null || _b === void 0 ? void 0 : _b.access_token) || '';
94
+ }
95
+ catch (error) {
96
+ console.error('Failed to get access token:', error);
97
+ // If silent refresh throws an error (e.g., network failure, missing silent_redirect_uri,
98
+ // invalid session, refresh token expired, or provider returned an error), redirect to login
99
+ auth.signinRedirect(oidcAuthRequest);
100
+ return '';
101
+ }
102
+ });
103
+ }, [auth, authRequest]);
104
+ const context = useMemo(() => {
105
+ var _a, _b, _c;
106
+ return auth.isAuthenticated && auth.user
107
+ ? {
108
+ account: {
109
+ id: auth.user.profile.sub,
110
+ name: (_a = auth.user.profile.name) !== null && _a !== void 0 ? _a : (`${(_b = auth.user.profile.given_name) !== null && _b !== void 0 ? _b : ''} ${(_c = auth.user.profile.family_name) !== null && _c !== void 0 ? _c : ''}` ||
111
+ undefined),
112
+ lastLoginTime: auth.user.profile.lastLoginTime,
113
+ },
114
+ logout: () => {
115
+ auth.signoutRedirect({
116
+ // Fusion auth requires an absolute url.
117
+ post_logout_redirect_uri: `${window.location.origin}/logout`,
118
+ });
119
+ },
120
+ getAccessToken,
121
+ }
122
+ : undefined;
123
+ }, [auth, getAccessToken]);
42
124
  return _jsx(Context.Provider, { value: context, children: children });
43
125
  }
44
126
  export function useAuthenticationContext() {
@@ -1,5 +1,8 @@
1
1
  import { ApiServices, Callback } from '../api/index.js';
2
2
  import { Filter } from './filters.js';
3
+ export type EvokeFormDisplayConfiguration = {
4
+ submitLabel?: string;
5
+ };
3
6
  export type EvokeForm = {
4
7
  id: string;
5
8
  name: string;
@@ -8,6 +11,7 @@ export type EvokeForm = {
8
11
  formObjectId?: string;
9
12
  actionId?: string;
10
13
  autosaveActionId?: string;
14
+ display?: EvokeFormDisplayConfiguration;
11
15
  };
12
16
  export type BaseObjReference = {
13
17
  objectId: string;
@@ -90,6 +94,7 @@ export type Property = {
90
94
  name: string;
91
95
  type: PropertyType;
92
96
  enum?: string[];
97
+ strictlyTrue?: boolean;
93
98
  nonStrictEnum?: boolean;
94
99
  objectId?: string;
95
100
  relatedPropertyId?: string;
@@ -108,12 +113,14 @@ export type InputStringValidation = StringValidation & {
108
113
  maxLength?: number;
109
114
  mask?: string;
110
115
  };
116
+ export type BasicInputParameter = Omit<InputParameter, 'name' | 'required'>;
111
117
  export type InputParameter = {
112
118
  id: string;
113
119
  name?: string;
114
120
  type: PropertyType;
115
121
  required?: boolean;
116
122
  enum?: string[];
123
+ strictlyTrue?: boolean;
117
124
  nonStrictEnum?: boolean;
118
125
  validation?: PropertyValidation | InputStringValidation;
119
126
  objectId?: string;
@@ -128,6 +135,7 @@ export type Action = {
128
135
  inputProperties?: ActionInput[];
129
136
  parameters?: InputParameter[];
130
137
  form?: Form;
138
+ defaultFormId?: string;
131
139
  customCode?: string;
132
140
  preconditions?: object;
133
141
  };
@@ -223,7 +231,7 @@ export type ReadonlyField = {
223
231
  };
224
232
  export type InputField = {
225
233
  type: 'inputField';
226
- input: InputParameter;
234
+ input: BasicInputParameter;
227
235
  display?: DisplayConfiguration;
228
236
  documentMetadata?: Record<string, string>;
229
237
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evoke-platform/context",
3
- "version": "1.2.0",
3
+ "version": "1.3.0-dev.10",
4
4
  "description": "Utilities that provide context to Evoke platform widgets",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -56,6 +56,7 @@
56
56
  "msw": "^1.3.1",
57
57
  "react": "^18.2.0",
58
58
  "react-dom": "^18.3.1",
59
+ "react-oidc-context": "^2.4.0",
59
60
  "react-router-dom": "^6.16.0",
60
61
  "sinon": "^18.0.0",
61
62
  "typescript": "^5.3.3"
@@ -64,12 +65,14 @@
64
65
  "@azure/msal-browser": ">=2",
65
66
  "@azure/msal-react": ">=1",
66
67
  "react": ">=18",
68
+ "react-oidc-context": ">=2",
67
69
  "react-router-dom": ">=6"
68
70
  },
69
71
  "dependencies": {
70
72
  "@isaacs/ttlcache": "^1.4.1",
71
73
  "@microsoft/signalr": "^7.0.12",
72
74
  "axios": "^1.7.9",
75
+ "oidc-client-ts": "^3.3.0",
73
76
  "uuid": "^9.0.1"
74
77
  }
75
78
  }