Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/flutter-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Flutter SDK
on:
pull_request:
paths: ['flutter/**', 'scripts/check_flutter_parity.py', 'scripts/check_rust_parity.py', 'typescript/src/**', '.github/workflows/flutter-sdk.yml']
push:
branches: [main]
paths: ['flutter/**', 'scripts/check_flutter_parity.py', 'scripts/check_rust_parity.py', 'typescript/src/**', '.github/workflows/flutter-sdk.yml']
workflow_dispatch:
permissions:
contents: read
concurrency:
group: flutter-sdk-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
runs-on: ubuntu-22.04
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- flutter: '3.32.0'
revision: be698c48a6750c8cb8e61c740ca9991bb947aba2
- flutter: '3.47.9'
revision: c197ca5199e9ee5661e865596c9c07294dfab51c
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
- name: Install Flutter SDK
run: |
git clone --depth 1 --branch '${{ matrix.flutter }}' https://github.com/flutter/flutter.git "$RUNNER_TEMP/flutter"
test "$(git -C "$RUNNER_TEMP/flutter" rev-parse HEAD)" = '${{ matrix.revision }}'
echo "$RUNNER_TEMP/flutter/bin" >> "$GITHUB_PATH"
"$RUNNER_TEMP/flutter/bin/flutter" config --no-analytics
"$RUNNER_TEMP/flutter/bin/flutter" --version
- name: Resolve dependencies
working-directory: flutter
run: |
flutter pub get
cd example
flutter pub get
- name: Check API presence
run: python3 scripts/check_flutter_parity.py
- name: Analyze
working-directory: flutter
run: flutter analyze --no-pub
- name: Check formatting
if: matrix.flutter == '3.47.9'
working-directory: flutter
run: dart format --output=none --set-exit-if-changed lib test example/lib
- name: Test services and widgets
working-directory: flutter
run: flutter test --no-pub
- name: Validate publish archive
if: matrix.flutter == '3.47.9'
working-directory: flutter
run: flutter pub publish --dry-run
- name: Build Web example
if: matrix.flutter == '3.47.9'
working-directory: flutter/example
run: flutter build web --release --no-pub
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
| Python | [`sentroy-client-sdk`](https://pypi.org/project/sentroy-client-sdk/) | `pip install sentroy-client-sdk` |
| PHP | [`sentroy-co/client-sdk`](https://packagist.org/packages/sentroy-co/client-sdk) | `composer require sentroy-co/client-sdk` |
| Rust | [`sentroy-client-sdk`](rust/) | [Git or path dependency](rust/README.md) — crates.io release pending |
| Flutter / Dart | [`sentroy_client_sdk`](flutter/) | [Git or path dependency](flutter/README.md) — pub.dev release pending |
| cURL | — | [Examples](curl/) |

All SDKs target the Mail and Storage APIs against the hosted platform (`https://sentroy.com`) or your own Sentroy deployment. The Rust SDK also covers WhatsApp, Identity, Env Vault and MCP; see each SDK's reference for its supported surface.
All SDKs target the Mail and Storage APIs against the hosted platform (`https://sentroy.com`) or your own Sentroy deployment. The Rust and Flutter SDKs also cover WhatsApp, Identity, Env Vault and MCP; see each SDK's reference for its supported surface.

## Authentication

Expand All @@ -45,6 +46,7 @@ Each SDK ships a single comprehensive `AGENTS.md` reference — feed the raw URL
```
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/typescript/AGENTS.md
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/rust/AGENTS.md
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/flutter/AGENTS.md
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/go/README.md
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/python/README.md
https://raw.githubusercontent.com/Sentroy-Co/client-sdk/refs/heads/main/php/README.md
Expand All @@ -61,7 +63,7 @@ https://docs.sentroy.com/skill.md # Anthropic Skill format
https://docs.sentroy.com/agents.md # universal markdown
```

> TypeScript and Rust have split `README.md` (lean, npm-facing) from `AGENTS.md` (full reference). The Go, Python, PHP and cURL READMEs remain comprehensive and double as agent-facing docs.
> TypeScript, Rust and Flutter have split `README.md` (lean, npm-facing) from `AGENTS.md` (full reference). The Go, Python, PHP and cURL READMEs remain comprehensive and double as agent-facing docs.

## License

Expand Down
8 changes: 8 additions & 0 deletions flutter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.dart_tool/
build/
.flutter-plugins
.flutter-plugins-dependencies
pubspec.lock
example/.dart_tool/
example/build/
example/pubspec.lock
10 changes: 10 additions & 0 deletions flutter/.pubignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.dart_tool/
build/
.flutter-plugins
.flutter-plugins-dependencies
pubspec.lock
example/.dart_tool/
example/build/
example/pubspec.lock
.gitignore
.pubignore
10 changes: 10 additions & 0 deletions flutter/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Flutter SDK maintenance

Read README.md and PARITY.md before changing APIs. The package targets Flutter 3.32 / Dart 3.8 and newer. Keep library imports portable; platform imports in tests are allowed.

- Preserve all mappings in api-parity.json. Run `python3 ../scripts/check_flutter_parity.py`; it checks presence, while contract tests check behavior.
- Keep company, Identity, Vault and MCP credentials separate. Preserve request UUIDs on uncertain BCC outcomes and never add automatic replay or redirects to writes/private transfers.
- Use the shared abortable HTTP transport for service calls. Enforce total response deadlines and byte limits; a Future timeout alone does not cancel an HTTP request.
- Do not persist rotating refresh tokens after dispatch or allow delayed responses to overwrite newer sessions. Keep pluggable storage failures explicit.
- Preserve bound private references, immutable origin, metadata/hash/receipt checks, streaming limits and close cancellation in the MCP adapter. Its upstream client is community-maintained mcp_dart.
- Validate with analysis, formatting, tests, package dry run, the parity guard and the example Web build. Check both minimum and current Flutter in CI. Never publish credentials or include build/cache output in the package.
6 changes: 6 additions & 0 deletions flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## 0.1.0

- Add typed Mail, Storage and WhatsApp APIs with all 70 TypeScript resource methods.
- Add Identity sessions, MFA, passkeys, server-side JWT verification and pluggable storage.
- Add Flutter auth controller/scope, Env Vault and MCP private-file helpers.
- Add contract and widget tests, a runnable Web example and API presence checks.
21 changes: 21 additions & 0 deletions flutter/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Sentroy

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
16 changes: 16 additions & 0 deletions flutter/PARITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# API coverage

The comparison source is TypeScript SDK 2.18.3, commit `326f131dec57c8e8a4dd6b2f8eb18020a772e861`. [api-parity.json](api-parity.json) maps the root asynchronous resource inventory to Dart classes and methods.

| Surface | Methods | Dart scope |
| --- | ---: | --- |
| Mail | 45 | Domains, Mailboxes, Templates, Inbox, Send, audience contacts/lists/members, suppressions, webhooks/deliveries, logs |
| Storage | 12 | Buckets, media upload/download and metadata, quota/usage |
| WhatsApp | 13 | Numbers, templates, audiences, send and logs |
| **Total** | **70** | Checked by the API presence guard |

Additional native surfaces include Identity and admin, signed JWT verification, memory/custom/serialized session storage, a Flutter controller/scope, thumbnail helpers, Env Vault, MCP connections and all five private file helpers. Dart methods retain the reference camelCase names; factory access such as `members(id)` and `deliveries(id)` is also available.

The presence guard detects unmapped current TypeScript methods and missing Dart methods. It does not prove wire or security equivalence. Tests separately cover serialization, routes, error receipts, session races, signatures, caching, file bounds and widgets.

Native boundaries are described in [README.md](README.md): the app supplies navigation, secure storage and WebAuthn integration. MCP uses the existing 2025 POST JSON/SSE service flow; optional standalone GET notification streams, resumption and 2026 discovery are outside this adapter. Contact import error row/reason diagnostics are intentionally not returned. React UI and the TypeScript CLI have no direct Dart replacement.
126 changes: 126 additions & 0 deletions flutter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Sentroy Flutter SDK

A native Flutter client for Sentroy Mail, Storage, WhatsApp, Identity, Env Vault and MCP. Requires Flutter 3.32 / Dart 3.8 or newer. Version 0.1.0 is prepared in this repository; a pub.dev release has not been published.

## Install

Pin a reviewed commit when installing from Git:

```yaml
dependencies:
sentroy_client_sdk:
git:
url: https://github.com/Sentroy-Co/client-sdk.git
ref: <reviewed-commit>
path: flutter
```

For a checkout, use `sentroy_client_sdk: {path: ../client-sdk/flutter}`. Import `package:sentroy_client_sdk/sentroy_client_sdk.dart` for service APIs or `sentroy_flutter.dart` for Flutter state integration as well.

## Company APIs

```dart
final sdk = Sentroy(
companySlug: 'my-company',
accessToken: token, // Obtain at runtime from your authorized application flow.
);
try {
final domains = await sdk.domains.list();
final buckets = await sdk.buckets.list();
final messages = await sdk.inbox.list(
const InboxListParams(unreadOnly: true, page: 1, limit: 25),
);
} finally {
sdk.close();
}
```

The root client exposes `domains`, `mailboxes`, `templates`, `inbox`, `send`, `audience`, `suppressions`, `webhooks`, `logs`, `buckets`, `media`, `storage`, and `whatsapp`. [PARITY.md](PARITY.md) lists the 70 mapped TypeScript resource methods. Requests and responses have Dart models; localized values accept a string or `Map<String,String>`, and address unions accept a string or `List<String>`. `PatchValue<String>(null)` explicitly clears a nullable update field, while an omitted wrapper leaves the field unchanged.

The company gateway routes are `/api/{mail,storage,whatsapp}/companies/{slug}`. Set `baseUrl`, `timeout`, or inject an `http.Client` for a custom deployment or platform integration. The default deadline is 30 seconds and covers the whole response. Injected clients remain caller-owned. Closing the SDK blocks new resource calls. Browser cookie authentication requires an appropriately configured injected client; the SDK does not automatically acquire a Sentroy browser session.

No service request automatically follows redirects or retries. Normal JSON responses have a 16 MiB cap. Media downloads return bytes and accept an optional `maxBytes` limit; multipart upload preserves filename, MIME type and processing flags.

### BCC and error recovery

BCC sends and drafts use dedicated versioned compose endpoints and a bounded 4 KiB receipt. A `ComposeRequestError` carries `requestId`, `statusCode`, `body`, and `cause`. Save the UUID on uncertain outcomes and reconcile the existing request before retrying with that same UUID. A new UUID represents a new send.

`SentroyException` preserves available status and response body details. Its string representation omits the body. Treat bodies, auth descriptions and raw protocol errors as potentially sensitive when handling diagnostics.

## Identity and Flutter state

```dart
final auth = SentroyAuth(projectSlug: 'my-project');
final controller = SentroyAuthController(auth);
await controller.initialize();

// Place in your widget tree:
SentroyAuthScope(controller: controller, child: const MyApp());

// Inside a descendant build method:
final state = SentroyAuthScope.of(context);
final user = state.user;
```

The scope rebuilds consumers when session, loading or error state changes. The controller exposes signup, signin, MFA verification, refresh, signout and restore. MFA login returns a challenge instead of a signed-in session; call `verifyMfa` with the user-entered code. Dispose the controller and then close the auth client when its owner is disposed.

The auth client also exposes password reset, email verification and changes, account deletion, sessions, activity, magic links, invitations, social authorization URLs and passkey begin/complete operations. The application handles deep links, validates callback context, clears callback fragments from navigation history, and invokes a platform WebAuthn authenticator. `consumeRedirectFragment` accepts the supplied fragment and verifies the user via `/me`; it does not implement a browser or invent PKCE endpoints.

Refresh is explicit and coalesced. The old rotating token is removed from durable storage before dispatch; an uncertain response requires signing in again. Delayed operations cannot replace a newer or cleared session. Choose your app's refresh scheduling and handle the controller's busy state during rotation.

### Persist a session securely

Storage defaults to memory. Supply an `AuthSessionStorage`, or bridge your chosen secure-storage plugin through callbacks bound to an origin/project-specific key:

```dart
final sessions = SerializedAuthSessionStorage(
read: () => secureStorage.read(key: sessionKey),
write: (value) => secureStorage.write(key: sessionKey, value: value),
clear: () => secureStorage.delete(key: sessionKey),
);
final auth = SentroyAuth(projectSlug: projectSlug, storage: sessions);
```

Here `secureStorage` belongs to your app, for example a configured `flutter_secure_storage` instance. The SDK adds no implicit persistent store. Company `stk_` tokens, Identity project keys, Vault `stk_env_` keys and MCP credentials have separate purposes. Keep project admin and private Vault keys on trusted servers. `SentroyAuthAdmin` verifies RS256 JWT signatures, issuer, audience, expiration and unique JWKS key identity; its configured-origin cache can be invalidated. The public server API has no admin user-list endpoint, matching the reference SDK.

## Vault

`VaultClient(VaultConfig(...))` supports cached reads, explicit resolver fallback, public filtering, invalidation and refresh. The default TTL is five minutes and timeout five seconds. `VaultConfig.fromMap` reads a caller-supplied environment map, so there is no unconditional `dart:io` dependency. Clones share cache, transport and lifetime. Verify webhook HMAC over raw bytes with `verifyVaultWebhook`; the default replay window is five minutes and rejects future timestamps. `handleWebhook` invalidates lazily; call `refresh()` for immediate fetching.

## MCP

```dart
final connection = await connectSentroyMcp(
app: 'mail', companySlug: 'my-company', token: mcpToken,
);
try {
final tools = await connection.client.listTools();
} finally {
await connection.close();
}
```

An async `McpTokenProvider` may be supplied instead of a static `mcp_`/`mcp_at_` token. It is resolved before requests; the SDK does not acquire consent or tokens. Core, Mail, Storage, Linear and Chaby resource/metadata URLs are supported. Tool writes remain subject to server permissions and approval requirements.

The community-maintained `mcp_dart` client/codec is connected through a bounded HTTP adapter. It uses MCP 2025-03-26 initialization and POST JSON/SSE exchanges, with no 2026 discovery, standalone GET notification stream, resumption, session reinitialization or automatic replay. Protocol JSON and SSE events have a 1 MiB bound; closing cancels active transport/private requests. Public MCP request/result types are re-exported for calling the client.

All five private helpers are provided: `downloadFile`, `downloadMailAttachment`, `downloadMailContactsExport`, `uploadMailContactsImport`, and `uploadStorageFile`. References are bound to the immutable resource origin, expected path/query and grant metadata. Hashes, sizes, filenames, MIME types, expiry, revisions and receipts are checked. Limits are 64 MiB for storage downloads, 8 MiB for decoded mail attachments, 1 MiB for contact files and 10 MiB for storage uploads. Default deadlines are 60 seconds for downloads and 15 seconds for uploads, configurable from 1 millisecond to 120 seconds. A lost acknowledgment, server error or invalid upload receipt yields `mcp_upload_outcome_unknown`; inspect the existing operation before another upload. Contact import 4xx errors retain a sanitized status without the optional server row/reason diagnostics.

## Example and checks

Run `cd example && flutter run -d chrome`. It demonstrates the auth scope and a company Mail API request using a token entered at runtime. The package has no unconditional platform imports and the example is built for Web in CI. Android and iOS device testing remains application-specific.

From this directory:

```sh
flutter pub get
(cd example && flutter pub get)
flutter analyze
flutter test
dart format --output=none --set-exit-if-changed lib test example/lib
python3 ../scripts/check_flutter_parity.py
flutter pub publish --dry-run
(cd example && flutter build web --release)
```

The inventory check validates method presence. Contract, lifecycle, cryptographic and widget tests provide separate behavioral coverage. React components and the Node CLI remain TypeScript integrations.
6 changes: 6 additions & 0 deletions flutter/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
invalid_use_of_visible_for_testing_member: error
exclude:
- build/**
Loading