@powerportalspro/react 6.0.0 → 7.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.
- package/dist/index.cjs +43 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.js +44 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ChangeEvent, ReactNode } from 'react';
|
|
3
|
-
import { components, TableMetadata as TableMetadata$1, ViewMetadata, CurrentUserInfo, Transport, AuthClient, PowerPortalsProClient, LoginRequest, LoginResponse, SwitchIdentityResponse, PowerPortalsProError, TableRecord, RetrieveRecordsResponse, GridDataRequest, GridDataResponse, GridFilterBase, ChartAggregation, ColumnSort, ResolvedColumn, TableSecurityPermission, ChartData, OrganizationRequest } from '@powerportalspro/core';
|
|
3
|
+
import { components, TableMetadata as TableMetadata$1, ViewMetadata, CurrentUserInfo, Transport, AuthClient, PowerPortalsProClient, LoginRequest, LoginResponse, SwitchIdentityResponse, ImpersonateResponse, StopImpersonationResponse, ImpersonationSearchResponse, PowerPortalsProError, TableRecord, RetrieveRecordsResponse, GridDataRequest, GridDataResponse, GridFilterBase, ChartAggregation, ColumnSort, ResolvedColumn, TableSecurityPermission, ChartData, OrganizationRequest } from '@powerportalspro/core';
|
|
4
4
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -343,6 +343,29 @@ interface PowerPortalsProContextValue {
|
|
|
343
343
|
* alt id is read from the cookie's claims server-side.
|
|
344
344
|
*/
|
|
345
345
|
readonly switchIdentity: () => Promise<SwitchIdentityResponse>;
|
|
346
|
+
/**
|
|
347
|
+
* Starts impersonating a portal contact, swapping the auth cookie for theirs. Permitted
|
|
348
|
+
* server-side only for internal users holding the `SystemAdmin` role, and only ever resolves a
|
|
349
|
+
* contact — so it can never be used to assume another staff identity.
|
|
350
|
+
*
|
|
351
|
+
* On `ImpersonateResult.Started` the auth state is refreshed from `/api/auth/me` and now
|
|
352
|
+
* describes the CONTACT: `user.userId`, `user.roles` and `user.portalUserType` are theirs, with
|
|
353
|
+
* `user.isImpersonating` and `user.impersonatorName` the only trace of who is behind it. Other
|
|
354
|
+
* outcomes leave the state untouched so the caller can surface a message.
|
|
355
|
+
*/
|
|
356
|
+
readonly impersonate: (targetUserId: string) => Promise<ImpersonateResponse>;
|
|
357
|
+
/**
|
|
358
|
+
* Ends an impersonation session and restores the internal user. Takes no parameters — the
|
|
359
|
+
* identity to return to is read from the session's claims server-side. On
|
|
360
|
+
* `StopImpersonationResult.Stopped` the auth state is refreshed.
|
|
361
|
+
*/
|
|
362
|
+
readonly stopImpersonation: () => Promise<StopImpersonationResponse>;
|
|
363
|
+
/**
|
|
364
|
+
* Searches contacts the current user may impersonate. Returns an empty result — never an
|
|
365
|
+
* error — when the caller isn't permitted to impersonate or the query is under three
|
|
366
|
+
* characters, so a picker can render "no matches" without branching on why.
|
|
367
|
+
*/
|
|
368
|
+
readonly searchImpersonationTargets: (query: string) => Promise<ImpersonationSearchResponse>;
|
|
346
369
|
}
|
|
347
370
|
declare const PowerPortalsProContext: react.Context<PowerPortalsProContextValue | null>;
|
|
348
371
|
|
|
@@ -482,6 +505,27 @@ type UseAuthResult = AuthState & {
|
|
|
482
505
|
* On `Switched`, the in-memory auth state is refreshed automatically.
|
|
483
506
|
*/
|
|
484
507
|
switchIdentity: () => Promise<SwitchIdentityResponse>;
|
|
508
|
+
/**
|
|
509
|
+
* Starts impersonating a portal contact. Allowed server-side only for internal users holding
|
|
510
|
+
* the `SystemAdmin` role, and only ever resolves a contact — never another staff account.
|
|
511
|
+
*
|
|
512
|
+
* On `ImpersonateResult.Started` the auth state refreshes and `auth.user` becomes the CONTACT:
|
|
513
|
+
* their id, their roles, their user type. `auth.user.isImpersonating` and
|
|
514
|
+
* `auth.user.impersonatorName` are the only fields that say otherwise, which is what makes the
|
|
515
|
+
* impersonated session an honest rehearsal of what that contact actually sees.
|
|
516
|
+
*/
|
|
517
|
+
impersonate: (targetUserId: string) => Promise<ImpersonateResponse>;
|
|
518
|
+
/**
|
|
519
|
+
* Ends an impersonation session and restores the internal user. Takes no parameters — the
|
|
520
|
+
* identity to return to comes from the session's own claims, so the client can't pick it.
|
|
521
|
+
*/
|
|
522
|
+
stopImpersonation: () => Promise<StopImpersonationResponse>;
|
|
523
|
+
/**
|
|
524
|
+
* Searches contacts the current user may impersonate — by name, email, or exact user id.
|
|
525
|
+
* Returns an empty result rather than an error when the caller isn't permitted or the query is
|
|
526
|
+
* too short, so a picker renders "no matches" without branching on why.
|
|
527
|
+
*/
|
|
528
|
+
searchImpersonationTargets: (query: string) => Promise<ImpersonationSearchResponse>;
|
|
485
529
|
};
|
|
486
530
|
declare function useAuth(): UseAuthResult;
|
|
487
531
|
|
|
@@ -800,6 +844,16 @@ interface ViewDataSource {
|
|
|
800
844
|
readonly status: QueryStatus;
|
|
801
845
|
readonly rows: readonly TableRecord[];
|
|
802
846
|
readonly totalCount: number | string | null | undefined;
|
|
847
|
+
/**
|
|
848
|
+
* Dataverse's `MoreRecords` flag from the most recent response — the
|
|
849
|
+
* authoritative "is there another page?" signal for sequential paging.
|
|
850
|
+
* Prefer it over comparing rows against {@link totalCount}: the count is
|
|
851
|
+
* computed before the server's per-row permission handlers drop rows,
|
|
852
|
+
* so the rows a consumer accumulates can legitimately fall short of it.
|
|
853
|
+
* `undefined` until the first response lands; `null` when the server
|
|
854
|
+
* didn't report one.
|
|
855
|
+
*/
|
|
856
|
+
readonly moreRecords: boolean | null | undefined;
|
|
803
857
|
readonly columns: readonly ResolvedColumn[];
|
|
804
858
|
/**
|
|
805
859
|
* Current user's combined table-level `TableSecurityPermission` mask for
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ChangeEvent, ReactNode } from 'react';
|
|
3
|
-
import { components, TableMetadata as TableMetadata$1, ViewMetadata, CurrentUserInfo, Transport, AuthClient, PowerPortalsProClient, LoginRequest, LoginResponse, SwitchIdentityResponse, PowerPortalsProError, TableRecord, RetrieveRecordsResponse, GridDataRequest, GridDataResponse, GridFilterBase, ChartAggregation, ColumnSort, ResolvedColumn, TableSecurityPermission, ChartData, OrganizationRequest } from '@powerportalspro/core';
|
|
3
|
+
import { components, TableMetadata as TableMetadata$1, ViewMetadata, CurrentUserInfo, Transport, AuthClient, PowerPortalsProClient, LoginRequest, LoginResponse, SwitchIdentityResponse, ImpersonateResponse, StopImpersonationResponse, ImpersonationSearchResponse, PowerPortalsProError, TableRecord, RetrieveRecordsResponse, GridDataRequest, GridDataResponse, GridFilterBase, ChartAggregation, ColumnSort, ResolvedColumn, TableSecurityPermission, ChartData, OrganizationRequest } from '@powerportalspro/core';
|
|
4
4
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -343,6 +343,29 @@ interface PowerPortalsProContextValue {
|
|
|
343
343
|
* alt id is read from the cookie's claims server-side.
|
|
344
344
|
*/
|
|
345
345
|
readonly switchIdentity: () => Promise<SwitchIdentityResponse>;
|
|
346
|
+
/**
|
|
347
|
+
* Starts impersonating a portal contact, swapping the auth cookie for theirs. Permitted
|
|
348
|
+
* server-side only for internal users holding the `SystemAdmin` role, and only ever resolves a
|
|
349
|
+
* contact — so it can never be used to assume another staff identity.
|
|
350
|
+
*
|
|
351
|
+
* On `ImpersonateResult.Started` the auth state is refreshed from `/api/auth/me` and now
|
|
352
|
+
* describes the CONTACT: `user.userId`, `user.roles` and `user.portalUserType` are theirs, with
|
|
353
|
+
* `user.isImpersonating` and `user.impersonatorName` the only trace of who is behind it. Other
|
|
354
|
+
* outcomes leave the state untouched so the caller can surface a message.
|
|
355
|
+
*/
|
|
356
|
+
readonly impersonate: (targetUserId: string) => Promise<ImpersonateResponse>;
|
|
357
|
+
/**
|
|
358
|
+
* Ends an impersonation session and restores the internal user. Takes no parameters — the
|
|
359
|
+
* identity to return to is read from the session's claims server-side. On
|
|
360
|
+
* `StopImpersonationResult.Stopped` the auth state is refreshed.
|
|
361
|
+
*/
|
|
362
|
+
readonly stopImpersonation: () => Promise<StopImpersonationResponse>;
|
|
363
|
+
/**
|
|
364
|
+
* Searches contacts the current user may impersonate. Returns an empty result — never an
|
|
365
|
+
* error — when the caller isn't permitted to impersonate or the query is under three
|
|
366
|
+
* characters, so a picker can render "no matches" without branching on why.
|
|
367
|
+
*/
|
|
368
|
+
readonly searchImpersonationTargets: (query: string) => Promise<ImpersonationSearchResponse>;
|
|
346
369
|
}
|
|
347
370
|
declare const PowerPortalsProContext: react.Context<PowerPortalsProContextValue | null>;
|
|
348
371
|
|
|
@@ -482,6 +505,27 @@ type UseAuthResult = AuthState & {
|
|
|
482
505
|
* On `Switched`, the in-memory auth state is refreshed automatically.
|
|
483
506
|
*/
|
|
484
507
|
switchIdentity: () => Promise<SwitchIdentityResponse>;
|
|
508
|
+
/**
|
|
509
|
+
* Starts impersonating a portal contact. Allowed server-side only for internal users holding
|
|
510
|
+
* the `SystemAdmin` role, and only ever resolves a contact — never another staff account.
|
|
511
|
+
*
|
|
512
|
+
* On `ImpersonateResult.Started` the auth state refreshes and `auth.user` becomes the CONTACT:
|
|
513
|
+
* their id, their roles, their user type. `auth.user.isImpersonating` and
|
|
514
|
+
* `auth.user.impersonatorName` are the only fields that say otherwise, which is what makes the
|
|
515
|
+
* impersonated session an honest rehearsal of what that contact actually sees.
|
|
516
|
+
*/
|
|
517
|
+
impersonate: (targetUserId: string) => Promise<ImpersonateResponse>;
|
|
518
|
+
/**
|
|
519
|
+
* Ends an impersonation session and restores the internal user. Takes no parameters — the
|
|
520
|
+
* identity to return to comes from the session's own claims, so the client can't pick it.
|
|
521
|
+
*/
|
|
522
|
+
stopImpersonation: () => Promise<StopImpersonationResponse>;
|
|
523
|
+
/**
|
|
524
|
+
* Searches contacts the current user may impersonate — by name, email, or exact user id.
|
|
525
|
+
* Returns an empty result rather than an error when the caller isn't permitted or the query is
|
|
526
|
+
* too short, so a picker renders "no matches" without branching on why.
|
|
527
|
+
*/
|
|
528
|
+
searchImpersonationTargets: (query: string) => Promise<ImpersonationSearchResponse>;
|
|
485
529
|
};
|
|
486
530
|
declare function useAuth(): UseAuthResult;
|
|
487
531
|
|
|
@@ -800,6 +844,16 @@ interface ViewDataSource {
|
|
|
800
844
|
readonly status: QueryStatus;
|
|
801
845
|
readonly rows: readonly TableRecord[];
|
|
802
846
|
readonly totalCount: number | string | null | undefined;
|
|
847
|
+
/**
|
|
848
|
+
* Dataverse's `MoreRecords` flag from the most recent response — the
|
|
849
|
+
* authoritative "is there another page?" signal for sequential paging.
|
|
850
|
+
* Prefer it over comparing rows against {@link totalCount}: the count is
|
|
851
|
+
* computed before the server's per-row permission handlers drop rows,
|
|
852
|
+
* so the rows a consumer accumulates can legitimately fall short of it.
|
|
853
|
+
* `undefined` until the first response lands; `null` when the server
|
|
854
|
+
* didn't report one.
|
|
855
|
+
*/
|
|
856
|
+
readonly moreRecords: boolean | null | undefined;
|
|
803
857
|
readonly columns: readonly ResolvedColumn[];
|
|
804
858
|
/**
|
|
805
859
|
* Current user's combined table-level `TableSecurityPermission` mask for
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useRef, useState, useEffect, useLayoutEffect, useCallback, useContext, useMemo, useSyncExternalStore, useReducer, useId } from 'react';
|
|
2
|
-
import { Transport, AuthClient, PowerPortalsProClient, LoginResult, SwitchIdentityResult } from '@powerportalspro/core';
|
|
2
|
+
import { Transport, AuthClient, PowerPortalsProClient, LoginResult, SwitchIdentityResult, ImpersonateResult, StopImpersonationResult } from '@powerportalspro/core';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/masking/reformat.ts
|
|
@@ -1057,6 +1057,24 @@ function PowerPortalsProProvider({
|
|
|
1057
1057
|
}
|
|
1058
1058
|
return response;
|
|
1059
1059
|
}, [auth, refreshAuth]);
|
|
1060
|
+
const impersonate = useCallback(async (targetUserId) => {
|
|
1061
|
+
const response = await auth.impersonateAsync(targetUserId);
|
|
1062
|
+
if (response.result === ImpersonateResult.Started) {
|
|
1063
|
+
await refreshAuth();
|
|
1064
|
+
}
|
|
1065
|
+
return response;
|
|
1066
|
+
}, [auth, refreshAuth]);
|
|
1067
|
+
const stopImpersonation = useCallback(async () => {
|
|
1068
|
+
const response = await auth.stopImpersonationAsync();
|
|
1069
|
+
if (response.result === StopImpersonationResult.Stopped || response.result === StopImpersonationResult.ImpersonatorNotFound) {
|
|
1070
|
+
await refreshAuth();
|
|
1071
|
+
}
|
|
1072
|
+
return response;
|
|
1073
|
+
}, [auth, refreshAuth]);
|
|
1074
|
+
const searchImpersonationTargets = useCallback(
|
|
1075
|
+
(query) => auth.searchImpersonationTargetsAsync(query),
|
|
1076
|
+
[auth]
|
|
1077
|
+
);
|
|
1060
1078
|
useEffect(() => {
|
|
1061
1079
|
refreshAuth();
|
|
1062
1080
|
}, [refreshAuth]);
|
|
@@ -1073,7 +1091,10 @@ function PowerPortalsProProvider({
|
|
|
1073
1091
|
refreshAuth,
|
|
1074
1092
|
login,
|
|
1075
1093
|
logout,
|
|
1076
|
-
switchIdentity
|
|
1094
|
+
switchIdentity,
|
|
1095
|
+
impersonate,
|
|
1096
|
+
stopImpersonation,
|
|
1097
|
+
searchImpersonationTargets
|
|
1077
1098
|
}),
|
|
1078
1099
|
[
|
|
1079
1100
|
transportInstance,
|
|
@@ -1087,7 +1108,10 @@ function PowerPortalsProProvider({
|
|
|
1087
1108
|
refreshAuth,
|
|
1088
1109
|
login,
|
|
1089
1110
|
logout,
|
|
1090
|
-
switchIdentity
|
|
1111
|
+
switchIdentity,
|
|
1112
|
+
impersonate,
|
|
1113
|
+
stopImpersonation,
|
|
1114
|
+
searchImpersonationTargets
|
|
1091
1115
|
]
|
|
1092
1116
|
);
|
|
1093
1117
|
let localizationContent;
|
|
@@ -1141,13 +1165,25 @@ function CacheLocaleSync() {
|
|
|
1141
1165
|
|
|
1142
1166
|
// src/use-auth.ts
|
|
1143
1167
|
function useAuth() {
|
|
1144
|
-
const {
|
|
1168
|
+
const {
|
|
1169
|
+
authState,
|
|
1170
|
+
refreshAuth,
|
|
1171
|
+
login,
|
|
1172
|
+
logout,
|
|
1173
|
+
switchIdentity,
|
|
1174
|
+
impersonate,
|
|
1175
|
+
stopImpersonation,
|
|
1176
|
+
searchImpersonationTargets
|
|
1177
|
+
} = usePowerPortalsPro();
|
|
1145
1178
|
return {
|
|
1146
1179
|
...authState,
|
|
1147
1180
|
refresh: refreshAuth,
|
|
1148
1181
|
login,
|
|
1149
1182
|
logout,
|
|
1150
|
-
switchIdentity
|
|
1183
|
+
switchIdentity,
|
|
1184
|
+
impersonate,
|
|
1185
|
+
stopImpersonation,
|
|
1186
|
+
searchImpersonationTargets
|
|
1151
1187
|
};
|
|
1152
1188
|
}
|
|
1153
1189
|
function AuthorizedView({ children, roles, fallback = null }) {
|
|
@@ -1401,6 +1437,7 @@ function useViewDataSource(options) {
|
|
|
1401
1437
|
const rows = data?.tableRecords ?? EMPTY_RECORDS;
|
|
1402
1438
|
const columns = data?.columns ?? EMPTY_COLUMNS;
|
|
1403
1439
|
const totalCount = data?.pagingInfo?.totalRecordCount;
|
|
1440
|
+
const moreRecords = data?.pagingInfo?.moreRecords;
|
|
1404
1441
|
const tablePermissions = data?.tablePermissions;
|
|
1405
1442
|
const aggregations = options.aggregations ?? EMPTY_AGGREGATION_SPEC;
|
|
1406
1443
|
const isFetchingAggregations = query.isFetching && debouncedRequest.aggregations !== void 0 && debouncedRequest.aggregations !== null && debouncedRequest.aggregations.length > 0;
|
|
@@ -1426,6 +1463,7 @@ function useViewDataSource(options) {
|
|
|
1426
1463
|
status: query.status,
|
|
1427
1464
|
rows,
|
|
1428
1465
|
totalCount,
|
|
1466
|
+
moreRecords,
|
|
1429
1467
|
columns,
|
|
1430
1468
|
tablePermissions,
|
|
1431
1469
|
aggregationResults,
|
|
@@ -1451,6 +1489,7 @@ function useViewDataSource(options) {
|
|
|
1451
1489
|
query.status,
|
|
1452
1490
|
rows,
|
|
1453
1491
|
totalCount,
|
|
1492
|
+
moreRecords,
|
|
1454
1493
|
columns,
|
|
1455
1494
|
tablePermissions,
|
|
1456
1495
|
aggregationResults,
|