@stacknet/userutils 0.5.6 → 0.6.1
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/adapters/index.d.cts +2 -2
- package/dist/adapters/index.d.ts +2 -2
- package/dist/{auth-DR2aYcor.d.cts → auth-c1d7Eji2.d.cts} +6 -0
- package/dist/{auth-DR2aYcor.d.ts → auth-c1d7Eji2.d.ts} +6 -0
- package/dist/components/index.cjs +2 -2
- package/dist/components/index.d.cts +25 -3
- package/dist/components/index.d.ts +25 -3
- package/dist/components/index.js +2 -2
- package/dist/config-CLzVWDrU.d.cts +177 -0
- package/dist/config-xNca5ufB.d.ts +177 -0
- package/dist/core/index.d.cts +3 -3
- package/dist/core/index.d.ts +3 -3
- package/dist/hooks/index.cjs +2 -2
- package/dist/hooks/index.d.cts +52 -3
- package/dist/hooks/index.d.ts +52 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +2 -2
- package/dist/server/index.d.cts +116 -21
- package/dist/server/index.d.ts +116 -21
- package/dist/server/index.js +2 -2
- package/dist/types/index.d.cts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/{types-CAoB_5kk.d.cts → types-B_Vj6cr4.d.cts} +1 -1
- package/dist/{types-Dghy_8Wh.d.ts → types-Cu0do-w-.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/config-Bjh8PEhY.d.cts +0 -123
- package/dist/config-_ZjAzNkJ.d.ts +0 -123
package/dist/hooks/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var react=require('react');function le(){if(typeof document>"u")return null;try{let e=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith("stackauth_session="));if(!e)return null;let r=e.slice(18);return JSON.parse(atob(r.replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function U(e="__csrf"){if(typeof document>"u")return null;let r=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith(`${e}=`));return r?r.slice(e.length+1):null}function M(){let[e,r]=react.useState(null),[i,l]=react.useState(true),d=react.useCallback(()=>{let s=le();s&&s.expiresAt>Date.now()?r({userId:s.userId,address:s.address,chain:s.chain,expiresAt:s.expiresAt,planId:s.planId,authMethod:s.authMethod}):r(null),l(false);},[]);react.useEffect(()=>{d();},[d]);let f=react.useCallback(async(s="")=>{try{let a=await fetch(`${s}/api/auth/session`);if(a.ok){let o=await a.json();if(o.session)return r(o.session),o.session}return r(null),null}catch{return null}},[]),p=!!e&&e.expiresAt>Date.now();return {session:e,loading:i,isAuthenticated:p,refresh:f,readSession:d}}function K(e="__csrf",r="x-csrf-token"){let[i,l]=react.useState(null);react.useEffect(()=>{l(U(e));},[e]);let d=i?{[r]:i}:{};return {token:i,headers:d}}function V(){let[e,r]=react.useState({connected:false,address:null,chain:null,provider:null}),[i,l]=react.useState(null),d=react.useCallback(async(a="phantom")=>{l(null);try{let o=typeof window<"u"?window:null,u=a==="phantom"?o?.phantom?.solana||o?.solana:o?.solflare;if(!u)return l(`${a} wallet not found`),null;let h=(await u.connect()).publicKey.toString();return r({connected:!0,address:h,chain:"solana",provider:a}),h}catch(o){return l(o.message||"Failed to connect wallet"),null}},[]),f=react.useCallback(async()=>{l(null);try{let o=(typeof window<"u"?window:null)?.ethereum;if(!o)return l("MetaMask not found"),null;let u=o;o.providers?.length&&(u=o.providers.find(t=>t.isMetaMask)||o);let h=(await u.request({method:"eth_requestAccounts"}))[0];return h?(r({connected:!0,address:h,chain:"ethereum",provider:"metamask"}),h):(l("No account selected"),null)}catch(a){return l(a.message||"Failed to connect wallet"),null}},[]),p=react.useCallback(async(a,o)=>{l(null);let u=o?.chain||e.chain,c=o?.provider||e.provider,h=o?.address||e.address;try{if(u==="solana"){let t=typeof window<"u"?window:null,g=c==="solflare"?t?.solflare:t?.phantom?.solana||t?.solana;if(!g)throw new Error("Wallet not available");let n=new TextEncoder().encode(a),m=await g.signMessage(n,"utf8"),y=new Uint8Array(m.signature||m),w="";for(let A=0;A<y.byteLength;A++)w+=String.fromCharCode(y[A]);return btoa(w)}if(u==="ethereum"){let g=(typeof window<"u"?window:null)?.ethereum;if(g?.providers?.length&&(g=g.providers.find(m=>m.isMetaMask)||g),!g)throw new Error("MetaMask not available");return await g.request({method:"personal_sign",params:[a,h]})}throw new Error("No wallet connected")}catch(t){return l(t.message||"Signing failed"),null}},[e]),s=react.useCallback(()=>{r({connected:false,address:null,chain:null,provider:null}),l(null);},[]);return {wallet:e,error:i,connectSolana:d,connectEVM:f,signMessage:p,disconnect:s}}var Pe="https://stacknet.magma-rpc.com/auth/bridge",J="stacknet-auth-bridge";function q(e){let r=e?.bridgeUrl||Pe,i=e?.disabled||false,l=react.useRef(null),[d,f]=react.useState({ready:false,known:false,identity:null,identityCount:0,resolvedStackId:null}),p=react.useRef([]),s=react.useRef(false),a=react.useCallback(t=>{let g={...t,protocol:J};s.current&&l.current?.contentWindow?l.current.contentWindow.postMessage(g,new URL(r).origin):p.current.push(g);},[r]);react.useEffect(()=>{if(i)return;let t=n=>{if(!(!n.data||n.data.protocol!==J)){try{if(n.origin!==new URL(r).origin)return}catch{return}switch(n.data.type){case "bridge:ready":s.current=true,f(m=>({...m,ready:true}));for(let m of p.current)l.current?.contentWindow?.postMessage(m,n.origin);p.current=[],l.current?.contentWindow?.postMessage({protocol:J,type:"auth:check"},n.origin),l.current?.contentWindow?.postMessage({protocol:J,type:"auth:resolve-stack"},n.origin);break;case "auth:status":f(m=>({...m,known:n.data.known,identity:n.data.identity,identityCount:n.data.identityCount||0}));break;case "auth:resolved-stack":f(m=>({...m,resolvedStackId:n.data.stackId||null}));break;}}};window.addEventListener("message",t);let g=document.createElement("iframe");return g.src=r,g.style.display="none",g.setAttribute("aria-hidden","true"),g.setAttribute("tabindex","-1"),g.setAttribute("sandbox","allow-scripts allow-same-origin"),document.body.appendChild(g),l.current=g,()=>{window.removeEventListener("message",t),g.parentNode&&g.parentNode.removeChild(g),l.current=null,s.current=false;}},[r,i]);let o=react.useCallback(t=>{a({type:"auth:connected",...t});},[a]),u=react.useCallback(t=>{a({type:"auth:disconnected",...t});},[a]),c=react.useCallback(()=>{a({type:"auth:clear"}),f({ready:d.ready,known:false,identity:null,identityCount:0,resolvedStackId:null});},[a,d.ready]),h=react.useCallback(()=>{a({type:"auth:check"});},[a]);return {...d,reportConnected:o,reportDisconnected:u,clearAll:c,refresh:h}}async function fe(e,r,i,l){let d=e.apiVersion||"v2",f=`${e.baseUrl}/api/${d}${i}`;try{let p=await fetch(f,{method:r,headers:{"Content-Type":"application/json"},body:l?JSON.stringify(l):void 0}),s=await p.json();return p.ok?s.success&&s.data!==void 0?{success:!0,data:s.data}:{success:!0,data:s}:{success:!1,error:s.error||{code:"UNKNOWN_ERROR",message:"Unknown error"}}}catch(p){return {success:false,error:{code:"NETWORK_ERROR",message:p instanceof Error?p.message:"Network error"}}}}function pe(e){return {getNetworkStatus:()=>fe(e,"GET","/network/status"),getWeb3Challenge:(r,i)=>fe(e,"POST",`/stacks/${encodeURIComponent(e.stackId)}/auth/web3/challenge`,{chain:r,address:i})}}function Ae(e={apiBaseUrl:""}){let{wallet:r,connectSolana:i,connectEVM:l,signMessage:d,disconnect:f}=V(),{session:p,isAuthenticated:s,refresh:a,readSession:o}=M(),{headers:u}=K(),c=q({disabled:typeof window>"u"}),[h,t]=react.useState(false),[g,n]=react.useState(null),[m,y]=react.useState(false),w=e.apiBaseUrl||"",A=e.stacknetUrl||"https://stacknet.magma-rpc.com",C=e.stackId||c.resolvedStackId||"",S=pe({baseUrl:A,stackId:C}),E=react.useCallback(async(k,P,T,x)=>{t(true),n(null);try{let v=P;if(!v){let D=await T();if(!D)return t(!1),!1;v=D;}let O=await S.getWeb3Challenge(k,v);if(!O.success||!O.data)return n("Failed to get challenge"),t(!1),!1;let R=await d(O.data.message,{chain:k,provider:x,address:v});if(!R)return t(!1),!1;let G={chain:k,message:O.data.message,signature:R,stackId:C};k==="solana"&&(G.publicKey=v);let F=await fetch(`${w}/api/auth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(G)});if(!F.ok){let D=await F.json().catch(()=>({}));return n(D.error||"Authentication failed"),t(!1),!1}return c.reportConnected({address:v,chain:k,method:x||(k==="solana"?"phantom":"metamask"),stackId:C}),o(),t(!1),!0}catch(v){return n(v.message||"Authentication failed"),t(false),false}},[w,S,d,o,c,C]),$=react.useCallback(async(k="phantom")=>{let P=await i(k);return P?E("solana",P,()=>i(k),k):false},[i,E]),W=react.useCallback(async()=>{let k=await l();return k?E("ethereum",k,l,"metamask"):false},[l,E]),b=react.useCallback(async k=>{t(true),n(null);try{let P=await fetch(`${w}/api/auth/otp`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({code:k})});if(!P.ok){let T=await P.json().catch(()=>({}));return n(T.error||"Invalid code"),t(!1),!1}return o(),t(!1),!0}catch(P){return n(P.message||"OTP verification failed"),t(false),false}},[w,o]),I=react.useCallback(async(k,P)=>{t(true),n(null);try{let T=P||`${window.location.origin}/api/auth/oauth/callback`,x=new URLSearchParams({provider:k,redirectUri:T,stackId:C}),v=await fetch(`${w}/api/auth/oauth/start?${x}`,{credentials:"include"});if(!v.ok){let R=await v.json().catch(()=>({}));return n(R.error||"Failed to start OAuth flow"),t(!1),!1}let O=await v.json();if(O.redirect_url){let R;try{R=new URL(O.redirect_url);}catch{return n("Invalid OAuth redirect URL"),t(!1),!1}let G=[/(^|\.)accounts\.google\.com$/,/(^|\.)discord\.com$/,/(^|\.)github\.com$/,/(^|\.)x\.com$/,/(^|\.)twitter\.com$/,/(^|\.)apple\.com$/];return R.protocol!=="https:"||!G.some(F=>F.test(R.hostname))?(n(`Refusing to redirect to non-OAuth host: ${R.hostname}`),t(!1),!1):(typeof sessionStorage<"u"&&(sessionStorage.setItem("oauth_state",O.state||""),sessionStorage.setItem("oauth_provider",k)),window.location.href=R.toString(),!0)}return n("No redirect URL returned"),t(!1),!1}catch(T){return n(T.message||"OAuth flow failed"),t(false),false}},[w,C]),N=react.useCallback(async(k,P,T)=>{t(true),n(null);try{if(typeof sessionStorage<"u"){let v=sessionStorage.getItem("oauth_state"),O=sessionStorage.getItem("oauth_provider");if(sessionStorage.removeItem("oauth_state"),sessionStorage.removeItem("oauth_provider"),!v||v!==T)return n("OAuth state mismatch \u2014 refusing to complete login"),t(!1),!1;if(O&&O!==k)return n("OAuth provider mismatch \u2014 refusing to complete login"),t(!1),!1}let x=await fetch(`${w}/api/auth/oauth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({provider:k,code:P,state:T,stackId:C})});if(!x.ok){let v=await x.json().catch(()=>({}));return n(v.error||"OAuth authentication failed"),t(!1),!1}return o(),t(!1),!0}catch(x){return n(x.message||"OAuth callback failed"),t(false),false}},[w,C,o]),j=react.useCallback(async()=>{r.address&&r.chain&&c.reportDisconnected({address:r.address,chain:r.chain,stackId:C});try{await fetch(`${w}/api/auth/logout`,{method:"POST",headers:u,credentials:"include"});}catch{}f(),o();},[w,u,f,o,r,c,C]);return react.useEffect(()=>{if(!e.autoConnect||m||s||!c.ready||!c.known||!c.identity)return;y(true);let{chain:k,method:P}=c.identity;k==="solana"&&(P==="phantom"||P==="solflare")?$(P):k==="ethereum"&&W();},[e.autoConnect,m,s,c,$,W]),{session:p,isAuthenticated:s,wallet:r,loading:h,error:g,authenticateSolana:$,authenticateEVM:W,authenticateOTP:b,authenticateOAuth:I,authenticateOAuthCallback:N,logout:j,refresh:()=>a(w),stackId:C,bridge:{ready:c.ready,known:c.known,identity:c.identity,identityCount:c.identityCount,resolvedStackId:c.resolvedStackId}}}function Y(e,r="https://stacknet.magma-rpc.com"){let[i,l]=react.useState(null),[d,f]=react.useState(false),[p,s]=react.useState(null),a=react.useCallback(async u=>{f(true),s(null);try{let c=await fetch(`${r}/api/v2/stacks/${u}`);if(!c.ok)return s("Stack not found"),f(!1),null;let h=await c.json(),t=h.data?.stack||h.stack||h,g={id:t.id,name:t.name,displayName:t.displayName||t.name,description:t.description,logoUrl:t.logoUrl,webPageUrl:t.webPageUrl,allowedChains:t.allowedChains||[],features:t.features,stripeProvider:t.stripeProvider,oauthProviders:t.oauthProviders?.map(n=>({provider:n.provider,clientId:n.clientId,enabled:n.enabled!==!1}))};return l(g),f(!1),g}catch(c){return s(c.message),f(false),null}},[r]);react.useEffect(()=>{e&&a(e);},[e,a]);let o=i?Te(i):[];return {config:i,loading:d,error:p,identityProviders:o,fetchConfig:a}}function Te(e){let r=[];if(e.features?.web3Auth!==false&&(e.allowedChains.includes("solana")&&(r.push({type:"wallet",id:"phantom",name:"Phantom",chain:"solana"}),r.push({type:"wallet",id:"solflare",name:"Solflare",chain:"solana"})),(e.allowedChains.includes("ethereum")||e.allowedChains.includes("polygon")||e.allowedChains.includes("base"))&&r.push({type:"wallet",id:"metamask",name:"MetaMask",chain:"ethereum"})),e.features?.apiKeyAuth!==false&&r.push({type:"otp",id:"otp",name:"Access Code"}),e.features?.oauthAuth&&e.oauthProviders)for(let i of e.oauthProviders)i.enabled&&r.push({type:"oauth",id:i.provider,name:i.provider});return r}function Ue(e=""){let[r,i]=react.useState([]),[l,d]=react.useState(true),[f,p]=react.useState(null),s=react.useCallback(async()=>{try{let a=await fetch(`${e}/api/billing/plans`);if(a.ok){let o=await a.json();i(o.plans||o||[]);}}catch(a){p(a.message);}finally{d(false);}},[e]);return react.useEffect(()=>{s();},[s]),{plans:r,loading:l,error:f,refresh:s}}function _e(e=""){let[r,i]=react.useState(null),[l,d]=react.useState(true),[f,p]=react.useState(null),s=react.useCallback(async()=>{try{let u=await fetch(`${e}/api/billing/subscription`);if(u.ok){let c=await u.json();i(c.plan?c:null);}}catch(u){p(u.message);}finally{d(false);}},[e]);react.useEffect(()=>{s();},[s]);let a=react.useCallback(async u=>{let c=U(),t=await(await fetch(`${e}/api/billing/subscribe`,{method:"POST",headers:{"Content-Type":"application/json",...c?{"x-csrf-token":c}:{}},body:JSON.stringify({planId:u})})).json();return t.url||t.checkoutUrl||null},[e]),o=react.useCallback(async()=>{let u=U();return (await fetch(`${e}/api/billing/cancel`,{method:"POST",headers:u?{"x-csrf-token":u}:{}})).ok?(await s(),true):false},[e,s]);return {subscription:r,loading:l,error:f,refresh:s,subscribe:a,cancel:o}}function Ne(e=""){let[r,i]=react.useState(null),[l,d]=react.useState(true),[f,p]=react.useState(null),s=react.useCallback(async()=>{try{let a=await fetch(`${e}/api/billing/usage`);if(a.ok){let o=await a.json();i(o);}}catch(a){p(a.message);}finally{d(false);}},[e]);return react.useEffect(()=>{s();},[s]),{usage:r,loading:l,error:f,refresh:s}}function Me(e=""){let[r,i]=react.useState(false),[l,d]=react.useState(null),f=react.useCallback(async s=>{i(true),d(null);try{let a=U(),o=await fetch(`${e}/api/billing/prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...a?{"x-csrf-token":a}:{}},body:JSON.stringify({amountCents:s})}),u=await o.json();return o.ok?u.url||null:(d(u.error||"Purchase failed"),null)}catch(a){return d(a.message),null}finally{i(false);}},[e]),p=react.useCallback(async s=>{i(true),d(null);try{let a=U(),o=await fetch(`${e}/api/billing/verify-prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...a?{"x-csrf-token":a}:{}},body:JSON.stringify({sessionId:s})}),u=await o.json();return o.ok?u:(d(u.error||"Verification failed"),null)}catch(a){return d(a.message),null}finally{i(false);}},[e]);return {purchase:f,verifySession:p,loading:r,error:l}}function Ge(e="",r){let[i,l]=react.useState([]),[d,f]=react.useState(true),[p,s]=react.useState(null),a=r?.limit||50,o=r?.offset||0,u=react.useCallback(async()=>{try{let c=await fetch(`${e}/api/billing/history?limit=${a}&offset=${o}`);if(c.ok){let h=await c.json();l(h.records||h.history||(Array.isArray(h)?h:[]));}}catch(c){s(c.message);}finally{f(false);}},[e,a,o]);return react.useEffect(()=>{u();},[u]),{records:i,loading:d,error:p,refresh:u}}function De(){if(typeof document>"u")return null;let e=document.cookie.split(";").map(r=>r.trim()).find(r=>r.startsWith("__csrf="));return e?e.slice(7):null}function Be(e,r){let[i,l]=react.useState(null),[d,f]=react.useState(true),[p,s]=react.useState(false),[a,o]=react.useState(null),u=r?.apiBaseUrl??"",c=r?.scope===void 0||r?.scope==="global"?"global":`stack:${r.scope.stackId}`,h=react.useCallback(n=>{let m=encodeURIComponent(n);if(c==="global")return `${u}/api/user/profile/${m}`;let y=c.slice(6);return `${u}/api/v2/stacks/${encodeURIComponent(y)}/members/${m}/profile`},[u,c]),t=react.useCallback(async()=>{if(!e){l(null),f(false);return}f(true),o(null);try{let n=await fetch(h(e));if(n.ok){let m=await n.json(),y=m.profile||m.data?.profile||m;l({mid:y.mid||e,username:y.username||"",avatarUrl:y.avatar_url||y.avatarUrl,bio:y.bio,paymentAddress:y.payment_address||y.paymentAddress,createdAt:y.created_at||y.createdAt,updatedAt:y.updated_at||y.updatedAt});}else if(n.status===404)l({mid:e,username:""});else throw new Error(`${n.status}`)}catch(n){o(n instanceof Error?n.message:"Failed to load profile");}finally{f(false);}},[e,h]);react.useEffect(()=>{t();},[t]);let g=react.useCallback(async n=>{if(!e)return false;s(true),o(null);try{let m=De(),y={};n.username!==void 0&&(y.username=n.username),n.avatarUrl!==void 0&&(y.avatar_url=n.avatarUrl),n.bio!==void 0&&(y.bio=n.bio),n.paymentAddress!==void 0&&(y.payment_address=n.paymentAddress);let w=await fetch(h(e),{method:"PUT",headers:{"Content-Type":"application/json",...m?{"x-csrf-token":m}:{}},credentials:"same-origin",body:JSON.stringify(y)});if(!w.ok){let S=await w.json().catch(()=>({}));throw new Error(S.error||S.message||`Update failed: ${w.status}`)}let A=await w.json(),C=A.profile||A.data?.profile||A;return l(S=>({mid:S?.mid||e,username:n.username??S?.username??"",avatarUrl:n.avatarUrl??S?.avatarUrl,bio:n.bio??S?.bio,paymentAddress:n.paymentAddress??C.payment_address??C.paymentAddress??S?.paymentAddress,createdAt:S?.createdAt,updatedAt:C.updated_at||C.updatedAt||Date.now()})),!0}catch(m){return o(m instanceof Error?m.message:"Update failed"),false}finally{s(false);}},[e,h]);return {profile:i,loading:d,saving:p,error:a,updateProfile:g,refresh:t}}var ye="google-identity-services",Je="https://accounts.google.com/gsi/client";function He({stackId:e,stacknetUrl:r="https://stacknet.magma-rpc.com",apiBaseUrl:i="",autoPrompt:l=true,cancelOnTapOutside:d=true,onSuccess:f,onError:p,disabled:s=false}){let{config:a}=Y(e,r),{isAuthenticated:o,loading:u,readSession:c}=M(),[h,t]=react.useState(false),[g,n]=react.useState(null),[m,y]=react.useState(false),w=react.useRef(false),A=react.useRef(false),S=a?.oauthProviders?.find(b=>b.provider==="google"&&b.enabled&&b.clientId)?.clientId||null;react.useEffect(()=>{if(s||!S||typeof window>"u")return;if(document.getElementById(ye)){y(true);return}let b=document.createElement("script");b.id=ye,b.src=Je,b.async=true,b.defer=true,b.onload=()=>y(true),b.onerror=()=>{n("Failed to load Google sign-in"),p?.("Failed to load Google Identity Services script");},document.head.appendChild(b);},[s,S,p]);let E=react.useCallback(async b=>{t(true),n(null);try{let I=await fetch(`${i}/api/auth/google/one-tap`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:b.credential,stackId:e})});if(!I.ok){let j=(await I.json().catch(()=>({}))).error||"Google sign-in failed";n(j),p?.(j),t(!1);return}c(),t(!1),f?.();}catch(I){let N=I.message||"Google sign-in failed";n(N),p?.(N),t(false);}},[i,e,c,f,p]);react.useEffect(()=>{if(s){console.debug("[GoogleOneTap] Disabled");return}if(!m){console.debug("[GoogleOneTap] Script not loaded yet, clientId:",S);return}if(!S){console.debug("[GoogleOneTap] No Google clientId from stack config");return}if(u){console.debug("[GoogleOneTap] Session still loading");return}if(o){console.debug("[GoogleOneTap] User already authenticated, skipping");return}if(!window.google?.accounts?.id){console.debug("[GoogleOneTap] GIS library not available on window");return}w.current||(console.debug("[GoogleOneTap] Initializing with clientId:",S),w.current=true,window.google.accounts.id.initialize({client_id:S,callback:E,auto_select:true,cancel_on_tap_outside:d}),l&&!A.current&&(A.current=true,console.debug("[GoogleOneTap] Showing prompt..."),window.google.accounts.id.prompt(b=>{b.isDisplayed?.()&&console.debug("[GoogleOneTap] Prompt displayed"),b.isNotDisplayed?.()&&console.debug("[GoogleOneTap] Not displayed:",b.getNotDisplayedReason?.()),b.isSkippedMoment?.()&&console.debug("[GoogleOneTap] Skipped:",b.getSkippedReason?.()),b.isDismissedMoment?.()&&console.debug("[GoogleOneTap] Dismissed:",b.getDismissedReason?.());})));},[s,m,S,u,o,E,l,d]),react.useEffect(()=>()=>{window.google?.accounts?.id&&w.current&&window.google.accounts.id.cancel();},[]);let $=react.useCallback(()=>{!window.google?.accounts?.id||!w.current||window.google.accounts.id.prompt();},[]),W=react.useCallback((b,I)=>{!b||!window.google?.accounts?.id||!w.current||window.google.accounts.id.renderButton(b,{theme:I?.theme||"filled_black",size:I?.size||"large",text:I?.text||"signin_with",width:I?.width});},[]);return {available:!!S,ready:m&&!!S,loading:h,error:g,prompt:$,renderButton:W,clientId:S}}
|
|
2
|
-
exports.useAuthBridge=q;exports.useBillingHistory=
|
|
1
|
+
'use strict';var react=require('react');function ce(){if(typeof document>"u")return null;try{let e=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith("stackauth_session="));if(!e)return null;let n=e.slice(18);return JSON.parse(atob(n.replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function x(e="__csrf"){if(typeof document>"u")return null;let n=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith(`${e}=`));return n?n.slice(e.length+1):null}function L(){let[e,n]=react.useState(null),[i,l]=react.useState(true),d=react.useCallback(()=>{let o=ce();o&&o.expiresAt>Date.now()?n({userId:o.userId,address:o.address,chain:o.chain,expiresAt:o.expiresAt,planId:o.planId,authMethod:o.authMethod}):n(null),l(false);},[]);react.useEffect(()=>{d();},[d]);let g=react.useCallback(async(o="")=>{try{let s=await fetch(`${o}/api/auth/session`);if(s.ok){let a=await s.json();if(a.session)return n(a.session),a.session}return n(null),null}catch{return null}},[]),f=!!e&&e.expiresAt>Date.now();return {session:e,loading:i,isAuthenticated:f,refresh:g,readSession:d}}function K(e="__csrf",n="x-csrf-token"){let[i,l]=react.useState(null);react.useEffect(()=>{l(x(e));},[e]);let d=i?{[n]:i}:{};return {token:i,headers:d}}function V(){let[e,n]=react.useState({connected:false,address:null,chain:null,provider:null}),[i,l]=react.useState(null),d=react.useCallback(async(s="phantom")=>{l(null);try{let a=typeof window<"u"?window:null,c=s==="phantom"?a?.phantom?.solana||a?.solana:a?.solflare;if(!c)return l(`${s} wallet not found`),null;let m=(await c.connect()).publicKey.toString();return n({connected:!0,address:m,chain:"solana",provider:s}),m}catch(a){return l(a.message||"Failed to connect wallet"),null}},[]),g=react.useCallback(async()=>{l(null);try{let a=(typeof window<"u"?window:null)?.ethereum;if(!a)return l("MetaMask not found"),null;let c=a;a.providers?.length&&(c=a.providers.find(t=>t.isMetaMask)||a);let m=(await c.request({method:"eth_requestAccounts"}))[0];return m?(n({connected:!0,address:m,chain:"ethereum",provider:"metamask"}),m):(l("No account selected"),null)}catch(s){return l(s.message||"Failed to connect wallet"),null}},[]),f=react.useCallback(async(s,a)=>{l(null);let c=a?.chain||e.chain,u=a?.provider||e.provider,m=a?.address||e.address;try{if(c==="solana"){let t=typeof window<"u"?window:null,p=u==="solflare"?t?.solflare:t?.phantom?.solana||t?.solana;if(!p)throw new Error("Wallet not available");let r=new TextEncoder().encode(s),h=await p.signMessage(r,"utf8"),y=new Uint8Array(h.signature||h),w="";for(let A=0;A<y.byteLength;A++)w+=String.fromCharCode(y[A]);return btoa(w)}if(c==="ethereum"){let p=(typeof window<"u"?window:null)?.ethereum;if(p?.providers?.length&&(p=p.providers.find(h=>h.isMetaMask)||p),!p)throw new Error("MetaMask not available");return await p.request({method:"personal_sign",params:[s,m]})}throw new Error("No wallet connected")}catch(t){return l(t.message||"Signing failed"),null}},[e]),o=react.useCallback(()=>{n({connected:false,address:null,chain:null,provider:null}),l(null);},[]);return {wallet:e,error:i,connectSolana:d,connectEVM:g,signMessage:f,disconnect:o}}var Ae="https://stacknet.magma-rpc.com/auth/bridge",D="stacknet-auth-bridge";function q(e){let n=e?.bridgeUrl||Ae,i=e?.disabled||false,l=react.useRef(null),[d,g]=react.useState({ready:false,known:false,identity:null,identityCount:0,resolvedStackId:null}),f=react.useRef([]),o=react.useRef(false),s=react.useCallback(t=>{let p={...t,protocol:D};o.current&&l.current?.contentWindow?l.current.contentWindow.postMessage(p,new URL(n).origin):f.current.push(p);},[n]);react.useEffect(()=>{if(i)return;let t=r=>{if(!(!r.data||r.data.protocol!==D)){try{if(r.origin!==new URL(n).origin)return}catch{return}switch(r.data.type){case "bridge:ready":o.current=true,g(h=>({...h,ready:true}));for(let h of f.current)l.current?.contentWindow?.postMessage(h,r.origin);f.current=[],l.current?.contentWindow?.postMessage({protocol:D,type:"auth:check"},r.origin),l.current?.contentWindow?.postMessage({protocol:D,type:"auth:resolve-stack"},r.origin);break;case "auth:status":g(h=>({...h,known:r.data.known,identity:r.data.identity,identityCount:r.data.identityCount||0}));break;case "auth:resolved-stack":g(h=>({...h,resolvedStackId:r.data.stackId||null}));break;}}};window.addEventListener("message",t);let p=document.createElement("iframe");return p.src=n,p.style.display="none",p.setAttribute("aria-hidden","true"),p.setAttribute("tabindex","-1"),p.setAttribute("sandbox","allow-scripts allow-same-origin"),document.body.appendChild(p),l.current=p,()=>{window.removeEventListener("message",t),p.parentNode&&p.parentNode.removeChild(p),l.current=null,o.current=false;}},[n,i]);let a=react.useCallback(t=>{s({type:"auth:connected",...t});},[s]),c=react.useCallback(t=>{s({type:"auth:disconnected",...t});},[s]),u=react.useCallback(()=>{s({type:"auth:clear"}),g({ready:d.ready,known:false,identity:null,identityCount:0,resolvedStackId:null});},[s,d.ready]),m=react.useCallback(()=>{s({type:"auth:check"});},[s]);return {...d,reportConnected:a,reportDisconnected:c,clearAll:u,refresh:m}}async function ge(e,n,i,l){let d=e.apiVersion||"v2",g=`${e.baseUrl}/api/${d}${i}`;try{let f=await fetch(g,{method:n,headers:{"Content-Type":"application/json"},body:l?JSON.stringify(l):void 0}),o=await f.json();return f.ok?o.success&&o.data!==void 0?{success:!0,data:o.data}:{success:!0,data:o}:{success:!1,error:o.error||{code:"UNKNOWN_ERROR",message:"Unknown error"}}}catch(f){return {success:false,error:{code:"NETWORK_ERROR",message:f instanceof Error?f.message:"Network error"}}}}function pe(e){return {getNetworkStatus:()=>ge(e,"GET","/network/status"),getWeb3Challenge:(n,i)=>ge(e,"POST",`/stacks/${encodeURIComponent(e.stackId)}/auth/web3/challenge`,{chain:n,address:i})}}function Te(e={apiBaseUrl:""}){let{wallet:n,connectSolana:i,connectEVM:l,signMessage:d,disconnect:g}=V(),{session:f,isAuthenticated:o,refresh:s,readSession:a}=L(),{headers:c}=K(),u=q({disabled:typeof window>"u"}),[m,t]=react.useState(false),[p,r]=react.useState(null),[h,y]=react.useState(false),w=e.apiBaseUrl||"",A=e.stacknetUrl||"https://stacknet.magma-rpc.com",C=e.stackId||u.resolvedStackId||"",S=pe({baseUrl:A,stackId:C}),E=react.useCallback(async(k,P,O,R)=>{t(true),r(null);try{let v=P;if(!v){let B=await O();if(!B)return t(!1),!1;v=B;}let T=await S.getWeb3Challenge(k,v);if(!T.success||!T.data)return r("Failed to get challenge"),t(!1),!1;let U=await d(T.data.message,{chain:k,provider:R,address:v});if(!U)return t(!1),!1;let G={chain:k,message:T.data.message,signature:U,stackId:C};k==="solana"&&(G.publicKey=v);let J=await fetch(`${w}/api/auth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(G)});if(!J.ok){let B=await J.json().catch(()=>({}));return r(B.error||"Authentication failed"),t(!1),!1}return u.reportConnected({address:v,chain:k,method:R||(k==="solana"?"phantom":"metamask"),stackId:C}),a(),t(!1),!0}catch(v){return r(v.message||"Authentication failed"),t(false),false}},[w,S,d,a,u,C]),$=react.useCallback(async(k="phantom")=>{let P=await i(k);return P?E("solana",P,()=>i(k),k):false},[i,E]),W=react.useCallback(async()=>{let k=await l();return k?E("ethereum",k,l,"metamask"):false},[l,E]),b=react.useCallback(async k=>{t(true),r(null);try{let P=await fetch(`${w}/api/auth/otp`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({code:k})});if(!P.ok){let O=await P.json().catch(()=>({}));return r(O.error||"Invalid code"),t(!1),!1}return a(),t(!1),!0}catch(P){return r(P.message||"OTP verification failed"),t(false),false}},[w,a]),I=react.useCallback(async(k,P)=>{t(true),r(null);try{let O=P||`${window.location.origin}/api/auth/oauth/callback`,R=new URLSearchParams({provider:k,redirectUri:O,stackId:C}),v=await fetch(`${w}/api/auth/oauth/start?${R}`,{credentials:"include"});if(!v.ok){let U=await v.json().catch(()=>({}));return r(U.error||"Failed to start OAuth flow"),t(!1),!1}let T=await v.json();if(T.redirect_url){let U;try{U=new URL(T.redirect_url);}catch{return r("Invalid OAuth redirect URL"),t(!1),!1}let G=[/(^|\.)accounts\.google\.com$/,/(^|\.)discord\.com$/,/(^|\.)github\.com$/,/(^|\.)x\.com$/,/(^|\.)twitter\.com$/,/(^|\.)apple\.com$/];return U.protocol!=="https:"||!G.some(J=>J.test(U.hostname))?(r(`Refusing to redirect to non-OAuth host: ${U.hostname}`),t(!1),!1):(typeof sessionStorage<"u"&&(sessionStorage.setItem("oauth_state",T.state||""),sessionStorage.setItem("oauth_provider",k)),window.location.href=U.toString(),!0)}return r("No redirect URL returned"),t(!1),!1}catch(O){return r(O.message||"OAuth flow failed"),t(false),false}},[w,C]),N=react.useCallback(async(k,P,O)=>{t(true),r(null);try{if(typeof sessionStorage<"u"){let v=sessionStorage.getItem("oauth_state"),T=sessionStorage.getItem("oauth_provider");if(sessionStorage.removeItem("oauth_state"),sessionStorage.removeItem("oauth_provider"),!v||v!==O)return r("OAuth state mismatch \u2014 refusing to complete login"),t(!1),!1;if(T&&T!==k)return r("OAuth provider mismatch \u2014 refusing to complete login"),t(!1),!1}let R=await fetch(`${w}/api/auth/oauth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({provider:k,code:P,state:O,stackId:C})});if(!R.ok){let v=await R.json().catch(()=>({}));return r(v.error||"OAuth authentication failed"),t(!1),!1}return a(),t(!1),!0}catch(R){return r(R.message||"OAuth callback failed"),t(false),false}},[w,C,a]),j=react.useCallback(async()=>{n.address&&n.chain&&u.reportDisconnected({address:n.address,chain:n.chain,stackId:C});try{await fetch(`${w}/api/auth/logout`,{method:"POST",headers:c,credentials:"include"});}catch{}g(),a();},[w,c,g,a,n,u,C]);return react.useEffect(()=>{if(!e.autoConnect||h||o||!u.ready||!u.known||!u.identity)return;y(true);let{chain:k,method:P}=u.identity;k==="solana"&&(P==="phantom"||P==="solflare")?$(P):k==="ethereum"&&W();},[e.autoConnect,h,o,u,$,W]),{session:f,isAuthenticated:o,wallet:n,loading:m,error:p,authenticateSolana:$,authenticateEVM:W,authenticateOTP:b,authenticateOAuth:I,authenticateOAuthCallback:N,logout:j,refresh:()=>s(w),stackId:C,bridge:{ready:u.ready,known:u.known,identity:u.identity,identityCount:u.identityCount,resolvedStackId:u.resolvedStackId}}}function Y(e,n="https://stacknet.magma-rpc.com"){let[i,l]=react.useState(null),[d,g]=react.useState(false),[f,o]=react.useState(null),s=react.useCallback(async c=>{g(true),o(null);try{let u=await fetch(`${n}/api/v2/stacks/${c}`);if(!u.ok)return o("Stack not found"),g(!1),null;let m=await u.json(),t=m.data?.stack||m.stack||m,p={id:t.id,name:t.name,displayName:t.displayName||t.name,description:t.description,logoUrl:t.logoUrl,webPageUrl:t.webPageUrl,allowedChains:t.allowedChains||[],features:t.features,stripeProvider:t.stripeProvider,oauthProviders:t.oauthProviders?.map(r=>({provider:r.provider,clientId:r.clientId,enabled:r.enabled!==!1}))};return l(p),g(!1),p}catch(u){return o(u.message),g(false),null}},[n]);react.useEffect(()=>{e&&s(e);},[e,s]);let a=i?Re(i):[];return {config:i,loading:d,error:f,identityProviders:a,fetchConfig:s}}function Re(e){let n=[];if(e.features?.web3Auth!==false&&(e.allowedChains.includes("solana")&&(n.push({type:"wallet",id:"phantom",name:"Phantom",chain:"solana"}),n.push({type:"wallet",id:"solflare",name:"Solflare",chain:"solana"})),(e.allowedChains.includes("ethereum")||e.allowedChains.includes("polygon")||e.allowedChains.includes("base"))&&n.push({type:"wallet",id:"metamask",name:"MetaMask",chain:"ethereum"})),e.features?.apiKeyAuth!==false&&n.push({type:"otp",id:"otp",name:"Access Code"}),e.features?.oauthAuth&&e.oauthProviders)for(let i of e.oauthProviders)i.enabled&&n.push({type:"oauth",id:i.provider,name:i.provider});return n}function _e(e=""){let[n,i]=react.useState([]),[l,d]=react.useState(true),[g,f]=react.useState(null),o=react.useCallback(async()=>{try{let s=await fetch(`${e}/api/billing/plans`);if(s.ok){let a=await s.json();i(a.plans||a||[]);}}catch(s){f(s.message);}finally{d(false);}},[e]);return react.useEffect(()=>{o();},[o]),{plans:n,loading:l,error:g,refresh:o}}function We(e=""){let[n,i]=react.useState(null),[l,d]=react.useState(true),[g,f]=react.useState(null),o=react.useCallback(async()=>{try{let c=await fetch(`${e}/api/billing/subscription`);if(c.ok){let u=await c.json();i(u.plan?u:null);}}catch(c){f(c.message);}finally{d(false);}},[e]);react.useEffect(()=>{o();},[o]);let s=react.useCallback(async c=>{let u=x(),t=await(await fetch(`${e}/api/billing/subscribe`,{method:"POST",headers:{"Content-Type":"application/json",...u?{"x-csrf-token":u}:{}},body:JSON.stringify({planId:c})})).json();return t.url||t.checkoutUrl||null},[e]),a=react.useCallback(async()=>{let c=x();return (await fetch(`${e}/api/billing/cancel`,{method:"POST",headers:c?{"x-csrf-token":c}:{}})).ok?(await o(),true):false},[e,o]);return {subscription:n,loading:l,error:g,refresh:o,subscribe:s,cancel:a}}function Me(e=""){let[n,i]=react.useState(null),[l,d]=react.useState(true),[g,f]=react.useState(null),o=react.useCallback(async()=>{try{let s=await fetch(`${e}/api/billing/usage`);if(s.ok){let a=await s.json();i(a);}}catch(s){f(s.message);}finally{d(false);}},[e]);return react.useEffect(()=>{o();},[o]),{usage:n,loading:l,error:g,refresh:o}}function je(e=""){let[n,i]=react.useState(false),[l,d]=react.useState(null),g=react.useCallback(async o=>{i(true),d(null);try{let s=x(),a=await fetch(`${e}/api/billing/prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...s?{"x-csrf-token":s}:{}},body:JSON.stringify({amountCents:o})}),c=await a.json();return a.ok?c.url||null:(d(c.error||"Purchase failed"),null)}catch(s){return d(s.message),null}finally{i(false);}},[e]),f=react.useCallback(async o=>{i(true),d(null);try{let s=x(),a=await fetch(`${e}/api/billing/verify-prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...s?{"x-csrf-token":s}:{}},body:JSON.stringify({sessionId:o})}),c=await a.json();return a.ok?c:(d(c.error||"Verification failed"),null)}catch(s){return d(s.message),null}finally{i(false);}},[e]);return {purchase:g,verifySession:f,loading:n,error:l}}function Be(e="",n){let[i,l]=react.useState([]),[d,g]=react.useState(true),[f,o]=react.useState(null),s=n?.limit||50,a=n?.offset||0,c=react.useCallback(async()=>{try{let u=await fetch(`${e}/api/billing/history?limit=${s}&offset=${a}`);if(u.ok){let m=await u.json();l(m.records||m.history||(Array.isArray(m)?m:[]));}}catch(u){o(u.message);}finally{g(false);}},[e,s,a]);return react.useEffect(()=>{c();},[c]),{records:i,loading:d,error:f,refresh:c}}function De(){if(typeof document>"u")return null;let e=document.cookie.split(";").map(n=>n.trim()).find(n=>n.startsWith("__csrf="));return e?e.slice(7):null}function He(e,n){let[i,l]=react.useState(null),[d,g]=react.useState(true),[f,o]=react.useState(false),[s,a]=react.useState(null),c=n?.apiBaseUrl??"",u=n?.scope===void 0||n?.scope==="global"?"global":`stack:${n.scope.stackId}`,m=react.useCallback(r=>{let h=encodeURIComponent(r);if(u==="global")return `${c}/api/user/profile/${h}`;let y=u.slice(6);return `${c}/api/v2/stacks/${encodeURIComponent(y)}/members/${h}/profile`},[c,u]),t=react.useCallback(async()=>{if(!e){l(null),g(false);return}g(true),a(null);try{let r=await fetch(m(e));if(r.ok){let h=await r.json(),y=h.profile||h.data?.profile||h;l({mid:y.mid||e,username:y.username||"",avatarUrl:y.avatar_url||y.avatarUrl,bio:y.bio,paymentAddress:y.payment_address||y.paymentAddress,createdAt:y.created_at||y.createdAt,updatedAt:y.updated_at||y.updatedAt});}else if(r.status===404)l({mid:e,username:""});else throw new Error(`${r.status}`)}catch(r){a(r instanceof Error?r.message:"Failed to load profile");}finally{g(false);}},[e,m]);react.useEffect(()=>{t();},[t]);let p=react.useCallback(async r=>{if(!e)return false;o(true),a(null);try{let h=De(),y={};r.username!==void 0&&(y.username=r.username),r.avatarUrl!==void 0&&(y.avatar_url=r.avatarUrl),r.bio!==void 0&&(y.bio=r.bio),r.paymentAddress!==void 0&&(y.payment_address=r.paymentAddress);let w=await fetch(m(e),{method:"PUT",headers:{"Content-Type":"application/json",...h?{"x-csrf-token":h}:{}},credentials:"same-origin",body:JSON.stringify(y)});if(!w.ok){let S=await w.json().catch(()=>({}));throw new Error(S.error||S.message||`Update failed: ${w.status}`)}let A=await w.json(),C=A.profile||A.data?.profile||A;return l(S=>({mid:S?.mid||e,username:r.username??S?.username??"",avatarUrl:r.avatarUrl??S?.avatarUrl,bio:r.bio??S?.bio,paymentAddress:r.paymentAddress??C.payment_address??C.paymentAddress??S?.paymentAddress,createdAt:S?.createdAt,updatedAt:C.updated_at||C.updatedAt||Date.now()})),!0}catch(h){return a(h instanceof Error?h.message:"Update failed"),false}finally{o(false);}},[e,m]);return {profile:i,loading:d,saving:f,error:s,updateProfile:p,refresh:t}}var be="google-identity-services",Ke="https://accounts.google.com/gsi/client";function Ve({stackId:e,stacknetUrl:n="https://stacknet.magma-rpc.com",apiBaseUrl:i="",autoPrompt:l=true,cancelOnTapOutside:d=true,onSuccess:g,onError:f,disabled:o=false}){let{config:s}=Y(e,n),{isAuthenticated:a,loading:c,readSession:u}=L(),[m,t]=react.useState(false),[p,r]=react.useState(null),[h,y]=react.useState(false),w=react.useRef(false),A=react.useRef(false),S=s?.oauthProviders?.find(b=>b.provider==="google"&&b.enabled&&b.clientId)?.clientId||null;react.useEffect(()=>{if(o||!S||typeof window>"u")return;if(document.getElementById(be)){y(true);return}let b=document.createElement("script");b.id=be,b.src=Ke,b.async=true,b.defer=true,b.onload=()=>y(true),b.onerror=()=>{r("Failed to load Google sign-in"),f?.("Failed to load Google Identity Services script");},document.head.appendChild(b);},[o,S,f]);let E=react.useCallback(async b=>{t(true),r(null);try{let I=await fetch(`${i}/api/auth/google/one-tap`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:b.credential,stackId:e})});if(!I.ok){let j=(await I.json().catch(()=>({}))).error||"Google sign-in failed";r(j),f?.(j),t(!1);return}u(),t(!1),g?.();}catch(I){let N=I.message||"Google sign-in failed";r(N),f?.(N),t(false);}},[i,e,u,g,f]);react.useEffect(()=>{if(o){console.debug("[GoogleOneTap] Disabled");return}if(!h){console.debug("[GoogleOneTap] Script not loaded yet, clientId:",S);return}if(!S){console.debug("[GoogleOneTap] No Google clientId from stack config");return}if(c){console.debug("[GoogleOneTap] Session still loading");return}if(a){console.debug("[GoogleOneTap] User already authenticated, skipping");return}if(!window.google?.accounts?.id){console.debug("[GoogleOneTap] GIS library not available on window");return}w.current||(console.debug("[GoogleOneTap] Initializing with clientId:",S),w.current=true,window.google.accounts.id.initialize({client_id:S,callback:E,auto_select:true,cancel_on_tap_outside:d}),l&&!A.current&&(A.current=true,console.debug("[GoogleOneTap] Showing prompt..."),window.google.accounts.id.prompt(b=>{b.isDisplayed?.()&&console.debug("[GoogleOneTap] Prompt displayed"),b.isNotDisplayed?.()&&console.debug("[GoogleOneTap] Not displayed:",b.getNotDisplayedReason?.()),b.isSkippedMoment?.()&&console.debug("[GoogleOneTap] Skipped:",b.getSkippedReason?.()),b.isDismissedMoment?.()&&console.debug("[GoogleOneTap] Dismissed:",b.getDismissedReason?.());})));},[o,h,S,c,a,E,l,d]),react.useEffect(()=>()=>{window.google?.accounts?.id&&w.current&&window.google.accounts.id.cancel();},[]);let $=react.useCallback(()=>{!window.google?.accounts?.id||!w.current||window.google.accounts.id.prompt();},[]),W=react.useCallback((b,I)=>{!b||!window.google?.accounts?.id||!w.current||window.google.accounts.id.renderButton(b,{theme:I?.theme||"filled_black",size:I?.size||"large",text:I?.text||"signin_with",width:I?.width});},[]);return {available:!!S,ready:h&&!!S,loading:m,error:p,prompt:$,renderButton:W,clientId:S}}var qe="/api";function Qe(e,n){return `${(n||(typeof window<"u"?window.location.origin:"")).replace(/\/$/,"")}/?ref=${encodeURIComponent(e)}`}function Xe(e={}){let{shareBaseUrl:n,autoMint:i=true}=e,[l,d]=react.useState(null),[g,f]=react.useState(true),[o,s]=react.useState(null),a=react.useCallback(async()=>{f(true),s(null);try{let m=i?"POST":"GET",t=await fetch(`${qe}/social/join-code`,{method:m,credentials:"include"});if(!t.ok){d(null),t.status===401?s("not_authenticated"):s(`HTTP ${t.status}`);return}let p=await t.json();d(p?.code??null);}catch(m){s(m?.message||"network_error"),d(null);}finally{f(false);}},[i]);react.useEffect(()=>{a();},[a]);let c=l?Qe(l,n):null,u=react.useCallback(async()=>{if(!c)return false;try{if(typeof navigator<"u"&&navigator.clipboard?.writeText)return await navigator.clipboard.writeText(c),!0}catch{}return false},[c]);return {code:l,shareUrl:c,loading:g,error:o,refresh:a,copyShareLink:u}}
|
|
2
|
+
exports.useAuthBridge=q;exports.useBillingHistory=Be;exports.useCSRFToken=K;exports.useGoogleOneTap=Ve;exports.useJoinCode=Xe;exports.usePlans=_e;exports.usePrepaidCheckout=je;exports.useProfile=He;exports.useSession=L;exports.useStackAuth=Te;exports.useStackConfig=Y;exports.useSubscription=We;exports.useUsage=Me;exports.useWeb3Wallet=V;
|
package/dist/hooks/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PublicSession, W as Web3Chain } from '../auth-
|
|
2
|
-
import { d as UserUtilsConfig, B as BillingPlan, S as Subscription, U as UsageSummary, b as PrepaidVerifyResult, a as BillingRecord } from '../config-
|
|
1
|
+
import { P as PublicSession, W as Web3Chain } from '../auth-c1d7Eji2.cjs';
|
|
2
|
+
import { d as UserUtilsConfig, B as BillingPlan, S as Subscription, U as UsageSummary, b as PrepaidVerifyResult, a as BillingRecord } from '../config-CLzVWDrU.cjs';
|
|
3
3
|
import { ProfileScope, UserProfile, ProfileUpdateInput } from '../types/index.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -296,4 +296,53 @@ declare function useGoogleOneTap({ stackId, stacknetUrl, apiBaseUrl, autoPrompt,
|
|
|
296
296
|
clientId: string | null;
|
|
297
297
|
};
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
/**
|
|
300
|
+
* useJoinCode — fetch (and lazy-mint) the signed-in user's affiliate code.
|
|
301
|
+
*
|
|
302
|
+
* The code is the user's stable invite identifier — backed by stacknet's
|
|
303
|
+
* `user_join_codes` table. A new user who signs up via `?ref=<code>` in
|
|
304
|
+
* the URL has `POST /user/join` called once during auth (see
|
|
305
|
+
* `use-stack-auth.ts`), binding them permanently to the referring user
|
|
306
|
+
* and awarding the referrer 20 social points (decayed per the schedule).
|
|
307
|
+
*
|
|
308
|
+
* This hook is a thin client over `GET /social/join-code` (read-only) and
|
|
309
|
+
* `POST /social/join-code` (fetch-or-mint). On first call we POST so the
|
|
310
|
+
* code exists; subsequent renders read from cached state. `copyShareLink`
|
|
311
|
+
* composes a full invite URL using the configured `stacknetUrl` host.
|
|
312
|
+
*/
|
|
313
|
+
interface UseJoinCodeResult {
|
|
314
|
+
/** The code string (e.g. 'aB3xK9pQ'), or null while loading / when signed out. */
|
|
315
|
+
code: string | null;
|
|
316
|
+
/** Full URL including `?ref=<code>`, suitable for sharing. */
|
|
317
|
+
shareUrl: string | null;
|
|
318
|
+
/** True while the initial fetch/mint is in flight. */
|
|
319
|
+
loading: boolean;
|
|
320
|
+
/** Last error from the fetch/mint call, if any. */
|
|
321
|
+
error: string | null;
|
|
322
|
+
/** Manually re-fetch (usually unnecessary — the code is stable). */
|
|
323
|
+
refresh: () => Promise<void>;
|
|
324
|
+
/**
|
|
325
|
+
* Copy the share URL to the clipboard. Returns true on success. A no-op
|
|
326
|
+
* (returning false) when the browser doesn't support the clipboard API
|
|
327
|
+
* — the caller can fall back to showing the URL inline.
|
|
328
|
+
*/
|
|
329
|
+
copyShareLink: () => Promise<boolean>;
|
|
330
|
+
}
|
|
331
|
+
interface UseJoinCodeConfig {
|
|
332
|
+
/**
|
|
333
|
+
* Base URL of the consumer app. The share link is `${shareBaseUrl}/?ref=<code>`
|
|
334
|
+
* so the landing page hands the code to stacknet via `POST /user/join`
|
|
335
|
+
* on the new user's first auth. Defaults to `window.location.origin`
|
|
336
|
+
* (the page the hook runs on), which is the right choice for most apps.
|
|
337
|
+
*/
|
|
338
|
+
shareBaseUrl?: string;
|
|
339
|
+
/**
|
|
340
|
+
* If false, the hook only reads the existing code (GET) and won't mint
|
|
341
|
+
* one if absent. Useful when you want to show the invite section only
|
|
342
|
+
* after the user has explicitly asked for their code. Default: true.
|
|
343
|
+
*/
|
|
344
|
+
autoMint?: boolean;
|
|
345
|
+
}
|
|
346
|
+
declare function useJoinCode(config?: UseJoinCodeConfig): UseJoinCodeResult;
|
|
347
|
+
|
|
348
|
+
export { type BridgeIdentity, type GoogleOneTapConfig, type StackPublicConfig, type UseJoinCodeConfig, type UseJoinCodeResult, type WalletState, useAuthBridge, useBillingHistory, useCSRFToken, useGoogleOneTap, useJoinCode, usePlans, usePrepaidCheckout, useProfile, useSession, useStackAuth, useStackConfig, useSubscription, useUsage, useWeb3Wallet };
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PublicSession, W as Web3Chain } from '../auth-
|
|
2
|
-
import { d as UserUtilsConfig, B as BillingPlan, S as Subscription, U as UsageSummary, b as PrepaidVerifyResult, a as BillingRecord } from '../config-
|
|
1
|
+
import { P as PublicSession, W as Web3Chain } from '../auth-c1d7Eji2.js';
|
|
2
|
+
import { d as UserUtilsConfig, B as BillingPlan, S as Subscription, U as UsageSummary, b as PrepaidVerifyResult, a as BillingRecord } from '../config-xNca5ufB.js';
|
|
3
3
|
import { ProfileScope, UserProfile, ProfileUpdateInput } from '../types/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -296,4 +296,53 @@ declare function useGoogleOneTap({ stackId, stacknetUrl, apiBaseUrl, autoPrompt,
|
|
|
296
296
|
clientId: string | null;
|
|
297
297
|
};
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
/**
|
|
300
|
+
* useJoinCode — fetch (and lazy-mint) the signed-in user's affiliate code.
|
|
301
|
+
*
|
|
302
|
+
* The code is the user's stable invite identifier — backed by stacknet's
|
|
303
|
+
* `user_join_codes` table. A new user who signs up via `?ref=<code>` in
|
|
304
|
+
* the URL has `POST /user/join` called once during auth (see
|
|
305
|
+
* `use-stack-auth.ts`), binding them permanently to the referring user
|
|
306
|
+
* and awarding the referrer 20 social points (decayed per the schedule).
|
|
307
|
+
*
|
|
308
|
+
* This hook is a thin client over `GET /social/join-code` (read-only) and
|
|
309
|
+
* `POST /social/join-code` (fetch-or-mint). On first call we POST so the
|
|
310
|
+
* code exists; subsequent renders read from cached state. `copyShareLink`
|
|
311
|
+
* composes a full invite URL using the configured `stacknetUrl` host.
|
|
312
|
+
*/
|
|
313
|
+
interface UseJoinCodeResult {
|
|
314
|
+
/** The code string (e.g. 'aB3xK9pQ'), or null while loading / when signed out. */
|
|
315
|
+
code: string | null;
|
|
316
|
+
/** Full URL including `?ref=<code>`, suitable for sharing. */
|
|
317
|
+
shareUrl: string | null;
|
|
318
|
+
/** True while the initial fetch/mint is in flight. */
|
|
319
|
+
loading: boolean;
|
|
320
|
+
/** Last error from the fetch/mint call, if any. */
|
|
321
|
+
error: string | null;
|
|
322
|
+
/** Manually re-fetch (usually unnecessary — the code is stable). */
|
|
323
|
+
refresh: () => Promise<void>;
|
|
324
|
+
/**
|
|
325
|
+
* Copy the share URL to the clipboard. Returns true on success. A no-op
|
|
326
|
+
* (returning false) when the browser doesn't support the clipboard API
|
|
327
|
+
* — the caller can fall back to showing the URL inline.
|
|
328
|
+
*/
|
|
329
|
+
copyShareLink: () => Promise<boolean>;
|
|
330
|
+
}
|
|
331
|
+
interface UseJoinCodeConfig {
|
|
332
|
+
/**
|
|
333
|
+
* Base URL of the consumer app. The share link is `${shareBaseUrl}/?ref=<code>`
|
|
334
|
+
* so the landing page hands the code to stacknet via `POST /user/join`
|
|
335
|
+
* on the new user's first auth. Defaults to `window.location.origin`
|
|
336
|
+
* (the page the hook runs on), which is the right choice for most apps.
|
|
337
|
+
*/
|
|
338
|
+
shareBaseUrl?: string;
|
|
339
|
+
/**
|
|
340
|
+
* If false, the hook only reads the existing code (GET) and won't mint
|
|
341
|
+
* one if absent. Useful when you want to show the invite section only
|
|
342
|
+
* after the user has explicitly asked for their code. Default: true.
|
|
343
|
+
*/
|
|
344
|
+
autoMint?: boolean;
|
|
345
|
+
}
|
|
346
|
+
declare function useJoinCode(config?: UseJoinCodeConfig): UseJoinCodeResult;
|
|
347
|
+
|
|
348
|
+
export { type BridgeIdentity, type GoogleOneTapConfig, type StackPublicConfig, type UseJoinCodeConfig, type UseJoinCodeResult, type WalletState, useAuthBridge, useBillingHistory, useCSRFToken, useGoogleOneTap, useJoinCode, usePlans, usePrepaidCheckout, useProfile, useSession, useStackAuth, useStackConfig, useSubscription, useUsage, useWeb3Wallet };
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {useState,useCallback,useEffect,useRef}from'react';function le(){if(typeof document>"u")return null;try{let e=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith("stackauth_session="));if(!e)return null;let r=e.slice(18);return JSON.parse(atob(r.replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function U(e="__csrf"){if(typeof document>"u")return null;let r=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith(`${e}=`));return r?r.slice(e.length+1):null}function M(){let[e,r]=useState(null),[i,l]=useState(true),d=useCallback(()=>{let s=le();s&&s.expiresAt>Date.now()?r({userId:s.userId,address:s.address,chain:s.chain,expiresAt:s.expiresAt,planId:s.planId,authMethod:s.authMethod}):r(null),l(false);},[]);useEffect(()=>{d();},[d]);let f=useCallback(async(s="")=>{try{let a=await fetch(`${s}/api/auth/session`);if(a.ok){let o=await a.json();if(o.session)return r(o.session),o.session}return r(null),null}catch{return null}},[]),p=!!e&&e.expiresAt>Date.now();return {session:e,loading:i,isAuthenticated:p,refresh:f,readSession:d}}function K(e="__csrf",r="x-csrf-token"){let[i,l]=useState(null);useEffect(()=>{l(U(e));},[e]);let d=i?{[r]:i}:{};return {token:i,headers:d}}function V(){let[e,r]=useState({connected:false,address:null,chain:null,provider:null}),[i,l]=useState(null),d=useCallback(async(a="phantom")=>{l(null);try{let o=typeof window<"u"?window:null,u=a==="phantom"?o?.phantom?.solana||o?.solana:o?.solflare;if(!u)return l(`${a} wallet not found`),null;let h=(await u.connect()).publicKey.toString();return r({connected:!0,address:h,chain:"solana",provider:a}),h}catch(o){return l(o.message||"Failed to connect wallet"),null}},[]),f=useCallback(async()=>{l(null);try{let o=(typeof window<"u"?window:null)?.ethereum;if(!o)return l("MetaMask not found"),null;let u=o;o.providers?.length&&(u=o.providers.find(t=>t.isMetaMask)||o);let h=(await u.request({method:"eth_requestAccounts"}))[0];return h?(r({connected:!0,address:h,chain:"ethereum",provider:"metamask"}),h):(l("No account selected"),null)}catch(a){return l(a.message||"Failed to connect wallet"),null}},[]),p=useCallback(async(a,o)=>{l(null);let u=o?.chain||e.chain,c=o?.provider||e.provider,h=o?.address||e.address;try{if(u==="solana"){let t=typeof window<"u"?window:null,g=c==="solflare"?t?.solflare:t?.phantom?.solana||t?.solana;if(!g)throw new Error("Wallet not available");let n=new TextEncoder().encode(a),m=await g.signMessage(n,"utf8"),y=new Uint8Array(m.signature||m),w="";for(let A=0;A<y.byteLength;A++)w+=String.fromCharCode(y[A]);return btoa(w)}if(u==="ethereum"){let g=(typeof window<"u"?window:null)?.ethereum;if(g?.providers?.length&&(g=g.providers.find(m=>m.isMetaMask)||g),!g)throw new Error("MetaMask not available");return await g.request({method:"personal_sign",params:[a,h]})}throw new Error("No wallet connected")}catch(t){return l(t.message||"Signing failed"),null}},[e]),s=useCallback(()=>{r({connected:false,address:null,chain:null,provider:null}),l(null);},[]);return {wallet:e,error:i,connectSolana:d,connectEVM:f,signMessage:p,disconnect:s}}var Pe="https://stacknet.magma-rpc.com/auth/bridge",J="stacknet-auth-bridge";function q(e){let r=e?.bridgeUrl||Pe,i=e?.disabled||false,l=useRef(null),[d,f]=useState({ready:false,known:false,identity:null,identityCount:0,resolvedStackId:null}),p=useRef([]),s=useRef(false),a=useCallback(t=>{let g={...t,protocol:J};s.current&&l.current?.contentWindow?l.current.contentWindow.postMessage(g,new URL(r).origin):p.current.push(g);},[r]);useEffect(()=>{if(i)return;let t=n=>{if(!(!n.data||n.data.protocol!==J)){try{if(n.origin!==new URL(r).origin)return}catch{return}switch(n.data.type){case "bridge:ready":s.current=true,f(m=>({...m,ready:true}));for(let m of p.current)l.current?.contentWindow?.postMessage(m,n.origin);p.current=[],l.current?.contentWindow?.postMessage({protocol:J,type:"auth:check"},n.origin),l.current?.contentWindow?.postMessage({protocol:J,type:"auth:resolve-stack"},n.origin);break;case "auth:status":f(m=>({...m,known:n.data.known,identity:n.data.identity,identityCount:n.data.identityCount||0}));break;case "auth:resolved-stack":f(m=>({...m,resolvedStackId:n.data.stackId||null}));break;}}};window.addEventListener("message",t);let g=document.createElement("iframe");return g.src=r,g.style.display="none",g.setAttribute("aria-hidden","true"),g.setAttribute("tabindex","-1"),g.setAttribute("sandbox","allow-scripts allow-same-origin"),document.body.appendChild(g),l.current=g,()=>{window.removeEventListener("message",t),g.parentNode&&g.parentNode.removeChild(g),l.current=null,s.current=false;}},[r,i]);let o=useCallback(t=>{a({type:"auth:connected",...t});},[a]),u=useCallback(t=>{a({type:"auth:disconnected",...t});},[a]),c=useCallback(()=>{a({type:"auth:clear"}),f({ready:d.ready,known:false,identity:null,identityCount:0,resolvedStackId:null});},[a,d.ready]),h=useCallback(()=>{a({type:"auth:check"});},[a]);return {...d,reportConnected:o,reportDisconnected:u,clearAll:c,refresh:h}}async function fe(e,r,i,l){let d=e.apiVersion||"v2",f=`${e.baseUrl}/api/${d}${i}`;try{let p=await fetch(f,{method:r,headers:{"Content-Type":"application/json"},body:l?JSON.stringify(l):void 0}),s=await p.json();return p.ok?s.success&&s.data!==void 0?{success:!0,data:s.data}:{success:!0,data:s}:{success:!1,error:s.error||{code:"UNKNOWN_ERROR",message:"Unknown error"}}}catch(p){return {success:false,error:{code:"NETWORK_ERROR",message:p instanceof Error?p.message:"Network error"}}}}function pe(e){return {getNetworkStatus:()=>fe(e,"GET","/network/status"),getWeb3Challenge:(r,i)=>fe(e,"POST",`/stacks/${encodeURIComponent(e.stackId)}/auth/web3/challenge`,{chain:r,address:i})}}function Ae(e={apiBaseUrl:""}){let{wallet:r,connectSolana:i,connectEVM:l,signMessage:d,disconnect:f}=V(),{session:p,isAuthenticated:s,refresh:a,readSession:o}=M(),{headers:u}=K(),c=q({disabled:typeof window>"u"}),[h,t]=useState(false),[g,n]=useState(null),[m,y]=useState(false),w=e.apiBaseUrl||"",A=e.stacknetUrl||"https://stacknet.magma-rpc.com",C=e.stackId||c.resolvedStackId||"",S=pe({baseUrl:A,stackId:C}),E=useCallback(async(k,P,T,x)=>{t(true),n(null);try{let v=P;if(!v){let D=await T();if(!D)return t(!1),!1;v=D;}let O=await S.getWeb3Challenge(k,v);if(!O.success||!O.data)return n("Failed to get challenge"),t(!1),!1;let R=await d(O.data.message,{chain:k,provider:x,address:v});if(!R)return t(!1),!1;let G={chain:k,message:O.data.message,signature:R,stackId:C};k==="solana"&&(G.publicKey=v);let F=await fetch(`${w}/api/auth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(G)});if(!F.ok){let D=await F.json().catch(()=>({}));return n(D.error||"Authentication failed"),t(!1),!1}return c.reportConnected({address:v,chain:k,method:x||(k==="solana"?"phantom":"metamask"),stackId:C}),o(),t(!1),!0}catch(v){return n(v.message||"Authentication failed"),t(false),false}},[w,S,d,o,c,C]),$=useCallback(async(k="phantom")=>{let P=await i(k);return P?E("solana",P,()=>i(k),k):false},[i,E]),W=useCallback(async()=>{let k=await l();return k?E("ethereum",k,l,"metamask"):false},[l,E]),b=useCallback(async k=>{t(true),n(null);try{let P=await fetch(`${w}/api/auth/otp`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({code:k})});if(!P.ok){let T=await P.json().catch(()=>({}));return n(T.error||"Invalid code"),t(!1),!1}return o(),t(!1),!0}catch(P){return n(P.message||"OTP verification failed"),t(false),false}},[w,o]),I=useCallback(async(k,P)=>{t(true),n(null);try{let T=P||`${window.location.origin}/api/auth/oauth/callback`,x=new URLSearchParams({provider:k,redirectUri:T,stackId:C}),v=await fetch(`${w}/api/auth/oauth/start?${x}`,{credentials:"include"});if(!v.ok){let R=await v.json().catch(()=>({}));return n(R.error||"Failed to start OAuth flow"),t(!1),!1}let O=await v.json();if(O.redirect_url){let R;try{R=new URL(O.redirect_url);}catch{return n("Invalid OAuth redirect URL"),t(!1),!1}let G=[/(^|\.)accounts\.google\.com$/,/(^|\.)discord\.com$/,/(^|\.)github\.com$/,/(^|\.)x\.com$/,/(^|\.)twitter\.com$/,/(^|\.)apple\.com$/];return R.protocol!=="https:"||!G.some(F=>F.test(R.hostname))?(n(`Refusing to redirect to non-OAuth host: ${R.hostname}`),t(!1),!1):(typeof sessionStorage<"u"&&(sessionStorage.setItem("oauth_state",O.state||""),sessionStorage.setItem("oauth_provider",k)),window.location.href=R.toString(),!0)}return n("No redirect URL returned"),t(!1),!1}catch(T){return n(T.message||"OAuth flow failed"),t(false),false}},[w,C]),N=useCallback(async(k,P,T)=>{t(true),n(null);try{if(typeof sessionStorage<"u"){let v=sessionStorage.getItem("oauth_state"),O=sessionStorage.getItem("oauth_provider");if(sessionStorage.removeItem("oauth_state"),sessionStorage.removeItem("oauth_provider"),!v||v!==T)return n("OAuth state mismatch \u2014 refusing to complete login"),t(!1),!1;if(O&&O!==k)return n("OAuth provider mismatch \u2014 refusing to complete login"),t(!1),!1}let x=await fetch(`${w}/api/auth/oauth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({provider:k,code:P,state:T,stackId:C})});if(!x.ok){let v=await x.json().catch(()=>({}));return n(v.error||"OAuth authentication failed"),t(!1),!1}return o(),t(!1),!0}catch(x){return n(x.message||"OAuth callback failed"),t(false),false}},[w,C,o]),j=useCallback(async()=>{r.address&&r.chain&&c.reportDisconnected({address:r.address,chain:r.chain,stackId:C});try{await fetch(`${w}/api/auth/logout`,{method:"POST",headers:u,credentials:"include"});}catch{}f(),o();},[w,u,f,o,r,c,C]);return useEffect(()=>{if(!e.autoConnect||m||s||!c.ready||!c.known||!c.identity)return;y(true);let{chain:k,method:P}=c.identity;k==="solana"&&(P==="phantom"||P==="solflare")?$(P):k==="ethereum"&&W();},[e.autoConnect,m,s,c,$,W]),{session:p,isAuthenticated:s,wallet:r,loading:h,error:g,authenticateSolana:$,authenticateEVM:W,authenticateOTP:b,authenticateOAuth:I,authenticateOAuthCallback:N,logout:j,refresh:()=>a(w),stackId:C,bridge:{ready:c.ready,known:c.known,identity:c.identity,identityCount:c.identityCount,resolvedStackId:c.resolvedStackId}}}function Y(e,r="https://stacknet.magma-rpc.com"){let[i,l]=useState(null),[d,f]=useState(false),[p,s]=useState(null),a=useCallback(async u=>{f(true),s(null);try{let c=await fetch(`${r}/api/v2/stacks/${u}`);if(!c.ok)return s("Stack not found"),f(!1),null;let h=await c.json(),t=h.data?.stack||h.stack||h,g={id:t.id,name:t.name,displayName:t.displayName||t.name,description:t.description,logoUrl:t.logoUrl,webPageUrl:t.webPageUrl,allowedChains:t.allowedChains||[],features:t.features,stripeProvider:t.stripeProvider,oauthProviders:t.oauthProviders?.map(n=>({provider:n.provider,clientId:n.clientId,enabled:n.enabled!==!1}))};return l(g),f(!1),g}catch(c){return s(c.message),f(false),null}},[r]);useEffect(()=>{e&&a(e);},[e,a]);let o=i?Te(i):[];return {config:i,loading:d,error:p,identityProviders:o,fetchConfig:a}}function Te(e){let r=[];if(e.features?.web3Auth!==false&&(e.allowedChains.includes("solana")&&(r.push({type:"wallet",id:"phantom",name:"Phantom",chain:"solana"}),r.push({type:"wallet",id:"solflare",name:"Solflare",chain:"solana"})),(e.allowedChains.includes("ethereum")||e.allowedChains.includes("polygon")||e.allowedChains.includes("base"))&&r.push({type:"wallet",id:"metamask",name:"MetaMask",chain:"ethereum"})),e.features?.apiKeyAuth!==false&&r.push({type:"otp",id:"otp",name:"Access Code"}),e.features?.oauthAuth&&e.oauthProviders)for(let i of e.oauthProviders)i.enabled&&r.push({type:"oauth",id:i.provider,name:i.provider});return r}function Ue(e=""){let[r,i]=useState([]),[l,d]=useState(true),[f,p]=useState(null),s=useCallback(async()=>{try{let a=await fetch(`${e}/api/billing/plans`);if(a.ok){let o=await a.json();i(o.plans||o||[]);}}catch(a){p(a.message);}finally{d(false);}},[e]);return useEffect(()=>{s();},[s]),{plans:r,loading:l,error:f,refresh:s}}function _e(e=""){let[r,i]=useState(null),[l,d]=useState(true),[f,p]=useState(null),s=useCallback(async()=>{try{let u=await fetch(`${e}/api/billing/subscription`);if(u.ok){let c=await u.json();i(c.plan?c:null);}}catch(u){p(u.message);}finally{d(false);}},[e]);useEffect(()=>{s();},[s]);let a=useCallback(async u=>{let c=U(),t=await(await fetch(`${e}/api/billing/subscribe`,{method:"POST",headers:{"Content-Type":"application/json",...c?{"x-csrf-token":c}:{}},body:JSON.stringify({planId:u})})).json();return t.url||t.checkoutUrl||null},[e]),o=useCallback(async()=>{let u=U();return (await fetch(`${e}/api/billing/cancel`,{method:"POST",headers:u?{"x-csrf-token":u}:{}})).ok?(await s(),true):false},[e,s]);return {subscription:r,loading:l,error:f,refresh:s,subscribe:a,cancel:o}}function Ne(e=""){let[r,i]=useState(null),[l,d]=useState(true),[f,p]=useState(null),s=useCallback(async()=>{try{let a=await fetch(`${e}/api/billing/usage`);if(a.ok){let o=await a.json();i(o);}}catch(a){p(a.message);}finally{d(false);}},[e]);return useEffect(()=>{s();},[s]),{usage:r,loading:l,error:f,refresh:s}}function Me(e=""){let[r,i]=useState(false),[l,d]=useState(null),f=useCallback(async s=>{i(true),d(null);try{let a=U(),o=await fetch(`${e}/api/billing/prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...a?{"x-csrf-token":a}:{}},body:JSON.stringify({amountCents:s})}),u=await o.json();return o.ok?u.url||null:(d(u.error||"Purchase failed"),null)}catch(a){return d(a.message),null}finally{i(false);}},[e]),p=useCallback(async s=>{i(true),d(null);try{let a=U(),o=await fetch(`${e}/api/billing/verify-prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...a?{"x-csrf-token":a}:{}},body:JSON.stringify({sessionId:s})}),u=await o.json();return o.ok?u:(d(u.error||"Verification failed"),null)}catch(a){return d(a.message),null}finally{i(false);}},[e]);return {purchase:f,verifySession:p,loading:r,error:l}}function Ge(e="",r){let[i,l]=useState([]),[d,f]=useState(true),[p,s]=useState(null),a=r?.limit||50,o=r?.offset||0,u=useCallback(async()=>{try{let c=await fetch(`${e}/api/billing/history?limit=${a}&offset=${o}`);if(c.ok){let h=await c.json();l(h.records||h.history||(Array.isArray(h)?h:[]));}}catch(c){s(c.message);}finally{f(false);}},[e,a,o]);return useEffect(()=>{u();},[u]),{records:i,loading:d,error:p,refresh:u}}function De(){if(typeof document>"u")return null;let e=document.cookie.split(";").map(r=>r.trim()).find(r=>r.startsWith("__csrf="));return e?e.slice(7):null}function Be(e,r){let[i,l]=useState(null),[d,f]=useState(true),[p,s]=useState(false),[a,o]=useState(null),u=r?.apiBaseUrl??"",c=r?.scope===void 0||r?.scope==="global"?"global":`stack:${r.scope.stackId}`,h=useCallback(n=>{let m=encodeURIComponent(n);if(c==="global")return `${u}/api/user/profile/${m}`;let y=c.slice(6);return `${u}/api/v2/stacks/${encodeURIComponent(y)}/members/${m}/profile`},[u,c]),t=useCallback(async()=>{if(!e){l(null),f(false);return}f(true),o(null);try{let n=await fetch(h(e));if(n.ok){let m=await n.json(),y=m.profile||m.data?.profile||m;l({mid:y.mid||e,username:y.username||"",avatarUrl:y.avatar_url||y.avatarUrl,bio:y.bio,paymentAddress:y.payment_address||y.paymentAddress,createdAt:y.created_at||y.createdAt,updatedAt:y.updated_at||y.updatedAt});}else if(n.status===404)l({mid:e,username:""});else throw new Error(`${n.status}`)}catch(n){o(n instanceof Error?n.message:"Failed to load profile");}finally{f(false);}},[e,h]);useEffect(()=>{t();},[t]);let g=useCallback(async n=>{if(!e)return false;s(true),o(null);try{let m=De(),y={};n.username!==void 0&&(y.username=n.username),n.avatarUrl!==void 0&&(y.avatar_url=n.avatarUrl),n.bio!==void 0&&(y.bio=n.bio),n.paymentAddress!==void 0&&(y.payment_address=n.paymentAddress);let w=await fetch(h(e),{method:"PUT",headers:{"Content-Type":"application/json",...m?{"x-csrf-token":m}:{}},credentials:"same-origin",body:JSON.stringify(y)});if(!w.ok){let S=await w.json().catch(()=>({}));throw new Error(S.error||S.message||`Update failed: ${w.status}`)}let A=await w.json(),C=A.profile||A.data?.profile||A;return l(S=>({mid:S?.mid||e,username:n.username??S?.username??"",avatarUrl:n.avatarUrl??S?.avatarUrl,bio:n.bio??S?.bio,paymentAddress:n.paymentAddress??C.payment_address??C.paymentAddress??S?.paymentAddress,createdAt:S?.createdAt,updatedAt:C.updated_at||C.updatedAt||Date.now()})),!0}catch(m){return o(m instanceof Error?m.message:"Update failed"),false}finally{s(false);}},[e,h]);return {profile:i,loading:d,saving:p,error:a,updateProfile:g,refresh:t}}var ye="google-identity-services",Je="https://accounts.google.com/gsi/client";function He({stackId:e,stacknetUrl:r="https://stacknet.magma-rpc.com",apiBaseUrl:i="",autoPrompt:l=true,cancelOnTapOutside:d=true,onSuccess:f,onError:p,disabled:s=false}){let{config:a}=Y(e,r),{isAuthenticated:o,loading:u,readSession:c}=M(),[h,t]=useState(false),[g,n]=useState(null),[m,y]=useState(false),w=useRef(false),A=useRef(false),S=a?.oauthProviders?.find(b=>b.provider==="google"&&b.enabled&&b.clientId)?.clientId||null;useEffect(()=>{if(s||!S||typeof window>"u")return;if(document.getElementById(ye)){y(true);return}let b=document.createElement("script");b.id=ye,b.src=Je,b.async=true,b.defer=true,b.onload=()=>y(true),b.onerror=()=>{n("Failed to load Google sign-in"),p?.("Failed to load Google Identity Services script");},document.head.appendChild(b);},[s,S,p]);let E=useCallback(async b=>{t(true),n(null);try{let I=await fetch(`${i}/api/auth/google/one-tap`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:b.credential,stackId:e})});if(!I.ok){let j=(await I.json().catch(()=>({}))).error||"Google sign-in failed";n(j),p?.(j),t(!1);return}c(),t(!1),f?.();}catch(I){let N=I.message||"Google sign-in failed";n(N),p?.(N),t(false);}},[i,e,c,f,p]);useEffect(()=>{if(s){console.debug("[GoogleOneTap] Disabled");return}if(!m){console.debug("[GoogleOneTap] Script not loaded yet, clientId:",S);return}if(!S){console.debug("[GoogleOneTap] No Google clientId from stack config");return}if(u){console.debug("[GoogleOneTap] Session still loading");return}if(o){console.debug("[GoogleOneTap] User already authenticated, skipping");return}if(!window.google?.accounts?.id){console.debug("[GoogleOneTap] GIS library not available on window");return}w.current||(console.debug("[GoogleOneTap] Initializing with clientId:",S),w.current=true,window.google.accounts.id.initialize({client_id:S,callback:E,auto_select:true,cancel_on_tap_outside:d}),l&&!A.current&&(A.current=true,console.debug("[GoogleOneTap] Showing prompt..."),window.google.accounts.id.prompt(b=>{b.isDisplayed?.()&&console.debug("[GoogleOneTap] Prompt displayed"),b.isNotDisplayed?.()&&console.debug("[GoogleOneTap] Not displayed:",b.getNotDisplayedReason?.()),b.isSkippedMoment?.()&&console.debug("[GoogleOneTap] Skipped:",b.getSkippedReason?.()),b.isDismissedMoment?.()&&console.debug("[GoogleOneTap] Dismissed:",b.getDismissedReason?.());})));},[s,m,S,u,o,E,l,d]),useEffect(()=>()=>{window.google?.accounts?.id&&w.current&&window.google.accounts.id.cancel();},[]);let $=useCallback(()=>{!window.google?.accounts?.id||!w.current||window.google.accounts.id.prompt();},[]),W=useCallback((b,I)=>{!b||!window.google?.accounts?.id||!w.current||window.google.accounts.id.renderButton(b,{theme:I?.theme||"filled_black",size:I?.size||"large",text:I?.text||"signin_with",width:I?.width});},[]);return {available:!!S,ready:m&&!!S,loading:h,error:g,prompt:$,renderButton:W,clientId:S}}
|
|
2
|
-
export{q as useAuthBridge,
|
|
1
|
+
import {useState,useCallback,useEffect,useRef}from'react';function ce(){if(typeof document>"u")return null;try{let e=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith("stackauth_session="));if(!e)return null;let n=e.slice(18);return JSON.parse(atob(n.replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function x(e="__csrf"){if(typeof document>"u")return null;let n=document.cookie.split(";").map(i=>i.trim()).find(i=>i.startsWith(`${e}=`));return n?n.slice(e.length+1):null}function L(){let[e,n]=useState(null),[i,l]=useState(true),d=useCallback(()=>{let o=ce();o&&o.expiresAt>Date.now()?n({userId:o.userId,address:o.address,chain:o.chain,expiresAt:o.expiresAt,planId:o.planId,authMethod:o.authMethod}):n(null),l(false);},[]);useEffect(()=>{d();},[d]);let g=useCallback(async(o="")=>{try{let s=await fetch(`${o}/api/auth/session`);if(s.ok){let a=await s.json();if(a.session)return n(a.session),a.session}return n(null),null}catch{return null}},[]),f=!!e&&e.expiresAt>Date.now();return {session:e,loading:i,isAuthenticated:f,refresh:g,readSession:d}}function K(e="__csrf",n="x-csrf-token"){let[i,l]=useState(null);useEffect(()=>{l(x(e));},[e]);let d=i?{[n]:i}:{};return {token:i,headers:d}}function V(){let[e,n]=useState({connected:false,address:null,chain:null,provider:null}),[i,l]=useState(null),d=useCallback(async(s="phantom")=>{l(null);try{let a=typeof window<"u"?window:null,c=s==="phantom"?a?.phantom?.solana||a?.solana:a?.solflare;if(!c)return l(`${s} wallet not found`),null;let m=(await c.connect()).publicKey.toString();return n({connected:!0,address:m,chain:"solana",provider:s}),m}catch(a){return l(a.message||"Failed to connect wallet"),null}},[]),g=useCallback(async()=>{l(null);try{let a=(typeof window<"u"?window:null)?.ethereum;if(!a)return l("MetaMask not found"),null;let c=a;a.providers?.length&&(c=a.providers.find(t=>t.isMetaMask)||a);let m=(await c.request({method:"eth_requestAccounts"}))[0];return m?(n({connected:!0,address:m,chain:"ethereum",provider:"metamask"}),m):(l("No account selected"),null)}catch(s){return l(s.message||"Failed to connect wallet"),null}},[]),f=useCallback(async(s,a)=>{l(null);let c=a?.chain||e.chain,u=a?.provider||e.provider,m=a?.address||e.address;try{if(c==="solana"){let t=typeof window<"u"?window:null,p=u==="solflare"?t?.solflare:t?.phantom?.solana||t?.solana;if(!p)throw new Error("Wallet not available");let r=new TextEncoder().encode(s),h=await p.signMessage(r,"utf8"),y=new Uint8Array(h.signature||h),w="";for(let A=0;A<y.byteLength;A++)w+=String.fromCharCode(y[A]);return btoa(w)}if(c==="ethereum"){let p=(typeof window<"u"?window:null)?.ethereum;if(p?.providers?.length&&(p=p.providers.find(h=>h.isMetaMask)||p),!p)throw new Error("MetaMask not available");return await p.request({method:"personal_sign",params:[s,m]})}throw new Error("No wallet connected")}catch(t){return l(t.message||"Signing failed"),null}},[e]),o=useCallback(()=>{n({connected:false,address:null,chain:null,provider:null}),l(null);},[]);return {wallet:e,error:i,connectSolana:d,connectEVM:g,signMessage:f,disconnect:o}}var Ae="https://stacknet.magma-rpc.com/auth/bridge",D="stacknet-auth-bridge";function q(e){let n=e?.bridgeUrl||Ae,i=e?.disabled||false,l=useRef(null),[d,g]=useState({ready:false,known:false,identity:null,identityCount:0,resolvedStackId:null}),f=useRef([]),o=useRef(false),s=useCallback(t=>{let p={...t,protocol:D};o.current&&l.current?.contentWindow?l.current.contentWindow.postMessage(p,new URL(n).origin):f.current.push(p);},[n]);useEffect(()=>{if(i)return;let t=r=>{if(!(!r.data||r.data.protocol!==D)){try{if(r.origin!==new URL(n).origin)return}catch{return}switch(r.data.type){case "bridge:ready":o.current=true,g(h=>({...h,ready:true}));for(let h of f.current)l.current?.contentWindow?.postMessage(h,r.origin);f.current=[],l.current?.contentWindow?.postMessage({protocol:D,type:"auth:check"},r.origin),l.current?.contentWindow?.postMessage({protocol:D,type:"auth:resolve-stack"},r.origin);break;case "auth:status":g(h=>({...h,known:r.data.known,identity:r.data.identity,identityCount:r.data.identityCount||0}));break;case "auth:resolved-stack":g(h=>({...h,resolvedStackId:r.data.stackId||null}));break;}}};window.addEventListener("message",t);let p=document.createElement("iframe");return p.src=n,p.style.display="none",p.setAttribute("aria-hidden","true"),p.setAttribute("tabindex","-1"),p.setAttribute("sandbox","allow-scripts allow-same-origin"),document.body.appendChild(p),l.current=p,()=>{window.removeEventListener("message",t),p.parentNode&&p.parentNode.removeChild(p),l.current=null,o.current=false;}},[n,i]);let a=useCallback(t=>{s({type:"auth:connected",...t});},[s]),c=useCallback(t=>{s({type:"auth:disconnected",...t});},[s]),u=useCallback(()=>{s({type:"auth:clear"}),g({ready:d.ready,known:false,identity:null,identityCount:0,resolvedStackId:null});},[s,d.ready]),m=useCallback(()=>{s({type:"auth:check"});},[s]);return {...d,reportConnected:a,reportDisconnected:c,clearAll:u,refresh:m}}async function ge(e,n,i,l){let d=e.apiVersion||"v2",g=`${e.baseUrl}/api/${d}${i}`;try{let f=await fetch(g,{method:n,headers:{"Content-Type":"application/json"},body:l?JSON.stringify(l):void 0}),o=await f.json();return f.ok?o.success&&o.data!==void 0?{success:!0,data:o.data}:{success:!0,data:o}:{success:!1,error:o.error||{code:"UNKNOWN_ERROR",message:"Unknown error"}}}catch(f){return {success:false,error:{code:"NETWORK_ERROR",message:f instanceof Error?f.message:"Network error"}}}}function pe(e){return {getNetworkStatus:()=>ge(e,"GET","/network/status"),getWeb3Challenge:(n,i)=>ge(e,"POST",`/stacks/${encodeURIComponent(e.stackId)}/auth/web3/challenge`,{chain:n,address:i})}}function Te(e={apiBaseUrl:""}){let{wallet:n,connectSolana:i,connectEVM:l,signMessage:d,disconnect:g}=V(),{session:f,isAuthenticated:o,refresh:s,readSession:a}=L(),{headers:c}=K(),u=q({disabled:typeof window>"u"}),[m,t]=useState(false),[p,r]=useState(null),[h,y]=useState(false),w=e.apiBaseUrl||"",A=e.stacknetUrl||"https://stacknet.magma-rpc.com",C=e.stackId||u.resolvedStackId||"",S=pe({baseUrl:A,stackId:C}),E=useCallback(async(k,P,O,R)=>{t(true),r(null);try{let v=P;if(!v){let B=await O();if(!B)return t(!1),!1;v=B;}let T=await S.getWeb3Challenge(k,v);if(!T.success||!T.data)return r("Failed to get challenge"),t(!1),!1;let U=await d(T.data.message,{chain:k,provider:R,address:v});if(!U)return t(!1),!1;let G={chain:k,message:T.data.message,signature:U,stackId:C};k==="solana"&&(G.publicKey=v);let J=await fetch(`${w}/api/auth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify(G)});if(!J.ok){let B=await J.json().catch(()=>({}));return r(B.error||"Authentication failed"),t(!1),!1}return u.reportConnected({address:v,chain:k,method:R||(k==="solana"?"phantom":"metamask"),stackId:C}),a(),t(!1),!0}catch(v){return r(v.message||"Authentication failed"),t(false),false}},[w,S,d,a,u,C]),$=useCallback(async(k="phantom")=>{let P=await i(k);return P?E("solana",P,()=>i(k),k):false},[i,E]),W=useCallback(async()=>{let k=await l();return k?E("ethereum",k,l,"metamask"):false},[l,E]),b=useCallback(async k=>{t(true),r(null);try{let P=await fetch(`${w}/api/auth/otp`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({code:k})});if(!P.ok){let O=await P.json().catch(()=>({}));return r(O.error||"Invalid code"),t(!1),!1}return a(),t(!1),!0}catch(P){return r(P.message||"OTP verification failed"),t(false),false}},[w,a]),I=useCallback(async(k,P)=>{t(true),r(null);try{let O=P||`${window.location.origin}/api/auth/oauth/callback`,R=new URLSearchParams({provider:k,redirectUri:O,stackId:C}),v=await fetch(`${w}/api/auth/oauth/start?${R}`,{credentials:"include"});if(!v.ok){let U=await v.json().catch(()=>({}));return r(U.error||"Failed to start OAuth flow"),t(!1),!1}let T=await v.json();if(T.redirect_url){let U;try{U=new URL(T.redirect_url);}catch{return r("Invalid OAuth redirect URL"),t(!1),!1}let G=[/(^|\.)accounts\.google\.com$/,/(^|\.)discord\.com$/,/(^|\.)github\.com$/,/(^|\.)x\.com$/,/(^|\.)twitter\.com$/,/(^|\.)apple\.com$/];return U.protocol!=="https:"||!G.some(J=>J.test(U.hostname))?(r(`Refusing to redirect to non-OAuth host: ${U.hostname}`),t(!1),!1):(typeof sessionStorage<"u"&&(sessionStorage.setItem("oauth_state",T.state||""),sessionStorage.setItem("oauth_provider",k)),window.location.href=U.toString(),!0)}return r("No redirect URL returned"),t(!1),!1}catch(O){return r(O.message||"OAuth flow failed"),t(false),false}},[w,C]),N=useCallback(async(k,P,O)=>{t(true),r(null);try{if(typeof sessionStorage<"u"){let v=sessionStorage.getItem("oauth_state"),T=sessionStorage.getItem("oauth_provider");if(sessionStorage.removeItem("oauth_state"),sessionStorage.removeItem("oauth_provider"),!v||v!==O)return r("OAuth state mismatch \u2014 refusing to complete login"),t(!1),!1;if(T&&T!==k)return r("OAuth provider mismatch \u2014 refusing to complete login"),t(!1),!1}let R=await fetch(`${w}/api/auth/oauth/callback`,{method:"POST",headers:{"Content-Type":"application/json"},credentials:"include",body:JSON.stringify({provider:k,code:P,state:O,stackId:C})});if(!R.ok){let v=await R.json().catch(()=>({}));return r(v.error||"OAuth authentication failed"),t(!1),!1}return a(),t(!1),!0}catch(R){return r(R.message||"OAuth callback failed"),t(false),false}},[w,C,a]),j=useCallback(async()=>{n.address&&n.chain&&u.reportDisconnected({address:n.address,chain:n.chain,stackId:C});try{await fetch(`${w}/api/auth/logout`,{method:"POST",headers:c,credentials:"include"});}catch{}g(),a();},[w,c,g,a,n,u,C]);return useEffect(()=>{if(!e.autoConnect||h||o||!u.ready||!u.known||!u.identity)return;y(true);let{chain:k,method:P}=u.identity;k==="solana"&&(P==="phantom"||P==="solflare")?$(P):k==="ethereum"&&W();},[e.autoConnect,h,o,u,$,W]),{session:f,isAuthenticated:o,wallet:n,loading:m,error:p,authenticateSolana:$,authenticateEVM:W,authenticateOTP:b,authenticateOAuth:I,authenticateOAuthCallback:N,logout:j,refresh:()=>s(w),stackId:C,bridge:{ready:u.ready,known:u.known,identity:u.identity,identityCount:u.identityCount,resolvedStackId:u.resolvedStackId}}}function Y(e,n="https://stacknet.magma-rpc.com"){let[i,l]=useState(null),[d,g]=useState(false),[f,o]=useState(null),s=useCallback(async c=>{g(true),o(null);try{let u=await fetch(`${n}/api/v2/stacks/${c}`);if(!u.ok)return o("Stack not found"),g(!1),null;let m=await u.json(),t=m.data?.stack||m.stack||m,p={id:t.id,name:t.name,displayName:t.displayName||t.name,description:t.description,logoUrl:t.logoUrl,webPageUrl:t.webPageUrl,allowedChains:t.allowedChains||[],features:t.features,stripeProvider:t.stripeProvider,oauthProviders:t.oauthProviders?.map(r=>({provider:r.provider,clientId:r.clientId,enabled:r.enabled!==!1}))};return l(p),g(!1),p}catch(u){return o(u.message),g(false),null}},[n]);useEffect(()=>{e&&s(e);},[e,s]);let a=i?Re(i):[];return {config:i,loading:d,error:f,identityProviders:a,fetchConfig:s}}function Re(e){let n=[];if(e.features?.web3Auth!==false&&(e.allowedChains.includes("solana")&&(n.push({type:"wallet",id:"phantom",name:"Phantom",chain:"solana"}),n.push({type:"wallet",id:"solflare",name:"Solflare",chain:"solana"})),(e.allowedChains.includes("ethereum")||e.allowedChains.includes("polygon")||e.allowedChains.includes("base"))&&n.push({type:"wallet",id:"metamask",name:"MetaMask",chain:"ethereum"})),e.features?.apiKeyAuth!==false&&n.push({type:"otp",id:"otp",name:"Access Code"}),e.features?.oauthAuth&&e.oauthProviders)for(let i of e.oauthProviders)i.enabled&&n.push({type:"oauth",id:i.provider,name:i.provider});return n}function _e(e=""){let[n,i]=useState([]),[l,d]=useState(true),[g,f]=useState(null),o=useCallback(async()=>{try{let s=await fetch(`${e}/api/billing/plans`);if(s.ok){let a=await s.json();i(a.plans||a||[]);}}catch(s){f(s.message);}finally{d(false);}},[e]);return useEffect(()=>{o();},[o]),{plans:n,loading:l,error:g,refresh:o}}function We(e=""){let[n,i]=useState(null),[l,d]=useState(true),[g,f]=useState(null),o=useCallback(async()=>{try{let c=await fetch(`${e}/api/billing/subscription`);if(c.ok){let u=await c.json();i(u.plan?u:null);}}catch(c){f(c.message);}finally{d(false);}},[e]);useEffect(()=>{o();},[o]);let s=useCallback(async c=>{let u=x(),t=await(await fetch(`${e}/api/billing/subscribe`,{method:"POST",headers:{"Content-Type":"application/json",...u?{"x-csrf-token":u}:{}},body:JSON.stringify({planId:c})})).json();return t.url||t.checkoutUrl||null},[e]),a=useCallback(async()=>{let c=x();return (await fetch(`${e}/api/billing/cancel`,{method:"POST",headers:c?{"x-csrf-token":c}:{}})).ok?(await o(),true):false},[e,o]);return {subscription:n,loading:l,error:g,refresh:o,subscribe:s,cancel:a}}function Me(e=""){let[n,i]=useState(null),[l,d]=useState(true),[g,f]=useState(null),o=useCallback(async()=>{try{let s=await fetch(`${e}/api/billing/usage`);if(s.ok){let a=await s.json();i(a);}}catch(s){f(s.message);}finally{d(false);}},[e]);return useEffect(()=>{o();},[o]),{usage:n,loading:l,error:g,refresh:o}}function je(e=""){let[n,i]=useState(false),[l,d]=useState(null),g=useCallback(async o=>{i(true),d(null);try{let s=x(),a=await fetch(`${e}/api/billing/prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...s?{"x-csrf-token":s}:{}},body:JSON.stringify({amountCents:o})}),c=await a.json();return a.ok?c.url||null:(d(c.error||"Purchase failed"),null)}catch(s){return d(s.message),null}finally{i(false);}},[e]),f=useCallback(async o=>{i(true),d(null);try{let s=x(),a=await fetch(`${e}/api/billing/verify-prepaid`,{method:"POST",headers:{"Content-Type":"application/json",...s?{"x-csrf-token":s}:{}},body:JSON.stringify({sessionId:o})}),c=await a.json();return a.ok?c:(d(c.error||"Verification failed"),null)}catch(s){return d(s.message),null}finally{i(false);}},[e]);return {purchase:g,verifySession:f,loading:n,error:l}}function Be(e="",n){let[i,l]=useState([]),[d,g]=useState(true),[f,o]=useState(null),s=n?.limit||50,a=n?.offset||0,c=useCallback(async()=>{try{let u=await fetch(`${e}/api/billing/history?limit=${s}&offset=${a}`);if(u.ok){let m=await u.json();l(m.records||m.history||(Array.isArray(m)?m:[]));}}catch(u){o(u.message);}finally{g(false);}},[e,s,a]);return useEffect(()=>{c();},[c]),{records:i,loading:d,error:f,refresh:c}}function De(){if(typeof document>"u")return null;let e=document.cookie.split(";").map(n=>n.trim()).find(n=>n.startsWith("__csrf="));return e?e.slice(7):null}function He(e,n){let[i,l]=useState(null),[d,g]=useState(true),[f,o]=useState(false),[s,a]=useState(null),c=n?.apiBaseUrl??"",u=n?.scope===void 0||n?.scope==="global"?"global":`stack:${n.scope.stackId}`,m=useCallback(r=>{let h=encodeURIComponent(r);if(u==="global")return `${c}/api/user/profile/${h}`;let y=u.slice(6);return `${c}/api/v2/stacks/${encodeURIComponent(y)}/members/${h}/profile`},[c,u]),t=useCallback(async()=>{if(!e){l(null),g(false);return}g(true),a(null);try{let r=await fetch(m(e));if(r.ok){let h=await r.json(),y=h.profile||h.data?.profile||h;l({mid:y.mid||e,username:y.username||"",avatarUrl:y.avatar_url||y.avatarUrl,bio:y.bio,paymentAddress:y.payment_address||y.paymentAddress,createdAt:y.created_at||y.createdAt,updatedAt:y.updated_at||y.updatedAt});}else if(r.status===404)l({mid:e,username:""});else throw new Error(`${r.status}`)}catch(r){a(r instanceof Error?r.message:"Failed to load profile");}finally{g(false);}},[e,m]);useEffect(()=>{t();},[t]);let p=useCallback(async r=>{if(!e)return false;o(true),a(null);try{let h=De(),y={};r.username!==void 0&&(y.username=r.username),r.avatarUrl!==void 0&&(y.avatar_url=r.avatarUrl),r.bio!==void 0&&(y.bio=r.bio),r.paymentAddress!==void 0&&(y.payment_address=r.paymentAddress);let w=await fetch(m(e),{method:"PUT",headers:{"Content-Type":"application/json",...h?{"x-csrf-token":h}:{}},credentials:"same-origin",body:JSON.stringify(y)});if(!w.ok){let S=await w.json().catch(()=>({}));throw new Error(S.error||S.message||`Update failed: ${w.status}`)}let A=await w.json(),C=A.profile||A.data?.profile||A;return l(S=>({mid:S?.mid||e,username:r.username??S?.username??"",avatarUrl:r.avatarUrl??S?.avatarUrl,bio:r.bio??S?.bio,paymentAddress:r.paymentAddress??C.payment_address??C.paymentAddress??S?.paymentAddress,createdAt:S?.createdAt,updatedAt:C.updated_at||C.updatedAt||Date.now()})),!0}catch(h){return a(h instanceof Error?h.message:"Update failed"),false}finally{o(false);}},[e,m]);return {profile:i,loading:d,saving:f,error:s,updateProfile:p,refresh:t}}var be="google-identity-services",Ke="https://accounts.google.com/gsi/client";function Ve({stackId:e,stacknetUrl:n="https://stacknet.magma-rpc.com",apiBaseUrl:i="",autoPrompt:l=true,cancelOnTapOutside:d=true,onSuccess:g,onError:f,disabled:o=false}){let{config:s}=Y(e,n),{isAuthenticated:a,loading:c,readSession:u}=L(),[m,t]=useState(false),[p,r]=useState(null),[h,y]=useState(false),w=useRef(false),A=useRef(false),S=s?.oauthProviders?.find(b=>b.provider==="google"&&b.enabled&&b.clientId)?.clientId||null;useEffect(()=>{if(o||!S||typeof window>"u")return;if(document.getElementById(be)){y(true);return}let b=document.createElement("script");b.id=be,b.src=Ke,b.async=true,b.defer=true,b.onload=()=>y(true),b.onerror=()=>{r("Failed to load Google sign-in"),f?.("Failed to load Google Identity Services script");},document.head.appendChild(b);},[o,S,f]);let E=useCallback(async b=>{t(true),r(null);try{let I=await fetch(`${i}/api/auth/google/one-tap`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({credential:b.credential,stackId:e})});if(!I.ok){let j=(await I.json().catch(()=>({}))).error||"Google sign-in failed";r(j),f?.(j),t(!1);return}u(),t(!1),g?.();}catch(I){let N=I.message||"Google sign-in failed";r(N),f?.(N),t(false);}},[i,e,u,g,f]);useEffect(()=>{if(o){console.debug("[GoogleOneTap] Disabled");return}if(!h){console.debug("[GoogleOneTap] Script not loaded yet, clientId:",S);return}if(!S){console.debug("[GoogleOneTap] No Google clientId from stack config");return}if(c){console.debug("[GoogleOneTap] Session still loading");return}if(a){console.debug("[GoogleOneTap] User already authenticated, skipping");return}if(!window.google?.accounts?.id){console.debug("[GoogleOneTap] GIS library not available on window");return}w.current||(console.debug("[GoogleOneTap] Initializing with clientId:",S),w.current=true,window.google.accounts.id.initialize({client_id:S,callback:E,auto_select:true,cancel_on_tap_outside:d}),l&&!A.current&&(A.current=true,console.debug("[GoogleOneTap] Showing prompt..."),window.google.accounts.id.prompt(b=>{b.isDisplayed?.()&&console.debug("[GoogleOneTap] Prompt displayed"),b.isNotDisplayed?.()&&console.debug("[GoogleOneTap] Not displayed:",b.getNotDisplayedReason?.()),b.isSkippedMoment?.()&&console.debug("[GoogleOneTap] Skipped:",b.getSkippedReason?.()),b.isDismissedMoment?.()&&console.debug("[GoogleOneTap] Dismissed:",b.getDismissedReason?.());})));},[o,h,S,c,a,E,l,d]),useEffect(()=>()=>{window.google?.accounts?.id&&w.current&&window.google.accounts.id.cancel();},[]);let $=useCallback(()=>{!window.google?.accounts?.id||!w.current||window.google.accounts.id.prompt();},[]),W=useCallback((b,I)=>{!b||!window.google?.accounts?.id||!w.current||window.google.accounts.id.renderButton(b,{theme:I?.theme||"filled_black",size:I?.size||"large",text:I?.text||"signin_with",width:I?.width});},[]);return {available:!!S,ready:h&&!!S,loading:m,error:p,prompt:$,renderButton:W,clientId:S}}var qe="/api";function Qe(e,n){return `${(n||(typeof window<"u"?window.location.origin:"")).replace(/\/$/,"")}/?ref=${encodeURIComponent(e)}`}function Xe(e={}){let{shareBaseUrl:n,autoMint:i=true}=e,[l,d]=useState(null),[g,f]=useState(true),[o,s]=useState(null),a=useCallback(async()=>{f(true),s(null);try{let m=i?"POST":"GET",t=await fetch(`${qe}/social/join-code`,{method:m,credentials:"include"});if(!t.ok){d(null),t.status===401?s("not_authenticated"):s(`HTTP ${t.status}`);return}let p=await t.json();d(p?.code??null);}catch(m){s(m?.message||"network_error"),d(null);}finally{f(false);}},[i]);useEffect(()=>{a();},[a]);let c=l?Qe(l,n):null,u=useCallback(async()=>{if(!c)return false;try{if(typeof navigator<"u"&&navigator.clipboard?.writeText)return await navigator.clipboard.writeText(c),!0}catch{}return false},[c]);return {code:l,shareUrl:c,loading:g,error:o,refresh:a,copyShareLink:u}}
|
|
2
|
+
export{q as useAuthBridge,Be as useBillingHistory,K as useCSRFToken,Ve as useGoogleOneTap,Xe as useJoinCode,_e as usePlans,je as usePrepaidCheckout,He as useProfile,L as useSession,Te as useStackAuth,Y as useStackConfig,We as useSubscription,Me as useUsage,V as useWeb3Wallet};
|