@easygov/react-components 0.11.0 → 0.13.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/README.md +60 -13
- package/dist/easygov.d.mts +71 -20
- package/dist/easygov.mjs +2 -2
- package/dist/easygov.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,9 +76,10 @@ that runs it.
|
|
|
76
76
|
|
|
77
77
|
### Reading the header context
|
|
78
78
|
|
|
79
|
-
The header
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
The header sends the current screen URL (origin and path) to core-backend, which resolves the
|
|
80
|
+
signed-in user, the selected customer account, the ServiceApp the screen belongs to, and the running
|
|
81
|
+
process or the parent process a sub-process is being created under. The header publishes the result
|
|
82
|
+
to consumers:
|
|
82
83
|
|
|
83
84
|
```tsx
|
|
84
85
|
import { useEasygovHeaderContext } from '@easygov/react-components';
|
|
@@ -86,26 +87,60 @@ import { useEasygovHeaderContext } from '@easygov/react-components';
|
|
|
86
87
|
function ContactForm() {
|
|
87
88
|
const { claims } = useEasygovHeaderContext();
|
|
88
89
|
const account = claims?.customerAccount;
|
|
89
|
-
const
|
|
90
|
+
const parentProcessId = claims?.parentProcessId;
|
|
90
91
|
|
|
91
92
|
return (
|
|
92
93
|
<form>
|
|
93
94
|
<input defaultValue={claims?.currentUser.firstName ?? ''} />
|
|
94
95
|
<input defaultValue={account?.label ?? ''} />
|
|
95
96
|
{account?.accountType === 'ORGANIZATION' && <input defaultValue={account.uid ?? ''} />}
|
|
96
|
-
<input defaultValue={
|
|
97
|
+
<input defaultValue={parentProcessId ?? ''} />
|
|
97
98
|
</form>
|
|
98
99
|
);
|
|
99
100
|
}
|
|
100
101
|
```
|
|
101
102
|
|
|
102
|
-
`claims.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
`claims.serviceApp` is the ServiceApp the current screen belongs to, resolved by core from the URL's
|
|
104
|
+
subdomain and path. It is absent on core and info pages and on a URL that matches no registered
|
|
105
|
+
ServiceApp.
|
|
106
|
+
|
|
107
|
+
`claims.process` and `claims.parentProcessId` are mutually exclusive. `process` is the Behördengang
|
|
108
|
+
the URL selects (`/process/{id}`); it references its ServiceApp by `serviceAppId` and its own parent
|
|
109
|
+
by `parentProcessId`, absent for a top-level Behördengang. `parentProcessId` at the top level is the
|
|
110
|
+
Behördengang a sub-process is about to be created under (`/parentprocess/{id}`) — not a selected
|
|
111
|
+
Behördengang. Neither is present when the URL addresses no process. The context carries no ancestor
|
|
112
|
+
chain and no full parent object, because it is forwarded as an HTTP header where 4/8 KB size limits
|
|
113
|
+
apply.
|
|
114
|
+
|
|
115
|
+
For an Agent (Mitwirkende/r), `customerAccount.managementRole.servicePermission` is the Agent's
|
|
116
|
+
permission on the current ServiceApp (`READ_ONLY`, `EDIT` or `AUTHORIZE`). It is absent for a
|
|
117
|
+
SuperAgent, on a screen without a current ServiceApp, for a ServiceApp with the individual permission
|
|
118
|
+
model (the fach module manages those rights itself), and when the Agent holds no permission on the
|
|
119
|
+
app. An absent permission never means "allowed".
|
|
120
|
+
|
|
121
|
+
The header refetches the context whenever the URL's pathname changes, including client-side router
|
|
122
|
+
navigations; a change of only the query string or fragment does not refetch.
|
|
123
|
+
|
|
124
|
+
#### The context is renewed before it expires
|
|
125
|
+
|
|
126
|
+
`contextJwt` is short-lived: core-backend signs it with an `exp` (5 minutes by default). Without a
|
|
127
|
+
renewal, a page that stays on one URL would send an expired token to its backend. So the header
|
|
128
|
+
fetches a new context about 60 seconds before `exp` and keeps doing so for as long as the user is
|
|
129
|
+
signed in. Plan for these consequences:
|
|
130
|
+
|
|
131
|
+
- **Forward `contextJwt` fresh.** Read it from the hook (or the latest event) when you make a
|
|
132
|
+
request; don't capture it once at mount.
|
|
133
|
+
- **A renewal is not a change.** Each renewal delivers a new `contextJwt`, but when the context
|
|
134
|
+
itself is unchanged, `claims` is the **same object** as before. An effect or memo keyed on
|
|
135
|
+
`claims` therefore does not re-run, and a form you prefilled from it keeps what the user typed.
|
|
136
|
+
Read `claims` for what the context says, `contextJwt` for the token.
|
|
137
|
+
- **Never reset user input from `onContextChange` or the `contextchange` event** without checking
|
|
138
|
+
whether the content changed (below). Both fire on every renewal, roughly every 4 minutes.
|
|
139
|
+
- **A failed renewal is retried quietly** every 15 seconds while the old token is still valid.
|
|
140
|
+
Only if the token expires without a successful renewal, or core refuses the request for good
|
|
141
|
+
(for example `customer-account-not-accessible`), the header shows its error dialog, once per
|
|
142
|
+
outage. The previous `claims` stay in place meanwhile. A failed fetch after a navigation shows
|
|
143
|
+
the dialog at once and is then retried the same way; a refusal for good is not retried.
|
|
109
144
|
|
|
110
145
|
`claims` is `null` until the first response lands and again after logout — render a fallback,
|
|
111
146
|
don't assume it is present on first paint. The hook reflects the last context an `EasygovHeader`
|
|
@@ -117,10 +152,22 @@ Outside React — including `<eg-easygov-header>` — listen for the event inste
|
|
|
117
152
|
|
|
118
153
|
```js
|
|
119
154
|
document.addEventListener('contextchange', (e) => {
|
|
120
|
-
const { contextJwt, claims } = e.detail;
|
|
155
|
+
const { contextJwt, claims, contentChanged } = e.detail;
|
|
156
|
+
if (contentChanged) {
|
|
157
|
+
prefillForm(claims); // a real change: new account, process, ...
|
|
158
|
+
}
|
|
159
|
+
// Always keep the newest token for requests to your backend.
|
|
160
|
+
latestContextJwt = contextJwt;
|
|
121
161
|
});
|
|
122
162
|
```
|
|
123
163
|
|
|
164
|
+
`contentChanged` is `true` for the first context after sign-in and whenever `claims` differs from
|
|
165
|
+
the previous event; it is `false` when the event only delivers a renewed token. Prefill only when
|
|
166
|
+
it is `true`, otherwise every renewal overwrites what the user has typed since. In TypeScript the
|
|
167
|
+
detail is typed as `HeaderContextChangeDetail`, exported by both `@easygov/react-components` and
|
|
168
|
+
`@easygov/web-components`. The `onContextChange` callback has no such flag —
|
|
169
|
+
compare `claims` by identity there (`claims !== previousClaims`).
|
|
170
|
+
|
|
124
171
|
**The claims are decoded, not verified.** Use them for prefilling and rendering. Any backend
|
|
125
172
|
that receives `contextJwt` must verify its signature against core-backend's published JWKS
|
|
126
173
|
before trusting a claim.
|
package/dist/easygov.d.mts
CHANGED
|
@@ -326,11 +326,7 @@ declare function getAuthStatus(): AuthStatus;
|
|
|
326
326
|
declare function requireSession(): void;
|
|
327
327
|
|
|
328
328
|
type ManagementRoleType = 'SUPER_AGENT' | 'AGENT';
|
|
329
|
-
/**
|
|
330
|
-
* Verification level of a customer account's identity, and — as `ServiceApp.requiredAccountStatus`
|
|
331
|
-
* — the minimum level a service demands. One type for both because the knockout rule compares
|
|
332
|
-
* them to each other.
|
|
333
|
-
*/
|
|
329
|
+
/** Verification level of a customer account's identity. */
|
|
334
330
|
type AccountVerificationStatus = 'NOT_VERIFIED' | 'VERIFIED';
|
|
335
331
|
/**
|
|
336
332
|
* Verification of a management-role grant. Same values as `AccountVerificationStatus` today, but
|
|
@@ -339,16 +335,33 @@ type AccountVerificationStatus = 'NOT_VERIFIED' | 'VERIFIED';
|
|
|
339
335
|
*/
|
|
340
336
|
type ManagementRoleStatus = 'NOT_VERIFIED' | 'VERIFIED';
|
|
341
337
|
type ProcessStatus = 'PRELIMINARY' | 'REQUEST_IN_PROGRESS' | 'REQUEST_COMPLETED' | 'SERVICE_PROVISION_PENDING' | 'FINISHED' | 'CANCELED';
|
|
338
|
+
/** An Agent's permission level on one ServiceApp (Leistungsberechtigung). */
|
|
339
|
+
type PermissionLevel = 'READ_ONLY' | 'EDIT' | 'AUTHORIZE';
|
|
340
|
+
/**
|
|
341
|
+
* An Agent's ServicePermission for the current ServiceApp. Decoded from the context JWT, signature
|
|
342
|
+
* unverified: safe to render, never to authorize.
|
|
343
|
+
*/
|
|
344
|
+
interface ServicePermission {
|
|
345
|
+
/** Always the current ServiceApp's id (`HeaderContextClaims.serviceApp.id`). */
|
|
346
|
+
serviceAppId: string;
|
|
347
|
+
permissionLevel: PermissionLevel;
|
|
348
|
+
}
|
|
342
349
|
/**
|
|
343
350
|
* The caller's management role on the selected account: SUPER_AGENT (Kontoverwalter/in,
|
|
344
|
-
* unrestricted) or AGENT (Mitwirkende/r, restricted by per-service permissions
|
|
345
|
-
* context does NOT carry).
|
|
351
|
+
* unrestricted) or AGENT (Mitwirkende/r, restricted by per-service permissions).
|
|
346
352
|
* @see HeaderContextClaims for unverified-claims boundary; roleType and status must not drive authorization.
|
|
347
353
|
*/
|
|
348
354
|
interface ManagementRole {
|
|
349
355
|
roleType: ManagementRoleType;
|
|
350
356
|
/** Only present for SUPER_AGENT. */
|
|
351
357
|
status?: ManagementRoleStatus;
|
|
358
|
+
/**
|
|
359
|
+
* Only for an AGENT, and only for the current ServiceApp. Absent for a SUPER_AGENT, on a screen
|
|
360
|
+
* without a current ServiceApp, for an app with the individual permission model (the fach module
|
|
361
|
+
* manages those rights), and when the Agent holds no permission on the app. Absent never means
|
|
362
|
+
* "allowed".
|
|
363
|
+
*/
|
|
364
|
+
servicePermission?: ServicePermission;
|
|
352
365
|
}
|
|
353
366
|
interface CustomerAccountBase {
|
|
354
367
|
id: string;
|
|
@@ -377,18 +390,23 @@ interface IndividualAccount extends CustomerAccountBase {
|
|
|
377
390
|
*/
|
|
378
391
|
type SelectedCustomerAccount = OrganizationAccount | IndividualAccount;
|
|
379
392
|
/**
|
|
380
|
-
* The
|
|
393
|
+
* The ServiceApp the current screen belongs to, resolved by core from the screen URL.
|
|
394
|
+
* Decoded from the context JWT, signature unverified: safe to render, never to authorize.
|
|
395
|
+
*/
|
|
396
|
+
interface ServiceApp {
|
|
397
|
+
id: string;
|
|
398
|
+
/** The fach module, not core, manages the Agents' rights for this ServiceApp. */
|
|
399
|
+
individualServicePermissionModel: boolean;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* The selected Behördengang. Its ServiceApp is a reference only; on a screen of that ServiceApp
|
|
403
|
+
* it equals `HeaderContextClaims.serviceApp.id`.
|
|
381
404
|
* Decoded from the context JWT, signature unverified: safe to render, never to authorize.
|
|
382
405
|
*/
|
|
383
406
|
interface SelectedProcess {
|
|
384
407
|
id: string;
|
|
385
408
|
status: ProcessStatus;
|
|
386
|
-
|
|
387
|
-
id: string;
|
|
388
|
-
/** Minimum `CustomerAccount.status` the service requires (knockout). */
|
|
389
|
-
requiredAccountStatus: AccountVerificationStatus;
|
|
390
|
-
individualServicePermissionModel: boolean;
|
|
391
|
-
};
|
|
409
|
+
serviceAppId: string;
|
|
392
410
|
/**
|
|
393
411
|
* Global identifier of this process's own parent. Absent for a top-level Behördengang. This
|
|
394
412
|
* reference is the only representation of a selected process's parent: the context carries no
|
|
@@ -420,15 +438,35 @@ interface HeaderContextClaims {
|
|
|
420
438
|
currentUser: UserProfile;
|
|
421
439
|
customerAccount?: SelectedCustomerAccount;
|
|
422
440
|
/**
|
|
423
|
-
* The
|
|
441
|
+
* The ServiceApp the current screen belongs to. Absent on core and info pages and on a URL that
|
|
442
|
+
* matches no registered ServiceApp.
|
|
443
|
+
*/
|
|
444
|
+
serviceApp?: ServiceApp;
|
|
445
|
+
/**
|
|
446
|
+
* The Behördengang the URL selects (`/process/{id}`). Mutually exclusive with `parentProcessId`.
|
|
424
447
|
*/
|
|
425
448
|
process?: SelectedProcess;
|
|
426
449
|
/**
|
|
427
|
-
* The Behördengang a sub-process is about to be created under (`/parentprocess/{id}`)
|
|
428
|
-
* Mutually exclusive with `process
|
|
429
|
-
*
|
|
450
|
+
* The Behördengang a sub-process is about to be created under (`/parentprocess/{id}`), by id
|
|
451
|
+
* only. Mutually exclusive with `process`. A selected process's own parent is
|
|
452
|
+
* `process.parentProcessId`.
|
|
430
453
|
*/
|
|
431
|
-
|
|
454
|
+
parentProcessId?: string;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* `detail` of the `contextchange` CustomEvent. The event fires for every new JWT, which includes
|
|
458
|
+
* the background refresh that renews it shortly before it expires; `contentChanged` says whether
|
|
459
|
+
* `claims` differs from what the previous event delivered.
|
|
460
|
+
*/
|
|
461
|
+
interface HeaderContextChangeDetail {
|
|
462
|
+
contextJwt: string;
|
|
463
|
+
claims: HeaderContextClaims;
|
|
464
|
+
/**
|
|
465
|
+
* `true` for the first context of a session and whenever `claims` differs from the previous
|
|
466
|
+
* event's. `false` for a refresh that only renewed the token: prefilling a form from `claims`
|
|
467
|
+
* on such an event would overwrite what the user has typed since.
|
|
468
|
+
*/
|
|
469
|
+
contentChanged: boolean;
|
|
432
470
|
}
|
|
433
471
|
|
|
434
472
|
interface EasygovHeaderContext {
|
|
@@ -575,6 +613,11 @@ declare function useEasygovAuthStatus(): AuthStatus;
|
|
|
575
613
|
* still describe a previously selected account after the URL has moved on. Only sign-out (or a
|
|
576
614
|
* failed *first* fetch) leaves `claims` at `null`.
|
|
577
615
|
*
|
|
616
|
+
* `contextJwt` is renewed shortly before it expires (5 minutes by default), so it changes about
|
|
617
|
+
* every 4 minutes while the page stays put. `claims` keeps its identity across a renewal that
|
|
618
|
+
* changed nothing else, so an effect or memo keyed on `claims` does not re-run — read
|
|
619
|
+
* `contextJwt` when forwarding the token, `claims` when rendering or prefilling.
|
|
620
|
+
*
|
|
578
621
|
* IMPORTANT — trust boundary: `claims` is decoded, NOT signature-verified. Use it to prefill
|
|
579
622
|
* forms and drive rendering; never to authorize. A backend receiving `contextJwt` MUST verify it
|
|
580
623
|
* against core-backend's published JWKS.
|
|
@@ -606,6 +649,14 @@ interface EasygovHeaderProps {
|
|
|
606
649
|
* `header-context` endpoint (mirrored by the `contextchange` CustomEvent
|
|
607
650
|
* dispatched on the header element), together with its decoded `claims`.
|
|
608
651
|
*
|
|
652
|
+
* IMPORTANT — fires on every renewal, not only when the context changes. The JWT is short-lived
|
|
653
|
+
* (5 minutes by default), so the header fetches a new one shortly before it expires; that
|
|
654
|
+
* arrives here as a new `contextJwt`. When the refreshed context equals the previous one,
|
|
655
|
+
* `claims` is the very same object as in the previous call, so `claims !== previousClaims`
|
|
656
|
+
* tells a real change from a renewal. Never reset user input (a prefilled form) from this
|
|
657
|
+
* callback without that check. The `contextchange` event carries the same answer as
|
|
658
|
+
* `detail.contentChanged`.
|
|
659
|
+
*
|
|
609
660
|
* Both are always present when this fires: a payload that fails to decode never produces a
|
|
610
661
|
* `contextJwt`, so a decode failure suppresses the call rather than delivering null claims.
|
|
611
662
|
*
|
|
@@ -665,4 +716,4 @@ declare function getEasygovDisplayLanguage(): Locale;
|
|
|
665
716
|
declare function setEasygovDisplayLanguage(locale: Locale): void;
|
|
666
717
|
declare function subscribeEasygovDisplayLanguage(listener: () => void): () => void;
|
|
667
718
|
|
|
668
|
-
export { type AuthStatus, BrandBase, BrandEasygov, BrandFacebook, BrandKdk, BrandLinkedin, type BrandProps, BrandSeg, BrandSgv, BrandSsv, BrandSwiss, BrandXing, BrandYoutube, Button, type ButtonProps, type DesktopSpan, Dropdown, DropdownChip, DropdownChipsOverflow, type DropdownChipsOverflowProps, DropdownChipsTrigger, type DropdownChipsTriggerProps, DropdownCollection, DropdownEmpty, DropdownGroup, DropdownGroupLabel, DropdownHelperText, type DropdownHelperTextProps, DropdownInput, DropdownItem, type DropdownItemProps, DropdownItemText, DropdownLabel, type DropdownLabelProps, DropdownList, DropdownPopup, type DropdownPopupProps, DropdownSeparator, DropdownToggle, DropdownTrigger, type DropdownTriggerProps, DropdownValue, EasygovFooter, EasygovHeader, type EasygovHeaderContext, type EasygovHeaderProps, type EasygovSession, type EasygovVersionInfo, ErrorDialog, type ErrorDialogProps, Grid, GridItem, type GridItemProps, type GridProps, type AccountVerificationStatus as HeaderContextAccountVerificationStatus, type HeaderContextClaims, type IndividualAccount as HeaderContextIndividualAccount, type ManagementRole as HeaderContextManagementRole, type ManagementRoleStatus as HeaderContextManagementRoleStatus, type ManagementRoleType as HeaderContextManagementRoleType, type OrganizationAccount as HeaderContextOrganizationAccount, type ProcessStatus as HeaderContextProcessStatus, type UserProfile as HeaderContextUserProfile, Heading, type HeadingProps, Input, type InputProps, type Locale, type MobileSpan, NavigationMenu, NavigationMenuAnchor, type NavigationMenuAnchorProps, NavigationMenuContent, type NavigationMenuContentProps, NavigationMenuItem, type NavigationMenuItemProps, NavigationMenuLink, type NavigationMenuLinkProps, NavigationMenuList, type NavigationMenuListProps, type NavigationMenuProps, NavigationMenuTrigger, type NavigationMenuTriggerProps, RequireCustomerAccount, type RequireCustomerAccountProps, RequireSession, type RequireSessionProps, Select, SelectContent, type SelectContentProps, SelectGroup, SelectGroupLabel, type SelectGroupLabelProps, SelectHelperText, type SelectHelperTextProps, SelectItem, type SelectItemProps, SelectLabel, type SelectLabelProps, SelectSeparator, type SelectSeparatorProps, SelectTrigger, type SelectTriggerProps, type SelectedCustomerAccount, type SelectedProcess, type SessionUser, type TabletSpan, Text, TextLink, type TextLinkProps, type TextProps, buttonVariants, getAuthStatus, getEasygovDisplayLanguage, headingVariants, requireSession, setEasygovBusinessModuleVersion, setEasygovDisplayLanguage, subscribeEasygovDisplayLanguage, textLinkVariants, textVariants, useEasygovAuthStatus, useEasygovDisplayLanguage, useEasygovHeaderContext, useEasygovSession, useEasygovVersionInfo, useRequireCustomerAccount };
|
|
719
|
+
export { type AuthStatus, BrandBase, BrandEasygov, BrandFacebook, BrandKdk, BrandLinkedin, type BrandProps, BrandSeg, BrandSgv, BrandSsv, BrandSwiss, BrandXing, BrandYoutube, Button, type ButtonProps, type DesktopSpan, Dropdown, DropdownChip, DropdownChipsOverflow, type DropdownChipsOverflowProps, DropdownChipsTrigger, type DropdownChipsTriggerProps, DropdownCollection, DropdownEmpty, DropdownGroup, DropdownGroupLabel, DropdownHelperText, type DropdownHelperTextProps, DropdownInput, DropdownItem, type DropdownItemProps, DropdownItemText, DropdownLabel, type DropdownLabelProps, DropdownList, DropdownPopup, type DropdownPopupProps, DropdownSeparator, DropdownToggle, DropdownTrigger, type DropdownTriggerProps, DropdownValue, EasygovFooter, EasygovHeader, type EasygovHeaderContext, type EasygovHeaderProps, type EasygovSession, type EasygovVersionInfo, ErrorDialog, type ErrorDialogProps, Grid, GridItem, type GridItemProps, type GridProps, type AccountVerificationStatus as HeaderContextAccountVerificationStatus, type HeaderContextChangeDetail, type HeaderContextClaims, type IndividualAccount as HeaderContextIndividualAccount, type ManagementRole as HeaderContextManagementRole, type ManagementRoleStatus as HeaderContextManagementRoleStatus, type ManagementRoleType as HeaderContextManagementRoleType, type OrganizationAccount as HeaderContextOrganizationAccount, type PermissionLevel as HeaderContextPermissionLevel, type ProcessStatus as HeaderContextProcessStatus, type ServiceApp as HeaderContextServiceApp, type ServicePermission as HeaderContextServicePermission, type UserProfile as HeaderContextUserProfile, Heading, type HeadingProps, Input, type InputProps, type Locale, type MobileSpan, NavigationMenu, NavigationMenuAnchor, type NavigationMenuAnchorProps, NavigationMenuContent, type NavigationMenuContentProps, NavigationMenuItem, type NavigationMenuItemProps, NavigationMenuLink, type NavigationMenuLinkProps, NavigationMenuList, type NavigationMenuListProps, type NavigationMenuProps, NavigationMenuTrigger, type NavigationMenuTriggerProps, RequireCustomerAccount, type RequireCustomerAccountProps, RequireSession, type RequireSessionProps, Select, SelectContent, type SelectContentProps, SelectGroup, SelectGroupLabel, type SelectGroupLabelProps, SelectHelperText, type SelectHelperTextProps, SelectItem, type SelectItemProps, SelectLabel, type SelectLabelProps, SelectSeparator, type SelectSeparatorProps, SelectTrigger, type SelectTriggerProps, type SelectedCustomerAccount, type SelectedProcess, type SessionUser, type TabletSpan, Text, TextLink, type TextLinkProps, type TextProps, buttonVariants, getAuthStatus, getEasygovDisplayLanguage, headingVariants, requireSession, setEasygovBusinessModuleVersion, setEasygovDisplayLanguage, subscribeEasygovDisplayLanguage, textLinkVariants, textVariants, useEasygovAuthStatus, useEasygovDisplayLanguage, useEasygovHeaderContext, useEasygovSession, useEasygovVersionInfo, useRequireCustomerAccount };
|