@poly-x/react 0.1.1 → 0.3.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 +129 -2
- package/dist/index.cjs +893 -83
- package/dist/index.d.cts +228 -3
- package/dist/index.d.mts +228 -3
- package/dist/index.mjs +886 -85
- package/dist/styles.css +151 -0
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
-
import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState } from "@poly-x/core";
|
|
2
|
+
import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState, TenantOption } from "@poly-x/core";
|
|
3
3
|
|
|
4
4
|
//#region src/provider.d.ts
|
|
5
5
|
interface PolyXProviderProps {
|
|
@@ -122,6 +122,95 @@ declare class PopupCancelledError extends Error {
|
|
|
122
122
|
}
|
|
123
123
|
declare function createLoginController(deps: LoginControllerDeps): LoginController;
|
|
124
124
|
//#endregion
|
|
125
|
+
//#region src/warm-hop.d.ts
|
|
126
|
+
/**
|
|
127
|
+
* How long to wait for the silent attempt before giving up and showing the form.
|
|
128
|
+
*
|
|
129
|
+
* A guess, and labelled as one. The requirement it serves (FR-FALL-2) is that a slow or
|
|
130
|
+
* dead platform must never leave someone watching a spinner — and for that, *any* bound
|
|
131
|
+
* beats none. The person this protects is already signed out and gains nothing from
|
|
132
|
+
* waiting, so the bound is deliberately short. Override it if you have measured yours.
|
|
133
|
+
*/
|
|
134
|
+
declare const WARM_HOP_TIMEOUT_MS = 5000;
|
|
135
|
+
type WarmHopResult = /** Signed in. The session cookie is set; `workspace` names the tenant actually entered. */{
|
|
136
|
+
status: "signed_in";
|
|
137
|
+
workspace?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Signed in — but **not** where you asked.
|
|
141
|
+
*
|
|
142
|
+
* The platform only honours a workspace hint when the person has more than one candidate for
|
|
143
|
+
* that application; with exactly one it seats them there regardless and still answers
|
|
144
|
+
* "authorized". The session is valid and the person is somewhere they are entitled to be, so
|
|
145
|
+
* this is not a failure — but it is not what was requested, and being silently put to work in
|
|
146
|
+
* the wrong tenant is worse than being asked. Decide deliberately: accept it, or send them to
|
|
147
|
+
* a picker.
|
|
148
|
+
*/
|
|
149
|
+
| {
|
|
150
|
+
status: "workspace_mismatch";
|
|
151
|
+
requested: string;
|
|
152
|
+
workspace: string;
|
|
153
|
+
} /** Several workspaces are available and none was requested — ask, never guess (D14). */ | {
|
|
154
|
+
status: "select_workspace";
|
|
155
|
+
workspaces: readonly TenantOption[];
|
|
156
|
+
} /** Recognised, but not entitled to this application or workspace (D15). */ | {
|
|
157
|
+
status: "no_access";
|
|
158
|
+
} /** Not recognised. The ordinary signed-out path — not an error. */ | {
|
|
159
|
+
status: "sign_in_required";
|
|
160
|
+
} /** The attempt could not be completed. Treated as signed-out by callers; surfaced for logs. */ | {
|
|
161
|
+
status: "failed";
|
|
162
|
+
reason: string;
|
|
163
|
+
};
|
|
164
|
+
/** Has an automatic attempt already run in this tab? */
|
|
165
|
+
declare function warmHopAttempted(): boolean;
|
|
166
|
+
/** Clear the once-per-entry marker. Exported for sign-out, so the next sign-in may try again. */
|
|
167
|
+
declare function resetWarmHop(): void;
|
|
168
|
+
interface WarmHopOptions {
|
|
169
|
+
/**
|
|
170
|
+
* Pin a workspace by tenant id, organization code or slug.
|
|
171
|
+
*
|
|
172
|
+
* The platform matches this **only within the person's own memberships**, so it can
|
|
173
|
+
* narrow a choice but never widen access — which is why passing it is safe even when the
|
|
174
|
+
* value came from something the page was holding.
|
|
175
|
+
*/
|
|
176
|
+
workspace?: string;
|
|
177
|
+
/** Override the fall-back bound. See `WARM_HOP_TIMEOUT_MS`. */
|
|
178
|
+
timeoutMs?: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Run one silent attempt and report what the platform said.
|
|
182
|
+
*
|
|
183
|
+
* Never throws: every failure resolves to `failed`, because a caller in the middle of
|
|
184
|
+
* rendering a sign-in page has no better answer than "show the form" and an exception
|
|
185
|
+
* there is how a blank screen happens.
|
|
186
|
+
*/
|
|
187
|
+
declare function attemptWarmHop(options?: WarmHopOptions): Promise<WarmHopResult>;
|
|
188
|
+
/**
|
|
189
|
+
* Move to another workspace (F028 / FR-SWITCH-1…6).
|
|
190
|
+
*
|
|
191
|
+
* The same mechanism as the entry attempt, aimed deliberately rather than on arrival — and
|
|
192
|
+
* deliberately **not** subject to the once-per-entry guard, because this one is a thing the
|
|
193
|
+
* person asked for.
|
|
194
|
+
*
|
|
195
|
+
* Eligibility is decided by the platform at this moment, not from any list held here, so a
|
|
196
|
+
* workspace revoked since the picker was drawn can never be entered (FR-SWITCH-4). Which
|
|
197
|
+
* refusal you get depends on what is left, verified against a live platform: still holding
|
|
198
|
+
* other workspaces gives `select_workspace` listing them; having lost them all gives
|
|
199
|
+
* `no_access`. Do not treat `no_access` as the only refusal.
|
|
200
|
+
*
|
|
201
|
+
* On `signed_in` the session cookie now names the new workspace. The caller decides what to
|
|
202
|
+
* do next — the SDK does not reload the page or discard anything on their behalf, because it
|
|
203
|
+
* cannot know what is unsaved on the screen (FR-SWITCH-7).
|
|
204
|
+
*/
|
|
205
|
+
declare function switchWorkspace(workspace: string, options?: WarmHopOptions): Promise<WarmHopResult>;
|
|
206
|
+
/**
|
|
207
|
+
* The automatic entry attempt: at most once per tab, whatever the caller does (FR-HOP-2).
|
|
208
|
+
*
|
|
209
|
+
* Returns `null` when it declines to attempt — already tried this entry — so a caller can
|
|
210
|
+
* tell "we tried and they must sign in" from "we did not try".
|
|
211
|
+
*/
|
|
212
|
+
declare function attemptWarmHopOnce(options?: WarmHopOptions): Promise<WarmHopResult | null>;
|
|
213
|
+
//#endregion
|
|
125
214
|
//#region src/hooks.d.ts
|
|
126
215
|
/** The raw session state machine value — tearing-free under concurrent React. */
|
|
127
216
|
declare function useSession(): SessionState;
|
|
@@ -136,11 +225,52 @@ interface UseAuth {
|
|
|
136
225
|
* BFF custody, where the token is httpOnly server-side by design (FR-SES-3).
|
|
137
226
|
*/
|
|
138
227
|
getToken: () => Promise<string | null>;
|
|
228
|
+
/**
|
|
229
|
+
* Re-read the session's authorization from the server and resolve whether it actually
|
|
230
|
+
* changed (FR-CLIENT-3).
|
|
231
|
+
*
|
|
232
|
+
* This capability already existed on the BFF store but was not reachable from the public
|
|
233
|
+
* surface, so both consuming apps wrote their own refresher — and they already differ:
|
|
234
|
+
* one treats a module change as an authorization change, the other only a permission
|
|
235
|
+
* change. Pair it with `useLiveAuthz`/`startLiveAuthz`, which single-flights callers.
|
|
236
|
+
*/
|
|
237
|
+
refresh: () => Promise<boolean>;
|
|
139
238
|
}
|
|
140
239
|
declare function useAuth(): UseAuth;
|
|
141
240
|
/** Run this on your callback route to complete a sign-in (see `<AuthCallback>`). */
|
|
142
241
|
declare function useAuthCallback(): () => Promise<void>;
|
|
143
242
|
declare function useUser(): SessionClaims | null;
|
|
243
|
+
/** What `useWarmHop` reports while and after it tries. */
|
|
244
|
+
type WarmHopState = {
|
|
245
|
+
status: "idle";
|
|
246
|
+
} | {
|
|
247
|
+
status: "attempting";
|
|
248
|
+
} | {
|
|
249
|
+
status: "settled";
|
|
250
|
+
result: WarmHopResult;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Try a silent sign-in once when this component mounts (F027 / FR-HOP-2, FR-FALL-1…3).
|
|
254
|
+
*
|
|
255
|
+
* The contract that matters is D11's: **an attempt that fails must be invisible.** Someone who
|
|
256
|
+
* is genuinely signed out pays the whole cost of this feature and gets nothing from it, so they
|
|
257
|
+
* must not see a flicker, a spinner that outlives their patience, or — worst — a redirect loop.
|
|
258
|
+
* Hence: no navigation anywhere in the flow, a hard timeout, and a guard that survives the route
|
|
259
|
+
* change to the sign-in page (a guard in React state would reset exactly when it is needed).
|
|
260
|
+
*
|
|
261
|
+
* ```tsx
|
|
262
|
+
* const hop = useWarmHop();
|
|
263
|
+
* if (hop.status !== "settled" ) return null; // no half-drawn form
|
|
264
|
+
* if (hop.result.status === "signed_in") return <Redirect/>;
|
|
265
|
+
* return <SignIn />; // the ordinary path
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* Strict Mode's double-mount does not produce two attempts: the marker is written before the
|
|
269
|
+
* first request goes out.
|
|
270
|
+
*/
|
|
271
|
+
declare function useWarmHop(options?: WarmHopOptions & {
|
|
272
|
+
enabled?: boolean;
|
|
273
|
+
}): WarmHopState;
|
|
144
274
|
//#endregion
|
|
145
275
|
//#region src/context.d.ts
|
|
146
276
|
interface PolyXContextValue {
|
|
@@ -239,6 +369,8 @@ interface SignInLabels {
|
|
|
239
369
|
noAccess: string;
|
|
240
370
|
passwordChangeRequired: string;
|
|
241
371
|
genericError: string;
|
|
372
|
+
/** Reserved second-factor step (F020). Unreachable until MFA ships. */
|
|
373
|
+
verificationRequired?: string;
|
|
242
374
|
}
|
|
243
375
|
/**
|
|
244
376
|
* `card` renders just the card — drop it into your own layout.
|
|
@@ -500,6 +632,70 @@ interface Viewport {
|
|
|
500
632
|
*/
|
|
501
633
|
declare function resolveSide(preferred: SideOption, trigger: Rect, menu: Size, viewport: Viewport, gap?: number): Side;
|
|
502
634
|
//#endregion
|
|
635
|
+
//#region src/components/user-profile.d.ts
|
|
636
|
+
/** Every user-facing string, overridable for i18n / white-labelling (FR-BRAND-4). */
|
|
637
|
+
interface UserProfileLabels {
|
|
638
|
+
trigger: string;
|
|
639
|
+
title: string;
|
|
640
|
+
description: string;
|
|
641
|
+
firstName: string;
|
|
642
|
+
lastName: string;
|
|
643
|
+
phoneNumber: string;
|
|
644
|
+
cnic: string;
|
|
645
|
+
gender: string;
|
|
646
|
+
photo: string;
|
|
647
|
+
changePhoto: string;
|
|
648
|
+
optional: string;
|
|
649
|
+
save: string;
|
|
650
|
+
saving: string;
|
|
651
|
+
cancel: string;
|
|
652
|
+
close: string;
|
|
653
|
+
loading: string;
|
|
654
|
+
loadFailed: string;
|
|
655
|
+
saveFailed: string;
|
|
656
|
+
saved: string;
|
|
657
|
+
signedOut: string;
|
|
658
|
+
genderUnset: string;
|
|
659
|
+
genderMale: string;
|
|
660
|
+
genderFemale: string;
|
|
661
|
+
genderOther: string;
|
|
662
|
+
}
|
|
663
|
+
interface UserProfileProps {
|
|
664
|
+
/** Replaces the trigger's contents. The SDK still owns the button and its dialog semantics. */
|
|
665
|
+
trigger?: ReactNode;
|
|
666
|
+
/** Render no trigger at all and control the dialog from outside (`open` / `onOpenChange`). */
|
|
667
|
+
open?: boolean;
|
|
668
|
+
defaultOpen?: boolean;
|
|
669
|
+
onOpenChange?: (open: boolean) => void;
|
|
670
|
+
/** Notified after a successful save, with the saved profile (FR-PROF-10). */
|
|
671
|
+
onSaved?: (user: Record<string, unknown>) => void;
|
|
672
|
+
/** Appended to the root element's class list. */
|
|
673
|
+
className?: string;
|
|
674
|
+
/** Appended to the trigger button's class list — the element a consumer places in their layout. */
|
|
675
|
+
triggerClassName?: string;
|
|
676
|
+
/** Appended to the dialog's class list. */
|
|
677
|
+
dialogClassName?: string;
|
|
678
|
+
labels?: Partial<UserProfileLabels>;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* The signed-in person's own profile, as a dialog (v0.4 / FR-PROF).
|
|
682
|
+
*
|
|
683
|
+
* Every application that wanted this had to build the same four things: a form, a validation
|
|
684
|
+
* ruleset guessed at from error messages, a file upload, and — because the browser holds no
|
|
685
|
+
* credential under BFF custody — a server route to carry the call. The last of those is
|
|
686
|
+
* security-adjacent work that had no business being duplicated per app.
|
|
687
|
+
*
|
|
688
|
+
* The credential never comes near this component: it posts to the SDK's own route, which
|
|
689
|
+
* attaches the session server-side and takes the identity from the sealed session rather than
|
|
690
|
+
* from anything sent here. This surface cannot be aimed at another person's profile even if a
|
|
691
|
+
* caller tries (FR-CUSTODY-4 / D24).
|
|
692
|
+
*
|
|
693
|
+
* ```tsx
|
|
694
|
+
* <UserProfile onSaved={() => router.refresh()} />
|
|
695
|
+
* ```
|
|
696
|
+
*/
|
|
697
|
+
declare function UserProfile(props: UserProfileProps): ReactNode;
|
|
698
|
+
//#endregion
|
|
503
699
|
//#region src/components/user-button.d.ts
|
|
504
700
|
/** Every user-facing string, overridable for i18n / white-labelling. */
|
|
505
701
|
interface UserButtonLabels {
|
|
@@ -542,6 +738,17 @@ interface UserButtonProps {
|
|
|
542
738
|
* hosted profile page, so the destination is the consumer's to own.
|
|
543
739
|
*/
|
|
544
740
|
manageAccountUrl?: string;
|
|
741
|
+
/**
|
|
742
|
+
* Make "Manage account" open the SDK's own `<UserProfile/>` dialog instead of navigating
|
|
743
|
+
* (v0.4 / FR-PROF-8). Opt-in, so an app that already links its menu to its own account page
|
|
744
|
+
* keeps that behavior untouched (FR-COMPAT-2).
|
|
745
|
+
*
|
|
746
|
+
* Ignored when `manageAccountUrl` is set — a menu entry that both navigates and opens a
|
|
747
|
+
* dialog is a bug, so the explicit destination wins and this is a no-op.
|
|
748
|
+
*/
|
|
749
|
+
showUserProfile?: boolean;
|
|
750
|
+
/** Passed through to the dialog `showUserProfile` opens. */
|
|
751
|
+
userProfileProps?: Omit<UserProfileProps, "open" | "onOpenChange">;
|
|
545
752
|
/** Offer "Sign out everywhere" (ends every session for the user). Default `false`. */
|
|
546
753
|
showSignOutEverywhere?: boolean;
|
|
547
754
|
/** Start with the menu open. Default `false`. */
|
|
@@ -631,6 +838,23 @@ declare function UserAvatar({
|
|
|
631
838
|
className
|
|
632
839
|
}: UserAvatarProps): ReactNode;
|
|
633
840
|
//#endregion
|
|
841
|
+
//#region src/auth-event.d.ts
|
|
842
|
+
/**
|
|
843
|
+
* Read a one-shot auth event published by the BFF (F017 / FR-CLIENT-5).
|
|
844
|
+
*
|
|
845
|
+
* Sign-in and sign-out are full-page navigations, so an app cannot simply react in the
|
|
846
|
+
* handler that triggered them — the page is gone. The BFF leaves a short-lived, readable
|
|
847
|
+
* cookie; the destination page consumes it once.
|
|
848
|
+
*/
|
|
849
|
+
type AuthEventKind = "signed-in" | "signed-out";
|
|
850
|
+
/**
|
|
851
|
+
* Read the pending auth event and clear it, so it fires exactly once.
|
|
852
|
+
*
|
|
853
|
+
* Returns `null` when there is nothing pending — which is the normal case on almost every
|
|
854
|
+
* page load. Safe to call during render or in an effect; it is a no-op on the server.
|
|
855
|
+
*/
|
|
856
|
+
declare function consumeAuthEvent(): AuthEventKind | null;
|
|
857
|
+
//#endregion
|
|
634
858
|
//#region src/live-authz.d.ts
|
|
635
859
|
/**
|
|
636
860
|
* @poly-x/react/live-authz — keeps live claims current (SDK v2: FR-LIVE, FR-SIG, FR-TRIG).
|
|
@@ -699,6 +923,7 @@ declare function useAuthzChanged(callback: () => void): void;
|
|
|
699
923
|
* F006; this entry is the state foundation (provider, hooks, browser seams).
|
|
700
924
|
*/
|
|
701
925
|
declare const PACKAGE_NAME = "@poly-x/react";
|
|
702
|
-
|
|
926
|
+
/** The published version of this package, injected at build time. */
|
|
927
|
+
declare const version: string;
|
|
703
928
|
//#endregion
|
|
704
|
-
export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, capturePreciseLocation, createLoginController, initialsFrom, resolveSide, startLiveAuthz, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, version };
|
|
929
|
+
export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, type AuthEventKind, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, UserProfile, type UserProfileLabels, type UserProfileProps, WARM_HOP_TIMEOUT_MS, type WarmHopOptions, type WarmHopResult, type WarmHopState, WebLocksLock, WindowBrowserBridge, attemptWarmHop, attemptWarmHopOnce, capturePreciseLocation, consumeAuthEvent, createLoginController, initialsFrom, resetWarmHop, resolveSide, startLiveAuthz, switchWorkspace, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, useWarmHop, version, warmHopAttempted };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState } from "@poly-x/core";
|
|
1
|
+
import { AuthClient, Lock, PkceEntry, PkceStore, SessionChannel, SessionChannelEvent, SessionClaims, SessionEngine, SessionState, TenantOption } from "@poly-x/core";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/provider.d.ts
|
|
@@ -122,6 +122,95 @@ declare class PopupCancelledError extends Error {
|
|
|
122
122
|
}
|
|
123
123
|
declare function createLoginController(deps: LoginControllerDeps): LoginController;
|
|
124
124
|
//#endregion
|
|
125
|
+
//#region src/warm-hop.d.ts
|
|
126
|
+
/**
|
|
127
|
+
* How long to wait for the silent attempt before giving up and showing the form.
|
|
128
|
+
*
|
|
129
|
+
* A guess, and labelled as one. The requirement it serves (FR-FALL-2) is that a slow or
|
|
130
|
+
* dead platform must never leave someone watching a spinner — and for that, *any* bound
|
|
131
|
+
* beats none. The person this protects is already signed out and gains nothing from
|
|
132
|
+
* waiting, so the bound is deliberately short. Override it if you have measured yours.
|
|
133
|
+
*/
|
|
134
|
+
declare const WARM_HOP_TIMEOUT_MS = 5000;
|
|
135
|
+
type WarmHopResult = /** Signed in. The session cookie is set; `workspace` names the tenant actually entered. */{
|
|
136
|
+
status: "signed_in";
|
|
137
|
+
workspace?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Signed in — but **not** where you asked.
|
|
141
|
+
*
|
|
142
|
+
* The platform only honours a workspace hint when the person has more than one candidate for
|
|
143
|
+
* that application; with exactly one it seats them there regardless and still answers
|
|
144
|
+
* "authorized". The session is valid and the person is somewhere they are entitled to be, so
|
|
145
|
+
* this is not a failure — but it is not what was requested, and being silently put to work in
|
|
146
|
+
* the wrong tenant is worse than being asked. Decide deliberately: accept it, or send them to
|
|
147
|
+
* a picker.
|
|
148
|
+
*/
|
|
149
|
+
| {
|
|
150
|
+
status: "workspace_mismatch";
|
|
151
|
+
requested: string;
|
|
152
|
+
workspace: string;
|
|
153
|
+
} /** Several workspaces are available and none was requested — ask, never guess (D14). */ | {
|
|
154
|
+
status: "select_workspace";
|
|
155
|
+
workspaces: readonly TenantOption[];
|
|
156
|
+
} /** Recognised, but not entitled to this application or workspace (D15). */ | {
|
|
157
|
+
status: "no_access";
|
|
158
|
+
} /** Not recognised. The ordinary signed-out path — not an error. */ | {
|
|
159
|
+
status: "sign_in_required";
|
|
160
|
+
} /** The attempt could not be completed. Treated as signed-out by callers; surfaced for logs. */ | {
|
|
161
|
+
status: "failed";
|
|
162
|
+
reason: string;
|
|
163
|
+
};
|
|
164
|
+
/** Has an automatic attempt already run in this tab? */
|
|
165
|
+
declare function warmHopAttempted(): boolean;
|
|
166
|
+
/** Clear the once-per-entry marker. Exported for sign-out, so the next sign-in may try again. */
|
|
167
|
+
declare function resetWarmHop(): void;
|
|
168
|
+
interface WarmHopOptions {
|
|
169
|
+
/**
|
|
170
|
+
* Pin a workspace by tenant id, organization code or slug.
|
|
171
|
+
*
|
|
172
|
+
* The platform matches this **only within the person's own memberships**, so it can
|
|
173
|
+
* narrow a choice but never widen access — which is why passing it is safe even when the
|
|
174
|
+
* value came from something the page was holding.
|
|
175
|
+
*/
|
|
176
|
+
workspace?: string;
|
|
177
|
+
/** Override the fall-back bound. See `WARM_HOP_TIMEOUT_MS`. */
|
|
178
|
+
timeoutMs?: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Run one silent attempt and report what the platform said.
|
|
182
|
+
*
|
|
183
|
+
* Never throws: every failure resolves to `failed`, because a caller in the middle of
|
|
184
|
+
* rendering a sign-in page has no better answer than "show the form" and an exception
|
|
185
|
+
* there is how a blank screen happens.
|
|
186
|
+
*/
|
|
187
|
+
declare function attemptWarmHop(options?: WarmHopOptions): Promise<WarmHopResult>;
|
|
188
|
+
/**
|
|
189
|
+
* Move to another workspace (F028 / FR-SWITCH-1…6).
|
|
190
|
+
*
|
|
191
|
+
* The same mechanism as the entry attempt, aimed deliberately rather than on arrival — and
|
|
192
|
+
* deliberately **not** subject to the once-per-entry guard, because this one is a thing the
|
|
193
|
+
* person asked for.
|
|
194
|
+
*
|
|
195
|
+
* Eligibility is decided by the platform at this moment, not from any list held here, so a
|
|
196
|
+
* workspace revoked since the picker was drawn can never be entered (FR-SWITCH-4). Which
|
|
197
|
+
* refusal you get depends on what is left, verified against a live platform: still holding
|
|
198
|
+
* other workspaces gives `select_workspace` listing them; having lost them all gives
|
|
199
|
+
* `no_access`. Do not treat `no_access` as the only refusal.
|
|
200
|
+
*
|
|
201
|
+
* On `signed_in` the session cookie now names the new workspace. The caller decides what to
|
|
202
|
+
* do next — the SDK does not reload the page or discard anything on their behalf, because it
|
|
203
|
+
* cannot know what is unsaved on the screen (FR-SWITCH-7).
|
|
204
|
+
*/
|
|
205
|
+
declare function switchWorkspace(workspace: string, options?: WarmHopOptions): Promise<WarmHopResult>;
|
|
206
|
+
/**
|
|
207
|
+
* The automatic entry attempt: at most once per tab, whatever the caller does (FR-HOP-2).
|
|
208
|
+
*
|
|
209
|
+
* Returns `null` when it declines to attempt — already tried this entry — so a caller can
|
|
210
|
+
* tell "we tried and they must sign in" from "we did not try".
|
|
211
|
+
*/
|
|
212
|
+
declare function attemptWarmHopOnce(options?: WarmHopOptions): Promise<WarmHopResult | null>;
|
|
213
|
+
//#endregion
|
|
125
214
|
//#region src/hooks.d.ts
|
|
126
215
|
/** The raw session state machine value — tearing-free under concurrent React. */
|
|
127
216
|
declare function useSession(): SessionState;
|
|
@@ -136,11 +225,52 @@ interface UseAuth {
|
|
|
136
225
|
* BFF custody, where the token is httpOnly server-side by design (FR-SES-3).
|
|
137
226
|
*/
|
|
138
227
|
getToken: () => Promise<string | null>;
|
|
228
|
+
/**
|
|
229
|
+
* Re-read the session's authorization from the server and resolve whether it actually
|
|
230
|
+
* changed (FR-CLIENT-3).
|
|
231
|
+
*
|
|
232
|
+
* This capability already existed on the BFF store but was not reachable from the public
|
|
233
|
+
* surface, so both consuming apps wrote their own refresher — and they already differ:
|
|
234
|
+
* one treats a module change as an authorization change, the other only a permission
|
|
235
|
+
* change. Pair it with `useLiveAuthz`/`startLiveAuthz`, which single-flights callers.
|
|
236
|
+
*/
|
|
237
|
+
refresh: () => Promise<boolean>;
|
|
139
238
|
}
|
|
140
239
|
declare function useAuth(): UseAuth;
|
|
141
240
|
/** Run this on your callback route to complete a sign-in (see `<AuthCallback>`). */
|
|
142
241
|
declare function useAuthCallback(): () => Promise<void>;
|
|
143
242
|
declare function useUser(): SessionClaims | null;
|
|
243
|
+
/** What `useWarmHop` reports while and after it tries. */
|
|
244
|
+
type WarmHopState = {
|
|
245
|
+
status: "idle";
|
|
246
|
+
} | {
|
|
247
|
+
status: "attempting";
|
|
248
|
+
} | {
|
|
249
|
+
status: "settled";
|
|
250
|
+
result: WarmHopResult;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Try a silent sign-in once when this component mounts (F027 / FR-HOP-2, FR-FALL-1…3).
|
|
254
|
+
*
|
|
255
|
+
* The contract that matters is D11's: **an attempt that fails must be invisible.** Someone who
|
|
256
|
+
* is genuinely signed out pays the whole cost of this feature and gets nothing from it, so they
|
|
257
|
+
* must not see a flicker, a spinner that outlives their patience, or — worst — a redirect loop.
|
|
258
|
+
* Hence: no navigation anywhere in the flow, a hard timeout, and a guard that survives the route
|
|
259
|
+
* change to the sign-in page (a guard in React state would reset exactly when it is needed).
|
|
260
|
+
*
|
|
261
|
+
* ```tsx
|
|
262
|
+
* const hop = useWarmHop();
|
|
263
|
+
* if (hop.status !== "settled" ) return null; // no half-drawn form
|
|
264
|
+
* if (hop.result.status === "signed_in") return <Redirect/>;
|
|
265
|
+
* return <SignIn />; // the ordinary path
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* Strict Mode's double-mount does not produce two attempts: the marker is written before the
|
|
269
|
+
* first request goes out.
|
|
270
|
+
*/
|
|
271
|
+
declare function useWarmHop(options?: WarmHopOptions & {
|
|
272
|
+
enabled?: boolean;
|
|
273
|
+
}): WarmHopState;
|
|
144
274
|
//#endregion
|
|
145
275
|
//#region src/context.d.ts
|
|
146
276
|
interface PolyXContextValue {
|
|
@@ -239,6 +369,8 @@ interface SignInLabels {
|
|
|
239
369
|
noAccess: string;
|
|
240
370
|
passwordChangeRequired: string;
|
|
241
371
|
genericError: string;
|
|
372
|
+
/** Reserved second-factor step (F020). Unreachable until MFA ships. */
|
|
373
|
+
verificationRequired?: string;
|
|
242
374
|
}
|
|
243
375
|
/**
|
|
244
376
|
* `card` renders just the card — drop it into your own layout.
|
|
@@ -500,6 +632,70 @@ interface Viewport {
|
|
|
500
632
|
*/
|
|
501
633
|
declare function resolveSide(preferred: SideOption, trigger: Rect, menu: Size, viewport: Viewport, gap?: number): Side;
|
|
502
634
|
//#endregion
|
|
635
|
+
//#region src/components/user-profile.d.ts
|
|
636
|
+
/** Every user-facing string, overridable for i18n / white-labelling (FR-BRAND-4). */
|
|
637
|
+
interface UserProfileLabels {
|
|
638
|
+
trigger: string;
|
|
639
|
+
title: string;
|
|
640
|
+
description: string;
|
|
641
|
+
firstName: string;
|
|
642
|
+
lastName: string;
|
|
643
|
+
phoneNumber: string;
|
|
644
|
+
cnic: string;
|
|
645
|
+
gender: string;
|
|
646
|
+
photo: string;
|
|
647
|
+
changePhoto: string;
|
|
648
|
+
optional: string;
|
|
649
|
+
save: string;
|
|
650
|
+
saving: string;
|
|
651
|
+
cancel: string;
|
|
652
|
+
close: string;
|
|
653
|
+
loading: string;
|
|
654
|
+
loadFailed: string;
|
|
655
|
+
saveFailed: string;
|
|
656
|
+
saved: string;
|
|
657
|
+
signedOut: string;
|
|
658
|
+
genderUnset: string;
|
|
659
|
+
genderMale: string;
|
|
660
|
+
genderFemale: string;
|
|
661
|
+
genderOther: string;
|
|
662
|
+
}
|
|
663
|
+
interface UserProfileProps {
|
|
664
|
+
/** Replaces the trigger's contents. The SDK still owns the button and its dialog semantics. */
|
|
665
|
+
trigger?: ReactNode;
|
|
666
|
+
/** Render no trigger at all and control the dialog from outside (`open` / `onOpenChange`). */
|
|
667
|
+
open?: boolean;
|
|
668
|
+
defaultOpen?: boolean;
|
|
669
|
+
onOpenChange?: (open: boolean) => void;
|
|
670
|
+
/** Notified after a successful save, with the saved profile (FR-PROF-10). */
|
|
671
|
+
onSaved?: (user: Record<string, unknown>) => void;
|
|
672
|
+
/** Appended to the root element's class list. */
|
|
673
|
+
className?: string;
|
|
674
|
+
/** Appended to the trigger button's class list — the element a consumer places in their layout. */
|
|
675
|
+
triggerClassName?: string;
|
|
676
|
+
/** Appended to the dialog's class list. */
|
|
677
|
+
dialogClassName?: string;
|
|
678
|
+
labels?: Partial<UserProfileLabels>;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* The signed-in person's own profile, as a dialog (v0.4 / FR-PROF).
|
|
682
|
+
*
|
|
683
|
+
* Every application that wanted this had to build the same four things: a form, a validation
|
|
684
|
+
* ruleset guessed at from error messages, a file upload, and — because the browser holds no
|
|
685
|
+
* credential under BFF custody — a server route to carry the call. The last of those is
|
|
686
|
+
* security-adjacent work that had no business being duplicated per app.
|
|
687
|
+
*
|
|
688
|
+
* The credential never comes near this component: it posts to the SDK's own route, which
|
|
689
|
+
* attaches the session server-side and takes the identity from the sealed session rather than
|
|
690
|
+
* from anything sent here. This surface cannot be aimed at another person's profile even if a
|
|
691
|
+
* caller tries (FR-CUSTODY-4 / D24).
|
|
692
|
+
*
|
|
693
|
+
* ```tsx
|
|
694
|
+
* <UserProfile onSaved={() => router.refresh()} />
|
|
695
|
+
* ```
|
|
696
|
+
*/
|
|
697
|
+
declare function UserProfile(props: UserProfileProps): ReactNode;
|
|
698
|
+
//#endregion
|
|
503
699
|
//#region src/components/user-button.d.ts
|
|
504
700
|
/** Every user-facing string, overridable for i18n / white-labelling. */
|
|
505
701
|
interface UserButtonLabels {
|
|
@@ -542,6 +738,17 @@ interface UserButtonProps {
|
|
|
542
738
|
* hosted profile page, so the destination is the consumer's to own.
|
|
543
739
|
*/
|
|
544
740
|
manageAccountUrl?: string;
|
|
741
|
+
/**
|
|
742
|
+
* Make "Manage account" open the SDK's own `<UserProfile/>` dialog instead of navigating
|
|
743
|
+
* (v0.4 / FR-PROF-8). Opt-in, so an app that already links its menu to its own account page
|
|
744
|
+
* keeps that behavior untouched (FR-COMPAT-2).
|
|
745
|
+
*
|
|
746
|
+
* Ignored when `manageAccountUrl` is set — a menu entry that both navigates and opens a
|
|
747
|
+
* dialog is a bug, so the explicit destination wins and this is a no-op.
|
|
748
|
+
*/
|
|
749
|
+
showUserProfile?: boolean;
|
|
750
|
+
/** Passed through to the dialog `showUserProfile` opens. */
|
|
751
|
+
userProfileProps?: Omit<UserProfileProps, "open" | "onOpenChange">;
|
|
545
752
|
/** Offer "Sign out everywhere" (ends every session for the user). Default `false`. */
|
|
546
753
|
showSignOutEverywhere?: boolean;
|
|
547
754
|
/** Start with the menu open. Default `false`. */
|
|
@@ -631,6 +838,23 @@ declare function UserAvatar({
|
|
|
631
838
|
className
|
|
632
839
|
}: UserAvatarProps): ReactNode;
|
|
633
840
|
//#endregion
|
|
841
|
+
//#region src/auth-event.d.ts
|
|
842
|
+
/**
|
|
843
|
+
* Read a one-shot auth event published by the BFF (F017 / FR-CLIENT-5).
|
|
844
|
+
*
|
|
845
|
+
* Sign-in and sign-out are full-page navigations, so an app cannot simply react in the
|
|
846
|
+
* handler that triggered them — the page is gone. The BFF leaves a short-lived, readable
|
|
847
|
+
* cookie; the destination page consumes it once.
|
|
848
|
+
*/
|
|
849
|
+
type AuthEventKind = "signed-in" | "signed-out";
|
|
850
|
+
/**
|
|
851
|
+
* Read the pending auth event and clear it, so it fires exactly once.
|
|
852
|
+
*
|
|
853
|
+
* Returns `null` when there is nothing pending — which is the normal case on almost every
|
|
854
|
+
* page load. Safe to call during render or in an effect; it is a no-op on the server.
|
|
855
|
+
*/
|
|
856
|
+
declare function consumeAuthEvent(): AuthEventKind | null;
|
|
857
|
+
//#endregion
|
|
634
858
|
//#region src/live-authz.d.ts
|
|
635
859
|
/**
|
|
636
860
|
* @poly-x/react/live-authz — keeps live claims current (SDK v2: FR-LIVE, FR-SIG, FR-TRIG).
|
|
@@ -699,6 +923,7 @@ declare function useAuthzChanged(callback: () => void): void;
|
|
|
699
923
|
* F006; this entry is the state foundation (provider, hooks, browser seams).
|
|
700
924
|
*/
|
|
701
925
|
declare const PACKAGE_NAME = "@poly-x/react";
|
|
702
|
-
|
|
926
|
+
/** The published version of this package, injected at build time. */
|
|
927
|
+
declare const version: string;
|
|
703
928
|
//#endregion
|
|
704
|
-
export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, capturePreciseLocation, createLoginController, initialsFrom, resolveSide, startLiveAuthz, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, version };
|
|
929
|
+
export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, type AuthEventKind, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, type CaptureOptions, type CapturedGeo, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, type GeolocationLike, type LiveAuthzHandle, type LiveAuthzOptions, type LoginController, type LoginControllerDeps, PACKAGE_NAME, PolyXContext, type PolyXContextValue, PolyXProvider, type PolyXProviderProps, PopupCancelledError, type PopupHandle, Protected, type ProtectedProps, ResetPassword, type ResetPasswordLabels, type ResetPasswordProps, SessionStoragePkceStore, type Side, type SideOption, SignIn, type SignInLabels, type SignInOptions, type SignInProps, type SignInVariant, type UseAuth, type UseLiveAuthzOptions, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, UserProfile, type UserProfileLabels, type UserProfileProps, WARM_HOP_TIMEOUT_MS, type WarmHopOptions, type WarmHopResult, type WarmHopState, WebLocksLock, WindowBrowserBridge, attemptWarmHop, attemptWarmHopOnce, capturePreciseLocation, consumeAuthEvent, createLoginController, initialsFrom, resetWarmHop, resolveSide, startLiveAuthz, switchWorkspace, useAuth, useAuthCallback, useAuthzChanged, useLiveAuthz, useSession, useUser, useWarmHop, version, warmHopAttempted };
|