@dypai-ai/client-sdk 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -2
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1181,8 +1181,7 @@ declare function setTokenProvider(provider: TokenProvider): void;
|
|
|
1181
1181
|
* @param config - Service configuration with optional callbacks.
|
|
1182
1182
|
*/
|
|
1183
1183
|
declare function configureApiService(config: ApiServiceConfig): void;
|
|
1184
|
-
declare function callApi(token: string, method: HttpMethod, endpoint: string, body?: any, showToastsOverride?: boolean, params?: Record<string, any>, apiKey?: string, baseUrl?: string
|
|
1185
|
-
_isRetry?: boolean): Promise<any>;
|
|
1184
|
+
declare function callApi(token: string, method: HttpMethod, endpoint: string, body?: any, showToastsOverride?: boolean, params?: Record<string, any>, apiKey?: string, baseUrl?: string): Promise<any>;
|
|
1186
1185
|
/**
|
|
1187
1186
|
* Creates a public API client for calling custom endpoints.
|
|
1188
1187
|
* Accepts a static context or a getter function for lazy token resolution.
|
package/dist/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class t extends Error{constructor(t,e=500,i,s){super(t),this.status=e,this.code=i,this.details=s,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,s=e.emailVerified??e.confirmed_at??null,n=e.app_metadata||{},r=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:n.role||r.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:s?"boolean"==typeof s?(new Date).toISOString():s:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:n,user_metadata:{...r,full_name:i},roleDetails:{name:n.role||r.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function s(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function n(e,i){let s={};try{s=await e.json()}catch{}const n=s.msg||s.error_description||s.message||i,r=s.error_code||s.error||s.code||void 0;return new t(n,e.status,r)}class r{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const o={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function a(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de autenticación",e.status||400)}}}class c{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=null,this.u=[],this.p=null,this.m=null,this.S=!1,this.v=null,this.k=null,this.D=0,this.A=0,this.storage=t.storage||o,i=!!t.debug;const n=t.storageKey||this.deriveStorageKey(t.apiKey);s("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${n})`),this.STORAGE_KEY=`dypai-${n}-auth-session`,this.P=this._().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.m;this.m=null,t?setTimeout(()=>{this.S=!0,this.I(t)},0):this.S=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.T.bind(this)),window.addEventListener("focus",this.T.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(s("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this._(!1))}))}async T(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(s("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this._(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}O(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),i=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,i)}$(t){try{if(!t)return;sessionStorage.setItem(c.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(c.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(c.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}R(t){try{sessionStorage.setItem(c.RESET_TOKEN_STORAGE_KEY,t)}catch{}}N(){try{return sessionStorage.getItem(c.RESET_TOKEN_STORAGE_KEY)}catch{return null}}K(){try{sessionStorage.removeItem(c.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(c.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(c.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(c.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(c.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return s("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.P.then(()=>{const e=this.U();s("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{s("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!s.ok)throw await n(s,"Login failed");const r=await s.json(),o=this.C(r);return await this.Y(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:r,phone:o,password:a,user_data:c,...h}=t,l=r||"",u=h.full_name||h.name||l.split("@")[0],d={...h,...c||{}},f={email:l,password:a,name:u};Object.keys(d).length>0&&Object.assign(f,d),e?.redirectTo?f.callbackURL=e.redirectTo:"undefined"!=typeof window&&(f.callbackURL=window.location.href.split("#")[0]);const p=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(f)});if(!p.ok)throw await n(p,"Registration failed");const w=await p.json(),y=this.C(w),m={...y,confirmationRequired:!y.token};return m.token?await this.Y(m):s("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),m})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.P,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.h&&(s("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>s("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.h||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return a((async()=>{await this.P;const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,s=await fetch(i,{headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw new t("Session invalid",s.status);const n=await s.json(),r=n?.jwt||s.headers.get("set-auth-jwt");r&&(this.i=r);const o=this.L(n);return this.j(o),o})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return a((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:s=window.location.href,scopes:n}=i,r=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:e,callbackURL:s});n?.length&&o.set("scopes",n.join(" "));const a=`${r}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=a})())}async signOut(){return a((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.J("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.redirectTo=e.redirectTo:"undefined"!=typeof window&&(s.redirectTo=window.location.href.split("#")[0]);const r=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Recovery failed");return await r.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.callbackURL=e.redirectTo:"undefined"!=typeof window&&(s.callbackURL=window.location.href.split("#")[0]);const r=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.v)return s("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.v.promise;const e=new r;this.v=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const s=new AbortController,n=setTimeout(()=>s.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:s.signal},async()=>(clearTimeout(n),await i()))}catch(s){if("AbortError"===s.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw s}}return await i()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.B());e.resolve(t)}catch(i){const s=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:s})}finally{this.v=null}})(),e.promise}async B(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,n=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-n>60)return s("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.session_token||t.access_token,this.h=t.refresh_token,this.l=t.expires_at,this.t=t.user,this.D=0,this.I("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const i=this.o||this.i;if(!i)throw new t("No hay sesión activa",401);s("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const n=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,o=setTimeout(()=>r.abort(),c.REFRESH_TIMEOUT_MS);let a;try{a=await fetch(n,{method:"GET",headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:r.signal})}finally{clearTimeout(o)}if(!a.ok){let e={};try{e=await a.json()}catch{}const i=a.status,n=e.error||e.code||"",r=(e.error_description||e.msg||"").toLowerCase(),o="invalid_grant"===n||r.includes("invalid refresh token")||r.includes("revoked");throw i>=400&&i<500&&o?(s("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.J(`refreshSession failed (${i}: ${n})`)):s("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const h=await a.json(),l=a.headers.get("set-auth-jwt"),u=h?.jwt,d=u||l,f=this.C(h,this.i);return d&&(f.token=d),f.sessionToken=i,this.D=0,await this.Y(f),s("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!d}).`),{data:f,error:null}}catch(e){this.D++,this.A=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,n=i?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),r=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.D),c.MAX_RETRY_MS);return i?s("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.D})`):s("error","[DYPAI SDK] ❌ Refresh falló:",n.message,`(status: ${n.status}). Backoff: ${r}ms (intento ${this.D})`),this.D>=c.MAX_REFRESH_FAILURES&&(s("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.J(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:n}}}async signInWithOtp(e){return a((async()=>{if(e.phone)throw new t("Phone OTP is not supported by the current DYPAI auth backend",400);if(!e.email)throw new t("Email is required for magic link sign-in",400);const i=await this.signInWithMagicLink(e.email);if(i.error)throw i.error;return{status:!0}})())}async verifyOtp(e){return a((async()=>{if(!e.token)throw new t("Verification token is required",400);if("magiclink"===e.type)return await this.q(e.token);if("signup"===e.type||"email_change"===e.type){const i=await this.M(e.token);if(i)return i;throw new t("Verification completed but no session was returned",400)}if("recovery"===e.type||"invite"===e.type)throw new t("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new t("Unsupported verification type",400)})())}async updateUser(e){return a((async()=>{const i=this.o||this.i;if(!i)throw new t("No hay sesión activa",401);const s=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"Update user failed",n.status)}const r=await n.json(),o=this.L(r);return this.j(o),o})())}async setPassword(e){return a((async()=>{if("undefined"==typeof window)throw new t("setPassword requiere un entorno de navegador",400);const i=new URLSearchParams(window.location.search).get("token")||this.N();if(!i)throw new t("No reset token found in the current URL",400);const s=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${s}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:e,token:i})});if(!r.ok)throw await n(r,"Reset password failed");return this.K(),this.O(),await r.json()})())}async signInWithMagicLink(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),r=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:s})});if(!r.ok)throw await n(r,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Enable 2FA failed");return await s.json()})())}async verifyTwoFactor(t){return a((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await n(i,"2FA verification failed");const s=await i.json(),r={token:s.jwt||s.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.L(s)};return await this.Y(r),r})())}async disableTwoFactor(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(e,i){return a((async()=>{const s=this.o||this.i;if(!s)throw new t("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${r}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:e,slug:i})});if(!o.ok)throw await n(o,"Create organization failed");return await o.json()})())}async inviteMember(e,i,s){return a((async()=>{const r=this.o||this.i;if(!r)throw new t("No hay sesión activa",401);const o=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${o}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:e,email:i,role:s||"member"})});if(!a.ok)throw await n(a,"Invite member failed");return await a.json()})())}async listOrganizations(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"List organizations failed");return await s.json()})())}startAutoRefresh(){this.stopAutoRefresh(),s("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.W(),this.k=setInterval(()=>this.W(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.k&&(s("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.k),this.k=null)}async W(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.D>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.D),c.MAX_RETRY_MS);if(Date.now()-this.A<t)return}s("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){s("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}L(t){return t?e(t):(s("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}C(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,s=t?.jwt||void 0,n=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:s||n,sessionToken:n,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.L(t)}}async Y(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.j(t.user,t.token,t.refreshToken,t.expiresAt),s("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async j(t,e,i,n){this.t=t,e&&(this.i=e),void 0!==i&&(this.h=i||null),void 0!==n&&(this.l=n||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else s("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){s("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.S){const t=e?"SIGNED_IN":"USER_UPDATED";this.I(t)}}async J(t="unknown"){s("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),s("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){s("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.I("SIGNED_OUT")}async q(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/magic-link/verify`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Magic link verification failed");const r=await s.json(),o=this.C(r);return await this.Y(o),o}async M(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/verify-email`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Email verification failed");if(!(s.headers.get("content-type")||s.headers.get("Content-Type")||"").includes("application/json"))return null;const r=await s.json(),o=this.C(r);return o.token&&o.user?.id?(await this.Y(o),o):null}async F(t,e){const i=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!i)return!1;if(s("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${i}). Procesando...`),this.$(i),"recovery"===i||"invite"===i)return this.R(t),this.O(),this.m="PASSWORD_RECOVERY",!0;if(this.O(),"magiclink"===i)return await this.q(t),this.m="SIGNED_IN",!0;if("signup"===i||"email_change"===i){const e=await this.M(t);return this.m=e?.token?"SIGNED_IN":null,!0}return!1}async _(t=!0){s("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.G())return void s("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(!e)return void s("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let t;s("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.session_token?t.session_token:t.access_token,this.h="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}const i=Math.floor(Date.now()/1e3),n=this.l&&this.l<=i;if(s("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${i}. Diferencia: ${(this.l||0)-i}s`),n&&this.h&&t){s("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void s("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&s("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.m="SIGNED_IN"}catch(t){s("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async G(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),i=t.get("token"),n=t.get("type");if(e){const i=t.get("error_code")||e,n=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return s("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${i} — ${n}`),this.p={code:i,message:n},this.O(),this.I("AUTH_ERROR"),!0}if(i)return await this.F(i,n);const r=window.location.hash.substring(1);if(!r)return!1;const o=new URLSearchParams(r);if(o.get("error")){const t=o.get("error_code")||"unknown",e=o.get("error_description")?.replace(/\+/g," ")||"Authentication error";return s("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.O(),this.I("AUTH_ERROR"),!0}const a=o.get("access_token");if(!a)return!1;const c=o.get("refresh_token"),h=o.get("type"),l=parseInt(o.get("expires_in")||"3600",10);s("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${h||"unknown"}). Procesando...`),this.$(h),this.O(),this.o=a,this.i=a,this.h=c||null,this.l=Math.floor(Date.now()/1e3)+l;const u=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${a}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!u.ok)return s("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${u.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.I("AUTH_ERROR"),!0;const d=await u.json(),f=d?.jwt||u.headers.get("set-auth-jwt"),p=this.L(d),w={token:f||a,sessionToken:a,refreshToken:c||void 0,expiresIn:l,expiresAt:this.l,user:p};return await this.Y(w),this.m="recovery"===h||"invite"===h?"PASSWORD_RECOVERY":"SIGNED_IN",s("log",`[DYPAI SDK] 🔑 Callback type=${h}. Pending event: ${this.m}`),!0}catch(t){return s("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}U(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}I(t="USER_UPDATED"){const e=this.U();s("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(i=>i(t,e))}handleSessionExpired(){this.J("handleSessionExpired called (likely 401 from API)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3,c.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",c.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",c.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class h{constructor(t,e,i){this.ws=null,this.bindings=[],this.V={},this.H=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.X=t;const e=this.client.Z();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.tt(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const s=this.bindings.some(t=>"broadcast"===t.type),n=this.bindings.some(t=>"presence"===t.type);(s||n)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!n||void 0})),this.H=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.et&&e.removeEventListener("message",this.et);const s=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.it(e)}catch{}};return this.et=s,e.addEventListener("message",s),e.addEventListener("close",()=>{this.H=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}st(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.tt(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(i||s)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.X?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.V}}async unsubscribe(){if(this.et&&this.ws&&(this.ws.removeEventListener("message",this.et),this.et=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.H=!1,this.bindings=[],this.V={},"ok"}it(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.V={};for(const[i,s]of Object.entries(t.presences||{})){const t=s[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(s)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,s]of Object.entries(t.joins)){const t=s[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(s);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[s],currentPresences:this.V[t]}))}if(t.leaves)for(const[i,s]of Object.entries(t.leaves)){const t=s[e]||i;this.V[t]=(this.V[t]||[]).filter(t=>t.userId!==i),0===this.V[t].length&&delete this.V[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[s],currentPresences:this.V[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}tt(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class l{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const s=new h(t,this,e?.config);return this.channels.set(t,s),s}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.Z())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}Z(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.H&&t.st(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=l.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,l.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.Z()},t)}}),i}}l.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class u{get direct(){if(!this.nt)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.nt}set direct(t){this.nt=t}constructor(t){this.api=t}from(t){return new d(t,this.api)}}class d{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}const f=1e3;class p{constructor(t){this.api=t}from(t){return new w(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class w{constructor(t,e){this.table=t,this.api=e,this.rt="public",this.ot=[],this.ct=100,this.ht=0,this.ut="ASC"}schema(t){return this.rt=t,this}eq(t,e){return this.ot.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.ot.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.ot.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.ot.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.ot.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.ot.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.ot.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.ot.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.ot.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.ot.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.ot.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.ot.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.ot.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.ot.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.ot.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.ot.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.ot.push({operator:"or",or:t}),this}limit(t){return this.ct=t,this}offset(t){return this.ht=t,this}orderBy(t,e="ASC"){return this.dt=t,this.ut=e,this}async select(t){const e=[...this.ot];let i=this.ft;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,s]of Object.entries(t))e.push({column:i,operator:"eq",value:s});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.ct,offset:this.ht,sort_by:this.dt,order:this.ut})}async single(){const e=await this.select();if(e.error)return e;const i=e.data;return!i||Array.isArray(i)&&0===i.length?{data:null,error:new t("No rows found.",404)}:{data:Array.isArray(i)?i[0]:i,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.ot],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>f){const e=[];for(let i=0;i<t.length;i+=f){const s=t.slice(i,i+f),{data:n,error:r}=await this.execute("insert",{data:s,mode:"bulk"});if(r)return{data:null,error:r};n&&e.push(...Array.isArray(n)?n:[n])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(e){return 0===this.ot.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.ot})}async delete(){return 0===this.ot.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.ot})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.rt,table_name:this.table,...e})}}class y{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const s=await this.api.put(`admin/users/${t}`,i);return s.data&&(s.data=e(s.data)),s}async delete(t){return this.api.delete(`admin/users/${t}`)}}let m=null;function S(t){m=t}const g=t=>{const{title:e,description:i,variant:s="default"}=t,n=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===s?console.error(n):"warning"===s&&console.warn(n)};function v(){const t=m||g;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const b=t=>(m||g)(t),k=(t,e)=>b({title:t,description:e,variant:"success"}),D=(t,e)=>b({title:t,description:e,variant:"error"}),A=(t,e)=>b({title:t,description:e,variant:"warning"}),P=(t,e)=>b({title:t,description:e,variant:"info"});let _={},I=null;function T(t){I=t}function E(t){_={..._,...t}}const O=new Map;function $(){let t=_;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),..._}}catch(t){}return t}async function R(e,i,s,n,r,o,a,c,h){const l=$(),u=c||null;if(!u&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof s||!s.trim())throw new Error("Endpoint debe ser un string válido");let d;if(s.startsWith("http"))d=s;else{const t=u.replace(/\/+$/,"");d=s.startsWith("/")?t+s:t+"/api/v0/"+s}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${i}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${i}[${t}]`,s)):t.append(i,String(s)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(d+=`?${i}`)}const f="GET"===i?`${i}:${d}:${JSON.stringify(n)}`:null;if(f&&O.has(f))return O.get(f);const p=n instanceof FormData,w={...l.headers||{},...p?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},y={method:i,headers:w,credentials:"include"};n&&"GET"!==i&&"DELETE"!==i&&(y.body=p?n:JSON.stringify(n));const m=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!m)throw new Error("Fetch no disponible.");const S=(async()=>{const e=$().toast||b,u=(void 0!==r?r:!1!==l.showToasts)&&e;try{const f=await m(d,y);if(!f.ok){let d,p="Error en la petición";try{const t=await f.text();try{const e=JSON.parse(t);d=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===f.status&&!h&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return R(t,i,s,n,r,o,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw u&&e({title:"Error",description:p,variant:"error"}),new t(p,f.status,void 0,d)}!u||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const p=f.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await f.blob():p.includes("application/json")?await f.json():await f.text()}finally{f&&O.delete(f)}})();return f&&O.set(f,S),S}async function N(t,e,i,s){const n=s?.method||(i?"POST":"GET"),{data:r,error:o}=await("GET"===n?t.get(e,{params:s?.params}):t.post(e,i,{params:s?.params}));if(o)throw o;if(r instanceof Blob){const t=window.URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=s?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(r&&"object"==typeof r&&("url"in r||"signed_url"in r||"signedUrl"in r)){const t=r.url||r.signed_url||r.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const n=s?.fileName||i;if(n){const e=document.createElement("a");e.href=t,e.download=n,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function K(e,i,s,n){const{data:r,error:o}=await e.post(i,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,client_upload:!0,...n?.params||{}});if(o)throw o;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return U(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return U(t);const s=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of s){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return U(t)}return null}(r);if(!a?.upload_url)throw new t("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;n?.onProgress&&n.onProgress(10);const f=await fetch(c,{method:h,headers:{"Content-Type":s.type||"application/octet-stream",...l},body:s});if(!f.ok)throw new t("Direct upload to cloud storage failed",f.status);n?.onProgress&&n.onProgress(90);const p=n?.confirmEndpoint||i,{data:w,error:y}=await e.post(p,{...n?.params,bucket:a.bucket||n?.params?.bucket,file_path:u,storage_path:d,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0,client_upload:!0});if(y)throw y;return n?.onProgress&&n.onProgress(100),w}function U(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function C(e){const i={get:Y(e,"GET"),post:Y(e,"POST"),put:Y(e,"PUT"),patch:Y(e,"PATCH"),delete:Y(e,"DELETE"),upload:async(e,s,n)=>{try{return{data:await K(i,e,s,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,s,n)=>{try{return await N(i,e,s,{...n,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return i}function Y(e,i){const s="POST"===i||"PUT"===i||"PATCH"===i;return async(n,r,o)=>{const a=e();let c,h={};s?(c=r,h=o||{}):(h=r||{},c=void 0);const l=h.token||a.token||(I?I():"")||"",u=h.apiKey||a.apiKey;try{return{data:await R(l,i,n,c,h.showToasts,h.params,u,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function x(e){const i=()=>"function"==typeof e?e():e,s={get:L(i,"GET"),post:L(i,"POST"),put:L(i,"PUT"),patch:L(i,"PATCH"),delete:L(i,"DELETE"),upload:async(e,i,n)=>{try{return{data:await K(s,e,i,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,i,n)=>{try{return await N(s,e,i,{...n,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return s}function L(e,i){return async(s,n,r)=>{const o=e(),a=await async function(t,e,i){let s,n={};"POST"===t||"PUT"===t||"PATCH"===t?(s=e,n=i||{}):(n=e||{},s=void 0);let r=n.token;return!r&&I&&(r=I()||""),r=r||"",{token:r,apiKey:n.apiKey,body:s,params:n.params,showToasts:n.showToasts}}(i,n,r),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await R(c,i,s,a.body,a.showToasts,a.params,h,o.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class j{get baseUrl(){return this.wt}getAuthToken(){return this.auth.token||this.yt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.wt=e,this.yt=i,this.realtime=new l(e,()=>this.getAuthToken());const s=t.auth?.storageKey||t.storageKey;t.global&&function(t){_={..._,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:s,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n=C(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=n,this.db=new u(n),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const i=C(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new p(i)}if(this.users=new y(n),this.api=x(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),T(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.P?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}E({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function z(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new j({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let J={};function B(t){J={...J,toast:b,...t},M()}function q(){return{...J}}function M(){try{const{configureApiService:t}=require("../services/ApiService");t(J)}catch(t){}}function W(){J={},M()}async function F(){M()}const G={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]};export{p as DirectDBModule,w as DirectQueryBuilder,j as DypaiClient,t as DypaiError,l as DypaiRealtimeClient,G as PACKAGE_INFO,h as RealtimeChannel,R as callApi,E as configureApiService,B as configureDypaiServices,x as createApiClient,z as createClient,q as getGlobalConfig,F as reloadDypaiConfig,W as resetGlobalConfig,S as setToastFunction,T as setTokenProvider,b as toast,D as toastError,P as toastInfo,k as toastSuccess,A as toastWarning,v as useToast};
|
|
1
|
+
class t extends Error{constructor(t,e=500,i,s){super(t),this.status=e,this.code=i,this.details=s,this.name="DypaiError"}}function e(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,s=e.emailVerified??e.confirmed_at??null,n=e.app_metadata||{},r=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:n.role||r.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:s?"boolean"==typeof s?(new Date).toISOString():s:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:n,user_metadata:{...r,full_name:i},roleDetails:{name:n.role||r.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function s(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function n(e,i){let s={};try{s=await e.json()}catch{}const n=s.msg||s.error_description||s.message||i,r=s.error_code||s.error||s.code||void 0;return new t(n,e.status,r)}class r{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const o={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function a(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de autenticación",e.status||400)}}}class c{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=null,this.u=[],this.p=null,this.m=null,this.S=!1,this.v=null,this.k=null,this.D=0,this.A=0,this.storage=t.storage||o,i=!!t.debug;const n=t.storageKey||this.deriveStorageKey(t.apiKey);s("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${n})`),this.STORAGE_KEY=`dypai-${n}-auth-session`,this.P=this._().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.m;this.m=null,t?setTimeout(()=>{this.S=!0,this.I(t)},0):this.S=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.T.bind(this)),window.addEventListener("focus",this.T.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(s("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this._(!1))}))}async T(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(s("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this._(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}O(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),i=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,i)}$(t){try{if(!t)return;sessionStorage.setItem(c.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(c.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(c.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}R(t){try{sessionStorage.setItem(c.RESET_TOKEN_STORAGE_KEY,t)}catch{}}N(){try{return sessionStorage.getItem(c.RESET_TOKEN_STORAGE_KEY)}catch{return null}}K(){try{sessionStorage.removeItem(c.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(c.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(c.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(c.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(c.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return s("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.P.then(()=>{const e=this.U();s("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{s("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!s.ok)throw await n(s,"Login failed");const r=await s.json(),o=this.C(r);return await this.Y(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:r,phone:o,password:a,user_data:c,...h}=t,l=r||"",u=h.full_name||h.name||l.split("@")[0],d={...h,...c||{}},f={email:l,password:a,name:u};Object.keys(d).length>0&&Object.assign(f,d),e?.redirectTo?f.callbackURL=e.redirectTo:"undefined"!=typeof window&&(f.callbackURL=window.location.href.split("#")[0]);const p=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(f)});if(!p.ok)throw await n(p,"Registration failed");const w=await p.json(),y=this.C(w),m={...y,confirmationRequired:!y.token};return m.token?await this.Y(m):s("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),m})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.P,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.h&&(s("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>s("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.h||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return a((async()=>{await this.P;const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,s=await fetch(i,{headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw new t("Session invalid",s.status);const n=await s.json(),r=n?.jwt||s.headers.get("set-auth-jwt");r&&(this.i=r);const o=this.L(n);return this.j(o),o})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return a((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:s=window.location.href,scopes:n}=i,r=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:e,callbackURL:s});n?.length&&o.set("scopes",n.join(" "));const a=`${r}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=a})())}async signOut(){return a((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.J("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.redirectTo=e.redirectTo:"undefined"!=typeof window&&(s.redirectTo=window.location.href.split("#")[0]);const r=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Recovery failed");return await r.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo?s.callbackURL=e.redirectTo:"undefined"!=typeof window&&(s.callbackURL=window.location.href.split("#")[0]);const r=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.v)return s("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.v.promise;const e=new r;this.v=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const s=new AbortController,n=setTimeout(()=>s.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:s.signal},async()=>(clearTimeout(n),await i()))}catch(s){if("AbortError"===s.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw s}}return await i()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.B());e.resolve(t)}catch(i){const s=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:s})}finally{this.v=null}})(),e.promise}async B(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,n=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-n>60)return s("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.session_token||t.access_token,this.h=t.refresh_token,this.l=t.expires_at,this.t=t.user,this.D=0,this.I("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const i=this.o||this.i;if(!i)throw new t("No hay sesión activa",401);s("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const n=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,o=setTimeout(()=>r.abort(),c.REFRESH_TIMEOUT_MS);let a;try{a=await fetch(n,{method:"GET",headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:r.signal})}finally{clearTimeout(o)}if(!a.ok){let e={};try{e=await a.json()}catch{}const i=a.status,n=e.error||e.code||"",r=(e.error_description||e.msg||"").toLowerCase(),o="invalid_grant"===n||r.includes("invalid refresh token")||r.includes("revoked");throw i>=400&&i<500&&o?(s("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.J(`refreshSession failed (${i}: ${n})`)):s("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const h=await a.json(),l=a.headers.get("set-auth-jwt"),u=h?.jwt,d=u||l,f=this.C(h,this.i);return d&&(f.token=d),f.sessionToken=i,this.D=0,await this.Y(f),s("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!d}).`),{data:f,error:null}}catch(e){this.D++,this.A=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,n=i?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),r=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.D),c.MAX_RETRY_MS);return i?s("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.D})`):s("error","[DYPAI SDK] ❌ Refresh falló:",n.message,`(status: ${n.status}). Backoff: ${r}ms (intento ${this.D})`),this.D>=c.MAX_REFRESH_FAILURES&&(s("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.J(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:n}}}async signInWithOtp(e){return a((async()=>{if(e.phone)throw new t("Phone OTP is not supported by the current DYPAI auth backend",400);if(!e.email)throw new t("Email is required for magic link sign-in",400);const i=await this.signInWithMagicLink(e.email);if(i.error)throw i.error;return{status:!0}})())}async verifyOtp(e){return a((async()=>{if(!e.token)throw new t("Verification token is required",400);if("magiclink"===e.type)return await this.q(e.token);if("signup"===e.type||"email_change"===e.type){const i=await this.M(e.token);if(i)return i;throw new t("Verification completed but no session was returned",400)}if("recovery"===e.type||"invite"===e.type)throw new t("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new t("Unsupported verification type",400)})())}async updateUser(e){return a((async()=>{const i=this.o||this.i;if(!i)throw new t("No hay sesión activa",401);const s=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"Update user failed",n.status)}const r=await n.json(),o=this.L(r);return this.j(o),o})())}async setPassword(e){return a((async()=>{if("undefined"==typeof window)throw new t("setPassword requiere un entorno de navegador",400);const i=new URLSearchParams(window.location.search).get("token")||this.N();if(!i)throw new t("No reset token found in the current URL",400);const s=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${s}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:e,token:i})});if(!r.ok)throw await n(r,"Reset password failed");return this.K(),this.O(),await r.json()})())}async signInWithMagicLink(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),r=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:s})});if(!r.ok)throw await n(r,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Enable 2FA failed");return await s.json()})())}async verifyTwoFactor(t){return a((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await n(i,"2FA verification failed");const s=await i.json(),r={token:s.jwt||s.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.L(s)};return await this.Y(r),r})())}async disableTwoFactor(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(e,i){return a((async()=>{const s=this.o||this.i;if(!s)throw new t("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${r}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:e,slug:i})});if(!o.ok)throw await n(o,"Create organization failed");return await o.json()})())}async inviteMember(e,i,s){return a((async()=>{const r=this.o||this.i;if(!r)throw new t("No hay sesión activa",401);const o=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${o}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:e,email:i,role:s||"member"})});if(!a.ok)throw await n(a,"Invite member failed");return await a.json()})())}async listOrganizations(){return a((async()=>{const e=this.o||this.i;if(!e)throw new t("No hay sesión activa",401);const i=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${i}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"List organizations failed");return await s.json()})())}startAutoRefresh(){this.stopAutoRefresh(),s("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.W(),this.k=setInterval(()=>this.W(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.k&&(s("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.k),this.k=null)}async W(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.D>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.D),c.MAX_RETRY_MS);if(Date.now()-this.A<t)return}s("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){s("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}L(t){return t?e(t):(s("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}C(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,s=t?.jwt||void 0,n=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:s||n,sessionToken:n,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.L(t)}}async Y(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.j(t.user,t.token,t.refreshToken,t.expiresAt),s("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async j(t,e,i,n){this.t=t,e&&(this.i=e),void 0!==i&&(this.h=i||null),void 0!==n&&(this.l=n||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else s("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){s("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.S){const t=e?"SIGNED_IN":"USER_UPDATED";this.I(t)}}async J(t="unknown"){s("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),s("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){s("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.I("SIGNED_OUT")}async q(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/magic-link/verify`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Magic link verification failed");const r=await s.json(),o=this.C(r);return await this.Y(o),o}async M(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/verify-email`);i.searchParams.set("token",t);const s=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!s.ok)throw await n(s,"Email verification failed");if(!(s.headers.get("content-type")||s.headers.get("Content-Type")||"").includes("application/json"))return null;const r=await s.json(),o=this.C(r);return o.token&&o.user?.id?(await this.Y(o),o):null}async F(t,e){const i=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!i)return!1;if(s("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${i}). Procesando...`),this.$(i),"recovery"===i||"invite"===i)return this.R(t),this.O(),this.m="PASSWORD_RECOVERY",!0;if(this.O(),"magiclink"===i)return await this.q(t),this.m="SIGNED_IN",!0;if("signup"===i||"email_change"===i){const e=await this.M(t);return this.m=e?.token?"SIGNED_IN":null,!0}return!1}async _(t=!0){s("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.G())return void s("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(!e)return void s("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let t;s("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.session_token?t.session_token:t.access_token,this.h="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}const i=Math.floor(Date.now()/1e3),n=this.l&&this.l<=i;if(s("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${i}. Diferencia: ${(this.l||0)-i}s`),n&&this.h&&t){s("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void s("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&s("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.m="SIGNED_IN"}catch(t){s("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async G(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),i=t.get("token"),n=t.get("type");if(e){const i=t.get("error_code")||e,n=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return s("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${i} — ${n}`),this.p={code:i,message:n},this.O(),this.I("AUTH_ERROR"),!0}if(i)return await this.F(i,n);const r=window.location.hash.substring(1);if(!r)return!1;const o=new URLSearchParams(r);if(o.get("error")){const t=o.get("error_code")||"unknown",e=o.get("error_description")?.replace(/\+/g," ")||"Authentication error";return s("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.O(),this.I("AUTH_ERROR"),!0}const a=o.get("access_token");if(!a)return!1;const c=o.get("refresh_token"),h=o.get("type"),l=parseInt(o.get("expires_in")||"3600",10);s("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${h||"unknown"}). Procesando...`),this.$(h),this.O(),this.o=a,this.i=a,this.h=c||null,this.l=Math.floor(Date.now()/1e3)+l;const u=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${a}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!u.ok)return s("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${u.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.I("AUTH_ERROR"),!0;const d=await u.json(),f=d?.jwt||u.headers.get("set-auth-jwt"),p=this.L(d),w={token:f||a,sessionToken:a,refreshToken:c||void 0,expiresIn:l,expiresAt:this.l,user:p};return await this.Y(w),this.m="recovery"===h||"invite"===h?"PASSWORD_RECOVERY":"SIGNED_IN",s("log",`[DYPAI SDK] 🔑 Callback type=${h}. Pending event: ${this.m}`),!0}catch(t){return s("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}U(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}I(t="USER_UPDATED"){const e=this.U();s("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(i=>i(t,e))}handleSessionExpired(){this.J("handleSessionExpired called (likely 401 from API)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3,c.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",c.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",c.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class h{constructor(t,e,i){this.ws=null,this.bindings=[],this.V={},this.H=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.X=t;const e=this.client.Z();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.tt(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const s=this.bindings.some(t=>"broadcast"===t.type),n=this.bindings.some(t=>"presence"===t.type);(s||n)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!n||void 0})),this.H=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.et&&e.removeEventListener("message",this.et);const s=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.it(e)}catch{}};return this.et=s,e.addEventListener("message",s),e.addEventListener("close",()=>{this.H=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}st(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.tt(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(i||s)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.X?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.V}}async unsubscribe(){if(this.et&&this.ws&&(this.ws.removeEventListener("message",this.et),this.et=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.H=!1,this.bindings=[],this.V={},"ok"}it(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.V={};for(const[i,s]of Object.entries(t.presences||{})){const t=s[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(s)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,s]of Object.entries(t.joins)){const t=s[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(s);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[s],currentPresences:this.V[t]}))}if(t.leaves)for(const[i,s]of Object.entries(t.leaves)){const t=s[e]||i;this.V[t]=(this.V[t]||[]).filter(t=>t.userId!==i),0===this.V[t].length&&delete this.V[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[s],currentPresences:this.V[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}tt(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class l{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const s=new h(t,this,e?.config);return this.channels.set(t,s),s}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.Z())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}Z(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.H&&t.st(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=l.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,l.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.Z()},t)}}),i}}l.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class u{get direct(){if(!this.nt)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.nt}set direct(t){this.nt=t}constructor(t){this.api=t}from(t){return new d(t,this.api)}}class d{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}const f=1e3;class p{constructor(t){this.api=t}from(t){return new w(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class w{constructor(t,e){this.table=t,this.api=e,this.rt="public",this.ot=[],this.ct=100,this.ht=0,this.ut="ASC"}schema(t){return this.rt=t,this}eq(t,e){return this.ot.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.ot.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.ot.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.ot.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.ot.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.ot.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.ot.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.ot.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.ot.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.ot.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.ot.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.ot.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.ot.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.ot.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.ot.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.ot.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.ot.push({operator:"or",or:t}),this}limit(t){return this.ct=t,this}offset(t){return this.ht=t,this}orderBy(t,e="ASC"){return this.dt=t,this.ut=e,this}async select(t){const e=[...this.ot];let i=this.ft;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,s]of Object.entries(t))e.push({column:i,operator:"eq",value:s});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.ct,offset:this.ht,sort_by:this.dt,order:this.ut})}async single(){const e=await this.select();if(e.error)return e;const i=e.data;return!i||Array.isArray(i)&&0===i.length?{data:null,error:new t("No rows found.",404)}:{data:Array.isArray(i)?i[0]:i,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.ot],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>f){const e=[];for(let i=0;i<t.length;i+=f){const s=t.slice(i,i+f),{data:n,error:r}=await this.execute("insert",{data:s,mode:"bulk"});if(r)return{data:null,error:r};n&&e.push(...Array.isArray(n)?n:[n])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(e){return 0===this.ot.length?{data:null,error:new t("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:e,filters:this.ot})}async delete(){return 0===this.ot.length?{data:null,error:new t("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.ot})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.rt,table_name:this.table,...e})}}class y{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const s=await this.api.put(`admin/users/${t}`,i);return s.data&&(s.data=e(s.data)),s}async delete(t){return this.api.delete(`admin/users/${t}`)}}let m=null;function S(t){m=t}const g=t=>{const{title:e,description:i,variant:s="default"}=t,n=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===s?console.error(n):"warning"===s&&console.warn(n)};function v(){const t=m||g;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const b=t=>(m||g)(t),k=(t,e)=>b({title:t,description:e,variant:"success"}),D=(t,e)=>b({title:t,description:e,variant:"error"}),A=(t,e)=>b({title:t,description:e,variant:"warning"}),P=(t,e)=>b({title:t,description:e,variant:"info"});let _={},I=null;function T(t){I=t}function E(t){_={..._,...t}}const O=new Map;function $(){let t=_;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),..._}}catch(t){}return t}async function R(e,i,s,n,r,o,a,c){const h=$(),l=c||null;if(!l&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof s||!s.trim())throw new Error("Endpoint debe ser un string válido");let u;if(s.startsWith("http"))u=s;else{const t=l.replace(/\/+$/,"");u=s.startsWith("/")?t+s:t+"/api/v0/"+s}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${i}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${i}[${t}]`,s)):t.append(i,String(s)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const d="GET"===i?`${i}:${u}:${JSON.stringify(n)}`:null;if(d&&O.has(d))return O.get(d);const f=n instanceof FormData,p={...h.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},w={method:i,headers:p,credentials:"include"};n&&"GET"!==i&&"DELETE"!==i&&(w.body=f?n:JSON.stringify(n));const y=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const m=(async()=>{const e=$().toast||b,s=(void 0!==r?r:!1!==h.showToasts)&&e;try{const n=await y(u,w);if(!n.ok){let i,r="Error en la petición";try{const t=await n.text();try{const e=JSON.parse(t);i=e,r=e.message||e.msg||e.error_description||e.error||r}catch{t.length<200&&(r=t)}}catch{}throw 401===n.status&&h.onUnauthorized&&h.onUnauthorized(),s&&e({title:"Error",description:r,variant:"error"}),new t(r,n.status,void 0,i)}!s||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const r=n.headers.get("content-type")||"";return r.includes("application/pdf")||r.includes("image/")||r.includes("audio/")||r.includes("video/")||r.includes("application/octet-stream")||r.includes("application/zip")||r.includes("application/vnd.openxmlformats-officedocument")?await n.blob():r.includes("application/json")?await n.json():await n.text()}finally{d&&O.delete(d)}})();return d&&O.set(d,m),m}async function N(t,e,i,s){const n=s?.method||(i?"POST":"GET"),{data:r,error:o}=await("GET"===n?t.get(e,{params:s?.params}):t.post(e,i,{params:s?.params}));if(o)throw o;if(r instanceof Blob){const t=window.URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=s?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(r&&"object"==typeof r&&("url"in r||"signed_url"in r||"signedUrl"in r)){const t=r.url||r.signed_url||r.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const n=s?.fileName||i;if(n){const e=document.createElement("a");e.href=t,e.download=n,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function K(e,i,s,n){const{data:r,error:o}=await e.post(i,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,client_upload:!0,...n?.params||{}});if(o)throw o;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return U(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return U(t);const s=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of s){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return U(t)}return null}(r);if(!a?.upload_url)throw new t("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;n?.onProgress&&n.onProgress(10);const f=await fetch(c,{method:h,headers:{"Content-Type":s.type||"application/octet-stream",...l},body:s});if(!f.ok)throw new t("Direct upload to cloud storage failed",f.status);n?.onProgress&&n.onProgress(90);const p=n?.confirmEndpoint||i,{data:w,error:y}=await e.post(p,{...n?.params,bucket:a.bucket||n?.params?.bucket,file_path:u,storage_path:d,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0,client_upload:!0});if(y)throw y;return n?.onProgress&&n.onProgress(100),w}function U(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function C(e){const i={get:Y(e,"GET"),post:Y(e,"POST"),put:Y(e,"PUT"),patch:Y(e,"PATCH"),delete:Y(e,"DELETE"),upload:async(e,s,n)=>{try{return{data:await K(i,e,s,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,s,n)=>{try{return await N(i,e,s,{...n,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return i}function Y(e,i){const s="POST"===i||"PUT"===i||"PATCH"===i;return async(n,r,o)=>{const a=e();let c,h={};s?(c=r,h=o||{}):(h=r||{},c=void 0);const l=h.token||a.token||(I?I():"")||"",u=h.apiKey||a.apiKey;try{return{data:await R(l,i,n,c,h.showToasts,h.params,u,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function x(e){const i=()=>"function"==typeof e?e():e,s={get:L(i,"GET"),post:L(i,"POST"),put:L(i,"PUT"),patch:L(i,"PATCH"),delete:L(i,"DELETE"),upload:async(e,i,n)=>{try{return{data:await K(s,e,i,n),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,i,n)=>{try{return await N(s,e,i,{...n,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return s}function L(e,i){return async(s,n,r)=>{const o=e(),a=await async function(t,e,i){let s,n={};"POST"===t||"PUT"===t||"PATCH"===t?(s=e,n=i||{}):(n=e||{},s=void 0);let r=n.token;return!r&&I&&(r=I()||""),r=r||"",{token:r,apiKey:n.apiKey,body:s,params:n.params,showToasts:n.showToasts}}(i,n,r),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await R(c,i,s,a.body,a.showToasts,a.params,h,o.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class j{get baseUrl(){return this.wt}getAuthToken(){return this.auth.token||this.yt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.wt=e,this.yt=i,this.realtime=new l(e,()=>this.getAuthToken());const s=t.auth?.storageKey||t.storageKey;t.global&&function(t){_={..._,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:s,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n=C(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=n,this.db=new u(n),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const i=C(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new p(i)}if(this.users=new y(n),this.api=x(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),T(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.P?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}E({onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}function z(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new j({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let J={};function B(t){J={...J,toast:b,...t},M()}function q(){return{...J}}function M(){try{const{configureApiService:t}=require("../services/ApiService");t(J)}catch(t){}}function W(){J={},M()}async function F(){M()}const G={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]};export{p as DirectDBModule,w as DirectQueryBuilder,j as DypaiClient,t as DypaiError,l as DypaiRealtimeClient,G as PACKAGE_INFO,h as RealtimeChannel,R as callApi,E as configureApiService,B as configureDypaiServices,x as createApiClient,z as createClient,q as getGlobalConfig,F as reloadDypaiConfig,W as resetGlobalConfig,S as setToastFunction,T as setTokenProvider,b as toast,D as toastError,P as toastInfo,k as toastSuccess,A as toastWarning,v as useToast};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";class DypaiError extends Error{constructor(t,e=500,i,r){super(t),this.status=e,this.code=i,this.details=r,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,r=e.emailVerified??e.confirmed_at??null,s=e.app_metadata||{},o=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:s.role||o.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:r?"boolean"==typeof r?(new Date).toISOString():r:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:s,user_metadata:{...o,full_name:i},roleDetails:{name:s.role||o.role||null,weight:0},appContext:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseAuthError(t,e){let i={};try{i=await t.json()}catch{}const r=i.msg||i.error_description||i.message||e,s=i.error_code||i.error||i.code||void 0;return new DypaiError(r,t.status,s)}class Deferred{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const localStorageAdapter={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function wrapAuthResponse(t){try{return{data:await t,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error de autenticación",t.status||400)}}}class AuthModule{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=null,this.u=[],this.p=null,this.m=null,this.A=!1,this.D=null,this.v=null,this.S=0,this._=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const i=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${i})`),this.STORAGE_KEY=`dypai-${i}-auth-session`,this.k=this.T().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.m;this.m=null,t?setTimeout(()=>{this.A=!0,this.P(t)},0):this.A=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.I.bind(this)),window.addEventListener("focus",this.I.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.T(!1))}))}async I(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.T(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}R(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),i=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,i)}C(t){try{if(!t)return;sessionStorage.setItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}O(t){try{sessionStorage.setItem(AuthModule.RESET_TOKEN_STORAGE_KEY,t)}catch{}}$(){try{return sessionStorage.getItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{return null}}U(){try{sessionStorage.removeItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.k.then(()=>{const e=this.M();_log("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!r.ok)throw await parseAuthError(r,"Login failed");const s=await r.json(),o=this.N(s);return await this.K(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:r,phone:s,password:o,user_data:n,...a}=t,h=r||"",c=a.full_name||a.name||h.split("@")[0],l={...a,...n||{}},u={email:h,password:o,name:c};Object.keys(l).length>0&&Object.assign(u,l),e?.redirectTo?u.callbackURL=e.redirectTo:"undefined"!=typeof window&&(u.callbackURL=window.location.href.split("#")[0]);const d=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(u)});if(!d.ok)throw await parseAuthError(d,"Registration failed");const p=await d.json(),f=this.N(p),y={...f,confirmationRequired:!f.token};return y.token?await this.K(y):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),y})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.k,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.h&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.h||void 0,token_type:"bearer",user:this.t},error:null}}catch(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{await this.k;const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,i=await fetch(e,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw new DypaiError("Session invalid",i.status);const r=await i.json(),s=r?.jwt||i.headers.get("set-auth-jwt");s&&(this.i=s);const o=this.Y(r);return this.L(o),o})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:i=window.location.href,scopes:r}=e,s=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:t,callbackURL:i});r?.length&&o.set("scopes",r.join(" "));const n=`${s}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=n})())}async signOut(){return wrapAuthResponse((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.j("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.redirectTo=e.redirectTo:"undefined"!=typeof window&&(r.redirectTo=window.location.href.split("#")[0]);const s=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok)throw await parseAuthError(s,"Recovery failed");return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.callbackURL=e.redirectTo:"undefined"!=typeof window&&(r.callbackURL=window.location.href.split("#")[0]);const s=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(r)});if(!s.ok)throw await parseAuthError(s,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.D)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.D.promise;const t=new Deferred;this.D=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const i=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const r=new AbortController,s=setTimeout(()=>r.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:r.signal},async()=>(clearTimeout(s),await i()))}catch(r){if("AbortError"===r.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw r}}return await i()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.B());t.resolve(i)}catch(e){const i=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:i})}finally{this.D=null}})(),t.promise}async B(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),i=e.expires_at||0,r=Math.floor(Date.now()/1e3);if(e.access_token!==this.i&&i-r>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=e.access_token,this.o=e.session_token||e.access_token,this.h=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.S=0,this.P("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const e=this.o||this.i;if(!e)throw new DypaiError("No hay sesión activa",401);_log("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,s=setTimeout(()=>r.abort(),AuthModule.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(i,{method:"GET",headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:r.signal})}finally{clearTimeout(s)}if(!o.ok){let t={};try{t=await o.json()}catch{}const e=o.status,i=t.error||t.code||"",r=(t.error_description||t.msg||"").toLowerCase(),s="invalid_grant"===i||r.includes("invalid refresh token")||r.includes("revoked");throw e>=400&&e<500&&s?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.j(`refreshSession failed (${e}: ${i})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await o.json(),a=o.headers.get("set-auth-jwt"),h=n?.jwt,c=h||a,l=this.N(n,this.i);return c&&(l.token=c),l.sessionToken=e,this.S=0,await this.K(l),_log("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!c}).`),{data:l,error:null}}catch(t){this.S++,this._=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,i=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),r=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.S})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",i.message,`(status: ${i.status}). Backoff: ${r}ms (intento ${this.S})`),this.S>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.j(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:i}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{if(t.phone)throw new DypaiError("Phone OTP is not supported by the current DYPAI auth backend",400);if(!t.email)throw new DypaiError("Email is required for magic link sign-in",400);const e=await this.signInWithMagicLink(t.email);if(e.error)throw e.error;return{status:!0}})())}async verifyOtp(t){return wrapAuthResponse((async()=>{if(!t.token)throw new DypaiError("Verification token is required",400);if("magiclink"===t.type)return await this.q(t.token);if("signup"===t.type||"email_change"===t.type){const e=await this.J(t.token);if(e)return e;throw new DypaiError("Verification completed but no session was returned",400)}if("recovery"===t.type||"invite"===t.type)throw new DypaiError("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new DypaiError("Unsupported verification type",400)})())}async updateUser(t){return wrapAuthResponse((async()=>{const e=this.o||this.i;if(!e)throw new DypaiError("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"Update user failed",r.status)}const s=await r.json(),o=this.Y(s);return this.L(o),o})())}async setPassword(t){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("setPassword requiere un entorno de navegador",400);const e=new URLSearchParams(window.location.search).get("token")||this.$();if(!e)throw new DypaiError("No reset token found in the current URL",400);const i=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${i}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:t,token:e})});if(!r.ok)throw await parseAuthError(r,"Reset password failed");return this.U(),this.R(),await r.json()})())}async signInWithMagicLink(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),s=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:r})});if(!s.ok)throw await parseAuthError(s,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Enable 2FA failed");return await i.json()})())}async verifyTwoFactor(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await parseAuthError(i,"2FA verification failed");const r=await i.json(),s={token:r.jwt||r.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.Y(r)};return await this.K(s),s})())}async disableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(t,e){return wrapAuthResponse((async()=>{const i=this.o||this.i;if(!i)throw new DypaiError("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${r}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:t,slug:e})});if(!s.ok)throw await parseAuthError(s,"Create organization failed");return await s.json()})())}async inviteMember(t,e,i){return wrapAuthResponse((async()=>{const r=this.o||this.i;if(!r)throw new DypaiError("No hay sesión activa",401);const s=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${s}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:t,email:e,role:i||"member"})});if(!o.ok)throw await parseAuthError(o,"Invite member failed");return await o.json()})())}async listOrganizations(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"List organizations failed");return await i.json()})())}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.F(),this.v=setInterval(()=>this.F(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.v&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.v),this.v=null)}async F(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);if(Date.now()-this._<t)return}_log("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){_log("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}Y(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}N(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,r=t?.jwt||void 0,s=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:r||s,sessionToken:s,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.Y(t)}}async K(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.L(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async L(t,e,i,r){this.t=t,e&&(this.i=e),void 0!==i&&(this.h=i||null),void 0!==r&&(this.l=r||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else _log("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){_log("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.A){const t=e?"SIGNED_IN":"USER_UPDATED";this.P(t)}}async j(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.P("SIGNED_OUT")}async q(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/magic-link/verify`);i.searchParams.set("token",t);const r=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Magic link verification failed");const s=await r.json(),o=this.N(s);return await this.K(o),o}async J(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/verify-email`);i.searchParams.set("token",t);const r=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Email verification failed");if(!(r.headers.get("content-type")||r.headers.get("Content-Type")||"").includes("application/json"))return null;const s=await r.json(),o=this.N(s);return o.token&&o.user?.id?(await this.K(o),o):null}async W(t,e){const i=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!i)return!1;if(_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${i}). Procesando...`),this.C(i),"recovery"===i||"invite"===i)return this.O(t),this.R(),this.m="PASSWORD_RECOVERY",!0;if(this.R(),"magiclink"===i)return await this.q(t),this.m="SIGNED_IN",!0;if("signup"===i||"email_change"===i){const e=await this.J(t);return this.m=e?.token?"SIGNED_IN":null,!0}return!1}async T(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.G())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(!e)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.session_token?t.session_token:t.access_token,this.h="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}const i=Math.floor(Date.now()/1e3),r=this.l&&this.l<=i;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${i}. Diferencia: ${(this.l||0)-i}s`),r&&this.h&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.m="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async G(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),i=t.get("token"),r=t.get("type");if(e){const i=t.get("error_code")||e,r=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${i} — ${r}`),this.p={code:i,message:r},this.R(),this.P("AUTH_ERROR"),!0}if(i)return await this.W(i,r);const s=window.location.hash.substring(1);if(!s)return!1;const o=new URLSearchParams(s);if(o.get("error")){const t=o.get("error_code")||"unknown",e=o.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.R(),this.P("AUTH_ERROR"),!0}const n=o.get("access_token");if(!n)return!1;const a=o.get("refresh_token"),h=o.get("type"),c=parseInt(o.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${h||"unknown"}). Procesando...`),this.C(h),this.R(),this.o=n,this.i=n,this.h=a||null,this.l=Math.floor(Date.now()/1e3)+c;const l=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${n}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!l.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${l.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.P("AUTH_ERROR"),!0;const u=await l.json(),d=u?.jwt||l.headers.get("set-auth-jwt"),p=this.Y(u),f={token:d||n,sessionToken:n,refreshToken:a||void 0,expiresIn:c,expiresAt:this.l,user:p};return await this.K(f),this.m="recovery"===h||"invite"===h?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${h}. Pending event: ${this.m}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}M(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}P(t="USER_UPDATED"){const e=this.M();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(i=>i(t,e))}handleSessionExpired(){this.j("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=5,AuthModule.AUTO_REFRESH_TICK_DURATION_MS=3e4,AuthModule.AUTO_REFRESH_TICK_THRESHOLD=3,AuthModule.RETRY_BASE_MS=200,AuthModule.MAX_RETRY_MS=3e4,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS=5e3,AuthModule.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",AuthModule.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",AuthModule.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class RealtimeChannel{constructor(t,e,i){this.ws=null,this.bindings=[],this.V={},this.H=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.X=t;const e=this.client.Z();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.tt(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const r=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(r||s)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.H=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.et&&e.removeEventListener("message",this.et);const r=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.it(e)}catch{}};return this.et=r,e.addEventListener("message",r),e.addEventListener("close",()=>{this.H=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}rt(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.tt(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),r=this.bindings.some(t=>"presence"===t.type);(i||r)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!r||void 0})),this.X?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.V}}async unsubscribe(){if(this.et&&this.ws&&(this.ws.removeEventListener("message",this.et),this.et=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.H=!1,this.bindings=[],this.V={},"ok"}it(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.V={};for(const[i,r]of Object.entries(t.presences||{})){const t=r[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(r)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,r]of Object.entries(t.joins)){const t=r[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(r);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[r],currentPresences:this.V[t]}))}if(t.leaves)for(const[i,r]of Object.entries(t.leaves)){const t=r[e]||i;this.V[t]=(this.V[t]||[]).filter(t=>t.userId!==i),0===this.V[t].length&&delete this.V[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[r],currentPresences:this.V[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}tt(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class DypaiRealtimeClient{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const r=new RealtimeChannel(t,this,e?.config);return this.channels.set(t,r),r}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.Z())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}Z(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.H&&t.rt(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=DypaiRealtimeClient.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,DypaiRealtimeClient.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.Z()},t)}}),i}}DypaiRealtimeClient.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class DataModule{get direct(){if(!this.st)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.st}set direct(t){this.st=t}constructor(t){this.api=t}from(t){return new QueryBuilder(t,this.api)}}class QueryBuilder{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class DirectDBModule{constructor(t){this.api=t}from(t){return new DirectQueryBuilder(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class DirectQueryBuilder{constructor(t,e){this.table=t,this.api=e,this.ot="public",this.nt=[],this.ht=100,this.ct=0,this.ut="ASC"}schema(t){return this.ot=t,this}eq(t,e){return this.nt.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.nt.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.nt.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.nt.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.nt.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.nt.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.nt.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.nt.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.nt.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.nt.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.nt.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.nt.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.nt.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.nt.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.nt.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.nt.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.nt.push({operator:"or",or:t}),this}limit(t){return this.ht=t,this}offset(t){return this.ct=t,this}orderBy(t,e="ASC"){return this.dt=t,this.ut=e,this}async select(t){const e=[...this.nt];let i=this.ft;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,r]of Object.entries(t))e.push({column:i,operator:"eq",value:r});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.ht,offset:this.ct,sort_by:this.dt,order:this.ut})}async single(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:new DypaiError("No rows found.",404)}:{data:Array.isArray(e)?e[0]:e,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.nt],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>1e3){const e=[];for(let i=0;i<t.length;i+=1e3){const r=t.slice(i,i+1e3),{data:s,error:o}=await this.execute("insert",{data:r,mode:"bulk"});if(o)return{data:null,error:o};s&&e.push(...Array.isArray(s)?s:[s])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(t){return 0===this.nt.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.nt})}async delete(){return 0===this.nt.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.nt})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.ot,table_name:this.table,...e})}}class UsersModule{constructor(t){this.api=t}async list(t={}){const e=await this.api.get("admin/users",{params:t});return e.data?.users&&(e.data.users=e.data.users.map(normalizeUser)),e}async create(t){const e=await this.api.post("admin/users",t);return e.data&&(e.data=normalizeUser(e.data)),e}async update(t,e){const i=await this.api.put(`admin/users/${t}`,e);return i.data&&(i.data=normalizeUser(i.data)),i}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:i,variant:r="default"}=t,s=`${"error"===r?"❌":"success"===r?"✅":"warning"===r?"⚠️":"info"===r?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===r?console.error(s):"warning"===r&&console.warn(s)},toast=t=>(customToastFunction||fallbackToast)(t);let serviceConfig={},globalTokenProvider=null;function setTokenProvider(t){globalTokenProvider=t}function configureApiService(t){serviceConfig={...serviceConfig,...t}}const pendingRequests=new Map;function getCompleteConfig(){let t=serviceConfig;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...serviceConfig}}catch(t){}return t}async function callApi(t,e,i,r,s,o,n,a,h){const c=getCompleteConfig(),l=a||null;if(!l&&!i.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof i||!i.trim())throw new Error("Endpoint debe ser un string válido");let u;if(i.startsWith("http"))u=i;else{const t=l.replace(/\/+$/,"");u=i.startsWith("/")?t+i:t+"/api/v0/"+i}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,r)=>{null!=r&&(Array.isArray(r)?r.forEach((t,r)=>e(`${i}[${r}]`,t)):"object"==typeof r?Object.entries(r).forEach(([t,r])=>e(`${i}[${t}]`,r)):t.append(i,String(r)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(r)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=r instanceof FormData,f={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...n&&{"x-api-key":n}},y={method:e,headers:f,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(y.body=p?r:JSON.stringify(r));const w=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!w)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,l=(void 0!==s?s:!1!==c.showToasts)&&t;try{const d=await w(u,y);if(!d.ok){let u,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);u=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status&&!h&&c.onTokenExpired)try{const t=await c.onTokenExpired();if(t)return callApi(t,e,i,r,s,o,n,a,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw l&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,d.status,void 0,u)}!l||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const p=d.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await d.blob():p.includes("application/json")?await d.json():await d.text()}finally{d&&pendingRequests.delete(d)}})();return d&&pendingRequests.set(d,g),g}async function handleSmartDownload(t,e,i,r){const s=r?.method||(i?"POST":"GET"),{data:o,error:n}=await("GET"===s?t.get(e,{params:r?.params}):t.post(e,i,{params:r?.params}));if(n)throw n;if(o instanceof Blob){const t=window.URL.createObjectURL(o),e=document.createElement("a");e.href=t,e.download=r?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(o&&"object"==typeof o&&("url"in o||"signed_url"in o||"signedUrl"in o)){const t=o.url||o.signed_url||o.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=r?.fileName||i;if(s){const e=document.createElement("a");e.href=t,e.download=s,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function handleSmartUpload(t,e,i,r){const{data:s,error:o}=await t.post(e,{file_path:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.size,confirm:!1,client_upload:!0,...r?.params||{}});if(o)throw o;const n=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return normalizeUploadPayload(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t);const r=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of r){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(s);if(!n?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:a,method:h="PUT",headers:c={},file_path:l,storage_path:u}=n;r?.onProgress&&r.onProgress(10);const d=await fetch(a,{method:h,headers:{"Content-Type":i.type||"application/octet-stream",...c},body:i});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:n.bucket||r?.params?.bucket,file_path:l,storage_path:u,filename:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),f}function normalizeUploadPayload(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function createInternalApiClient(t){const e={get:createMethod(t,"GET"),post:createMethod(t,"POST"),put:createMethod(t,"PUT"),patch:createMethod(t,"PATCH"),delete:createMethod(t,"DELETE"),upload:async(t,i,r)=>{try{return{data:await handleSmartUpload(e,t,i,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,i,r)=>{try{return await handleSmartDownload(e,t,i,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return e}function createMethod(t,e){const i="POST"===e||"PUT"===e||"PATCH"===e;return async(r,s,o)=>{const n=t();let a,h={};i?(a=s,h=o||{}):(h=s||{},a=void 0);const c=h.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",l=h.apiKey||n.apiKey;try{return{data:await callApi(c,e,r,a,h.showToasts,h.params,l,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,i={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,r)=>{try{return{data:await handleSmartUpload(i,t,e,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,e,r)=>{try{return await handleSmartDownload(i,t,e,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return i}function createMethodFromCtx(t,e){return async(i,r,s)=>{const o=t(),n=await async function(t,e,i){let r,s={};"POST"===t||"PUT"===t||"PATCH"===t?(r=e,s=i||{}):(s=e||{},r=void 0);let o=s.token;return!o&&globalTokenProvider&&(o=globalTokenProvider()||""),o=o||"",{token:o,apiKey:s.apiKey,body:r,params:s.params,showToasts:s.showToasts}}(e,r,s),a=n.token||o.token||"",h=n.apiKey||o.apiKey;try{return{data:await callApi(a,e,i,n.body,n.showToasts,n.params,h,o.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}class DypaiClient{get baseUrl(){return this.yt}getAuthToken(){return this.auth.token||this.wt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.yt=e,this.wt=i,this.realtime=new DypaiRealtimeClient(e,()=>this.getAuthToken());const r=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:i,storageKey:r,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s=createInternalApiClient(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=s,this.db=new DataModule(s),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const i=createInternalApiClient(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new DirectDBModule(i)}if(this.users=new UsersModule(s),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),setTokenProvider(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.k?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}configureApiService({onTokenExpired:async()=>{const t=await this.auth.refreshSession();if(t.error)throw t.error;return t.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}let globalConfig={};function applyConfigToAllServices(){try{const{configureApiService:t}=require("../services/ApiService");t(globalConfig)}catch(t){}}exports.DirectDBModule=DirectDBModule,exports.DirectQueryBuilder=DirectQueryBuilder,exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.DypaiRealtimeClient=DypaiRealtimeClient,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]},exports.RealtimeChannel=RealtimeChannel,exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}};
|
|
1
|
+
"use strict";class DypaiError extends Error{constructor(t,e=500,i,r){super(t),this.status=e,this.code=i,this.details=r,this.name="DypaiError"}}function normalizeUser(t){if(!t)return{};const e=t.user||t,i=e.name||e.user_metadata?.full_name||null,r=e.emailVerified??e.confirmed_at??null,s=e.app_metadata||{},o=e.user_metadata||{full_name:i};return{id:e.id,email:e.email,phone:e.phone||null,role:s.role||o.role||e.role||"authenticated",created_at:e.createdAt||e.created_at,updated_at:e.updatedAt||e.updated_at,confirmed_at:r?"boolean"==typeof r?(new Date).toISOString():r:null,last_sign_in_at:e.last_sign_in_at||null,app_metadata:s,user_metadata:{...o,full_name:i},roleDetails:{name:s.role||o.role||null,weight:0},appContext:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseAuthError(t,e){let i={};try{i=await t.json()}catch{}const r=i.msg||i.error_description||i.message||e,s=i.error_code||i.error||i.code||void 0;return new DypaiError(r,t.status,s)}class Deferred{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const localStorageAdapter={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function wrapAuthResponse(t){try{return{data:await t,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error de autenticación",t.status||400)}}}class AuthModule{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=null,this.u=[],this.p=null,this.m=null,this.A=!1,this.D=null,this.v=null,this.S=0,this._=0,this.storage=t.storage||localStorageAdapter,_debugEnabled=!!t.debug;const i=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${i})`),this.STORAGE_KEY=`dypai-${i}-auth-session`,this.k=this.T().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh());const t=this.m;this.m=null,t?setTimeout(()=>{this.A=!0,this.P(t)},0):this.A=!0}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.I.bind(this)),window.addEventListener("focus",this.I.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.T(!1))}))}async I(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.T(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}getBaseUrl(){return this.config.baseUrl||"http://localhost:8000"}R(){if("undefined"==typeof window)return;const t=new URLSearchParams(window.location.search);["token","type","error","error_code","error_description","access_token","refresh_token","expires_in"].forEach(e=>t.delete(e));const e=t.toString(),i=`${window.location.pathname}${e?`?${e}`:""}`;window.history.replaceState({},document.title,i)}C(t){try{if(!t)return;sessionStorage.setItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY,t),"recovery"===t||"invite"===t?sessionStorage.setItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY,"PASSWORD_RECOVERY"):sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{}}O(t){try{sessionStorage.setItem(AuthModule.RESET_TOKEN_STORAGE_KEY,t)}catch{}}$(){try{return sessionStorage.getItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{return null}}U(){try{sessionStorage.removeItem(AuthModule.RESET_TOKEN_STORAGE_KEY)}catch{}}get user(){return this.t}get token(){return this.i}get lastError(){return this.p}consumeCallbackType(){try{const t=sessionStorage.getItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY);return sessionStorage.removeItem(AuthModule.CALLBACK_TYPE_STORAGE_KEY),sessionStorage.removeItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY),t}catch{return null}}get isPasswordRecoveryCallback(){try{return"PASSWORD_RECOVERY"===sessionStorage.getItem(AuthModule.CALLBACK_REDIRECT_STORAGE_KEY)}catch{return!1}}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.u.push(t),this.k.then(()=>{const e=this.M();_log("log",`[DYPAI SDK] 📣 INITIAL_SESSION para suscriptor (Sesión activa: ${!!e})`),t("INITIAL_SESSION",e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.u=this.u.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-in/email`,i={email:t.email||t.identifier||"",password:t.password},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(i)});if(!r.ok)throw await parseAuthError(r,"Login failed");const s=await r.json(),o=this.N(s);return await this.K(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/sign-up/email`,{email:r,phone:s,password:o,user_data:n,...a}=t,h=r||"",c=a.full_name||a.name||h.split("@")[0],l={...a,...n||{}},u={email:h,password:o,name:c};Object.keys(l).length>0&&Object.assign(u,l),e?.redirectTo?u.callbackURL=e.redirectTo:"undefined"!=typeof window&&(u.callbackURL=window.location.href.split("#")[0]);const d=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(u)});if(!d.ok)throw await parseAuthError(d,"Registration failed");const p=await d.json(),f=this.N(p),y={...f,confirmationRequired:!f.token};return y.token?await this.K(y):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),y})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.k,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.h&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.h||void 0,token_type:"bearer",user:this.t},error:null}}catch(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{await this.k;const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,i=await fetch(e,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw new DypaiError("Session invalid",i.status);const r=await i.json(),s=r?.jwt||i.headers.get("set-auth-jwt");s&&(this.i=s);const o=this.Y(r);return this.L(o),o})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:i=window.location.href,scopes:r}=e,s=this.config.baseUrl||"http://localhost:8000",o=new URLSearchParams({provider:t,callbackURL:i});r?.length&&o.set("scopes",r.join(" "));const n=`${s}/api/v0/auth/sign-in/social?${o.toString()}`;window.location.href=n})())}async signOut(){return wrapAuthResponse((async()=>{try{const t=this.o||this.i;if(t){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/api/v0/auth/sign-out`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}})}}finally{this.j("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.redirectTo=e.redirectTo:"undefined"!=typeof window&&(r.redirectTo=window.location.href.split("#")[0]);const s=await fetch(`${i}/api/v0/auth/request-password-reset`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!s.ok)throw await parseAuthError(s,"Recovery failed");return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo?r.callbackURL=e.redirectTo:"undefined"!=typeof window&&(r.callbackURL=window.location.href.split("#")[0]);const s=await fetch(`${i}/api/v0/auth/send-verification-email`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(r)});if(!s.ok)throw await parseAuthError(s,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.D)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.D.promise;const t=new Deferred;this.D=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const i=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const r=new AbortController,s=setTimeout(()=>r.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:r.signal},async()=>(clearTimeout(s),await i()))}catch(r){if("AbortError"===r.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw r}}return await i()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.B());t.resolve(i)}catch(e){const i=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:i})}finally{this.D=null}})(),t.promise}async B(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),i=e.expires_at||0,r=Math.floor(Date.now()/1e3);if(e.access_token!==this.i&&i-r>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=e.access_token,this.o=e.session_token||e.access_token,this.h=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.S=0,this.P("TOKEN_REFRESHED"),{data:{token:this.i,sessionToken:this.o||void 0,refreshToken:this.h||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}const e=this.o||this.i;if(!e)throw new DypaiError("No hay sesión activa",401);_log("log","[DYPAI SDK] 🔄 Verificando sesión activa con session_token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/get-session`,r=new AbortController,s=setTimeout(()=>r.abort(),AuthModule.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(i,{method:"GET",headers:{Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},signal:r.signal})}finally{clearTimeout(s)}if(!o.ok){let t={};try{t=await o.json()}catch{}const e=o.status,i=t.error||t.code||"",r=(t.error_description||t.msg||"").toLowerCase(),s="invalid_grant"===i||r.includes("invalid refresh token")||r.includes("revoked");throw e>=400&&e<500&&s?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.j(`refreshSession failed (${e}: ${i})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await o.json(),a=o.headers.get("set-auth-jwt"),h=n?.jwt,c=h||a,l=this.N(n,this.i);return c&&(l.token=c),l.sessionToken=e,this.S=0,await this.K(l),_log("log",`[DYPAI SDK] ✅ Session refresh exitoso (fresh JWT: ${!!c}).`),{data:l,error:null}}catch(t){this.S++,this._=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,i=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),r=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.S})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",i.message,`(status: ${i.status}). Backoff: ${r}ms (intento ${this.S})`),this.S>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.j(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:i}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{if(t.phone)throw new DypaiError("Phone OTP is not supported by the current DYPAI auth backend",400);if(!t.email)throw new DypaiError("Email is required for magic link sign-in",400);const e=await this.signInWithMagicLink(t.email);if(e.error)throw e.error;return{status:!0}})())}async verifyOtp(t){return wrapAuthResponse((async()=>{if(!t.token)throw new DypaiError("Verification token is required",400);if("magiclink"===t.type)return await this.q(t.token);if("signup"===t.type||"email_change"===t.type){const e=await this.J(t.token);if(e)return e;throw new DypaiError("Verification completed but no session was returned",400)}if("recovery"===t.type||"invite"===t.type)throw new DypaiError("Recovery links do not use verifyOtp(). Use setPassword() after the PASSWORD_RECOVERY callback.",400);throw new DypaiError("Unsupported verification type",400)})())}async updateUser(t){return wrapAuthResponse((async()=>{const e=this.o||this.i;if(!e)throw new DypaiError("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/api/v0/auth/update-user`,r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify(t)});if(!r.ok){const t=await r.json();throw new DypaiError(t.detail||"Update user failed",r.status)}const s=await r.json(),o=this.Y(s);return this.L(o),o})())}async setPassword(t){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("setPassword requiere un entorno de navegador",400);const e=new URLSearchParams(window.location.search).get("token")||this.$();if(!e)throw new DypaiError("No reset token found in the current URL",400);const i=this.config.baseUrl||"http://localhost:8000",r=await fetch(`${i}/api/v0/auth/reset-password`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({newPassword:t,token:e})});if(!r.ok)throw await parseAuthError(r,"Reset password failed");return this.U(),this.R(),await r.json()})())}async signInWithMagicLink(t,e){return wrapAuthResponse((async()=>{const i=this.config.baseUrl||"http://localhost:8000",r=e?.redirectTo||("undefined"!=typeof window?window.location.href.split("#")[0]:void 0),s=await fetch(`${i}/api/v0/auth/sign-in/magic-link`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({email:t,callbackURL:r})});if(!s.ok)throw await parseAuthError(s,"Magic link failed");return{message:"Magic link sent"}})())}async enableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/enable`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Enable 2FA failed");return await i.json()})())}async verifyTwoFactor(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/verify-totp`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o||this.i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({code:t})});if(!i.ok)throw await parseAuthError(i,"2FA verification failed");const r=await i.json(),s={token:r.jwt||r.token||this.i,expiresIn:604800,expiresAt:Math.floor(Date.now()/1e3)+604800,user:this.Y(r)};return await this.K(s),s})())}async disableTwoFactor(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/two-factor/disable`,{method:"POST",headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"Disable 2FA failed");return{message:"2FA disabled"}})())}async createOrganization(t,e){return wrapAuthResponse((async()=>{const i=this.o||this.i;if(!i)throw new DypaiError("No hay sesión activa",401);const r=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${r}/api/v0/auth/organization/create`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({name:t,slug:e})});if(!s.ok)throw await parseAuthError(s,"Create organization failed");return await s.json()})())}async inviteMember(t,e,i){return wrapAuthResponse((async()=>{const r=this.o||this.i;if(!r)throw new DypaiError("No hay sesión activa",401);const s=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${s}/api/v0/auth/organization/invite-member`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}},body:JSON.stringify({organizationId:t,email:e,role:i||"member"})});if(!o.ok)throw await parseAuthError(o,"Invite member failed");return await o.json()})())}async listOrganizations(){return wrapAuthResponse((async()=>{const t=this.o||this.i;if(!t)throw new DypaiError("No hay sesión activa",401);const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/api/v0/auth/organization/list`,{headers:{Authorization:`Bearer ${t}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!i.ok)throw await parseAuthError(i,"List organizations failed");return await i.json()})())}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.F(),this.v=setInterval(()=>this.F(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.v&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.v),this.v=null)}async F(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.h&&!this.o||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.S),AuthModule.MAX_RETRY_MS);if(Date.now()-this._<t)return}_log("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){_log("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}Y(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}N(t,e){const i="number"==typeof t?.expiresIn?t.expiresIn:"number"==typeof t?.expires_in?t.expires_in:604800,r=t?.jwt||void 0,s=t?.session_token||t?.token||t?.access_token||t?.session?.token||t?.session?.accessToken||t?.session?.access_token||e;return{token:r||s,sessionToken:s,refreshToken:t?.refreshToken||t?.refresh_token||t?.session?.refreshToken||t?.session?.refresh_token||void 0,expiresIn:i,expiresAt:Math.floor(Date.now()/1e3)+i,user:this.Y(t)}}async K(t){this.p=null,t.token&&(this.i=t.token,this.o=t.sessionToken||t.token,void 0!==t.refreshToken&&(this.h=t.refreshToken||null),void 0!==t.expiresAt&&(this.l=t.expiresAt||null),await this.L(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.h}, has_session_token: ${!!this.o})`),this.startAutoRefresh())}async L(t,e,i,r){this.t=t,e&&(this.i=e),void 0!==i&&(this.h=i||null),void 0!==r&&(this.l=r||null);try{if(this.i&&this.t){const t={access_token:this.i,session_token:this.o,refresh_token:this.h,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else _log("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){_log("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}if(this.A){const t=e?"SIGNED_IN":"USER_UPDATED";this.P(t)}}async j(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.h=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.P("SIGNED_OUT")}async q(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/magic-link/verify`);i.searchParams.set("token",t);const r=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Magic link verification failed");const s=await r.json(),o=this.N(s);return await this.K(o),o}async J(t){const e=this.getBaseUrl(),i=new URL(`${e}/api/v0/auth/verify-email`);i.searchParams.set("token",t);const r=await fetch(i.toString(),{method:"GET",headers:{...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!r.ok)throw await parseAuthError(r,"Email verification failed");if(!(r.headers.get("content-type")||r.headers.get("Content-Type")||"").includes("application/json"))return null;const s=await r.json(),o=this.N(s);return o.token&&o.user?.id?(await this.K(o),o):null}async W(t,e){const i=e||("undefined"!=typeof window&&window.location.pathname.includes("reset-password")?"recovery":null);if(!i)return!1;if(_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en query (type: ${i}). Procesando...`),this.C(i),"recovery"===i||"invite"===i)return this.O(t),this.R(),this.m="PASSWORD_RECOVERY",!0;if(this.R(),"magiclink"===i)return await this.q(t),this.m="SIGNED_IN",!0;if("signup"===i||"email_change"===i){const e=await this.J(t);return this.m=e?.token?"SIGNED_IN":null,!0}return!1}async T(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.G())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(!e)return void _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.session_token?t.session_token:t.access_token,this.h="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}const i=Math.floor(Date.now()/1e3),r=this.l&&this.l<=i;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${i}. Diferencia: ${(this.l||0)-i}s`),r&&this.h&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.m="SIGNED_IN"}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async G(){if("undefined"==typeof window)return!1;try{const t=new URLSearchParams(window.location.search),e=t.get("error"),i=t.get("token"),r=t.get("type");if(e){const i=t.get("error_code")||e,r=t.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${i} — ${r}`),this.p={code:i,message:r},this.R(),this.P("AUTH_ERROR"),!0}if(i)return await this.W(i,r);const s=window.location.hash.substring(1);if(!s)return!1;const o=new URLSearchParams(s);if(o.get("error")){const t=o.get("error_code")||"unknown",e=o.get("error_description")?.replace(/\+/g," ")||"Authentication error";return _log("warn",`[DYPAI SDK] ⚠️ Auth callback error: ${t} — ${e}`),this.p={code:t,message:e},this.R(),this.P("AUTH_ERROR"),!0}const n=o.get("access_token");if(!n)return!1;const a=o.get("refresh_token"),h=o.get("type"),c=parseInt(o.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Auth callback detectado en URL hash (type: ${h||"unknown"}). Procesando...`),this.C(h),this.R(),this.o=n,this.i=n,this.h=a||null,this.l=Math.floor(Date.now()/1e3)+c;const l=await fetch(`${this.getBaseUrl()}/api/v0/auth/get-session`,{headers:{Authorization:`Bearer ${n}`,...this.config.apiKey&&{"x-api-key":this.config.apiKey}}});if(!l.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${l.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,this.l=null,this.p={code:"token_invalid",message:"The authentication link is invalid or has expired"},this.P("AUTH_ERROR"),!0;const u=await l.json(),d=u?.jwt||l.headers.get("set-auth-jwt"),p=this.Y(u),f={token:d||n,sessionToken:n,refreshToken:a||void 0,expiresIn:c,expiresAt:this.l,user:p};return await this.K(f),this.m="recovery"===h||"invite"===h?"PASSWORD_RECOVERY":"SIGNED_IN",_log("log",`[DYPAI SDK] 🔑 Callback type=${h}. Pending event: ${this.m}`),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}M(){return this.i&&this.t?{access_token:this.i,refresh_token:this.h||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}P(t="USER_UPDATED"){const e=this.M();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.u.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.u.forEach(i=>i(t,e))}handleSessionExpired(){this.j("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=5,AuthModule.AUTO_REFRESH_TICK_DURATION_MS=3e4,AuthModule.AUTO_REFRESH_TICK_THRESHOLD=3,AuthModule.RETRY_BASE_MS=200,AuthModule.MAX_RETRY_MS=3e4,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS=5e3,AuthModule.CALLBACK_TYPE_STORAGE_KEY="dypai-auth-callback-type",AuthModule.CALLBACK_REDIRECT_STORAGE_KEY="dypai-auth-callback-redirect",AuthModule.RESET_TOKEN_STORAGE_KEY="dypai-auth-reset-token";class RealtimeChannel{constructor(t,e,i){this.ws=null,this.bindings=[],this.V={},this.H=!1,this.topic=t,this.client=e,this.config=i||{}}on(t,e,i){return this.bindings.push({type:t,filter:e,callback:i}),this}subscribe(t){this.X=t;const e=this.client.Z();this.ws=e;const i=()=>{const i=this.bindings.filter(t=>"postgres_changes"===t.type);for(const t of i)e.send(JSON.stringify({type:"subscribe",table:t.filter.table||"*",event:t.filter.event||"*",filter:t.filter.filter?this.tt(t.filter.filter):void 0,schema:t.filter.schema||"public"}));const r=this.bindings.some(t=>"broadcast"===t.type),s=this.bindings.some(t=>"presence"===t.type);(r||s)&&e.send(JSON.stringify({type:"join",channel:this.topic,presence:!!s||void 0})),this.H=!0,t?.("SUBSCRIBED")};e.readyState===WebSocket.OPEN?i():e.readyState===WebSocket.CONNECTING&&e.addEventListener("open",i,{once:!0}),this.et&&e.removeEventListener("message",this.et);const r=t=>{try{const e=JSON.parse("string"==typeof t.data?t.data:"");this.it(e)}catch{}};return this.et=r,e.addEventListener("message",r),e.addEventListener("close",()=>{this.H=!1,t?.("CLOSED")},{once:!0}),e.addEventListener("error",()=>{t?.("CHANNEL_ERROR",new Error("WebSocket error"))},{once:!0}),this}rt(t){const e=this.bindings.filter(t=>"postgres_changes"===t.type);for(const i of e)t.send(JSON.stringify({type:"subscribe",table:i.filter.table||"*",event:i.filter.event||"*",filter:i.filter.filter?this.tt(i.filter.filter):void 0,schema:i.filter.schema||"public"}));const i=this.bindings.some(t=>"broadcast"===t.type),r=this.bindings.some(t=>"presence"===t.type);(i||r)&&t.send(JSON.stringify({type:"join",channel:this.topic,presence:!!r||void 0})),this.X?.("SUBSCRIBED")}async send(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"broadcast",channel:this.topic,event:t.event,payload:t.payload,self:this.config.broadcast?.self||!1})),"ok"}catch{return"error"}}async track(t){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)return"error";try{return this.ws.send(JSON.stringify({type:"presence",channel:this.topic,data:t})),"ok"}catch{return"error"}}async untrack(){return"ok"}presenceState(){return{...this.V}}async unsubscribe(){if(this.et&&this.ws&&(this.ws.removeEventListener("message",this.et),this.et=void 0),this.ws?.readyState===WebSocket.OPEN){const t=this.bindings.filter(t=>"postgres_changes"===t.type);for(const e of t)this.ws.send(JSON.stringify({type:"unsubscribe",table:e.filter.table}));this.ws.send(JSON.stringify({type:"leave",channel:this.topic}))}return this.H=!1,this.bindings=[],this.V={},"ok"}it(t){if("change"===t.type)for(const e of this.bindings)"postgres_changes"===e.type&&(e.filter.table&&"*"!==e.filter.table&&e.filter.table!==t.table||e.filter.event&&"*"!==e.filter.event&&e.filter.event!==t.event||e.callback({schema:"public",table:t.table,eventType:t.event,new:t.record||{},old:t.old_record||{},commit_timestamp:t.timestamp}));if("broadcast"===t.type&&t.channel===this.topic)for(const e of this.bindings)"broadcast"===e.type&&("*"!==e.filter.event&&e.filter.event!==t.event||e.callback({event:t.event,payload:t.payload,sender:t.sender,timestamp:t.timestamp}));if("presence_state"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";this.V={};for(const[i,r]of Object.entries(t.presences||{})){const t=r[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(r)}for(const t of this.bindings)"presence"===t.type&&("sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"}))}if("presence_diff"===t.type&&t.channel===this.topic){const e=this.config.presence?.key||"userId";if(t.joins)for(const[i,r]of Object.entries(t.joins)){const t=r[e]||i;this.V[t]||(this.V[t]=[]),this.V[t].push(r);for(const e of this.bindings)"presence"===e.type&&("join"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"join",key:t,newPresences:[r],currentPresences:this.V[t]}))}if(t.leaves)for(const[i,r]of Object.entries(t.leaves)){const t=r[e]||i;this.V[t]=(this.V[t]||[]).filter(t=>t.userId!==i),0===this.V[t].length&&delete this.V[t];for(const e of this.bindings)"presence"===e.type&&("leave"!==e.filter.event&&"*"!==e.filter.event||e.callback({event:"leave",key:t,leftPresences:[r],currentPresences:this.V[t]||[]}))}for(const t of this.bindings)"presence"!==t.type||"sync"!==t.filter.event&&"*"!==t.filter.event||t.callback({event:"sync"})}}tt(t){const e=t.match(/^(\w+)=eq\.(.+)$/);if(e)return{[e[1]]:e[2]}}}class DypaiRealtimeClient{constructor(t,e){this.ws=null,this.channels=new Map,this.heartbeatTimer=null,this.reconnectAttempt=0,this.reconnectTimer=null,this.baseUrl=t,this.tokenProvider=e}channel(t,e){const i=this.channels.get(t);if(i)return i;const r=new RealtimeChannel(t,this,e?.config);return this.channels.set(t,r),r}getChannels(){return Array.from(this.channels.values())}async removeChannel(t){await t.unsubscribe(),this.channels.delete(t.topic),0===this.channels.size&&this.disconnect()}async removeAllChannels(){for(const t of this.channels.values())await t.unsubscribe();this.channels.clear(),this.disconnect()}async setAuth(t){t&&(this.tokenProvider=()=>t),this.ws&&this.channels.size>0&&(this.disconnect(),this.Z())}disconnect(){this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.ws?.close(),this.ws=null}Z(){if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.ws;const t=this.tokenProvider(),e=this.baseUrl.replace(/^http/,"ws")+`/realtime?token=${encodeURIComponent(t||"")}`,i=new WebSocket(e);return this.ws=i,i.addEventListener("open",()=>{this.reconnectAttempt=0;for(const t of this.channels.values())t.H&&t.rt(i);this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>{i.readyState===WebSocket.OPEN&&i.send(JSON.stringify({type:"heartbeat"}))},25e3)}),i.addEventListener("close",()=>{if(this.ws=null,this.heartbeatTimer&&clearInterval(this.heartbeatTimer),this.channels.size>0&&!this.reconnectTimer){const t=DypaiRealtimeClient.RECONNECT_INTERVALS[Math.min(this.reconnectAttempt,DypaiRealtimeClient.RECONNECT_INTERVALS.length-1)];this.reconnectAttempt++,this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.channels.size>0&&this.Z()},t)}}),i}}DypaiRealtimeClient.RECONNECT_INTERVALS=[1e3,2e3,5e3,1e4,3e4];class DataModule{get direct(){if(!this.st)throw new Error("client.db.direct is not available. Pass serviceRoleKey to createClient() to enable direct database access.\n\nExample:\n const client = createClient(url, { serviceRoleKey: process.env.DYPAI_SERVICE_ROLE_KEY });\n\nWARNING: serviceRoleKey grants admin-level access. Only use from server-side code (scripts, migrations, seeds). Never expose it in browser/client-side code.");return this.st}set direct(t){this.st=t}constructor(t){this.api=t}from(t){return new QueryBuilder(t,this.api)}}class QueryBuilder{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class DirectDBModule{constructor(t){this.api=t}from(t){return new DirectQueryBuilder(t,this.api)}async sql(t,e,i){return this.api.post("/api/v0/admin/db/sql",{query:t,params:e||[],...void 0!==i?.limit&&{limit:i.limit}})}}class DirectQueryBuilder{constructor(t,e){this.table=t,this.api=e,this.ot="public",this.nt=[],this.ht=100,this.ct=0,this.ut="ASC"}schema(t){return this.ot=t,this}eq(t,e){return this.nt.push({column:t,operator:"eq",value:e}),this}neq(t,e){return this.nt.push({column:t,operator:"neq",value:e}),this}gt(t,e){return this.nt.push({column:t,operator:"gt",value:e}),this}gte(t,e){return this.nt.push({column:t,operator:"gte",value:e}),this}lt(t,e){return this.nt.push({column:t,operator:"lt",value:e}),this}lte(t,e){return this.nt.push({column:t,operator:"lte",value:e}),this}like(t,e){return this.nt.push({column:t,operator:"ilike",value:e}),this}ilike(t,e){return this.like(t,e)}isNull(t){return this.nt.push({column:t,operator:"is_null",value:null}),this}is(t,e){return this.isNull(t)}notNull(t){return this.nt.push({column:t,operator:"not_null",value:null}),this}in(t,e){return this.nt.push({column:t,operator:"in",value:e}),this}contains(t,e){return this.nt.push({column:t,operator:"array_contains",value:e}),this}containedBy(t,e){return this.nt.push({column:t,operator:"contained_by",value:e}),this}overlaps(t,e){return this.nt.push({column:t,operator:"overlaps",value:e}),this}textSearch(t,e){return this.nt.push({column:t,operator:"fts",value:e}),this}phraseSearch(t,e){return this.nt.push({column:t,operator:"phfts",value:e}),this}not(t,e,i){return this.nt.push({column:t,operator:"not",value:{operator:e,value:i}}),this}or(t){return this.nt.push({operator:"or",or:t}),this}limit(t){return this.ht=t,this}offset(t){return this.ct=t,this}orderBy(t,e="ASC"){return this.dt=t,this.ut=e,this}async select(t){const e=[...this.nt];let i=this.ft;if("string"==typeof t)i=t;else if(t&&"object"==typeof t)for(const[i,r]of Object.entries(t))e.push({column:i,operator:"eq",value:r});return this.execute("select",{...i&&{columns:i},filters:e,limit:this.ht,offset:this.ct,sort_by:this.dt,order:this.ut})}async single(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:new DypaiError("No rows found.",404)}:{data:Array.isArray(e)?e[0]:e,error:null}}async maybeSingle(){const t=await this.select();if(t.error)return t;const e=t.data;return!e||Array.isArray(e)&&0===e.length?{data:null,error:null}:{data:Array.isArray(e)?e[0]:e,error:null}}async count(){const t=await this.execute("select",{columns:"count(*) as count",filters:[...this.nt],limit:1,offset:0});if(t.error)return t;const e=t.data;return{data:Array.isArray(e)&&e.length>0?Number(e[0].count):0,error:null}}async insert(t){if(Array.isArray(t)&&t.length>1e3){const e=[];for(let i=0;i<t.length;i+=1e3){const r=t.slice(i,i+1e3),{data:s,error:o}=await this.execute("insert",{data:r,mode:"bulk"});if(o)return{data:null,error:o};s&&e.push(...Array.isArray(s)?s:[s])}return{data:e,error:null}}const e=Array.isArray(t)?"bulk":"single";return this.execute("insert",{data:t,mode:e})}async update(t){return 0===this.nt.length?{data:null,error:new DypaiError("Update requires at least one filter. Use .eq(), .in(), etc.",400)}:this.execute("update",{data:t,filters:this.nt})}async delete(){return 0===this.nt.length?{data:null,error:new DypaiError("Delete requires at least one filter.",400)}:this.execute("delete",{filters:this.nt})}async upsert(t,e="id"){return this.execute("upsert",{data:t,conflict_column:e})}async execute(t,e){return this.api.post("/api/v0/admin/db/query",{operation:t,schema_name:this.ot,table_name:this.table,...e})}}class UsersModule{constructor(t){this.api=t}async list(t={}){const e=await this.api.get("admin/users",{params:t});return e.data?.users&&(e.data.users=e.data.users.map(normalizeUser)),e}async create(t){const e=await this.api.post("admin/users",t);return e.data&&(e.data=normalizeUser(e.data)),e}async update(t,e){const i=await this.api.put(`admin/users/${t}`,e);return i.data&&(i.data=normalizeUser(i.data)),i}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:i,variant:r="default"}=t,s=`${"error"===r?"❌":"success"===r?"✅":"warning"===r?"⚠️":"info"===r?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===r?console.error(s):"warning"===r&&console.warn(s)},toast=t=>(customToastFunction||fallbackToast)(t);let serviceConfig={},globalTokenProvider=null;function setTokenProvider(t){globalTokenProvider=t}function configureApiService(t){serviceConfig={...serviceConfig,...t}}const pendingRequests=new Map;function getCompleteConfig(){let t=serviceConfig;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...serviceConfig}}catch(t){}return t}async function callApi(t,e,i,r,s,o,n,a){const h=getCompleteConfig(),c=a||null;if(!c&&!i.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof i||!i.trim())throw new Error("Endpoint debe ser un string válido");let l;if(i.startsWith("http"))l=i;else{const t=c.replace(/\/+$/,"");l=i.startsWith("/")?t+i:t+"/api/v0/"+i}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,r)=>{null!=r&&(Array.isArray(r)?r.forEach((t,r)=>e(`${i}[${r}]`,t)):"object"==typeof r?Object.entries(r).forEach(([t,r])=>e(`${i}[${t}]`,r)):t.append(i,String(r)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(l+=`?${i}`)}const u="GET"===e?`${e}:${l}:${JSON.stringify(r)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const d=r instanceof FormData,p={...h.headers||{},...d?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...n&&{"x-api-key":n}},f={method:e,headers:p,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(f.body=d?r:JSON.stringify(r));const y=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const w=(async()=>{const t=getCompleteConfig().toast||toast,i=(void 0!==s?s:!1!==h.showToasts)&&t;try{const r=await y(l,f);if(!r.ok){let e,s="Error en la petición";try{const t=await r.text();try{const i=JSON.parse(t);e=i,s=i.message||i.msg||i.error_description||i.error||s}catch{t.length<200&&(s=t)}}catch{}throw 401===r.status&&h.onUnauthorized&&h.onUnauthorized(),i&&t({title:"Error",description:s,variant:"error"}),new DypaiError(s,r.status,void 0,e)}!i||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const s=r.headers.get("content-type")||"";return s.includes("application/pdf")||s.includes("image/")||s.includes("audio/")||s.includes("video/")||s.includes("application/octet-stream")||s.includes("application/zip")||s.includes("application/vnd.openxmlformats-officedocument")?await r.blob():s.includes("application/json")?await r.json():await r.text()}finally{u&&pendingRequests.delete(u)}})();return u&&pendingRequests.set(u,w),w}async function handleSmartDownload(t,e,i,r){const s=r?.method||(i?"POST":"GET"),{data:o,error:n}=await("GET"===s?t.get(e,{params:r?.params}):t.post(e,i,{params:r?.params}));if(n)throw n;if(o instanceof Blob){const t=window.URL.createObjectURL(o),e=document.createElement("a");e.href=t,e.download=r?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(o&&"object"==typeof o&&("url"in o||"signed_url"in o||"signedUrl"in o)){const t=o.url||o.signed_url||o.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=r?.fileName||i;if(s){const e=document.createElement("a");e.href=t,e.download=s,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}async function handleSmartUpload(t,e,i,r){const{data:s,error:o}=await t.post(e,{file_path:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.size,confirm:!1,client_upload:!0,...r?.params||{}});if(o)throw o;const n=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return normalizeUploadPayload(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t);const r=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of r){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(s);if(!n?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:a,method:h="PUT",headers:c={},file_path:l,storage_path:u}=n;r?.onProgress&&r.onProgress(10);const d=await fetch(a,{method:h,headers:{"Content-Type":i.type||"application/octet-stream",...c},body:i});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:n.bucket||r?.params?.bucket,file_path:l,storage_path:u,filename:i.name,content_type:i.type||"application/octet-stream",size_bytes:i.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),f}function normalizeUploadPayload(t){return t&&"object"==typeof t?{...t,upload_url:t.upload_url||t.uploadUrl||t.url,storage_path:t.storage_path||t.storagePath}:null}function createInternalApiClient(t){const e={get:createMethod(t,"GET"),post:createMethod(t,"POST"),put:createMethod(t,"PUT"),patch:createMethod(t,"PATCH"),delete:createMethod(t,"DELETE"),upload:async(t,i,r)=>{try{return{data:await handleSmartUpload(e,t,i,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,i,r)=>{try{return await handleSmartDownload(e,t,i,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return e}function createMethod(t,e){const i="POST"===e||"PUT"===e||"PATCH"===e;return async(r,s,o)=>{const n=t();let a,h={};i?(a=s,h=o||{}):(h=s||{},a=void 0);const c=h.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",l=h.apiKey||n.apiKey;try{return{data:await callApi(c,e,r,a,h.showToasts,h.params,l,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,i={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,r)=>{try{return{data:await handleSmartUpload(i,t,e,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,e,r)=>{try{return await handleSmartDownload(i,t,e,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return i}function createMethodFromCtx(t,e){return async(i,r,s)=>{const o=t(),n=await async function(t,e,i){let r,s={};"POST"===t||"PUT"===t||"PATCH"===t?(r=e,s=i||{}):(s=e||{},r=void 0);let o=s.token;return!o&&globalTokenProvider&&(o=globalTokenProvider()||""),o=o||"",{token:o,apiKey:s.apiKey,body:r,params:s.params,showToasts:s.showToasts}}(e,r,s),a=n.token||o.token||"",h=n.apiKey||o.apiKey;try{return{data:await callApi(a,e,i,n.body,n.showToasts,n.params,h,o.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}class DypaiClient{get baseUrl(){return this.yt}getAuthToken(){return this.auth.token||this.wt||null}constructor(t){const{baseUrl:e,apiKey:i}=t;this.yt=e,this.wt=i,this.realtime=new DypaiRealtimeClient(e,()=>this.getAuthToken());const r=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:i,storageKey:r,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s=createInternalApiClient(()=>({token:this.auth.token,apiKey:i,baseUrl:e}));if(this.auth.api=s,this.db=new DataModule(s),t.serviceRoleKey)if("undefined"!=typeof window&&void 0!==window.document)console.error("[DYPAI SDK] ❌ serviceRoleKey detected in a browser environment. This is a security risk — the key grants admin-level database access. db.direct has been disabled. Use serviceRoleKey only from server-side code (Node.js, Bun, Deno, scripts).");else{const i=createInternalApiClient(()=>({token:t.serviceRoleKey,baseUrl:e}));this.db.direct=new DirectDBModule(i)}if(this.users=new UsersModule(s),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),setTokenProvider(()=>this.auth.token),"undefined"!=typeof window&&t.redirects){const e=t.redirects;this.auth.k?.then(()=>{if(this.auth.isPasswordRecoveryCallback&&e.passwordRecovery)this.auth.consumeCallbackType(),window.location.pathname.includes(e.passwordRecovery)||window.location.replace(e.passwordRecovery);else if(this.auth.lastError&&e.authError)window.location.pathname.includes(e.authError)||window.location.replace(e.authError);else if(this.auth.token&&e.signIn){const t=this.auth.consumeCallbackType();t&&"recovery"!==t&&"invite"!==t&&(window.location.pathname.includes(e.signIn)||window.location.replace(e.signIn))}})}configureApiService({onUnauthorized:()=>this.auth.handleSessionExpired()})}from(t){return this.db.from(t)}async me(){return this.auth.getUser()}}let globalConfig={};function applyConfigToAllServices(){try{const{configureApiService:t}=require("../services/ApiService");t(globalConfig)}catch(t){}}exports.DirectDBModule=DirectDBModule,exports.DirectQueryBuilder=DirectQueryBuilder,exports.DypaiClient=DypaiClient,exports.DypaiError=DypaiError,exports.DypaiRealtimeClient=DypaiRealtimeClient,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"1.1.0",description:"Official JavaScript/TypeScript SDK for DYPAI",features:["Authentication (email, OAuth, OTP)","Database CRUD","Direct database access (service role)","Custom endpoints (typed API)","File upload/download (Smart Upload)","User management (admin)","React hooks (useAuth, useEndpoint, useAction, useUpload)"]},exports.RealtimeChannel=RealtimeChannel,exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}};
|
package/package.json
CHANGED