@poly-x/react 0.1.0-alpha.14 → 0.1.0-alpha.15
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 +66 -2
- package/dist/index.d.cts +65 -1
- package/dist/index.d.mts +65 -1
- package/dist/index.mjs +66 -3
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -433,6 +433,9 @@ var BffSessionStore = class {
|
|
|
433
433
|
status: "signed-out",
|
|
434
434
|
reason: "signed-out"
|
|
435
435
|
});
|
|
436
|
+
if (options?.onSignOut) try {
|
|
437
|
+
await options.onSignOut();
|
|
438
|
+
} catch {}
|
|
436
439
|
window.location.assign(options?.redirectTo ?? "/");
|
|
437
440
|
};
|
|
438
441
|
/** Null by design: under BFF custody the token never reaches the browser (FR-SES-3). */
|
|
@@ -511,6 +514,45 @@ function Protected({ children, fallback = null, loading = null }) {
|
|
|
511
514
|
return isSignedIn ? children : fallback;
|
|
512
515
|
}
|
|
513
516
|
//#endregion
|
|
517
|
+
//#region src/geolocation.ts
|
|
518
|
+
/**
|
|
519
|
+
* Capture the browser's precise location IF the user consented. Resolves to a consent-labelled
|
|
520
|
+
* result and never throws/rejects.
|
|
521
|
+
*/
|
|
522
|
+
async function capturePreciseLocation(consented, opts = {}) {
|
|
523
|
+
if (!consented) return { consent: "denied" };
|
|
524
|
+
const geo = opts.geolocation ?? (typeof navigator !== "undefined" ? navigator.geolocation : void 0);
|
|
525
|
+
if (!geo) return { consent: "denied" };
|
|
526
|
+
const timeoutMs = opts.timeoutMs ?? 8e3;
|
|
527
|
+
return new Promise((resolve) => {
|
|
528
|
+
let settled = false;
|
|
529
|
+
const done = (result) => {
|
|
530
|
+
if (settled) return;
|
|
531
|
+
settled = true;
|
|
532
|
+
resolve(result);
|
|
533
|
+
};
|
|
534
|
+
const timer = setTimeout(() => done({ consent: "denied" }), timeoutMs + 500);
|
|
535
|
+
const finish = (result) => {
|
|
536
|
+
clearTimeout(timer);
|
|
537
|
+
done(result);
|
|
538
|
+
};
|
|
539
|
+
try {
|
|
540
|
+
geo.getCurrentPosition((position) => finish({
|
|
541
|
+
latitude: String(position.coords.latitude),
|
|
542
|
+
longitude: String(position.coords.longitude),
|
|
543
|
+
accuracyMeters: typeof position.coords.accuracy === "number" ? position.coords.accuracy : void 0,
|
|
544
|
+
consent: "granted"
|
|
545
|
+
}), () => finish({ consent: "denied" }), {
|
|
546
|
+
enableHighAccuracy: true,
|
|
547
|
+
timeout: timeoutMs,
|
|
548
|
+
maximumAge: 0
|
|
549
|
+
});
|
|
550
|
+
} catch {
|
|
551
|
+
finish({ consent: "denied" });
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
514
556
|
//#region src/components/sign-in.tsx
|
|
515
557
|
const DEFAULT_LABELS$3 = {
|
|
516
558
|
heading: "Sign in",
|
|
@@ -525,6 +567,7 @@ const DEFAULT_LABELS$3 = {
|
|
|
525
567
|
submit: "Sign in",
|
|
526
568
|
submitting: "Signing in…",
|
|
527
569
|
forgotPassword: "Forgot password?",
|
|
570
|
+
shareLocation: "Share my location for added account security",
|
|
528
571
|
chooseWorkspace: "Choose a workspace",
|
|
529
572
|
chooseWorkspaceSubheading: "You have access to more than one workspace.",
|
|
530
573
|
securedBy: "Secured by PolyX",
|
|
@@ -638,6 +681,7 @@ function SignIn(props) {
|
|
|
638
681
|
const [tenants, setTenants] = (0, react.useState)(null);
|
|
639
682
|
const [submitting, setSubmitting] = (0, react.useState)(false);
|
|
640
683
|
const [error, setError] = (0, react.useState)(null);
|
|
684
|
+
const [shareLocation, setShareLocation] = (0, react.useState)(false);
|
|
641
685
|
const polyx = (0, react.useContext)(PolyXContext);
|
|
642
686
|
const [branding, setBranding] = (0, react.useState)({});
|
|
643
687
|
(0, react.useEffect)(() => {
|
|
@@ -670,6 +714,7 @@ function SignIn(props) {
|
|
|
670
714
|
setSubmitting(true);
|
|
671
715
|
setError(null);
|
|
672
716
|
try {
|
|
717
|
+
const geo = props.captureLocation ? await capturePreciseLocation(shareLocation) : void 0;
|
|
673
718
|
const body = await (await fetch(action, {
|
|
674
719
|
method: "POST",
|
|
675
720
|
headers: { "content-type": "application/json" },
|
|
@@ -678,6 +723,7 @@ function SignIn(props) {
|
|
|
678
723
|
password,
|
|
679
724
|
organizationCode: props.organizationCode,
|
|
680
725
|
tenantId: options.tenantId,
|
|
726
|
+
...geo ? { geo } : {},
|
|
681
727
|
...options.passwords
|
|
682
728
|
})
|
|
683
729
|
})).json().catch(() => ({}));
|
|
@@ -881,6 +927,19 @@ function SignIn(props) {
|
|
|
881
927
|
children: labels.forgotPassword
|
|
882
928
|
})
|
|
883
929
|
}) : null,
|
|
930
|
+
props.captureLocation ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
931
|
+
className: "polyx-signin__consent",
|
|
932
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
933
|
+
type: "checkbox",
|
|
934
|
+
className: "polyx-signin__checkbox",
|
|
935
|
+
checked: shareLocation,
|
|
936
|
+
disabled: submitting,
|
|
937
|
+
onChange: (event) => setShareLocation(event.target.checked)
|
|
938
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
939
|
+
className: "polyx-signin__consent-label",
|
|
940
|
+
children: labels.shareLocation
|
|
941
|
+
})]
|
|
942
|
+
}) : null,
|
|
884
943
|
errorNode,
|
|
885
944
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
886
945
|
type: "submit",
|
|
@@ -1684,7 +1743,10 @@ function UserButtonRoot(props) {
|
|
|
1684
1743
|
key: "signOut",
|
|
1685
1744
|
label: labels.signOut,
|
|
1686
1745
|
labelIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExitIcon, {}),
|
|
1687
|
-
onClick: () => void signOut({
|
|
1746
|
+
onClick: () => void signOut({
|
|
1747
|
+
redirectTo: props.afterSignOutUrl,
|
|
1748
|
+
onSignOut: props.onSignOut
|
|
1749
|
+
})
|
|
1688
1750
|
});
|
|
1689
1751
|
if (props.showSignOutEverywhere) builtIns.set("signOutEverywhere", {
|
|
1690
1752
|
key: "signOutEverywhere",
|
|
@@ -1692,7 +1754,8 @@ function UserButtonRoot(props) {
|
|
|
1692
1754
|
labelIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ExitIcon, {}),
|
|
1693
1755
|
onClick: () => void signOut({
|
|
1694
1756
|
scope: "everywhere",
|
|
1695
|
-
redirectTo: props.afterSignOutUrl
|
|
1757
|
+
redirectTo: props.afterSignOutUrl,
|
|
1758
|
+
onSignOut: props.onSignOut
|
|
1696
1759
|
})
|
|
1697
1760
|
});
|
|
1698
1761
|
const items = resolveItems(props.children, builtIns);
|
|
@@ -1878,6 +1941,7 @@ exports.UserAvatar = UserAvatar;
|
|
|
1878
1941
|
exports.UserButton = UserButton;
|
|
1879
1942
|
exports.WebLocksLock = WebLocksLock;
|
|
1880
1943
|
exports.WindowBrowserBridge = WindowBrowserBridge;
|
|
1944
|
+
exports.capturePreciseLocation = capturePreciseLocation;
|
|
1881
1945
|
exports.createLoginController = createLoginController;
|
|
1882
1946
|
exports.initialsFrom = initialsFrom;
|
|
1883
1947
|
exports.resolveSide = resolveSide;
|
package/dist/index.d.cts
CHANGED
|
@@ -24,6 +24,13 @@ interface BffSignOutOptions {
|
|
|
24
24
|
scope?: "device" | "everywhere";
|
|
25
25
|
/** Where to land afterwards. Resolved here, client-side — the server takes no redirect target. */
|
|
26
26
|
redirectTo?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Run consumer cleanup after the session is revoked but BEFORE the post-sign-out
|
|
29
|
+
* redirect (the page is still alive at this point) — e.g. clear app cookies, reset
|
|
30
|
+
* local state, flush a store. Awaited; a thrown/rejected hook is swallowed so a
|
|
31
|
+
* failing callback can never trap the user in a signed-out-but-not-redirected state.
|
|
32
|
+
*/
|
|
33
|
+
onSignOut?: () => void | Promise<void>;
|
|
27
34
|
}
|
|
28
35
|
//#endregion
|
|
29
36
|
//#region src/auth-channel.d.ts
|
|
@@ -218,6 +225,8 @@ interface SignInLabels {
|
|
|
218
225
|
submit: string;
|
|
219
226
|
submitting: string;
|
|
220
227
|
forgotPassword: string;
|
|
228
|
+
/** Consent checkbox label shown when `captureLocation` is enabled (v6). */
|
|
229
|
+
shareLocation: string;
|
|
221
230
|
chooseWorkspace: string;
|
|
222
231
|
chooseWorkspaceSubheading: string;
|
|
223
232
|
securedBy: string;
|
|
@@ -255,6 +264,12 @@ interface SignInProps {
|
|
|
255
264
|
* "Forgot password?" link appears under the password field. Omit to hide it.
|
|
256
265
|
*/
|
|
257
266
|
forgotPasswordUrl?: string;
|
|
267
|
+
/**
|
|
268
|
+
* v6 (FR-CNST): show an explicit, opt-in consent checkbox to share precise location for
|
|
269
|
+
* security/presence. On submit, if checked, the browser captures a position and it rides to
|
|
270
|
+
* the platform with the login. Denial/timeout never blocks sign-in (coarse fallback). Off by default.
|
|
271
|
+
*/
|
|
272
|
+
captureLocation?: boolean;
|
|
258
273
|
/** Where to send the browser after success. Overrides the server's default redirect. */
|
|
259
274
|
afterSignInPath?: string;
|
|
260
275
|
/** Pre-scope the login to a single organization (skips org discovery). */
|
|
@@ -392,6 +407,49 @@ declare function AuthCallback({
|
|
|
392
407
|
children
|
|
393
408
|
}: AuthCallbackProps): ReactNode;
|
|
394
409
|
//#endregion
|
|
410
|
+
//#region src/geolocation.d.ts
|
|
411
|
+
/**
|
|
412
|
+
* @poly-x/react/geolocation — browser precise-location capture for sign-in (v6, FR-CNST/FR-LOC).
|
|
413
|
+
*
|
|
414
|
+
* The consent-gated bridge to `navigator.geolocation`. It NEVER rejects: a decline, a browser
|
|
415
|
+
* permission denial, a missing API, or a timeout all resolve to `{ consent: "denied" }` with no
|
|
416
|
+
* coordinates, so the caller can always proceed — sign-in must never block on location (D52).
|
|
417
|
+
* The captured value is posted to the BFF, which forwards it to the platform on the token
|
|
418
|
+
* exchange; the platform stores precise only when consent is `granted`, else coarse (IP).
|
|
419
|
+
*/
|
|
420
|
+
/** Minimal shape of the browser Geolocation API we depend on (injectable for testing). */
|
|
421
|
+
interface GeolocationLike {
|
|
422
|
+
getCurrentPosition(success: (position: {
|
|
423
|
+
coords: {
|
|
424
|
+
latitude: number;
|
|
425
|
+
longitude: number;
|
|
426
|
+
accuracy?: number;
|
|
427
|
+
};
|
|
428
|
+
}) => void, error?: (err: unknown) => void, options?: {
|
|
429
|
+
enableHighAccuracy?: boolean;
|
|
430
|
+
timeout?: number;
|
|
431
|
+
maximumAge?: number;
|
|
432
|
+
}): void;
|
|
433
|
+
}
|
|
434
|
+
/** The consent-labelled result of a capture attempt. Coordinates present only when granted. */
|
|
435
|
+
interface CapturedGeo {
|
|
436
|
+
latitude?: string;
|
|
437
|
+
longitude?: string;
|
|
438
|
+
accuracyMeters?: number;
|
|
439
|
+
consent: "granted" | "denied";
|
|
440
|
+
}
|
|
441
|
+
interface CaptureOptions {
|
|
442
|
+
/** Max time to wait for a fix before giving up (coarse fallback). Default 8000ms. */
|
|
443
|
+
timeoutMs?: number;
|
|
444
|
+
/** Override the geolocation source (tests). Defaults to `navigator.geolocation`. */
|
|
445
|
+
geolocation?: GeolocationLike;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Capture the browser's precise location IF the user consented. Resolves to a consent-labelled
|
|
449
|
+
* result and never throws/rejects.
|
|
450
|
+
*/
|
|
451
|
+
declare function capturePreciseLocation(consented: boolean, opts?: CaptureOptions): Promise<CapturedGeo>;
|
|
452
|
+
//#endregion
|
|
395
453
|
//#region src/components/placement.d.ts
|
|
396
454
|
/**
|
|
397
455
|
* Collision-aware placement for the SDK's popovers.
|
|
@@ -463,6 +521,12 @@ interface UserButtonProps {
|
|
|
463
521
|
showName?: boolean;
|
|
464
522
|
/** Where to land after signing out. Default the app root. */
|
|
465
523
|
afterSignOutUrl?: string;
|
|
524
|
+
/**
|
|
525
|
+
* Consumer cleanup run after the session is revoked but before the redirect —
|
|
526
|
+
* clear app cookies, reset local stores, etc. Awaited; errors are swallowed so a
|
|
527
|
+
* failing hook can't trap the user. Applies to both "Sign out" and "Sign out everywhere".
|
|
528
|
+
*/
|
|
529
|
+
onSignOut?: () => void | Promise<void>;
|
|
466
530
|
/**
|
|
467
531
|
* Destination for the "Manage account" entry. Omitted entirely when unset — PolyX has no
|
|
468
532
|
* hosted profile page, so the destination is the consumer's to own.
|
|
@@ -565,4 +629,4 @@ declare function UserAvatar({
|
|
|
565
629
|
declare const PACKAGE_NAME = "@poly-x/react";
|
|
566
630
|
declare const version = "0.0.0";
|
|
567
631
|
//#endregion
|
|
568
|
-
export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, 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, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, createLoginController, initialsFrom, resolveSide, useAuth, useAuthCallback, useSession, useUser, version };
|
|
632
|
+
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 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, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, capturePreciseLocation, createLoginController, initialsFrom, resolveSide, useAuth, useAuthCallback, useSession, useUser, version };
|
package/dist/index.d.mts
CHANGED
|
@@ -24,6 +24,13 @@ interface BffSignOutOptions {
|
|
|
24
24
|
scope?: "device" | "everywhere";
|
|
25
25
|
/** Where to land afterwards. Resolved here, client-side — the server takes no redirect target. */
|
|
26
26
|
redirectTo?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Run consumer cleanup after the session is revoked but BEFORE the post-sign-out
|
|
29
|
+
* redirect (the page is still alive at this point) — e.g. clear app cookies, reset
|
|
30
|
+
* local state, flush a store. Awaited; a thrown/rejected hook is swallowed so a
|
|
31
|
+
* failing callback can never trap the user in a signed-out-but-not-redirected state.
|
|
32
|
+
*/
|
|
33
|
+
onSignOut?: () => void | Promise<void>;
|
|
27
34
|
}
|
|
28
35
|
//#endregion
|
|
29
36
|
//#region src/auth-channel.d.ts
|
|
@@ -218,6 +225,8 @@ interface SignInLabels {
|
|
|
218
225
|
submit: string;
|
|
219
226
|
submitting: string;
|
|
220
227
|
forgotPassword: string;
|
|
228
|
+
/** Consent checkbox label shown when `captureLocation` is enabled (v6). */
|
|
229
|
+
shareLocation: string;
|
|
221
230
|
chooseWorkspace: string;
|
|
222
231
|
chooseWorkspaceSubheading: string;
|
|
223
232
|
securedBy: string;
|
|
@@ -255,6 +264,12 @@ interface SignInProps {
|
|
|
255
264
|
* "Forgot password?" link appears under the password field. Omit to hide it.
|
|
256
265
|
*/
|
|
257
266
|
forgotPasswordUrl?: string;
|
|
267
|
+
/**
|
|
268
|
+
* v6 (FR-CNST): show an explicit, opt-in consent checkbox to share precise location for
|
|
269
|
+
* security/presence. On submit, if checked, the browser captures a position and it rides to
|
|
270
|
+
* the platform with the login. Denial/timeout never blocks sign-in (coarse fallback). Off by default.
|
|
271
|
+
*/
|
|
272
|
+
captureLocation?: boolean;
|
|
258
273
|
/** Where to send the browser after success. Overrides the server's default redirect. */
|
|
259
274
|
afterSignInPath?: string;
|
|
260
275
|
/** Pre-scope the login to a single organization (skips org discovery). */
|
|
@@ -392,6 +407,49 @@ declare function AuthCallback({
|
|
|
392
407
|
children
|
|
393
408
|
}: AuthCallbackProps): ReactNode;
|
|
394
409
|
//#endregion
|
|
410
|
+
//#region src/geolocation.d.ts
|
|
411
|
+
/**
|
|
412
|
+
* @poly-x/react/geolocation — browser precise-location capture for sign-in (v6, FR-CNST/FR-LOC).
|
|
413
|
+
*
|
|
414
|
+
* The consent-gated bridge to `navigator.geolocation`. It NEVER rejects: a decline, a browser
|
|
415
|
+
* permission denial, a missing API, or a timeout all resolve to `{ consent: "denied" }` with no
|
|
416
|
+
* coordinates, so the caller can always proceed — sign-in must never block on location (D52).
|
|
417
|
+
* The captured value is posted to the BFF, which forwards it to the platform on the token
|
|
418
|
+
* exchange; the platform stores precise only when consent is `granted`, else coarse (IP).
|
|
419
|
+
*/
|
|
420
|
+
/** Minimal shape of the browser Geolocation API we depend on (injectable for testing). */
|
|
421
|
+
interface GeolocationLike {
|
|
422
|
+
getCurrentPosition(success: (position: {
|
|
423
|
+
coords: {
|
|
424
|
+
latitude: number;
|
|
425
|
+
longitude: number;
|
|
426
|
+
accuracy?: number;
|
|
427
|
+
};
|
|
428
|
+
}) => void, error?: (err: unknown) => void, options?: {
|
|
429
|
+
enableHighAccuracy?: boolean;
|
|
430
|
+
timeout?: number;
|
|
431
|
+
maximumAge?: number;
|
|
432
|
+
}): void;
|
|
433
|
+
}
|
|
434
|
+
/** The consent-labelled result of a capture attempt. Coordinates present only when granted. */
|
|
435
|
+
interface CapturedGeo {
|
|
436
|
+
latitude?: string;
|
|
437
|
+
longitude?: string;
|
|
438
|
+
accuracyMeters?: number;
|
|
439
|
+
consent: "granted" | "denied";
|
|
440
|
+
}
|
|
441
|
+
interface CaptureOptions {
|
|
442
|
+
/** Max time to wait for a fix before giving up (coarse fallback). Default 8000ms. */
|
|
443
|
+
timeoutMs?: number;
|
|
444
|
+
/** Override the geolocation source (tests). Defaults to `navigator.geolocation`. */
|
|
445
|
+
geolocation?: GeolocationLike;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Capture the browser's precise location IF the user consented. Resolves to a consent-labelled
|
|
449
|
+
* result and never throws/rejects.
|
|
450
|
+
*/
|
|
451
|
+
declare function capturePreciseLocation(consented: boolean, opts?: CaptureOptions): Promise<CapturedGeo>;
|
|
452
|
+
//#endregion
|
|
395
453
|
//#region src/components/placement.d.ts
|
|
396
454
|
/**
|
|
397
455
|
* Collision-aware placement for the SDK's popovers.
|
|
@@ -463,6 +521,12 @@ interface UserButtonProps {
|
|
|
463
521
|
showName?: boolean;
|
|
464
522
|
/** Where to land after signing out. Default the app root. */
|
|
465
523
|
afterSignOutUrl?: string;
|
|
524
|
+
/**
|
|
525
|
+
* Consumer cleanup run after the session is revoked but before the redirect —
|
|
526
|
+
* clear app cookies, reset local stores, etc. Awaited; errors are swallowed so a
|
|
527
|
+
* failing hook can't trap the user. Applies to both "Sign out" and "Sign out everywhere".
|
|
528
|
+
*/
|
|
529
|
+
onSignOut?: () => void | Promise<void>;
|
|
466
530
|
/**
|
|
467
531
|
* Destination for the "Manage account" entry. Omitted entirely when unset — PolyX has no
|
|
468
532
|
* hosted profile page, so the destination is the consumer's to own.
|
|
@@ -565,4 +629,4 @@ declare function UserAvatar({
|
|
|
565
629
|
declare const PACKAGE_NAME = "@poly-x/react";
|
|
566
630
|
declare const version = "0.0.0";
|
|
567
631
|
//#endregion
|
|
568
|
-
export { type Align, AuthCallback, type AuthCallbackMessage, type AuthCallbackProps, type AuthChannel, BroadcastAuthChannel, BroadcastChannelSessionChannel, type BrowserBridge, type CallbackParams, ForgotPassword, type ForgotPasswordLabels, type ForgotPasswordProps, 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, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, createLoginController, initialsFrom, resolveSide, useAuth, useAuthCallback, useSession, useUser, version };
|
|
632
|
+
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 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, UserAvatar, type UserAvatarProps, UserButton, type UserButtonActionProps, type UserButtonLabels, type UserButtonLinkProps, type UserButtonProps, type UserButtonSlotProps, WebLocksLock, WindowBrowserBridge, capturePreciseLocation, createLoginController, initialsFrom, resolveSide, useAuth, useAuthCallback, useSession, useUser, version };
|
package/dist/index.mjs
CHANGED
|
@@ -432,6 +432,9 @@ var BffSessionStore = class {
|
|
|
432
432
|
status: "signed-out",
|
|
433
433
|
reason: "signed-out"
|
|
434
434
|
});
|
|
435
|
+
if (options?.onSignOut) try {
|
|
436
|
+
await options.onSignOut();
|
|
437
|
+
} catch {}
|
|
435
438
|
window.location.assign(options?.redirectTo ?? "/");
|
|
436
439
|
};
|
|
437
440
|
/** Null by design: under BFF custody the token never reaches the browser (FR-SES-3). */
|
|
@@ -510,6 +513,45 @@ function Protected({ children, fallback = null, loading = null }) {
|
|
|
510
513
|
return isSignedIn ? children : fallback;
|
|
511
514
|
}
|
|
512
515
|
//#endregion
|
|
516
|
+
//#region src/geolocation.ts
|
|
517
|
+
/**
|
|
518
|
+
* Capture the browser's precise location IF the user consented. Resolves to a consent-labelled
|
|
519
|
+
* result and never throws/rejects.
|
|
520
|
+
*/
|
|
521
|
+
async function capturePreciseLocation(consented, opts = {}) {
|
|
522
|
+
if (!consented) return { consent: "denied" };
|
|
523
|
+
const geo = opts.geolocation ?? (typeof navigator !== "undefined" ? navigator.geolocation : void 0);
|
|
524
|
+
if (!geo) return { consent: "denied" };
|
|
525
|
+
const timeoutMs = opts.timeoutMs ?? 8e3;
|
|
526
|
+
return new Promise((resolve) => {
|
|
527
|
+
let settled = false;
|
|
528
|
+
const done = (result) => {
|
|
529
|
+
if (settled) return;
|
|
530
|
+
settled = true;
|
|
531
|
+
resolve(result);
|
|
532
|
+
};
|
|
533
|
+
const timer = setTimeout(() => done({ consent: "denied" }), timeoutMs + 500);
|
|
534
|
+
const finish = (result) => {
|
|
535
|
+
clearTimeout(timer);
|
|
536
|
+
done(result);
|
|
537
|
+
};
|
|
538
|
+
try {
|
|
539
|
+
geo.getCurrentPosition((position) => finish({
|
|
540
|
+
latitude: String(position.coords.latitude),
|
|
541
|
+
longitude: String(position.coords.longitude),
|
|
542
|
+
accuracyMeters: typeof position.coords.accuracy === "number" ? position.coords.accuracy : void 0,
|
|
543
|
+
consent: "granted"
|
|
544
|
+
}), () => finish({ consent: "denied" }), {
|
|
545
|
+
enableHighAccuracy: true,
|
|
546
|
+
timeout: timeoutMs,
|
|
547
|
+
maximumAge: 0
|
|
548
|
+
});
|
|
549
|
+
} catch {
|
|
550
|
+
finish({ consent: "denied" });
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
513
555
|
//#region src/components/sign-in.tsx
|
|
514
556
|
const DEFAULT_LABELS$3 = {
|
|
515
557
|
heading: "Sign in",
|
|
@@ -524,6 +566,7 @@ const DEFAULT_LABELS$3 = {
|
|
|
524
566
|
submit: "Sign in",
|
|
525
567
|
submitting: "Signing in…",
|
|
526
568
|
forgotPassword: "Forgot password?",
|
|
569
|
+
shareLocation: "Share my location for added account security",
|
|
527
570
|
chooseWorkspace: "Choose a workspace",
|
|
528
571
|
chooseWorkspaceSubheading: "You have access to more than one workspace.",
|
|
529
572
|
securedBy: "Secured by PolyX",
|
|
@@ -637,6 +680,7 @@ function SignIn(props) {
|
|
|
637
680
|
const [tenants, setTenants] = useState(null);
|
|
638
681
|
const [submitting, setSubmitting] = useState(false);
|
|
639
682
|
const [error, setError] = useState(null);
|
|
683
|
+
const [shareLocation, setShareLocation] = useState(false);
|
|
640
684
|
const polyx = useContext(PolyXContext);
|
|
641
685
|
const [branding, setBranding] = useState({});
|
|
642
686
|
useEffect(() => {
|
|
@@ -669,6 +713,7 @@ function SignIn(props) {
|
|
|
669
713
|
setSubmitting(true);
|
|
670
714
|
setError(null);
|
|
671
715
|
try {
|
|
716
|
+
const geo = props.captureLocation ? await capturePreciseLocation(shareLocation) : void 0;
|
|
672
717
|
const body = await (await fetch(action, {
|
|
673
718
|
method: "POST",
|
|
674
719
|
headers: { "content-type": "application/json" },
|
|
@@ -677,6 +722,7 @@ function SignIn(props) {
|
|
|
677
722
|
password,
|
|
678
723
|
organizationCode: props.organizationCode,
|
|
679
724
|
tenantId: options.tenantId,
|
|
725
|
+
...geo ? { geo } : {},
|
|
680
726
|
...options.passwords
|
|
681
727
|
})
|
|
682
728
|
})).json().catch(() => ({}));
|
|
@@ -880,6 +926,19 @@ function SignIn(props) {
|
|
|
880
926
|
children: labels.forgotPassword
|
|
881
927
|
})
|
|
882
928
|
}) : null,
|
|
929
|
+
props.captureLocation ? /* @__PURE__ */ jsxs("label", {
|
|
930
|
+
className: "polyx-signin__consent",
|
|
931
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
932
|
+
type: "checkbox",
|
|
933
|
+
className: "polyx-signin__checkbox",
|
|
934
|
+
checked: shareLocation,
|
|
935
|
+
disabled: submitting,
|
|
936
|
+
onChange: (event) => setShareLocation(event.target.checked)
|
|
937
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
938
|
+
className: "polyx-signin__consent-label",
|
|
939
|
+
children: labels.shareLocation
|
|
940
|
+
})]
|
|
941
|
+
}) : null,
|
|
883
942
|
errorNode,
|
|
884
943
|
/* @__PURE__ */ jsx("button", {
|
|
885
944
|
type: "submit",
|
|
@@ -1683,7 +1742,10 @@ function UserButtonRoot(props) {
|
|
|
1683
1742
|
key: "signOut",
|
|
1684
1743
|
label: labels.signOut,
|
|
1685
1744
|
labelIcon: /* @__PURE__ */ jsx(ExitIcon, {}),
|
|
1686
|
-
onClick: () => void signOut({
|
|
1745
|
+
onClick: () => void signOut({
|
|
1746
|
+
redirectTo: props.afterSignOutUrl,
|
|
1747
|
+
onSignOut: props.onSignOut
|
|
1748
|
+
})
|
|
1687
1749
|
});
|
|
1688
1750
|
if (props.showSignOutEverywhere) builtIns.set("signOutEverywhere", {
|
|
1689
1751
|
key: "signOutEverywhere",
|
|
@@ -1691,7 +1753,8 @@ function UserButtonRoot(props) {
|
|
|
1691
1753
|
labelIcon: /* @__PURE__ */ jsx(ExitIcon, {}),
|
|
1692
1754
|
onClick: () => void signOut({
|
|
1693
1755
|
scope: "everywhere",
|
|
1694
|
-
redirectTo: props.afterSignOutUrl
|
|
1756
|
+
redirectTo: props.afterSignOutUrl,
|
|
1757
|
+
onSignOut: props.onSignOut
|
|
1695
1758
|
})
|
|
1696
1759
|
});
|
|
1697
1760
|
const items = resolveItems(props.children, builtIns);
|
|
@@ -1861,4 +1924,4 @@ const UserButton = Object.assign(UserButtonRoot, {
|
|
|
1861
1924
|
const PACKAGE_NAME = "@poly-x/react";
|
|
1862
1925
|
const version = "0.0.0";
|
|
1863
1926
|
//#endregion
|
|
1864
|
-
export { AuthCallback, BroadcastAuthChannel, BroadcastChannelSessionChannel, ForgotPassword, PACKAGE_NAME, PolyXContext, PolyXProvider, PopupCancelledError, Protected, ResetPassword, SessionStoragePkceStore, SignIn, UserAvatar, UserButton, WebLocksLock, WindowBrowserBridge, createLoginController, initialsFrom, resolveSide, useAuth, useAuthCallback, useSession, useUser, version };
|
|
1927
|
+
export { AuthCallback, BroadcastAuthChannel, BroadcastChannelSessionChannel, ForgotPassword, PACKAGE_NAME, PolyXContext, PolyXProvider, PopupCancelledError, Protected, ResetPassword, SessionStoragePkceStore, SignIn, UserAvatar, UserButton, WebLocksLock, WindowBrowserBridge, capturePreciseLocation, createLoginController, initialsFrom, resolveSide, useAuth, useAuthCallback, useSession, useUser, version };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poly-x/react",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.15",
|
|
4
4
|
"description": "PolyX SDK for React SPAs - provider, hooks, drop-in auth UI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"module": "./dist/index.mjs",
|
|
28
28
|
"types": "./dist/index.d.cts",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@poly-x/core": "0.1.0-alpha.
|
|
30
|
+
"@poly-x/core": "0.1.0-alpha.15"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": ">=18",
|