@rift-finance/react 0.3.1 → 0.3.2
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/RiftAuth.d.ts.map +1 -1
- package/dist/rift-react.cjs +1 -1
- package/dist/rift-react.cjs.map +1 -1
- package/dist/rift-react.js +1 -1
- package/dist/rift-react.js.map +1 -1
- package/package.json +1 -1
package/dist/RiftAuth.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,kDAiHf"}
|
package/dist/rift-react.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("react/jsx-runtime"),r=require("react");let d=null,j=!1,D=[],x=null;const f=new Map;function Y(){return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,9)}`}function G(e){if(typeof document>"u")return Promise.reject(new Error("Cannot mount refresh iframe outside the browser"));if(d&&j)return Promise.resolve();if(x=new URL(e.widgetUrl).origin,!d){d=document.createElement("iframe"),d.setAttribute("aria-hidden","true"),d.setAttribute("tabindex","-1"),d.title="Rift session refresh",d.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"});d.src=`${e.widgetUrl.replace(/\/$/,"")}/?${i.toString()}`,document.body.appendChild(d),window.addEventListener("message",a=>{if(a.origin!==x)return;const t=a.data;if(!(!t||typeof t!="object"||typeof t.type!="string")&&t.type.startsWith("rift:")){if(t.type==="rift:ready"){j=!0,D.forEach(o=>o()),D=[];return}if(t.type==="rift:refresh-result"){const o=f.get(t.requestId);o&&(f.delete(t.requestId),o.resolve({accessToken:t.accessToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn}));return}if(t.type==="rift:refresh-error"){const o=f.get(t.requestId);o&&(f.delete(t.requestId),o.reject(new Error(t.message||"Refresh failed")));return}if(t.type==="rift:logout-result"){const o=f.get(t.requestId);o&&(f.delete(t.requestId),o.resolve({accessToken:"",expiresAt:"",expiresIn:0}))}}})}return j?Promise.resolve():new Promise(i=>{D.push(i),setTimeout(()=>{if(!j){const a=D.shift();a&&a()}},8e3)})}async function z(e){if(await G(e),!d?.contentWindow||!x)throw new Error("Refresh iframe is not available");const i=Y();return new Promise((a,t)=>{f.set(i,{resolve:a,reject:t}),d.contentWindow.postMessage({type:"rift:refresh-request",requestId:i},x),setTimeout(()=>{const o=f.get(i);o&&(f.delete(i),o.reject(new Error("Refresh timed out")))},1e4)})}async function Z(e){try{if(await G(e),!d?.contentWindow||!x)return;const i=Y();await new Promise(a=>{f.set(i,{resolve:()=>a(),reject:()=>a()}),d.contentWindow.postMessage({type:"rift:logout-request",requestId:i},x),setTimeout(()=>{f.delete(i),a()},5e3)})}catch{}}const K={production:"https://widget.riftfi.xyz",sandbox:"https://widget.sandbox.riftfi.com"},W="rift:identity",ee=60,J=r.createContext(null);function V(){const e=r.useContext(J);if(!e)throw new Error("[@rift/react] useRift() / <RiftAuth> must be used inside <RiftProvider>");return e}function te(){if(typeof window>"u")return null;try{const e=localStorage.getItem(W);return e?JSON.parse(e):null}catch{return null}}function re(e){if(!(typeof window>"u"))try{e?localStorage.setItem(W,JSON.stringify(e)):localStorage.removeItem(W)}catch{}}function se({apiKey:e,environment:i,widgetUrl:a,children:t,autoOpen:o=!1,persist:
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const I=require("react/jsx-runtime"),r=require("react");let d=null,j=!1,D=[],x=null;const f=new Map;function Y(){return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,9)}`}function G(e){if(typeof document>"u")return Promise.reject(new Error("Cannot mount refresh iframe outside the browser"));if(d&&j)return Promise.resolve();if(x=new URL(e.widgetUrl).origin,!d){d=document.createElement("iframe"),d.setAttribute("aria-hidden","true"),d.setAttribute("tabindex","-1"),d.title="Rift session refresh",d.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"});d.src=`${e.widgetUrl.replace(/\/$/,"")}/?${i.toString()}`,document.body.appendChild(d),window.addEventListener("message",a=>{if(a.origin!==x)return;const t=a.data;if(!(!t||typeof t!="object"||typeof t.type!="string")&&t.type.startsWith("rift:")){if(t.type==="rift:ready"){j=!0,D.forEach(o=>o()),D=[];return}if(t.type==="rift:refresh-result"){const o=f.get(t.requestId);o&&(f.delete(t.requestId),o.resolve({accessToken:t.accessToken,expiresAt:t.expiresAt,expiresIn:t.expiresIn}));return}if(t.type==="rift:refresh-error"){const o=f.get(t.requestId);o&&(f.delete(t.requestId),o.reject(new Error(t.message||"Refresh failed")));return}if(t.type==="rift:logout-result"){const o=f.get(t.requestId);o&&(f.delete(t.requestId),o.resolve({accessToken:"",expiresAt:"",expiresIn:0}))}}})}return j?Promise.resolve():new Promise(i=>{D.push(i),setTimeout(()=>{if(!j){const a=D.shift();a&&a()}},8e3)})}async function z(e){if(await G(e),!d?.contentWindow||!x)throw new Error("Refresh iframe is not available");const i=Y();return new Promise((a,t)=>{f.set(i,{resolve:a,reject:t}),d.contentWindow.postMessage({type:"rift:refresh-request",requestId:i},x),setTimeout(()=>{const o=f.get(i);o&&(f.delete(i),o.reject(new Error("Refresh timed out")))},1e4)})}async function Z(e){try{if(await G(e),!d?.contentWindow||!x)return;const i=Y();await new Promise(a=>{f.set(i,{resolve:()=>a(),reject:()=>a()}),d.contentWindow.postMessage({type:"rift:logout-request",requestId:i},x),setTimeout(()=>{f.delete(i),a()},5e3)})}catch{}}const K={production:"https://widget.riftfi.xyz",sandbox:"https://widget.sandbox.riftfi.com"},W="rift:identity",ee=60,J=r.createContext(null);function V(){const e=r.useContext(J);if(!e)throw new Error("[@rift/react] useRift() / <RiftAuth> must be used inside <RiftProvider>");return e}function te(){if(typeof window>"u")return null;try{const e=localStorage.getItem(W);return e?JSON.parse(e):null}catch{return null}}function re(e){if(!(typeof window>"u"))try{e?localStorage.setItem(W,JSON.stringify(e)):localStorage.removeItem(W)}catch{}}function se({apiKey:e,environment:i,widgetUrl:a,children:t,autoOpen:o=!1,persist:p=!0}){const c=a||K[i??"production"],v=r.useMemo(()=>{try{return new URL(c).origin}catch{return c}},[c]),[y,R]=r.useState(null),[l,h]=r.useState(!1),[L,A]=r.useState("signin"),[_,b]=r.useState(!1),[C,U]=r.useState(null),[E,O]=r.useState(540),[S,M]=r.useState(0),k=r.useRef(null);k.current=y;const T=r.useRef(null),m=r.useCallback(s=>{R(s),p&&re(s?{user:s.user,address:s.address,btcAddress:s.btcAddress}:null)},[p]);r.useEffect(()=>{if(!p)return;const s=te();if(!s)return;let u=!0;return(async()=>{try{const n=await z({apiKey:e,widgetUrl:c});if(!u)return;m({user:s.user,address:s.address,btcAddress:s.btcAddress,accessToken:n.accessToken,expiresAt:n.expiresAt})}catch{if(!u)return;m(null)}})(),()=>{u=!1}},[]);const q=r.useCallback(s=>{A(s?.mode||"signin"),U(null),b(!1),M(u=>u+1),h(!0)},[]),P=r.useCallback(()=>{h(!1),b(!1)},[]),F=r.useCallback(async()=>{await Z({apiKey:e,widgetUrl:c}),m(null)},[e,c,m]),$=r.useCallback(async()=>{const s=k.current;if(!s)throw new Error("Not signed in");const u=s.expiresAt?new Date(s.expiresAt).getTime():null,n=Date.now();return!(!u||u-n<ee*1e3)&&s.accessToken?s.accessToken:(T.current||(T.current=(async()=>{try{const w=await z({apiKey:e,widgetUrl:c}),N=k.current;if(!N)throw new Error("Signed out during refresh");const Q={...N,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{T.current=null}})()),T.current)},[e,c,m]);r.useEffect(()=>{if(typeof window>"u")return;const s=u=>{if(u.origin!==v)return;const n=u.data;if(!(!n||typeof n!="object"||typeof n.type!="string")&&n.type.startsWith("rift:"))switch(n.type){case"rift:ready":l&&b(!0);break;case"rift:close":P();break;case"rift:resize":O(Math.max(360,Math.min(820,n.height+8)));break;case"rift:signin-success":{const g={user:n.user,address:n.address,btcAddress:n.btcAddress,accessToken:n.accessToken,expiresAt:n.expiresAt};m(g),h(!1);break}case"rift:signin-error":U(n.message);break}};return window.addEventListener("message",s),()=>window.removeEventListener("message",s)},[v,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(()=>{o&&!y&&q()},[]);const B=r.useMemo(()=>{const s=new URLSearchParams({key:e,mode:L,origin:typeof window<"u"?window.location.origin:"",t:String(S)});if(typeof document<"u"){const u=document.documentElement,n=u.getAttribute("data-theme");let g=null;n==="dark"||n==="light"?g=n:(u.classList.contains("dark")||document.body?.classList.contains("dark")||window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches)&&(g="dark"),g&&s.set("theme",g)}return`${c.replace(/\/$/,"")}/?${s.toString()}`},[e,L,S,c]),H=r.useCallback(()=>{},[]),X=r.useMemo(()=>({apiKey:e,widgetUrl:c,user:y,isOpen:l,isReady:_,error:C,open:q,close:P,signOut:F,getAccessToken:$,_iframeSrc:B,_iframeHeight:E,_onIframeLoad:H}),[e,c,y,l,_,C,q,P,F,$,B,E,H]);return I.jsx(J.Provider,{value:X,children:t})}const ne="rgba(15,15,20,0.55)",ie=6,oe=480,ae=18;function ce({onSuccess:e,onError:i,onClose:a,maxHeight:t,maxWidth:o=oe,radius:p=ae,backdrop:c,backdropStyle:v,iframeStyle:y}){const{isOpen:R,isReady:l,error:h,close:L,user:A,_iframeSrc:_,_iframeHeight:b,_onIframeLoad:C}=V();if(r.useEffect(()=>{A&&e&&e(A)},[A,e]),r.useEffect(()=>{h&&i&&i(h)},[h,i]),r.useEffect(()=>{!R&&a&&a()},[R]),!R)return null;const U=c?.color??ne,E=c?.blur??ie,O=E>0?`blur(${E}px)`:void 0;let S=b,M;return typeof t=="number"?S=Math.min(b,t):typeof t=="string"&&(M=t),I.jsxs("div",{role:"dialog","aria-modal":"true","aria-label":"Sign in",onClick:k=>{k.target===k.currentTarget&&L()},style:{position:"fixed",inset:0,zIndex:2147483646,background:U,backdropFilter:O,WebkitBackdropFilter:O,display:"flex",alignItems:"center",justifyContent:"center",padding:16,animation:"rift-fade 180ms ease-out",...v},children:[I.jsx("style",{children:"@keyframes rift-fade { from { opacity: 0 } to { opacity: 1 } }"}),!l&&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:_,onLoad:C,title:"Rift sign-in",allow:"publickey-credentials-create; publickey-credentials-get; identity-credentials-get",style:{border:0,background:"transparent",colorScheme:"light",width:"100%",maxWidth:o,height:S,maxHeight:M,borderRadius:p,boxShadow:"0 24px 60px -12px rgba(0,0,0,0.35)",transition:"height 200ms ease",opacity:l?1:0,...y}})]})}function ue(){const{user:e,isOpen:i,open:a,close:t,signOut:o,getAccessToken:p,error:c}=V();return{user:e,isAuthenticated:!!e,isOpen:i,open:a,close:t,signOut:o,getAccessToken:p,error:c}}exports.RiftAuth=ce;exports.RiftProvider=se;exports.useRift=ue;
|
|
2
2
|
//# sourceMappingURL=rift-react.cjs.map
|
package/dist/rift-react.cjs.map
CHANGED
|
@@ -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\n// Widget URL by environment. The widget is a separate Vite SPA that\n// gets built once per environment (the backend URL is baked into the\n// bundle), so picking the environment here is equivalent to picking\n// which deployed widget the iframe loads.\n//\n// Override either by passing `widgetUrl=` directly (wins over\n// `environment`), or self-host and point at your own URL.\nconst WIDGET_URL_BY_ENV: Record<\"production\" | \"sandbox\", string> = {\n production: \"https://widget.riftfi.xyz\",\n sandbox: \"https://widget.sandbox.riftfi.com\",\n};\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 environment,\n widgetUrl,\n children,\n autoOpen = false,\n persist = true,\n}: RiftProviderProps) {\n const resolvedWidgetUrl =\n widgetUrl || WIDGET_URL_BY_ENV[environment ?? \"production\"];\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","WIDGET_URL_BY_ENV","IDENTITY_STORAGE_KEY","REFRESH_LEEWAY_SECONDS","RiftContext","createContext","useRiftContext","ctx","useContext","loadIdentity","raw","saveIdentity","id","RiftProvider","apiKey","environment","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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"}
|
|
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\n// Widget URL by environment. The widget is a separate Vite SPA that\n// gets built once per environment (the backend URL is baked into the\n// bundle), so picking the environment here is equivalent to picking\n// which deployed widget the iframe loads.\n//\n// Override either by passing `widgetUrl=` directly (wins over\n// `environment`), or self-host and point at your own URL.\nconst WIDGET_URL_BY_ENV: Record<\"production\" | \"sandbox\", string> = {\n production: \"https://widget.riftfi.xyz\",\n sandbox: \"https://widget.sandbox.riftfi.com\",\n};\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 environment,\n widgetUrl,\n children,\n autoOpen = false,\n persist = true,\n}: RiftProviderProps) {\n const resolvedWidgetUrl =\n widgetUrl || WIDGET_URL_BY_ENV[environment ?? \"production\"];\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 // WebAuthn permissions delegated to the cross-origin widget iframe:\n // -create: needed for enrolment (navigator.credentials.create). The\n // widget's sandbox / v3 flavour enrols a passkey post-login\n // and calls /wallet/migrate-to-v3.\n // -get: needed for assertion (navigator.credentials.get) when the\n // widget signs userOp hashes with an existing passkey.\n // identity-credentials-get: Google Identity Services one-tap.\n allow=\"publickey-credentials-create; 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","WIDGET_URL_BY_ENV","IDENTITY_STORAGE_KEY","REFRESH_LEEWAY_SECONDS","RiftContext","createContext","useRiftContext","ctx","useContext","loadIdentity","raw","saveIdentity","id","RiftProvider","apiKey","environment","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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"}
|
package/dist/rift-react.js
CHANGED
|
@@ -365,7 +365,7 @@ function we({
|
|
|
365
365
|
src: U,
|
|
366
366
|
onLoad: O,
|
|
367
367
|
title: "Rift sign-in",
|
|
368
|
-
allow: "publickey-credentials-get; identity-credentials-get",
|
|
368
|
+
allow: "publickey-credentials-create; publickey-credentials-get; identity-credentials-get",
|
|
369
369
|
style: {
|
|
370
370
|
border: 0,
|
|
371
371
|
background: "transparent",
|
package/dist/rift-react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rift-react.js","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\n// Widget URL by environment. The widget is a separate Vite SPA that\n// gets built once per environment (the backend URL is baked into the\n// bundle), so picking the environment here is equivalent to picking\n// which deployed widget the iframe loads.\n//\n// Override either by passing `widgetUrl=` directly (wins over\n// `environment`), or self-host and point at your own URL.\nconst WIDGET_URL_BY_ENV: Record<\"production\" | \"sandbox\", string> = {\n production: \"https://widget.riftfi.xyz\",\n sandbox: \"https://widget.sandbox.riftfi.com\",\n};\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 environment,\n widgetUrl,\n children,\n autoOpen = false,\n persist = true,\n}: RiftProviderProps) {\n const resolvedWidgetUrl =\n widgetUrl || WIDGET_URL_BY_ENV[environment ?? \"production\"];\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","WIDGET_URL_BY_ENV","IDENTITY_STORAGE_KEY","REFRESH_LEEWAY_SECONDS","RiftContext","createContext","useRiftContext","ctx","useContext","loadIdentity","raw","saveIdentity","id","RiftProvider","apiKey","environment","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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"}
|
|
1
|
+
{"version":3,"file":"rift-react.js","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\n// Widget URL by environment. The widget is a separate Vite SPA that\n// gets built once per environment (the backend URL is baked into the\n// bundle), so picking the environment here is equivalent to picking\n// which deployed widget the iframe loads.\n//\n// Override either by passing `widgetUrl=` directly (wins over\n// `environment`), or self-host and point at your own URL.\nconst WIDGET_URL_BY_ENV: Record<\"production\" | \"sandbox\", string> = {\n production: \"https://widget.riftfi.xyz\",\n sandbox: \"https://widget.sandbox.riftfi.com\",\n};\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 environment,\n widgetUrl,\n children,\n autoOpen = false,\n persist = true,\n}: RiftProviderProps) {\n const resolvedWidgetUrl =\n widgetUrl || WIDGET_URL_BY_ENV[environment ?? \"production\"];\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 // WebAuthn permissions delegated to the cross-origin widget iframe:\n // -create: needed for enrolment (navigator.credentials.create). The\n // widget's sandbox / v3 flavour enrols a passkey post-login\n // and calls /wallet/migrate-to-v3.\n // -get: needed for assertion (navigator.credentials.get) when the\n // widget signs userOp hashes with an existing passkey.\n // identity-credentials-get: Google Identity Services one-tap.\n allow=\"publickey-credentials-create; 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","WIDGET_URL_BY_ENV","IDENTITY_STORAGE_KEY","REFRESH_LEEWAY_SECONDS","RiftContext","createContext","useRiftContext","ctx","useContext","loadIdentity","raw","saveIdentity","id","RiftProvider","apiKey","environment","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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"}
|
package/package.json
CHANGED