@dypai-ai/client-sdk 0.0.23 → 0.0.25

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
@@ -146,6 +146,28 @@ interface TypedApiClient<TMap extends EndpointMap = EndpointMap> {
146
146
  * DELETE request tipado
147
147
  */
148
148
  delete<TEndpoint extends (keyof TMap & string) | (string & {})>(endpoint: TEndpoint, options?: TypedGetOptions<TEndpoint extends keyof TMap ? EndpointParams<TMap, TEndpoint> : Record<string, any>>): Promise<DypaiResponse<TEndpoint extends keyof TMap ? EndpointResponse<TMap, TEndpoint> : any>>;
149
+ /**
150
+ * Sube un archivo via presigned URL a través de un endpoint con nodo dypai_storage
151
+ */
152
+ upload(endpoint: string, file: File, options?: ApiUploadOptions): Promise<DypaiResponse<any>>;
153
+ /**
154
+ * Descarga un archivo a través de un endpoint con nodo dypai_storage
155
+ */
156
+ download(endpoint: string, body?: any, options?: {
157
+ fileName?: string;
158
+ params?: Record<string, any>;
159
+ }): Promise<DypaiResponse<void>>;
160
+ }
161
+ /**
162
+ * Opciones para subida de archivos via endpoint
163
+ */
164
+ interface ApiUploadOptions {
165
+ /** Parámetros adicionales para el endpoint */
166
+ params?: Record<string, any>;
167
+ /** Callback de progreso (0-100) */
168
+ onProgress?: (percent: number) => void;
169
+ /** Endpoint de confirmación (si es diferente al de preparación) */
170
+ confirmEndpoint?: string;
149
171
  }
150
172
  /**
151
173
  * Tipo para el cliente API sin tipado (legacy/fallback)
@@ -171,6 +193,13 @@ interface ApiClient {
171
193
  params?: Record<string, any>;
172
194
  [key: string]: any;
173
195
  }) => Promise<DypaiResponse<any>>;
196
+ /** Sube un archivo via presigned URL a través de un endpoint con nodo dypai_storage */
197
+ upload: (endpoint: string, file: File, options?: ApiUploadOptions) => Promise<DypaiResponse<any>>;
198
+ /** Descarga un archivo a través de un endpoint con nodo dypai_storage */
199
+ download: (endpoint: string, body?: any, options?: {
200
+ fileName?: string;
201
+ params?: Record<string, any>;
202
+ }) => Promise<DypaiResponse<void>>;
174
203
  }
175
204
  interface AppConfig {
176
205
  DYPAI_API_URL: string;
@@ -237,8 +266,8 @@ interface User {
237
266
  phone?: string;
238
267
  confirmed_at?: string | null;
239
268
  role?: string;
240
- _role_details?: AppRole;
241
- _app_context?: {
269
+ roleDetails?: AppRole;
270
+ appContext?: {
242
271
  app_id: string;
243
272
  };
244
273
  created_at?: string;
@@ -550,256 +579,6 @@ declare class UsersModule {
550
579
  }>>;
551
580
  }
552
581
 
553
- /**
554
- * Types for the Storage module
555
- *
556
- * All operations go through a single engine workflow endpoint per bucket:
557
- * storage_{bucket} → { operation: 'upload' | 'download' | 'read' | 'list' | 'delete', ... }
558
- *
559
- * The underlying storage service is internal and never exposed directly.
560
- */
561
- interface UploadOptions {
562
- /** Progress callback (0-100). Only works with clientUpload: true */
563
- onProgress?: (percent: number) => void;
564
- /** Custom params passed to the workflow */
565
- params?: Record<string, any>;
566
- /** Custom workflow endpoint name (default: storage_{bucket}) */
567
- endpoint?: string;
568
- /** Custom confirm endpoint for client uploads (default: same as endpoint) */
569
- confirmEndpoint?: string;
570
- /** Override MIME type (default: file.type or application/octet-stream) */
571
- contentType?: string;
572
- /** Upload directly from the browser via presigned URL (default: true). Set to false to upload through the engine (base64) */
573
- clientUpload?: boolean;
574
- }
575
- interface DownloadOptions {
576
- /** Filename for the downloaded file */
577
- fileName?: string;
578
- /** Custom params passed to the workflow */
579
- params?: Record<string, any>;
580
- /** Custom workflow endpoint name (default: storage_{bucket}) */
581
- endpoint?: string;
582
- }
583
- interface SignedUrlOptions {
584
- /** Expiration in minutes (5-120, default: 15) */
585
- expiresIn?: number;
586
- /** Custom params passed to the workflow */
587
- params?: Record<string, any>;
588
- /** Custom workflow endpoint name (default: storage_{bucket}) */
589
- endpoint?: string;
590
- }
591
- interface ListOptions {
592
- /** Filter by folder/prefix */
593
- prefix?: string;
594
- /** Page number (1-indexed, default: 1) */
595
- page?: number;
596
- /** Results per page (1-100, default: 20) */
597
- perPage?: number;
598
- /** Custom params passed to the workflow */
599
- params?: Record<string, any>;
600
- /** Custom workflow endpoint name (default: storage_{bucket}) */
601
- endpoint?: string;
602
- }
603
- interface RemoveOptions {
604
- /** Custom params passed to the workflow */
605
- params?: Record<string, any>;
606
- /** Custom workflow endpoint name (default: storage_{bucket}) */
607
- endpoint?: string;
608
- }
609
- interface UploadResponse {
610
- status: string;
611
- file_id?: string;
612
- filename: string;
613
- file_path: string;
614
- storage_path?: string;
615
- size?: number;
616
- mime_type?: string;
617
- entity_id?: string;
618
- }
619
- interface SignedUrlResponse {
620
- signedUrl: string;
621
- expiresIn: number;
622
- path: string;
623
- }
624
- interface StorageObject {
625
- id: string;
626
- name: string;
627
- size: number;
628
- mime_type?: string;
629
- created_at: string;
630
- updated_at: string;
631
- metadata?: Record<string, any>;
632
- }
633
- interface ListResponse {
634
- data: StorageObject[];
635
- hasMore?: boolean;
636
- }
637
-
638
- /**
639
- * Storage Module
640
- *
641
- * Provides a Supabase-like API for file management.
642
- * All operations go through a single engine workflow endpoint per bucket.
643
- *
644
- * Endpoint convention: `storage_{bucket}` with `operation` in the request body.
645
- */
646
- declare class StorageModule {
647
- private api;
648
- constructor(api: ApiClient);
649
- /**
650
- * Select a bucket for operations
651
- * @param bucketName Bucket name
652
- * @returns Builder for operations on the bucket
653
- *
654
- * @example
655
- * ```typescript
656
- * const { data, error } = await dypai.storage
657
- * .from('documents')
658
- * .upload('invoices/2024/inv-001.pdf', file);
659
- * ```
660
- */
661
- from(bucketName: string): StorageBucketBuilder;
662
- }
663
- /**
664
- * Builder for operations on a specific bucket.
665
- *
666
- * All methods call a single endpoint `storage_{bucket}` and pass
667
- * `operation` in the body to select the action (upload, download, read, list, delete).
668
- */
669
- declare class StorageBucketBuilder {
670
- private bucketName;
671
- private api;
672
- constructor(bucketName: string, api: ApiClient);
673
- /** Default endpoint for this bucket */
674
- private defaultEndpoint;
675
- /**
676
- * Upload a file to the bucket
677
- *
678
- * @param path Path where the file will be stored (e.g. 'folder/file.jpg')
679
- * @param file File to upload
680
- * @param options Additional options
681
- *
682
- * @example
683
- * ```typescript
684
- * // Default: client-side upload via presigned URL (direct to storage)
685
- * const { data, error } = await dypai.storage
686
- * .from('documents')
687
- * .upload('invoices/2024/inv-001.pdf', file);
688
- *
689
- * // With progress tracking
690
- * const { data, error } = await dypai.storage
691
- * .from('documents')
692
- * .upload('invoices/2024/inv-001.pdf', file, {
693
- * onProgress: (pct) => console.log(`${pct}%`)
694
- * });
695
- *
696
- * // Server-side upload via engine (opt-in, for small files or custom logic)
697
- * const { data, error } = await dypai.storage
698
- * .from('documents')
699
- * .upload('invoices/2024/inv-001.pdf', file, {
700
- * clientUpload: false
701
- * });
702
- * ```
703
- */
704
- upload(path: string, file: File, options?: UploadOptions): Promise<DypaiResponse<UploadResponse>>;
705
- /**
706
- * Download a file from the bucket.
707
- * Gets a signed URL from the engine and triggers a browser download.
708
- *
709
- * @param path File path to download
710
- * @param options Additional options
711
- *
712
- * @example
713
- * ```typescript
714
- * await dypai.storage
715
- * .from('documents')
716
- * .download('invoices/2024/inv-001.pdf', {
717
- * fileName: 'invoice.pdf'
718
- * });
719
- * ```
720
- */
721
- download(path: string, options?: DownloadOptions): Promise<DypaiResponse<void>>;
722
- /**
723
- * Get a signed URL for temporary access.
724
- *
725
- * @param path File path
726
- * @param expiresIn Expiration in minutes (5-120, default: 15)
727
- * @param options Additional options
728
- *
729
- * @example
730
- * ```typescript
731
- * const { data, error } = await dypai.storage
732
- * .from('documents')
733
- * .createSignedUrl('report.pdf', 30);
734
- *
735
- * if (data) {
736
- * console.log('Signed URL:', data.signedUrl);
737
- * // URL is valid for 30 minutes
738
- * }
739
- * ```
740
- */
741
- createSignedUrl(path: string, expiresIn?: number, options?: Omit<SignedUrlOptions, 'expiresIn'>): Promise<DypaiResponse<SignedUrlResponse>>;
742
- /**
743
- * List files in the bucket
744
- *
745
- * @param path Folder/prefix to list (optional, lists root if empty)
746
- * @param options Pagination options
747
- *
748
- * @example
749
- * ```typescript
750
- * const { data, error } = await dypai.storage
751
- * .from('documents')
752
- * .list('invoices/2024', { page: 1, perPage: 20 });
753
- *
754
- * if (data) {
755
- * data.data.forEach(file => {
756
- * console.log(file.name, file.size);
757
- * });
758
- * if (data.hasMore) {
759
- * // Fetch next page
760
- * }
761
- * }
762
- * ```
763
- */
764
- list(path?: string, options?: ListOptions): Promise<DypaiResponse<ListResponse>>;
765
- /**
766
- * Delete a file from the bucket
767
- *
768
- * @param path File path or file ID to delete
769
- * @param options Additional options
770
- *
771
- * @example
772
- * ```typescript
773
- * // Delete by path
774
- * const { error } = await dypai.storage
775
- * .from('documents')
776
- * .remove('invoices/2024/inv-001.pdf');
777
- *
778
- * // Delete by file ID
779
- * const { error } = await dypai.storage
780
- * .from('documents')
781
- * .remove('550e8400-e29b-41d4-a716-446655440000');
782
- * ```
783
- */
784
- remove(path: string, options?: RemoveOptions): Promise<DypaiResponse<{
785
- success: boolean;
786
- }>>;
787
- /**
788
- * Move/rename a file.
789
- * Not supported by the built-in storage node. Create a custom workflow endpoint if needed.
790
- */
791
- move(_fromPath: string, _toPath: string, _options?: RemoveOptions): Promise<DypaiResponse<{
792
- success: boolean;
793
- }>>;
794
- /**
795
- * Copy a file.
796
- * Not supported by the built-in storage node. Create a custom workflow endpoint if needed.
797
- */
798
- copy(_fromPath: string, _toPath: string, _options?: RemoveOptions): Promise<DypaiResponse<{
799
- success: boolean;
800
- }>>;
801
- }
802
-
803
582
  interface DypaiConfig {
804
583
  /** URL base de la API (ej: 'http://localhost:8000') - Debe apuntar al Gateway unificado */
805
584
  baseUrl: string;
@@ -825,8 +604,6 @@ declare class DypaiClient<TDatabase = any, TApi extends EndpointMap = EndpointMa
825
604
  db: DataModule<TDatabase>;
826
605
  /** Módulo de gestión de usuarios (admin) */
827
606
  users: UsersModule;
828
- /** Módulo de almacenamiento (Storage) */
829
- storage: StorageModule;
830
607
  /** Cliente API tipado para endpoints personalizados */
831
608
  api: TApi extends EndpointMap ? TypedApiClient<TApi> : ApiClient;
832
609
  constructor(config: DypaiConfig);
@@ -848,6 +625,44 @@ declare class DypaiClient<TDatabase = any, TApi extends EndpointMap = EndpointMa
848
625
  */
849
626
  declare function createClient<TDatabase = any, TApi extends EndpointMap = EndpointMap>(url: string, apiKeyOrOptions?: string | Omit<DypaiConfig, 'baseUrl' | 'apiKey'>, maybeOptions?: Omit<DypaiConfig, 'baseUrl' | 'apiKey'>): DypaiClient<TDatabase, TApi>;
850
627
 
628
+ /**
629
+ * Types for the Storage module
630
+ *
631
+ * All operations go through a single engine workflow endpoint per bucket:
632
+ * storage_{bucket} → { operation: 'upload' | 'download' | 'read' | 'list' | 'delete', ... }
633
+ *
634
+ * The underlying storage service is internal and never exposed directly.
635
+ */
636
+ interface UploadOptions {
637
+ /** Progress callback (0-100). Only works with clientUpload: true */
638
+ onProgress?: (percent: number) => void;
639
+ /** Custom params passed to the workflow */
640
+ params?: Record<string, any>;
641
+ /** Custom workflow endpoint name (default: storage_{bucket}) */
642
+ endpoint?: string;
643
+ /** Custom confirm endpoint for client uploads (default: same as endpoint) */
644
+ confirmEndpoint?: string;
645
+ /** Override MIME type (default: file.type or application/octet-stream) */
646
+ contentType?: string;
647
+ /** Upload directly from the browser via presigned URL (default: true). Set to false to upload through the engine (base64) */
648
+ clientUpload?: boolean;
649
+ }
650
+ interface UploadResponse {
651
+ status: string;
652
+ file_id?: string;
653
+ filename: string;
654
+ file_path: string;
655
+ storage_path?: string;
656
+ size?: number;
657
+ mime_type?: string;
658
+ entity_id?: string;
659
+ }
660
+ interface SignedUrlResponse {
661
+ signedUrl: string;
662
+ expiresIn: number;
663
+ path: string;
664
+ }
665
+
851
666
  /**
852
667
  * @dypai/client-sdk - Cliente HTTP centralizado
853
668
  *
@@ -982,14 +797,8 @@ declare const toastInfo: (title: string, description?: string) => void;
982
797
  * // API personalizada (endpoints)
983
798
  * const facturas = await dypai.api.get('mis_facturas');
984
799
  *
985
- * // Storage (Almacenamiento) - Estilo Supabase
986
- * const { data, error } = await dypai.storage
987
- * .from('documentos')
988
- * .upload('facturas/2024/inv-001.pdf', file);
989
- *
990
- * const { data: url } = await dypai.storage
991
- * .from('documentos')
992
- * .getSignedUrl('facturas/2024/inv-001.pdf', { expiresIn: 15 });
800
+ * // Storage - Usa endpoints personalizados para subir/descargar archivos
801
+ * const { data, error } = await dypai.api.post('mi_endpoint_subida', { ... });
993
802
  * ```
994
803
  *
995
804
  * @example Uso avanzado con tipado de Base de Datos y API
@@ -1022,8 +831,8 @@ declare const PACKAGE_INFO: {
1022
831
  readonly name: "@dypai-ai/client-sdk";
1023
832
  readonly version: "0.0.1";
1024
833
  readonly description: "Cliente JavaScript para Dypai Engine";
1025
- readonly features: readonly ["API REST autenticada", "Smart Storage (Upload/Download delegado)", "Gestión de usuarios (Admin)"];
834
+ readonly features: readonly ["API REST autenticada", "Gestión de usuarios (Admin)", "Storage via endpoints personalizados"];
1026
835
  };
1027
836
 
1028
837
  export { DypaiClient, PACKAGE_INFO, callApi, configureApiService, configureDypaiServices, createApiClient, createClient, getGlobalConfig, reloadDypaiConfig, resetGlobalConfig, setToastFunction, setTokenProvider, toast, toastError, toastInfo, toastSuccess, toastWarning, useToast };
1029
- export type { ApiClient, ApiContextType, ApiOptions, ApiServiceConfig, AppConfig, AppData, AppRole, AppUserResponse, AuthChangeEvent, AuthResponse, CreateUserRequest, DownloadOptions, DypaiConfig, EndpointBody, EndpointDefinition, EndpointMap, EndpointParams, EndpointResponse, HttpMethod, JoinResponse, ListOptions, ListResponse, LoginCredentials, OAuthOptions, PasswordRecoveryRequest, PasswordRecoveryResponse, PasswordResetRequest, PasswordResetResponse, RegisterData, RemoveOptions, ResetPasswordWithTokenRequest, Session, SessionResponse, SignedUrlOptions, SignedUrlResponse, StorageObject, ToastFunction, TypedApiClient, TypedGetOptions, TypedMutationOptions, UpdateUserRequest, UploadOptions, UploadResponse, User, UserData, UserListResponse };
838
+ export type { ApiClient, ApiContextType, ApiOptions, ApiServiceConfig, ApiUploadOptions, AppConfig, AppData, AppRole, AppUserResponse, AuthChangeEvent, AuthResponse, CreateUserRequest, DypaiConfig, EndpointBody, EndpointDefinition, EndpointMap, EndpointParams, EndpointResponse, HttpMethod, JoinResponse, LoginCredentials, OAuthOptions, PasswordRecoveryRequest, PasswordRecoveryResponse, PasswordResetRequest, PasswordResetResponse, RegisterData, ResetPasswordWithTokenRequest, Session, SessionResponse, SignedUrlResponse, ToastFunction, TypedApiClient, TypedGetOptions, TypedMutationOptions, UpdateUserRequest, UploadOptions, UploadResponse, User, UserData, UserListResponse };
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"}}function e(t){if(!t)return{};const e=t.user||t,i=e.app_metadata||{},n=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:i.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:i,user_metadata:n,t:{name:i.role||"authenticated",weight:0},i:{app_id:"default"}}}let i=!1;function n(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function s(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 o{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 a(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de autenticación",e.status||400)}}}class c{constructor(t,e=null){this.config=t,this.o=null,this.h=null,this.l=null,this.u=null,this.p=[],this.S=null,this.D=null,this.m=0,this.I=0,this.storage=t.storage||r,i=!!t.debug;const s=t.storageKey||this.deriveStorageKey(t.apiKey);n("log",`[DYPAI SDK] 🛠️ Inicializando AuthModule (storageKey: ${s})`),this.STORAGE_KEY=`dypai-${s}-auth-session`,this.v=this.A().then(()=>{this.h&&(this.getUser().catch(()=>{}),this.startAutoRefresh())}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.P.bind(this)),window.addEventListener("focus",this.P.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(n("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.A(!1))}))}async P(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(n("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.A(!0),this.h&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.o}get token(){return this.h}isLoggedIn(){return!(!this.h||!this.o)}onAuthStateChange(t){return n("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.p.push(t),this.v.then(()=>{const e=this.k(),i=e?"SIGNED_IN":"INITIAL_SESSION";n("log",`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${i} (Sesión activa: ${!!e})`),t(i,e)}).catch(e=>{n("error","[DYPAI SDK] ❌ Error esperando recuperación de sesión para suscriptor:",e),t("INITIAL_SESSION",null)}),{data:{subscription:{unsubscribe:()=>{this.p=this.p.filter(e=>e!==t)}}}}}async signInWithPassword(t){return a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,i={email:t.email||t.identifier||"",password:t.password},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(i)});if(!n.ok)throw await s(n,"Login failed");const o=await n.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._(o)};return await this.$(r),r})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${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(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await s(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._(d),confirmationRequired:p};return f.token?await this.$(f):n("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.v,!this.h||!this.o)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.u||0)-t<30&&this.l&&(n("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>n("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.h,refresh_token:this.l||void 0,token_type:"bearer",user:this.o},error:null}}catch(e){return{data:null,error:new t(e.message,500)}}}async getUser(){return a((async()=>{if(!this.h)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.h}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!i.ok)throw new t("Session invalid",i.status);const n=await i.json(),s=this._(n);return this.T(s),s})())}async me(){return this.getUser()}async signInWithOAuth(e,i={}){return a((async()=>{if("undefined"==typeof window)throw new t("signInWithOAuth requiere un entorno de navegador (window no está disponible)",400);const{redirectTo: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 a((async()=>{try{if(this.h){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.h}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.K("signOut called")}})())}async logout(){return this.signOut()}async resetPasswordForEmail(t,e){return a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n={email:t};e?.redirectTo&&(n.redirect_to=e.redirectTo);const o=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok)throw await s(o,"Recovery failed");return await o.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return a((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 s(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.S)return n("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.S.promise;const e=new o;this.S=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const 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()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this.O());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.S=null}})(),e.promise}async O(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,s=Math.floor(Date.now()/1e3);if(t.access_token!==this.h&&i-s>60)return n("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.h=t.access_token,this.l=t.refresh_token,this.u=t.expires_at,this.o=t.user,this.m=0,this.Y("TOKEN_REFRESHED"),{data:{token:this.h,refreshToken:this.l||void 0,expiresAt:this.u||void 0,user:this.o},error:null}}if(!this.l)throw new t("No hay refresh token disponible",401);n("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,s=new AbortController,o=setTimeout(()=>s.abort(),c.REFRESH_TIMEOUT_MS);let r;try{r=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify({refresh_token:this.l}),signal:s.signal})}finally{clearTimeout(o)}if(!r.ok){let e={};try{e=await r.json()}catch{}const i=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 i>=400&&i<500&&a?(n("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.K(`refreshSession failed (${i}: ${s})`)):n("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const a=await r.json(),h={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this._(a)};return this.m=0,await this.$(h),n("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.m++,this.I=Date.now();const i=e instanceof DOMException&&"AbortError"===e.name,s=i?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(c.RETRY_BASE_MS*Math.pow(2,this.m),c.MAX_RETRY_MS);return i?n("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.m})`):n("error","[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${o}ms (intento ${this.m})`),this.m>=c.MAX_REFRESH_FAILURES&&(n("error",`[DYPAI SDK] 🗑️ ${this.m} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.K(`${this.m} consecutive refresh failures`),this.m=0),{data:null,error:s}}}async signInWithOtp(e){return a((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 a((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._(s)};return o.token&&await this.$(o),o})())}async updateUser(e){return a((async()=>{if(!this.h)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.h}`,...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._(s);return this.T(o),o})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),n("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.N(),this.D=setInterval(()=>this.N(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.D&&(n("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.D),this.D=null)}async N(){try{const t=Math.floor(Date.now()/1e3),e=this.u||0;if(!this.l||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.m>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.m),c.MAX_RETRY_MS);if(Date.now()-this.I<t)return}n("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){n("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}_(t){return t?e(t):(n("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async $(t){t.token&&(this.h=t.token,this.l=t.refreshToken||null,this.u=t.expiresAt||null,await this.T(t.user,t.token,t.refreshToken,t.expiresAt),n("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.u}, has_refresh: ${!!this.l})`),this.startAutoRefresh())}async T(t,e,i,s){this.o=t,e&&(this.h=e),void 0!==i&&(this.l=i||null),void 0!==s&&(this.u=s||null);try{if(this.h&&this.o){const t={access_token:this.h,refresh_token:this.l,expires_at:this.u,user:this.o};await this.storage.setItem(this.STORAGE_KEY,JSON.stringify(t))}else n("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){n("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const o=e?"SIGNED_IN":"USER_UPDATED";this.Y(o)}async K(t="unknown"){n("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.h=null,this.l=null,this.o=null,this.u=null,this.stopAutoRefresh();try{await this.storage.removeItem(this.STORAGE_KEY),n("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){n("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.Y("SIGNED_OUT")}async A(t=!0){n("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.U())return void n("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;n("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.h=t.access_token,this.l="string"==typeof t.refresh_token?t.refresh_token:null,this.u="number"==typeof t.expires_at?t.expires_at:null,this.o=t.user&&"object"==typeof t.user?t.user:null}else{n("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}`),i=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!i)return void n("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let s;n("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{s=JSON.parse(i)}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.h=e,this.o=s;const o=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),r=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.l=o,this.u=r?parseInt(r,10):null,await this.T(s,this.h||void 0,this.l||void 0,this.u||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){}n("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const i=Math.floor(Date.now()/1e3),s=this.u&&this.u<=i;if(n("log",`[DYPAI SDK] 🕒 Token expira en: ${this.u}. Ahora es: ${i}. Diferencia: ${(this.u||0)-i}s`),s&&this.l&&t){n("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void n("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.u&&n("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.Y("SIGNED_IN")}catch(t){n("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),i=e.get("access_token");if(!i)return!1;const s=e.get("refresh_token"),o=e.get("type"),r=parseInt(e.get("expires_in")||"3600",10);n("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.h=i,this.l=s||null,this.u=Math.floor(Date.now()/1e3)+r;const a=this.config.baseUrl||"http://localhost:8000",c=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return n("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${c.status}). Limpiando...`),this.h=null,this.l=null,this.u=null,!1;const h=await c.json(),l=this._(h),u={token:i,refreshToken:s||void 0,expiresIn:r,expiresAt:this.u,user:l};return await this.$(u),"recovery"===o||"invite"===o?(n("log",`[DYPAI SDK] 🔑 ${o||"recovery"} callback. Emitiendo PASSWORD_RECOVERY...`),this.Y("PASSWORD_RECOVERY")):(n("log",`[DYPAI SDK] ✅ Sesión establecida desde callback URL (type: ${o||"signup"}). Emitiendo SIGNED_IN...`),this.Y("SIGNED_IN")),!0}catch(t){return n("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}k(){return this.h&&this.o?{access_token:this.h,refresh_token:this.l||void 0,expires_at:this.u||void 0,token_type:"bearer",user:this.o}:null}Y(t="USER_UPDATED"){const e=this.k();n("log",`[DYPAI SDK] 📢 Notificando a ${this.p.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.p.forEach(i=>i(t,e))}handleSessionExpired(){this.K("handleSessionExpired called (likely 401 from API)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class h{constructor(t){this.api=t}from(t){return new l(t,this.api)}}class l{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 u{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const n=await this.api.put(`admin/users/${t}`,i);return n.data&&(n.data=e(n.data)),n}async delete(t){return this.api.delete(`admin/users/${t}`)}}let d=null;function p(t){d=t}const f=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 w(){const t=d||f;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 y=t=>(d||f)(t),g=(t,e)=>y({title:t,description:e,variant:"success"}),S=(t,e)=>y({title:t,description:e,variant:"error"}),D=(t,e)=>y({title:t,description:e,variant:"warning"}),m=(t,e)=>y({title:t,description:e,variant:"info"});let I={},v=null;function A(t){v=t}function P(t){I={...I,...t}}const k=new Map;function _(){let t=I;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...I}}catch(t){}return t}async function $(e,i,n,s,o,r,a,c,h){const l=_(),u=c||null;if(!u&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let d;if(n.startsWith("http"))d=n;else{const t=u.replace(/\/+$/,"");d=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&&(d+=`?${i}`)}const p="GET"===i?`${i}:${d}:${JSON.stringify(s)}`:null;if(p&&k.has(p))return k.get(p);const f=s instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},g={method:i,headers:w,credentials:"include"};s&&"GET"!==i&&"DELETE"!==i&&(g.body=f?s:JSON.stringify(s));const S=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!S)throw new Error("Fetch no disponible.");const D=(async()=>{const e=_().toast||y,u=(void 0!==o?o:!1!==l.showToasts)&&e;try{const p=await S(d,g);if(!p.ok){let d,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);d=e,f=e.message||e.msg||e.error_description||e.error||f}catch{t.length<200&&(f=t)}}catch{}if(401===p.status&&!h&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return $(t,i,n,s,o,r,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw u&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,d)}!u||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const f=p.headers.get("content-type")||"";return f.includes("application/pdf")||f.includes("image/")||f.includes("audio/")||f.includes("video/")||f.includes("application/octet-stream")||f.includes("application/zip")||f.includes("application/vnd.openxmlformats-officedocument")?await p.blob():f.includes("application/json")?await p.json():await p.text()}finally{p&&k.delete(p)}})();return p&&k.set(p,D),D}function E(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,i){const n="POST"===i||"PUT"===i||"PATCH"===i;return async(s,o,r)=>{const a=e();let c,h={};n?(c=o,h=r||{}):(h=o||{},c=void 0);const l=h.token||a.token||(v?v():"")||"",u=h.apiKey||a.apiKey;try{return{data:await $(l,i,s,c,h.showToasts,h.params,u,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function b(t){const e=()=>"function"==typeof t?t():t;return{get:K(e,"GET"),post:K(e,"POST"),put:K(e,"PUT"),patch:K(e,"PATCH"),delete:K(e,"DELETE")}}function K(e,i){return async(n,s,o)=>{const r=e(),a=await async function(t,e,i){let n,s={};"POST"===t||"PUT"===t||"PATCH"===t?(n=e,s=i||{}):(s=e||{},n=void 0);let o=s.token;return!o&&v&&(o=v()||""),o=o||"",{token:o,apiKey:s.apiKey,body:n,params:s.params,showToasts:s.showToasts}}(i,s,o),c=a.token||r.token||"",h=a.apiKey||r.apiKey;try{return{data:await $(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 O{constructor(t){this.api=t}from(t){return new Y(t,this.api)}}class Y{constructor(t,e){this.bucketName=t,this.api=e}defaultEndpoint(){return`storage_${this.bucketName}`}async upload(e,i,n={}){return N(async()=>{const s=n.endpoint||this.defaultEndpoint(),o=n.clientUpload??!0,r={operation:"upload",file_path:e,client_upload:o,...n.params};if(o)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 E(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 E(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 E(t)}return null}(o);if(!a?.upload_url)throw new t("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;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("Direct upload to cloud storage failed",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 a=n.contentType||i.type||"application/octet-stream",c=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:h,error:l}=await this.api.post(s,{...r,content:c,content_type:a,size_bytes:i.size,confirm:!0});if(l)throw l;return h})}async download(t,e={}){return N(async()=>{const i=e.endpoint||this.defaultEndpoint(),n={operation:"download",bucket:this.bucketName,file_path:t,download:!0,...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 N(async()=>{const n=i.endpoint||this.defaultEndpoint(),s={operation:"download",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}})}async list(t="",e={}){return N(async()=>{const i=e.endpoint||this.defaultEndpoint(),n={operation:"list",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 N(async()=>{const i=e.endpoint||this.defaultEndpoint(),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={operation:"delete",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 N(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 N(async()=>{throw new t("copy() is not supported by the built-in storage node. Create a custom workflow endpoint if needed.",400)})}}async function N(e){try{return{data:await e(),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"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){I={...I,...t}}(t.global),this.auth=new c({baseUrl:e,apiKey:i,storageKey:n,storage:t.auth?.storage,autoRefreshToken:t.auth?.autoRefreshToken,persistSession:t.auth?.persistSession},null);const s={get:T(o=()=>({token:this.auth.token,apiKey:i,baseUrl:e}),"GET"),post:T(o,"POST"),put:T(o,"PUT"),patch:T(o,"PATCH"),delete:T(o,"DELETE")};var o;this.auth.api=s,this.db=new h(s),this.users=new u(s),this.storage=new O(s),this.api=b(()=>({token:this.auth.token||"",apiKey:i,baseUrl:e})),A(()=>this.auth.token),P({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 R(t,e,i){if(!t)throw new Error("createClient() requiere la URL base");return new U({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let x={};function C(t){x={...x,toast:y,...t},j()}function L(){return{...x}}function j(){try{const{configureApiService:t}=require("../services/ApiService");t(x)}catch(t){}}function M(){x={},j()}async function z(){j()}const J={name:"@dypai-ai/client-sdk",version:"0.0.1",description:"Cliente JavaScript para Dypai Engine",features:["API REST autenticada","Smart Storage (Upload/Download delegado)","Gestión de usuarios (Admin)"]};export{U as DypaiClient,J as PACKAGE_INFO,$ as callApi,P as configureApiService,C as configureDypaiServices,b as createApiClient,R as createClient,L as getGlobalConfig,z as reloadDypaiConfig,M as resetGlobalConfig,p as setToastFunction,A as setTokenProvider,y as toast,S as toastError,m as toastInfo,g as toastSuccess,D as toastWarning,w 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"}}function e(t){if(!t)return{};const e=t.user||t,i=e.app_metadata||{},n=e.user_metadata||{};return{id:e.id,email:e.email,phone:e.phone,role:i.role||n.role||e.role||null,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:i,user_metadata:n,roleDetails:{name:i.role||n.role||null,weight:0},appContext:{app_id:"default"}}}let i=!1;function n(t,...e){"error"===t?console.error(...e):i&&("warn"===t?console.warn(...e):console.log(...e))}async function s(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 o{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 a(e){try{return{data:await e,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e.message||"Error de autenticación",e.status||400)}}}class c{constructor(t,e=null){this.config=t,this.t=null,this.i=null,this.o=null,this.h=null,this.l=[],this.u=null,this.p=null,this.S=0,this.D=0,this.storage=t.storage||r,i=!!t.debug;const s=t.storageKey||this.deriveStorageKey(t.apiKey);n("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&&(n("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.I(!1))}))}async v(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(n("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 n("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.l.push(t),this.m.then(()=>{const e=this.A(),i=e?"SIGNED_IN":"INITIAL_SESSION";n("log",`[DYPAI SDK] 📣 Notificando estado inicial a nuevo suscriptor: ${i} (Sesión activa: ${!!e})`),t(i,e)}).catch(e=>{n("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 a((async()=>{const e=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=password`,i={email:t.email||t.identifier||"",password:t.password},n=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(i)});if(!n.ok)throw await s(n,"Login failed");const o=await n.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.T(r),r})())}async login(t){return this.signInWithPassword(t)}async signUp(t,e){return a((async()=>{const i=`${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(i,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!u.ok)throw await s(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.T(f):n("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&&(n("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>n("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 a((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 a((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 a((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 a((async()=>{const i=this.config.baseUrl||"http://localhost:8000",n={email:t};e?.redirectTo&&(n.redirect_to=e.redirectTo);const o=await fetch(`${i}/auth/v1/recover`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)});if(!o.ok)throw await s(o,"Recovery failed");return await o.json()})())}async recoverPassword(t){return this.resetPasswordForEmail(t.email)}async resendConfirmationEmail(t){return a((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 s(i,"Failed to resend confirmation email");return{message:"Confirmation email resent"}})())}async refreshSession(){if(this.u)return n("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.u.promise;const e=new o;this.u=e;const i=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const t=await async function(t,e,i){if("undefined"!=typeof globalThis&&globalThis.navigator?.locks?.request){const 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()}(i,c.LOCK_ACQUIRE_TIMEOUT_MS,()=>this._());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 _(){try{const e=await this.storage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e),i=t.expires_at||0,s=Math.floor(Date.now()/1e3);if(t.access_token!==this.i&&i-s>60)return n("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);n("log","[DYPAI SDK] 🔄 Iniciando refresco de sesión con Refresh Token...");const i=`${this.config.baseUrl||"http://localhost:8000"}/auth/v1/token?grant_type=refresh_token`,s=new AbortController,o=setTimeout(()=>s.abort(),c.REFRESH_TIMEOUT_MS);let r;try{r=await fetch(i,{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 i=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 i>=400&&i<500&&a?(n("error","[DYPAI SDK] ❌ Error DEFINITIVO en refresco (Token inválido/revocado). Limpiando sesión."),this.$(`refreshSession failed (${i}: ${s})`)):n("warn",`[DYPAI SDK] ⚠️ Fallo en refresco (${i}). Posible error de red o servidor temporal. MANTENIENDO SESIÓN.`),new t(e.msg||e.error_description||"Refresh session failed",i)}const a=await r.json(),h={token:a.access_token,refreshToken:a.refresh_token,expiresIn:a.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(a.expires_in||3600),user:this.P(a)};return this.S=0,await this.T(h),n("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 i=e instanceof DOMException&&"AbortError"===e.name,s=i?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(c.RETRY_BASE_MS*Math.pow(2,this.S),c.MAX_RETRY_MS);return i?n("error",`[DYPAI SDK] ⏱️ Timeout en refresh después de ${c.REFRESH_TIMEOUT_MS/1e3}s. Backoff: ${o}ms (intento ${this.S})`):n("error","[DYPAI SDK] ❌ Refresh falló:",s.message,`(status: ${s.status}). Backoff: ${o}ms (intento ${this.S})`),this.S>=c.MAX_REFRESH_FAILURES&&(n("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 a((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 a((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.T(o),o})())}async updateUser(e){return a((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})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),n("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.O(),this.p=setInterval(()=>this.O(),c.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(n("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async O(){try{const t=Math.floor(Date.now()/1e3),e=this.h||0;if(!this.o||!e)return;if(1e3*(e-t)>c.AUTO_REFRESH_TICK_THRESHOLD*c.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.S>0){const t=Math.min(c.RETRY_BASE_MS*Math.pow(2,this.S),c.MAX_RETRY_MS);if(Date.now()-this.D<t)return}n("log",`[DYPAI SDK] ⏱️ Auto-refresh tick: token expira en ${e-t}s. Refrescando...`),await this.refreshSession()}catch(t){n("error","[DYPAI SDK] ❌ Error en auto-refresh tick:",t)}}P(t){return t?e(t):(n("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async T(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),n("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.h}, has_refresh: ${!!this.o})`),this.startAutoRefresh())}async k(t,e,i,s){this.t=t,e&&(this.i=e),void 0!==i&&(this.o=i||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 n("warn","[DYPAI SDK] ⚠️ _updateUser: No se guardó sesión porque falta token o user.")}catch(t){n("error","[DYPAI SDK] ❌ Error guardando sesión en storage:",t)}const o=e?"SIGNED_IN":"USER_UPDATED";this.K(o)}async $(t="unknown"){n("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),n("log","[DYPAI SDK] ✅ Storage limpiado con éxito.")}catch(t){n("error","[DYPAI SDK] ❌ Error eliminando sesión de storage:",t)}this.K("SIGNED_OUT")}async I(t=!0){n("log","[DYPAI SDK] 🔍 Iniciando recuperación de sesión...");try{if(await this.Y())return void n("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;n("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{n("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}`),i=await this.storage.getItem(`dypai-auth-user-${t}`);if(!e||!i)return void n("log","[DYPAI SDK] ℹ️ No se encontró ninguna sesión (storage vacío).");{let s;n("log","[DYPAI SDK] 🚚 Migración: Datos antiguos detectados. Consolidando...");try{s=JSON.parse(i)}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){}n("log","[DYPAI SDK] ✨ Migración completada con éxito.")}}const i=Math.floor(Date.now()/1e3),s=this.h&&this.h<=i;if(n("log",`[DYPAI SDK] 🕒 Token expira en: ${this.h}. Ahora es: ${i}. Diferencia: ${(this.h||0)-i}s`),s&&this.o&&t){n("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void n("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.h&&n("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.K("SIGNED_IN")}catch(t){n("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),i=e.get("access_token");if(!i)return!1;const s=e.get("refresh_token"),o=e.get("type"),r=parseInt(e.get("expires_in")||"3600",10);n("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=i,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 ${i}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!c.ok)return n("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:i,refreshToken:s||void 0,expiresIn:r,expiresAt:this.h,user:l};return await this.T(u),"recovery"===o||"invite"===o?(n("log",`[DYPAI SDK] 🔑 ${o||"recovery"} callback. Emitiendo PASSWORD_RECOVERY...`),this.K("PASSWORD_RECOVERY")):(n("log",`[DYPAI SDK] ✅ Sesión establecida desde callback URL (type: ${o||"signup"}). Emitiendo SIGNED_IN...`),this.K("SIGNED_IN")),!0}catch(t){return n("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();n("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)")}}c.REFRESH_TIMEOUT_MS=15e3,c.MAX_REFRESH_FAILURES=5,c.AUTO_REFRESH_TICK_DURATION_MS=3e4,c.AUTO_REFRESH_TICK_THRESHOLD=3,c.RETRY_BASE_MS=200,c.MAX_RETRY_MS=3e4,c.LOCK_ACQUIRE_TIMEOUT_MS=5e3;class h{constructor(t){this.api=t}from(t){return new l(t,this.api)}}class l{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 u{constructor(t){this.api=t}async list(t={}){const i=await this.api.get("admin/users",{params:t});return i.data?.users&&(i.data.users=i.data.users.map(e)),i}async create(t){const i=await this.api.post("admin/users",t);return i.data&&(i.data=e(i.data)),i}async update(t,i){const n=await this.api.put(`admin/users/${t}`,i);return n.data&&(n.data=e(n.data)),n}async delete(t){return this.api.delete(`admin/users/${t}`)}}let d=null;function p(t){d=t}const f=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 w(){const t=d||f;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 y=t=>(d||f)(t),S=(t,e)=>y({title:t,description:e,variant:"success"}),D=(t,e)=>y({title:t,description:e,variant:"error"}),g=(t,e)=>y({title:t,description:e,variant:"warning"}),m=(t,e)=>y({title:t,description:e,variant:"info"});let I={},v=null;function A(t){v=t}function P(t){I={...I,...t}}const T=new Map;function k(){let t=I;try{const{getGlobalConfig:e}=require("../config/global-config");t={...e(),...I}}catch(t){}return t}async function E(e,i,n,s,o,r,a,c,h){const l=k(),u=c||null;if(!u&&!n.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof n||!n.trim())throw new Error("Endpoint debe ser un string válido");let d;if(n.startsWith("http"))d=n;else{const t=u.replace(/\/+$/,"");d=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&&(d+=`?${i}`)}const p="GET"===i?`${i}:${d}:${JSON.stringify(s)}`:null;if(p&&T.has(p))return T.get(p);const f=s instanceof FormData,w={...l.headers||{},...f?{}:{"Content-Type":"application/json"},...e&&{Authorization:`Bearer ${e}`},...a&&{"x-api-key":a}},S={method:i,headers:w,credentials:"include"};s&&"GET"!==i&&"DELETE"!==i&&(S.body=f?s:JSON.stringify(s));const D=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!D)throw new Error("Fetch no disponible.");const g=(async()=>{const e=k().toast||y,u=(void 0!==o?o:!1!==l.showToasts)&&e;try{const p=await D(d,S);if(!p.ok){let d,f="Error en la petición";try{const t=await p.text();try{const e=JSON.parse(t);d=e,f=e.message||e.msg||e.error_description||e.error||f}catch{t.length<200&&(f=t)}}catch{}if(401===p.status&&!h&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return E(t,i,n,s,o,r,a,c,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw u&&e({title:"Error",description:f,variant:"error"}),new t(f,p.status,void 0,d)}!u||"POST"!==i&&"PUT"!==i&&"PATCH"!==i&&"DELETE"!==i||e({title:"Éxito",description:"Operación completada",variant:"success"});const f=p.headers.get("content-type")||"";return f.includes("application/pdf")||f.includes("image/")||f.includes("audio/")||f.includes("video/")||f.includes("application/octet-stream")||f.includes("application/zip")||f.includes("application/vnd.openxmlformats-officedocument")?await p.blob():f.includes("application/json")?await p.json():await p.text()}finally{p&&T.delete(p)}})();return p&&T.set(p,g),g}async function $(t,e,i,n){const s=n?.method||(i?"POST":"GET"),{data:o,error:r}=await("GET"===s?t.get(e,{params:n?.params}):t.post(e,i,{params:n?.params}));if(r)throw r;if(o instanceof Blob){const t=window.URL.createObjectURL(o),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(o&&"object"==typeof o&&("url"in o||"signed_url"in o||"signedUrl"in o)){const t=o.url||o.signed_url||o.signedUrl;if("string"==typeof t){const e=new URL(t).searchParams.get("response-content-disposition");let i;if(e){const t=decodeURIComponent(e).match(/filename\*?=(?:UTF-8''|")?([^";]+)/i);t?.[1]&&(i=t[1].replace(/"/g,""))}const s=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")}}}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("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:c,method:h="PUT",headers:l={},file_path:u,storage_path:d}=a;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("Direct upload to cloud storage failed",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}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 K(e,i){const n="POST"===i||"PUT"===i||"PATCH"===i;return async(s,o,r)=>{const a=e();let c,h={};n?(c=o,h=r||{}):(h=o||{},c=void 0);const l=h.token||a.token||(v?v():"")||"",u=h.apiKey||a.apiKey;try{return{data:await E(l,i,s,c,h.showToasts,h.params,u,a.baseUrl),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Error desconocido",e?.status??500)}}}}function O(e){const i=()=>"function"==typeof e?e():e,n={get:Y(i,"GET"),post:Y(i,"POST"),put:Y(i,"PUT"),patch:Y(i,"PATCH"),delete:Y(i,"DELETE"),upload:async(e,i,s)=>{try{return{data:await _(n,e,i,s),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,i,s)=>{try{return await $(n,e,i,{...s,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return n}function Y(e,i){return async(n,s,o)=>{const r=e(),a=await async function(t,e,i){let n,s={};"POST"===t||"PUT"===t||"PATCH"===t?(n=e,s=i||{}):(s=e||{},n=void 0);let o=s.token;return!o&&v&&(o=v()||""),o=o||"",{token:o,apiKey:s.apiKey,body:n,params:s.params,showToasts:s.showToasts}}(i,s,o),c=a.token||r.token||"",h=a.apiKey||r.apiKey;try{return{data:await E(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 N{constructor(e){const{baseUrl:i,apiKey:n}=e,s=e.auth?.storageKey||e.storageKey;e.global&&function(t){I={...I,...t}}(e.global),this.auth=new c({baseUrl:i,apiKey:n,storageKey:s,storage:e.auth?.storage,autoRefreshToken:e.auth?.autoRefreshToken,persistSession:e.auth?.persistSession},null);const o=function(e){const i={get:K(e,"GET"),post:K(e,"POST"),put:K(e,"PUT"),patch:K(e,"PATCH"),delete:K(e,"DELETE"),upload:async(e,n,s)=>{try{return{data:await _(i,e,n,s),error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Upload failed",e?.status??500)}}},download:async(e,n,s)=>{try{return await $(i,e,n,{...s,method:"POST"}),{data:void 0,error:null}}catch(e){return{data:null,error:e instanceof t?e:new t(e?.message??"Download failed",e?.status??500)}}}};return i}(()=>({token:this.auth.token,apiKey:n,baseUrl:i}));this.auth.api=o,this.db=new h(o),this.users=new u(o),this.api=O(()=>({token:this.auth.token||"",apiKey:n,baseUrl:i})),A(()=>this.auth.token),P({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)throw new Error("createClient() requiere la URL base");return new N({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?i:e})}let R={};function x(t){R={...R,toast:y,...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","Gestión de usuarios (Admin)","Storage via endpoints personalizados"]};export{N as DypaiClient,z as PACKAGE_INFO,E as callApi,P as configureApiService,x as configureDypaiServices,O as createApiClient,U as createClient,C as getGlobalConfig,M as reloadDypaiConfig,j as resetGlobalConfig,p as setToastFunction,A as setTokenProvider,y as toast,D as toastError,m as toastInfo,S as toastSuccess,g as toastWarning,w 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"}}function normalizeUser(t){if(!t)return{};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,t:{name:o.role||"authenticated",weight:0},o:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function 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.i=null,this.l=null,this.h=null,this.u=null,this.p=[],this.D=null,this.S=null,this.m=0,this.A=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._=this.v().then(()=>{this.l&&(this.getUser().catch(()=>{}),this.startAutoRefresh())}),"undefined"!=typeof window&&(window.addEventListener("visibilitychange",this.P.bind(this)),window.addEventListener("focus",this.P.bind(this)),window.addEventListener("storage",t=>{t.key===this.STORAGE_KEY&&(_log("log","[DYPAI SDK] 🔄 Sesión actualizada en otra pestaña. Sincronizando..."),this.v(!1))}))}async P(){"undefined"!=typeof document&&"visible"===document.visibilityState&&(_log("log","[DYPAI SDK] 👁️ Ventana visible. Sincronizando estado y reiniciando auto-refresh..."),await this.v(!0),this.l&&this.startAutoRefresh())}deriveStorageKey(t){return t?t.substring(0,8):"default"}get user(){return this.i}get token(){return this.l}isLoggedIn(){return!(!this.l||!this.i)}onAuthStateChange(t){return _log("log","[DYPAI SDK] 👂 Nuevo suscriptor añadido a onAuthStateChange"),this.p.push(t),this._.then(()=>{const e=this.I(),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.p=this.p.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.T(i)};return await this.k(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.T(h),confirmationRequired:u};return d.token?await this.k(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._,!this.l||!this.i)return{data:null,error:null};const t=Math.floor(Date.now()/1e3);return(this.u||0)-t<30&&this.h&&(_log("log","[DYPAI SDK] ⏳ getSession: Token próximo a expirar. Refrescando..."),await this.refreshSession().catch(t=>_log("warn","[DYPAI SDK] Error refreshing session in getSession:",t))),{data:{access_token:this.l,refresh_token:this.h||void 0,token_type:"bearer",user:this.i},error:null}}catch(t){return{data:null,error:new DypaiError(t.message,500)}}}async getUser(){return wrapAuthResponse((async()=>{if(!this.l)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.l}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!e.ok)throw new DypaiError("Session invalid",e.status);const o=await e.json(),r=this.T(o);return this.$(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.l){const t=this.config.baseUrl||"http://localhost:8000";await fetch(`${t}/auth/v1/logout`,{method:"POST",headers:{Authorization:`Bearer ${this.l}`,...this.config.apiKey&&{apikey:this.config.apiKey}}})}}finally{this.C("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.D)return _log("log","[DYPAI SDK] 🔄 Refresco de sesión ya en curso. Esperando resolución..."),this.D.promise;const t=new Deferred;this.D=t;const e=`lock:${this.STORAGE_KEY}`;return(async()=>{try{const 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.R());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.D=null}})(),t.promise}async R(){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.l&&o-r>60)return _log("log","[DYPAI SDK] 🔄 Otra pestaña ya refrescó el token. Adoptando sesión del storage."),this.l=e.access_token,this.h=e.refresh_token,this.u=e.expires_at,this.i=e.user,this.m=0,this.K("TOKEN_REFRESHED"),{data:{token:this.l,refreshToken:this.h||void 0,expiresAt:this.u||void 0,user:this.i},error:null}}if(!this.h)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.h}),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.C(`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.T(n)};return this.m=0,await this.k(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.m++,this.A=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.m),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.m})`):_log("error","[DYPAI SDK] ❌ Refresh falló:",o.message,`(status: ${o.status}). Backoff: ${r}ms (intento ${this.m})`),this.m>=AuthModule.MAX_REFRESH_FAILURES&&(_log("error",`[DYPAI SDK] 🗑️ ${this.m} fallos consecutivos de refresh. Sesión irrecuperable. Limpiando...`),this.C(`${this.m} consecutive refresh failures`),this.m=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.T(r)};return i.token&&await this.k(i),i})())}async updateUser(t){return wrapAuthResponse((async()=>{if(!this.l)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.l}`,...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.T(r);return this.$(i),i})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.M(),this.S=setInterval(()=>this.M(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.S&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.S),this.S=null)}async M(){try{const t=Math.floor(Date.now()/1e3),e=this.u||0;if(!this.h||!e)return;if(1e3*(e-t)>AuthModule.AUTO_REFRESH_TICK_THRESHOLD*AuthModule.AUTO_REFRESH_TICK_DURATION_MS)return;if(this.m>0){const t=Math.min(AuthModule.RETRY_BASE_MS*Math.pow(2,this.m),AuthModule.MAX_RETRY_MS);if(Date.now()-this.A<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)}}T(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async k(t){t.token&&(this.l=t.token,this.h=t.refreshToken||null,this.u=t.expiresAt||null,await this.$(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.u}, has_refresh: ${!!this.h})`),this.startAutoRefresh())}async $(t,e,o,r){this.i=t,e&&(this.l=e),void 0!==o&&(this.h=o||null),void 0!==r&&(this.u=r||null);try{if(this.l&&this.i){const t={access_token:this.l,refresh_token:this.h,expires_at:this.u,user:this.i};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.K(i)}async C(t="unknown"){_log("log",`[DYPAI SDK] 🧹 Limpiando sesión del estado y storage. Motivo: ${t}`),this.l=null,this.h=null,this.i=null,this.u=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.K("SIGNED_OUT")}async v(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.l=t.access_token,this.h="string"==typeof t.refresh_token?t.refresh_token:null,this.u="number"==typeof t.expires_at?t.expires_at:null,this.i=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.l=e,this.i=r;const i=await this.storage.getItem(`dypai-auth-refresh-token-${t}`),n=await this.storage.getItem(`dypai-auth-expires-at-${t}`);this.h=i,this.u=n?parseInt(n,10):null,await this.$(r,this.l||void 0,this.h||void 0,this.u||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.u&&this.u<=o;if(_log("log",`[DYPAI SDK] 🕒 Token expira en: ${this.u}. Ahora es: ${o}. Diferencia: ${(this.u||0)-o}s`),r&&this.h&&t){_log("log","[DYPAI SDK] ⚠️ El Access Token ha caducado. Intentando refrescar sesión inmediatamente...");const t=await this.refreshSession();if(t.error)return void _log("error","[DYPAI SDK] ❌ El refresco falló:",t.error.message)}else this.u&&_log("log","[DYPAI SDK] ✨ Sesión válida. Iniciando auto-refresh.");this.K("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.l=o,this.h=r||null,this.u=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.l=null,this.h=null,this.u=null,!1;const c=await a.json(),l=this.T(c),h={token:o,refreshToken:r||void 0,expiresIn:n,expiresAt:this.u,user:l};return await this.k(h),"recovery"===i||"invite"===i?(_log("log",`[DYPAI SDK] 🔑 ${i||"recovery"} callback. Emitiendo PASSWORD_RECOVERY...`),this.K("PASSWORD_RECOVERY")):(_log("log",`[DYPAI SDK] ✅ Sesión establecida desde callback URL (type: ${i||"signup"}). Emitiendo SIGNED_IN...`),this.K("SIGNED_IN")),!0}catch(t){return _log("error","[DYPAI SDK] ❌ Error procesando callback URL:",t),!1}}I(){return this.l&&this.i?{access_token:this.l,refresh_token:this.h||void 0,expires_at:this.u||void 0,token_type:"bearer",user:this.i}:null}K(t="USER_UPDATED"){const e=this.I();_log("log",`[DYPAI SDK] 📢 Notificando a ${this.p.length} suscriptores: Evento=${t} (Sesión activa: ${!!e})`),this.p.forEach(o=>o(t,e))}handleSessionExpired(){this.C("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={}){const e=await this.api.get("admin/users",{params:t});return e.data?.users&&(e.data.users=e.data.users.map(normalizeUser)),e}async create(t){const e=await this.api.post("admin/users",t);return e.data&&(e.data=normalizeUser(e.data)),e}async update(t,e){const o=await this.api.put(`admin/users/${t}`,e);return o.data&&(o.data=normalizeUser(o.data)),o}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,c){const l=getCompleteConfig(),h=a||null;if(!h&&!o.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof o||!o.trim())throw new Error("Endpoint debe ser un string válido");let u;if(o.startsWith("http"))u=o;else{const t=h.replace(/\/+$/,"");u=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&&(u+=`?${o}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(r)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=r instanceof FormData,f={...l.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...s&&{"x-api-key":s}},y={method:e,headers:f,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(y.body=p?r:JSON.stringify(r));const w=l.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!w)throw new Error("Fetch no disponible.");const g=(async()=>{const t=getCompleteConfig().toast||toast,h=(void 0!==i?i:!1!==l.showToasts)&&t;try{const d=await w(u,y);if(!d.ok){let u,p="Error en la petición";try{const t=await d.text();try{const e=JSON.parse(t);u=e,p=e.message||e.msg||e.error_description||e.error||p}catch{t.length<200&&(p=t)}}catch{}if(401===d.status&&!c&&l.onTokenExpired)try{const t=await l.onTokenExpired();if(t)return callApi(t,e,o,r,i,n,s,a,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw h&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,d.status,void 0,u)}!h||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const p=d.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await d.blob():p.includes("application/json")?await d.json():await d.text()}finally{d&&pendingRequests.delete(d)}})();return d&&pendingRequests.set(d,g),g}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){const o="POST"===e||"PUT"===e||"PATCH"===e;return async(r,i,n)=>{const s=t();let a,c={};o?(a=i,c=n||{}):(c=i||{},a=void 0);const l=c.token||s.token||(globalTokenProvider?globalTokenProvider():"")||"",h=c.apiKey||s.apiKey;try{return{data:await callApi(l,e,r,a,c.showToasts,c.params,h,s.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t;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,o){let r,i={};"POST"===t||"PUT"===t||"PATCH"===t?(r=e,i=o||{}):(i=e||{},r=void 0);let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:r,params:i.params,showToasts:i.showToasts}}(e,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}defaultEndpoint(){return`storage_${this.bucketName}`}async upload(t,e,o={}){return wrapStorageResponse(async()=>{const r=o.endpoint||this.defaultEndpoint(),i=o.clientUpload??!0,n={operation:"upload",file_path:t,client_upload:i,...o.params};if(i)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("The workflow did not return a valid upload URL (missing storage upload node?)",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("Direct upload to cloud storage failed",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket: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:n,onProgress:o.onProgress});const s=o.contentType||e.type||"application/octet-stream",a=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:c,error:l}=await this.api.post(r,{...n,content:a,content_type:s,size_bytes:e.size,confirm:!0});if(l)throw l;return c})}async download(t,e={}){return wrapStorageResponse(async()=>{const o=e.endpoint||this.defaultEndpoint(),r={operation:"download",bucket:this.bucketName,file_path:t,download:!0,...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||this.defaultEndpoint(),i={operation:"download",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}})}async list(t="",e={}){return wrapStorageResponse(async()=>{const o=e.endpoint||this.defaultEndpoint(),r={operation:"list",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||this.defaultEndpoint(),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={operation:"delete",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)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?o:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,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"}}function normalizeUser(t){if(!t)return{};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||r.role||e.role||null,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,roleDetails:{name:o.role||r.role||null,weight:0},appContext:{app_id:"default"}}}let _debugEnabled=!1;function _log(t,...e){"error"===t?console.error(...e):_debugEnabled&&("warn"===t?console.warn(...e):console.log(...e))}async function 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.v.bind(this)),window.addEventListener("focus",this.v.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 v(){"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._(),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.P(i)};return await this.T(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:a,...s}=t,l={email:r,phone:i,password:n,data:{...s,...a}};e?.redirectTo&&(l.redirect_to=e.redirectTo);const c=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json",...this.config.apiKey&&{apikey:this.config.apiKey}},body:JSON.stringify(l)});if(!c.ok)throw await parseGoTrueError(c,"Registration failed");const h=await c.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.P(h),confirmationRequired:u};return d.token?await this.T(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.P(o);return this.I(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(" "))}`:"",a=`${i}/auth/v1/authorize?provider=${t}${n}&redirect_to=${encodeURIComponent(o)}`;window.location.href=a})())}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.k("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.k(`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(),a={token:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,expiresAt:Math.floor(Date.now()/1e3)+(n.expires_in||3600),user:this.P(n)};return this.D=0,await this.T(a),_log("log",`[DYPAI SDK] ✅ Refresh exitoso. Nuevo token expira en ${a.expiresAt} (en ${a.expiresIn}s). Refresh token actualizado: ${!!a.refreshToken}`),{data:a,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.k(`${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.P(r)};return i.token&&await this.T(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.P(r);return this.I(i),i})())}async setPassword(t){return this.updateUser({password:t})}startAutoRefresh(){this.stopAutoRefresh(),_log("log","[DYPAI SDK] ⏱️ Auto-refresh iniciado (tick cada 30s)."),this.K(),this.p=setInterval(()=>this.K(),AuthModule.AUTO_REFRESH_TICK_DURATION_MS)}stopAutoRefresh(){this.p&&(_log("log","[DYPAI SDK] ⏹️ Auto-refresh detenido."),clearInterval(this.p),this.p=null)}async K(){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)}}P(t){return t?normalizeUser(t):(_log("warn","[DYPAI SDK] ⚠️ Intentando normalizar un usuario inexistente (data es null/undefined)"),{})}async T(t){t.token&&(this.o=t.token,this.i=t.refreshToken||null,this.l=t.expiresAt||null,await this.I(t.user,t.token,t.refreshToken,t.expiresAt),_log("log",`[DYPAI SDK] 💾 Sesión persistida en storage (expires_at: ${this.l}, has_refresh: ${!!this.i})`),this.startAutoRefresh())}async I(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 k(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.I(r,this.o||void 0,this.i||void 0,this.l||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){}_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 a=this.config.baseUrl||"http://localhost:8000",s=await fetch(`${a}/auth/v1/user`,{headers:{Authorization:`Bearer ${o}`,...this.config.apiKey&&{apikey:this.config.apiKey}}});if(!s.ok)return _log("error",`[DYPAI SDK] ❌ Callback URL: token inválido o expirado (status: ${s.status}). Limpiando...`),this.o=null,this.i=null,this.l=null,!1;const l=await s.json(),c=this.P(l),h={token:o,refreshToken:r||void 0,expiresIn:n,expiresAt:this.l,user:c};return await this.T(h),"recovery"===i||"invite"===i?(_log("log",`[DYPAI SDK] 🔑 ${i||"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}}_(){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._();_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.k("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={}){const e=await this.api.get("admin/users",{params:t});return e.data?.users&&(e.data.users=e.data.users.map(normalizeUser)),e}async create(t){const e=await this.api.post("admin/users",t);return e.data&&(e.data=normalizeUser(e.data)),e}async update(t,e){const o=await this.api.put(`admin/users/${t}`,e);return o.data&&(o.data=normalizeUser(o.data)),o}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,a,s,l){const c=getCompleteConfig(),h=s||null;if(!h&&!o.startsWith("http"))throw new Error("Base URL no definida. Usa createClient(url[, apiKey]).");if("string"!=typeof o||!o.trim())throw new Error("Endpoint debe ser un string válido");let u;if(o.startsWith("http"))u=o;else{const t=h.replace(/\/+$/,"");u=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&&(u+=`?${o}`)}const d="GET"===e?`${e}:${u}:${JSON.stringify(r)}`:null;if(d&&pendingRequests.has(d))return pendingRequests.get(d);const p=r instanceof FormData,f={...c.headers||{},...p?{}:{"Content-Type":"application/json"},...t&&{Authorization:`Bearer ${t}`},...a&&{"x-api-key":a}},y={method:e,headers:f,credentials:"include"};r&&"GET"!==e&&"DELETE"!==e&&(y.body=p?r:JSON.stringify(r));const g=c.fetch||("undefined"!=typeof window?window.fetch.bind(window):fetch);if(!g)throw new Error("Fetch no disponible.");const w=(async()=>{const t=getCompleteConfig().toast||toast,h=(void 0!==i?i:!1!==c.showToasts)&&t;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&&!l&&c.onTokenExpired)try{const t=await c.onTokenExpired();if(t)return callApi(t,e,o,r,i,n,a,s,!0)}catch(t){console.error("[DYPAI SDK] ❌ Error durante el intento de refresco:",t)}throw h&&t({title:"Error",description:p,variant:"error"}),new DypaiError(p,d.status,void 0,u)}!h||"POST"!==e&&"PUT"!==e&&"PATCH"!==e&&"DELETE"!==e||t({title:"Éxito",description:"Operación completada",variant:"success"});const p=d.headers.get("content-type")||"";return p.includes("application/pdf")||p.includes("image/")||p.includes("audio/")||p.includes("video/")||p.includes("application/octet-stream")||p.includes("application/zip")||p.includes("application/vnd.openxmlformats-officedocument")?await d.blob():p.includes("application/json")?await d.json():await d.text()}finally{d&&pendingRequests.delete(d)}})();return d&&pendingRequests.set(d,w),w}async function handleSmartDownload(t,e,o,r){const i=r?.method||(o?"POST":"GET"),{data:n,error:a}=await("GET"===i?t.get(e,{params:r?.params}):t.post(e,o,{params:r?.params}));if(a)throw a;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")}}}async function handleSmartUpload(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 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 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(!a?.upload_url)throw new DypaiError("The workflow did not return a valid upload URL (missing storage upload node?)",400);const{upload_url:s,method:l="PUT",headers:c={},file_path:h,storage_path:u}=a;r?.onProgress&&r.onProgress(10);const d=await fetch(s,{method:l,headers:{"Content-Type":o.type||"application/octet-stream",...c},body:o});if(!d.ok)throw new DypaiError("Direct upload to cloud storage failed",d.status);r?.onProgress&&r.onProgress(90);const p=r?.confirmEndpoint||e,{data:f,error:y}=await t.post(p,{...r?.params,bucket:a.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}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){const o="POST"===e||"PUT"===e||"PATCH"===e;return async(r,i,n)=>{const a=t();let s,l={};o?(s=i,l=n||{}):(l=i||{},s=void 0);const c=l.token||a.token||(globalTokenProvider?globalTokenProvider():"")||"",h=l.apiKey||a.apiKey;try{return{data:await callApi(c,e,r,s,l.showToasts,l.params,h,a.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}function createApiClient(t){const e=()=>"function"==typeof t?t():t,o={get:createMethodFromCtx(e,"GET"),post:createMethodFromCtx(e,"POST"),put:createMethodFromCtx(e,"PUT"),patch:createMethodFromCtx(e,"PATCH"),delete:createMethodFromCtx(e,"DELETE"),upload:async(t,e,r)=>{try{return{data:await handleSmartUpload(o,t,e,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,e,r)=>{try{return await handleSmartDownload(o,t,e,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return o}function createMethodFromCtx(t,e){return async(o,r,i)=>{const n=t(),a=await async function(t,e,o){let r,i={};"POST"===t||"PUT"===t||"PATCH"===t?(r=e,i=o||{}):(i=e||{},r=void 0);let n=i.token;return!n&&globalTokenProvider&&(n=globalTokenProvider()||""),n=n||"",{token:n,apiKey:i.apiKey,body:r,params:i.params,showToasts:i.showToasts}}(e,r,i),s=a.token||n.token||"",l=a.apiKey||n.apiKey;try{return{data:await callApi(s,e,o,a.body,a.showToasts,a.params,l,n.baseUrl),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Error desconocido",t?.status??500)}}}}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=function(t){const e={get:createMethod(t,"GET"),post:createMethod(t,"POST"),put:createMethod(t,"PUT"),patch:createMethod(t,"PATCH"),delete:createMethod(t,"DELETE"),upload:async(t,o,r)=>{try{return{data:await handleSmartUpload(e,t,o,r),error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Upload failed",t?.status??500)}}},download:async(t,o,r)=>{try{return await handleSmartDownload(e,t,o,{...r,method:"POST"}),{data:void 0,error:null}}catch(t){return{data:null,error:t instanceof DypaiError?t:new DypaiError(t?.message??"Download failed",t?.status??500)}}}};return e}(()=>({token:this.auth.token,apiKey:o,baseUrl:e}));this.auth.api=i,this.db=new DataModule(i),this.users=new UsersModule(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","Gestión de usuarios (Admin)","Storage via endpoints personalizados"]},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)throw new Error("createClient() requiere la URL base");return new DypaiClient({baseUrl:t,apiKey:"string"==typeof e?e:void 0,..."string"==typeof e?o:e})},exports.getGlobalConfig=function(){return{...globalConfig}},exports.reloadDypaiConfig=async function(){applyConfigToAllServices()},exports.resetGlobalConfig=function(){globalConfig={},applyConfigToAllServices()},exports.setToastFunction=function(t){customToastFunction=t},exports.setTokenProvider=setTokenProvider,exports.toast=toast,exports.toastError=(t,e)=>toast({title:t,description:e,variant:"error"}),exports.toastInfo=(t,e)=>toast({title:t,description:e,variant:"info"}),exports.toastSuccess=(t,e)=>toast({title:t,description:e,variant:"success"}),exports.toastWarning=(t,e)=>toast({title:t,description:e,variant:"warning"}),exports.useToast=function(){const t=customToastFunction||fallbackToast;return{toast:t,toastSuccess:(e,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.23",
3
+ "version": "0.0.25",
4
4
  "description": "Cliente JavaScript para Dypai Engine",
5
5
  "type": "module",
6
6
  "private": false,