crdt_socket_sync_serverpod 0.1.0
crdt_socket_sync_serverpod: ^0.1.0 copied to clipboard
Serverpod adapter for crdt_socket_sync - serve CRDT sync and relay WebSocket sessions from a Serverpod web server.
CRDT Socket Sync — Serverpod #
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 Serverpod backend. New to CRDTs? Start from the
crdt_lf documentation.
What it does #
crdt_socket_sync for Serverpod.
It gives Serverpod web routes, ready to add to your pod, that serve
crdt_socket_sync sessions: CrdtSyncRoute for the server–client mode and
CrdtRelayRoute for the relay mode. Clients connect with the plain
WebSocketClient / WebSocketRelayClient of crdt_socket_sync.
- How the sync works (modes, protocol, plugins, persistence): the
crdt_socket_syncdocumentation. - Routes, sessions, authentication and server configuration: the Serverpod documentation.
Installation #
dart pub add crdt_socket_sync_serverpod
One library per communication mode, like crdt_socket_sync itself:
// Server–client mode: CrdtSyncRoute, DocumentSessionHost
import 'package:crdt_socket_sync_serverpod/sync.dart';
// Relay mode: CrdtRelayRoute, RelaySessionHost
import 'package:crdt_socket_sync_serverpod/relay.dart';
Quick start #
Relay mode #
import 'package:crdt_socket_sync/relay_server.dart' show InMemoryRelayStore;
import 'package:crdt_socket_sync_serverpod/relay.dart';
import 'package:serverpod/serverpod.dart';
import 'src/generated/endpoints.dart';
import 'src/generated/protocol.dart';
Future<void> main(List<String> args) async {
final relayHost = RelaySessionHost(store: InMemoryRelayStore());
final pod = Serverpod(args, Protocol(), Endpoints());
pod.webServer.addRoute(CrdtRelayRoute(relayHost), '/relay');
await pod.start();
}
Clients connect with WebSocketRelayClient from crdt_socket_sync, pointed at
the web server's /relay (ws://localhost:8080/relay in the examples).
Server–client mode #
import 'package:crdt_socket_sync/server.dart' show InMemoryCRDTServerRegistry;
import 'package:crdt_socket_sync_serverpod/sync.dart';
final syncHost = DocumentSessionHost(
serverRegistry: InMemoryCRDTServerRegistry(),
);
pod.webServer.addRoute(CrdtSyncRoute(syncHost), '/sync');
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.
Authenticating before the upgrade #
The route upgrades in handleCall. Subclass it and refuse first: a Response
is an ordinary HTTP answer, and super.handleCall is the upgrade.
class AuthSyncRoute extends CrdtSyncRoute {
AuthSyncRoute(super.sessionHost);
@override
FutureOr<Result> handleCall(Session session, Request request) {
if (!session.isUserSignedIn) {
return Response.unauthorized();
}
return super.handleCall(session, request);
}
}
A rejected client gets a plain 401 and never reaches the protocol.
Serverpod resolves the web route's session from the Authorization header,
through the authenticationHandler given to the Serverpod constructor. Set
one: without it, any request that carries the header fails with a 500
before handleCall runs. A malformed header gets a 400.
Browsers cannot set headers on a WebSocket. For a browser client, send the key
in a query parameter or a cookie, and check it yourself in handleCall.
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. Serverpod handles SIGINT and SIGTERM by itself and runs its
shutdown tasks after the web server has stopped. That is the place to dispose
the host:
pod.experimental.shutdownTasks.addTask('crdt_sync', syncHost.dispose);
shutdownTasks is part of Serverpod's experimental API, which may change
between minor versions.
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 #
- The
Sessioncloses before the socket opens. Serverpod closes it as soon ashandleCallreturns. Read what you need from it before the upgrade. - An
Authorizationheader needs anauthenticationHandler. Serverpod's default handler throws, so the request fails with a500before the route runs. - Name clashes.
serverpodexports Relic'sHandlerandMessage, which clash withHandlerfromcrdt_lfandMessagefromcrdt_socket_sync. In a file that needs both, hide one:import 'package:serverpod/serverpod.dart' hide Message;. - 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 Serverpod servers, one per mode. They build the pod without a
database and without generated code, to stay small: a real project passes its
generated Protocol() and Endpoints().
example/— server–client mode on/sync, documents kept in Hive throughcrdt_lf_hive, a token check in front of the upgrade.relay_example/— relay mode on/relay, rooms kept in memory.
cd example # or relay_example
dart run bin/main.dart
The greyhound_markdown app also runs locally
against a Serverpod relay server:
server_serverpod/.
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: