roble 1.12.0 copy "roble: ^1.12.0" to clipboard
roble: ^1.12.0 copied to clipboard

Cliente Flutter para la plataforma ROBLE de Uninorte OpenLab. Autenticación y CRUD sobre bases de datos PostgreSQL.

Changelog #

1.12.0 #

Añadido #

  • Sesión de invitado. Alguien puede escribir antes de tener cuenta, y sin dejar de ser dueño de lo que escribe.

    if (!db.isLoggedIn) await db.signInAnonymously();
    await db.create('carrito', {'producto': id});   // suyo, y de nadie más
    

    Un invitado es un usuario de verdad: tiene userId y cada fila que inserta queda a su nombre, así que isMine() y el alcance own funcionan igual que con una cuenta normal. Lo que no tiene es credenciales.

    db.isAnonymous responde desde el token, sin ir al servidor, para decidir si la pantalla ofrece «guarda tu cuenta».

    Su email es una dirección sintética anon_…@anonymous.invalid que no existe y no puede recibir correo: no la muestres.

    El proyecto tiene que tenerlo habilitado y aplicar propiedad por fila en alguna tabla. Si no, sale RobleAnonymousAuthException y su code dice cuál de las dos cosas falta (ANON_AUTH_DISABLED o ANON_REQUIRES_ROW_OWNERSHIP). Que el servidor se niegue en el segundo caso es a propósito: un invitado sin propiedad por fila escribe filas que puede borrar cualquier otro invitado.

  • Ascender al invitado a una cuenta, conservando lo suyo.

    await db.upgradeAccount(email: email, password: password);
    

    No crea un usuario nuevo: muta el que ya hay. El userId no cambia, así que cada fila que escribió sigue siendo suya sin mover un dato. Si el correo ya tiene cuenta, sale RobleAnonUpgradeEmailTakenException y no se toca nada: Roble no fusiona dos cuentas a escondidas, porque así es como se pierden datos.

    linkIdentity(provider: 'google') hace lo mismo con un proveedor: devuelve la url a la que mandar a la persona, y al volver la identidad queda unida a esta cuenta en vez de crear otra.

  • Clave publicable: escribir sin sesión ninguna.

    final buzon = RobleApiDataBase(
      config: RobleApiConfig.fromContract(
        baseUrl: baseUrl,
        contractId: contractId,
        anonKey: 'roble_anon_…',
      ),
    );
    await buzon.create('sugerencias', {'texto': texto});
    

    Para un formulario de contacto, un buzón, una encuesta: gente que no se va a registrar. La clave va dentro de la app y es pública por diseño — quien desensamble el binario la va a ver, y eso no es una filtración. Lo que la hace segura es que no puede leer nada.

    Un cliente en este modo sólo inserta, y sólo en las tablas que el proyecto marque. Todo lo demás falla en el cliente con RobleAnonKeyScopeException, sin salir a la red, para que el error aparezca en la línea que lo causó en vez de llegar como un 401 que parece una sesión caducada. db.isAnonKeyMode lo dice, para no ofrecer en pantalla lo que no se va a poder hacer.

    Las filas que escribe no tienen dueño. Nadie con alcance own podrá editarlas ni borrarlas después, así que no sirve para datos que la persona tenga que poder volver a tocar: para eso está la sesión de invitado.

    Pasar un roble_pat_ como anonKey falla al construir la configuración: ese es un secreto de servidor y no va en una app.

  • RobleUser.isAnonymous.

Corregido #

  • Los errores del servidor que traen code en el cuerpo ahora lo conservan. Antes se perdía, y dos 409 que se arreglan en sitios distintos llegaban indistinguibles.

1.11.0 #

Añadido #

  • Roles y permisos por tabla. Un rol puede leer una tabla y no poder borrarla: los permisos dejaron de ser de todo el proyecto y pasaron a ser por tabla y por acción. Cuando el rol no alcanza, la llamada responde 403 y ahora sale con su propio tipo.

    try {
      await db.delete('Product', id);
    } on RobleApiForbiddenException {
      mostrar('Tu cuenta no puede borrar');
    }
    

    Con esto también salen tipados los rechazos de la política de envío de notificaciones (NOTIFICATIONS_SEND_FORBIDDEN, NOTIFICATIONS_CONSOLE_ONLY), que ya respondían 403 y llegaban como un error HTTP cualquiera.

    RobleRole trae los roles que crea un proyecto nuevo —admin, user, editor y editor_own—. editor_own es el que suele hacer falta: cada quien edita lo suyo, sin que promoverlo le deje vaciar la tabla. El rol de la sesión viene en RobleUser.role.

  • _owner: de quién es cada fila. Las tablas nuevas traen una columna con el usuario que insertó el registro. La sella el servidor con el token, así que no se puede falsear.

    final mias = (await db.read('Product')).where(db.isMine).toList();
    

    isMine(fila) y currentUserId responden sin ir al servidor: salen del token que ya está en memoria. Es para la pantalla —quien decide qué puedes tocar es el servidor—. También está robleOwnerOf(fila) y la constante robleOwnerColumn.

    Una tabla puede ocultar la columna desde la consola, que es lo que quiere una tabla anónima: ahí no viene en las lecturas, filtrar por ella da error, e isMine responde false porque no hay forma de saberlo.

  • RobleApiNotFoundException para el 404. Con la propiedad activada en una tabla, tocar la fila de otra persona responde lo mismo que si no existiera. Es a propósito: si respondiera distinto, probar identificadores diría cuáles existen y de quién son.

  • REALTIME_FORBIDDEN sale como RobleApiForbiddenException. Suscribirse a una colección que el rol no puede leer parecía un problema de sesión, y volver a entrar no lo arreglaba nunca.

    Mismas funciones que el paquete de JS (3.10.0).

Cambiado #

  • _owner se quita de lo que envías, igual que ya pasaba con _id: el servidor lo asigna él y rechaza el que mandes. Sin esto, leer una fila, cambiarle un campo y volver a escribirla acababa en un 400.

Ojo #

  • Borrar dos veces el mismo _id ya no responde 200, responde 404. Antes un borrado que no encontraba nada decía que sí; con la propiedad por fila eso significaba que borrar la fila de otra persona reportaba éxito sin borrar nada. Si tu app reintenta borrados, trata ese 404 como éxito.

  • json.remove('mensajes') —la colección entera— es cosa de administradores. A un usuario normal le responde 403. Borra las ramas concretas (json.remove('mensajes/$id')) y no hace falta ningún rol.

1.10.0 #

Añadido #

  • db.notifications: avisos que se guardan y llegan al momento. Función aparte del árbol JSON: no hay colección que crear ni ruta que elegir, el destinatario es un usuario del proyecto y cada uno lleva su propio estado de leído.

    db.notifications.watch().listen((e) => mostrar(e.notification.title));
    
    await db.notifications.send(to: otroUsuarioId, title: 'Te toca');
    

    send(to: robleNotificationEveryone) va a todo el proyecto, y que una persona la lea no la marca para las demás.

    list(), unreadCount(), markRead(), markAllRead() y remove() para lo que ya estaba ahí —el stream solo trae lo que llegue a partir de ahora—, y notifications.unreadCountChanges para el globito, que el servidor manda al conectar sin que haya que pedirlo.

    Va por su propio socket, contra el namespace /notifications: una app puede usar notificaciones sin usar tiempo real, y al revés. Cerrar sesión lo cierra, igual que ya hacía con el de tiempo real.

    Mismas funciones que db.notifications del paquete de JS (3.9.0).

  • Canales: avisar a un grupo. Cuando el aviso no es para una persona ni para todo el proyecto —los de un curso, los que siguen un tema—, se envía una vez y lo reciben quienes estén dentro.

    await db.notifications.subscribe('curso-101');
    await db.notifications.send(channel: 'curso-101', title: 'Examen el viernes');
    

    Un canal existe con solo usarlo. Desde la consola se elige quién puede entrar: cualquiera, ciertos roles, o solo el servidor —para una matrícula, que no la decide el estudiante—. Al entrar no recibes lo anterior, y salirte siempre puedes.

    send y schedule pasan a aceptar to o channel; mandar los dos es un ArgumentError, porque quien estuviera en las dos listas la recibiría dos veces.

  • schedule(...): enviar más tarde, o todos los días. Lo manda el servidor cuando llegue la hora, aunque nadie tenga la app abierta.

    await db.notifications.schedule(
      to: usuarioId,
      title: 'Tu cita es en una hora',
      at: DateTime.now().add(const Duration(hours: 1)),
    );
    

    Con RobleRepeat.daily o weekly se repite, que es como se hace un «buenos días» sin montar un cron. La fecha se convierte a UTC antes de enviarla, así que una hora local no acaba programada para la hora equivocada. scheduled() lista los tuyos y cancelScheduled(id) cancela uno pendiente.

  • registerDevice(token, platform): notificaciones con la app cerrada. El token lo da firebase_messaging, que va en tu app y no en este paquete: así una app que no quiera push no carga Firebase.

    final token = await FirebaseMessaging.instance.getToken();
    if (token != null) {
      await db.notifications.registerDevice(token, RobleDevicePlatform.android);
    }
    

    Hacen falta tus credenciales de Firebase, subidas en la consola de Roble: un token de FCM está atado al proyecto de Firebase con el que registraste la app, así que Roble no puede enviarle push con las suyas.

    unregisterDevice(token) antes de cerrar sesión; si no, ese aparato sigue recibiendo los avisos de esa cuenta.

1.9.0 #

Añadido #

  • RobleUser: el perfil con tipos. Lo mismo que devuelve currentUser(), pero convertido: userId, email, name, role, extra y las fechas ya como DateTime.

    Está aquí y no en cada app porque el Map viene siempre igual: si cada proyecto lo convierte por su cuenta, cada proyecto se equivoca por su cuenta con los campos que pueden faltar —role no existía antes de la v1.7.8 del backend— y con los nombres que el servidor cambió por el camino. Lo que el paquete todavía no conozca sigue estando en raw.

    Es el mismo tipo que el paquete de JS ya tenía.

Cambiado #

  • RobleAuthState.user pasa de Map<String, dynamic>? a RobleUser?. Quien escuche authStateChanges recibe el perfil ya convertido, así que una app no necesita traducirlo en su capa de datos para no meter mapas sueltos en la interfaz.

    currentUser() sigue devolviendo el Map tal cual, así que nada de lo que ya funcionaba deja de hacerlo.

1.8.0 #

Añadido #

  • db.authStateChanges: la sesión como un flujo. Emite al entrar, al recuperar una sesión guardada, al salir y cuando se cae sola. Quien se suscribe recibe primero el estado actual, así que una pantalla puede pintarse desde aquí sin preguntar nada aparte.

    StreamBuilder<RobleAuthState>(
      stream: db.authStateChanges,
      builder: (_, snap) =>
          snap.data?.isSignedIn ?? false ? const Inicio() : const Login(),
    );
    

    Cada estado dice por qué cambió (RobleAuthReason), que es lo que un User? a secas no cuenta: signedOut y expired dejan los dos sin sesión, pero solo uno merece un «tu sesión caducó». restored se distingue de signedIn porque recuperar una sesión guardada no es que alguien acabe de entrar.

    db.authState da el estado de ahora mismo sin esperar al siguiente cambio.

Cambiado #

  • onSessionExpired pasa a ser un filtro de authStateChanges, no otro mecanismo. Mismo comportamiento que en 1.7.0 —avisa una sola vez por caída, se rearma al entrar, calla en logout()—, y sigue sin repetir el estado actual al suscribirse: es un aviso de lo que pase a partir de ahora, no algo que se reparta a quien llega tarde.

  • restoreSession() pide el perfil al comprobar que la sesión sigue viva, para poder emitirlo con el estado. Una app que ya lo pedía por su cuenta al arrancar puede dejar de hacerlo.

1.7.0 #

Añadido #

  • db.onSessionExpired: aviso cuando la sesión se cae sola. Emite cuando el servidor rechaza el access token y el refresh token tampoco vale, que es el punto en el que ya no hay forma de seguir.

    db.onSessionExpired.listen((_) => irALogin());
    

    Antes esto solo se podía deducir cazando RobleApiAuthException en la app, y únicamente si alguien hacía una llamada y la capturaba en el sitio correcto: una sesión caducada se quedaba enseñando el mensaje de error en cada pantalla mientras la app seguía creyéndose dentro. El paquete es quien primero lo sabe, porque es el código al que le acaba de fallar el refresco.

    La sesión ya está descartada cuando emite —isLoggedIn es false—, así que quien escuche solo tiene que llevar a la persona de vuelta a la entrada. Emite una sola vez por sesión caída, aunque fallen a la vez varias llamadas, y no emite en logout(): cerrar sesión a propósito no es que se te caiga.

1.6.0 #

Añadido #

  • db.files: archivos en el bucket del proyecto. upload, list, getDownloadUrl, download y remove.

    Los bytes no pasan por Roble: el paquete pide una URL firmada y sube o baja directo contra el bucket S3 del proyecto. Por eso no hay límite de tamaño impuesto por el paquete —el que manda es el del bucket— y el archivo no consume el ancho de banda del servidor.

    final fileId = await db.files.upload(fileName: 'foto.png', data: bytes);
    final bytes = await db.files.download(fileId);
    

    Si el PUT al bucket falla, el archivo queda registrado como PENDING y no aparece en list: no hay fichas apuntando a algo que no llegó.

    El proyecto necesita un bucket conectado desde la consola, en Configuración → Almacenamiento. Sin él, el servidor responde diciendo eso mismo y dónde hacerlo.

    Requiere app-roble v1.9.1 o superior y db-service-roble v1.8.0 o superior.

1.5.1 #

Documentación #

  • El aviso de obsolescencia de watchTable y watchRecord decía «se retira en 2.0.0». Este repositorio ya usó y descartó esa numeración —el commit fffd13c, «Release 1.4.0, not 2.2.0», la bajó de vuelta—, y quedaba un tag v2.0.0 colgando de trabajo que nunca se publicó. Citar ese número invitaba a confundir una cosa con la otra, así que ahora dice «una versión mayor futura». El tag se borró.

1.5.0 #

Cambiado #

  • El tiempo real escucha colecciones del árbol JSON, no tablas SQL. El servidor dejó de replicar tablas: emitía a cualquiera con sesión sin pasar por los permisos por rol, y compartía espacio de nombres con las colecciones, así que una tabla homónima se entregaba a quien escuchaba la colección. Requiere realtime v0.10.1.

    El cambio es del servidor, así que quedarse en 1.4.0 no lo evita: quien no actualice tendrá watchTable fallando igual, pero sin el aviso del analizador.

Obsoleto #

  • watchTable y watchRecord. El servidor rechaza esas suscripciones con REALTIME_UNKNOWN_COLLECTION y ya no entregan nada. Usa db.json.watch sobre la colección correspondiente. Se retiran en una versión mayor futura.

    No se borran ahora: dejarlas hace que el error del servidor llegue por el stream explicando qué usar, mientras que quitarlas rompería la compilación sin decir por dónde seguir.

1.4.0 #

Todo lo que sigue es aditivo respecto a 1.3.0: no desaparece ningún método ni cambia lo que devuelve ninguno. Actualizar no debería obligarte a tocar nada.

Añadido #

Inicio de sesión social

  • signInWithGoogle(): una sola llamada. Usa el SDK nativo donde lo haya y la ventana de navegador donde no; el paquete elige solo.
  • signInWithProvider(provider): el flujo de ventana, con PKCE por dentro.
  • signInWithIdToken(...): canjea un token que ya obtuvo un SDK nativo.
  • listProviders() y providerClientId(nombre): los proveedores configurados, para pintar solo los botones que funcionan y para que la app no lleve una segunda copia del Client ID.
  • startSocialLogin() + exchangeSocialCode() e isSocialCallback(), para quien quiera conducir el flujo a mano.
  • robleNativeOpener(esquema): abre el navegador del sistema en móvil.
  • RobleApiConflictException: el 409 de cuando el correo ya tiene cuenta con otro proveedor.
  • RoblePkce y RobleApiDataBase.newNonce(), sueltos.

Tiempo real

  • watchTable(tabla) y watchRecord(tabla, id): un Stream<RobleChange> con cada fila insertada, modificada o borrada. La suscripción se pide cuando alguien empieza a escuchar y se cancela sola al cerrar el StreamSubscription.
  • Filtros y selección de operaciones que evalúa el servidor, así que lo que no interesa ni viaja.
  • Políticas: realtimePolicies(), realtimePolicy(), setRealtimePolicy() y disableRealtime(), para decidir qué tablas emiten.

Base de datos JSON

  • db.json: un árbol por proyecto, al estilo de Firebase Realtime Database, con collections, read (con shallow), write, update, push, remove y watch. No hay esquema que declarar —la estructura nace al escribir— y el árbol vive fuera del esquema del proyecto, así que no aparece entre sus tablas.
  • RobleChange.path: la ruta que cambió dentro del árbol. En una tabla SQL llega vacía, porque ahí la fila la identifica primaryKey.

Datos

  • El perfil trae role. Llega en cualquier forma de entrar —contraseña o social— porque todas devuelven el mismo perfil. Requiere auth-service v1.7.8. Es null si a esa persona no se le asignó rol.
  • executeQueryByName(nombre): ejecuta una consulta guardada por su nombre. El nombre se lee en la consola y sobrevive a recrear la consulta; el UUID no.

Cambiado #

  • El paquete depende ahora de google_sign_in y flutter_web_auth_2. Se inyectaban para no arrastrar plugins nativos, pero flutter_secure_storage ya era uno. Los puntos de inyección siguen ahí para quien los necesite.

Corregido #

  • La suscripción de tiempo real no se enviaba nunca en web. El id de petición usaba 1 << 32, que en web vale 0, y nextInt(0) lanza antes de emitir el subscribe.
  • Los filtros del servidor coincidían con todo. El cliente los envolvía en simple y el servidor los lee planos, así que el operador llegaba vacío y todo pasaba el filtro.
  • El botón de Google moría sin plugin registrado. La comprobación solo atrapaba UnimplementedError; sin plugin salta MissingPluginException, y ahora degrada al flujo de navegador en vez de reventar.

1.3.0 #

Añadido #

  • createMany(..., strict: true) lanza RoblePartialInsertException si el servidor rechaza alguna fila, en vez de confiar en que quien llama revise skipped. La excepción conserva el resultado completo, así que se sabe qué sí llegó a escribirse.
  • RobleApiConfig.fromContract valida sus argumentos y lanza ArgumentError si baseUrl no es una URL o si el contractId está vacío o sigue siendo un valor de ejemplo. Antes eso se manifestaba como un 500 incomprensible en la primera petición.
  • Pista en el 500 de autenticación: es lo que devuelve Roble cuando el contrato no existe, así que ahora el mensaje lo sugiere en lugar de dejar solo Error inesperado al autenticar.
  • register(autoLogin: true) inicia sesión al terminar el registro y devuelve el perfil, igual que login. Por defecto es false y se sigue devolviendo el mensaje del servidor. registerWithVerification no lo admite: hasta validar el código del correo la cuenta no puede entrar.
  • login(persistSession: false) mantiene la sesión solo en memoria: sirve para todo mientras la app esté abierta, pero no sobrevive al reinicio. Es el "recordarme" de siempre. Poner false borra además la sesión que hubiera guardada, para no dejar una sesión anterior recuperable en el dispositivo. El valor se respeta también en los refrescos automáticos posteriores.

Cambios incompatibles #

  • El servicio Realtime sale de la API pública. db.realtime y los tipos RobleRealtime* se retiran mientras se estabiliza, junto con la dependencia socket_io_client. El código sigue en el historial (v1.2.0) para reincorporarlo más adelante.

  • Se recorta la superficie de datos a lo esencial. Desaparecen createTable() y getTableData() —usaban endpoints que ROBLE no documenta—, createTableFromTemplate() (las tablas se crean en la consola) y los envoltorios getAll() y getWhere(), que eran read() con otro nombre. Se mantiene getById(), que sí aporta: devuelve una fila o null.

    Antes Ahora
    getAll(tabla) read(tabla)
    getWhere(tabla, col, valor) read(tabla, filters: {col: valor})
  • La sesión se persiste sola. El paquete usa flutter_secure_storage (Keychain / Keystore / almacenamiento cifrado) por defecto, así que ya no hay que implementar ni pasar un RobleTokenStorage. El parámetro storage sigue existiendo para sustituirlo en pruebas por RobleMemoryStorage.

    Esto añade la dependencia flutter_secure_storage y sube el SDK mínimo a Dart 3.3 / Flutter 3.19.

  • RobleApiConfig solo se crea con fromContract(). El constructor con authUrl/dataUrl/realtimeUrl sueltas pasa a ser privado, y desaparecen fromStrings(), copyWith() y validate(). Las URLs se componen siempre a partir del host y del identificador del contrato.

  • La sesión deja de ser manipulable desde fuera. Se eliminan de la API pública accessToken, refreshToken, setTokens(), clearTokens() y onTokenUpdate, y el http.Client pasa a ser privado. El paquete guarda los tokens, los adjunta a cada petición, los renueva ante un 401 y los borra al cerrar sesión; nada de eso necesita intervención de la app.

    En su lugar hay un único miembro de consulta:

    bool get isLoggedIn
    

    Equivalencias: db.accessToken != nulldb.isLoggedIn; guardar tokens a mano → pasar un storage al constructor; restaurar sesión → restoreSession(); borrarla → logout(). El http.Client se sigue pudiendo inyectar por el constructor para pruebas, pero ya no se expone.

Cambiado #

  • restoreSession() ahora comprueba que la sesión siga viva. Además de cargar los tokens guardados, renueva el access token contra el servidor, así que un true significa que la sesión sirve de verdad y no solo que había tokens en el almacenamiento. Si el refresh token caducó o fue revocado, limpia la sesión y devuelve false.

    Los fallos de red no borran la sesión: se propagan RobleApiNetworkException y RobleApiTimeoutException para poder distinguir "sesión caducada" de "sin conexión".

    Con restoreSession(verify: false) se mantiene el comportamiento anterior de solo leer el almacenamiento.

1.2.0 #

Cambios incompatibles #

  • currentUser() ahora devuelve el perfil del usuario, no los datos del token. Pasa de GET /verify-token a GET /me, que es lo que realmente interesa a una app: userId, email, name, el extra del registro y las fechas de creación y actualización. Antes devolvía los claims del JWT (sub, role, sessionId), que son detalle interno de la autenticación.

    Antes (/verify-token) Ahora (/me)
    sub userId
    email email
    dbName, role, sessionId
    id, name, extra, createdAt, updatedAt

    Si leías user['sub'], usa user['userId']. La librería ya no llama a /verify-token: la validez del token la gestiona ella sola con el refresco automático.

  • login() devuelve el perfil del usuario, no los tokens. Tras autenticar pide /me y devuelve el mismo mapa que [currentUser]. Los tokens se guardan internamente y siguen disponibles en accessToken y refreshToken.

    Si la llamada a /me falla, la sesión sigue activa: el error se propaga pero accessToken ya tiene valor, así que se puede distinguir un fallo de credenciales de uno de perfil y reintentar con currentUser().

1.1.0 #

Añadido #

  • Persistencia de sesión opcional: la interfaz RobleTokenStorage, el parámetro storage del constructor y restoreSession(). El cliente guarda la sesión en cada login y refresco y la borra al cerrar sesión, así que sobrevive a un reinicio de la app. Incluye RobleMemoryStorage para pruebas. Sin storage, los tokens siguen viviendo solo en memoria, como hasta ahora.

Corregido #

  • Si el servidor rotara el refresh token al refrescar, ahora se conserva en lugar de descartarse. Hoy /refresh-token solo devuelve accessToken, así que es prevención.

1.0.0 #

Primera versión publicada bajo el nombre roble. Sustituye al paquete roble_api_database, cuya API se mantiene salvo por los cambios listados abajo.

Añadido #

  • RobleApiConfig.fromContract({baseUrl, contractId}): compone las rutas /auth/... y /database/... a partir del identificador del contrato.
  • Getters públicos accessToken y refreshToken.
  • Callback onTokenUpdate, invocado en cada cambio del access token.
  • RobleApiConfig.timeout configurable (30 s por defecto).
  • Documentación de todos los métodos públicos en el README.
  • Cobertura completa de la API documentada de ROBLE (19 endpoints): registerWithVerification(), verifyEmail(), resendCode(), currentUser() (/verify-token, el único endpoint que devuelve la identidad del usuario), forgotPassword(), resetPassword(), deleteAccount(), createMany(), executeQuery(), createTableFromTemplate() y publicRead().
  • Modelos RobleInsertResult, RobleSkippedRecord y RobleQueryResult.
  • Servicio Realtime (db.realtime): árbol JSON por proyecto con API al estilo Firebase — ref(), child(), parent, key, get(shallow:), set(), update(), push(), remove(), más collections() y health().
  • Suscripciones en tiempo real: ref.onValue y ref.onEvent sobre WebSocket, con RobleRealtimeEvent, status, onStatusChange y close(). Un solo socket compartido, resuscripción automática al reconectar y cancelación por colección cuando no quedan escuchas. Añade la dependencia socket_io_client.
  • register() y registerWithVerification() aceptan un extra opcional (Map<String, dynamic>) con campos adicionales que el backend guarda junto al usuario. Se envía en el campo extra del cuerpo, y se omite si es nulo.

Eliminado #

  • authHeaders y dataHeaders de RobleApiConfig, junto con withBearerToken() y el getter muerto defaultHeaders. La API solo necesita Content-Type y Authorization, y ambos los pone el cliente. Si necesitas cabeceras propias, inyecta un http.Client que las añada.

Corregido #

  • PATCH se enviaba como PUT. El switch de _makeRequest agrupaba ambos métodos en client.put(), así que realtime.ref().update() sobrescribía el nodo en lugar de fusionar los campos. Ahora usa client.patch().
  • create() podía informar éxito sobre una fila rechazada. Enviaba el registro a /insert, que responde 200 con {inserted: [], skipped: [...]} cuando el servidor lo rechaza; al no haber nada en inserted, el método devolvía ese objeto como si fuera la fila creada, sin _id y sin error. Ahora usa /insert-one, que devuelve la fila directamente y falla con un error HTTP si la rechaza. Para varios registros, createMany() expone skipped en lugar de descartarlo.

Cambiado #

  • Las excepciones que lanza el cliente ahora son las subclases exportadas del paquete: RobleApiNetworkException, RobleApiTimeoutException, RobleApiFormatException, RobleApiHttpException (con statusCode) y RobleApiAuthException. Antes se lanzaba una clase interna homónima que nunca coincidía con la exportada, por lo que on RobleApiException catch jamás capturaba nada.
  • logout() ya no recibe accessToken: usa el token almacenado y limpia la sesión al terminar.
  • El punto de entrada de la librería pasa a ser package:roble/roble.dart.
  • Un error HTTP ya no se envuelve como Error inesperado: ...; se propaga como RobleApiHttpException con el mensaje del servidor.
  • Tras un 401, solo el fallo del refresco produce RobleApiAuthException; si el reintento falla, se reporta el error real de esa petición.

Eliminado #

  • refreshAccessToken() y refreshToken({refreshToken}) públicos. El refresco del token es interno y automático ante un 401.
  • simulateGet(), que no hacía nada.
  • RobleApiDataBase.timeoutDuration, reemplazado por RobleApiConfig.timeout.
0
likes
140
points
455
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Cliente Flutter para la plataforma ROBLE de Uninorte OpenLab. Autenticación y CRUD sobre bases de datos PostgreSQL.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

crypto, flutter, flutter_secure_storage, flutter_web_auth_2, google_sign_in, http, socket_io_client, web

More

Packages that depend on roble