chore: scaffold the Serverpod 4 build
Deviations from a stock `serverpod create`:
- Dropped Flutter web support, for two reasons:
- Appflowy editor had issues in the past, potentially related to the
Flutter-Web IME bridge. Upstream has also open bugs in relation to
that:
- AppFlowy-IO/appflowy-editor#1029 [Bug][Web] Unwanted space on click
and overall space bugs (open)
- AppFlowy-IO/appflowy-editor#782 [Bug] Editor auto-scrolling when
pressing space too quickly if placed inside a scrollable (open)
- Offline-first is a required feature. serverpod_offline_sync's SQLite
support in browsers, maybe via sqlite3.wasm + OPFS, is not
documented.
- SDK constraints raised to ^3.13.0, instead of ^3.12.2, so enjoy the
newest language features.
- Pinned to Flutter 3.47.4 via FVM.
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
commit
e17d99fa51
182 changed files with 17563 additions and 0 deletions
3
.fvmrc
Normal file
3
.fvmrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"flutter": "3.47.4"
|
||||||
|
}
|
||||||
25
.github/workflows/analyze.yml
vendored
Normal file
25
.github/workflows/analyze.yml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: Analyze
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: '3.44.4'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Analyze
|
||||||
|
working-directory: halkyon_learn_server
|
||||||
|
run: dart analyze --fatal-infos
|
||||||
25
.github/workflows/format.yml
vendored
Normal file
25
.github/workflows/format.yml
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: Format
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: '3.44.4'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
working-directory: halkyon_learn_server
|
||||||
|
run: dart format --set-exit-if-changed .
|
||||||
45
.github/workflows/tests.yml
vendored
Normal file
45
.github/workflows/tests.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
VERSION: '4.0.0'
|
||||||
|
SERVERPOD_PASSWORD_database: 'h8hg2NM2Ho1N7Zv-WxwJyhfJC76hxy6h'
|
||||||
|
SERVERPOD_PASSWORD_redis: 'JRcMj86Jrhg4s6soZ_DShJqQilJGzEJo'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: '3.44.4'
|
||||||
|
|
||||||
|
- name: Start containers
|
||||||
|
working-directory: halkyon_learn_server
|
||||||
|
run: docker compose up --quiet-pull --build -d
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Install Serverpod CLI
|
||||||
|
run: dart install serverpod_cli $VERSION
|
||||||
|
|
||||||
|
- name: Run Serverpod generator
|
||||||
|
working-directory: halkyon_learn_server
|
||||||
|
run: serverpod generate
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: halkyon_learn_server
|
||||||
|
run: dart test
|
||||||
|
|
||||||
|
- name: Stop containers
|
||||||
|
if: always()
|
||||||
|
working-directory: halkyon_learn_server
|
||||||
|
run: docker compose down -v
|
||||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Files and directories created by pub
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Pubspec overrides
|
||||||
|
pubspec_overrides.yaml
|
||||||
|
|
||||||
|
# Agents
|
||||||
|
.dart_skills/
|
||||||
|
.agents/
|
||||||
|
.claude/
|
||||||
|
.gemini/
|
||||||
|
.cursor/
|
||||||
|
.vscode/mcp.json
|
||||||
|
.codex/
|
||||||
|
.mcp.json
|
||||||
|
|
||||||
|
# Serverpod Cloud CLI
|
||||||
|
.scloud/
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
||||||
43
.vscode/launch.json
vendored
Normal file
43
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
// If the server pubspec configures multiple Flutter apps, add an attach
|
||||||
|
// configuration like this for each app and add every configuration name
|
||||||
|
// to the compound below. Use the VM-service info file pattern
|
||||||
|
// `${workspaceFolder}/halkyon_learn_server/.dart_tool/serverpod/flutter-vm-service-info-<appId>.json`,
|
||||||
|
// where `<appId>` is the app's key under `serverpod.flutter_apps` in the
|
||||||
|
// server pubspec.
|
||||||
|
{
|
||||||
|
// Attaches to the Flutter app `serverpod start` spawns. Don't
|
||||||
|
// launch a second instance here - it would race for the port.
|
||||||
|
"name": "halkyon_learn_flutter",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "attach",
|
||||||
|
"vmServiceInfoFile": "${workspaceFolder}/halkyon_learn_server/.dart_tool/serverpod/flutter-vm-service-info-halkyon_learn.json",
|
||||||
|
"preLaunchTask": "serverpod_start",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "halkyon_learn_server",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "attach",
|
||||||
|
"vmServiceInfoFile": "${workspaceFolder}/halkyon_learn_server/.dart_tool/serverpod/vm-service-info.json",
|
||||||
|
"preLaunchTask": "serverpod_start",
|
||||||
|
"internalConsoleOptions": "neverOpen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "halkyon_learn (full stack)",
|
||||||
|
"configurations": [
|
||||||
|
"halkyon_learn_server",
|
||||||
|
"halkyon_learn_flutter"
|
||||||
|
],
|
||||||
|
// Stopping one session stops both; the orphan blocks re-attach.
|
||||||
|
"stopAll": true,
|
||||||
|
"presentation": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"dart.flutterSdkPath": ".fvm/versions/3.47.4"
|
||||||
|
}
|
||||||
48
.vscode/tasks.json
vendored
Normal file
48
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "serverpod_start",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "serverpod",
|
||||||
|
"args": ["start"],
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/halkyon_learn_server",
|
||||||
|
"env": {
|
||||||
|
"SERVERPOD_PASSWORD_database": "zLZiS0hYW86MNDWF3fTB9iYudoEQ4rZG",
|
||||||
|
// Marks a `serverpod start` launched from the IDE, which is the
|
||||||
|
// only kind that honors the Flutter device picked in VS Code. A run
|
||||||
|
// started from a plain terminal carries no marker, so a device file
|
||||||
|
// left behind by a session that was killed cannot retarget it.
|
||||||
|
"SERVERPOD_LAUNCHED_FROM_IDE": "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "dedicated"
|
||||||
|
},
|
||||||
|
// The TUI captures stdout, so we can't string-match for "ready". The
|
||||||
|
// attach config polls vm-service-info.json for the actual readiness
|
||||||
|
// signal; this matcher exists only to satisfy VS Code's requirement
|
||||||
|
// that background tasks declare one. The pattern intentionally never
|
||||||
|
// matches; begin/end patterns mark the task active+ready immediately.
|
||||||
|
"problemMatcher": {
|
||||||
|
"owner": "serverpod",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^(SERVERPOD_NEVER_MATCH):(\\d+):(.+)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"message": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": ".",
|
||||||
|
"endsPattern": "."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
50
AGENTS.md
Normal file
50
AGENTS.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Flutter & Serverpod project
|
||||||
|
|
||||||
|
This project is a Flutter app (frontend) backed by a Serverpod server (backend). Always build the app's backend with Serverpod.
|
||||||
|
Build for multiple users, use Serverpod's built-in authentication, which is already set up in `lib/server.dart`.
|
||||||
|
|
||||||
|
The user starts the server and Flutter app with `serverpod start`. There is no need to check if the server is running: make the changes and call the `serverpod` MCP tools as needed. If the server is not running, an informative error message will be received from the MCP server. Then STOP and ask the user to start it. NEVER start the server yourself. The Flutter app is started along with it, or can be launched from the MCP tool `spawn_flutter_app`.
|
||||||
|
|
||||||
|
While running, `serverpod start` watches for file changes to run incremental code generation and hot reload both the server and the Flutter app.
|
||||||
|
|
||||||
|
Calling `serverpod generate` directly is not needed, but might be useful to troubleshoot when an incremental generation fails.
|
||||||
|
|
||||||
|
ALWAYS use the MCP server instead of the command line. Use the MCP server to:
|
||||||
|
|
||||||
|
- `create_migration` and `apply_migrations` for database (after you change data models).
|
||||||
|
- `create_repair_migration` if the database has drifted out of sync with the migrations.
|
||||||
|
- `tail_server_logs` to read logs from the server.
|
||||||
|
- `tail_flutter_logs` to read the raw stdout/stderr of the Flutter app.
|
||||||
|
- `hot_reload` / `hot_restart` to reload or restart the server and the Flutter app. ALWAYS call `hot_restart` after doing changes in the Flutter app that may not work with normal hot reload (which is automatically applied).
|
||||||
|
- `spawn_flutter_app` to start a Flutter app declared under `serverpod: flutter_apps:` in the server `pubspec.yaml`.
|
||||||
|
- `get_flutter_app_dtd` (Dart tooling daemon) for connecting to the app through the `dart` MCP.
|
||||||
|
|
||||||
|
NEVER edit generated code. The server's `lib/src/generated/` directory and the whole `halkyon_learn_client` package are rewritten by the code generator. Change the `.spy.yaml` models, the endpoints, or `lib/server.dart` instead.
|
||||||
|
|
||||||
|
Migrations are a narrow exception: the `migration.sql` of a generated migration MAY be edited by hand when the generated SQL would lose data — to add a data transformation, or to reach a destructive change through non-destructive steps. Never touch the other files in the migration directory, and keep the schema the SQL ends up with identical to `definition.sql` — new databases are created from that file and never run `migration.sql`.
|
||||||
|
|
||||||
|
Only when the server cannot be started at all, fall back to the CLI in the server package:
|
||||||
|
|
||||||
|
- `serverpod generate` to regenerate the client and the generated server code.
|
||||||
|
- `serverpod create-migration` after changing a model with a `table` (add `--force` for destructive changes). It only writes the migration; `serverpod start` applies pending migrations when it boots the server.
|
||||||
|
|
||||||
|
Tests need no Docker. `config/test.yaml` sets `database.dataPath`, so Serverpod starts and manages the test database (an embedded PostgreSQL) itself, and the project's `docker-compose.yaml` is not used for it. Just run `dart test` in the server package.
|
||||||
|
|
||||||
|
Checklist after doing changes, in this order:
|
||||||
|
|
||||||
|
- `dart analyze` (CLI)
|
||||||
|
- `dart format` (CLI)
|
||||||
|
- `create_migration` and `apply_migrations` (MCP - only if necessary)
|
||||||
|
- Do `serverpod` MCP `hot_restart` if required (hot reload is done automatically). Will also hot restart Flutter app
|
||||||
|
- Run tests, if applicable (`dart test` in the server package)
|
||||||
|
- Check `serverpod` MCP `tail_server_logs` and `tail_flutter_logs` for any issues.
|
||||||
|
|
||||||
|
If the user asks you to test the app:
|
||||||
|
|
||||||
|
1. Use `get_flutter_app_dtd` (`serverpod` MCP) to get the Flutter app's DTD
|
||||||
|
2. Pass the DTD to `connect_dart_tooling_daemon` (`dart` MCP) to connect to the app
|
||||||
|
3. Use `flutter_driver` (`dart` MCP) to navigate through the app
|
||||||
|
|
||||||
|
The app is launched from `halkyon_learn_flutter/lib/driver.dart`, which starts the Flutter driver extension with text entry emulation turned off so the app stays usable by hand. To let the driver type, set `enableTextEntryEmulation: true` there and `hot_restart` the app.
|
||||||
|
|
||||||
|
IMPORTANT: After building the first version of the app, update this AGENTS.md file with information about the app we're building. KEEP the info about the MCP server and the checklist. Remove this paragraph.
|
||||||
1
CLAUDE.md
Normal file
1
CLAUDE.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
@AGENTS.md
|
||||||
13
halkyon_learn_client/.gitignore
vendored
Normal file
13
halkyon_learn_client/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Files and directories created by pub
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
|
||||||
|
# Omit committing pubspec.lock for library packages:
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock
|
||||||
|
pubspec.lock
|
||||||
|
|
||||||
|
# Conventional directory for build outputs
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Directory created by dartdoc
|
||||||
|
doc/api/
|
||||||
3
halkyon_learn_client/CHANGELOG.md
Normal file
3
halkyon_learn_client/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version, created by Stagehand
|
||||||
5
halkyon_learn_client/README.md
Normal file
5
halkyon_learn_client/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# halkyon_learn_client
|
||||||
|
|
||||||
|
This is your Serverpod client. The code in here is mostly generated by
|
||||||
|
Serverpod, but you may want to make changes if you are adding modules to your
|
||||||
|
project.
|
||||||
16
halkyon_learn_client/analysis_options.yaml
Normal file
16
halkyon_learn_client/analysis_options.yaml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Defines a default set of lint rules enforced for
|
||||||
|
# projects at Google. For details and rationale,
|
||||||
|
# see https://github.com/dart-lang/pedantic#enabled-lints.
|
||||||
|
|
||||||
|
# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
|
||||||
|
# Uncomment to specify additional rules.
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- lib/src/protocol/**
|
||||||
|
|
||||||
|
formatter:
|
||||||
|
trailing_commas: preserve
|
||||||
5
halkyon_learn_client/dartdoc_options.yaml
Normal file
5
halkyon_learn_client/dartdoc_options.yaml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
dartdoc:
|
||||||
|
categories:
|
||||||
|
"Endpoint":
|
||||||
|
markdown: doc/endpoint.md
|
||||||
|
name: Endpoint
|
||||||
15
halkyon_learn_client/doc/endpoint.md
Normal file
15
halkyon_learn_client/doc/endpoint.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Callable endpoints
|
||||||
|
|
||||||
|
Each class contains callable methods that will call a method on the server side. These are normally defined in the `endpoint` directory in your server project. This client sends requests to these endpoints and returns the result.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// How to use GreetingEndpoint.
|
||||||
|
client.greeting.hello("world!");
|
||||||
|
|
||||||
|
// Generic format.
|
||||||
|
client.<endpoint>.<method>(...);
|
||||||
|
```
|
||||||
|
|
||||||
|
Please see the full official documentation [here](https://docs.serverpod.dev)
|
||||||
2
halkyon_learn_client/lib/halkyon_learn_client.dart
Normal file
2
halkyon_learn_client/lib/halkyon_learn_client.dart
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export 'src/protocol/protocol.dart';
|
||||||
|
export 'package:serverpod_client/serverpod_client.dart';
|
||||||
331
halkyon_learn_client/lib/src/protocol/client.dart
Normal file
331
halkyon_learn_client/lib/src/protocol/client.dart
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||||
|
/* To generate run: "serverpod generate" */
|
||||||
|
|
||||||
|
// ignore_for_file: implementation_imports
|
||||||
|
// ignore_for_file: library_private_types_in_public_api
|
||||||
|
// ignore_for_file: non_constant_identifier_names
|
||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
// ignore_for_file: type_literal_in_constant_pattern
|
||||||
|
// ignore_for_file: use_super_parameters
|
||||||
|
// ignore_for_file: invalid_use_of_internal_member
|
||||||
|
|
||||||
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
|
import 'dart:async' as _ida;
|
||||||
|
import 'package:halkyon_learn_client/src/protocol/greetings/greeting.dart'
|
||||||
|
as _ili0bmg9;
|
||||||
|
import 'package:http/http.dart' as _i85jenna;
|
||||||
|
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
|
||||||
|
as _iacc;
|
||||||
|
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
|
||||||
|
as _iaic;
|
||||||
|
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||||
|
import 'protocol.dart' as _il2as5qe;
|
||||||
|
|
||||||
|
/// By extending [EmailIdpBaseEndpoint], the email identity provider endpoints
|
||||||
|
/// are made available on the server and enable the corresponding sign-in widget
|
||||||
|
/// on the client.
|
||||||
|
/// {@category Endpoint}
|
||||||
|
class EndpointEmailIdp extends _iaic.EndpointEmailIdpBase {
|
||||||
|
EndpointEmailIdp(_isc.EndpointCaller caller) : super(caller);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'emailIdp';
|
||||||
|
|
||||||
|
/// Logs in the user and returns a new session.
|
||||||
|
///
|
||||||
|
/// Throws an [EmailAccountLoginException] in case of errors, with reason:
|
||||||
|
/// - [EmailAccountLoginExceptionReason.invalidCredentials] if the email or
|
||||||
|
/// password is incorrect.
|
||||||
|
/// - [EmailAccountLoginExceptionReason.tooManyAttempts] if there have been
|
||||||
|
/// too many failed login attempts.
|
||||||
|
///
|
||||||
|
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
|
||||||
|
@override
|
||||||
|
_ida.Future<_iacc.AuthSuccess> login({
|
||||||
|
required String email,
|
||||||
|
required String password,
|
||||||
|
}) => caller.callServerEndpoint<_iacc.AuthSuccess>(
|
||||||
|
'emailIdp',
|
||||||
|
'login',
|
||||||
|
{
|
||||||
|
'email': email,
|
||||||
|
'password': password,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Starts the registration for a new user account with an email-based login
|
||||||
|
/// associated to it.
|
||||||
|
///
|
||||||
|
/// Upon successful completion of this method, an email will have been
|
||||||
|
/// sent to [email] with a verification link, which the user must open to
|
||||||
|
/// complete the registration.
|
||||||
|
///
|
||||||
|
/// Always returns a account request ID, which can be used to complete the
|
||||||
|
/// registration. If the email is already registered, the returned ID will not
|
||||||
|
/// be valid.
|
||||||
|
@override
|
||||||
|
_ida.Future<_isc.UuidValue> startRegistration({required String email}) =>
|
||||||
|
caller.callServerEndpoint<_isc.UuidValue>(
|
||||||
|
'emailIdp',
|
||||||
|
'startRegistration',
|
||||||
|
{'email': email},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Verifies an account request code and returns a token
|
||||||
|
/// that can be used to complete the account creation.
|
||||||
|
///
|
||||||
|
/// Throws an [EmailAccountRequestException] in case of errors, with reason:
|
||||||
|
/// - [EmailAccountRequestExceptionReason.expired] if the account request has
|
||||||
|
/// already expired.
|
||||||
|
/// - [EmailAccountRequestExceptionReason.policyViolation] if the password
|
||||||
|
/// does not comply with the password policy.
|
||||||
|
/// - [EmailAccountRequestExceptionReason.invalid] if no request exists
|
||||||
|
/// for the given [accountRequestId] or [verificationCode] is invalid.
|
||||||
|
@override
|
||||||
|
_ida.Future<String> verifyRegistrationCode({
|
||||||
|
required _isc.UuidValue accountRequestId,
|
||||||
|
required String verificationCode,
|
||||||
|
}) => caller.callServerEndpoint<String>(
|
||||||
|
'emailIdp',
|
||||||
|
'verifyRegistrationCode',
|
||||||
|
{
|
||||||
|
'accountRequestId': accountRequestId,
|
||||||
|
'verificationCode': verificationCode,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Completes a new account registration, creating a new auth user with a
|
||||||
|
/// profile and attaching the given email account to it.
|
||||||
|
///
|
||||||
|
/// Throws an [EmailAccountRequestException] in case of errors, with reason:
|
||||||
|
/// - [EmailAccountRequestExceptionReason.expired] if the account request has
|
||||||
|
/// already expired.
|
||||||
|
/// - [EmailAccountRequestExceptionReason.policyViolation] if the password
|
||||||
|
/// does not comply with the password policy.
|
||||||
|
/// - [EmailAccountRequestExceptionReason.invalid] if the [registrationToken]
|
||||||
|
/// is invalid.
|
||||||
|
///
|
||||||
|
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
|
||||||
|
///
|
||||||
|
/// Returns a session for the newly created user.
|
||||||
|
@override
|
||||||
|
_ida.Future<_iacc.AuthSuccess> finishRegistration({
|
||||||
|
required String registrationToken,
|
||||||
|
required String password,
|
||||||
|
}) => caller.callServerEndpoint<_iacc.AuthSuccess>(
|
||||||
|
'emailIdp',
|
||||||
|
'finishRegistration',
|
||||||
|
{
|
||||||
|
'registrationToken': registrationToken,
|
||||||
|
'password': password,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Requests a password reset for [email].
|
||||||
|
///
|
||||||
|
/// If the email address is registered, an email with reset instructions will
|
||||||
|
/// be send out. If the email is unknown, this method will have no effect.
|
||||||
|
///
|
||||||
|
/// Always returns a password reset request ID, which can be used to complete
|
||||||
|
/// the reset. If the email is not registered, the returned ID will not be
|
||||||
|
/// valid.
|
||||||
|
///
|
||||||
|
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
|
||||||
|
/// - [EmailAccountPasswordResetExceptionReason.tooManyAttempts] if the user has
|
||||||
|
/// made too many attempts trying to request a password reset.
|
||||||
|
///
|
||||||
|
@override
|
||||||
|
_ida.Future<_isc.UuidValue> startPasswordReset({required String email}) =>
|
||||||
|
caller.callServerEndpoint<_isc.UuidValue>(
|
||||||
|
'emailIdp',
|
||||||
|
'startPasswordReset',
|
||||||
|
{'email': email},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Verifies a password reset code and returns a finishPasswordResetToken
|
||||||
|
/// that can be used to finish the password reset.
|
||||||
|
///
|
||||||
|
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
|
||||||
|
/// - [EmailAccountPasswordResetExceptionReason.expired] if the password reset
|
||||||
|
/// request has already expired.
|
||||||
|
/// - [EmailAccountPasswordResetExceptionReason.tooManyAttempts] if the user has
|
||||||
|
/// made too many attempts trying to verify the password reset.
|
||||||
|
/// - [EmailAccountPasswordResetExceptionReason.invalid] if no request exists
|
||||||
|
/// for the given [passwordResetRequestId] or [verificationCode] is invalid.
|
||||||
|
///
|
||||||
|
/// If multiple steps are required to complete the password reset, this endpoint
|
||||||
|
/// should be overridden to return credentials for the next step instead
|
||||||
|
/// of the credentials for setting the password.
|
||||||
|
@override
|
||||||
|
_ida.Future<String> verifyPasswordResetCode({
|
||||||
|
required _isc.UuidValue passwordResetRequestId,
|
||||||
|
required String verificationCode,
|
||||||
|
}) => caller.callServerEndpoint<String>(
|
||||||
|
'emailIdp',
|
||||||
|
'verifyPasswordResetCode',
|
||||||
|
{
|
||||||
|
'passwordResetRequestId': passwordResetRequestId,
|
||||||
|
'verificationCode': verificationCode,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Completes a password reset request by setting a new password.
|
||||||
|
///
|
||||||
|
/// The [verificationCode] returned from [verifyPasswordResetCode] is used to
|
||||||
|
/// validate the password reset request.
|
||||||
|
///
|
||||||
|
/// Throws an [EmailAccountPasswordResetException] in case of errors, with reason:
|
||||||
|
/// - [EmailAccountPasswordResetExceptionReason.expired] if the password reset
|
||||||
|
/// request has already expired.
|
||||||
|
/// - [EmailAccountPasswordResetExceptionReason.policyViolation] if the new
|
||||||
|
/// password does not comply with the password policy.
|
||||||
|
/// - [EmailAccountPasswordResetExceptionReason.invalid] if no request exists
|
||||||
|
/// for the given [passwordResetRequestId] or [verificationCode] is invalid.
|
||||||
|
///
|
||||||
|
/// Throws an [AuthUserBlockedException] if the auth user is blocked.
|
||||||
|
@override
|
||||||
|
_ida.Future<void> finishPasswordReset({
|
||||||
|
required String finishPasswordResetToken,
|
||||||
|
required String newPassword,
|
||||||
|
}) => caller.callServerEndpoint<void>(
|
||||||
|
'emailIdp',
|
||||||
|
'finishPasswordReset',
|
||||||
|
{
|
||||||
|
'finishPasswordResetToken': finishPasswordResetToken,
|
||||||
|
'newPassword': newPassword,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ida.Future<bool> hasAccount() => caller.callServerEndpoint<bool>(
|
||||||
|
'emailIdp',
|
||||||
|
'hasAccount',
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// By extending [RefreshJwtTokensEndpoint], the JWT token refresh endpoint
|
||||||
|
/// is made available on the server and enables automatic token refresh on the client.
|
||||||
|
/// {@category Endpoint}
|
||||||
|
class EndpointJwtRefresh extends _iacc.EndpointRefreshJwtTokens {
|
||||||
|
EndpointJwtRefresh(_isc.EndpointCaller caller) : super(caller);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'jwtRefresh';
|
||||||
|
|
||||||
|
/// Creates a new token pair for the given [refreshToken].
|
||||||
|
///
|
||||||
|
/// If [refreshToken] is omitted, cookie-mode web clients fall back to the
|
||||||
|
/// configured HttpOnly refresh cookie. When neither source is present this
|
||||||
|
/// throws [RefreshTokenNotFoundException], the same public "no usable refresh
|
||||||
|
/// credential" exception used for unknown refresh tokens.
|
||||||
|
///
|
||||||
|
/// Can throw the following exceptions:
|
||||||
|
/// -[RefreshTokenMalformedException]: refresh token is malformed and could
|
||||||
|
/// not be parsed. Not expected to happen for tokens issued by the server.
|
||||||
|
/// -[RefreshTokenNotFoundException]: refresh token is unknown to the server.
|
||||||
|
/// Either the token was deleted or generated by a different server.
|
||||||
|
/// -[RefreshTokenExpiredException]: refresh token has expired. Will happen
|
||||||
|
/// only if it has not been used within configured `refreshTokenLifetime`.
|
||||||
|
/// -[RefreshTokenInvalidSecretException]: refresh token is incorrect, meaning
|
||||||
|
/// it does not refer to the current secret refresh token. This indicates
|
||||||
|
/// either a malfunctioning client or a malicious attempt by someone who has
|
||||||
|
/// obtained the refresh token. In this case the underlying refresh token
|
||||||
|
/// will be deleted, and access to it will expire fully when the last access
|
||||||
|
/// token is elapsed.
|
||||||
|
///
|
||||||
|
/// This endpoint is unauthenticated, meaning the client won't include any
|
||||||
|
/// authentication information with the call.
|
||||||
|
@override
|
||||||
|
_ida.Future<_iacc.AuthSuccess> refreshAccessToken({String? refreshToken}) =>
|
||||||
|
caller.callServerEndpoint<_iacc.AuthSuccess>(
|
||||||
|
'jwtRefresh',
|
||||||
|
'refreshAccessToken',
|
||||||
|
{'refreshToken': refreshToken},
|
||||||
|
authenticated: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is an example endpoint that returns a greeting message through
|
||||||
|
/// its [hello] method.
|
||||||
|
/// {@category Endpoint}
|
||||||
|
class EndpointGreeting extends _isc.EndpointRef {
|
||||||
|
EndpointGreeting(_isc.EndpointCaller caller) : super(caller);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'greeting';
|
||||||
|
|
||||||
|
/// Returns a personalized greeting message: "Hello {name}".
|
||||||
|
_ida.Future<_ili0bmg9.Greeting> hello(String name) =>
|
||||||
|
caller.callServerEndpoint<_ili0bmg9.Greeting>(
|
||||||
|
'greeting',
|
||||||
|
'hello',
|
||||||
|
{'name': name},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Modules {
|
||||||
|
Modules(Client client) {
|
||||||
|
serverpod_auth_idp = _iaic.Caller(client);
|
||||||
|
serverpod_auth_core = _iacc.Caller(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _iaic.Caller serverpod_auth_idp;
|
||||||
|
|
||||||
|
late final _iacc.Caller serverpod_auth_core;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Client extends _isc.ServerpodClientShared {
|
||||||
|
Client(
|
||||||
|
String host, {
|
||||||
|
dynamic securityContext,
|
||||||
|
Duration? streamingConnectionTimeout,
|
||||||
|
Duration? connectionTimeout,
|
||||||
|
Function(
|
||||||
|
_isc.MethodCallContext,
|
||||||
|
Object,
|
||||||
|
StackTrace,
|
||||||
|
)?
|
||||||
|
onFailedCall,
|
||||||
|
Function(_isc.MethodCallContext)? onSucceededCall,
|
||||||
|
bool? disconnectStreamsOnLostInternetConnection,
|
||||||
|
_i85jenna.Client? httpClientOverride,
|
||||||
|
}) : super(
|
||||||
|
host,
|
||||||
|
_il2as5qe.Protocol(),
|
||||||
|
securityContext: securityContext,
|
||||||
|
streamingConnectionTimeout: streamingConnectionTimeout,
|
||||||
|
connectionTimeout: connectionTimeout,
|
||||||
|
onFailedCall: onFailedCall,
|
||||||
|
onSucceededCall: onSucceededCall,
|
||||||
|
disconnectStreamsOnLostInternetConnection:
|
||||||
|
disconnectStreamsOnLostInternetConnection,
|
||||||
|
httpClientOverride: httpClientOverride,
|
||||||
|
) {
|
||||||
|
emailIdp = EndpointEmailIdp(this);
|
||||||
|
jwtRefresh = EndpointJwtRefresh(this);
|
||||||
|
greeting = EndpointGreeting(this);
|
||||||
|
modules = Modules(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final EndpointEmailIdp emailIdp;
|
||||||
|
|
||||||
|
late final EndpointJwtRefresh jwtRefresh;
|
||||||
|
|
||||||
|
late final EndpointGreeting greeting;
|
||||||
|
|
||||||
|
late final Modules modules;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, _isc.EndpointRef> get endpointRefLookup => {
|
||||||
|
'emailIdp': emailIdp,
|
||||||
|
'jwtRefresh': jwtRefresh,
|
||||||
|
'greeting': greeting,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {
|
||||||
|
'serverpod_auth_idp': modules.serverpod_auth_idp,
|
||||||
|
'serverpod_auth_core': modules.serverpod_auth_core,
|
||||||
|
};
|
||||||
|
}
|
||||||
109
halkyon_learn_client/lib/src/protocol/greetings/greeting.dart
Normal file
109
halkyon_learn_client/lib/src/protocol/greetings/greeting.dart
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||||
|
/* To generate run: "serverpod generate" */
|
||||||
|
|
||||||
|
// ignore_for_file: implementation_imports
|
||||||
|
// ignore_for_file: library_private_types_in_public_api
|
||||||
|
// ignore_for_file: non_constant_identifier_names
|
||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
// ignore_for_file: type_literal_in_constant_pattern
|
||||||
|
// ignore_for_file: use_super_parameters
|
||||||
|
// ignore_for_file: invalid_use_of_internal_member
|
||||||
|
|
||||||
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
|
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||||
|
|
||||||
|
/// A greeting message which can be sent to or from the server.
|
||||||
|
abstract class Greeting
|
||||||
|
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||||
|
Greeting._({
|
||||||
|
required this.message,
|
||||||
|
required this.author,
|
||||||
|
required this.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Greeting({
|
||||||
|
required String message,
|
||||||
|
required String author,
|
||||||
|
required DateTime timestamp,
|
||||||
|
}) = _GreetingImpl;
|
||||||
|
|
||||||
|
factory Greeting.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||||
|
return Greeting(
|
||||||
|
message: jsonSerialization['message'] as String,
|
||||||
|
author: jsonSerialization['author'] as String,
|
||||||
|
timestamp: _isc.DateTimeJsonExtension.fromJson(
|
||||||
|
jsonSerialization['timestamp'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The greeting message.
|
||||||
|
String message;
|
||||||
|
|
||||||
|
/// The author of the greeting message.
|
||||||
|
String author;
|
||||||
|
|
||||||
|
/// The time when the message was created.
|
||||||
|
DateTime timestamp;
|
||||||
|
|
||||||
|
/// Returns a shallow copy of this [Greeting]
|
||||||
|
/// with some or all fields replaced by the given arguments.
|
||||||
|
@_isc.useResult
|
||||||
|
Greeting copyWith({
|
||||||
|
String? message,
|
||||||
|
String? author,
|
||||||
|
DateTime? timestamp,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'__className__': 'Greeting',
|
||||||
|
'message': message,
|
||||||
|
'author': author,
|
||||||
|
'timestamp': timestamp.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJsonForProtocol() {
|
||||||
|
return {
|
||||||
|
'__className__': 'Greeting',
|
||||||
|
'message': message,
|
||||||
|
'author': author,
|
||||||
|
'timestamp': timestamp.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return _isc.SerializationManager.encode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GreetingImpl extends Greeting {
|
||||||
|
_GreetingImpl({
|
||||||
|
required String message,
|
||||||
|
required String author,
|
||||||
|
required DateTime timestamp,
|
||||||
|
}) : super._(
|
||||||
|
message: message,
|
||||||
|
author: author,
|
||||||
|
timestamp: timestamp,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Returns a shallow copy of this [Greeting]
|
||||||
|
/// with some or all fields replaced by the given arguments.
|
||||||
|
@_isc.useResult
|
||||||
|
@override
|
||||||
|
Greeting copyWith({
|
||||||
|
String? message,
|
||||||
|
String? author,
|
||||||
|
DateTime? timestamp,
|
||||||
|
}) {
|
||||||
|
return Greeting(
|
||||||
|
message: message ?? this.message,
|
||||||
|
author: author ?? this.author,
|
||||||
|
timestamp: timestamp ?? this.timestamp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
155
halkyon_learn_client/lib/src/protocol/protocol.dart
Normal file
155
halkyon_learn_client/lib/src/protocol/protocol.dart
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||||
|
/* To generate run: "serverpod generate" */
|
||||||
|
|
||||||
|
// ignore_for_file: implementation_imports
|
||||||
|
// ignore_for_file: library_private_types_in_public_api
|
||||||
|
// ignore_for_file: non_constant_identifier_names
|
||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
// ignore_for_file: type_literal_in_constant_pattern
|
||||||
|
// ignore_for_file: use_super_parameters
|
||||||
|
// ignore_for_file: invalid_use_of_internal_member
|
||||||
|
// ignore_for_file: dead_code, unnecessary_type_check
|
||||||
|
|
||||||
|
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||||
|
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
|
||||||
|
as _iacc;
|
||||||
|
import 'package:serverpod_auth_idp_client/serverpod_auth_idp_client.dart'
|
||||||
|
as _iaic;
|
||||||
|
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||||
|
import 'greetings/greeting.dart' as _izw8z7ou;
|
||||||
|
export 'greetings/greeting.dart';
|
||||||
|
export 'client.dart';
|
||||||
|
|
||||||
|
class Protocol extends _isc.SerializationManager {
|
||||||
|
Protocol._();
|
||||||
|
|
||||||
|
factory Protocol() => _instance;
|
||||||
|
|
||||||
|
static final Protocol _instance = Protocol._().._registerHostProtocols();
|
||||||
|
|
||||||
|
static String? getClassNameFromObjectJson(dynamic data) {
|
||||||
|
if (data is! Map) return null;
|
||||||
|
final className = data['__className__'] as String?;
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
T deserialize<T>(
|
||||||
|
dynamic data, [
|
||||||
|
Type? t,
|
||||||
|
]) {
|
||||||
|
t ??= T;
|
||||||
|
|
||||||
|
final dataClassName = getClassNameFromObjectJson(data);
|
||||||
|
if (dataClassName != null && dataClassName != getClassNameForType(t)) {
|
||||||
|
try {
|
||||||
|
return deserializeByClassName({
|
||||||
|
'className': dataClassName,
|
||||||
|
'data': data,
|
||||||
|
});
|
||||||
|
} on _isc.DeserializationClassNameNotFoundException catch (_) {
|
||||||
|
// If the className is not recognized (e.g., older client receiving
|
||||||
|
// data with a new subtype), fall back to deserializing without the
|
||||||
|
// className, using the expected type T.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t == _izw8z7ou.Greeting) {
|
||||||
|
return _izw8z7ou.Greeting.fromJson(data) as T;
|
||||||
|
}
|
||||||
|
if (t == _isc.getType<_izw8z7ou.Greeting?>()) {
|
||||||
|
return (data != null ? _izw8z7ou.Greeting.fromJson(data) : null) as T;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return _iaic.Protocol().deserialize<T>(data, t);
|
||||||
|
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
||||||
|
try {
|
||||||
|
return _iacc.Protocol().deserialize<T>(data, t);
|
||||||
|
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
||||||
|
return super.deserialize<T>(data, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? getClassNameForType(Type type) {
|
||||||
|
return switch (type) {
|
||||||
|
_izw8z7ou.Greeting => 'Greeting',
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? getClassNameForObject(Object? data) {
|
||||||
|
String? className = super.getClassNameForObject(data);
|
||||||
|
if (className != null) return className;
|
||||||
|
|
||||||
|
if (data is Map<String, dynamic> && data['__className__'] is String) {
|
||||||
|
return (data['__className__'] as String).replaceFirst(
|
||||||
|
'halkyon_learn.',
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data) {
|
||||||
|
case _izw8z7ou.Greeting():
|
||||||
|
return 'Greeting';
|
||||||
|
}
|
||||||
|
className = _iaic.Protocol().getClassNameForObject(data);
|
||||||
|
if (className != null) {
|
||||||
|
return className.contains('.')
|
||||||
|
? className
|
||||||
|
: 'serverpod_auth_idp.$className';
|
||||||
|
}
|
||||||
|
className = _iacc.Protocol().getClassNameForObject(data);
|
||||||
|
if (className != null) {
|
||||||
|
return className.contains('.')
|
||||||
|
? className
|
||||||
|
: 'serverpod_auth_core.$className';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic deserializeByClassName(Map<String, dynamic> data) {
|
||||||
|
var dataClassName = data['className'];
|
||||||
|
if (dataClassName is! String) {
|
||||||
|
return super.deserializeByClassName(data);
|
||||||
|
}
|
||||||
|
if (dataClassName == 'Greeting') {
|
||||||
|
return deserialize<_izw8z7ou.Greeting>(data['data']);
|
||||||
|
}
|
||||||
|
if (dataClassName.startsWith('serverpod_auth_idp.')) {
|
||||||
|
data['className'] = dataClassName.substring(19);
|
||||||
|
return _iaic.Protocol().deserializeByClassName(data);
|
||||||
|
}
|
||||||
|
if (dataClassName.startsWith('serverpod_auth_core.')) {
|
||||||
|
data['className'] = dataClassName.substring(20);
|
||||||
|
return _iacc.Protocol().deserializeByClassName(data);
|
||||||
|
}
|
||||||
|
return super.deserializeByClassName(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _registerHostProtocols() {
|
||||||
|
_iaic.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||||
|
_iacc.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getModuleName() => 'halkyon_learn';
|
||||||
|
|
||||||
|
/// Maps any `Record`s known to this [Protocol] to their JSON representation
|
||||||
|
///
|
||||||
|
/// Throws in case the record type is not known.
|
||||||
|
///
|
||||||
|
/// This method will return `null` (only) for `null` inputs.
|
||||||
|
Map<String, dynamic>? mapRecordToJson(Record? record) {
|
||||||
|
if (record == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return _iaic.Protocol().mapRecordToJson(record);
|
||||||
|
} catch (_) {}
|
||||||
|
try {
|
||||||
|
return _iacc.Protocol().mapRecordToJson(record);
|
||||||
|
} catch (_) {}
|
||||||
|
throw Exception('Unsupported record type ${record.runtimeType}');
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue