@dypai-ai/client-sdk 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -542,50 +542,62 @@ declare class UsersModule {
542
542
  }
543
543
 
544
544
  /**
545
- * Tipos para el módulo de Storage
545
+ * Types for the Storage module
546
+ *
547
+ * All operations go through engine workflow endpoints (/api/v0/{endpoint}).
548
+ * The underlying storage service is internal and never exposed directly.
546
549
  */
547
550
  interface UploadOptions {
548
- path?: string;
551
+ /** Progress callback (0-100). Only works with clientUpload: true */
549
552
  onProgress?: (percent: number) => void;
553
+ /** Custom params passed to the workflow */
550
554
  params?: Record<string, any>;
555
+ /** Custom workflow endpoint name (default: storage_upload_{bucket}) */
551
556
  uploadEndpoint?: string;
557
+ /** Custom confirm endpoint (default: same as uploadEndpoint) */
552
558
  confirmEndpoint?: string;
559
+ /** Override MIME type (default: file.type or application/octet-stream) */
553
560
  contentType?: string;
554
- cacheControl?: string;
555
- metadata?: Record<string, string>;
561
+ /** Use presigned URL upload (3-step: prepare → upload to cloud → confirm). Recommended for large files */
556
562
  clientUpload?: boolean;
557
563
  }
558
564
  interface DownloadOptions {
565
+ /** Filename for the downloaded file */
559
566
  fileName?: string;
560
- method?: 'GET' | 'POST';
567
+ /** Custom params passed to the workflow */
561
568
  params?: Record<string, any>;
569
+ /** Custom workflow endpoint name (default: storage_download_{bucket}) */
562
570
  downloadEndpoint?: string;
563
571
  }
564
572
  interface SignedUrlOptions {
573
+ /** Expiration in minutes (5-120, default: 15) */
565
574
  expiresIn?: number;
575
+ /** Custom params passed to the workflow */
566
576
  params?: Record<string, any>;
577
+ /** Custom workflow endpoint name (default: storage_download_{bucket}) */
567
578
  endpoint?: string;
568
579
  }
569
580
  interface ListOptions {
581
+ /** Filter by folder/prefix */
570
582
  prefix?: string;
571
- limit?: number;
572
- offset?: number;
573
- sortBy?: 'name' | 'created_at' | 'updated_at' | 'last_accessed_at';
574
- order?: 'asc' | 'desc';
575
- search?: string;
583
+ /** Page number (1-indexed, default: 1) */
584
+ page?: number;
585
+ /** Results per page (1-100, default: 20) */
586
+ perPage?: number;
587
+ /** Custom params passed to the workflow */
576
588
  params?: Record<string, any>;
589
+ /** Custom workflow endpoint name (default: storage_list_{bucket}) */
577
590
  endpoint?: string;
578
591
  }
579
592
  interface UploadResponse {
580
- files: Array<{
581
- file_name: string;
582
- file_path: string;
583
- size_bytes: number;
584
- content_type: string;
585
- public_url?: string;
586
- id?: string;
587
- }>;
588
- bucket: string;
593
+ status: string;
594
+ file_id?: string;
595
+ filename: string;
596
+ file_path: string;
597
+ storage_path?: string;
598
+ size?: number;
599
+ mime_type?: string;
600
+ entity_id?: string;
589
601
  }
