crdt_socket_sync_dart_frog 0.1.0+1
crdt_socket_sync_dart_frog: ^0.1.0+1 copied to clipboard
Dart Frog adapter for crdt_socket_sync - serve CRDT sync and relay WebSocket sessions from a Dart Frog backend.
CRDT Socket Sync — Dart Frog #
This package is one of the sync adapters for popular Dart server
frameworks. It keeps the documents of the crdt_lf library in sync
through a Dart Frog backend. New to CRDTs? Start from the
crdt_lf documentation.
What it does #
crdt_socket_sync for Dart Frog.
It gives Dart Frog handlers, ready to return from a route, that serve
crdt_socket_sync sessions: crdtSyncWebSocketHandler for the server–client
mode and crdtRelayWebSocketHandler for the relay mode, plus a middleware
provider for each host. Clients connect with the plain WebSocketClient /
WebSocketRelayClient of crdt_socket_sync.
- How the sync works (modes, protocol, plugins, persistence): the
crdt_socket_syncdocumentation. - Routes, middleware, WebSocket options and server setup: the Dart Frog documentation.
Installation #
dart pub add crdt_socket_sync_dart_frog
One library per communication mode, like crdt_socket_sync itself:
// Server–client mode: crdtSyncWebSocketHandler, crdtSyncHostProvider,
// DocumentSessionHost
import 'package:crdt_socket_sync_dart_frog/sync.dart';
// Relay mode: crdtRelayWebSocketHandler, crdtRelayHostProvider,
// RelaySessionHost
import 'package:crdt_socket_sync_dart_frog/relay.dart';
Quick start #
Relay mode #
// lib/src/host.dart — built once, shared by every request
import 'package:crdt_socket_sync/relay_server.dart';
final relayHost = RelaySessionHost(store: InMemoryRelayStore());
// routes/_middleware.dart
import 'package:crdt_socket_sync_dart_frog/relay.dart';
import 'package:dart_frog/dart_frog.dart';
import '../lib/src/host.dart';
Handler middleware(Handler handler) {
return handler.use(crdtRelayHostProvider(relayHost));
}
// routes/relay.dart
import 'package:crdt_socket_sync_dart_frog/relay.dart';
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
return crdtRelayWebSocketHandler(context.read<RelaySessionHost>())(context);
}
Clients connect with WebSocketRelayClient from crdt_socket_sync, pointed at
ws://localhost:8080/relay.
Server–client mode #
// lib/src/host.dart
import 'package:crdt_socket_sync/server.dart';
final syncHost = DocumentSessionHost(
serverRegistry: InMemoryCRDTServerRegistry(),
);
// routes/_middleware.dart
Handler middleware(Handler handler) {
return handler.use(crdtSyncHostProvider(syncHost));
}
// routes/sync.dart
import 'package:crdt_socket_sync_dart_frog/sync.dart';
import 'package:dart_frog/dart_frog.dart';
Future<Response> onRequest(RequestContext context) async {
return crdtSyncWebSocketHandler(context.read<DocumentSessionHost>())(context);
}
Use a persistent registry (PersistentServerRegistry with crdt_lf_hive,
crdt_lf_sqlite, crdt_lf_drift) for anything that must survive a restart —
example/ does, with Hive. A registry is opened asynchronously,
so build it in Dart Frog's init() (see Lifecycle) and keep it
in a top-level late final.
Authenticating before the upgrade #
The handler is a value: check the request first, and call the handler only when the check passes.
Future<Response> onRequest(RequestContext context) async {
final token = context.request.headers['authorization'];
if (!await isAuthorized(token)) {
return Response(statusCode: HttpStatus.unauthorized);
}
return crdtSyncWebSocketHandler(context.read<DocumentSessionHost>())(context);
}
A rejected client gets a plain 401 and never reaches the protocol.
Lifecycle #
Build the host once, outside the request. A host per request would give every client its own empty room.
start() is optional: a host with no transport of its own starts on its first
connection. Call it explicitly from a
custom init method
when you also want an orderly shutdown — Dart Frog has no shutdown hook, so
signals are the place for it. init() runs once, before the handler tree is
built; the
custom entrypoint
run() is called again on every hot reload, so a listener registered there
would pile up:
// main.dart
import 'dart:io';
import 'package:dart_frog/dart_frog.dart';
Future<void> init(InternetAddress ip, int port) async {
await relayHost.start();
for (final signal in [ProcessSignal.sigint, ProcessSignal.sigterm]) {
signal.watch().listen((_) async {
await relayHost.dispose();
exit(0);
});
}
}
dispose() closes the open sessions and, in server–client mode, the registry
under them — for a durable registry that is where pending writes get flushed,
so skipping it loses data. A RelayStore has no close: a durable one is yours
to close after dispose().
Gotchas #
Handleris ambiguous.crdt_lfexports a CRDTHandleranddart_frogexports a requestHandler. In a file that needs both, hide one:import 'package:crdt_lf/crdt_lf.dart' hide Handler;.- One plugin instance per host. A
ServerSyncPluginbinds to the host it is given to, once; handing the same instance to a second host throws aLateInitializationErrorfar from the line that caused it. - A refused connection is closed, not rejected. Once the response is a
101, there is no status code left to send. A host that is stopped or disposed closes the socket instead, which the client sees as its stream ending.
Examples #
Two runnable Dart Frog projects, one per mode, each with a custom init()
that starts the host and a custom entrypoint:
example/— server–client mode on/sync, documents kept in Hive throughcrdt_lf_hive, a token check in front of the route.relay_example/— relay mode on/relay, rooms kept in memory.
cd example # or relay_example
dart_frog dev
The greyhound_markdown app also runs locally
against a Dart Frog relay server:
server_dart_frog/.
Roadmap #
A roadmap is available in the project page.
Apps #
- greyhound_markdown — Real-time collaborative markdown editor built on crdt_lf
Packages #
Other bricks of the crdt "system" are: