@dypai-ai/client-sdk 0.0.10 → 0.0.11
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.
Potentially problematic release.
This version of @dypai-ai/client-sdk might be problematic. Click here for more details.
- package/dist/index.d.ts +29 -9
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -339,14 +339,17 @@ declare class AuthModule {
|
|
|
339
339
|
private _refreshToken;
|
|
340
340
|
private _expiresAt;
|
|
341
341
|
private _listeners;
|
|
342
|
-
private
|
|
343
|
-
private
|
|
344
|
-
private
|
|
345
|
-
private
|
|
346
|
-
private static readonly REFRESH_COOLDOWN_MS;
|
|
342
|
+
private _refreshDeferred;
|
|
343
|
+
private _autoRefreshInterval;
|
|
344
|
+
private _retryCount;
|
|
345
|
+
private _lastRefreshAttempt;
|
|
347
346
|
private static readonly REFRESH_TIMEOUT_MS;
|
|
348
347
|
private static readonly MAX_REFRESH_FAILURES;
|
|
349
|
-
private static readonly
|
|
348
|
+
private static readonly AUTO_REFRESH_TICK_DURATION_MS;
|
|
349
|
+
private static readonly AUTO_REFRESH_TICK_THRESHOLD;
|
|
350
|
+
private static readonly RETRY_BASE_MS;
|
|
351
|
+
private static readonly MAX_RETRY_MS;
|
|
352
|
+
private static readonly LOCK_ACQUIRE_TIMEOUT_MS;
|
|
350
353
|
private readonly STORAGE_KEY;
|
|
351
354
|
private storage;
|
|
352
355
|
private _sessionRecovered;
|
|
@@ -405,9 +408,14 @@ declare class AuthModule {
|
|
|
405
408
|
recoverPassword(data: PasswordRecoveryRequest): Promise<DypaiResponse<PasswordRecoveryResponse>>;
|
|
406
409
|
/**
|
|
407
410
|
* Refresca la sesión actual utilizando el refresh token.
|
|
408
|
-
*
|
|
411
|
+
* Usa Web Locks API para coordinar entre pestañas y Deferred para deduplicar dentro de la misma pestaña.
|
|
409
412
|
*/
|
|
410
413
|
refreshSession(): Promise<DypaiResponse<AuthResponse>>;
|
|
414
|
+
/**
|
|
415
|
+
* Lógica interna de refresh. Se ejecuta dentro del Web Lock.
|
|
416
|
+
* Antes de llamar a GoTrue, verifica si otra pestaña ya refrescó el token.
|
|
417
|
+
*/
|
|
418
|
+
private _callRefreshToken;
|
|
411
419
|
/**
|
|
412
420
|
* Envía un código OTP (One Time Password) al email o teléfono indicado.
|
|
413
421
|
* Utilizado para login sin contraseña (Magic Link o SMS OTP).
|
|
@@ -424,9 +432,21 @@ declare class AuthModule {
|
|
|
424
432
|
*/
|
|
425
433
|
updateUser(attributes: UpdateUserAttributes): Promise<DypaiResponse<User>>;
|
|
426
434
|
/**
|
|
427
|
-
*
|
|
435
|
+
* Inicia el auto-refresh basado en ticks (estilo Supabase).
|
|
436
|
+
* Cada 30s verifica si el token está próximo a expirar y lo refresca.
|
|
437
|
+
* Más robusto que setTimeout porque Chrome throttlea setTimeout en background tabs.
|
|
438
|
+
*/
|
|
439
|
+
startAutoRefresh(): void;
|
|
440
|
+
/**
|
|
441
|
+
* Detiene el auto-refresh.
|
|
442
|
+
*/
|
|
443
|
+
stopAutoRefresh(): void;
|
|
444
|
+
/**
|
|
445
|
+
* Tick de auto-refresh. Se ejecuta cada 30s.
|
|
446
|
+
* Si el token expira dentro de 3 ticks (90s), intenta refrescar.
|
|
447
|
+
* Respeta el backoff exponencial si hubo fallos recientes.
|
|
428
448
|
*/
|
|
429
|
-
private
|
|
449
|
+
private _autoRefreshTick;
|
|
430
450
|
private _normalizeUser;
|
|
431
451
|
private _handleAuthResponse;
|
|
432
452
|
private _updateUser;
|
package/dist/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class t extends Error{constructor(t,e=500,n,s){super(t),this.status=e,this.code=n,this.details=s,this.name="DypaiError"}}const e={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 n(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 s{constructor(t,n=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=[],this.u=null,this.p=null,this.S=0,this.m=0,this.storage=t.storage||e;const s=t.storageKey||this.deriveStorageKey(t.apiKey);console.log(`[DYPAI SDK] 🛠️ Inicializando AuthModule (apiKey: ${t.apiKey?.substring(0,8)}..., storageKey: ${s})`),this.STORAGE_KEY=`dypai-${s}-auth-session`,this.D=this.v().then(()=>{this.i&&this.getUser().catch(()=>{})}),"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&&(console.log("[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.v(!1))}))}async I(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(console.log("[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado desde storage..."),await this.v(!0))}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}onAuthStateChange(t){return console.log("[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.D.then(()=>{const e=this._(),n=e?"SIGNED_IN":"INITIAL_SESSION";console.log(`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${n} (Sesión activa: ${!!e})`),t(n,e)}).catch(e=>{console.error("[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(e){return n((async()=>{const n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,s={email:e.email||e.identifier||"",password:e.password},i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(s)});if(!i.ok){const e=await i.json();throw new t(e.msg||e.error_description||"Login failed",i.status)}const o=await i.json(),a={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this.$(o)};return await this.P(a),a})())}async login(t){return this.signInWithPassword(t)}async signUp(e){return n((async()=>{const n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,s={email:e.email,password:e.password,data:e.user_data||{}},i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(s)});if(!i.ok){const e=await i.json();throw new t(e.msg||e.error_description||"Registration failed",i.status)}const o=await i.json(),a={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this.$(o)};return a.token&&await this.P(a),a})())}async register(t){return this.signUp(t)}async getSession(){try{if(!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(console.log("[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>console.warn("Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.o||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return n((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!n.ok)throw new t("Session invalid",n.status);const s=await n.json(),i=this.$(s);return this.A(i),i})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return n((async()=>{const{redirectTo:n=window.location.href}=e,s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/authorize?provider=${t}&redirect_to=${encodeURIComponent(n)}`;window.location.href=s})())}async signOut(){return n((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.T("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(e){return n((async()=>{const n=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${n}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e})});if(!s.ok)throw new t("Recovery failed",s.status);return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async refreshSession(){if(this.p)return console.log("[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.p;const e=Date.now()-this.S;if(this.S>0&&e<s.REFRESH_COOLDOWN_MS){const n=Math.ceil((s.REFRESH_COOLDOWN_MS-e)/1e3);return console.log(`[DYPAI SDK] ⏳ Refresh en cooldown. Reintentando en ${n}s...`),{data:null,error:new t(`Refresh en cooldown (${n}s restantes)`,429)}}return console.log("[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token..."),this.p=(async()=>{try{if(!this.o)throw new t("No hay refresh token disponible",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,n=new AbortController,i=setTimeout(()=>n.abort(),s.REFRESH_TIMEOUT_MS);let o;try{o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o}),signal:n.signal})}finally{clearTimeout(i)}if(!o.ok){let e={};try{e=await o.json()}catch{}const n=o.status,s=e.error||e.code||"",i=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===s||i.includes("invalid refresh token")||i.includes("revoked");throw n>=400&&n<500&&a?(console.error("[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.T(`refreshSession failed (${n}: ${s})`)):console.warn(`[DYPAI SDK] ⚠️ Fallo en refresco (${n}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",n)}const a=await o.json(),r={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this.$(a)};return this.S=0,this.m=0,await this.P(r),console.log(`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${r.expiresAt} (en ${r.expiresIn}s). Refresh token actualizado: ${!!r.refreshToken}`),{data:r,error:null}}catch(e){this.S=Date.now(),this.m++;const n=e instanceof DOMException&&"AbortError"===e.name,i=n?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401);return n?console.error(`[DYPAI SDK] ⏱️ Timeout en refresh después de ${s.REFRESH_TIMEOUT_MS/1e3}s. El servidor no respondió.`):console.error("[DYPAI SDK] ❌ Refresh falló:",i.message,`(status: ${i.status}). Cooldown activo ${s.REFRESH_COOLDOWN_MS/1e3}s.`),this.m>=s.MAX_REFRESH_FAILURES&&(console.error(`[DYPAI SDK] 🗑️ ${this.m} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.T(`${this.m} consecutive refresh failures`),this.m=0),{data:null,error:i}}finally{this.p=null}})(),this.p}async signInWithOtp(e){return n((async()=>{const n=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${n}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new t(e.detail||"OTP request failed",s.status)}return await s.json()})())}async verifyOtp(e){return n((async()=>{const n=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${n}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new t(e.detail||"OTP verification failed",s.status)}const i=await s.json(),o={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this.$(i)};return o.token&&await this.P(o),o})())}async updateUser(e){return n((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,s=await fetch(n,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new t(e.detail||"Update user failed",s.status)}const i=await s.json(),o=this.$(i);return this.A(o),o})())}k(t){this.u&&(clearTimeout(this.u),this.u=null);const e=t-Math.floor(Date.now()/1e3)-60;e>0?(console.log(`[DYPAI SDK] ⏱️ Refresco de sesión programado en ${e} segundos.`),this.u=setTimeout(()=>{this.refreshSession().catch(t=>{console.error("Error in auto-refresh:",t)})},1e3*e)):this.o&&this.refreshSession().catch(()=>{})}$(t){if(!t)return console.warn("[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const e=t.user||t,n=e.app_metadata||{},s=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:n.role||e.role,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:n,user_metadata:s,K:{name:n.role||"authenticated",weight:0},U:{app_id:"default"}}}async P(t){t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,await this.A(t.user,t.token,t.refreshToken,t.expiresAt),console.log(`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`))}async A(t,e,n,s){this.t=t,e&&(this.i=e),void 0!==n&&(this.o=n||null),void 0!==s&&(this.h=s||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,expires_at:this.h,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else console.warn("[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.",{hasToken:!!this.i,hasUser:!!this.t})}catch(t){console.error("[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}this.h&&this.k(this.h);const i=e?"SIGNED_IN":"USER_UPDATED";this.O(i)}async T(t="unknown"){console.log(`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.t=null,this.h=null,this.u&&(clearTimeout(this.u),this.u=null);try{await this.storage.removeItem(this.STORAGE_KEY),console.log("[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){console.error("[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.O("SIGNED_OUT")}async v(t=!0){console.log("[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){console.log("[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");const t=JSON.parse(e);this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user}else{console.log("[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),n=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!n)return void console.log("[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{console.log("[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");const s=JSON.parse(n);this.i=e,this.t=s;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=i,this.h=o?parseInt(o,10):null,await this.A(s,this.i||void 0,this.o||void 0,this.h||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}console.log("[DYPAI SDK] ✨ Migración completada con éxito.")}}const n=Math.floor(Date.now()/1e3),i=this.h&&this.h<=n,o=i?n-(this.h||0):0;if(console.log(`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${n}. Diferencia: ${(this.h||0)-n}s`),i&&o>s.MAX_TOKEN_AGE_S)return console.warn(`[DYPAI SDK] 🗑️ Token expirado hace ${Math.floor(o/3600)}h (>${s.MAX_TOKEN_AGE_S/3600}h límite). Sesión irrecuperable. Limpiando...`),void this.T(`Token expired ${Math.floor(o/3600)}h ago, exceeds max age`);if(i&&this.o&&t){console.log("[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void console.error("[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&(console.log("[DYPAI SDK] ✨ Sesión válida. Programando refresco futuro."),this.k(this.h));this.O("SIGNED_IN")}catch(t){console.error("[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}_(){return this.i&&this.t?{access_token:this.i,refresh_token:this.o||void 0,expires_at:this.h||void 0,token_type:"bearer",user:this.t}:null}O(t="USER_UPDATED"){const e=this._();console.log(`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(n=>n(t,e))}handleSessionExpired(){this.T("handleSessionExpired called (likely 401 from API)")}}s.REFRESH_COOLDOWN_MS=1e4,s.REFRESH_TIMEOUT_MS=15e3,s.MAX_REFRESH_FAILURES=3,s.MAX_TOKEN_AGE_S=604800;class i{constructor(t){this.api=t}from(t){return new o(t,this.api)}}class o{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}`)}}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 desconocido",e.status||500)}}}class r{constructor(t){this.api=t}async list(t={}){return a(this.api.get("admin/users",{params:t}))}async create(t){return a(this.api.post("admin/users",{body:t}))}async update(t,e){return a(this.api.put(`admin/users/${t}`,{body:e}))}async delete(t){return a(this.api.delete(`admin/users/${t}`))}}let c=null;function h(t){c=t}const l=t=>{const{title:e,description:n,variant:s="default"}=t,i=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${n?`: ${n}`:""}`;"error"===s?console.error(i):"warning"===s?console.warn(i):console.log(i)};function u(){const t=c||l;return{toast:t,toastSuccess:(e,n)=>t({title:e,description:n,variant:"success"}),toastError:(e,n)=>t({title:e,description:n,variant:"error"}),toastWarning:(e,n)=>t({title:e,description:n,variant:"warning"}),toastInfo:(e,n)=>t({title:e,description:n,variant:"info"})}}const d=t=>(c||l)(t),p=(t,e)=>d({title:t,description:e,variant:"success"}),f=(t,e)=>d({title:t,description:e,variant:"error"}),y=(t,e)=>d({title:t,description:e,variant:"warning"}),w=(t,e)=>d({title:t,description:e,variant:"info"});let S={},m=null;function D(t){m=t}function g(t){S={...S,...t}}const v=new Map;function I(){let t=S;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...S}}catch(t){}return t}async function _(e,n,s,i,o,a,r,c){const h=I(),l=c||null;if(!l&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");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(a&&Object.keys(a).length>0){const t=new URLSearchParams,e=(n,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${n}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${n}[${t}]`,s)):t.append(n,String(s)))};Object.entries(a).forEach(([t,n])=>e(t,n));const n=t.toString();n&&(u+=`?${n}`)}const p="GET"===n?`${n}:${u}:${JSON.stringify(i)}`:null;if(p&&v.has(p))return v.get(p);const f=i instanceof FormData,y={...h.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...r&&{"x-api-key":r}},w={method:n,headers:y,credentials:"include"};i&&"GET"!==n&&"DELETE"!==n&&(w.body=f?i:JSON.stringify(i));const S=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!S)throw new Error("Fetch no disponible.");const m=(async()=>{const e=I().toast||d,l=(void 0!==o?o:!1!==h.showToasts)&&e;try{const d=await S(u,w);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.onTokenExpired){console.log("[DYPAI SDK] 🔄 Token caducado detectado. Intentando refresco atómico...");try{const t=await h.onTokenExpired();if(t)return console.log("[DYPAI SDK] ✅ Token refrescado con éxito. Reintentando petición..."),_(t,n,s,i,o,a,r,c)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}}throw l&&e({title:"Error",description:p,variant:"error"}),new t(p,d.status,void 0,u)}!l||"POST"!==n&&"PUT"!==n&&"PATCH"!==n&&"DELETE"!==n||e({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{p&&v.delete(p)}})();return p&&v.set(p,m),m}function $(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 P(t,e){return async(n,s,i)=>{const o=t();let a,r={};s&&"object"==typeof s&&("token"in s||"params"in s||"apiKey"in s)?(r=s,a=r.body):(a=s,r=i||{});const c=r.token||o.token||(m?m():"")||"",h=r.apiKey||o.apiKey;return _(c,e,n,a,r.showToasts,r.params,h,o.baseUrl)}}function A(t){const e=()=>"function"==typeof t?t():t;return{get:T(e,"GET"),post:T(e,"POST"),put:T(e,"PUT"),patch:T(e,"PATCH"),delete:T(e,"DELETE")}}function T(t,e){return async(n,s,i)=>{const o=t(),a=await async function(t,e){let n,s={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(s=t,n=s.body):(n=t,s=e||{});let i=s.token;return!i&&m&&(i=m()||""),i=i||"",{token:i,apiKey:s.apiKey,body:n,params:s.params,showToasts:s.showToasts}}(s,i),r=a.token||o.token||"",c=a.apiKey||o.apiKey;return _(r,e,n,a.body,a.showToasts,a.params,c,o.baseUrl)}}class k{constructor(t){this.api=t}from(t){return new E(t,this.api)}}class E{constructor(t,e){this.bucketName=t,this.api=e}async upload(e,n,s={}){return b(async()=>{const i=s.uploadEndpoint||`storage_upload_${this.bucketName}`,o={file_path:e,client_upload:s.clientUpload??!1,...s.params};if(s.clientUpload)return await async function(e,n,s,i){const o=await e.post(n,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,client_upload:!0,...i?.params||{}});console.log("[SmartUpload] prepareData:",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 $(e);const n=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of n)if(t&&(t.upload_url||t.uploadUrl||t.url))return $(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 $(t)}return null}(o);if(console.log("[SmartUpload] preparedPayload:",a),!a?.upload_url)throw new t("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:r,method:c="PUT",headers:h={},file_path:l,storage_path:u}=a;i?.onProgress&&i.onProgress(10),console.log("[SmartUpload] uploadRequest:",{upload_url:r,method:c,headers:h,file_path:l});const d=await fetch(r,{method:c,headers:{"Content-Type":s.type||"application/octet-stream",...h},body:s});if(console.log("[SmartUpload] uploadResponse:",{ok:d.ok,status:d.status}),!d.ok)throw new t("Error en la subida directa a la nube",d.status);i?.onProgress&&i.onProgress(90);const p=i?.confirmEndpoint||n,f=await e.post(p,{...i?.params,bucket:a.bucket||i?.params?.bucket,file_path:l,storage_path:u,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0,client_upload:!0});return console.log("[SmartUpload] confirmResult:",f),i?.onProgress&&i.onProgress(100),f}(this.api,i,n,{confirmEndpoint:s.confirmEndpoint,params:o,onProgress:s.onProgress});const a=s.contentType||n.type||"application/octet-stream",r=await async function(t){return new Promise((e,n)=>{const s=new FileReader;s.onload=()=>e(String(s.result)),s.onerror=()=>n(s.error),s.readAsDataURL(t)})}(n);return await this.api.post(i,{...o,content:r,content_type:a,size_bytes:n.size,confirm:!0})})}async download(t,e={}){return b(async()=>{const n=e.downloadEndpoint||`storage_download_${this.bucketName}`,s={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,n,s){const i=s?.method,o=await("GET"===i?t.get(e,{params:s?.params}):t.post(e,n,{params:s?.params}));if(o instanceof Blob){const t=window.URL.createObjectURL(o),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(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 n;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(n=t[1].replace(/"/g,""))}const i=s?.fileName||n;if(i){const e=document.createElement("a");e.href=t,e.download=i,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}(this.api,n,s,{fileName:e.fileName,method:e.method||"POST"})})}async createSignedUrl(t,e=15,n={}){return b(async()=>{const s=n.endpoint||`storage_download_${this.bucketName}`,i={bucket:this.bucketName,file_path:t,expires_minutes:e,download:!1,...n.params},o=await this.api.post(s,i);return{signedUrl:o.signed_url||o.signedUrl||o.url,expiresIn:o.expires_in||o.expiresIn||e,path:t}})}getPublicUrl(t){const e=t.replace(/^\/+/,"");return{data:{publicUrl:`${this.api.baseUrl||""}/storage/v1/render/public/${this.bucketName}/${e}`}}}async list(t="",e={}){return b(async()=>{const n=e.endpoint||`storage_list_${this.bucketName}`,s={bucket:this.bucketName,prefix:t||e.prefix||"",limit:e.limit,offset:e.offset,sort_by:e.sortBy,order:e.order,search:e.search,...e.params},i=await this.api.post(n,s);return{data:i.data||i.files||[],total:i.total,hasMore:i.has_more||i.hasMore||!1}})}async remove(t,e={}){return b(async()=>{const n=e.endpoint||`storage_delete_${this.bucketName}`,s=Array.isArray(t)?t:[t],i={bucket:this.bucketName,paths:s,...e.params};return await this.api.post(n,i),{success:!0}})}async move(e,n,s={}){return b(async()=>{throw new t("move() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}async copy(e,n,s={}){return b(async()=>{throw new t("copy() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}}async function b(e){try{return{data:await e(),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de storage",e.status||500)}}}class K{constructor(t){const{baseUrl:e,apiKey:n}=t,o=t.auth?.storageKey||t.storageKey;t.global&&function(t){S={...S,...t}}(t.global),this.auth=new s({baseUrl:e,apiKey:n,storageKey:o,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const a={get:P(c=()=>({token:this.auth.token,apiKey:n,baseUrl:e}),"GET"),post:P(c,"POST"),put:P(c,"PUT"),patch:P(c,"PATCH"),delete:P(c,"DELETE")};var c;this.auth.api=a,this.db=new i(a),this.users=new r(a),this.storage=new k(a),this.api=A(()=>({token:this.auth.token||"",apiKey:n,baseUrl:e})),D(()=>this.auth.token),g({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 U(t,e,n){if(!t||!e)throw new Error("createClient() requiere URL y API Key");return new K({baseUrl:t,apiKey:e,...n})}let O={};function Y(t){O={...O,toast:d,...t},x()}function N(){return{...O}}function x(){try{const{configureApiService:t}=require("../services/ApiService");t(O)}catch(t){}}function R(){O={},x()}async function C(){x()}const j={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Smart Storage (Upload/Download delegado)","Gestión de usuarios (Admin)"]};export{K as DypaiClient,j as PACKAGE_INFO,_ as callApi,g as configureApiService,Y as configureDypaiServices,A as createApiClient,U as createClient,N as getGlobalConfig,C as reloadDypaiConfig,R as resetGlobalConfig,h as setToastFunction,D as setTokenProvider,d as toast,f as toastError,w as toastInfo,p as toastSuccess,y as toastWarning,u as useToast};
|
|
1
|
+
class t extends Error{constructor(t,e=500,n,s){super(t),this.status=e,this.code=n,this.details=s,this.name="DypaiError"}}class e{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const n={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 s(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 i{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=[],this.u=null,this.p=null,this.S=0,this.D=0,this.storage=t.storage||n;const s=t.storageKey||this.deriveStorageKey(t.apiKey);console.log(`[DYPAI SDK] 🛠️ Inicializando AuthModule (apiKey: ${t.apiKey?.substring(0,8)}..., storageKey: ${s})`),this.STORAGE_KEY=`dypai-${s}-auth-session`,this.m=this.I().then(()=>{this.i&&(this.getUser().catch(()=>{}),this.startAutoRefresh())}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.v.bind(this)),window.addEventListener("focus",this.v.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(console.log("[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.I(!1))}))}async v(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(console.log("[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.I(!0),this.i&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.i}onAuthStateChange(t){return console.log("[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.m.then(()=>{const e=this.A(),n=e?"SIGNED_IN":"INITIAL_SESSION";console.log(`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${n} (Sesión activa: ${!!e})`),t(n,e)}).catch(e=>{console.error("[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.l=this.l.filter(e=>e!==t)}}}}}async signInWithPassword(e){return s((async()=>{const n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,s={email:e.email||e.identifier||"",password:e.password},i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(s)});if(!i.ok){const e=await i.json();throw new t(e.msg||e.error_description||"Login failed",i.status)}const o=await i.json(),a={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this._(o)};return await this.$(a),a})())}async login(t){return this.signInWithPassword(t)}async signUp(e){return s((async()=>{const n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,s={email:e.email,password:e.password,data:e.user_data||{}},i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(s)});if(!i.ok){const e=await i.json();throw new t(e.msg||e.error_description||"Registration failed",i.status)}const o=await i.json(),a={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this._(o)};return a.token&&await this.$(a),a})())}async register(t){return this.signUp(t)}async getSession(){try{if(!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(console.log("[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>console.warn("Error refreshing session in getSession:",t))),{data:{access_token:this.i,refresh_token:this.o||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return s((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!n.ok)throw new t("Session invalid",n.status);const s=await n.json(),i=this._(s);return this.k(i),i})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return s((async()=>{const{redirectTo:n=window.location.href}=e,s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/authorize?provider=${t}&redirect_to=${encodeURIComponent(n)}`;window.location.href=s})())}async signOut(){return s((async()=>{try{if(this.i){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.P("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(e){return s((async()=>{const n=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${n}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e})});if(!s.ok)throw new t("Recovery failed",s.status);return await s.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async refreshSession(){if(this.u)return console.log("[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.u.promise;const n=new e;this.u=n;const s=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,n){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const s=new AbortController,i=setTimeout(()=>s.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:s.signal},async()=>(clearTimeout(i),await n()))}catch(s){if("AbortError"===s.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await n();throw s}}return await n()}(s,i.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.T());n.resolve(t)}catch(e){const s=e instanceof t?e:new t(e.message||"Error refrescando sesión",401);n.resolve({data:null,error:s})}finally{this.u=null}})(),n.promise}async T(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),n=t.expires_at||0,s=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&n-s>60)return console.log("[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user,this.S=0,this.K("TOKEN_REFRESHED"),{data:{token:this.i,refreshToken:this.o||void 0,expiresAt:this.h||void 0,user:this.t},error:null}}if(!this.o)throw new t("No hay refresh token disponible",401);console.log("[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,s=new AbortController,o=setTimeout(()=>s.abort(),i.REFRESH_TIMEOUT_MS);let a;try{a=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.o}),signal:s.signal})}finally{clearTimeout(o)}if(!a.ok){let e={};try{e=await a.json()}catch{}const n=a.status,s=e.error||e.code||"",i=(e.error_description||e.msg||"").toLowerCase(),o="invalid_grant"===s||i.includes("invalid refresh token")||i.includes("revoked");throw n>=400&&n<500&&o?(console.error("[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.P(`refreshSession failed (${n}: ${s})`)):console.warn(`[DYPAI SDK] ⚠️ Fallo en refresco (${n}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",n)}const r=await a.json(),c={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this._(r)};return this.S=0,await this.$(c),console.log(`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${c.expiresAt} (en ${c.expiresIn}s). Refresh token actualizado: ${!!c.refreshToken}`),{data:c,error:null}}catch(e){this.S++,this.D=Date.now();const n=e instanceof DOMException&&"AbortError"===e.name,s=n?new t("Refresh token timeout (servidor no responde)",408):e instanceof t?e:new t(e.message||"Error refrescando sesión",401),o=Math.min(i.RETRY_BASE_MS*Math.pow(2,this.S),i.MAX_RETRY_MS);return n?console.error(`[DYPAI SDK] ⏱️ Timeout en refresh después de ${i.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.S})`):console.error("[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${o}ms (intento ${this.S})`),this.S>=i.MAX_REFRESH_FAILURES&&(console.error(`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.P(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:s}}}async signInWithOtp(e){return s((async()=>{const n=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${n}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new t(e.detail||"OTP request failed",s.status)}return await s.json()})())}async verifyOtp(e){return s((async()=>{const n=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${n}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new t(e.detail||"OTP verification failed",s.status)}const i=await s.json(),o={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this._(i)};return o.token&&await this.$(o),o})())}async updateUser(e){return s((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const n=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,s=await fetch(n,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!s.ok){const e=await s.json();throw new t(e.detail||"Update user failed",s.status)}const i=await s.json(),o=this._(i);return this.k(o),o})())}startAutoRefresh(){this.stopAutoRefresh(),console.log("[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.O(),this.p=setInterval(()=>this.O(),i.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(console.log("[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async O(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>i.AUTO_REFRESH_TICK_THRESHOLD*i.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(i.RETRY_BASE_MS*Math.pow(2,this.S),i.MAX_RETRY_MS);if(Date.now()-this.D<t)return}console.log(`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){console.error("[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}_(t){if(!t)return console.warn("[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const e=t.user||t,n=e.app_metadata||{},s=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:n.role||e.role,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:n,user_metadata:s,U:{name:n.role||"authenticated",weight:0},Y:{app_id:"default"}}}async $(t){t.token&&(this.i=t.token,this.o=t.refreshToken||null,this.h=t.expiresAt||null,await this.k(t.user,t.token,t.refreshToken,t.expiresAt),console.log(`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async k(t,e,n,s){this.t=t,e&&(this.i=e),void 0!==n&&(this.o=n||null),void 0!==s&&(this.h=s||null);try{if(this.i&&this.t){const t={access_token:this.i,refresh_token:this.o,expires_at:this.h,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else console.warn("[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.",{hasToken:!!this.i,hasUser:!!this.t})}catch(t){console.error("[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const i=e?"SIGNED_IN":"USER_UPDATED";this.K(i)}async P(t="unknown"){console.log(`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.i=null,this.o=null,this.t=null,this.h=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),console.log("[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){console.error("[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.K("SIGNED_OUT")}async I(t=!0){console.log("[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){console.log("[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");const t=JSON.parse(e);this.i=t.access_token,this.o=t.refresh_token,this.h=t.expires_at,this.t=t.user}else{console.log("[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),n=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!n)return void console.log("[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{console.log("[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");const s=JSON.parse(n);this.i=e,this.t=s;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=i,this.h=o?parseInt(o,10):null,await this.k(s,this.i||void 0,this.o||void 0,this.h||void 0);const a=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of a)try{await this.storage.removeItem(t)}catch(t){}console.log("[DYPAI SDK] ✨ Migración completada con éxito.")}}const n=Math.floor(Date.now()/1e3),s=this.h&&this.h<=n;if(console.log(`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${n}. Diferencia: ${(this.h||0)-n}s`),s&&this.o&&t){console.log("[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void console.error("[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&console.log("[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.K("SIGNED_IN")}catch(t){console.error("[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}A(){return this.i&&this.t?{access_token:this.i,refresh_token:this.o||void 0,expires_at:this.h||void 0,token_type:"bearer",user:this.t}:null}K(t="USER_UPDATED"){const e=this.A();console.log(`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(n=>n(t,e))}handleSessionExpired(){this.P("handleSessionExpired called (likely 401 from API)")}}i.REFRESH_TIMEOUT_MS=15e3,i.MAX_REFRESH_FAILURES=5,i.AUTO_REFRESH_TICK_DURATION_MS=3e4,i.AUTO_REFRESH_TICK_THRESHOLD=3,i.RETRY_BASE_MS=200,i.MAX_RETRY_MS=3e4,i.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class o{constructor(t){this.api=t}from(t){return new a(t,this.api)}}class a{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}`)}}async function r(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error desconocido",e.status||500)}}}class c{constructor(t){this.api=t}async list(t={}){return r(this.api.get("admin/users",{params:t}))}async create(t){return r(this.api.post("admin/users",{body:t}))}async update(t,e){return r(this.api.put(`admin/users/${t}`,{body:e}))}async delete(t){return r(this.api.delete(`admin/users/${t}`))}}let h=null;function l(t){h=t}const u=t=>{const{title:e,description:n,variant:s="default"}=t,i=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${n?`: ${n}`:""}`;"error"===s?console.error(i):"warning"===s?console.warn(i):console.log(i)};function d(){const t=h||u;return{toast:t,toastSuccess:(e,n)=>t({title:e,description:n,variant:"success"}),toastError:(e,n)=>t({title:e,description:n,variant:"error"}),toastWarning:(e,n)=>t({title:e,description:n,variant:"warning"}),toastInfo:(e,n)=>t({title:e,description:n,variant:"info"})}}const p=t=>(h||u)(t),f=(t,e)=>p({title:t,description:e,variant:"success"}),y=(t,e)=>p({title:t,description:e,variant:"error"}),w=(t,e)=>p({title:t,description:e,variant:"warning"}),S=(t,e)=>p({title:t,description:e,variant:"info"});let D={},m=null;function g(t){m=t}function I(t){D={...D,...t}}const v=new Map;function A(){let t=D;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...D}}catch(t){}return t}async function _(e,n,s,i,o,a,r,c){const h=A(),l=c||null;if(!l&&!s.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");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(a&&Object.keys(a).length>0){const t=new URLSearchParams,e=(n,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${n}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${n}[${t}]`,s)):t.append(n,String(s)))};Object.entries(a).forEach(([t,n])=>e(t,n));const n=t.toString();n&&(u+=`?${n}`)}const d="GET"===n?`${n}:${u}:${JSON.stringify(i)}`:null;if(d&&v.has(d))return v.get(d);const f=i instanceof FormData,y={...h.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...r&&{"x-api-key":r}},w={method:n,headers:y,credentials:"include"};i&&"GET"!==n&&"DELETE"!==n&&(w.body=f?i:JSON.stringify(i));const S=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!S)throw new Error("Fetch no disponible.");const D=(async()=>{const e=A().toast||p,l=(void 0!==o?o:!1!==h.showToasts)&&e;try{const d=await S(u,w);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.onTokenExpired){console.log("[DYPAI SDK] 🔄 Token caducado detectado. Intentando refresco atómico...");try{const t=await h.onTokenExpired();if(t)return console.log("[DYPAI SDK] ✅ Token refrescado con éxito. Reintentando petición..."),_(t,n,s,i,o,a,r,c)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}}throw l&&e({title:"Error",description:p,variant:"error"}),new t(p,d.status,void 0,u)}!l||"POST"!==n&&"PUT"!==n&&"PATCH"!==n&&"DELETE"!==n||e({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&&v.delete(d)}})();return d&&v.set(d,D),D}function $(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 k(t,e){return async(n,s,i)=>{const o=t();let a,r={};s&&"object"==typeof s&&("token"in s||"params"in s||"apiKey"in s)?(r=s,a=r.body):(a=s,r=i||{});const c=r.token||o.token||(m?m():"")||"",h=r.apiKey||o.apiKey;return _(c,e,n,a,r.showToasts,r.params,h,o.baseUrl)}}function P(t){const e=()=>"function"==typeof t?t():t;return{get:T(e,"GET"),post:T(e,"POST"),put:T(e,"PUT"),patch:T(e,"PATCH"),delete:T(e,"DELETE")}}function T(t,e){return async(n,s,i)=>{const o=t(),a=await async function(t,e){let n,s={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(s=t,n=s.body):(n=t,s=e||{});let i=s.token;return!i&&m&&(i=m()||""),i=i||"",{token:i,apiKey:s.apiKey,body:n,params:s.params,showToasts:s.showToasts}}(s,i),r=a.token||o.token||"",c=a.apiKey||o.apiKey;return _(r,e,n,a.body,a.showToasts,a.params,c,o.baseUrl)}}class E{constructor(t){this.api=t}from(t){return new b(t,this.api)}}class b{constructor(t,e){this.bucketName=t,this.api=e}async upload(e,n,s={}){return K(async()=>{const i=s.uploadEndpoint||`storage_upload_${this.bucketName}`,o={file_path:e,client_upload:s.clientUpload??!1,...s.params};if(s.clientUpload)return await async function(e,n,s,i){const o=await e.post(n,{file_path:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!1,client_upload:!0,...i?.params||{}});console.log("[SmartUpload] prepareData:",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 $(e);const n=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of n)if(t&&(t.upload_url||t.uploadUrl||t.url))return $(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 $(t)}return null}(o);if(console.log("[SmartUpload] preparedPayload:",a),!a?.upload_url)throw new t("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:r,method:c="PUT",headers:h={},file_path:l,storage_path:u}=a;i?.onProgress&&i.onProgress(10),console.log("[SmartUpload] uploadRequest:",{upload_url:r,method:c,headers:h,file_path:l});const d=await fetch(r,{method:c,headers:{"Content-Type":s.type||"application/octet-stream",...h},body:s});if(console.log("[SmartUpload] uploadResponse:",{ok:d.ok,status:d.status}),!d.ok)throw new t("Error en la subida directa a la nube",d.status);i?.onProgress&&i.onProgress(90);const p=i?.confirmEndpoint||n,f=await e.post(p,{...i?.params,bucket:a.bucket||i?.params?.bucket,file_path:l,storage_path:u,filename:s.name,content_type:s.type||"application/octet-stream",size_bytes:s.size,confirm:!0,client_upload:!0});return console.log("[SmartUpload] confirmResult:",f),i?.onProgress&&i.onProgress(100),f}(this.api,i,n,{confirmEndpoint:s.confirmEndpoint,params:o,onProgress:s.onProgress});const a=s.contentType||n.type||"application/octet-stream",r=await async function(t){return new Promise((e,n)=>{const s=new FileReader;s.onload=()=>e(String(s.result)),s.onerror=()=>n(s.error),s.readAsDataURL(t)})}(n);return await this.api.post(i,{...o,content:r,content_type:a,size_bytes:n.size,confirm:!0})})}async download(t,e={}){return K(async()=>{const n=e.downloadEndpoint||`storage_download_${this.bucketName}`,s={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,n,s){const i=s?.method,o=await("GET"===i?t.get(e,{params:s?.params}):t.post(e,n,{params:s?.params}));if(o instanceof Blob){const t=window.URL.createObjectURL(o),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(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 n;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(n=t[1].replace(/"/g,""))}const i=s?.fileName||n;if(i){const e=document.createElement("a");e.href=t,e.download=i,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}(this.api,n,s,{fileName:e.fileName,method:e.method||"POST"})})}async createSignedUrl(t,e=15,n={}){return K(async()=>{const s=n.endpoint||`storage_download_${this.bucketName}`,i={bucket:this.bucketName,file_path:t,expires_minutes:e,download:!1,...n.params},o=await this.api.post(s,i);return{signedUrl:o.signed_url||o.signedUrl||o.url,expiresIn:o.expires_in||o.expiresIn||e,path:t}})}getPublicUrl(t){const e=t.replace(/^\/+/,"");return{data:{publicUrl:`${this.api.baseUrl||""}/storage/v1/render/public/${this.bucketName}/${e}`}}}async list(t="",e={}){return K(async()=>{const n=e.endpoint||`storage_list_${this.bucketName}`,s={bucket:this.bucketName,prefix:t||e.prefix||"",limit:e.limit,offset:e.offset,sort_by:e.sortBy,order:e.order,search:e.search,...e.params},i=await this.api.post(n,s);return{data:i.data||i.files||[],total:i.total,hasMore:i.has_more||i.hasMore||!1}})}async remove(t,e={}){return K(async()=>{const n=e.endpoint||`storage_delete_${this.bucketName}`,s=Array.isArray(t)?t:[t],i={bucket:this.bucketName,paths:s,...e.params};return await this.api.post(n,i),{success:!0}})}async move(e,n,s={}){return K(async()=>{throw new t("move() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}async copy(e,n,s={}){return K(async()=>{throw new t("copy() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}}async function K(e){try{return{data:await e(),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de storage",e.status||500)}}}class O{constructor(t){const{baseUrl:e,apiKey:n}=t,s=t.auth?.storageKey||t.storageKey;t.global&&function(t){D={...D,...t}}(t.global),this.auth=new i({baseUrl:e,apiKey:n,storageKey:s,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const a={get:k(r=()=>({token:this.auth.token,apiKey:n,baseUrl:e}),"GET"),post:k(r,"POST"),put:k(r,"PUT"),patch:k(r,"PATCH"),delete:k(r,"DELETE")};var r;this.auth.api=a,this.db=new o(a),this.users=new c(a),this.storage=new E(a),this.api=P(()=>({token:this.auth.token||"",apiKey:n,baseUrl:e})),g(()=>this.auth.token),I({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 U(t,e,n){if(!t||!e)throw new Error("createClient() requiere URL y API Key");return new O({baseUrl:t,apiKey:e,...n})}let Y={};function N(t){Y={...Y,toast:p,...t},R()}function x(){return{...Y}}function R(){try{const{configureApiService:t}=require("../services/ApiService");t(Y)}catch(t){}}function C(){Y={},R()}async function j(){R()}const M={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Smart Storage (Upload/Download delegado)","Gestión de usuarios (Admin)"]};export{O as DypaiClient,M as PACKAGE_INFO,_ as callApi,I as configureApiService,N as configureDypaiServices,P as createApiClient,U as createClient,x as getGlobalConfig,j as reloadDypaiConfig,C as resetGlobalConfig,l as setToastFunction,g as setTokenProvider,p as toast,y as toastError,S as toastInfo,f as toastSuccess,w as toastWarning,d as useToast};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";class DypaiError extends Error{constructor(e,t=500,o,s){super(e),this.status=t,this.code=o,this.details=s,this.name="DypaiError"}}const localStorageAdapter={getItem:e=>"undefined"==typeof window?null:window.localStorage.getItem(e),setItem:(e,t)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(e,t)}catch(e){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",e)}},removeItem:e=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(e)}catch(e){}}};async function wrapAuthResponse(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof DypaiError?e:new DypaiError(e.message||"Error de autenticación",e.status||400)}}}class AuthModule{constructor(e,t=null){this.config=e,this.t=null,this.o=null,this.i=null,this.l=null,this.h=[],this.u=null,this.p=null,this.D=0,this.S=0,this.storage=e.storage||localStorageAdapter;const o=e.storageKey||this.deriveStorageKey(e.apiKey);console.log(`[DYPAI SDK] 🛠️ Inicializando AuthModule (apiKey: ${e.apiKey?.substring(0,8)}..., storageKey: ${o})`),this.STORAGE_KEY=`dypai-${o}-auth-session`,this.m=this.A().then(()=>{this.o&&this.getUser().catch(()=>{})}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.v.bind(this)),window.addEventListener("focus",this.v.bind(this)),window.addEventListener("storage",e=>{e.key===this.STORAGE_KEY&&(console.log("[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.A(!1))}))}async v(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(console.log("[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado desde storage..."),await this.A(!0))}deriveStorageKey(e){return e?e.substring(0,8):"default"}get user(){return this.t}get token(){return this.o}onAuthStateChange(e){return console.log("[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(e),this.m.then(()=>{const t=this.T(),o=t?"SIGNED_IN":"INITIAL_SESSION";console.log(`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${o} (Sesión activa: ${!!t})`),e(o,t)}).catch(t=>{console.error("[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",t),e("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.h=this.h.filter(t=>t!==e)}}}}}async signInWithPassword(e){return wrapAuthResponse((async()=>{const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,o={email:e.email||e.identifier||"",password:e.password},s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!s.ok){const e=await s.json();throw new DypaiError(e.msg||e.error_description||"Login failed",s.status)}const n=await s.json(),i={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.P(n)};return await this.I(i),i})())}async login(e){return this.signInWithPassword(e)}async signUp(e){return wrapAuthResponse((async()=>{const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,o={email:e.email,password:e.password,data:e.user_data||{}},s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!s.ok){const e=await s.json();throw new DypaiError(e.msg||e.error_description||"Registration failed",s.status)}const n=await s.json(),i={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.P(n)};return i.token&&await this.I(i),i})())}async register(e){return this.signUp(e)}async getSession(){try{if(!this.o||!this.t)return{data:null,error:null};const e=Math.floor(Date.now()/1e3);return(this.l||0)-e<30&&this.i&&(console.log("[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(e=>console.warn("Error refreshing session in getSession:",e))),{data:{access_token:this.o,refresh_token:this.i||void 0,token_type:"bearer",user:this.t},error:null}}catch(e){return{data:null,error:new DypaiError(e.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,t=await fetch(e,{headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!t.ok)throw new DypaiError("Session invalid",t.status);const o=await t.json(),s=this.P(o);return this._(s),s})())}async me(){return this.getUser()}async signInWithOAuth(e,t={}){return wrapAuthResponse((async()=>{const{redirectTo:o=window.location.href}=t,s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/authorize?provider=${e}&redirect_to=${encodeURIComponent(o)}`;window.location.href=s})())}async signOut(){return wrapAuthResponse((async()=>{try{if(this.o){const e=this.config.baseUrl||"http://localhost:8000";await fetch(`${e}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.k("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(e){return wrapAuthResponse((async()=>{const t=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${t}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e})});if(!o.ok)throw new DypaiError("Recovery failed",o.status);return await o.json()})())}async recoverPassword(e){return this.resetPasswordForEmail(e.email)}async refreshSession(){if(this.p)return console.log("[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.p;const e=Date.now()-this.D;if(this.D>0&&e<AuthModule.REFRESH_COOLDOWN_MS){const t=Math.ceil((AuthModule.REFRESH_COOLDOWN_MS-e)/1e3);return console.log(`[DYPAI SDK] ⏳ Refresh en cooldown. Reintentando en ${t}s...`),{data:null,error:new DypaiError(`Refresh en cooldown (${t}s restantes)`,429)}}return console.log("[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token..."),this.p=(async()=>{try{if(!this.i)throw new DypaiError("No hay refresh token disponible",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,t=new AbortController,o=setTimeout(()=>t.abort(),AuthModule.REFRESH_TIMEOUT_MS);let s;try{s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.i}),signal:t.signal})}finally{clearTimeout(o)}if(!s.ok){let e={};try{e=await s.json()}catch{}const t=s.status,o=e.error||e.code||"",n=(e.error_description||e.msg||"").toLowerCase(),i="invalid_grant"===o||n.includes("invalid refresh token")||n.includes("revoked");throw t>=400&&t<500&&i?(console.error("[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.k(`refreshSession failed (${t}: ${o})`)):console.warn(`[DYPAI SDK] ⚠️ Fallo en refresco (${t}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(e.msg||e.error_description||"Refresh session failed",t)}const n=await s.json(),i={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.P(n)};return this.D=0,this.S=0,await this.I(i),console.log(`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${i.expiresAt} (en ${i.expiresIn}s). Refresh token actualizado: ${!!i.refreshToken}`),{data:i,error:null}}catch(e){this.D=Date.now(),this.S++;const t=e instanceof DOMException&&"AbortError"===e.name,o=t?new DypaiError("Refresh token timeout (servidor no responde)",408):e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);return t?console.error(`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. El servidor no respondió.`):console.error("[DYPAI SDK] ❌ Refresh falló:",o.message,`(status: ${o.status}). Cooldown activo ${AuthModule.REFRESH_COOLDOWN_MS/1e3}s.`),this.S>=AuthModule.MAX_REFRESH_FAILURES&&(console.error(`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.k(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:o}}finally{this.p=null}})(),this.p}async signInWithOtp(e){return wrapAuthResponse((async()=>{const t=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${t}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!o.ok){const e=await o.json();throw new DypaiError(e.detail||"OTP request failed",o.status)}return await o.json()})())}async verifyOtp(e){return wrapAuthResponse((async()=>{const t=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${t}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!o.ok){const e=await o.json();throw new DypaiError(e.detail||"OTP verification failed",o.status)}const s=await o.json(),n={token:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(s.expires_in||3600),user:this.P(s)};return n.token&&await this.I(n),n})())}async updateUser(e){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,o=await fetch(t,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!o.ok){const e=await o.json();throw new DypaiError(e.detail||"Update user failed",o.status)}const s=await o.json(),n=this.P(s);return this._(n),n})())}$(e){this.u&&(clearTimeout(this.u),this.u=null);const t=e-Math.floor(Date.now()/1e3)-60;t>0?(console.log(`[DYPAI SDK] ⏱️ Refresco de sesión programado en ${t} segundos.`),this.u=setTimeout(()=>{this.refreshSession().catch(e=>{console.error("Error in auto-refresh:",e)})},1e3*t)):this.i&&this.refreshSession().catch(()=>{})}P(e){if(!e)return console.warn("[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const t=e.user||e,o=t.app_metadata||{},s=t.user_metadata||{};return{id:t.id,email:t.email,phone:t.phone,role:o.role||t.role,created_at:t.created_at,updated_at:t.updated_at,confirmed_at:t.confirmed_at,last_sign_in_at:t.last_sign_in_at,app_metadata:o,user_metadata:s,C:{name:o.role||"authenticated",weight:0},R:{app_id:"default"}}}async I(e){e.token&&(this.o=e.token,this.i=e.refreshToken||null,this.l=e.expiresAt||null,await this._(e.user,e.token,e.refreshToken,e.expiresAt),console.log(`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`))}async _(e,t,o,s){this.t=e,t&&(this.o=t),void 0!==o&&(this.i=o||null),void 0!==s&&(this.l=s||null);try{if(this.o&&this.t){const e={access_token:this.o,refresh_token:this.i,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(e))}else console.warn("[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.",{hasToken:!!this.o,hasUser:!!this.t})}catch(e){console.error("[DYPAI SDK] ❌ Error guardando sesión en storage:",e)}this.l&&this.$(this.l);const n=t?"SIGNED_IN":"USER_UPDATED";this.K(n)}async k(e="unknown"){console.log(`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${e}`),this.o=null,this.i=null,this.t=null,this.l=null,this.u&&(clearTimeout(this.u),this.u=null);try{await this.storage.removeItem(this.STORAGE_KEY),console.log("[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(e){console.error("[DYPAI SDK] ❌ Error eliminando sesión de storage:",e)}this.K("SIGNED_OUT")}async A(e=!0){console.log("[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){console.log("[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");const e=JSON.parse(t);this.o=e.access_token,this.i=e.refresh_token,this.l=e.expires_at,this.t=e.user}else{console.log("[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const e=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),t=await this.storage.getItem(`dypai-auth-token-${e}`),o=await this.storage.getItem(`dypai-auth-user-${e}`);if(!t||!o)return void console.log("[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{console.log("[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");const s=JSON.parse(o);this.o=t,this.t=s;const n=await this.storage.getItem(`dypai-auth-refresh-token-${e}`),i=await this.storage.getItem(`dypai-auth-expires-at-${e}`);this.i=n,this.l=i?parseInt(i,10):null,await this._(s,this.o||void 0,this.i||void 0,this.l||void 0);const r=[`dypai-auth-token-${e}`,`dypai-auth-refresh-token-${e}`,`dypai-auth-user-${e}`,`dypai-auth-expires-at-${e}`];for(const e of r)try{await this.storage.removeItem(e)}catch(e){}console.log("[DYPAI SDK] ✨ Migración completada con éxito.")}}const o=Math.floor(Date.now()/1e3),s=this.l&&this.l<=o,n=s?o-(this.l||0):0;if(console.log(`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${o}. Diferencia: ${(this.l||0)-o}s`),s&&n>AuthModule.MAX_TOKEN_AGE_S)return console.warn(`[DYPAI SDK] 🗑️ Token expirado hace ${Math.floor(n/3600)}h (>${AuthModule.MAX_TOKEN_AGE_S/3600}h límite). Sesión irrecuperable. Limpiando...`),void this.k(`Token expired ${Math.floor(n/3600)}h ago, exceeds max age`);if(s&&this.i&&e){console.log("[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const e=await this.refreshSession();if(e.error)return void console.error("[DYPAI SDK] ❌ El refresco falló:",e.error.message)}else this.l&&(console.log("[DYPAI SDK] ✨ Sesión válida. Programando refresco futuro."),this.$(this.l));this.K("SIGNED_IN")}catch(e){console.error("[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",e)}}T(){return this.o&&this.t?{access_token:this.o,refresh_token:this.i||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}K(e="USER_UPDATED"){const t=this.T();console.log(`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${e} (Sesión activa: ${!!t})`),this.h.forEach(o=>o(e,t))}handleSessionExpired(){this.k("handleSessionExpired called (likely 401 from API)")}}AuthModule.REFRESH_COOLDOWN_MS=1e4,AuthModule.REFRESH_TIMEOUT_MS=15e3,AuthModule.MAX_REFRESH_FAILURES=3,AuthModule.MAX_TOKEN_AGE_S=604800;class DataModule{constructor(e){this.api=e}from(e){return new QueryBuilder(e,this.api)}}class QueryBuilder{constructor(e,t){this.table=e,this.api=t}async select(e={}){return this.api.get(this.table,{params:e})}async insert(e){return this.api.post(this.table,e)}async update(e,t){return this.api.patch(`${this.table}/${e}`,t)}async delete(e){return this.api.delete(`${this.table}/${e}`)}}async function wrapResponse(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof DypaiError?e:new DypaiError(e.message||"Error desconocido",e.status||500)}}}class UsersModule{constructor(e){this.api=e}async list(e={}){return wrapResponse(this.api.get("admin/users",{params:e}))}async create(e){return wrapResponse(this.api.post("admin/users",{body:e}))}async update(e,t){return wrapResponse(this.api.put(`admin/users/${e}`,{body:t}))}async delete(e){return wrapResponse(this.api.delete(`admin/users/${e}`))}}let customToastFunction=null;const fallbackToast=e=>{const{title:t,description:o,variant:s="default"}=e,n=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${t}${o?`: ${o}`:""}`;"error"===s?console.error(n):"warning"===s?console.warn(n):console.log(n)},toast=e=>(customToastFunction||fallbackToast)(e);let serviceConfig={},globalTokenProvider=null;function setTokenProvider(e){globalTokenProvider=e}function configureApiService(e){serviceConfig={...serviceConfig,...e}}const pendingRequests=new Map;function getCompleteConfig(){let e=serviceConfig;try{const{getGlobalConfig:t}=require("../config/global-config");e={...t(),...serviceConfig}}catch(e){}return e}async function callApi(e,t,o,s,n,i,r,a){const c=getCompleteConfig(),l=a||null;if(!l&&!o.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");if("string"!=typeof o||!o.trim())throw new Error("Endpoint debe ser un string válido");let h;if(o.startsWith("http"))h=o;else{const e=l.replace(/\/+$/,"");h=o.startsWith("/")?e+o:e+"/api/v0/"+o}if(i&&Object.keys(i).length>0){const e=new URLSearchParams,t=(o,s)=>{null!=s&&(Array.isArray(s)?s.forEach((e,s)=>t(`${o}[${s}]`,e)):"object"==typeof s?Object.entries(s).forEach(([e,s])=>t(`${o}[${e}]`,s)):e.append(o,String(s)))};Object.entries(i).forEach(([e,o])=>t(e,o));const o=e.toString();o&&(h+=`?${o}`)}const u="GET"===t?`${t}:${h}:${JSON.stringify(s)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const d=s instanceof FormData,p={...c.headers||{},...d?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...r&&{"x-api-key":r}},f={method:t,headers:p,credentials:"include"};s&&"GET"!==t&&"DELETE"!==t&&(f.body=d?s:JSON.stringify(s));const y=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const w=(async()=>{const e=getCompleteConfig().toast||toast,l=(void 0!==n?n:!1!==c.showToasts)&&e;try{const u=await y(h,f);if(!u.ok){let h,d="Error en la petición";try{const e=await u.text();try{const t=JSON.parse(e);h=t,d=t.message||t.msg||t.error_description||t.error||d}catch{e.length<200&&(d=e)}}catch{}if(401===u.status&&c.onTokenExpired){console.log("[DYPAI SDK] 🔄 Token caducado detectado. Intentando refresco atómico...");try{const e=await c.onTokenExpired();if(e)return console.log("[DYPAI SDK] ✅ Token refrescado con éxito. Reintentando petición..."),callApi(e,t,o,s,n,i,r,a)}catch(e){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",e)}}throw l&&e({title:"Error",description:d,variant:"error"}),new DypaiError(d,u.status,void 0,h)}!l||"POST"!==t&&"PUT"!==t&&"PATCH"!==t&&"DELETE"!==t||e({title:"Éxito",description:"Operación completada",variant:"success"});const d=u.headers.get("content-type")||"";return d.includes("application/pdf")||d.includes("image/")||d.includes("audio/")||d.includes("video/")||d.includes("application/octet-stream")||d.includes("application/zip")||d.includes("application/vnd.openxmlformats-officedocument")?await u.blob():d.includes("application/json")?await u.json():await u.text()}finally{u&&pendingRequests.delete(u)}})();return u&&pendingRequests.set(u,w),w}function normalizeUploadPayload(e){return e&&"object"==typeof e?{...e,upload_url:e.upload_url||e.uploadUrl||e.url,storage_path:e.storage_path||e.storagePath}:null}function createMethod(e,t){return async(o,s,n)=>{const i=e();let r,a={};s&&"object"==typeof s&&("token"in s||"params"in s||"apiKey"in s)?(a=s,r=a.body):(r=s,a=n||{});const c=a.token||i.token||(globalTokenProvider?globalTokenProvider():"")||"",l=a.apiKey||i.apiKey;return callApi(c,t,o,r,a.showToasts,a.params,l,i.baseUrl)}}function createApiClient(e){const t=()=>"function"==typeof e?e():e;return{get:createMethodFromCtx(t,"GET"),post:createMethodFromCtx(t,"POST"),put:createMethodFromCtx(t,"PUT"),patch:createMethodFromCtx(t,"PATCH"),delete:createMethodFromCtx(t,"DELETE")}}function createMethodFromCtx(e,t){return async(o,s,n)=>{const i=e(),r=await async function(e,t){let o,s={};e&&"object"==typeof e&&("token"in e||"params"in e||"apiKey"in e)?(s=e,o=s.body):(o=e,s=t||{});let n=s.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:s.apiKey,body:o,params:s.params,showToasts:s.showToasts}}(s,n),a=r.token||i.token||"",c=r.apiKey||i.apiKey;return callApi(a,t,o,r.body,r.showToasts,r.params,c,i.baseUrl)}}class StorageModule{constructor(e){this.api=e}from(e){return new StorageBucketBuilder(e,this.api)}}class StorageBucketBuilder{constructor(e,t){this.bucketName=e,this.api=t}async upload(e,t,o={}){return wrapStorageResponse(async()=>{const s=o.uploadEndpoint||`storage_upload_${this.bucketName}`,n={file_path:e,client_upload:o.clientUpload??!1,...o.params};if(o.clientUpload)return await async function(e,t,o,s){const n=await e.post(t,{file_path:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!1,client_upload:!0,...s?.params||{}});console.log("[SmartUpload] prepareData:",n);const i=function(e){if(!e||"object"!=typeof e)return null;const t=e.upload_url||e.uploadUrl||e.url?e:null;if(t?.upload_url)return normalizeUploadPayload(t);const o=[e.data,e.result,e.output,e.payload].filter(Boolean);for(const e of o)if(e&&(e.upload_url||e.uploadUrl||e.url))return normalizeUploadPayload(e);const s=[e.steps_results,e.nodes_results].filter(Boolean);for(const e of s){const t=Array.isArray(e)?e:Object.values(e);for(const e of t)if(e&&(e.upload_url||e.uploadUrl||e.url))return normalizeUploadPayload(e)}return null}(n);if(console.log("[SmartUpload] preparedPayload:",i),!i?.upload_url)throw new DypaiError("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:r,method:a="PUT",headers:c={},file_path:l,storage_path:h}=i;s?.onProgress&&s.onProgress(10),console.log("[SmartUpload] uploadRequest:",{upload_url:r,method:a,headers:c,file_path:l});const u=await fetch(r,{method:a,headers:{"Content-Type":o.type||"application/octet-stream",...c},body:o});if(console.log("[SmartUpload] uploadResponse:",{ok:u.ok,status:u.status}),!u.ok)throw new DypaiError("Error en la subida directa a la nube",u.status);s?.onProgress&&s.onProgress(90);const d=s?.confirmEndpoint||t,p=await e.post(d,{...s?.params,bucket:i.bucket||s?.params?.bucket,file_path:l,storage_path:h,filename:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!0,client_upload:!0});return console.log("[SmartUpload] confirmResult:",p),s?.onProgress&&s.onProgress(100),p}(this.api,s,t,{confirmEndpoint:o.confirmEndpoint,params:n,onProgress:o.onProgress});const i=o.contentType||t.type||"application/octet-stream",r=await async function(e){return new Promise((t,o)=>{const s=new FileReader;s.onload=()=>t(String(s.result)),s.onerror=()=>o(s.error),s.readAsDataURL(e)})}(t);return await this.api.post(s,{...n,content:r,content_type:i,size_bytes:t.size,confirm:!0})})}async download(e,t={}){return wrapStorageResponse(async()=>{const o=t.downloadEndpoint||`storage_download_${this.bucketName}`,s={bucket:this.bucketName,file_path:e,...t.params};await async function(e,t,o,s){const n=s?.method,i=await("GET"===n?e.get(t,{params:s?.params}):e.post(t,o,{params:s?.params}));if(i instanceof Blob){const e=window.URL.createObjectURL(i),t=document.createElement("a");t.href=e,t.download=s?.fileName||"archivo-descargado",document.body.appendChild(t),t.click(),window.URL.revokeObjectURL(e),document.body.removeChild(t)}else if(i&&"object"==typeof i&&("url"in i||"signed_url"in i||"signedUrl"in i)){const e=i.url||i.signed_url||i.signedUrl;if("string"==typeof e){const t=new URL(e).searchParams.get("response-content-disposition");let o;if(t){const e=decodeURIComponent(t).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);e?.[1]&&(o=e[1].replace(/"/g,""))}const n=s?.fileName||o;if(n){const t=document.createElement("a");t.href=e,t.download=n,document.body.appendChild(t),t.click(),document.body.removeChild(t)}else window.open(e,"_blank")}}}(this.api,o,s,{fileName:t.fileName,method:t.method||"POST"})})}async createSignedUrl(e,t=15,o={}){return wrapStorageResponse(async()=>{const s=o.endpoint||`storage_download_${this.bucketName}`,n={bucket:this.bucketName,file_path:e,expires_minutes:t,download:!1,...o.params},i=await this.api.post(s,n);return{signedUrl:i.signed_url||i.signedUrl||i.url,expiresIn:i.expires_in||i.expiresIn||t,path:e}})}getPublicUrl(e){const t=e.replace(/^\/+/,"");return{data:{publicUrl:`${this.api.baseUrl||""}/storage/v1/render/public/${this.bucketName}/${t}`}}}async list(e="",t={}){return wrapStorageResponse(async()=>{const o=t.endpoint||`storage_list_${this.bucketName}`,s={bucket:this.bucketName,prefix:e||t.prefix||"",limit:t.limit,offset:t.offset,sort_by:t.sortBy,order:t.order,search:t.search,...t.params},n=await this.api.post(o,s);return{data:n.data||n.files||[],total:n.total,hasMore:n.has_more||n.hasMore||!1}})}async remove(e,t={}){return wrapStorageResponse(async()=>{const o=t.endpoint||`storage_delete_${this.bucketName}`,s=Array.isArray(e)?e:[e],n={bucket:this.bucketName,paths:s,...t.params};return await this.api.post(o,n),{success:!0}})}async move(e,t,o={}){return wrapStorageResponse(async()=>{throw new DypaiError("move() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}async copy(e,t,o={}){return wrapStorageResponse(async()=>{throw new DypaiError("copy() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}}async function wrapStorageResponse(e){try{return{data:await e(),error:null}}catch(e){return{data:null,error:e instanceof DypaiError?e:new DypaiError(e.message||"Error de storage",e.status||500)}}}class DypaiClient{constructor(e){const{baseUrl:t,apiKey:o}=e,s=e.auth?.storageKey||e.storageKey;e.global&&function(e){serviceConfig={...serviceConfig,...e}}(e.global),this.auth=new AuthModule({baseUrl:t,apiKey:o,storageKey:s,storage:e.auth?.storage,autoRefreshToken:e.auth?.autoRefreshToken,persistSession:e.auth?.persistSession},null);const n={get:createMethod(i=()=>({token:this.auth.token,apiKey:o,baseUrl:t}),"GET"),post:createMethod(i,"POST"),put:createMethod(i,"PUT"),patch:createMethod(i,"PATCH"),delete:createMethod(i,"DELETE")};var i;this.auth.api=n,this.db=new DataModule(n),this.users=new UsersModule(n),this.storage=new StorageModule(n),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:o,baseUrl:t})),setTokenProvider(()=>this.auth.token),configureApiService({onTokenExpired:async()=>{const e=await this.auth.refreshSession();if(e.error)throw e.error;return e.data?.token||null},onUnauthorized:()=>this.auth.handleSessionExpired()})}from(e){return this.db.from(e)}async me(){return this.auth.getUser()}}let globalConfig={};function applyConfigToAllServices(){try{const{configureApiService:e}=require("../services/ApiService");e(globalConfig)}catch(e){}}exports.DypaiClient=DypaiClient,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Smart Storage (Upload/Download delegado)","Gestión de usuarios (Admin)"]},exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(e){globalConfig={...globalConfig,toast:toast,...e},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(e,t,o){if(!e||!t)throw new Error("createClient() requiere URL y API Key");return new DypaiClient({baseUrl:e,apiKey:t,...o})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(e){customToastFunction=e},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(e,t)=>toast({title:e,description:t,variant:"error"}),exports.toastInfo=(e,t)=>toast({title:e,description:t,variant:"info"}),exports.toastSuccess=(e,t)=>toast({title:e,description:t,variant:"success"}),exports.toastWarning=(e,t)=>toast({title:e,description:t,variant:"warning"}),exports.useToast=function(){const e=customToastFunction||fallbackToast;return{toast:e,toastSuccess:(t,o)=>e({title:t,description:o,variant:"success"}),toastError:(t,o)=>e({title:t,description:o,variant:"error"}),toastWarning:(t,o)=>e({title:t,description:o,variant:"warning"}),toastInfo:(t,o)=>e({title:t,description:o,variant:"info"})}};
|
|
1
|
+
"use strict";class DypaiError extends Error{constructor(t,e=500,o,s){super(t),this.status=e,this.code=o,this.details=s,this.name="DypaiError"}}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.o=null,this.i=null,this.l=null,this.h=[],this.u=null,this.p=null,this.D=0,this.S=0,this.storage=t.storage||localStorageAdapter;const o=t.storageKey||this.deriveStorageKey(t.apiKey);console.log(`[DYPAI SDK] 🛠️ Inicializando AuthModule (apiKey: ${t.apiKey?.substring(0,8)}..., storageKey: ${o})`),this.STORAGE_KEY=`dypai-${o}-auth-session`,this.A=this.m().then(()=>{this.o&&(this.getUser().catch(()=>{}),this.startAutoRefresh())}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.v.bind(this)),window.addEventListener("focus",this.v.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(console.log("[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.m(!1))}))}async v(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(console.log("[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.m(!0),this.o&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.t}get token(){return this.o}onAuthStateChange(t){return console.log("[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(t),this.A.then(()=>{const e=this.k(),o=e?"SIGNED_IN":"INITIAL_SESSION";console.log(`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${o} (Sesión activa: ${!!e})`),t(o,e)}).catch(e=>{console.error("[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.h=this.h.filter(e=>e!==t)}}}}}async signInWithPassword(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,o={email:t.email||t.identifier||"",password:t.password},s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!s.ok){const t=await s.json();throw new DypaiError(t.msg||t.error_description||"Login failed",s.status)}const n=await s.json(),i={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.T(n)};return await this.P(i),i})())}async login(t){return this.signInWithPassword(t)}async signUp(t){return wrapAuthResponse((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,o={email:t.email,password:t.password,data:t.user_data||{}},s=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!s.ok){const t=await s.json();throw new DypaiError(t.msg||t.error_description||"Registration failed",s.status)}const n=await s.json(),i={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.T(n)};return i.token&&await this.P(i),i})())}async register(t){return this.signUp(t)}async getSession(){try{if(!this.o||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.i&&(console.log("[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>console.warn("Error refreshing session in getSession:",t))),{data:{access_token:this.o,refresh_token:this.i||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()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const t=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,e=await fetch(t,{headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const o=await e.json(),s=this.T(o);return this.I(s),s})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{const{redirectTo:o=window.location.href}=e,s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/authorize?provider=${t}&redirect_to=${encodeURIComponent(o)}`;window.location.href=s})())}async signOut(){return wrapAuthResponse((async()=>{try{if(this.o){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this._("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:t})});if(!o.ok)throw new DypaiError("Recovery failed",o.status);return await o.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async refreshSession(){if(this.u)return console.log("[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.u.promise;const t=new Deferred;this.u=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const o=await async function(t,e,o){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 o()))}catch(s){if("AbortError"===s.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await o();throw s}}return await o()}(e,AuthModule.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.$());t.resolve(o)}catch(e){const o=e instanceof DypaiError?e:new DypaiError(e.message||"Error refrescando sesión",401);t.resolve({data:null,error:o})}finally{this.u=null}})(),t.promise}async $(){try{const t=await this.storage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t),o=e.expires_at||0,s=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&o-s>60)return console.log("[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.o=e.access_token,this.i=e.refresh_token,this.l=e.expires_at,this.t=e.user,this.D=0,this.R("TOKEN_REFRESHED"),{data:{token:this.o,refreshToken:this.i||void 0,expiresAt:this.l||void 0,user:this.t},error:null}}if(!this.i)throw new DypaiError("No hay refresh token disponible",401);console.log("[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,o=new AbortController,s=setTimeout(()=>o.abort(),AuthModule.REFRESH_TIMEOUT_MS);let n;try{n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.i}),signal:o.signal})}finally{clearTimeout(s)}if(!n.ok){let t={};try{t=await n.json()}catch{}const e=n.status,o=t.error||t.code||"",s=(t.error_description||t.msg||"").toLowerCase(),i="invalid_grant"===o||s.includes("invalid refresh token")||s.includes("revoked");throw e>=400&&e<500&&i?(console.error("[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this._(`refreshSession failed (${e}: ${o})`)):console.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 i=await n.json(),r={token:i.access_token,refreshToken:i.refresh_token,expiresIn:i.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(i.expires_in||3600),user:this.T(i)};return this.D=0,await this.P(r),console.log(`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${r.expiresAt} (en ${r.expiresIn}s). Refresh token actualizado: ${!!r.refreshToken}`),{data:r,error:null}}catch(t){this.D++,this.S=Date.now();const e=t instanceof DOMException&&"AbortError"===t.name,o=e?new DypaiError("Refresh token timeout (servidor no responde)",408):t instanceof DypaiError?t:new DypaiError(t.message||"Error refrescando sesión",401),s=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.D),AuthModule.MAX_RETRY_MS);return e?console.error(`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${s}ms (intento ${this.D})`):console.error("[DYPAI SDK] ❌ Refresh falló:",o.message,`(status: ${o.status}). Backoff: ${s}ms (intento ${this.D})`),this.D>=AuthModule.MAX_REFRESH_FAILURES&&(console.error(`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this._(`${this.D} consecutive refresh failures`),this.D=0),{data:null,error:o}}}async signInWithOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"OTP request failed",o.status)}return await o.json()})())}async verifyOtp(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"OTP verification failed",o.status)}const s=await o.json(),n={token:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(s.expires_in||3600),user:this.T(s)};return n.token&&await this.P(n),n})())}async updateUser(t){return wrapAuthResponse((async()=>{if(!this.o)throw new DypaiError("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,o=await fetch(e,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.o}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(t)});if(!o.ok){const t=await o.json();throw new DypaiError(t.detail||"Update user failed",o.status)}const s=await o.json(),n=this.T(s);return this.I(n),n})())}startAutoRefresh(){this.stopAutoRefresh(),console.log("[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.C(),this.p=setInterval(()=>this.C(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(console.log("[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async C(){try{const t=Math.floor(Date.now()/1e3),e=this.l||0;if(!this.i||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.D>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.D),AuthModule.MAX_RETRY_MS);if(Date.now()-this.S<t)return}console.log(`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){console.error("[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}T(t){if(!t)return console.warn("[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const e=t.user||t,o=e.app_metadata||{},s=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:o.role||e.role,created_at:e.created_at,updated_at:e.updated_at,confirmed_at:e.confirmed_at,last_sign_in_at:e.last_sign_in_at,app_metadata:o,user_metadata:s,K:{name:o.role||"authenticated",weight:0},M:{app_id:"default"}}}async P(t){t.token&&(this.o=t.token,this.i=t.refreshToken||null,this.l=t.expiresAt||null,await this.I(t.user,t.token,t.refreshToken,t.expiresAt),console.log(`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`),this.startAutoRefresh())}async I(t,e,o,s){this.t=t,e&&(this.o=e),void 0!==o&&(this.i=o||null),void 0!==s&&(this.l=s||null);try{if(this.o&&this.t){const t={access_token:this.o,refresh_token:this.i,expires_at:this.l,user:this.t};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else console.warn("[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.",{hasToken:!!this.o,hasUser:!!this.t})}catch(t){console.error("[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const n=e?"SIGNED_IN":"USER_UPDATED";this.R(n)}async _(t="unknown"){console.log(`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.o=null,this.i=null,this.t=null,this.l=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),console.log("[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){console.error("[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.R("SIGNED_OUT")}async m(t=!0){console.log("[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){console.log("[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");const t=JSON.parse(e);this.o=t.access_token,this.i=t.refresh_token,this.l=t.expires_at,this.t=t.user}else{console.log("[DYPAI SDK] ℹ️ No hay sesión consolidada. Buscando llaves antiguas para migración...");const t=this.STORAGE_KEY.replace("dypai-","").replace("-auth-session",""),e=await this.storage.getItem(`dypai-auth-token-${t}`),o=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!o)return void console.log("[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{console.log("[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");const s=JSON.parse(o);this.o=e,this.t=s;const n=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),i=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.i=n,this.l=i?parseInt(i,10):null,await this.I(s,this.o||void 0,this.i||void 0,this.l||void 0);const r=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of r)try{await this.storage.removeItem(t)}catch(t){}console.log("[DYPAI SDK] ✨ Migración completada con éxito.")}}const o=Math.floor(Date.now()/1e3),s=this.l&&this.l<=o;if(console.log(`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${o}. Diferencia: ${(this.l||0)-o}s`),s&&this.i&&t){console.log("[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void console.error("[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&console.log("[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.R("SIGNED_IN")}catch(t){console.error("[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}k(){return this.o&&this.t?{access_token:this.o,refresh_token:this.i||void 0,expires_at:this.l||void 0,token_type:"bearer",user:this.t}:null}R(t="USER_UPDATED"){const e=this.k();console.log(`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(o=>o(t,e))}handleSessionExpired(){this._("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;class DataModule{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}`)}}async function wrapResponse(t){try{return{data:await t,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error desconocido",t.status||500)}}}class UsersModule{constructor(t){this.api=t}async list(t={}){return wrapResponse(this.api.get("admin/users",{params:t}))}async create(t){return wrapResponse(this.api.post("admin/users",{body:t}))}async update(t,e){return wrapResponse(this.api.put(`admin/users/${t}`,{body:e}))}async delete(t){return wrapResponse(this.api.delete(`admin/users/${t}`))}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:o,variant:s="default"}=t,n=`${"error"===s?"❌":"success"===s?"✅":"warning"===s?"⚠️":"info"===s?"ℹ️":"📢"} ${e}${o?`: ${o}`:""}`;"error"===s?console.error(n):"warning"===s?console.warn(n):console.log(n)},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,o,s,n,i,r,a){const c=getCompleteConfig(),l=a||null;if(!l&&!o.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");if("string"!=typeof o||!o.trim())throw new Error("Endpoint debe ser un string válido");let h;if(o.startsWith("http"))h=o;else{const t=l.replace(/\/+$/,"");h=o.startsWith("/")?t+o:t+"/api/v0/"+o}if(i&&Object.keys(i).length>0){const t=new URLSearchParams,e=(o,s)=>{null!=s&&(Array.isArray(s)?s.forEach((t,s)=>e(`${o}[${s}]`,t)):"object"==typeof s?Object.entries(s).forEach(([t,s])=>e(`${o}[${t}]`,s)):t.append(o,String(s)))};Object.entries(i).forEach(([t,o])=>e(t,o));const o=t.toString();o&&(h+=`?${o}`)}const u="GET"===e?`${e}:${h}:${JSON.stringify(s)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const d=s instanceof FormData,p={...c.headers||{},...d?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...r&&{"x-api-key":r}},f={method:e,headers:p,credentials:"include"};s&&"GET"!==e&&"DELETE"!==e&&(f.body=d?s:JSON.stringify(s));const y=c.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,l=(void 0!==n?n:!1!==c.showToasts)&&t;try{const u=await y(h,f);if(!u.ok){let h,d="Error en la petición";try{const t=await u.text();try{const e=JSON.parse(t);h=e,d=e.message||e.msg||e.error_description||e.error||d}catch{t.length<200&&(d=t)}}catch{}if(401===u.status&&c.onTokenExpired){console.log("[DYPAI SDK] 🔄 Token caducado detectado. Intentando refresco atómico...");try{const t=await c.onTokenExpired();if(t)return console.log("[DYPAI SDK] ✅ Token refrescado con éxito. Reintentando petición..."),callApi(t,e,o,s,n,i,r,a)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}}throw l&&t({title:"Error",description:d,variant:"error"}),new DypaiError(d,u.status,void 0,h)}!l||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const d=u.headers.get("content-type")||"";return d.includes("application/pdf")||d.includes("image/")||d.includes("audio/")||d.includes("video/")||d.includes("application/octet-stream")||d.includes("application/zip")||d.includes("application/vnd.openxmlformats-officedocument")?await u.blob():d.includes("application/json")?await u.json():await u.text()}finally{u&&pendingRequests.delete(u)}})();return u&&pendingRequests.set(u,w),w}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 createMethod(t,e){return async(o,s,n)=>{const i=t();let r,a={};s&&"object"==typeof s&&("token"in s||"params"in s||"apiKey"in s)?(a=s,r=a.body):(r=s,a=n||{});const c=a.token||i.token||(globalTokenProvider?globalTokenProvider():"")||"",l=a.apiKey||i.apiKey;return callApi(c,e,o,r,a.showToasts,a.params,l,i.baseUrl)}}function createApiClient(t){const e=()=>"function"==typeof t?t():t;return{get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE")}}function createMethodFromCtx(t,e){return async(o,s,n)=>{const i=t(),r=await async function(t,e){let o,s={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(s=t,o=s.body):(o=t,s=e||{});let n=s.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:s.apiKey,body:o,params:s.params,showToasts:s.showToasts}}(s,n),a=r.token||i.token||"",c=r.apiKey||i.apiKey;return callApi(a,e,o,r.body,r.showToasts,r.params,c,i.baseUrl)}}class StorageModule{constructor(t){this.api=t}from(t){return new StorageBucketBuilder(t,this.api)}}class StorageBucketBuilder{constructor(t,e){this.bucketName=t,this.api=e}async upload(t,e,o={}){return wrapStorageResponse(async()=>{const s=o.uploadEndpoint||`storage_upload_${this.bucketName}`,n={file_path:t,client_upload:o.clientUpload??!1,...o.params};if(o.clientUpload)return await async function(t,e,o,s){const n=await t.post(e,{file_path:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!1,client_upload:!0,...s?.params||{}});console.log("[SmartUpload] prepareData:",n);const i=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 o=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of o)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(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 normalizeUploadPayload(t)}return null}(n);if(console.log("[SmartUpload] preparedPayload:",i),!i?.upload_url)throw new DypaiError("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:r,method:a="PUT",headers:c={},file_path:l,storage_path:h}=i;s?.onProgress&&s.onProgress(10),console.log("[SmartUpload] uploadRequest:",{upload_url:r,method:a,headers:c,file_path:l});const u=await fetch(r,{method:a,headers:{"Content-Type":o.type||"application/octet-stream",...c},body:o});if(console.log("[SmartUpload] uploadResponse:",{ok:u.ok,status:u.status}),!u.ok)throw new DypaiError("Error en la subida directa a la nube",u.status);s?.onProgress&&s.onProgress(90);const d=s?.confirmEndpoint||e,p=await t.post(d,{...s?.params,bucket:i.bucket||s?.params?.bucket,file_path:l,storage_path:h,filename:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!0,client_upload:!0});return console.log("[SmartUpload] confirmResult:",p),s?.onProgress&&s.onProgress(100),p}(this.api,s,e,{confirmEndpoint:o.confirmEndpoint,params:n,onProgress:o.onProgress});const i=o.contentType||e.type||"application/octet-stream",r=await async function(t){return new Promise((e,o)=>{const s=new FileReader;s.onload=()=>e(String(s.result)),s.onerror=()=>o(s.error),s.readAsDataURL(t)})}(e);return await this.api.post(s,{...n,content:r,content_type:i,size_bytes:e.size,confirm:!0})})}async download(t,e={}){return wrapStorageResponse(async()=>{const o=e.downloadEndpoint||`storage_download_${this.bucketName}`,s={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,o,s){const n=s?.method,i=await("GET"===n?t.get(e,{params:s?.params}):t.post(e,o,{params:s?.params}));if(i instanceof Blob){const t=window.URL.createObjectURL(i),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(i&&"object"==typeof i&&("url"in i||"signed_url"in i||"signedUrl"in i)){const t=i.url||i.signed_url||i.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let o;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(o=t[1].replace(/"/g,""))}const n=s?.fileName||o;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")}}}(this.api,o,s,{fileName:e.fileName,method:e.method||"POST"})})}async createSignedUrl(t,e=15,o={}){return wrapStorageResponse(async()=>{const s=o.endpoint||`storage_download_${this.bucketName}`,n={bucket:this.bucketName,file_path:t,expires_minutes:e,download:!1,...o.params},i=await this.api.post(s,n);return{signedUrl:i.signed_url||i.signedUrl||i.url,expiresIn:i.expires_in||i.expiresIn||e,path:t}})}getPublicUrl(t){const e=t.replace(/^\/+/,"");return{data:{publicUrl:`${this.api.baseUrl||""}/storage/v1/render/public/${this.bucketName}/${e}`}}}async list(t="",e={}){return wrapStorageResponse(async()=>{const o=e.endpoint||`storage_list_${this.bucketName}`,s={bucket:this.bucketName,prefix:t||e.prefix||"",limit:e.limit,offset:e.offset,sort_by:e.sortBy,order:e.order,search:e.search,...e.params},n=await this.api.post(o,s);return{data:n.data||n.files||[],total:n.total,hasMore:n.has_more||n.hasMore||!1}})}async remove(t,e={}){return wrapStorageResponse(async()=>{const o=e.endpoint||`storage_delete_${this.bucketName}`,s=Array.isArray(t)?t:[t],n={bucket:this.bucketName,paths:s,...e.params};return await this.api.post(o,n),{success:!0}})}async move(t,e,o={}){return wrapStorageResponse(async()=>{throw new DypaiError("move() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}async copy(t,e,o={}){return wrapStorageResponse(async()=>{throw new DypaiError("copy() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}}async function wrapStorageResponse(t){try{return{data:await t(),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t.message||"Error de storage",t.status||500)}}}class DypaiClient{constructor(t){const{baseUrl:e,apiKey:o}=t,s=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:o,storageKey:s,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const n={get:createMethod(i=()=>({token:this.auth.token,apiKey:o,baseUrl:e}),"GET"),post:createMethod(i,"POST"),put:createMethod(i,"PUT"),patch:createMethod(i,"PATCH"),delete:createMethod(i,"DELETE")};var i;this.auth.api=n,this.db=new DataModule(n),this.users=new UsersModule(n),this.storage=new StorageModule(n),this.api=createApiClient(()=>({token:this.auth.token||"",apiKey:o,baseUrl:e})),setTokenProvider(()=>this.auth.token),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.DypaiClient=DypaiClient,exports.PACKAGE_INFO={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Smart Storage (Upload/Download delegado)","Gestión de usuarios (Admin)"]},exports.callApi=callApi,exports.configureApiService=configureApiService,exports.configureDypaiServices=function(t){globalConfig={...globalConfig,toast:toast,...t},applyConfigToAllServices()},exports.createApiClient=createApiClient,exports.createClient=function(t,e,o){if(!t||!e)throw new Error("createClient() requiere URL y API Key");return new DypaiClient({baseUrl:t,apiKey:e,...o})},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,o)=>t({title:e,description:o,variant:"success"}),toastError:(e,o)=>t({title:e,description:o,variant:"error"}),toastWarning:(e,o)=>t({title:e,description:o,variant:"warning"}),toastInfo:(e,o)=>t({title:e,description:o,variant:"info"})}};
|