PackageTrack
Sign in Get early access

appstrax_services

A library to integrate with Appstrax Services, an authentication, database and file storage service.

1.0.0 144 downloads/mo #1646 most downloaded on pub.dev appstrax/appstrax-services-flutter

What this package is like to depend on

Last release 5 days ago

18 Aug 2026

Release timing varies

gaps range from 4 months to 2.7 years

Nearly every release is documented

notes for 5 of 5 stable releases

Nothing withdrawn

no release was ever pulled

3 years old

5 releases · first in 2023

1 release in the last 12 months

see the full history below

Release timeline

5 releases · Feb 2023 to Aug 2026
2024 2025 2026
Release Pre-release

Releases

latest 5
  1. 1.0.0 18 Aug 2026
    Release notes

    Addresses the remaining findings from the August 2026 audit, and stabilises the API.

    If you are upgrading, you are coming from 0.1.0. 0.2.0 and 0.3.0 were written but never published, so their entries above apply to you as well - Google SSO arrived in 0.2.0, and the bulk of the audit fixes in 0.3.0. Because nothing was released between, every breaking change the audit called for is collected here in one release rather than spread over a deprecation cycle.

    Breaking

    • Signing in again is required once, on upgrade. On Apple platforms the keychain item is now first_unlock_this_device, so a session no longer outlives the app it belongs to or restores onto a different phone. On Android the token moved to flutter_secure_storage 11's cipher storage, which does not read what 9.x wrote. Either way a token from an earlier version is not found, and the user signs in again - once.

      (0.3.0 said this would land in EncryptedSharedPreferences. That backend was deprecated by Google and removed from the plugin before this release shipped, so it never did - see the dependency note below.)

    • flutter_secure_storage 9 → 11, which moves three floors. Dart 3.5 → 3.8, Flutter 3.24 → 3.32, and Android minSdk 21 → 24. The Dart bound is what pub enforces; the Flutter bound is stated so a consumer gets a clear resolution error rather than a confusing SDK one, and minSdk comes from the plugin's own Gradle configuration, so an app below 24 will fail its Android build.

      This is the audit's highest-value dependency finding. 9.x rested on Jetpack Security's EncryptedSharedPreferences, which Google deprecated, and pulled flutter_secure_storage_macos, which is now marked discontinued on pub.dev. The ^9.2.4 constraint also blocked a consuming app from resolving 10 or 11 at all, so an app that needed the newer plugin for any other reason could not use this SDK. Both discontinued packages drop out of the resolution entirely, js 0.6.7 along with them.

      The Android ciphers are now stated in SecureStorageService rather than inherited - AES-GCM for the data, RSA-OAEP to wrap the key. They match the plugin's defaults, but a default is something the plugin may change, and this is the one credential in the package.

    • A 403 no longer raises UnauthorizedException. It raises the new ForbiddenException instead. Anyone catching UnauthorizedException to mean "the session expired, show the login screen" must add on ForbiddenException - and should, because the two call for opposite responses.

      The database's collection policies are what made the shared type untenable. A 401 means the session is over and signing in again fixes it; a 403 means the session is perfectly good and the caller is not permitted to do this, so signing in again accomplishes nothing. Sharing a type sent users to a login screen for a button they were never allowed to press.

    • LoginDto.remember is gone, and signing in always keeps the session. LoginDto(email: ..., password: ..., remember: false) no longer compiles, and the flag is no longer sent to the API.

      This is a behaviour change as well as a compile break. remember: false was the only way a session did not survive a restart: it kept the refresh token in memory and never wrote it to secure storage. A user who signs in now stays signed in until they sign out or the refresh token expires - including on a shared device, where logout() is now the only way to end a session early.

    • An HTTP error no longer puts the response body in toString(). It reads Invalid Request: invalidEmailOrPassword rather than the body verbatim.

      This one is worth reading twice, because the old behaviour was a quiet leak. An API is free to echo a rejected request back in a validation error, and logging a caught exception - catch (err) { log('$err'); }, the pattern this README recommended - therefore wrote whatever was submitted into your crash reporter, passwords included. The body is still on err.body, now alongside a new err.status, and err.code remains the supported way to branch.

    • A cleartext apiUrl is rejected in release builds, loopback included. The localhost / 127.0.0.1 / ::1 / 10.0.2.2 exemption is now conditional on kDebugMode. 10.0.2.2 is only "the Android emulator's host" by convention - it is otherwise a routable private address, so a shipped app pointed at it sent passwords in the clear over whatever network it was on.

    • FindResult is deleted. Nothing in the package produced it, and its type parameter did nothing - data was hardcoded to List<User>, so FindResult<Person> compiled and handed back users. It existed to serve an appstraxUsers service the README documented and the package never shipped.

    • FindResultDto is no longer generic. Drop the type argument. data was a List<DocumentDto> whatever T was, so the parameter was a promise the class did not keep. Use CrudService<T> to get typed models back.

    • SsoCancelledException and SsoCallbackMismatchException now extend AppstraxException. They implemented Exception directly, so on AppstraxException - the obvious way to catch everything this SDK raises - silently missed a cancelled sign-in, the most common of the lot. Both now carry a code. Their toString() gains the Exception: prefix as the price of the shared base class.

    • Utils is replaced by shared/jwt.dart. Utils().decodeToken(t) becomes decodeToken(t) and Utils().isTokenExpired(t) becomes isTokenExpired(t); decodeToken now returns Map<String, dynamic> rather than dynamic. generateRandomString and sleep are gone - nothing in the SDK used them.

    • The package declares platforms: android, ios. It imports dart:io for file upload, from a file every service depends on, so it has never compiled for web - pub simply did not say so, and a web developer found out via a compile error inside someone else's code. Desktop is untested rather than broken.

    Fixed

    • A refresh whose response is lost no longer signs the user out. The token refresh was retried, on the reasoning that a dropped packet should not end a session. It had the opposite effect: the API rotates refresh tokens, so a replay after a lost response presents a token the server has already spent, which comes back 401, which means the session is over. It is no longer replayed, so the refresh fails cleanly and the stored token survives to be tried again.

    • A refresh that does not rotate the token is reported instead of stored. Only a non-null refresh token was ever written, so a response that omitted one left the spent token in secure storage - and the next refresh then failed 401 and signed the user out, two requests after the actual cause.

    • A malformed response names the endpoint instead of throwing a TypeError. 0.3.0 set out to remove that whole class of defect and left three parsers outside the net: a 200 from /login missing token still raised type 'Null' is not a subtype of type 'String' from inside the SDK. Tokens, SsoProviderInfo, TwoFactorAuthDto, Message, DocumentDto and User are now hand-written against checked readers. (strict-casts cannot catch these - it forbids implicit casts, and every one of them was explicit.)

      User is included because being all-nullable protected it from the missing-field failure but not from two others: roles cast each element, so one number in the list threw, and createdAt was parsed without checking it was a date. It is the most frequently parsed response in the SDK, so it was the last place worth leaving an unchecked read.

    • A 2xx response that is not JSON is reported as a failed request. A captive portal answering 200 with an HTML page raised FormatException: Unexpected character (at character 1), naming neither the status nor what arrived.

    • An upload can no longer hang forever. The timeout was attached to sending the request, which resolves as soon as response headers arrive; the read that followed had no ceiling at all. Uploads also no longer share the 30-second request budget - see uploadTimeout.

    • The refresh token is sent once per request, not twice. It went out in a Cookie and in the JSON body, which is two chances for a proxy log or a crash dump to capture it, for no gain: x-auth-transport: body already tells the API which to read.

    • Using the SDK before initializing says so. It raised Invalid argument(s) (baseUrl missing, please initialize.): Must not be null - which states the opposite of the problem, since nothing was null. Now a StateError naming initializeAppstraxServices.

    • A write conflict no longer reports as an internal server error. The database answers 409 when a document changed between the policy check and the write, and 409 had no case in the status mapping - so it fell through to FetchDataException, whose prefix is Internal Server Error:. It now raises ConflictException, which is what it is: the request was fine and can be retried once the caller has re-read the document. The SDK does not retry it, because resolving a conflict means deciding what the record should now contain.

    • A saved token that cannot be read no longer bricks startup. SecureStorageService.initialize() sat one layer above the catch-all in AppstraxAuth.initialize() and had none of its own, so a platform that could not decrypt what it found failed initializeAppstraxServices - and because the value stayed in storage, it failed on every later launch too, with no way out but reinstalling. Unreadable is now treated as absent and the value is cleared.

      The flutter_secure_storage upgrade is what made this worth guarding rather than theorising about, but the platform has always had its own reasons: a KeyStore entry invalidated by a new fingerprint, ciphertext restored onto a device whose key did not travel with it, a locked keyring.

    • A failed session restore is no longer silent. It still cannot throw - an exception there fails app startup permanently, because the offending value stays in secure storage - but it now reports to onRestoreError and to the debug console.

    • The user endpoints no longer make two extra round trips. changePassword, saveUserData, verifyEmailAddress and four others each opened with isAuthenticated(), which refreshes the token and fetches /session. Three sequential requests for one action, and none of them read the user.

    • An SSO failure keeps the provider's wording off the message. The callback's error query parameter became the exception message an application would show. It is now on SsoRedirectException.providerError, with fixed text in message.

    Added

    • ForbiddenException (403) and ConflictException (409), described above.

    • Operator.contains, which sends CONTAINS. The API has supported it for some time and this SDK was the only place it could not be expressed. Not the same question as Operator.qIn: IN asks whether a scalar field is one of several values, CONTAINS asks whether an array field holds one.

    • uploadTimeout on initializeAppstraxServices, defaulting to five minutes. Thirty seconds is generous for a JSON call and far too short for a large file on a mobile connection - this package's own test fixture is a 10 MB video.

    • onRestoreError on initializeAppstraxServices, for the startup failure that is not allowed to throw.

    • disposeAppstraxServices(), which releases the HTTP client and clears in-memory session state without signing the user out. Optional in an app that runs until the process ends; necessary anywhere the SDK is initialized more than once - a hot restart, a test suite, switching API. Previously each initialize leaked the last client and left the previous session's user in memory.

    • HttpException.status, the HTTP status as a field. It was either absent or smuggled into FetchDataException's message text.

    • Codes on every SDK-raised error, so err.code works for the SSO failures as it already did for the session ones.

    Notes on the database API

    None of these need a change in your code; they are behaviour on the API's side that this SDK now documents rather than lets you discover.

    • Collections are registered, not created by writing to them. Writing to a name nobody registered answers 404 instead of creating the collection. Existing installs are unaffected - the API registers every collection that already holds data with a permissive legacy policy when it boots, so an upgrade changes nothing until an administrator authors a real policy.

    • find() returns at most 1000 documents, whether or not a limit is given. An absent limit used to mean no limit at all; it is now the ceiling. A larger limit is clamped rather than refused, and nothing is hidden by that: FindResultDto.limit reports the limit actually applied and count is the total you are allowed to read, so count > data.length means page with offset.

    • A document may arrive with fields missing, where a policy allows the document but not every field on it. DocumentDto.data is a map and copes; a CrudService model that declares those fields as required will not.

    • CrudService.save() cannot be used with a collection that restricts writable fields. It sends the whole model, and the API rejects any field the policy does not allow the caller to write - including a field sent back exactly as it was read, since the write mask is checked before immutable fields are restored. Such a model must declare only writable fields, or use appstraxDb.edit() with just the fields being changed.

    Internal

    • Dev dependencies current: flutter_lints 5 → 6, build_runner → 2.15.1. The two lints flutter_lints 6 adds - use_null_aware_elements and unnecessary_underscores - are applied rather than suppressed.

    • The test dev dependency is gone. One integration file imported package:test while the seventeen around it used flutter_test, which is a mismatch worth removing on its own - package:test provides no Flutter binding, and that file needs one. It had also begun to conflict with the test_api version flutter_test pins.

    • avoid_dynamic_calls is enabled. strict-casts catches an implicit cast, but reaching into a dynamic is a dynamic call - and since every response body enters this package as dynamic, that was the larger half of the same problem.

    • The database, storage, CRUD and SSO-browser layers have unit tests for the first time. They were reachable only through test/integration, which needs a live API and so never runs in CI - and they are exactly the layers 0.3.0 rewrote. The offline suite went from 140 tests to 201, and those four files from no coverage to 100%, 100%, 77% and 100%.

    • SsoBrowser is exercised through the plugin's own method channel, so a cancelled sheet and a callback that arrived at the wrong address are now testable without a device.

    Open source →
  2. 0.1.0 12 Dec 2023
    Release notes
    • Update to use the latest combined appstrax services
    Open source →
  3. 0.0.3 18 Aug 2023
    Release notes
    • Bug fixes -
    Open source →
  4. 0.0.2 24 Feb 2023
    Release notes
    • Bug fixes -
    Open source →
  5. 0.0.1 22 Feb 2023
    Release notes
    • Initial Release
    Open source →

Every package, every release, already written down.

The archive is open and free. Watching your own project is what we are building next.

Browse the archive