590
602
  interface SignedUrlResponse {
591
603
  signedUrl: string;
@@ -595,159 +607,176 @@ interface SignedUrlResponse {
595
607
  interface StorageObject {
596
608
  id: string;
597
609
  name: string;
598
- path: string;
599
610
  size: number;
600
611
  mime_type?: string;
601
612
  created_at: string;
602
613
  updated_at: string;
603
- last_accessed_at?: string;
604
614
  metadata?: Record<string, any>;
605
615
  }
606
616
  interface ListResponse {
607
617
  data: StorageObject[];
608
- total?: number;
609
618
  hasMore?: boolean;
610
619
  }
611
620
 
612
621
  /**
613
- * Módulo de Storage (Almacenamiento)
622
+ * Storage Module
614
623
  *
615
- * Proporciona una API tipo Supabase para gestionar archivos.
616
- * Internamente usa Smart Storage con workflows del Engine.
624
+ * Provides a Supabase-like API for file management.
625
+ * All operations go through engine workflow endpoints.
617
626
  */
618
627
  declare class StorageModule {
619
628
  private api;
620
629
  constructor(api: ApiClient);
621
630
  /**
622
- * Selecciona un bucket para operaciones
623
- * @param bucketName Nombre del bucket
624
- * @returns Builder para operaciones en el bucket
631
+ * Select a bucket for operations
632
+ * @param bucketName Bucket name
633
+ * @returns Builder for operations on the bucket
625
634
  *
626
635
  * @example
627
636
  * ```typescript
628
637
  * const { data, error } = await dypai.storage
629
- * .from('imagenes')
638
+ * .from('images')
630
639
  * .upload('avatar.jpg', file);
631
640
  * ```
632
641
  */
633
642
  from(bucketName: string): StorageBucketBuilder;
634
643
  }
635
644
  /**
636
- * Builder para operaciones en un bucket específico
645
+ * Builder for operations on a specific bucket
637
646
  */
638
647
  declare class StorageBucketBuilder {
639
648
  private bucketName;
640
649
  private api;
641
650
  constructor(bucketName: string, api: ApiClient);
642
651
  /**
643
- * Sube un archivo al bucket
652
+ * Upload a file to the bucket
644
653
  *
645
- * @param path Path donde guardar el archivo (ej: 'carpeta/archivo.jpg')
646
- * @param file Archivo a subir
647
- * @param options Opciones adicionales
654
+ * @param path Path where the file will be stored (e.g. 'folder/file.jpg')
655
+ * @param file File to upload
656
+ * @param options Additional options
648
657
  *
649
658
  * @example
650
659
  * ```typescript
651
- * const file = document.getElementById('input').files[0];
660
+ * // Server-side upload (small files)
661
+ * const { data, error } = await dypai.storage
662
+ * .from('documents')
663
+ * .upload('invoices/2024/inv-001.pdf', file);
664
+ *
665
+ * // Client-side upload via presigned URL (large files)
652
666
  * const { data, error } = await dypai.storage
653
- * .from('documentos')
654
- * .upload('facturas/2024/inv-001.pdf', file, {
667
+ * .from('documents')
668
+ * .upload('invoices/2024/inv-001.pdf', file, {
669
+ * clientUpload: true,
655
670
  * onProgress: (pct) => console.log(`${pct}%`)
656
671
  * });
657
672
  * ```
658
673
  */
659
674
  upload(path: string, file: File, options?: UploadOptions): Promise<DypaiResponse<UploadResponse>>;
660
675
  /**
661
- * Descarga un archivo del bucket
676
+ * Download a file from the bucket.
677
+ * Gets a signed URL from the engine and triggers a browser download.
662
678
  *
663
- * @param path Path del archivo a descargar
664
- * @param options Opciones adicionales
679
+ * @param path File path to download
680
+ * @param options Additional options
665
681
  *
666
682
  * @example
667
683
  * ```typescript
668
684
  * await dypai.storage
669
- * .from('documentos')
670
- * .download('facturas/2024/inv-001.pdf', {
671
- * fileName: 'factura.pdf'
685
+ * .from('documents')
686
+ * .download('invoices/2024/inv-001.pdf', {
687
+ * fileName: 'invoice.pdf'
672
688
  * });
673
689
  * ```
674
690
  */
675
691
  download(path: string, options?: DownloadOptions): Promise<DypaiResponse<void>>;
676
692
  /**
677
- * Obtiene una URL firmada para acceso temporal.
678
- * Usa el endpoint de descarga (storage.download) para generar la URL.
693
+ * Get a signed URL for temporary access.
694
+ * Uses the download workflow endpoint to generate the URL.
695
+ *
696
+ * @param path File path
697
+ * @param expiresIn Expiration in minutes (5-120, default: 15)
698
+ * @param options Additional options
699
+ *
700
+ * @example
701
+ * ```typescript
702
+ * const { data, error } = await dypai.storage
703
+ * .from('documents')
704
+ * .createSignedUrl('report.pdf', 30);
705
+ *
706
+ * if (data) {
707
+ * console.log('Signed URL:', data.signedUrl);
708
+ * // URL is valid for 30 minutes
709
+ * }
710
+ * ```
679
711
  */
680
712
  createSignedUrl(path: string, expiresIn?: number, options?: Omit<SignedUrlOptions, 'expiresIn'>): Promise<DypaiResponse<SignedUrlResponse>>;
681
713
  /**
682
- * Obtiene la URL pública de un archivo (solo para buckets públicos)
683
- *
684
- * @param path Path del archivo
714
+ * @deprecated The storage service is not publicly accessible. Use createSignedUrl() instead.
685
715
  *
686
716
  * @example
687
717
  * ```typescript
718
+ * // Instead of getPublicUrl(), use:
688
719
  * const { data } = await dypai.storage
689
- * .from('public-images')
690
- * .getPublicUrl('logo.png');
691
- *
692
- * console.log('URL pública:', data.publicUrl);
720
+ * .from('images')
721
+ * .createSignedUrl('logo.png', 60); // 60 min
693
722
  * ```
694
723
  */
695
- getPublicUrl(path: string): {
724
+ getPublicUrl(_path: string): {
696
725
  data: {
697
726
  publicUrl: string;
698
727
  };
699
728
  };
700
729
  /**
701
- * Lista archivos en el bucket
730
+ * List files in the bucket
702
731
  *
703
- * @param path Path/carpeta a listar (opcional, lista raíz si no se especifica)
704
- * @param options Opciones adicionales
732
+ * @param path Folder/prefix to list (optional, lists root if empty)
733
+ * @param options Pagination options
705
734
  *
706
735
  * @example
707
736
  * ```typescript
708
737
  * const { data, error } = await dypai.storage
709
- * .from('documentos')
710
- * .list('facturas/2024', {
711
- * limit: 20,
712
- * sortBy: 'created_at',
713
- * order: 'desc'
714
- * });
738
+ * .from('documents')
739
+ * .list('invoices/2024', { page: 1, perPage: 20 });
715
740
  *
716
741
  * if (data) {
717
742
  * data.data.forEach(file => {
718
743
  * console.log(file.name, file.size);
719
744
  * });
745
+ * if (data.hasMore) {
746
+ * // Fetch next page
747
+ * }
720
748
  * }
721
749
  * ```
722
750
  */
723
751
  list(path?: string, options?: ListOptions): Promise<DypaiResponse<ListResponse>>;
724
752
  /**
725
- * Elimina un archivo del bucket
753
+ * Delete a file from the bucket
726
754
  *
727
- * @param path Path del archivo a eliminar
728
- * @param options Opciones adicionales
755
+ * @param path File path or file ID to delete
756
+ * @param options Additional options
729
757
  *
730
758
  * @example
731
759
  * ```typescript
760
+ * // Delete by path
732
761
  * const { error } = await dypai.storage
733
- * .from('documentos')
734
- * .remove('facturas/2024/inv-001.pdf');
762
+ * .from('documents')
763
+ * .remove('invoices/2024/inv-001.pdf');
735
764
  *
736
- * if (!error) {
737
- * console.log('Archivo eliminado');
738
- * }
765
+ * // Delete by file ID
766
+ * const { error } = await dypai.storage
767
+ * .from('documents')
768
+ * .remove('550e8400-e29b-41d4-a716-446655440000');
739
769
  * ```
740
770
  */
741
- remove(path: string | string[], options?: {
771
+ remove(path: string, options?: {
742
772
  endpoint?: string;
743
773
  params?: Record<string, any>;
744
774
  }): Promise<DypaiResponse<{
745
775
  success: boolean;
746
776
  }>>;
747
777
  /**
748
- * Mueve/renombra un archivo.
749
- * Nota: en el engine actual no existe un nodo nativo para move/copy.
750
- * Crea un endpoint personalizado si lo necesitas.
778
+ * Move/rename a file.
779
+ * Not supported by the built-in storage node. Create a custom workflow endpoint if needed.
751
780
  */
752
781
  move(_fromPath: string, _toPath: string, _options?: {
753
782
  endpoint?: string;
@@ -756,9 +785,8 @@ declare class StorageBucketBuilder {
756
785
  success: boolean;
757
786
  }>>;
758
787
  /**
759
- * Copia un archivo.
760
- * Nota: en el engine actual no existe un nodo nativo para move/copy.
761
- * Crea un endpoint personalizado si lo necesitas.
788
+ * Copy a file.
789
+ * Not supported by the built-in storage node. Create a custom workflow endpoint if needed.
762
790
  */
763
791
  copy(_fromPath: string, _toPath: string, _options?: {
764
792
  endpoint?: string;
package/dist/index.esm.js CHANGED
@@ -1 +1 @@
1
- class t extends Error{constructor(t,e=500,i,n){super(t),this.status=e,this.code=i,this.details=n,this.name="DypaiError"}}let e=!1;function i(t,...i){"error"===t?console.error(...i):e&&("warn"===t?console.warn(...i):console.log(...i))}async function n(e,i){let n={};try{n=await e.json()}catch{}const s=n.msg||n.error_description||n.message||i,r=n.error_code||n.error||n.code||void 0;return new t(s,e.status,r)}class s{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const r={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 o(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 a{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.D=0,this.storage=t.storage||r,e=!!t.debug;const s=t.storageKey||this.deriveStorageKey(t.apiKey);i("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (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&&(i("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.I(!1))}))}async v(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(i("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}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return i("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";i("log",`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${n} (Sesión activa: ${!!e})`),t(n,e)}).catch(e=>{i("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(t){return o((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,i={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(i)});if(!s.ok)throw await n(s,"Login failed");const r=await s.json(),o={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this.P(r)};return await this._(o),o})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return o((async()=>{const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:r,phone:o,password:a,user_data:c,...h}=t,l={email:r,phone:o,password:a,data:{...h,...c}};e?.redirectTo&&(l.redirect_to=e.redirectTo);const u=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await n(u,"Registration failed");const d=await u.json(),p=!d.access_token,f={token:d.access_token,refreshToken:d.refresh_token,expiresIn:d.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(d.expires_in||3600),user:this.P(d),confirmationRequired:p};return f.token?await this._(f):i("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),f})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.m,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(i("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>i("warn","[DYPAI SDK] 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 o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,i=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!i.ok)throw new t("Session invalid",i.status);const n=await i.json(),s=this.P(n);return this.k(s),s})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return o((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:n=window.location.href,scopes:s}=i,r=this.config.baseUrl||"http://localhost:8000",o=s?.length?`&scopes=${encodeURIComponent(s.join(" "))}`:"",a=`${r}/auth/v1/authorize?provider=${e}${o}&redirect_to=${encodeURIComponent(n)}`;window.location.href=a})())}async signOut(){return o((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.$("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return o((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo&&(s.redirect_to=e.redirectTo);const r=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!r.ok)throw await n(r,"Recovery failed");return await r.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return o((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!i.ok)throw await n(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.u)return i("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.u.promise;const e=new s;this.u=e;const n=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const n=new AbortController,s=setTimeout(()=>n.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:n.signal},async()=>(clearTimeout(s),await i()))}catch(n){if("AbortError"===n.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw n}}return await i()}(n,a.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.T());e.resolve(t)}catch(i){const n=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:n})}finally{this.u=null}})(),e.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 i("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);i("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,r=setTimeout(()=>s.abort(),a.REFRESH_TIMEOUT_MS);let o;try{o=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(r)}if(!o.ok){let e={};try{e=await o.json()}catch{}const n=o.status,s=e.error||e.code||"",r=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===s||r.includes("invalid refresh token")||r.includes("revoked");throw n>=400&&n<500&&a?(i("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.$(`refreshSession failed (${n}: ${s})`)):i("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 c=await o.json(),h={token:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(c.expires_in||3600),user:this.P(c)};return this.S=0,await this._(h),i("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${h.expiresAt} (en ${h.expiresIn}s). Refresh token actualizado: ${!!h.refreshToken}`),{data:h,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),r=Math.min(a.RETRY_BASE_MS*Math.pow(2,this.S),a.MAX_RETRY_MS);return n?i("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${a.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.S})`):i("error","[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${r}ms (intento ${this.S})`),this.S>=a.MAX_REFRESH_FAILURES&&(i("error",`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.$(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:s}}}async signInWithOtp(e){return o((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP request failed",n.status)}return await n.json()})())}async verifyOtp(e){return o((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP verification failed",n.status)}const s=await n.json(),r={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 r.token&&await this._(r),r})())}async updateUser(e){return o((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(i,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"Update user failed",n.status)}const s=await n.json(),r=this.P(s);return this.k(r),r})())}startAutoRefresh(){this.stopAutoRefresh(),i("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.Y(),this.p=setInterval(()=>this.Y(),a.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(i("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async Y(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>a.AUTO_REFRESH_TICK_THRESHOLD*a.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(a.RETRY_BASE_MS*Math.pow(2,this.S),a.MAX_RETRY_MS);if(Date.now()-this.D<t)return}i("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){i("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}P(t){if(!t)return i("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,O:{name:n.role||"authenticated",weight:0},N:{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),i("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 i("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){i("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const r=e?"SIGNED_IN":"USER_UPDATED";this.K(r)}async $(t="unknown"){i("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),i("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){i("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.K("SIGNED_OUT")}async I(t=!0){i("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.U())return void i("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;i("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.refresh_token?t.refresh_token:null,this.h="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{i("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 i("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let s;i("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{s=JSON.parse(n)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!s||"object"!=typeof s)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.i=e,this.t=s;const r=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),o=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=r,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){}i("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const n=Math.floor(Date.now()/1e3),s=this.h&&this.h<=n;if(i("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${n}. Diferencia: ${(this.h||0)-n}s`),s&&this.o&&t){i("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void i("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&i("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.K("SIGNED_IN")}catch(t){i("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async U(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t),n=e.get("access_token");if(!n)return!1;const s=e.get("refresh_token"),r=e.get("type"),o=parseInt(e.get("expires_in")||"3600",10);i("log",`[DYPAI SDK] 🔗 Callback de GoTrue detectado en URL (type: ${r||"unknown"}). Procesando...`),window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=n,this.o=s||null,this.h=Math.floor(Date.now()/1e3)+o;const a=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${n}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return i("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,!1;const h=await c.json(),l=this.P(h),u={token:n,refreshToken:s||void 0,expiresIn:o,expiresAt:this.h,user:l};return await this._(u),"recovery"===r?(i("log","[DYPAI SDK] 🔑 Password recovery callback. Emitiendo PASSWORD_RECOVERY..."),this.K("PASSWORD_RECOVERY")):(i("log",`[DYPAI SDK] ✅ Sesión establecida desde callback URL (type: ${r||"signup"}). Emitiendo SIGNED_IN...`),this.K("SIGNED_IN")),!0}catch(t){return i("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}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();i("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(i=>i(t,e))}handleSessionExpired(){this.$("handleSessionExpired called (likely 401 from API)")}}a.REFRESH_TIMEOUT_MS=15e3,a.MAX_REFRESH_FAILURES=5,a.AUTO_REFRESH_TICK_DURATION_MS=3e4,a.AUTO_REFRESH_TICK_THRESHOLD=3,a.RETRY_BASE_MS=200,a.MAX_RETRY_MS=3e4,a.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class c{constructor(t){this.api=t}from(t){return new h(t,this.api)}}class h{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class l{constructor(t){this.api=t}async list(t={}){return this.api.get("admin/users",{params:t})}async create(t){return this.api.post("admin/users",{body:t})}async update(t,e){return this.api.put(`admin/users/${t}`,{body:e})}async delete(t){return this.api.delete(`admin/users/${t}`)}}let u=null;function d(t){u=t}const p=t=>{const{title:e,description:i,variant:n="default"}=t,s=`${"error"===n?"❌":"success"===n?"✅":"warning"===n?"⚠️":"info"===n?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===n?console.error(s):"warning"===n&&console.warn(s)};function f(){const t=u||p;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const w=t=>(u||p)(t),y=(t,e)=>w({title:t,description:e,variant:"success"}),g=(t,e)=>w({title:t,description:e,variant:"error"}),S=(t,e)=>w({title:t,description:e,variant:"warning"}),D=(t,e)=>w({title:t,description:e,variant:"info"});let m={},I=null;function v(t){I=t}function A(t){m={...m,...t}}const P=new Map;function _(){let t=m;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...m}}catch(t){}return t}async function k(e,i,n,s,r,o,a,c){const h=_(),l=c||null;if(!l&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let u;if(n.startsWith("http"))u=n;else{const t=l.replace(/\/+$/,"");u=n.startsWith("/")?t+n:t+"/api/v0/"+n}if(o&&Object.keys(o).length>0){const t=new URLSearchParams,e=(i,n)=>{null!=n&&(Array.isArray(n)?n.forEach((t,n)=>e(`${i}[${n}]`,t)):"object"==typeof n?Object.entries(n).forEach(([t,n])=>e(`${i}[${t}]`,n)):t.append(i,String(n)))};Object.entries(o).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const d="GET"===i?`${i}:${u}:${JSON.stringify(s)}`:null;if(d&&P.has(d))return P.get(d);const p=s instanceof FormData,f={...h.headers||{},...p?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},y={method:i,headers:f,credentials:"include"};s&&"GET"!==i&&"DELETE"!==i&&(y.body=p?s:JSON.stringify(s));const g=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!g)throw new Error("Fetch no disponible.");const S=(async()=>{const e=_().toast||w,l=(void 0!==r?r:!1!==h.showToasts)&&e;try{const d=await g(u,y);if(!d.ok){let u,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);u=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status&&h.onTokenExpired)try{const t=await h.onTokenExpired();if(t)return k(t,i,n,s,r,o,a,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"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||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&&P.delete(d)}})();return d&&P.set(d,S),S}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 E(t,e){return async(i,n,s)=>{const r=t();let o,a={};n&&"object"==typeof n&&("token"in n||"params"in n||"apiKey"in n)?(a=n,o=a.body):(o=n,a=s||{});const c=a.token||r.token||(I?I():"")||"",h=a.apiKey||r.apiKey;return k(c,e,i,o,a.showToasts,a.params,h,r.baseUrl)}}function b(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(e,i){return async(n,s,r)=>{const o=e(),a=await async function(t,e){let i,n={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(n=t,i=n.body):(i=t,n=e||{});let s=n.token;return!s&&I&&(s=I()||""),s=s||"",{token:s,apiKey:n.apiKey,body:i,params:n.params,showToasts:n.showToasts}}(s,r),c=a.token||o.token||"",h=a.apiKey||o.apiKey;try{return{data:await k(c,i,n,a.body,a.showToasts,a.params,h,o.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class K{constructor(t){this.api=t}from(t){return new Y(t,this.api)}}class Y{constructor(t,e){this.bucketName=t,this.api=e}async upload(e,i,n={}){return O(async()=>{const s=n.uploadEndpoint||`storage_upload_${this.bucketName}`,r={file_path:e,client_upload:n.clientUpload??!1,...n.params};if(n.clientUpload)return await async function(e,i,n,s){const{data:r,error:o}=await e.post(i,{file_path:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!1,client_upload:!0,...s?.params||{}});if(o)throw o;const a=function(t){if(!t||"object"!=typeof t)return null;const e=t.upload_url||t.uploadUrl||t.url?t:null;if(e?.upload_url)return $(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return $(t);const n=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of n){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}(r);if(!a?.upload_url)throw new t("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;s?.onProgress&&s.onProgress(10);const p=await fetch(c,{method:h,headers:{"Content-Type":n.type||"application/octet-stream",...l},body:n});if(!p.ok)throw new t("Error en la subida directa a la nube",p.status);s?.onProgress&&s.onProgress(90);const f=s?.confirmEndpoint||i,{data:w,error:y}=await e.post(f,{...s?.params,bucket:a.bucket||s?.params?.bucket,file_path:u,storage_path:d,filename:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!0,client_upload:!0});if(y)throw y;return s?.onProgress&&s.onProgress(100),w}(this.api,s,i,{confirmEndpoint:n.confirmEndpoint,params:r,onProgress:n.onProgress});const o=n.contentType||i.type||"application/octet-stream",a=await async function(t){return new Promise((e,i)=>{const n=new FileReader;n.onload=()=>e(String(n.result)),n.onerror=()=>i(n.error),n.readAsDataURL(t)})}(i),{data:c,error:h}=await this.api.post(s,{...r,content:a,content_type:o,size_bytes:i.size,confirm:!0});if(h)throw h;return c})}async download(t,e={}){return O(async()=>{const i=e.downloadEndpoint||`storage_download_${this.bucketName}`,n={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,i,n){const s=n?.method,{data:r,error:o}=await("GET"===s?t.get(e,{params:n?.params}):t.post(e,i,{params:n?.params}));if(o)throw o;if(r instanceof Blob){const t=window.URL.createObjectURL(r),e=document.createElement("a");e.href=t,e.download=n?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(r&&"object"==typeof r&&("url"in r||"signed_url"in r||"signedUrl"in r)){const t=r.url||r.signed_url||r.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=n?.fileName||i;if(s){const e=document.createElement("a");e.href=t,e.download=s,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}(this.api,i,n,{fileName:e.fileName,method:e.method||"POST"})})}async createSignedUrl(t,e=15,i={}){return O(async()=>{const n=i.endpoint||`storage_download_${this.bucketName}`,s={bucket:this.bucketName,file_path:t,expires_minutes:e,download:!1,...i.params},{data:r,error:o}=await this.api.post(n,s);if(o)throw o;return{signedUrl:r?.signed_url||r?.signedUrl||r?.url,expiresIn:r?.expires_in||r?.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 O(async()=>{const i=e.endpoint||`storage_list_${this.bucketName}`,n={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},{data:s,error:r}=await this.api.post(i,n);if(r)throw r;return{data:s?.data||s?.files||[],total:s?.total,hasMore:s?.has_more||s?.hasMore||!1}})}async remove(t,e={}){return O(async()=>{const i=e.endpoint||`storage_delete_${this.bucketName}`,n=Array.isArray(t)?t:[t],s={bucket:this.bucketName,paths:n,...e.params},{error:r}=await this.api.post(i,s);if(r)throw r;return{success:!0}})}async move(e,i,n={}){return O(async()=>{throw new t("move() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}async copy(e,i,n={}){return O(async()=>{throw new t("copy() no está soportado por el engine actual. Crea un endpoint personalizado.",400)})}}async function O(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 N{constructor(t){const{baseUrl:e,apiKey:i}=t,n=t.auth?.storageKey||t.storageKey;t.global&&function(t){m={...m,...t}}(t.global),this.auth=new a({baseUrl:e,apiKey:i,storageKey:n,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s={get:E(r=()=>({token:this.auth.token,apiKey:i,baseUrl:e}),"GET"),post:E(r,"POST"),put:E(r,"PUT"),patch:E(r,"PATCH"),delete:E(r,"DELETE")};var r;this.auth.api=s,this.db=new c(s),this.users=new l(s),this.storage=new K(s),this.api=b(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),v(()=>this.auth.token),A({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,i){if(!t||!e)throw new Error("createClient() requiere URL y API Key");return new N({baseUrl:t,apiKey:e,...i})}let R={};function x(t){R={...R,toast:w,...t},L()}function C(){return{...R}}function L(){try{const{configureApiService:t}=require("../services/ApiService");t(R)}catch(t){}}function j(){R={},L()}async function M(){L()}const z={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{N as DypaiClient,z as PACKAGE_INFO,k as callApi,A as configureApiService,x as configureDypaiServices,b as createApiClient,U as createClient,C as getGlobalConfig,M as reloadDypaiConfig,j as resetGlobalConfig,d as setToastFunction,v as setTokenProvider,w as toast,g as toastError,D as toastInfo,y as toastSuccess,S as toastWarning,f as useToast};
1
+ class t extends Error{constructor(t,e=500,i,n){super(t),this.status=e,this.code=i,this.details=n,this.name="DypaiError"}}let e=!1;function i(t,...i){"error"===t?console.error(...i):e&&("warn"===t?console.warn(...i):console.log(...i))}async function n(e,i){let n={};try{n=await e.json()}catch{}const s=n.msg||n.error_description||n.message||i,o=n.error_code||n.error||n.code||void 0;return new t(s,e.status,o)}class s{constructor(){this.promise=new Promise((t,e)=>{this.resolve=t,this.reject=e})}}const o={getItem:t=>"undefined"==typeof window?null:window.localStorage.getItem(t),setItem:(t,e)=>{if("undefined"!=typeof window)try{window.localStorage.setItem(t,e)}catch(t){console.error("[DYPAI SDK] ❌ Error crítico guardando en localStorage (¿Quota/Permisos?):",t)}},removeItem:t=>{if("undefined"!=typeof window)try{window.localStorage.removeItem(t)}catch(t){}}};async function r(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 a{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.D=0,this.storage=t.storage||o,e=!!t.debug;const s=t.storageKey||this.deriveStorageKey(t.apiKey);i("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (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&&(i("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.I(!1))}))}async v(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(i("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}isLoggedIn(){return!(!this.i||!this.t)}onAuthStateChange(t){return i("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";i("log",`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${n} (Sesión activa: ${!!e})`),t(n,e)}).catch(e=>{i("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(t){return r((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,i={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(i)});if(!s.ok)throw await n(s,"Login failed");const o=await s.json(),r={token:o.access_token,refreshToken:o.refresh_token,expiresIn:o.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(o.expires_in||3600),user:this.P(o)};return await this._(r),r})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return r((async()=>{const s=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:o,phone:r,password:a,user_data:c,...h}=t,l={email:o,phone:r,password:a,data:{...h,...c}};e?.redirectTo&&(l.redirect_to=e.redirectTo);const u=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await n(u,"Registration failed");const d=await u.json(),p=!d.access_token,f={token:d.access_token,refreshToken:d.refresh_token,expiresIn:d.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(d.expires_in||3600),user:this.P(d),confirmationRequired:p};return f.token?await this._(f):i("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),f})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.m,!this.i||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.h||0)-t<30&&this.o&&(i("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>i("warn","[DYPAI SDK] 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 r((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,i=await fetch(e,{headers:{Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!i.ok)throw new t("Session invalid",i.status);const n=await i.json(),s=this.P(n);return this.k(s),s})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return r((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:n=window.location.href,scopes:s}=i,o=this.config.baseUrl||"http://localhost:8000",r=s?.length?`&scopes=${encodeURIComponent(s.join(" "))}`:"",a=`${o}/auth/v1/authorize?provider=${e}${r}&redirect_to=${encodeURIComponent(n)}`;window.location.href=a})())}async signOut(){return r((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.$("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return r((async()=>{const i=this.config.baseUrl||"http://localhost:8000",s={email:t};e?.redirectTo&&(s.redirect_to=e.redirectTo);const o=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)});if(!o.ok)throw await n(o,"Recovery failed");return await o.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return r((async()=>{const e=this.config.baseUrl||"http://localhost:8000",i=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!i.ok)throw await n(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.u)return i("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.u.promise;const e=new s;this.u=e;const n=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const n=new AbortController,s=setTimeout(()=>n.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:n.signal},async()=>(clearTimeout(s),await i()))}catch(n){if("AbortError"===n.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await i();throw n}}return await i()}(n,a.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.T());e.resolve(t)}catch(i){const n=i instanceof t?i:new t(i.message||"Error refrescando sesión",401);e.resolve({data:null,error:n})}finally{this.u=null}})(),e.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 i("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);i("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(),a.REFRESH_TIMEOUT_MS);let r;try{r=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(!r.ok){let e={};try{e=await r.json()}catch{}const n=r.status,s=e.error||e.code||"",o=(e.error_description||e.msg||"").toLowerCase(),a="invalid_grant"===s||o.includes("invalid refresh token")||o.includes("revoked");throw n>=400&&n<500&&a?(i("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.$(`refreshSession failed (${n}: ${s})`)):i("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 c=await r.json(),h={token:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(c.expires_in||3600),user:this.P(c)};return this.S=0,await this._(h),i("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${h.expiresAt} (en ${h.expiresIn}s). Refresh token actualizado: ${!!h.refreshToken}`),{data:h,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(a.RETRY_BASE_MS*Math.pow(2,this.S),a.MAX_RETRY_MS);return n?i("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${a.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.S})`):i("error","[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${o}ms (intento ${this.S})`),this.S>=a.MAX_REFRESH_FAILURES&&(i("error",`[DYPAI SDK] 🗑️ ${this.S} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.$(`${this.S} consecutive refresh failures`),this.S=0),{data:null,error:s}}}async signInWithOtp(e){return r((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/otp`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP request failed",n.status)}return await n.json()})())}async verifyOtp(e){return r((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n=await fetch(`${i}/auth/v1/verify`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"OTP verification failed",n.status)}const s=await n.json(),o={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 o.token&&await this._(o),o})())}async updateUser(e){return r((async()=>{if(!this.i)throw new t("No hay sesión activa",401);const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/user`,n=await fetch(i,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.i}`,...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(e)});if(!n.ok){const e=await n.json();throw new t(e.detail||"Update user failed",n.status)}const s=await n.json(),o=this.P(s);return this.k(o),o})())}startAutoRefresh(){this.stopAutoRefresh(),i("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.Y(),this.p=setInterval(()=>this.Y(),a.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(i("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async Y(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>a.AUTO_REFRESH_TICK_THRESHOLD*a.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(a.RETRY_BASE_MS*Math.pow(2,this.S),a.MAX_RETRY_MS);if(Date.now()-this.D<t)return}i("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){i("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}P(t){if(!t)return i("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,O:{name:n.role||"authenticated",weight:0},U:{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),i("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 i("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){i("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const o=e?"SIGNED_IN":"USER_UPDATED";this.K(o)}async $(t="unknown"){i("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),i("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){i("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.K("SIGNED_OUT")}async I(t=!0){i("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.N())return void i("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;i("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.i=t.access_token,this.o="string"==typeof t.refresh_token?t.refresh_token:null,this.h="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{i("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 i("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let s;i("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{s=JSON.parse(n)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!s||"object"!=typeof s)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.i=e,this.t=s;const o=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),r=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.o=o,this.h=r?parseInt(r,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){}i("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const n=Math.floor(Date.now()/1e3),s=this.h&&this.h<=n;if(i("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${n}. Diferencia: ${(this.h||0)-n}s`),s&&this.o&&t){i("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void i("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&i("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.K("SIGNED_IN")}catch(t){i("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async N(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t),n=e.get("access_token");if(!n)return!1;const s=e.get("refresh_token"),o=e.get("type"),r=parseInt(e.get("expires_in")||"3600",10);i("log",`[DYPAI SDK] 🔗 Callback de GoTrue detectado en URL (type: ${o||"unknown"}). Procesando...`),window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.i=n,this.o=s||null,this.h=Math.floor(Date.now()/1e3)+r;const a=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${n}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return i("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.i=null,this.o=null,this.h=null,!1;const h=await c.json(),l=this.P(h),u={token:n,refreshToken:s||void 0,expiresIn:r,expiresAt:this.h,user:l};return await this._(u),"recovery"===o?(i("log","[DYPAI SDK] 🔑 Password recovery callback. Emitiendo PASSWORD_RECOVERY..."),this.K("PASSWORD_RECOVERY")):(i("log",`[DYPAI SDK] ✅ Sesión establecida desde callback URL (type: ${o||"signup"}). Emitiendo SIGNED_IN...`),this.K("SIGNED_IN")),!0}catch(t){return i("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}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();i("log",`[DYPAI SDK] 📢 Notificando a ${this.l.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.l.forEach(i=>i(t,e))}handleSessionExpired(){this.$("handleSessionExpired called (likely 401 from API)")}}a.REFRESH_TIMEOUT_MS=15e3,a.MAX_REFRESH_FAILURES=5,a.AUTO_REFRESH_TICK_DURATION_MS=3e4,a.AUTO_REFRESH_TICK_THRESHOLD=3,a.RETRY_BASE_MS=200,a.MAX_RETRY_MS=3e4,a.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class c{constructor(t){this.api=t}from(t){return new h(t,this.api)}}class h{constructor(t,e){this.table=t,this.api=e}async select(t={}){return this.api.get(this.table,{params:t})}async insert(t){return this.api.post(this.table,t)}async update(t,e){return this.api.patch(`${this.table}/${t}`,e)}async delete(t){return this.api.delete(`${this.table}/${t}`)}}class l{constructor(t){this.api=t}async list(t={}){return this.api.get("admin/users",{params:t})}async create(t){return this.api.post("admin/users",{body:t})}async update(t,e){return this.api.put(`admin/users/${t}`,{body:e})}async delete(t){return this.api.delete(`admin/users/${t}`)}}let u=null;function d(t){u=t}const p=t=>{const{title:e,description:i,variant:n="default"}=t,s=`${"error"===n?"❌":"success"===n?"✅":"warning"===n?"⚠️":"info"===n?"ℹ️":"📢"} ${e}${i?`: ${i}`:""}`;"error"===n?console.error(s):"warning"===n&&console.warn(s)};function f(){const t=u||p;return{toast:t,toastSuccess:(e,i)=>t({title:e,description:i,variant:"success"}),toastError:(e,i)=>t({title:e,description:i,variant:"error"}),toastWarning:(e,i)=>t({title:e,description:i,variant:"warning"}),toastInfo:(e,i)=>t({title:e,description:i,variant:"info"})}}const w=t=>(u||p)(t),y=(t,e)=>w({title:t,description:e,variant:"success"}),g=(t,e)=>w({title:t,description:e,variant:"error"}),S=(t,e)=>w({title:t,description:e,variant:"warning"}),D=(t,e)=>w({title:t,description:e,variant:"info"});let m={},I=null;function v(t){I=t}function A(t){m={...m,...t}}const P=new Map;function _(){let t=m;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...m}}catch(t){}return t}async function k(e,i,n,s,o,r,a,c){const h=_(),l=c||null;if(!l&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url, key).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let u;if(n.startsWith("http"))u=n;else{const t=l.replace(/\/+$/,"");u=n.startsWith("/")?t+n:t+"/api/v0/"+n}if(r&&Object.keys(r).length>0){const t=new URLSearchParams,e=(i,n)=>{null!=n&&(Array.isArray(n)?n.forEach((t,n)=>e(`${i}[${n}]`,t)):"object"==typeof n?Object.entries(n).forEach(([t,n])=>e(`${i}[${t}]`,n)):t.append(i,String(n)))};Object.entries(r).forEach(([t,i])=>e(t,i));const i=t.toString();i&&(u+=`?${i}`)}const d="GET"===i?`${i}:${u}:${JSON.stringify(s)}`:null;if(d&&P.has(d))return P.get(d);const p=s instanceof FormData,f={...h.headers||{},...p?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},y={method:i,headers:f,credentials:"include"};s&&"GET"!==i&&"DELETE"!==i&&(y.body=p?s:JSON.stringify(s));const g=h.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!g)throw new Error("Fetch no disponible.");const S=(async()=>{const e=_().toast||w,l=(void 0!==o?o:!1!==h.showToasts)&&e;try{const d=await g(u,y);if(!d.ok){let u,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);u=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status&&h.onTokenExpired)try{const t=await h.onTokenExpired();if(t)return k(t,i,n,s,o,r,a,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"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||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&&P.delete(d)}})();return d&&P.set(d,S),S}function b(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 $(t,e){return async(i,n,s)=>{const o=t();let r,a={};n&&"object"==typeof n&&("token"in n||"params"in n||"apiKey"in n)?(a=n,r=a.body):(r=n,a=s||{});const c=a.token||o.token||(I?I():"")||"",h=a.apiKey||o.apiKey;return k(c,e,i,r,a.showToasts,a.params,h,o.baseUrl)}}function E(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(e,i){return async(n,s,o)=>{const r=e(),a=await async function(t,e){let i,n={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(n=t,i=n.body):(i=t,n=e||{});let s=n.token;return!s&&I&&(s=I()||""),s=s||"",{token:s,apiKey:n.apiKey,body:i,params:n.params,showToasts:n.showToasts}}(s,o),c=a.token||r.token||"",h=a.apiKey||r.apiKey;try{return{data:await k(c,i,n,a.body,a.showToasts,a.params,h,r.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}class K{constructor(t){this.api=t}from(t){return new Y(t,this.api)}}class Y{constructor(t,e){this.bucketName=t,this.api=e}async upload(e,i,n={}){return O(async()=>{const s=n.uploadEndpoint||`storage_upload_${this.bucketName}`,o={file_path:e,client_upload:n.clientUpload??!1,...n.params};if(n.clientUpload)return await async function(e,i,n,s){const{data:o,error:r}=await e.post(i,{file_path:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!1,client_upload:!0,...s?.params||{}});if(r)throw r;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 b(e);const i=[t.data,t.result,t.output,t.payload].filter(Boolean);for(const t of i)if(t&&(t.upload_url||t.uploadUrl||t.url))return b(t);const n=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of n){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return b(t)}return null}(o);if(!a?.upload_url)throw new t("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;s?.onProgress&&s.onProgress(10);const p=await fetch(c,{method:h,headers:{"Content-Type":n.type||"application/octet-stream",...l},body:n});if(!p.ok)throw new t("Error en la subida directa a la nube",p.status);s?.onProgress&&s.onProgress(90);const f=s?.confirmEndpoint||i,{data:w,error:y}=await e.post(f,{...s?.params,bucket:a.bucket||s?.params?.bucket,file_path:u,storage_path:d,filename:n.name,content_type:n.type||"application/octet-stream",size_bytes:n.size,confirm:!0,client_upload:!0});if(y)throw y;return s?.onProgress&&s.onProgress(100),w}(this.api,s,i,{confirmEndpoint:n.confirmEndpoint,params:o,onProgress:n.onProgress});const r=n.contentType||i.type||"application/octet-stream",a=await async function(t){return new Promise((e,i)=>{const n=new FileReader;n.onload=()=>e(String(n.result)),n.onerror=()=>i(n.error),n.readAsDataURL(t)})}(i),{data:c,error:h}=await this.api.post(s,{...o,content:a,content_type:r,size_bytes:i.size,confirm:!0});if(h)throw h;return c})}async download(t,e={}){return O(async()=>{const i=e.downloadEndpoint||`storage_download_${this.bucketName}`,n={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,i,n){const{data:s,error:o}=await t.post(e,i,{params:n?.params});if(o)throw o;if(s instanceof Blob){const t=window.URL.createObjectURL(s),e=document.createElement("a");e.href=t,e.download=n?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(s&&"object"==typeof s&&("url"in s||"signed_url"in s||"signedUrl"in s)){const t=s.url||s.signed_url||s.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=n?.fileName||i;if(s){const e=document.createElement("a");e.href=t,e.download=s,document.body.appendChild(e),e.click(),document.body.removeChild(e)}else window.open(t,"_blank")}}}(this.api,i,n,{fileName:e.fileName,method:"POST"})})}async createSignedUrl(t,e=15,i={}){return O(async()=>{const n=i.endpoint||`storage_download_${this.bucketName}`,s={bucket:this.bucketName,file_path:t,expires_minutes:e,download:!1,...i.params},{data:o,error:r}=await this.api.post(n,s);if(r)throw r;return{signedUrl:o?.signed_url||o?.signedUrl||o?.url,expiresIn:o?.expires_in||o?.expiresIn||e,path:t}})}getPublicUrl(t){return console.warn("[DYPAI SDK] getPublicUrl() is deprecated. The storage service is not publicly accessible. Use createSignedUrl() instead."),{data:{publicUrl:""}}}async list(t="",e={}){return O(async()=>{const i=e.endpoint||`storage_list_${this.bucketName}`,n={bucket:this.bucketName,prefix:t||e.prefix||"",page:e.page||1,per_page:e.perPage||20,...e.params},{data:s,error:o}=await this.api.post(i,n);if(o)throw o;return{data:s?.data||s?.files||[],hasMore:s?.has_more||s?.hasMore||!1}})}async remove(t,e={}){return O(async()=>{const i=e.endpoint||`storage_delete_${this.bucketName}`,n=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t),s={bucket:this.bucketName,...n?{file_id:t}:{file_path:t},...e.params},{error:o}=await this.api.post(i,s);if(o)throw o;return{success:!0}})}async move(e,i,n={}){return O(async()=>{throw new t("move() is not supported by the built-in storage node. Create a custom workflow endpoint if needed.",400)})}async copy(e,i,n={}){return O(async()=>{throw new t("copy() is not supported by the built-in storage node. Create a custom workflow endpoint if needed.",400)})}}async function O(e){try{return{data:await e(),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Storage operation failed",e.status||500)}}}class U{constructor(t){const{baseUrl:e,apiKey:i}=t,n=t.auth?.storageKey||t.storageKey;t.global&&function(t){m={...m,...t}}(t.global),this.auth=new a({baseUrl:e,apiKey:i,storageKey:n,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s={get:$(o=()=>({token:this.auth.token,apiKey:i,baseUrl:e}),"GET"),post:$(o,"POST"),put:$(o,"PUT"),patch:$(o,"PATCH"),delete:$(o,"DELETE")};var o;this.auth.api=s,this.db=new c(s),this.users=new l(s),this.storage=new K(s),this.api=E(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),v(()=>this.auth.token),A({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 N(t,e,i){if(!t||!e)throw new Error("createClient() requiere URL y API Key");return new U({baseUrl:t,apiKey:e,...i})}let R={};function x(t){R={...R,toast:w,...t},L()}function C(){return{...R}}function L(){try{const{configureApiService:t}=require("../services/ApiService");t(R)}catch(t){}}function j(){R={},L()}async function M(){L()}const z={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{U as DypaiClient,z as PACKAGE_INFO,k as callApi,A as configureApiService,x as configureDypaiServices,E as createApiClient,N as createClient,C as getGlobalConfig,M as reloadDypaiConfig,j as resetGlobalConfig,d as setToastFunction,v as setTokenProvider,w as toast,g as toastError,D as toastInfo,y as toastSuccess,S as toastWarning,f as useToast};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";class DypaiError extends Error{constructor(t,e=500,o,r){super(t),this.status=e,this.code=o,this.details=r,this.name="DypaiError"}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseGoTrueError(t,e){let o={};try{o=await t.json()}catch{}const r=o.msg||o.error_description||o.message||e,i=o.error_code||o.error||o.code||void 0;return new DypaiError(r,t.status,i)}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,_debugEnabled=!!t.debug;const o=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (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._.bind(this)),window.addEventListener("focus",this._.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.m(!1))}))}async _(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("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}isLoggedIn(){return!(!this.o||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(t),this.A.then(()=>{const e=this.v(),o=e?"SIGNED_IN":"INITIAL_SESSION";_log("log",`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${o} (Sesión activa: ${!!e})`),t(o,e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.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},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!r.ok)throw await parseGoTrueError(r,"Login failed");const i=await r.json(),n={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(i)};return await this.P(n),n})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const o=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:r,phone:i,password:n,user_data:s,...a}=t,c={email:r,phone:i,password:n,data:{...a,...s}};e?.redirectTo&&(c.redirect_to=e.redirectTo);const l=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(c)});if(!l.ok)throw await parseGoTrueError(l,"Registration failed");const h=await l.json(),u=!h.access_token,d={token:h.access_token,refreshToken:h.refresh_token,expiresIn:h.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(h.expires_in||3600),user:this.I(h),confirmationRequired:u};return d.token?await this.P(d):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),d})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.A,!this.o||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.i&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.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(),r=this.I(o);return this.k(r),r})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:o=window.location.href,scopes:r}=e,i=this.config.baseUrl||"http://localhost:8000",n=r?.length?`&scopes=${encodeURIComponent(r.join(" "))}`:"",s=`${i}/auth/v1/authorize?provider=${t}${n}&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.T("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const o=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo&&(r.redirect_to=e.redirectTo);const i=await fetch(`${o}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!i.ok)throw await parseGoTrueError(i,"Recovery failed");return await i.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!o.ok)throw await parseGoTrueError(o,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.u)return _log("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 r=new AbortController,i=setTimeout(()=>r.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:r.signal},async()=>(clearTimeout(i),await o()))}catch(r){if("AbortError"===r.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await o();throw r}}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,r=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&o-r>60)return _log("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.C("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);_log("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,r=setTimeout(()=>o.abort(),AuthModule.REFRESH_TIMEOUT_MS);let i;try{i=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(r)}if(!i.ok){let t={};try{t=await i.json()}catch{}const e=i.status,o=t.error||t.code||"",r=(t.error_description||t.msg||"").toLowerCase(),n="invalid_grant"===o||r.includes("invalid refresh token")||r.includes("revoked");throw e>=400&&e<500&&n?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.T(`refreshSession failed (${e}: ${o})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await i.json(),s={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.I(n)};return this.D=0,await this.P(s),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${s.expiresAt} (en ${s.expiresIn}s). Refresh token actualizado: ${!!s.refreshToken}`),{data:s,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),r=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.D),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.D})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",o.message,`(status: ${o.status}). Backoff: ${r}ms (intento ${this.D})`),this.D>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.T(`${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 r=await o.json(),i={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this.I(r)};return i.token&&await this.P(i),i})())}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 r=await o.json(),i=this.I(r);return this.k(i),i})())}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.R(),this.p=setInterval(()=>this.R(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async R(){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}_log("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){_log("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}I(t){if(!t)return _log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const e=t.user||t,o=e.app_metadata||{},r=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:r,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.k(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`),this.startAutoRefresh())}async k(t,e,o,r){this.t=t,e&&(this.o=e),void 0!==o&&(this.i=o||null),void 0!==r&&(this.l=r||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 _log("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){_log("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const i=e?"SIGNED_IN":"USER_UPDATED";this.C(i)}async T(t="unknown"){_log("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),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.C("SIGNED_OUT")}async m(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.Y())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.o=t.access_token,this.i="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{_log("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 _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let r;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{r=JSON.parse(o)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!r||"object"!=typeof r)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.o=e,this.t=r;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),n=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.i=i,this.l=n?parseInt(n,10):null,await this.k(r,this.o||void 0,this.i||void 0,this.l||void 0);const s=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of s)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const o=Math.floor(Date.now()/1e3),r=this.l&&this.l<=o;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${o}. Diferencia: ${(this.l||0)-o}s`),r&&this.i&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.C("SIGNED_IN")}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async Y(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t),o=e.get("access_token");if(!o)return!1;const r=e.get("refresh_token"),i=e.get("type"),n=parseInt(e.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Callback de GoTrue detectado en URL (type: ${i||"unknown"}). Procesando...`),window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.o=o,this.i=r||null,this.l=Math.floor(Date.now()/1e3)+n;const s=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${s}/auth/v1/user`,{headers:{Authorization:`Bearer ${o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!a.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${a.status}). Limpiando...`),this.o=null,this.i=null,this.l=null,!1;const c=await a.json(),l=this.I(c),h={token:o,refreshToken:r||void 0,expiresIn:n,expiresAt:this.l,user:l};return await this.P(h),"recovery"===i?(_log("log","[DYPAI SDK] 🔑 Password recovery callback. Emitiendo PASSWORD_RECOVERY..."),this.C("PASSWORD_RECOVERY")):(_log("log",`[DYPAI SDK] ✅ Sesión establecida desde callback URL (type: ${i||"signup"}). Emitiendo SIGNED_IN...`),this.C("SIGNED_IN")),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}v(){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}C(t="USER_UPDATED"){const e=this.v();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(o=>o(t,e))}handleSessionExpired(){this.T("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}`)}}class UsersModule{constructor(t){this.api=t}async list(t={}){return this.api.get("admin/users",{params:t})}async create(t){return this.api.post("admin/users",{body:t})}async update(t,e){return this.api.put(`admin/users/${t}`,{body:e})}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:o,variant:r="default"}=t,i=`${"error"===r?"❌":"success"===r?"✅":"warning"===r?"⚠️":"info"===r?"ℹ️":"📢"} ${e}${o?`: ${o}`:""}`;"error"===r?console.error(i):"warning"===r&&console.warn(i)},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,r,i,n,s,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(n&&Object.keys(n).length>0){const t=new URLSearchParams,e=(o,r)=>{null!=r&&(Array.isArray(r)?r.forEach((t,r)=>e(`${o}[${r}]`,t)):"object"==typeof r?Object.entries(r).forEach(([t,r])=>e(`${o}[${t}]`,r)):t.append(o,String(r)))};Object.entries(n).forEach(([t,o])=>e(t,o));const o=t.toString();o&&(h+=`?${o}`)}const u="GET"===e?`${e}:${h}:${JSON.stringify(r)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const d=r instanceof FormData,p={...c.headers||{},...d?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...s&&{"x-api-key":s}},f={method:e,headers:p,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(f.body=d?r:JSON.stringify(r));const y=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,l=(void 0!==i?i:!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)try{const t=await c.onTokenExpired();if(t)return callApi(t,e,o,r,i,n,s,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,g),g}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,r,i)=>{const n=t();let s,a={};r&&"object"==typeof r&&("token"in r||"params"in r||"apiKey"in r)?(a=r,s=a.body):(s=r,a=i||{});const c=a.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",l=a.apiKey||n.apiKey;return callApi(c,e,o,s,a.showToasts,a.params,l,n.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,r,i)=>{const n=t(),s=await async function(t,e){let o,r={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(r=t,o=r.body):(o=t,r=e||{});let i=r.token;return!i&&globalTokenProvider&&(i=globalTokenProvider()||""),i=i||"",{token:i,apiKey:r.apiKey,body:o,params:r.params,showToasts:r.showToasts}}(r,i),a=s.token||n.token||"",c=s.apiKey||n.apiKey;try{return{data:await callApi(a,e,o,s.body,s.showToasts,s.params,c,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}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 r=o.uploadEndpoint||`storage_upload_${this.bucketName}`,i={file_path:t,client_upload:o.clientUpload??!1,...o.params};if(o.clientUpload)return await async function(t,e,o,r){const{data:i,error: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,...r?.params||{}});if(n)throw n;const s=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 r=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of r){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(i);if(!s?.upload_url)throw new DypaiError("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:a,method:c="PUT",headers:l={},file_path:h,storage_path:u}=s;r?.onProgress&&r.onProgress(10);const d=await fetch(a,{method:c,headers:{"Content-Type":o.type||"application/octet-stream",...l},body:o});if(!d.ok)throw new DypaiError("Error en la subida directa a la nube",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:s.bucket||r?.params?.bucket,file_path:h,storage_path:u,filename:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),f}(this.api,r,e,{confirmEndpoint:o.confirmEndpoint,params:i,onProgress:o.onProgress});const n=o.contentType||e.type||"application/octet-stream",s=await async function(t){return new Promise((e,o)=>{const r=new FileReader;r.onload=()=>e(String(r.result)),r.onerror=()=>o(r.error),r.readAsDataURL(t)})}(e),{data:a,error:c}=await this.api.post(r,{...i,content:s,content_type:n,size_bytes:e.size,confirm:!0});if(c)throw c;return a})}async download(t,e={}){return wrapStorageResponse(async()=>{const o=e.downloadEndpoint||`storage_download_${this.bucketName}`,r={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,o,r){const i=r?.method,{data:n,error:s}=await("GET"===i?t.get(e,{params:r?.params}):t.post(e,o,{params:r?.params}));if(s)throw s;if(n instanceof Blob){const t=window.URL.createObjectURL(n),e=document.createElement("a");e.href=t,e.download=r?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(n&&"object"==typeof n&&("url"in n||"signed_url"in n||"signedUrl"in n)){const t=n.url||n.signed_url||n.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 i=r?.fileName||o;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,o,r,{fileName:e.fileName,method:e.method||"POST"})})}async createSignedUrl(t,e=15,o={}){return wrapStorageResponse(async()=>{const r=o.endpoint||`storage_download_${this.bucketName}`,i={bucket:this.bucketName,file_path:t,expires_minutes:e,download:!1,...o.params},{data:n,error:s}=await this.api.post(r,i);if(s)throw s;return{signedUrl:n?.signed_url||n?.signedUrl||n?.url,expiresIn:n?.expires_in||n?.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}`,r={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},{data:i,error:n}=await this.api.post(o,r);if(n)throw n;return{data:i?.data||i?.files||[],total:i?.total,hasMore:i?.has_more||i?.hasMore||!1}})}async remove(t,e={}){return wrapStorageResponse(async()=>{const o=e.endpoint||`storage_delete_${this.bucketName}`,r=Array.isArray(t)?t:[t],i={bucket:this.bucketName,paths:r,...e.params},{error:n}=await this.api.post(o,i);if(n)throw n;return{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,r=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:o,storageKey:r,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const i={get:createMethod(n=()=>({token:this.auth.token,apiKey:o,baseUrl:e}),"GET"),post:createMethod(n,"POST"),put:createMethod(n,"PUT"),patch:createMethod(n,"PATCH"),delete:createMethod(n,"DELETE")};var n;this.auth.api=i,this.db=new DataModule(i),this.users=new UsersModule(i),this.storage=new StorageModule(i),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"})}};
1
+ "use strict";class DypaiError extends Error{constructor(t,e=500,o,r){super(t),this.status=e,this.code=o,this.details=r,this.name="DypaiError"}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function parseGoTrueError(t,e){let o={};try{o=await t.json()}catch{}const r=o.msg||o.error_description||o.message||e,i=o.error_code||o.error||o.code||void 0;return new DypaiError(r,t.status,i)}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,_debugEnabled=!!t.debug;const o=t.storageKey||this.deriveStorageKey(t.apiKey);_log("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (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._.bind(this)),window.addEventListener("focus",this._.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.m(!1))}))}async _(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("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}isLoggedIn(){return!(!this.o||!this.t)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.h.push(t),this.A.then(()=>{const e=this.v(),o=e?"SIGNED_IN":"INITIAL_SESSION";_log("log",`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${o} (Sesión activa: ${!!e})`),t(o,e)}).catch(e=>{_log("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.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},r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(o)});if(!r.ok)throw await parseGoTrueError(r,"Login failed");const i=await r.json(),n={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(i)};return await this.P(n),n})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return wrapAuthResponse((async()=>{const o=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/signup`,{email:r,phone:i,password:n,user_data:s,...a}=t,c={email:r,phone:i,password:n,data:{...a,...s}};e?.redirectTo&&(c.redirect_to=e.redirectTo);const l=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(c)});if(!l.ok)throw await parseGoTrueError(l,"Registration failed");const h=await l.json(),u=!h.access_token,d={token:h.access_token,refreshToken:h.refresh_token,expiresIn:h.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(h.expires_in||3600),user:this.I(h),confirmationRequired:u};return d.token?await this.P(d):_log("log","[DYPAI SDK] 📧 Signup exitoso, se requiere confirmación de email."),d})())}async register(t){return this.signUp(t)}async getSession(){try{if(await this.A,!this.o||!this.t)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.l||0)-t<30&&this.i&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.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(),r=this.I(o);return this.k(r),r})())}async me(){return this.getUser()}async signInWithOAuth(t,e={}){return wrapAuthResponse((async()=>{if("undefined"==typeof window)throw new DypaiError("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo:o=window.location.href,scopes:r}=e,i=this.config.baseUrl||"http://localhost:8000",n=r?.length?`&scopes=${encodeURIComponent(r.join(" "))}`:"",s=`${i}/auth/v1/authorize?provider=${t}${n}&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.T("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return wrapAuthResponse((async()=>{const o=this.config.baseUrl||"http://localhost:8000",r={email:t};e?.redirectTo&&(r.redirect_to=e.redirectTo);const i=await fetch(`${o}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!i.ok)throw await parseGoTrueError(i,"Recovery failed");return await i.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return wrapAuthResponse((async()=>{const e=this.config.baseUrl||"http://localhost:8000",o=await fetch(`${e}/auth/v1/resend`,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({email:t,type:"signup"})});if(!o.ok)throw await parseGoTrueError(o,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.u)return _log("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 r=new AbortController,i=setTimeout(()=>r.abort(),e);try{return await globalThis.navigator.locks.request(t,{signal:r.signal},async()=>(clearTimeout(i),await o()))}catch(r){if("AbortError"===r.name)return console.warn(`[DYPAI SDK] ⚠️ Web Lock "${t}" timeout (${e}ms). Proceeding without lock.`),await o();throw r}}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,r=Math.floor(Date.now()/1e3);if(e.access_token!==this.o&&o-r>60)return _log("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.C("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);_log("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,r=setTimeout(()=>o.abort(),AuthModule.REFRESH_TIMEOUT_MS);let i;try{i=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(r)}if(!i.ok){let t={};try{t=await i.json()}catch{}const e=i.status,o=t.error||t.code||"",r=(t.error_description||t.msg||"").toLowerCase(),n="invalid_grant"===o||r.includes("invalid refresh token")||r.includes("revoked");throw e>=400&&e<500&&n?(_log("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.T(`refreshSession failed (${e}: ${o})`)):_log("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${e}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new DypaiError(t.msg||t.error_description||"Refresh session failed",e)}const n=await i.json(),s={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.I(n)};return this.D=0,await this.P(s),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${s.expiresAt} (en ${s.expiresIn}s). Refresh token actualizado: ${!!s.refreshToken}`),{data:s,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),r=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.D),AuthModule.MAX_RETRY_MS);return e?_log("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${AuthModule.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${r}ms (intento ${this.D})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",o.message,`(status: ${o.status}). Backoff: ${r}ms (intento ${this.D})`),this.D>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.D} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.T(`${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 r=await o.json(),i={token:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(r.expires_in||3600),user:this.I(r)};return i.token&&await this.P(i),i})())}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 r=await o.json(),i=this.I(r);return this.k(i),i})())}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.R(),this.p=setInterval(()=>this.R(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async R(){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}_log("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){_log("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}I(t){if(!t)return _log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{};const e=t.user||t,o=e.app_metadata||{},r=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:r,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.k(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`),this.startAutoRefresh())}async k(t,e,o,r){this.t=t,e&&(this.o=e),void 0!==o&&(this.i=o||null),void 0!==r&&(this.l=r||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 _log("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){_log("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const i=e?"SIGNED_IN":"USER_UPDATED";this.C(i)}async T(t="unknown"){_log("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),_log("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){_log("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.C("SIGNED_OUT")}async m(t=!0){_log("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.U())return void _log("log","[DYPAI SDK] ✅ Sesión establecida desde callback URL. Saltando recuperación de localStorage.");const e=await this.storage.getItem(this.STORAGE_KEY);if(e){let t;_log("log","[DYPAI SDK] ✅ Sesión consolidada encontrada. Restaurando datos...");try{t=JSON.parse(e)}catch{return console.warn("[DYPAI SDK] ⚠️ Sesión en storage corrupta (JSON inválido). Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY)}if(!t||"object"!=typeof t||"string"!=typeof t.access_token)return console.warn("[DYPAI SDK] ⚠️ Sesión en storage con formato inválido. Limpiando..."),void await this.storage.removeItem(this.STORAGE_KEY);this.o=t.access_token,this.i="string"==typeof t.refresh_token?t.refresh_token:null,this.l="number"==typeof t.expires_at?t.expires_at:null,this.t=t.user&&"object"==typeof t.user?t.user:null}else{_log("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 _log("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let r;_log("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{r=JSON.parse(o)}catch{return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage corrupto (JSON inválido). Ignorando migración...")}if(!r||"object"!=typeof r)return void console.warn("[DYPAI SDK] ⚠️ Usuario antiguo en storage con formato inválido. Ignorando migración...");this.o=e,this.t=r;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),n=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.i=i,this.l=n?parseInt(n,10):null,await this.k(r,this.o||void 0,this.i||void 0,this.l||void 0);const s=[`dypai-auth-token-${t}`,`dypai-auth-refresh-token-${t}`,`dypai-auth-user-${t}`,`dypai-auth-expires-at-${t}`];for(const t of s)try{await this.storage.removeItem(t)}catch(t){}_log("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const o=Math.floor(Date.now()/1e3),r=this.l&&this.l<=o;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.l}. Ahora es: ${o}. Diferencia: ${(this.l||0)-o}s`),r&&this.i&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.l&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.C("SIGNED_IN")}catch(t){_log("error","[DYPAI SDK] ❌ Error crítico durante la recuperación de sesión:",t)}}async U(){if("undefined"==typeof window)return!1;try{const t=window.location.hash.substring(1);if(!t)return!1;const e=new URLSearchParams(t),o=e.get("access_token");if(!o)return!1;const r=e.get("refresh_token"),i=e.get("type"),n=parseInt(e.get("expires_in")||"3600",10);_log("log",`[DYPAI SDK] 🔗 Callback de GoTrue detectado en URL (type: ${i||"unknown"}). Procesando...`),window.history.replaceState({},document.title,window.location.pathname+window.location.search),this.o=o,this.i=r||null,this.l=Math.floor(Date.now()/1e3)+n;const s=this.config.baseUrl||"http://localhost:8000",a=await fetch(`${s}/auth/v1/user`,{headers:{Authorization:`Bearer ${o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!a.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${a.status}). Limpiando...`),this.o=null,this.i=null,this.l=null,!1;const c=await a.json(),l=this.I(c),h={token:o,refreshToken:r||void 0,expiresIn:n,expiresAt:this.l,user:l};return await this.P(h),"recovery"===i?(_log("log","[DYPAI SDK] 🔑 Password recovery callback. Emitiendo PASSWORD_RECOVERY..."),this.C("PASSWORD_RECOVERY")):(_log("log",`[DYPAI SDK] ✅ Sesión establecida desde callback URL (type: ${i||"signup"}). Emitiendo SIGNED_IN...`),this.C("SIGNED_IN")),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}v(){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}C(t="USER_UPDATED"){const e=this.v();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.h.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.h.forEach(o=>o(t,e))}handleSessionExpired(){this.T("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}`)}}class UsersModule{constructor(t){this.api=t}async list(t={}){return this.api.get("admin/users",{params:t})}async create(t){return this.api.post("admin/users",{body:t})}async update(t,e){return this.api.put(`admin/users/${t}`,{body:e})}async delete(t){return this.api.delete(`admin/users/${t}`)}}let customToastFunction=null;const fallbackToast=t=>{const{title:e,description:o,variant:r="default"}=t,i=`${"error"===r?"❌":"success"===r?"✅":"warning"===r?"⚠️":"info"===r?"ℹ️":"📢"} ${e}${o?`: ${o}`:""}`;"error"===r?console.error(i):"warning"===r&&console.warn(i)},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,r,i,n,s,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(n&&Object.keys(n).length>0){const t=new URLSearchParams,e=(o,r)=>{null!=r&&(Array.isArray(r)?r.forEach((t,r)=>e(`${o}[${r}]`,t)):"object"==typeof r?Object.entries(r).forEach(([t,r])=>e(`${o}[${t}]`,r)):t.append(o,String(r)))};Object.entries(n).forEach(([t,o])=>e(t,o));const o=t.toString();o&&(h+=`?${o}`)}const u="GET"===e?`${e}:${h}:${JSON.stringify(r)}`:null;if(u&&pendingRequests.has(u))return pendingRequests.get(u);const d=r instanceof FormData,p={...c.headers||{},...d?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...s&&{"x-api-key":s}},f={method:e,headers:p,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(f.body=d?r:JSON.stringify(r));const y=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!y)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,l=(void 0!==i?i:!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)try{const t=await c.onTokenExpired();if(t)return callApi(t,e,o,r,i,n,s,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,g),g}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,r,i)=>{const n=t();let s,a={};r&&"object"==typeof r&&("token"in r||"params"in r||"apiKey"in r)?(a=r,s=a.body):(s=r,a=i||{});const c=a.token||n.token||(globalTokenProvider?globalTokenProvider():"")||"",l=a.apiKey||n.apiKey;return callApi(c,e,o,s,a.showToasts,a.params,l,n.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,r,i)=>{const n=t(),s=await async function(t,e){let o,r={};t&&"object"==typeof t&&("token"in t||"params"in t||"apiKey"in t)?(r=t,o=r.body):(o=t,r=e||{});let i=r.token;return!i&&globalTokenProvider&&(i=globalTokenProvider()||""),i=i||"",{token:i,apiKey:r.apiKey,body:o,params:r.params,showToasts:r.showToasts}}(r,i),a=s.token||n.token||"",c=s.apiKey||n.apiKey;try{return{data:await callApi(a,e,o,s.body,s.showToasts,s.params,c,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}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 r=o.uploadEndpoint||`storage_upload_${this.bucketName}`,i={file_path:t,client_upload:o.clientUpload??!1,...o.params};if(o.clientUpload)return await async function(t,e,o,r){const{data:i,error: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,...r?.params||{}});if(n)throw n;const s=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 r=[t.steps_results,t.nodes_results].filter(Boolean);for(const t of r){const e=Array.isArray(t)?t:Object.values(t);for(const t of e)if(t&&(t.upload_url||t.uploadUrl||t.url))return normalizeUploadPayload(t)}return null}(i);if(!s?.upload_url)throw new DypaiError("El workflow no devolvió una URL de subida válida (¿falta storage.upload?)",400);const{upload_url:a,method:c="PUT",headers:l={},file_path:h,storage_path:u}=s;r?.onProgress&&r.onProgress(10);const d=await fetch(a,{method:c,headers:{"Content-Type":o.type||"application/octet-stream",...l},body:o});if(!d.ok)throw new DypaiError("Error en la subida directa a la nube",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:s.bucket||r?.params?.bucket,file_path:h,storage_path:u,filename:o.name,content_type:o.type||"application/octet-stream",size_bytes:o.size,confirm:!0,client_upload:!0});if(y)throw y;return r?.onProgress&&r.onProgress(100),f}(this.api,r,e,{confirmEndpoint:o.confirmEndpoint,params:i,onProgress:o.onProgress});const n=o.contentType||e.type||"application/octet-stream",s=await async function(t){return new Promise((e,o)=>{const r=new FileReader;r.onload=()=>e(String(r.result)),r.onerror=()=>o(r.error),r.readAsDataURL(t)})}(e),{data:a,error:c}=await this.api.post(r,{...i,content:s,content_type:n,size_bytes:e.size,confirm:!0});if(c)throw c;return a})}async download(t,e={}){return wrapStorageResponse(async()=>{const o=e.downloadEndpoint||`storage_download_${this.bucketName}`,r={bucket:this.bucketName,file_path:t,...e.params};await async function(t,e,o,r){const{data:i,error:n}=await t.post(e,o,{params:r?.params});if(n)throw n;if(i instanceof Blob){const t=window.URL.createObjectURL(i),e=document.createElement("a");e.href=t,e.download=r?.fileName||"archivo-descargado",document.body.appendChild(e),e.click(),window.URL.revokeObjectURL(t),document.body.removeChild(e)}else if(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 i=r?.fileName||o;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,o,r,{fileName:e.fileName,method:"POST"})})}async createSignedUrl(t,e=15,o={}){return wrapStorageResponse(async()=>{const r=o.endpoint||`storage_download_${this.bucketName}`,i={bucket:this.bucketName,file_path:t,expires_minutes:e,download:!1,...o.params},{data:n,error:s}=await this.api.post(r,i);if(s)throw s;return{signedUrl:n?.signed_url||n?.signedUrl||n?.url,expiresIn:n?.expires_in||n?.expiresIn||e,path:t}})}getPublicUrl(t){return console.warn("[DYPAI SDK] getPublicUrl() is deprecated. The storage service is not publicly accessible. Use createSignedUrl() instead."),{data:{publicUrl:""}}}async list(t="",e={}){return wrapStorageResponse(async()=>{const o=e.endpoint||`storage_list_${this.bucketName}`,r={bucket:this.bucketName,prefix:t||e.prefix||"",page:e.page||1,per_page:e.perPage||20,...e.params},{data:i,error:n}=await this.api.post(o,r);if(n)throw n;return{data:i?.data||i?.files||[],hasMore:i?.has_more||i?.hasMore||!1}})}async remove(t,e={}){return wrapStorageResponse(async()=>{const o=e.endpoint||`storage_delete_${this.bucketName}`,r=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t),i={bucket:this.bucketName,...r?{file_id:t}:{file_path:t},...e.params},{error:n}=await this.api.post(o,i);if(n)throw n;return{success:!0}})}async move(t,e,o={}){return wrapStorageResponse(async()=>{throw new DypaiError("move() is not supported by the built-in storage node. Create a custom workflow endpoint if needed.",400)})}async copy(t,e,o={}){return wrapStorageResponse(async()=>{throw new DypaiError("copy() is not supported by the built-in storage node. Create a custom workflow endpoint if needed.",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||"Storage operation failed",t.status||500)}}}class DypaiClient{constructor(t){const{baseUrl:e,apiKey:o}=t,r=t.auth?.storageKey||t.storageKey;t.global&&function(t){serviceConfig={...serviceConfig,...t}}(t.global),this.auth=new AuthModule({baseUrl:e,apiKey:o,storageKey:r,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const i={get:createMethod(n=()=>({token:this.auth.token,apiKey:o,baseUrl:e}),"GET"),post:createMethod(n,"POST"),put:createMethod(n,"PUT"),patch:createMethod(n,"PATCH"),delete:createMethod(n,"DELETE")};var n;this.auth.api=i,this.db=new DataModule(i),this.users=new UsersModule(i),this.storage=new StorageModule(i),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"})}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dypai-ai/client-sdk",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Cliente JavaScript para Dypai Engine",
5
5
  "type": "module",
6
6
  "private": false,