@rift-finance/react 0.1.2 → 0.2.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 CHANGED
@@ -79,14 +79,28 @@ async function send() {
79
79
 
80
80
  ### `<RiftAuth />`
81
81
 
82
- | Prop | Type | Description |
83
- |---|---|---|
84
- | `onSuccess` | `(user) => void` | Fires when sign-in completes. |
85
- | `onError` | `(message) => void` | Fires on auth failure. |
86
- | `onClose` | `() => void` | Fires when the modal closes. |
82
+ | Prop | Type | Default | Description |
83
+ |---|---|---|---|
84
+ | `onSuccess` | `(user) => void` | — | Fires when sign-in completes. |
85
+ | `onError` | `(message) => void` | — | Fires on auth failure. |
86
+ | `onClose` | `() => void` | — | Fires when the modal closes. |
87
+ | `maxHeight` | `number \| string` | unset | Cap the modal height. Pass `600` (px) or `"70vh"`. iframe scrolls internally if content exceeds this. |
88
+ | `maxWidth` | `number \| string` | `480` | Cap the modal width. Any CSS length. |
89
+ | `radius` | `number \| string` | `18` | Modal corner radius. |
90
+ | `backdrop` | `{ color?: string; blur?: number }` | `{ color: "rgba(15,15,20,0.55)", blur: 6 }` | Backdrop fill and CSS `backdrop-filter` blur in px. Set `blur: 0` to disable. |
91
+ | `backdropStyle` | `CSSProperties` | — | Escape hatch for anything `backdrop` doesn't cover (custom transitions, z-index, etc.). |
92
+ | `iframeStyle` | `CSSProperties` | — | Escape hatch for the iframe (borders, custom shadows, filters). |
87
93
 
88
94
  Mount this once near the root. It renders nothing until `useRift().open()` is called.
89
95
 
96
+ ```tsx
97
+ <RiftAuth
98
+ maxHeight={600}
99
+ radius={22}
100
+ backdrop={{ color: "rgba(255,255,255,0.5)", blur: 10 }}
101
+ />
102
+ ```
103
+
90
104
  ### `useRift()`
91
105
 
92
106
  | Field | Type | Description |
@@ -112,11 +126,74 @@ interface RiftUser {
112
126
  }
113
127
  ```
114
128
 
115
- ## Configuring Google sign-in
129
+ ## Auth methods
130
+
131
+ Google, Apple, email OTP, and phone OTP all surface automatically in the widget — zero setup. You don't register anything with Google or Apple; Rift handles all that under the hood. Drop `<RiftProvider>` + `<RiftAuth />` and the modal renders every available method.
132
+
133
+ ## Theming the modal
134
+
135
+ The modal's chrome — backdrop colour, blur, modal width, height cap, corner radius — is controllable from the host via props on `<RiftAuth>`. The widget's content area is iframed and brand-locked to Rift's visual identity (this is intentional — it's a trust signal so users know they're signing into Rift, not a phishing page that just looks like it). But the **frame around the iframe** is yours to style.
136
+
137
+ ### Why this exists
138
+
139
+ On large viewports the modal renders quite tall (~750px+ for the full email/phone/Google flow). Hosts with compact layouts or non-dark designs needed a way to:
140
+
141
+ - Cap the height so the modal doesn't dwarf the page.
142
+ - Swap the dark scrim for a light backdrop that matches their brand.
143
+ - Soften the blur (or disable it entirely on low-end devices).
144
+ - Round the modal more or less to match the rest of their UI.
145
+
146
+ Before 0.2.0, none of this was reachable — the styles were inline and the iframe ignored host CSS. Now they're typed props.
147
+
148
+ ### The props
149
+
150
+ ```tsx
151
+ <RiftAuth
152
+ // Numeric (px) or any CSS length. Iframe scrolls internally
153
+ // if widget content exceeds this. Default: no cap.
154
+ maxHeight={600} // or "70vh"
155
+
156
+ // Default 480 (px). Any CSS length works.
157
+ maxWidth={520}
158
+
159
+ // Default 18. Match the rest of your UI's roundness.
160
+ radius={22}
161
+
162
+ // Defaults: dark scrim with 6px blur.
163
+ backdrop={{
164
+ color: "rgba(255,255,255,0.5)",
165
+ blur: 10, // 0 disables backdrop-filter entirely
166
+ }}
167
+
168
+ // Escape hatches — spread last over the typed props above:
169
+ backdropStyle={{ animation: "myFadeIn 220ms ease" }}
170
+ iframeStyle={{ boxShadow: "0 4px 12px rgba(0,0,0,0.1)" }}
171
+ />
172
+ ```
173
+
174
+ ### How `maxHeight` actually works
175
+
176
+ The widget posts its desired height to the SDK via `postMessage` (`rift:resize` events) whenever its content reflows — toggling between email and phone, showing an error, etc. Without `maxHeight`, the SDK sets the iframe to that reported height verbatim. With `maxHeight`, the SDK clamps:
177
+
178
+ - **Numeric `maxHeight`**: iframe `height` is `min(widget.reported, maxHeight)` — never grows past your cap.
179
+ - **String `maxHeight`** (e.g. `"70vh"`): iframe `height` stays at the widget's reported value but with `max-height` set in CSS so the browser does the clamp. Same effective behavior.
180
+
181
+ Either way, if the widget's content exceeds the cap, the iframe's native scrollbar takes over — no clipping, no layout breakage.
182
+
183
+ ### Anything not exposed as a typed prop
184
+
185
+ Use `backdropStyle` and `iframeStyle`. They're spread **after** the typed props, so they override anything that conflicts. Useful for:
186
+
187
+ - Custom enter/exit animations (the default is a 180ms opacity fade)
188
+ - A z-index lower than the default `2147483646` if you're nesting under another high-z portal
189
+ - Borders, rings, gradient shadows on the modal that the `radius` prop alone can't express
190
+ - `transition` overrides on the iframe height — the default is `200ms ease`
191
+
192
+ These escape hatches keep the surface tight (no need to add a prop for every possible CSS knob) while letting any unusual host design ship without forking the SDK.
193
+
194
+ ### Backwards compatibility
116
195
 
117
- 1. Create an OAuth 2.0 Client ID in Google Cloud Console for your domain.
118
- 2. Paste it into your project's **Auth** tab in the Rift dashboard.
119
- 3. The widget refetches its config on next load — the Google button shows up automatically.
196
+ Every new prop is optional with the previous default. Apps on 0.1.x can upgrade to 0.2.0 with no code change and get the same modal they had before.
120
197
 
121
198
  ## Self-hosting the widget
122
199
 
@@ -131,7 +208,7 @@ The default widget URL is `https://widget.riftfi.xyz`. If you need to self-host
131
208
  Integration walkthrough, OpenAPI spec, transaction signing flows: **https://service.riftfi.xyz/docs**
132
209
 
133
210
  - Live API explorer: https://developers.riftfi.xyz
134
- - OpenAPI spec: https://developers.riftfi.xyz/docs.json
211
+ - OpenAPI spec: https://github.com/Rift-FI/Rift-Sdk-Wrapper/blob/main/docs.json
135
212
  - Source: https://github.com/Rift-FI/rift-react
136
213
 
137
214
  ## License
@@ -0,0 +1,55 @@
1
+ import { CSSProperties } from 'react';
2
+ import { RiftUser } from './types';
3
+ interface BackdropStyle {
4
+ /** Backdrop fill colour. Default `rgba(15,15,20,0.55)` (dark scrim). */
5
+ color?: string;
6
+ /** CSS `backdrop-filter: blur(<px>)`. Default 6, set 0 to disable. */
7
+ blur?: number;
8
+ }
9
+ interface RiftAuthProps {
10
+ onSuccess?: (user: RiftUser) => void;
11
+ onError?: (message: string) => void;
12
+ onClose?: () => void;
13
+ /**
14
+ * Cap the modal height. Pass a number for px (e.g. `600`) or a CSS
15
+ * string for viewport units (`"70vh"`). The iframe scrolls
16
+ * internally if its content exceeds this. Defaults to no cap; the
17
+ * widget reports its natural height via postMessage.
18
+ */
19
+ maxHeight?: number | string;
20
+ /**
21
+ * Cap the modal width. Defaults to 480 (px). Pass any CSS length.
22
+ */
23
+ maxWidth?: number | string;
24
+ /**
25
+ * Corner radius on the modal. Defaults to 18 (px).
26
+ */
27
+ radius?: number | string;
28
+ /**
29
+ * Backdrop styling. See `BackdropStyle`. Each field falls back to
30
+ * the default if omitted.
31
+ */
32
+ backdrop?: BackdropStyle;
33
+ /**
34
+ * Extra style applied to the backdrop wrapper. Use for things outside
35
+ * the typed `backdrop` knob (custom transitions, z-index, etc.).
36
+ */
37
+ backdropStyle?: CSSProperties;
38
+ /**
39
+ * Extra style applied to the iframe. Useful for borders, custom
40
+ * shadows, or filters that the typed props don't cover.
41
+ */
42
+ iframeStyle?: CSSProperties;
43
+ }
44
+ /**
45
+ * Renders the modal backdrop + iframe whenever the provider's `isOpen` is
46
+ * true. Place this once near the root of your app (typically just inside
47
+ * <RiftProvider>); call `useRift().open()` to show it.
48
+ *
49
+ * All visual knobs are overridable from the host: see `maxHeight`,
50
+ * `maxWidth`, `radius`, `backdrop`, plus escape hatches `backdropStyle`
51
+ * and `iframeStyle` for anything else.
52
+ */
53
+ export declare function RiftAuth({ onSuccess, onError, onClose, maxHeight, maxWidth, radius, backdrop, backdropStyle, iframeStyle, }: RiftAuthProps): import("react/jsx-runtime").JSX.Element | null;
54
+ export {};
55
+ //# sourceMappingURL=RiftAuth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RiftAuth.d.ts","sourceRoot":"","sources":["../src/RiftAuth.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,UAAU,aAAa;IACrB,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,aAAa;IAErB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC;IAEzB;;;OAGG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC;CAC7B;AAOD;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EACvB,SAAS,EACT,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAA4B,EAC5B,MAAuB,EACvB,QAAQ,EACR,aAAa,EACb,WAAW,GACZ,EAAE,aAAa,kDA0Gf"}
@@ -0,0 +1,34 @@
1
+ import { ReactNode } from 'react';
2
+ import { RiftConfig, RiftMode, RiftUser } from './types';
3
+ interface RiftContextValue {
4
+ apiKey: string;
5
+ widgetUrl: string;
6
+ user: RiftUser | null;
7
+ isOpen: boolean;
8
+ isReady: boolean;
9
+ error: string | null;
10
+ open: (opts?: {
11
+ mode?: RiftMode;
12
+ }) => void;
13
+ close: () => void;
14
+ signOut: () => Promise<void>;
15
+ /**
16
+ * Returns a valid access token, refreshing silently if the current
17
+ * one is missing or about to expire. Rejects if the user is signed
18
+ * out or the refresh fails (in which case state is cleared and the
19
+ * caller should prompt re-auth).
20
+ */
21
+ getAccessToken: () => Promise<string>;
22
+ _iframeSrc: string;
23
+ _iframeHeight: number;
24
+ _onIframeLoad: () => void;
25
+ }
26
+ export declare function useRiftContext(): RiftContextValue;
27
+ interface RiftProviderProps extends RiftConfig {
28
+ children: ReactNode;
29
+ autoOpen?: boolean;
30
+ persist?: boolean;
31
+ }
32
+ export declare function RiftProvider({ apiKey, widgetUrl, children, autoOpen, persist, }: RiftProviderProps): import("react/jsx-runtime").JSX.Element;
33
+ export {};
34
+ //# sourceMappingURL=RiftProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RiftProvider.d.ts","sourceRoot":"","sources":["../src/RiftProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,UAAU,EAAa,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAuBzE,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,QAAQ,CAAA;KAAE,KAAK,IAAI,CAAC;IAC3C,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAID,wBAAgB,cAAc,IAAI,gBAAgB,CAQjD;AAED,UAAU,iBAAkB,SAAQ,UAAU;IAC5C,QAAQ,EAAE,SAAS,CAAC;IAGpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAInB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAsBD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,SAAS,EACT,QAAQ,EACR,QAAgB,EAChB,OAAc,GACf,EAAE,iBAAiB,2CAoRnB"}
package/dist/index.d.ts CHANGED
@@ -1 +1,5 @@
1
- export {}
1
+ export { RiftProvider } from './RiftProvider';
2
+ export { RiftAuth } from './RiftAuth';
3
+ export { useRift } from './useRift';
4
+ export type { RiftUser, RiftConfig, RiftMode, RiftEvent } from './types';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const k=require("react/jsx-runtime"),r=require("react");let u=null,E=!1,S=[],h=null;const d=new Map;function H(){return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,9)}`}function N(e){if(typeof document>"u")return Promise.reject(new Error("Cannot mount refresh iframe outside the browser"));if(u&&E)return Promise.resolve();if(h=new URL(e.widgetUrl).origin,!u){u=document.createElement("iframe"),u.setAttribute("aria-hidden","true"),u.setAttribute("tabindex","-1"),u.title="Rift session refresh",u.style.cssText="position:absolute;width:1px;height:1px;border:0;opacity:0;pointer-events:none;left:-9999px;top:-9999px;";const i=new URLSearchParams({key:e.apiKey,headless:"1"});u.src=`${e.widgetUrl.replace(/\/$/,"")}/?${i.toString()}`,document.body.appendChild(u),window.addEventListener("message",o=>{if(o.origin!==h)return;const t=o.data;if(!(!t||typeof t!="object"||typeof t.type!="string")&&t.type.startsWith("rift:")){if(t.type==="rift:ready"){E=!0,S.forEach(n=>n()),S=[];return}if(t.type==="rift:refresh-result"){const n=d.get(t.requestId);n&&(d.delete(t.requestId),n.resolve({accessToken:t.accessToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn}));return}if(t.type==="rift:refresh-error"){const n=d.get(t.requestId);n&&(d.delete(t.requestId),n.reject(new Error(t.message||"Refresh failed")));return}if(t.type==="rift:logout-result"){const n=d.get(t.requestId);n&&(d.delete(t.requestId),n.resolve({accessToken:"",expiresAt:"",expiresIn:0}))}}})}return E?Promise.resolve():new Promise(i=>{S.push(i),setTimeout(()=>{if(!E){const o=S.shift();o&&o()}},8e3)})}async function F(e){if(await N(e),!u?.contentWindow||!h)throw new Error("Refresh iframe is not available");const i=H();return new Promise((o,t)=>{d.set(i,{resolve:o,reject:t}),u.contentWindow.postMessage({type:"rift:refresh-request",requestId:i},h),setTimeout(()=>{const n=d.get(i);n&&(d.delete(i),n.reject(new Error("Refresh timed out")))},1e4)})}async function X(e){try{if(await N(e),!u?.contentWindow||!h)return;const i=H();await new Promise(o=>{d.set(i,{resolve:()=>o(),reject:()=>o()}),u.contentWindow.postMessage({type:"rift:logout-request",requestId:i},h),setTimeout(()=>{d.delete(i),o()},5e3)})}catch{}}const Z="https://widget.riftfi.xyz",L="rift:identity",K=60,z=r.createContext(null);function Y(){const e=r.useContext(z);if(!e)throw new Error("[@rift/react] useRift() / <RiftAuth> must be used inside <RiftProvider>");return e}function ee(){if(typeof window>"u")return null;try{const e=localStorage.getItem(L);return e?JSON.parse(e):null}catch{return null}}function te(e){if(!(typeof window>"u"))try{e?localStorage.setItem(L,JSON.stringify(e)):localStorage.removeItem(L)}catch{}}function re({apiKey:e,widgetUrl:i,children:o,autoOpen:t=!1,persist:n=!0}){const a=i||Z,w=r.useMemo(()=>{try{return new URL(a).origin}catch{return a}},[a]),[g,A]=r.useState(null),[m,y]=r.useState(!1),[b,G]=r.useState("signin"),[j,I]=r.useState(!1),[C,O]=r.useState(null),[P,J]=r.useState(540),[U,B]=r.useState(0),T=r.useRef(null);T.current=g;const x=r.useRef(null),l=r.useCallback(s=>{A(s),n&&te(s?{user:s.user,address:s.address,btcAddress:s.btcAddress}:null)},[n]);r.useEffect(()=>{if(!n)return;const s=ee();if(!s)return;let f=!0;return(async()=>{try{const c=await F({apiKey:e,widgetUrl:a});if(!f)return;l({user:s.user,address:s.address,btcAddress:s.btcAddress,accessToken:c.accessToken,expiresAt:c.expiresAt})}catch{if(!f)return;l(null)}})(),()=>{f=!1}},[]);const v=r.useCallback(s=>{G(s?.mode||"signin"),O(null),I(!1),B(f=>f+1),y(!0)},[]),R=r.useCallback(()=>{y(!1),I(!1)},[]),q=r.useCallback(async()=>{await X({apiKey:e,widgetUrl:a}),l(null)},[e,a,l]),M=r.useCallback(async()=>{const s=T.current;if(!s)throw new Error("Not signed in");const f=s.expiresAt?new Date(s.expiresAt).getTime():null,c=Date.now();return!(!f||f-c<K*1e3)&&s.accessToken?s.accessToken:(x.current||(x.current=(async()=>{try{const p=await F({apiKey:e,widgetUrl:a}),D=T.current;if(!D)throw new Error("Signed out during refresh");const V={...D,accessToken:p.accessToken,expiresAt:p.expiresAt};return l(V),p.accessToken}catch(p){throw l(null),p instanceof Error?p:new Error(String(p))}finally{x.current=null}})()),x.current)},[e,a,l]);r.useEffect(()=>{if(typeof window>"u")return;const s=f=>{if(f.origin!==w)return;const c=f.data;if(!(!c||typeof c!="object"||typeof c.type!="string")&&c.type.startsWith("rift:"))switch(c.type){case"rift:ready":m&&I(!0);break;case"rift:close":R();break;case"rift:resize":J(Math.max(360,Math.min(820,c.height+8)));break;case"rift:signin-success":{const $={user:c.user,address:c.address,btcAddress:c.btcAddress,accessToken:c.accessToken,expiresAt:c.expiresAt};l($),y(!1);break}case"rift:signin-error":O(c.message);break}};return window.addEventListener("message",s),()=>window.removeEventListener("message",s)},[w,R,l,m]),r.useEffect(()=>{if(!(typeof document>"u")&&m){const s=document.documentElement.style.overflow;return document.documentElement.style.overflow="hidden",()=>{document.documentElement.style.overflow=s}}},[m]),r.useEffect(()=>{t&&!g&&v()},[]);const _=r.useMemo(()=>{const s=new URLSearchParams({key:e,mode:b,origin:typeof window<"u"?window.location.origin:"",t:String(U)});return`${a.replace(/\/$/,"")}/?${s.toString()}`},[e,b,U,a]),W=r.useCallback(()=>{},[]),Q=r.useMemo(()=>({apiKey:e,widgetUrl:a,user:g,isOpen:m,isReady:j,error:C,open:v,close:R,signOut:q,getAccessToken:M,_iframeSrc:_,_iframeHeight:P,_onIframeLoad:W}),[e,a,g,m,j,C,v,R,q,M,_,P,W]);return k.jsx(z.Provider,{value:Q,children:o})}function se({onSuccess:e,onError:i,onClose:o}){const{isOpen:t,isReady:n,error:a,close:w,user:g,_iframeSrc:A,_iframeHeight:m,_onIframeLoad:y}=Y();return r.useEffect(()=>{g&&e&&e(g)},[g,e]),r.useEffect(()=>{a&&i&&i(a)},[a,i]),r.useEffect(()=>{!t&&o&&o()},[t]),t?k.jsxs("div",{role:"dialog","aria-modal":"true","aria-label":"Sign in",onClick:b=>{b.target===b.currentTarget&&w()},style:{position:"fixed",inset:0,zIndex:2147483646,background:"rgba(15,15,20,0.55)",backdropFilter:"blur(6px)",WebkitBackdropFilter:"blur(6px)",display:"flex",alignItems:"center",justifyContent:"center",padding:16,animation:"rift-fade 180ms ease-out"},children:[k.jsx("style",{children:"@keyframes rift-fade { from { opacity: 0 } to { opacity: 1 } }"}),!n&&k.jsx("div",{"aria-hidden":!0,style:{position:"absolute",color:"rgba(255,255,255,0.75)",fontSize:13,fontFamily:"Inter, ui-sans-serif, system-ui, sans-serif"},children:"Loading sign-in…"}),k.jsx("iframe",{src:A,onLoad:y,title:"Rift sign-in",allow:"publickey-credentials-get; identity-credentials-get",style:{border:0,background:"transparent",colorScheme:"light",width:"100%",maxWidth:480,height:m,borderRadius:18,boxShadow:"0 24px 60px -12px rgba(0,0,0,0.35)",transition:"height 200ms ease",opacity:n?1:0}})]}):null}function ne(){const{user:e,isOpen:i,open:o,close:t,signOut:n,getAccessToken:a,error:w}=Y();return{user:e,isAuthenticated:!!e,isOpen:i,open:o,close:t,signOut:n,getAccessToken:a,error:w}}exports.RiftAuth=se;exports.RiftProvider=re;exports.useRift=ne;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("react/jsx-runtime"),r=require("react");let f=null,O=!1,M=[],A=null;const d=new Map;function z(){return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,9)}`}function Y(e){if(typeof document>"u")return Promise.reject(new Error("Cannot mount refresh iframe outside the browser"));if(f&&O)return Promise.resolve();if(A=new URL(e.widgetUrl).origin,!f){f=document.createElement("iframe"),f.setAttribute("aria-hidden","true"),f.setAttribute("tabindex","-1"),f.title="Rift session refresh",f.style.cssText="position:absolute;width:1px;height:1px;border:0;opacity:0;pointer-events:none;left:-9999px;top:-9999px;";const o=new URLSearchParams({key:e.apiKey,headless:"1"});f.src=`${e.widgetUrl.replace(/\/$/,"")}/?${o.toString()}`,document.body.appendChild(f),window.addEventListener("message",a=>{if(a.origin!==A)return;const t=a.data;if(!(!t||typeof t!="object"||typeof t.type!="string")&&t.type.startsWith("rift:")){if(t.type==="rift:ready"){O=!0,M.forEach(n=>n()),M=[];return}if(t.type==="rift:refresh-result"){const n=d.get(t.requestId);n&&(d.delete(t.requestId),n.resolve({accessToken:t.accessToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn}));return}if(t.type==="rift:refresh-error"){const n=d.get(t.requestId);n&&(d.delete(t.requestId),n.reject(new Error(t.message||"Refresh failed")));return}if(t.type==="rift:logout-result"){const n=d.get(t.requestId);n&&(d.delete(t.requestId),n.resolve({accessToken:"",expiresAt:"",expiresIn:0}))}}})}return O?Promise.resolve():new Promise(o=>{M.push(o),setTimeout(()=>{if(!O){const a=M.shift();a&&a()}},8e3)})}async function N(e){if(await Y(e),!f?.contentWindow||!A)throw new Error("Refresh iframe is not available");const o=z();return new Promise((a,t)=>{d.set(o,{resolve:a,reject:t}),f.contentWindow.postMessage({type:"rift:refresh-request",requestId:o},A),setTimeout(()=>{const n=d.get(o);n&&(d.delete(o),n.reject(new Error("Refresh timed out")))},1e4)})}async function V(e){try{if(await Y(e),!f?.contentWindow||!A)return;const o=z();await new Promise(a=>{d.set(o,{resolve:()=>a(),reject:()=>a()}),f.contentWindow.postMessage({type:"rift:logout-request",requestId:o},A),setTimeout(()=>{d.delete(o),a()},5e3)})}catch{}}const Z="https://widget.riftfi.xyz",q="rift:identity",K=60,G=r.createContext(null);function J(){const e=r.useContext(G);if(!e)throw new Error("[@rift/react] useRift() / <RiftAuth> must be used inside <RiftProvider>");return e}function ee(){if(typeof window>"u")return null;try{const e=localStorage.getItem(q);return e?JSON.parse(e):null}catch{return null}}function te(e){if(!(typeof window>"u"))try{e?localStorage.setItem(q,JSON.stringify(e)):localStorage.removeItem(q)}catch{}}function re({apiKey:e,widgetUrl:o,children:a,autoOpen:t=!1,persist:n=!0}){const c=o||Z,p=r.useMemo(()=>{try{return new URL(c).origin}catch{return c}},[c]),[y,P]=r.useState(null),[l,b]=r.useState(!1),[k,D]=r.useState("signin"),[R,E]=r.useState(!1),[S,v]=r.useState(null),[L,U]=r.useState(540),[T,C]=r.useState(0),x=r.useRef(null);x.current=y;const g=r.useRef(null),m=r.useCallback(s=>{P(s),n&&te(s?{user:s.user,address:s.address,btcAddress:s.btcAddress}:null)},[n]);r.useEffect(()=>{if(!n)return;const s=ee();if(!s)return;let u=!0;return(async()=>{try{const i=await N({apiKey:e,widgetUrl:c});if(!u)return;m({user:s.user,address:s.address,btcAddress:s.btcAddress,accessToken:i.accessToken,expiresAt:i.expiresAt})}catch{if(!u)return;m(null)}})(),()=>{u=!1}},[]);const j=r.useCallback(s=>{D(s?.mode||"signin"),v(null),E(!1),C(u=>u+1),b(!0)},[]),_=r.useCallback(()=>{b(!1),E(!1)},[]),W=r.useCallback(async()=>{await V({apiKey:e,widgetUrl:c}),m(null)},[e,c,m]),F=r.useCallback(async()=>{const s=x.current;if(!s)throw new Error("Not signed in");const u=s.expiresAt?new Date(s.expiresAt).getTime():null,i=Date.now();return!(!u||u-i<K*1e3)&&s.accessToken?s.accessToken:(g.current||(g.current=(async()=>{try{const w=await N({apiKey:e,widgetUrl:c}),B=x.current;if(!B)throw new Error("Signed out during refresh");const Q={...B,accessToken:w.accessToken,expiresAt:w.expiresAt};return m(Q),w.accessToken}catch(w){throw m(null),w instanceof Error?w:new Error(String(w))}finally{g.current=null}})()),g.current)},[e,c,m]);r.useEffect(()=>{if(typeof window>"u")return;const s=u=>{if(u.origin!==p)return;const i=u.data;if(!(!i||typeof i!="object"||typeof i.type!="string")&&i.type.startsWith("rift:"))switch(i.type){case"rift:ready":l&&E(!0);break;case"rift:close":_();break;case"rift:resize":U(Math.max(360,Math.min(820,i.height+8)));break;case"rift:signin-success":{const h={user:i.user,address:i.address,btcAddress:i.btcAddress,accessToken:i.accessToken,expiresAt:i.expiresAt};m(h),b(!1);break}case"rift:signin-error":v(i.message);break}};return window.addEventListener("message",s),()=>window.removeEventListener("message",s)},[p,_,m,l]),r.useEffect(()=>{if(!(typeof document>"u")&&l){const s=document.documentElement.style.overflow;return document.documentElement.style.overflow="hidden",()=>{document.documentElement.style.overflow=s}}},[l]),r.useEffect(()=>{t&&!y&&j()},[]);const $=r.useMemo(()=>{const s=new URLSearchParams({key:e,mode:k,origin:typeof window<"u"?window.location.origin:"",t:String(T)});if(typeof document<"u"){const u=document.documentElement,i=u.getAttribute("data-theme");let h=null;i==="dark"||i==="light"?h=i:(u.classList.contains("dark")||document.body?.classList.contains("dark")||window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches)&&(h="dark"),h&&s.set("theme",h)}return`${c.replace(/\/$/,"")}/?${s.toString()}`},[e,k,T,c]),H=r.useCallback(()=>{},[]),X=r.useMemo(()=>({apiKey:e,widgetUrl:c,user:y,isOpen:l,isReady:R,error:S,open:j,close:_,signOut:W,getAccessToken:F,_iframeSrc:$,_iframeHeight:L,_onIframeLoad:H}),[e,c,y,l,R,S,j,_,W,F,$,L,H]);return I.jsx(G.Provider,{value:X,children:a})}const se="rgba(15,15,20,0.55)",ne=6,ie=480,oe=18;function ae({onSuccess:e,onError:o,onClose:a,maxHeight:t,maxWidth:n=ie,radius:c=oe,backdrop:p,backdropStyle:y,iframeStyle:P}){const{isOpen:l,isReady:b,error:k,close:D,user:R,_iframeSrc:E,_iframeHeight:S,_onIframeLoad:v}=J();if(r.useEffect(()=>{R&&e&&e(R)},[R,e]),r.useEffect(()=>{k&&o&&o(k)},[k,o]),r.useEffect(()=>{!l&&a&&a()},[l]),!l)return null;const L=p?.color??se,U=p?.blur??ne,T=U>0?`blur(${U}px)`:void 0;let C=S,x;return typeof t=="number"?C=Math.min(S,t):typeof t=="string"&&(x=t),I.jsxs("div",{role:"dialog","aria-modal":"true","aria-label":"Sign in",onClick:g=>{g.target===g.currentTarget&&D()},style:{position:"fixed",inset:0,zIndex:2147483646,background:L,backdropFilter:T,WebkitBackdropFilter:T,display:"flex",alignItems:"center",justifyContent:"center",padding:16,animation:"rift-fade 180ms ease-out",...y},children:[I.jsx("style",{children:"@keyframes rift-fade { from { opacity: 0 } to { opacity: 1 } }"}),!b&&I.jsx("div",{"aria-hidden":!0,style:{position:"absolute",color:"rgba(255,255,255,0.75)",fontSize:13,fontFamily:"Inter, ui-sans-serif, system-ui, sans-serif"},children:"Loading sign-in…"}),I.jsx("iframe",{src:E,onLoad:v,title:"Rift sign-in",allow:"publickey-credentials-get; identity-credentials-get",style:{border:0,background:"transparent",colorScheme:"light",width:"100%",maxWidth:n,height:C,maxHeight:x,borderRadius:c,boxShadow:"0 24px 60px -12px rgba(0,0,0,0.35)",transition:"height 200ms ease",opacity:b?1:0,...P}})]})}function ce(){const{user:e,isOpen:o,open:a,close:t,signOut:n,getAccessToken:c,error:p}=J();return{user:e,isAuthenticated:!!e,isOpen:o,open:a,close:t,signOut:n,getAccessToken:c,error:p}}exports.RiftAuth=ae;exports.RiftProvider=re;exports.useRift=ce;
2
2
  //# sourceMappingURL=rift-react.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"rift-react.cjs","sources":["../src/silentRefresh.ts","../src/RiftProvider.tsx","../src/RiftAuth.tsx","../src/useRift.ts"],"sourcesContent":["/**\n * Silent-refresh bridge.\n *\n * The v2 backend session sits behind an httpOnly refresh cookie scoped\n * to the widget origin (widget.riftfi.xyz → service.riftfi.xyz). The\n * cookie cannot be read or sent from the merchant's own JS — only\n * widget-origin code can use it. So to refresh, we mount a HIDDEN\n * widget iframe in `?headless=1` mode and ask it (via postMessage) to\n * call /auth/refresh on our behalf. It posts the new access token back.\n *\n * This module owns that iframe as a singleton: we lazily create it on\n * the first refresh request, keep it alive across the page's lifetime,\n * and use a requestId-based pending map so concurrent refresh calls\n * dedupe to one network round trip.\n */\n\nlet iframe: HTMLIFrameElement | null = null;\nlet ready = false;\nlet readyResolvers: Array<() => void> = [];\nlet widgetOrigin: string | null = null;\n\ninterface Pending {\n resolve: (value: RefreshSuccess) => void;\n reject: (err: Error) => void;\n}\nconst pending = new Map<string, Pending>();\n\nexport interface RefreshSuccess {\n accessToken: string;\n expiresAt: string;\n expiresIn: number;\n}\n\nfunction uuid(): string {\n // Lightweight ID — doesn't need crypto strength, just unique per page.\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction ensureMounted(opts: { apiKey: string; widgetUrl: string }): Promise<void> {\n if (typeof document === \"undefined\") {\n return Promise.reject(new Error(\"Cannot mount refresh iframe outside the browser\"));\n }\n if (iframe && ready) return Promise.resolve();\n\n widgetOrigin = new URL(opts.widgetUrl).origin;\n\n if (!iframe) {\n iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\"aria-hidden\", \"true\");\n iframe.setAttribute(\"tabindex\", \"-1\");\n iframe.title = \"Rift session refresh\";\n iframe.style.cssText =\n \"position:absolute;width:1px;height:1px;border:0;opacity:0;pointer-events:none;left:-9999px;top:-9999px;\";\n const params = new URLSearchParams({\n key: opts.apiKey,\n headless: \"1\",\n });\n iframe.src = `${opts.widgetUrl.replace(/\\/$/, \"\")}/?${params.toString()}`;\n document.body.appendChild(iframe);\n\n window.addEventListener(\"message\", (e) => {\n if (e.origin !== widgetOrigin) return;\n const data = e.data;\n if (!data || typeof data !== \"object\" || typeof data.type !== \"string\") return;\n if (!data.type.startsWith(\"rift:\")) return;\n\n if (data.type === \"rift:ready\") {\n ready = true;\n readyResolvers.forEach((r) => r());\n readyResolvers = [];\n return;\n }\n if (data.type === \"rift:refresh-result\") {\n const slot = pending.get(data.requestId);\n if (slot) {\n pending.delete(data.requestId);\n slot.resolve({\n accessToken: data.accessToken,\n expiresAt: data.expiresAt,\n expiresIn: data.expiresIn,\n });\n }\n return;\n }\n if (data.type === \"rift:refresh-error\") {\n const slot = pending.get(data.requestId);\n if (slot) {\n pending.delete(data.requestId);\n slot.reject(new Error(data.message || \"Refresh failed\"));\n }\n return;\n }\n if (data.type === \"rift:logout-result\") {\n const slot = pending.get(data.requestId);\n if (slot) {\n pending.delete(data.requestId);\n slot.resolve({ accessToken: \"\", expiresAt: \"\", expiresIn: 0 });\n }\n }\n });\n }\n\n if (ready) return Promise.resolve();\n return new Promise((resolve) => {\n readyResolvers.push(resolve);\n // Safety net: if the iframe somehow never posts ready (e.g. blocked\n // by browser privacy mode), reject after 8s so callers can surface\n // a useful error.\n setTimeout(() => {\n if (!ready) {\n const r = readyResolvers.shift();\n if (r) r();\n }\n }, 8000);\n });\n}\n\nexport async function silentRefresh(opts: {\n apiKey: string;\n widgetUrl: string;\n}): Promise<RefreshSuccess> {\n await ensureMounted(opts);\n if (!iframe?.contentWindow || !widgetOrigin) {\n throw new Error(\"Refresh iframe is not available\");\n }\n const requestId = uuid();\n return new Promise<RefreshSuccess>((resolve, reject) => {\n pending.set(requestId, { resolve, reject });\n iframe!.contentWindow!.postMessage(\n { type: \"rift:refresh-request\", requestId },\n widgetOrigin!\n );\n setTimeout(() => {\n const slot = pending.get(requestId);\n if (slot) {\n pending.delete(requestId);\n slot.reject(new Error(\"Refresh timed out\"));\n }\n }, 10000);\n });\n}\n\nexport async function silentLogout(opts: {\n apiKey: string;\n widgetUrl: string;\n}): Promise<void> {\n try {\n await ensureMounted(opts);\n if (!iframe?.contentWindow || !widgetOrigin) return;\n const requestId = uuid();\n await new Promise<void>((resolve) => {\n pending.set(requestId, {\n resolve: () => resolve(),\n reject: () => resolve(), // logout is idempotent — never reject\n });\n iframe!.contentWindow!.postMessage(\n { type: \"rift:logout-request\", requestId },\n widgetOrigin!\n );\n setTimeout(() => {\n pending.delete(requestId);\n resolve();\n }, 5000);\n });\n } catch {\n /* logout is best-effort */\n }\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport type { RiftConfig, RiftEvent, RiftMode, RiftUser } from \"./types\";\nimport { silentLogout, silentRefresh } from \"./silentRefresh\";\n\nconst DEFAULT_WIDGET_URL = \"https://widget.riftfi.xyz\";\n\n// v2 session-mode policy: the access token lives in memory only. We\n// persist a small \"identity hint\" (user id, address, btcAddress) so the\n// UI can render an authenticated state on hard reload, but the actual\n// access JWT is re-issued via the refresh cookie. Refresh tokens live\n// in an httpOnly cookie scoped to the widget origin — totally invisible\n// to this code, which is the whole point.\nconst IDENTITY_STORAGE_KEY = \"rift:identity\";\n\ninterface PersistedIdentity {\n user: string;\n address: string;\n btcAddress?: string;\n}\n\n// Refresh proactively this many seconds before the access token expires.\n// Keeps API calls from racing the actual expiry.\nconst REFRESH_LEEWAY_SECONDS = 60;\n\ninterface RiftContextValue {\n apiKey: string;\n widgetUrl: string;\n user: RiftUser | null;\n isOpen: boolean;\n isReady: boolean;\n error: string | null;\n open: (opts?: { mode?: RiftMode }) => void;\n close: () => void;\n signOut: () => Promise<void>;\n /**\n * Returns a valid access token, refreshing silently if the current\n * one is missing or about to expire. Rejects if the user is signed\n * out or the refresh fails (in which case state is cleared and the\n * caller should prompt re-auth).\n */\n getAccessToken: () => Promise<string>;\n _iframeSrc: string;\n _iframeHeight: number;\n _onIframeLoad: () => void;\n}\n\nconst RiftContext = createContext<RiftContextValue | null>(null);\n\nexport function useRiftContext(): RiftContextValue {\n const ctx = useContext(RiftContext);\n if (!ctx) {\n throw new Error(\n \"[@rift/react] useRift() / <RiftAuth> must be used inside <RiftProvider>\"\n );\n }\n return ctx;\n}\n\ninterface RiftProviderProps extends RiftConfig {\n children: ReactNode;\n // Auto-open the modal on mount. Most apps will leave this false and call\n // open() in response to a user clicking \"Sign in\".\n autoOpen?: boolean;\n // Restore the persisted identity (just the user id / address — never\n // the access token) on mount, then silently refresh to mint a token.\n // Default: true.\n persist?: boolean;\n}\n\nfunction loadIdentity(): PersistedIdentity | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(IDENTITY_STORAGE_KEY);\n return raw ? (JSON.parse(raw) as PersistedIdentity) : null;\n } catch {\n return null;\n }\n}\n\nfunction saveIdentity(id: PersistedIdentity | null) {\n if (typeof window === \"undefined\") return;\n try {\n if (id) localStorage.setItem(IDENTITY_STORAGE_KEY, JSON.stringify(id));\n else localStorage.removeItem(IDENTITY_STORAGE_KEY);\n } catch {\n /* private mode / quota — non-fatal */\n }\n}\n\nexport function RiftProvider({\n apiKey,\n widgetUrl,\n children,\n autoOpen = false,\n persist = true,\n}: RiftProviderProps) {\n const resolvedWidgetUrl = widgetUrl || DEFAULT_WIDGET_URL;\n const widgetOrigin = useMemo(() => {\n try {\n return new URL(resolvedWidgetUrl).origin;\n } catch {\n return resolvedWidgetUrl;\n }\n }, [resolvedWidgetUrl]);\n\n const [user, setUser] = useState<RiftUser | null>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [mode, setMode] = useState<RiftMode>(\"signin\");\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [iframeHeight, setIframeHeight] = useState(540);\n const [openToken, setOpenToken] = useState(0);\n\n // Hot ref to the current user — getAccessToken() reads from this so\n // it never closes over a stale React state snapshot.\n const userRef = useRef<RiftUser | null>(null);\n userRef.current = user;\n\n // Dedupe in-flight refreshes: if multiple API calls hit\n // getAccessToken() simultaneously and the token is stale, we only\n // want one network call.\n const refreshInFlight = useRef<Promise<string> | null>(null);\n\n const setAndPersist = useCallback(\n (next: RiftUser | null) => {\n setUser(next);\n if (persist) {\n saveIdentity(\n next\n ? {\n user: next.user,\n address: next.address,\n btcAddress: next.btcAddress,\n }\n : null\n );\n }\n },\n [persist]\n );\n\n // On mount, if we have a persisted identity, try a silent refresh to\n // rehydrate the access token. If it fails, drop the identity — the\n // user will be prompted to sign in again on first action.\n useEffect(() => {\n if (!persist) return;\n const identity = loadIdentity();\n if (!identity) return;\n let alive = true;\n (async () => {\n try {\n const result = await silentRefresh({\n apiKey,\n widgetUrl: resolvedWidgetUrl,\n });\n if (!alive) return;\n setAndPersist({\n user: identity.user,\n address: identity.address,\n btcAddress: identity.btcAddress,\n accessToken: result.accessToken,\n expiresAt: result.expiresAt,\n });\n } catch {\n if (!alive) return;\n // Refresh failed — likely cookie expired or revoked. Clear the\n // identity hint so the UI shows the signed-out state.\n setAndPersist(null);\n }\n })();\n return () => {\n alive = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const open = useCallback((opts?: { mode?: RiftMode }) => {\n setMode(opts?.mode || \"signin\");\n setError(null);\n setIsReady(false);\n setOpenToken((t) => t + 1);\n setIsOpen(true);\n }, []);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setIsReady(false);\n }, []);\n\n const signOut = useCallback(async () => {\n await silentLogout({ apiKey, widgetUrl: resolvedWidgetUrl });\n setAndPersist(null);\n }, [apiKey, resolvedWidgetUrl, setAndPersist]);\n\n const getAccessToken = useCallback(async (): Promise<string> => {\n const current = userRef.current;\n if (!current) throw new Error(\"Not signed in\");\n\n const expiresAt = current.expiresAt\n ? new Date(current.expiresAt).getTime()\n : null;\n const now = Date.now();\n const needsRefresh =\n !expiresAt || expiresAt - now < REFRESH_LEEWAY_SECONDS * 1000;\n\n if (!needsRefresh && current.accessToken) {\n return current.accessToken;\n }\n\n if (refreshInFlight.current) {\n return refreshInFlight.current;\n }\n\n refreshInFlight.current = (async () => {\n try {\n const result = await silentRefresh({\n apiKey,\n widgetUrl: resolvedWidgetUrl,\n });\n const latest = userRef.current;\n if (!latest) throw new Error(\"Signed out during refresh\");\n const next: RiftUser = {\n ...latest,\n accessToken: result.accessToken,\n expiresAt: result.expiresAt,\n };\n setAndPersist(next);\n return result.accessToken;\n } catch (err: any) {\n // Refresh failed — wipe state so the host UI can prompt re-auth.\n setAndPersist(null);\n throw err instanceof Error ? err : new Error(String(err));\n } finally {\n refreshInFlight.current = null;\n }\n })();\n return refreshInFlight.current;\n }, [apiKey, resolvedWidgetUrl, setAndPersist]);\n\n // Listen for messages from the VISIBLE login iframe (not the silent\n // refresh one — that one's events are handled inside silentRefresh.ts).\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handler = (e: MessageEvent) => {\n if (e.origin !== widgetOrigin) return;\n const data = e.data as RiftEvent | undefined;\n if (!data || typeof data !== \"object\" || typeof data.type !== \"string\") return;\n if (!data.type.startsWith(\"rift:\")) return;\n\n switch (data.type) {\n case \"rift:ready\":\n // Only treat as \"modal ready\" while it's open — the silent\n // refresh iframe also emits ready, but we don't care here.\n if (isOpen) setIsReady(true);\n break;\n case \"rift:close\":\n close();\n break;\n case \"rift:resize\":\n setIframeHeight(Math.max(360, Math.min(820, data.height + 8)));\n break;\n case \"rift:signin-success\": {\n const next: RiftUser = {\n user: data.user,\n address: data.address,\n btcAddress: data.btcAddress,\n accessToken: data.accessToken,\n expiresAt: data.expiresAt,\n };\n setAndPersist(next);\n setIsOpen(false);\n break;\n }\n case \"rift:signin-error\":\n setError(data.message);\n break;\n // refresh / logout result events belong to silentRefresh.ts —\n // ignore them here.\n }\n };\n window.addEventListener(\"message\", handler);\n return () => window.removeEventListener(\"message\", handler);\n }, [widgetOrigin, close, setAndPersist, isOpen]);\n\n // Lock host page scroll while the modal is open.\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n if (isOpen) {\n const prev = document.documentElement.style.overflow;\n document.documentElement.style.overflow = \"hidden\";\n return () => {\n document.documentElement.style.overflow = prev;\n };\n }\n }, [isOpen]);\n\n useEffect(() => {\n if (autoOpen && !user) open();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const iframeSrc = useMemo(() => {\n const params = new URLSearchParams({\n key: apiKey,\n mode,\n origin: typeof window !== \"undefined\" ? window.location.origin : \"\",\n t: String(openToken),\n });\n return `${resolvedWidgetUrl.replace(/\\/$/, \"\")}/?${params.toString()}`;\n }, [apiKey, mode, openToken, resolvedWidgetUrl]);\n\n const onIframeLoad = useCallback(() => {\n /* readiness is signalled via postMessage, not the load event */\n }, []);\n\n const value = useMemo<RiftContextValue>(\n () => ({\n apiKey,\n widgetUrl: resolvedWidgetUrl,\n user,\n isOpen,\n isReady,\n error,\n open,\n close,\n signOut,\n getAccessToken,\n _iframeSrc: iframeSrc,\n _iframeHeight: iframeHeight,\n _onIframeLoad: onIframeLoad,\n }),\n [\n apiKey,\n resolvedWidgetUrl,\n user,\n isOpen,\n isReady,\n error,\n open,\n close,\n signOut,\n getAccessToken,\n iframeSrc,\n iframeHeight,\n onIframeLoad,\n ]\n );\n\n return <RiftContext.Provider value={value}>{children}</RiftContext.Provider>;\n}\n","import { useEffect } from \"react\";\nimport { useRiftContext } from \"./RiftProvider\";\nimport type { RiftUser } from \"./types\";\n\ninterface RiftAuthProps {\n // Optional event hooks so callers don't have to compose useEffect by hand.\n onSuccess?: (user: RiftUser) => void;\n onError?: (message: string) => void;\n onClose?: () => void;\n}\n\n/**\n * Renders the modal backdrop + iframe whenever the provider's `isOpen` is\n * true. Place this once near the root of your app (typically just inside\n * <RiftProvider>); call `useRift().open()` to show it.\n */\nexport function RiftAuth({ onSuccess, onError, onClose }: RiftAuthProps) {\n const {\n isOpen,\n isReady,\n error,\n close,\n user,\n _iframeSrc,\n _iframeHeight,\n _onIframeLoad,\n } = useRiftContext();\n\n useEffect(() => {\n if (user && onSuccess) onSuccess(user);\n }, [user, onSuccess]);\n\n useEffect(() => {\n if (error && onError) onError(error);\n }, [error, onError]);\n\n useEffect(() => {\n if (!isOpen && onClose) onClose();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen]);\n\n if (!isOpen) return null;\n\n return (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Sign in\"\n onClick={(e) => {\n if (e.target === e.currentTarget) close();\n }}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483646,\n background: \"rgba(15,15,20,0.55)\",\n backdropFilter: \"blur(6px)\",\n WebkitBackdropFilter: \"blur(6px)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: 16,\n animation: \"rift-fade 180ms ease-out\",\n }}\n >\n <style>{`@keyframes rift-fade { from { opacity: 0 } to { opacity: 1 } }`}</style>\n {!isReady && (\n <div\n aria-hidden\n style={{\n position: \"absolute\",\n color: \"rgba(255,255,255,0.75)\",\n fontSize: 13,\n fontFamily:\n \"Inter, ui-sans-serif, system-ui, sans-serif\",\n }}\n >\n Loading sign-in…\n </div>\n )}\n <iframe\n src={_iframeSrc}\n onLoad={_onIframeLoad}\n title=\"Rift sign-in\"\n allow=\"publickey-credentials-get; identity-credentials-get\"\n style={{\n border: 0,\n background: \"transparent\",\n colorScheme: \"light\",\n width: \"100%\",\n maxWidth: 480,\n height: _iframeHeight,\n borderRadius: 18,\n boxShadow: \"0 24px 60px -12px rgba(0,0,0,0.35)\",\n transition: \"height 200ms ease\",\n opacity: isReady ? 1 : 0,\n }}\n />\n </div>\n );\n}\n","import { useRiftContext } from \"./RiftProvider\";\nimport type { RiftMode, RiftUser } from \"./types\";\n\ninterface UseRiftReturn {\n user: RiftUser | null;\n isAuthenticated: boolean;\n isOpen: boolean;\n open: (opts?: { mode?: RiftMode }) => void;\n close: () => void;\n signOut: () => Promise<void>;\n /**\n * Async getter for a valid access token. Use this when calling Rift /\n * your backend — it returns the current token if fresh, or silently\n * refreshes via a hidden iframe if near expiry. Rejects when the user\n * isn't signed in or the refresh fails (in which case auth state is\n * cleared and the host should prompt re-auth).\n */\n getAccessToken: () => Promise<string>;\n error: string | null;\n}\n\n/**\n * Read auth state and drive the widget from anywhere inside <RiftProvider>.\n *\n * const { user, isAuthenticated, open, signOut, getAccessToken } = useRift();\n * return isAuthenticated\n * ? <button onClick={signOut}>Sign out</button>\n * : <button onClick={() => open({ mode: 'signup' })}>Get started</button>;\n *\n * // When calling your backend with Rift's session JWT:\n * const token = await getAccessToken();\n * fetch('/api/my-thing', { headers: { Authorization: `Bearer ${token}` } });\n */\nexport function useRift(): UseRiftReturn {\n const { user, isOpen, open, close, signOut, getAccessToken, error } =\n useRiftContext();\n return {\n user,\n isAuthenticated: !!user,\n isOpen,\n open,\n close,\n signOut,\n getAccessToken,\n error,\n };\n}\n"],"names":["iframe","ready","readyResolvers","widgetOrigin","pending","uuid","ensureMounted","opts","params","e","data","r","slot","resolve","silentRefresh","requestId","reject","silentLogout","DEFAULT_WIDGET_URL","IDENTITY_STORAGE_KEY","REFRESH_LEEWAY_SECONDS","RiftContext","createContext","useRiftContext","ctx","useContext","loadIdentity","raw","saveIdentity","id","RiftProvider","apiKey","widgetUrl","children","autoOpen","persist","resolvedWidgetUrl","useMemo","user","setUser","useState","isOpen","setIsOpen","mode","setMode","isReady","setIsReady","error","setError","iframeHeight","setIframeHeight","openToken","setOpenToken","userRef","useRef","refreshInFlight","setAndPersist","useCallback","next","useEffect","identity","alive","result","open","t","close","signOut","getAccessToken","current","expiresAt","now","latest","err","handler","prev","iframeSrc","onIframeLoad","value","jsx","RiftAuth","onSuccess","onError","onClose","_iframeSrc","_iframeHeight","_onIframeLoad","jsxs","useRift"],"mappings":"wIAgBA,IAAIA,EAAmC,KACnCC,EAAQ,GACRC,EAAoC,CAAA,EACpCC,EAA8B,KAMlC,MAAMC,MAAc,IAQpB,SAASC,GAAe,CAEtB,MAAO,GAAG,KAAK,IAAA,EAAM,SAAS,EAAE,CAAC,IAAI,KAAK,OAAA,EAAS,SAAS,EAAE,EAAE,MAAM,EAAG,CAAC,CAAC,EAC7E,CAEA,SAASC,EAAcC,EAA4D,CACjF,GAAI,OAAO,SAAa,IACtB,OAAO,QAAQ,OAAO,IAAI,MAAM,iDAAiD,CAAC,EAEpF,GAAIP,GAAUC,EAAO,OAAO,QAAQ,QAAA,EAIpC,GAFAE,EAAe,IAAI,IAAII,EAAK,SAAS,EAAE,OAEnC,CAACP,EAAQ,CACXA,EAAS,SAAS,cAAc,QAAQ,EACxCA,EAAO,aAAa,cAAe,MAAM,EACzCA,EAAO,aAAa,WAAY,IAAI,EACpCA,EAAO,MAAQ,uBACfA,EAAO,MAAM,QACX,0GACF,MAAMQ,EAAS,IAAI,gBAAgB,CACjC,IAAKD,EAAK,OACV,SAAU,GAAA,CACX,EACDP,EAAO,IAAM,GAAGO,EAAK,UAAU,QAAQ,MAAO,EAAE,CAAC,KAAKC,EAAO,SAAA,CAAU,GACvE,SAAS,KAAK,YAAYR,CAAM,EAEhC,OAAO,iBAAiB,UAAYS,GAAM,CACxC,GAAIA,EAAE,SAAWN,EAAc,OAC/B,MAAMO,EAAOD,EAAE,KACf,GAAI,GAACC,GAAQ,OAAOA,GAAS,UAAY,OAAOA,EAAK,MAAS,WACzDA,EAAK,KAAK,WAAW,OAAO,EAEjC,IAAIA,EAAK,OAAS,aAAc,CAC9BT,EAAQ,GACRC,EAAe,QAASS,GAAMA,EAAA,CAAG,EACjCT,EAAiB,CAAA,EACjB,MACF,CACA,GAAIQ,EAAK,OAAS,sBAAuB,CACvC,MAAME,EAAOR,EAAQ,IAAIM,EAAK,SAAS,EACnCE,IACFR,EAAQ,OAAOM,EAAK,SAAS,EAC7BE,EAAK,QAAQ,CACX,YAAaF,EAAK,YAClB,UAAWA,EAAK,UAChB,UAAWA,EAAK,SAAA,CACjB,GAEH,MACF,CACA,GAAIA,EAAK,OAAS,qBAAsB,CACtC,MAAME,EAAOR,EAAQ,IAAIM,EAAK,SAAS,EACnCE,IACFR,EAAQ,OAAOM,EAAK,SAAS,EAC7BE,EAAK,OAAO,IAAI,MAAMF,EAAK,SAAW,gBAAgB,CAAC,GAEzD,MACF,CACA,GAAIA,EAAK,OAAS,qBAAsB,CACtC,MAAME,EAAOR,EAAQ,IAAIM,EAAK,SAAS,EACnCE,IACFR,EAAQ,OAAOM,EAAK,SAAS,EAC7BE,EAAK,QAAQ,CAAE,YAAa,GAAI,UAAW,GAAI,UAAW,EAAG,EAEjE,EACF,CAAC,CACH,CAEA,OAAIX,EAAc,QAAQ,QAAA,EACnB,IAAI,QAASY,GAAY,CAC9BX,EAAe,KAAKW,CAAO,EAI3B,WAAW,IAAM,CACf,GAAI,CAACZ,EAAO,CACV,MAAMU,EAAIT,EAAe,MAAA,EACrBS,GAAGA,EAAA,CACT,CACF,EAAG,GAAI,CACT,CAAC,CACH,CAEA,eAAsBG,EAAcP,EAGR,CAE1B,GADA,MAAMD,EAAcC,CAAI,EACpB,CAACP,GAAQ,eAAiB,CAACG,EAC7B,MAAM,IAAI,MAAM,iCAAiC,EAEnD,MAAMY,EAAYV,EAAA,EAClB,OAAO,IAAI,QAAwB,CAACQ,EAASG,IAAW,CACtDZ,EAAQ,IAAIW,EAAW,CAAE,QAAAF,EAAS,OAAAG,EAAQ,EAC1ChB,EAAQ,cAAe,YACrB,CAAE,KAAM,uBAAwB,UAAAe,CAAA,EAChCZ,CAAA,EAEF,WAAW,IAAM,CACf,MAAMS,EAAOR,EAAQ,IAAIW,CAAS,EAC9BH,IACFR,EAAQ,OAAOW,CAAS,EACxBH,EAAK,OAAO,IAAI,MAAM,mBAAmB,CAAC,EAE9C,EAAG,GAAK,CACV,CAAC,CACH,CAEA,eAAsBK,EAAaV,EAGjB,CAChB,GAAI,CAEF,GADA,MAAMD,EAAcC,CAAI,EACpB,CAACP,GAAQ,eAAiB,CAACG,EAAc,OAC7C,MAAMY,EAAYV,EAAA,EAClB,MAAM,IAAI,QAAeQ,GAAY,CACnCT,EAAQ,IAAIW,EAAW,CACrB,QAAS,IAAMF,EAAA,EACf,OAAQ,IAAMA,EAAA,CAAQ,CACvB,EACDb,EAAQ,cAAe,YACrB,CAAE,KAAM,sBAAuB,UAAAe,CAAA,EAC/BZ,CAAA,EAEF,WAAW,IAAM,CACfC,EAAQ,OAAOW,CAAS,EACxBF,EAAA,CACF,EAAG,GAAI,CACT,CAAC,CACH,MAAQ,CAER,CACF,CC1JA,MAAMK,EAAqB,4BAQrBC,EAAuB,gBAUvBC,EAAyB,GAwBzBC,EAAcC,EAAAA,cAAuC,IAAI,EAExD,SAASC,GAAmC,CACjD,MAAMC,EAAMC,EAAAA,WAAWJ,CAAW,EAClC,GAAI,CAACG,EACH,MAAM,IAAI,MACR,yEAAA,EAGJ,OAAOA,CACT,CAaA,SAASE,IAAyC,CAChD,GAAI,OAAO,OAAW,IAAa,OAAO,KAC1C,GAAI,CACF,MAAMC,EAAM,aAAa,QAAQR,CAAoB,EACrD,OAAOQ,EAAO,KAAK,MAAMA,CAAG,EAA0B,IACxD,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAASC,GAAaC,EAA8B,CAClD,GAAI,SAAO,OAAW,KACtB,GAAI,CACEA,EAAI,aAAa,QAAQV,EAAsB,KAAK,UAAUU,CAAE,CAAC,EAChE,aAAa,WAAWV,CAAoB,CACnD,MAAQ,CAER,CACF,CAEO,SAASW,GAAa,CAC3B,OAAAC,EACA,UAAAC,EACA,SAAAC,EACA,SAAAC,EAAW,GACX,QAAAC,EAAU,EACZ,EAAsB,CACpB,MAAMC,EAAoBJ,GAAad,EACjCf,EAAekC,EAAAA,QAAQ,IAAM,CACjC,GAAI,CACF,OAAO,IAAI,IAAID,CAAiB,EAAE,MACpC,MAAQ,CACN,OAAOA,CACT,CACF,EAAG,CAACA,CAAiB,CAAC,EAEhB,CAACE,EAAMC,CAAO,EAAIC,EAAAA,SAA0B,IAAI,EAChD,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAS,EAAK,EACpC,CAACG,EAAMC,CAAO,EAAIJ,EAAAA,SAAmB,QAAQ,EAC7C,CAACK,EAASC,CAAU,EAAIN,EAAAA,SAAS,EAAK,EACtC,CAACO,EAAOC,CAAQ,EAAIR,EAAAA,SAAwB,IAAI,EAChD,CAACS,EAAcC,CAAe,EAAIV,EAAAA,SAAS,GAAG,EAC9C,CAACW,EAAWC,CAAY,EAAIZ,EAAAA,SAAS,CAAC,EAItCa,EAAUC,EAAAA,OAAwB,IAAI,EAC5CD,EAAQ,QAAUf,EAKlB,MAAMiB,EAAkBD,EAAAA,OAA+B,IAAI,EAErDE,EAAgBC,EAAAA,YACnBC,GAA0B,CACzBnB,EAAQmB,CAAI,EACRvB,GACFP,GACE8B,EACI,CACE,KAAMA,EAAK,KACX,QAASA,EAAK,QACd,WAAYA,EAAK,UAAA,EAEnB,IAAA,CAGV,EACA,CAACvB,CAAO,CAAA,EAMVwB,EAAAA,UAAU,IAAM,CACd,GAAI,CAACxB,EAAS,OACd,MAAMyB,EAAWlC,GAAA,EACjB,GAAI,CAACkC,EAAU,OACf,IAAIC,EAAQ,GACZ,OAAC,SAAY,CACX,GAAI,CACF,MAAMC,EAAS,MAAMhD,EAAc,CACjC,OAAAiB,EACA,UAAWK,CAAA,CACZ,EACD,GAAI,CAACyB,EAAO,OACZL,EAAc,CACZ,KAAMI,EAAS,KACf,QAASA,EAAS,QAClB,WAAYA,EAAS,WACrB,YAAaE,EAAO,YACpB,UAAWA,EAAO,SAAA,CACnB,CACH,MAAQ,CACN,GAAI,CAACD,EAAO,OAGZL,EAAc,IAAI,CACpB,CACF,GAAA,EACO,IAAM,CACXK,EAAQ,EACV,CAEF,EAAG,CAAA,CAAE,EAEL,MAAME,EAAON,cAAalD,GAA+B,CACvDqC,EAAQrC,GAAM,MAAQ,QAAQ,EAC9ByC,EAAS,IAAI,EACbF,EAAW,EAAK,EAChBM,EAAcY,GAAMA,EAAI,CAAC,EACzBtB,EAAU,EAAI,CAChB,EAAG,CAAA,CAAE,EAECuB,EAAQR,EAAAA,YAAY,IAAM,CAC9Bf,EAAU,EAAK,EACfI,EAAW,EAAK,CAClB,EAAG,CAAA,CAAE,EAECoB,EAAUT,EAAAA,YAAY,SAAY,CACtC,MAAMxC,EAAa,CAAE,OAAAc,EAAQ,UAAWK,EAAmB,EAC3DoB,EAAc,IAAI,CACpB,EAAG,CAACzB,EAAQK,EAAmBoB,CAAa,CAAC,EAEvCW,EAAiBV,EAAAA,YAAY,SAA6B,CAC9D,MAAMW,EAAUf,EAAQ,QACxB,GAAI,CAACe,EAAS,MAAM,IAAI,MAAM,eAAe,EAE7C,MAAMC,EAAYD,EAAQ,UACtB,IAAI,KAAKA,EAAQ,SAAS,EAAE,QAAA,EAC5B,KACEE,EAAM,KAAK,IAAA,EAIjB,MAAI,EAFF,CAACD,GAAaA,EAAYC,EAAMlD,EAAyB,MAEtCgD,EAAQ,YACpBA,EAAQ,aAGbb,EAAgB,UAIpBA,EAAgB,SAAW,SAAY,CACrC,GAAI,CACF,MAAMO,EAAS,MAAMhD,EAAc,CACjC,OAAAiB,EACA,UAAWK,CAAA,CACZ,EACKmC,EAASlB,EAAQ,QACvB,GAAI,CAACkB,EAAQ,MAAM,IAAI,MAAM,2BAA2B,EACxD,MAAMb,EAAiB,CACrB,GAAGa,EACH,YAAaT,EAAO,YACpB,UAAWA,EAAO,SAAA,EAEpB,OAAAN,EAAcE,CAAI,EACXI,EAAO,WAChB,OAASU,EAAU,CAEjB,MAAAhB,EAAc,IAAI,EACZgB,aAAe,MAAQA,EAAM,IAAI,MAAM,OAAOA,CAAG,CAAC,CAC1D,QAAA,CACEjB,EAAgB,QAAU,IAC5B,CACF,GAAA,GACOA,EAAgB,QACzB,EAAG,CAACxB,EAAQK,EAAmBoB,CAAa,CAAC,EAI7CG,EAAAA,UAAU,IAAM,CACd,GAAI,OAAO,OAAW,IAAa,OACnC,MAAMc,EAAWhE,GAAoB,CACnC,GAAIA,EAAE,SAAWN,EAAc,OAC/B,MAAMO,EAAOD,EAAE,KACf,GAAI,GAACC,GAAQ,OAAOA,GAAS,UAAY,OAAOA,EAAK,MAAS,WACzDA,EAAK,KAAK,WAAW,OAAO,EAEjC,OAAQA,EAAK,KAAA,CACX,IAAK,aAGC+B,KAAmB,EAAI,EAC3B,MACF,IAAK,aACHwB,EAAA,EACA,MACF,IAAK,cACHf,EAAgB,KAAK,IAAI,IAAK,KAAK,IAAI,IAAKxC,EAAK,OAAS,CAAC,CAAC,CAAC,EAC7D,MACF,IAAK,sBAAuB,CAC1B,MAAMgD,EAAiB,CACrB,KAAMhD,EAAK,KACX,QAASA,EAAK,QACd,WAAYA,EAAK,WACjB,YAAaA,EAAK,YAClB,UAAWA,EAAK,SAAA,EAElB8C,EAAcE,CAAI,EAClBhB,EAAU,EAAK,EACf,KACF,CACA,IAAK,oBACHM,EAAStC,EAAK,OAAO,EACrB,KAAA,CAIN,EACA,cAAO,iBAAiB,UAAW+D,CAAO,EACnC,IAAM,OAAO,oBAAoB,UAAWA,CAAO,CAC5D,EAAG,CAACtE,EAAc8D,EAAOT,EAAef,CAAM,CAAC,EAG/CkB,EAAAA,UAAU,IAAM,CACd,GAAI,SAAO,SAAa,MACpBlB,EAAQ,CACV,MAAMiC,EAAO,SAAS,gBAAgB,MAAM,SAC5C,gBAAS,gBAAgB,MAAM,SAAW,SACnC,IAAM,CACX,SAAS,gBAAgB,MAAM,SAAWA,CAC5C,CACF,CACF,EAAG,CAACjC,CAAM,CAAC,EAEXkB,EAAAA,UAAU,IAAM,CACVzB,GAAY,CAACI,GAAMyB,EAAA,CAEzB,EAAG,CAAA,CAAE,EAEL,MAAMY,EAAYtC,EAAAA,QAAQ,IAAM,CAC9B,MAAM7B,EAAS,IAAI,gBAAgB,CACjC,IAAKuB,EACL,KAAAY,EACA,OAAQ,OAAO,OAAW,IAAc,OAAO,SAAS,OAAS,GACjE,EAAG,OAAOQ,CAAS,CAAA,CACpB,EACD,MAAO,GAAGf,EAAkB,QAAQ,MAAO,EAAE,CAAC,KAAK5B,EAAO,SAAA,CAAU,EACtE,EAAG,CAACuB,EAAQY,EAAMQ,EAAWf,CAAiB,CAAC,EAEzCwC,EAAenB,EAAAA,YAAY,IAAM,CAEvC,EAAG,CAAA,CAAE,EAECoB,EAAQxC,EAAAA,QACZ,KAAO,CACL,OAAAN,EACA,UAAWK,EACX,KAAAE,EACA,OAAAG,EACA,QAAAI,EACA,MAAAE,EACA,KAAAgB,EACA,MAAAE,EACA,QAAAC,EACA,eAAAC,EACA,WAAYQ,EACZ,cAAe1B,EACf,cAAe2B,CAAA,GAEjB,CACE7C,EACAK,EACAE,EACAG,EACAI,EACAE,EACAgB,EACAE,EACAC,EACAC,EACAQ,EACA1B,EACA2B,CAAA,CACF,EAGF,OAAOE,EAAAA,IAACzD,EAAY,SAAZ,CAAqB,MAAAwD,EAAe,SAAA5C,CAAA,CAAS,CACvD,CCtVO,SAAS8C,GAAS,CAAE,UAAAC,EAAW,QAAAC,EAAS,QAAAC,GAA0B,CACvE,KAAM,CACJ,OAAAzC,EACA,QAAAI,EACA,MAAAE,EACA,MAAAkB,EACA,KAAA3B,EACA,WAAA6C,EACA,cAAAC,EACA,cAAAC,CAAA,EACE9D,EAAA,EAeJ,OAbAoC,EAAAA,UAAU,IAAM,CACVrB,GAAQ0C,GAAWA,EAAU1C,CAAI,CACvC,EAAG,CAACA,EAAM0C,CAAS,CAAC,EAEpBrB,EAAAA,UAAU,IAAM,CACVZ,GAASkC,GAASA,EAAQlC,CAAK,CACrC,EAAG,CAACA,EAAOkC,CAAO,CAAC,EAEnBtB,EAAAA,UAAU,IAAM,CACV,CAAClB,GAAUyC,GAASA,EAAA,CAE1B,EAAG,CAACzC,CAAM,CAAC,EAENA,EAGH6C,EAAAA,KAAC,MAAA,CACC,KAAK,SACL,aAAW,OACX,aAAW,UACX,QAAU7E,GAAM,CACVA,EAAE,SAAWA,EAAE,eAAewD,EAAA,CACpC,EACA,MAAO,CACL,SAAU,QACV,MAAO,EACP,OAAQ,WACR,WAAY,sBACZ,eAAgB,YAChB,qBAAsB,YACtB,QAAS,OACT,WAAY,SACZ,eAAgB,SAChB,QAAS,GACT,UAAW,0BAAA,EAGb,SAAA,CAAAa,EAAAA,IAAC,SAAO,SAAA,gEAAA,CAAiE,EACxE,CAACjC,GACAiC,EAAAA,IAAC,MAAA,CACC,cAAW,GACX,MAAO,CACL,SAAU,WACV,MAAO,yBACP,SAAU,GACV,WACE,6CAAA,EAEL,SAAA,kBAAA,CAAA,EAIHA,EAAAA,IAAC,SAAA,CACC,IAAKK,EACL,OAAQE,EACR,MAAM,eACN,MAAM,sDACN,MAAO,CACL,OAAQ,EACR,WAAY,cACZ,YAAa,QACb,MAAO,OACP,SAAU,IACV,OAAQD,EACR,aAAc,GACd,UAAW,qCACX,WAAY,oBACZ,QAASvC,EAAU,EAAI,CAAA,CACzB,CAAA,CACF,CAAA,CAAA,EAxDgB,IA2DtB,CCnEO,SAAS0C,IAAyB,CACvC,KAAM,CAAE,KAAAjD,EAAM,OAAAG,EAAQ,KAAAsB,EAAM,MAAAE,EAAO,QAAAC,EAAS,eAAAC,EAAgB,MAAApB,CAAA,EAC1DxB,EAAA,EACF,MAAO,CACL,KAAAe,EACA,gBAAiB,CAAC,CAACA,EACnB,OAAAG,EACA,KAAAsB,EACA,MAAAE,EACA,QAAAC,EACA,eAAAC,EACA,MAAApB,CAAA,CAEJ"}
1
+ {"version":3,"file":"rift-react.cjs","sources":["../src/silentRefresh.ts","../src/RiftProvider.tsx","../src/RiftAuth.tsx","../src/useRift.ts"],"sourcesContent":["/**\n * Silent-refresh bridge.\n *\n * The v2 backend session sits behind an httpOnly refresh cookie scoped\n * to the widget origin (widget.riftfi.xyz → service.riftfi.xyz). The\n * cookie cannot be read or sent from the merchant's own JS — only\n * widget-origin code can use it. So to refresh, we mount a HIDDEN\n * widget iframe in `?headless=1` mode and ask it (via postMessage) to\n * call /auth/refresh on our behalf. It posts the new access token back.\n *\n * This module owns that iframe as a singleton: we lazily create it on\n * the first refresh request, keep it alive across the page's lifetime,\n * and use a requestId-based pending map so concurrent refresh calls\n * dedupe to one network round trip.\n */\n\nlet iframe: HTMLIFrameElement | null = null;\nlet ready = false;\nlet readyResolvers: Array<() => void> = [];\nlet widgetOrigin: string | null = null;\n\ninterface Pending {\n resolve: (value: RefreshSuccess) => void;\n reject: (err: Error) => void;\n}\nconst pending = new Map<string, Pending>();\n\nexport interface RefreshSuccess {\n accessToken: string;\n expiresAt: string;\n expiresIn: number;\n}\n\nfunction uuid(): string {\n // Lightweight ID — doesn't need crypto strength, just unique per page.\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction ensureMounted(opts: { apiKey: string; widgetUrl: string }): Promise<void> {\n if (typeof document === \"undefined\") {\n return Promise.reject(new Error(\"Cannot mount refresh iframe outside the browser\"));\n }\n if (iframe && ready) return Promise.resolve();\n\n widgetOrigin = new URL(opts.widgetUrl).origin;\n\n if (!iframe) {\n iframe = document.createElement(\"iframe\");\n iframe.setAttribute(\"aria-hidden\", \"true\");\n iframe.setAttribute(\"tabindex\", \"-1\");\n iframe.title = \"Rift session refresh\";\n iframe.style.cssText =\n \"position:absolute;width:1px;height:1px;border:0;opacity:0;pointer-events:none;left:-9999px;top:-9999px;\";\n const params = new URLSearchParams({\n key: opts.apiKey,\n headless: \"1\",\n });\n iframe.src = `${opts.widgetUrl.replace(/\\/$/, \"\")}/?${params.toString()}`;\n document.body.appendChild(iframe);\n\n window.addEventListener(\"message\", (e) => {\n if (e.origin !== widgetOrigin) return;\n const data = e.data;\n if (!data || typeof data !== \"object\" || typeof data.type !== \"string\") return;\n if (!data.type.startsWith(\"rift:\")) return;\n\n if (data.type === \"rift:ready\") {\n ready = true;\n readyResolvers.forEach((r) => r());\n readyResolvers = [];\n return;\n }\n if (data.type === \"rift:refresh-result\") {\n const slot = pending.get(data.requestId);\n if (slot) {\n pending.delete(data.requestId);\n slot.resolve({\n accessToken: data.accessToken,\n expiresAt: data.expiresAt,\n expiresIn: data.expiresIn,\n });\n }\n return;\n }\n if (data.type === \"rift:refresh-error\") {\n const slot = pending.get(data.requestId);\n if (slot) {\n pending.delete(data.requestId);\n slot.reject(new Error(data.message || \"Refresh failed\"));\n }\n return;\n }\n if (data.type === \"rift:logout-result\") {\n const slot = pending.get(data.requestId);\n if (slot) {\n pending.delete(data.requestId);\n slot.resolve({ accessToken: \"\", expiresAt: \"\", expiresIn: 0 });\n }\n }\n });\n }\n\n if (ready) return Promise.resolve();\n return new Promise((resolve) => {\n readyResolvers.push(resolve);\n // Safety net: if the iframe somehow never posts ready (e.g. blocked\n // by browser privacy mode), reject after 8s so callers can surface\n // a useful error.\n setTimeout(() => {\n if (!ready) {\n const r = readyResolvers.shift();\n if (r) r();\n }\n }, 8000);\n });\n}\n\nexport async function silentRefresh(opts: {\n apiKey: string;\n widgetUrl: string;\n}): Promise<RefreshSuccess> {\n await ensureMounted(opts);\n if (!iframe?.contentWindow || !widgetOrigin) {\n throw new Error(\"Refresh iframe is not available\");\n }\n const requestId = uuid();\n return new Promise<RefreshSuccess>((resolve, reject) => {\n pending.set(requestId, { resolve, reject });\n iframe!.contentWindow!.postMessage(\n { type: \"rift:refresh-request\", requestId },\n widgetOrigin!\n );\n setTimeout(() => {\n const slot = pending.get(requestId);\n if (slot) {\n pending.delete(requestId);\n slot.reject(new Error(\"Refresh timed out\"));\n }\n }, 10000);\n });\n}\n\nexport async function silentLogout(opts: {\n apiKey: string;\n widgetUrl: string;\n}): Promise<void> {\n try {\n await ensureMounted(opts);\n if (!iframe?.contentWindow || !widgetOrigin) return;\n const requestId = uuid();\n await new Promise<void>((resolve) => {\n pending.set(requestId, {\n resolve: () => resolve(),\n reject: () => resolve(), // logout is idempotent — never reject\n });\n iframe!.contentWindow!.postMessage(\n { type: \"rift:logout-request\", requestId },\n widgetOrigin!\n );\n setTimeout(() => {\n pending.delete(requestId);\n resolve();\n }, 5000);\n });\n } catch {\n /* logout is best-effort */\n }\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport type { RiftConfig, RiftEvent, RiftMode, RiftUser } from \"./types\";\nimport { silentLogout, silentRefresh } from \"./silentRefresh\";\n\nconst DEFAULT_WIDGET_URL = \"https://widget.riftfi.xyz\";\n\n// v2 session-mode policy: the access token lives in memory only. We\n// persist a small \"identity hint\" (user id, address, btcAddress) so the\n// UI can render an authenticated state on hard reload, but the actual\n// access JWT is re-issued via the refresh cookie. Refresh tokens live\n// in an httpOnly cookie scoped to the widget origin — totally invisible\n// to this code, which is the whole point.\nconst IDENTITY_STORAGE_KEY = \"rift:identity\";\n\ninterface PersistedIdentity {\n user: string;\n address: string;\n btcAddress?: string;\n}\n\n// Refresh proactively this many seconds before the access token expires.\n// Keeps API calls from racing the actual expiry.\nconst REFRESH_LEEWAY_SECONDS = 60;\n\ninterface RiftContextValue {\n apiKey: string;\n widgetUrl: string;\n user: RiftUser | null;\n isOpen: boolean;\n isReady: boolean;\n error: string | null;\n open: (opts?: { mode?: RiftMode }) => void;\n close: () => void;\n signOut: () => Promise<void>;\n /**\n * Returns a valid access token, refreshing silently if the current\n * one is missing or about to expire. Rejects if the user is signed\n * out or the refresh fails (in which case state is cleared and the\n * caller should prompt re-auth).\n */\n getAccessToken: () => Promise<string>;\n _iframeSrc: string;\n _iframeHeight: number;\n _onIframeLoad: () => void;\n}\n\nconst RiftContext = createContext<RiftContextValue | null>(null);\n\nexport function useRiftContext(): RiftContextValue {\n const ctx = useContext(RiftContext);\n if (!ctx) {\n throw new Error(\n \"[@rift/react] useRift() / <RiftAuth> must be used inside <RiftProvider>\"\n );\n }\n return ctx;\n}\n\ninterface RiftProviderProps extends RiftConfig {\n children: ReactNode;\n // Auto-open the modal on mount. Most apps will leave this false and call\n // open() in response to a user clicking \"Sign in\".\n autoOpen?: boolean;\n // Restore the persisted identity (just the user id / address — never\n // the access token) on mount, then silently refresh to mint a token.\n // Default: true.\n persist?: boolean;\n}\n\nfunction loadIdentity(): PersistedIdentity | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(IDENTITY_STORAGE_KEY);\n return raw ? (JSON.parse(raw) as PersistedIdentity) : null;\n } catch {\n return null;\n }\n}\n\nfunction saveIdentity(id: PersistedIdentity | null) {\n if (typeof window === \"undefined\") return;\n try {\n if (id) localStorage.setItem(IDENTITY_STORAGE_KEY, JSON.stringify(id));\n else localStorage.removeItem(IDENTITY_STORAGE_KEY);\n } catch {\n /* private mode / quota — non-fatal */\n }\n}\n\nexport function RiftProvider({\n apiKey,\n widgetUrl,\n children,\n autoOpen = false,\n persist = true,\n}: RiftProviderProps) {\n const resolvedWidgetUrl = widgetUrl || DEFAULT_WIDGET_URL;\n const widgetOrigin = useMemo(() => {\n try {\n return new URL(resolvedWidgetUrl).origin;\n } catch {\n return resolvedWidgetUrl;\n }\n }, [resolvedWidgetUrl]);\n\n const [user, setUser] = useState<RiftUser | null>(null);\n const [isOpen, setIsOpen] = useState(false);\n const [mode, setMode] = useState<RiftMode>(\"signin\");\n const [isReady, setIsReady] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [iframeHeight, setIframeHeight] = useState(540);\n const [openToken, setOpenToken] = useState(0);\n\n // Hot ref to the current user — getAccessToken() reads from this so\n // it never closes over a stale React state snapshot.\n const userRef = useRef<RiftUser | null>(null);\n userRef.current = user;\n\n // Dedupe in-flight refreshes: if multiple API calls hit\n // getAccessToken() simultaneously and the token is stale, we only\n // want one network call.\n const refreshInFlight = useRef<Promise<string> | null>(null);\n\n const setAndPersist = useCallback(\n (next: RiftUser | null) => {\n setUser(next);\n if (persist) {\n saveIdentity(\n next\n ? {\n user: next.user,\n address: next.address,\n btcAddress: next.btcAddress,\n }\n : null\n );\n }\n },\n [persist]\n );\n\n // On mount, if we have a persisted identity, try a silent refresh to\n // rehydrate the access token. If it fails, drop the identity — the\n // user will be prompted to sign in again on first action.\n useEffect(() => {\n if (!persist) return;\n const identity = loadIdentity();\n if (!identity) return;\n let alive = true;\n (async () => {\n try {\n const result = await silentRefresh({\n apiKey,\n widgetUrl: resolvedWidgetUrl,\n });\n if (!alive) return;\n setAndPersist({\n user: identity.user,\n address: identity.address,\n btcAddress: identity.btcAddress,\n accessToken: result.accessToken,\n expiresAt: result.expiresAt,\n });\n } catch {\n if (!alive) return;\n // Refresh failed — likely cookie expired or revoked. Clear the\n // identity hint so the UI shows the signed-out state.\n setAndPersist(null);\n }\n })();\n return () => {\n alive = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const open = useCallback((opts?: { mode?: RiftMode }) => {\n setMode(opts?.mode || \"signin\");\n setError(null);\n setIsReady(false);\n setOpenToken((t) => t + 1);\n setIsOpen(true);\n }, []);\n\n const close = useCallback(() => {\n setIsOpen(false);\n setIsReady(false);\n }, []);\n\n const signOut = useCallback(async () => {\n await silentLogout({ apiKey, widgetUrl: resolvedWidgetUrl });\n setAndPersist(null);\n }, [apiKey, resolvedWidgetUrl, setAndPersist]);\n\n const getAccessToken = useCallback(async (): Promise<string> => {\n const current = userRef.current;\n if (!current) throw new Error(\"Not signed in\");\n\n const expiresAt = current.expiresAt\n ? new Date(current.expiresAt).getTime()\n : null;\n const now = Date.now();\n const needsRefresh =\n !expiresAt || expiresAt - now < REFRESH_LEEWAY_SECONDS * 1000;\n\n if (!needsRefresh && current.accessToken) {\n return current.accessToken;\n }\n\n if (refreshInFlight.current) {\n return refreshInFlight.current;\n }\n\n refreshInFlight.current = (async () => {\n try {\n const result = await silentRefresh({\n apiKey,\n widgetUrl: resolvedWidgetUrl,\n });\n const latest = userRef.current;\n if (!latest) throw new Error(\"Signed out during refresh\");\n const next: RiftUser = {\n ...latest,\n accessToken: result.accessToken,\n expiresAt: result.expiresAt,\n };\n setAndPersist(next);\n return result.accessToken;\n } catch (err: any) {\n // Refresh failed — wipe state so the host UI can prompt re-auth.\n setAndPersist(null);\n throw err instanceof Error ? err : new Error(String(err));\n } finally {\n refreshInFlight.current = null;\n }\n })();\n return refreshInFlight.current;\n }, [apiKey, resolvedWidgetUrl, setAndPersist]);\n\n // Listen for messages from the VISIBLE login iframe (not the silent\n // refresh one — that one's events are handled inside silentRefresh.ts).\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handler = (e: MessageEvent) => {\n if (e.origin !== widgetOrigin) return;\n const data = e.data as RiftEvent | undefined;\n if (!data || typeof data !== \"object\" || typeof data.type !== \"string\") return;\n if (!data.type.startsWith(\"rift:\")) return;\n\n switch (data.type) {\n case \"rift:ready\":\n // Only treat as \"modal ready\" while it's open — the silent\n // refresh iframe also emits ready, but we don't care here.\n if (isOpen) setIsReady(true);\n break;\n case \"rift:close\":\n close();\n break;\n case \"rift:resize\":\n setIframeHeight(Math.max(360, Math.min(820, data.height + 8)));\n break;\n case \"rift:signin-success\": {\n const next: RiftUser = {\n user: data.user,\n address: data.address,\n btcAddress: data.btcAddress,\n accessToken: data.accessToken,\n expiresAt: data.expiresAt,\n };\n setAndPersist(next);\n setIsOpen(false);\n break;\n }\n case \"rift:signin-error\":\n setError(data.message);\n break;\n // refresh / logout result events belong to silentRefresh.ts —\n // ignore them here.\n }\n };\n window.addEventListener(\"message\", handler);\n return () => window.removeEventListener(\"message\", handler);\n }, [widgetOrigin, close, setAndPersist, isOpen]);\n\n // Lock host page scroll while the modal is open.\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n if (isOpen) {\n const prev = document.documentElement.style.overflow;\n document.documentElement.style.overflow = \"hidden\";\n return () => {\n document.documentElement.style.overflow = prev;\n };\n }\n }, [isOpen]);\n\n useEffect(() => {\n if (autoOpen && !user) open();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const iframeSrc = useMemo(() => {\n const params = new URLSearchParams({\n key: apiKey,\n mode,\n origin: typeof window !== \"undefined\" ? window.location.origin : \"\",\n t: String(openToken),\n });\n\n // Best-effort: match the host page's theme so the modal blends in\n // instead of flashing white over a dark site. Checks data-theme,\n // the `dark` class convention, then system preference.\n if (typeof document !== \"undefined\") {\n const html = document.documentElement;\n const attr = html.getAttribute(\"data-theme\");\n let theme: string | null = null;\n if (attr === \"dark\" || attr === \"light\") theme = attr;\n else if (\n html.classList.contains(\"dark\") ||\n document.body?.classList.contains(\"dark\")\n )\n theme = \"dark\";\n else if (\n window.matchMedia &&\n window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n )\n theme = \"dark\";\n if (theme) params.set(\"theme\", theme);\n }\n\n return `${resolvedWidgetUrl.replace(/\\/$/, \"\")}/?${params.toString()}`;\n }, [apiKey, mode, openToken, resolvedWidgetUrl]);\n\n const onIframeLoad = useCallback(() => {\n /* readiness is signalled via postMessage, not the load event */\n }, []);\n\n const value = useMemo<RiftContextValue>(\n () => ({\n apiKey,\n widgetUrl: resolvedWidgetUrl,\n user,\n isOpen,\n isReady,\n error,\n open,\n close,\n signOut,\n getAccessToken,\n _iframeSrc: iframeSrc,\n _iframeHeight: iframeHeight,\n _onIframeLoad: onIframeLoad,\n }),\n [\n apiKey,\n resolvedWidgetUrl,\n user,\n isOpen,\n isReady,\n error,\n open,\n close,\n signOut,\n getAccessToken,\n iframeSrc,\n iframeHeight,\n onIframeLoad,\n ]\n );\n\n return <RiftContext.Provider value={value}>{children}</RiftContext.Provider>;\n}\n","import { useEffect, type CSSProperties } from \"react\";\nimport { useRiftContext } from \"./RiftProvider\";\nimport type { RiftUser } from \"./types\";\n\ninterface BackdropStyle {\n /** Backdrop fill colour. Default `rgba(15,15,20,0.55)` (dark scrim). */\n color?: string;\n /** CSS `backdrop-filter: blur(<px>)`. Default 6, set 0 to disable. */\n blur?: number;\n}\n\ninterface RiftAuthProps {\n // Optional event hooks so callers don't have to compose useEffect by hand.\n onSuccess?: (user: RiftUser) => void;\n onError?: (message: string) => void;\n onClose?: () => void;\n\n /**\n * Cap the modal height. Pass a number for px (e.g. `600`) or a CSS\n * string for viewport units (`\"70vh\"`). The iframe scrolls\n * internally if its content exceeds this. Defaults to no cap; the\n * widget reports its natural height via postMessage.\n */\n maxHeight?: number | string;\n\n /**\n * Cap the modal width. Defaults to 480 (px). Pass any CSS length.\n */\n maxWidth?: number | string;\n\n /**\n * Corner radius on the modal. Defaults to 18 (px).\n */\n radius?: number | string;\n\n /**\n * Backdrop styling. See `BackdropStyle`. Each field falls back to\n * the default if omitted.\n */\n backdrop?: BackdropStyle;\n\n /**\n * Extra style applied to the backdrop wrapper. Use for things outside\n * the typed `backdrop` knob (custom transitions, z-index, etc.).\n */\n backdropStyle?: CSSProperties;\n\n /**\n * Extra style applied to the iframe. Useful for borders, custom\n * shadows, or filters that the typed props don't cover.\n */\n iframeStyle?: CSSProperties;\n}\n\nconst DEFAULT_BACKDROP_COLOR = \"rgba(15,15,20,0.55)\";\nconst DEFAULT_BACKDROP_BLUR = 6;\nconst DEFAULT_MAX_WIDTH = 480;\nconst DEFAULT_RADIUS = 18;\n\n/**\n * Renders the modal backdrop + iframe whenever the provider's `isOpen` is\n * true. Place this once near the root of your app (typically just inside\n * <RiftProvider>); call `useRift().open()` to show it.\n *\n * All visual knobs are overridable from the host: see `maxHeight`,\n * `maxWidth`, `radius`, `backdrop`, plus escape hatches `backdropStyle`\n * and `iframeStyle` for anything else.\n */\nexport function RiftAuth({\n onSuccess,\n onError,\n onClose,\n maxHeight,\n maxWidth = DEFAULT_MAX_WIDTH,\n radius = DEFAULT_RADIUS,\n backdrop,\n backdropStyle,\n iframeStyle,\n}: RiftAuthProps) {\n const {\n isOpen,\n isReady,\n error,\n close,\n user,\n _iframeSrc,\n _iframeHeight,\n _onIframeLoad,\n } = useRiftContext();\n\n useEffect(() => {\n if (user && onSuccess) onSuccess(user);\n }, [user, onSuccess]);\n\n useEffect(() => {\n if (error && onError) onError(error);\n }, [error, onError]);\n\n useEffect(() => {\n if (!isOpen && onClose) onClose();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen]);\n\n if (!isOpen) return null;\n\n const backdropColor = backdrop?.color ?? DEFAULT_BACKDROP_COLOR;\n const backdropBlur = backdrop?.blur ?? DEFAULT_BACKDROP_BLUR;\n const blurCss = backdropBlur > 0 ? `blur(${backdropBlur}px)` : undefined;\n\n // Resolve the iframe's final height. The widget posts its desired\n // height via `rift:resize`; we honor it but clamp to `maxHeight` if\n // the host asked us to. For numeric maxHeight, we min() against the\n // reported height (so a fixed modal doesn't grow past it). For string\n // values like \"70vh\", we hand the limit to CSS via `maxHeight` and\n // let the browser do the math, but still cap our height attribute by\n // the reported natural height so we don't reserve unused space.\n let iframeHeight: number | string = _iframeHeight;\n let iframeMaxHeight: number | string | undefined;\n if (typeof maxHeight === \"number\") {\n iframeHeight = Math.min(_iframeHeight, maxHeight);\n } else if (typeof maxHeight === \"string\") {\n iframeMaxHeight = maxHeight;\n }\n\n return (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Sign in\"\n onClick={(e) => {\n if (e.target === e.currentTarget) close();\n }}\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: 2147483646,\n background: backdropColor,\n backdropFilter: blurCss,\n WebkitBackdropFilter: blurCss,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n padding: 16,\n animation: \"rift-fade 180ms ease-out\",\n ...backdropStyle,\n }}\n >\n <style>{`@keyframes rift-fade { from { opacity: 0 } to { opacity: 1 } }`}</style>\n {!isReady && (\n <div\n aria-hidden\n style={{\n position: \"absolute\",\n color: \"rgba(255,255,255,0.75)\",\n fontSize: 13,\n fontFamily:\n \"Inter, ui-sans-serif, system-ui, sans-serif\",\n }}\n >\n Loading sign-in…\n </div>\n )}\n <iframe\n src={_iframeSrc}\n onLoad={_onIframeLoad}\n title=\"Rift sign-in\"\n allow=\"publickey-credentials-get; identity-credentials-get\"\n style={{\n border: 0,\n background: \"transparent\",\n colorScheme: \"light\",\n width: \"100%\",\n maxWidth,\n height: iframeHeight,\n maxHeight: iframeMaxHeight,\n borderRadius: radius,\n boxShadow: \"0 24px 60px -12px rgba(0,0,0,0.35)\",\n transition: \"height 200ms ease\",\n opacity: isReady ? 1 : 0,\n ...iframeStyle,\n }}\n />\n </div>\n );\n}\n","import { useRiftContext } from \"./RiftProvider\";\nimport type { RiftMode, RiftUser } from \"./types\";\n\ninterface UseRiftReturn {\n user: RiftUser | null;\n isAuthenticated: boolean;\n isOpen: boolean;\n open: (opts?: { mode?: RiftMode }) => void;\n close: () => void;\n signOut: () => Promise<void>;\n /**\n * Async getter for a valid access token. Use this when calling Rift /\n * your backend — it returns the current token if fresh, or silently\n * refreshes via a hidden iframe if near expiry. Rejects when the user\n * isn't signed in or the refresh fails (in which case auth state is\n * cleared and the host should prompt re-auth).\n */\n getAccessToken: () => Promise<string>;\n error: string | null;\n}\n\n/**\n * Read auth state and drive the widget from anywhere inside <RiftProvider>.\n *\n * const { user, isAuthenticated, open, signOut, getAccessToken } = useRift();\n * return isAuthenticated\n * ? <button onClick={signOut}>Sign out</button>\n * : <button onClick={() => open({ mode: 'signup' })}>Get started</button>;\n *\n * // When calling your backend with Rift's session JWT:\n * const token = await getAccessToken();\n * fetch('/api/my-thing', { headers: { Authorization: `Bearer ${token}` } });\n */\nexport function useRift(): UseRiftReturn {\n const { user, isOpen, open, close, signOut, getAccessToken, error } =\n useRiftContext();\n return {\n user,\n isAuthenticated: !!user,\n isOpen,\n open,\n close,\n signOut,\n getAccessToken,\n error,\n };\n}\n"],"names":["iframe","ready","readyResolvers","widgetOrigin","pending","uuid","ensureMounted","opts","params","e","data","r","slot","resolve","silentRefresh","requestId","reject","silentLogout","DEFAULT_WIDGET_URL","IDENTITY_STORAGE_KEY","REFRESH_LEEWAY_SECONDS","RiftContext","createContext","useRiftContext","ctx","useContext","loadIdentity","raw","saveIdentity","id","RiftProvider","apiKey","widgetUrl","children","autoOpen","persist","resolvedWidgetUrl","useMemo","user","setUser","useState","isOpen","setIsOpen","mode","setMode","isReady","setIsReady","error","setError","iframeHeight","setIframeHeight","openToken","setOpenToken","userRef","useRef","refreshInFlight","setAndPersist","useCallback","next","useEffect","identity","alive","result","open","t","close","signOut","getAccessToken","current","expiresAt","now","latest","err","handler","prev","iframeSrc","html","attr","theme","onIframeLoad","value","jsx","DEFAULT_BACKDROP_COLOR","DEFAULT_BACKDROP_BLUR","DEFAULT_MAX_WIDTH","DEFAULT_RADIUS","RiftAuth","onSuccess","onError","onClose","maxHeight","maxWidth","radius","backdrop","backdropStyle","iframeStyle","_iframeSrc","_iframeHeight","_onIframeLoad","backdropColor","backdropBlur","blurCss","iframeMaxHeight","jsxs","useRift"],"mappings":"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"}