From an empty Flutter project to a client that connects, enters a zone and sees a peer move. Do it on Linux, macOS or Windows desktop first: it is the build you can drive directly, and the offline demo needs no credential.
dependencies:
yingyeothon_gamebase_client:
git:
url: https://github.com/yingyeothon/flutterlib.git
path: packages/yingyeothon_gamebase_client
ref: v0.2.0
yingyeothon_auth_client:
git:
url: https://github.com/yingyeothon/flutterlib.git
path: packages/yingyeothon_auth_client
ref: v0.2.0The siblings each one needs (codec, logger) are path dependencies inside the same
checkout, so nothing else is declared; keep every entry on the same ref:. The
packages are pure Dart, so this works on every platform.
| Value | Where | Goes into |
|---|---|---|
gateway origin, e.g. wss://gw.yyt.life |
the lobby channel page, wsUrl (drop the query) |
GatewayClientOptions.url |
lobby channel id, lobby_… |
the same page | GatewayClientOptions.channelId |
auth base URL, e.g. https://auth.yyt.life |
the auth channel page | AuthClient(baseUrl:) |
auth channel id, auth_… |
the same page | AuthClient(channelId:) |
Console and options lists every field. Pass them with
--dart-define=YYT_GATEWAY_URL=… and friends, never as literals in the tree.
A channel JWT is the only credential. The shortest path on a phone is the browser redirect; on a desktop or in a test you can paste one. Authentication has both flows; the essence:
import 'package:yingyeothon_auth_client/yingyeothon_auth_client.dart';
final auth = AuthClient(baseUrl: Uri.parse(authBaseUrl), channelId: authChannelId);
final nonce = AuthClient.newNonce();
final start = auth.buildStartUrl(provider: 'github', redirect: myRedirect, nonce: nonce);
// open `start` in the browser; when the app receives `returned`:
final token = auth.parseRedirect(returned, expectedNonce: nonce);import 'package:yingyeothon_gamebase_client/yingyeothon_gamebase_client.dart';
final lobby = GatewayLobbyClient(GatewayLobbyClientOptions(
url: gatewayUrl,
channelId: lobbyChannelId,
token: token.jwt,
));
lobby.snapshots.listen((_) => setState(() {}));
lobby.peerEntered.listen((_) => setState(() {}));
lobby.peerLeft.listen((_) => setState(() {}));
lobby.peerMoved.listen((_) => setState(() {}));
lobby.stopped.listen((e) => showDialog(context: context, builder: (_) => Text(e.reason)));
final hello = await lobby.connect();connect() completes when the gateway's hello arrives, not when the socket opens;
after that lobby.hello, lobby.capabilities and lobby.peers are filled. It throws
GatewayStoppedException if the connection stops first (a refused token, a gone
channel), so await it in a try.
The app is one of these pieces; the packages are the middle two:
flowchart LR
app["your Flutter app"] --> lobby["GatewayLobbyClient"]
app --> auth["AuthClient"]
lobby --> transport["web_socket_channel<br/>(io or web)"]
transport --> gw["gateway"]
auth --> authsvc["auth service"]
lobby --> map["map fetch (http)"]
lobby.pos(zone: hello.zone, x: 3, y: 4, dir: 'n');The first pos enters the zone; the gateway answers with a snapshot of everyone in
view, and from then on peerEntered, peerLeft and peerMoved keep lobby.peers
current. Render from lobby.peers.all(), once per hello.tick milliseconds at most.
hello.zone is where a new player starts. A returning player whose position the
gateway wrote less than 30 minutes ago is already placed there, and a first pos far
from it in that zone is refused, as is every step after it, until the socket closes
with 4003. Read A retained position before you
ship; it has the few lines that handle it.
Lobby covers chat, events, parties and the map.
flutter run -d linux \
--dart-define=YYT_GATEWAY_URL=wss://gw.yyt.life \
--dart-define=YYT_CHANNEL_ID=lobby_… \
--dart-define=YYT_AUTH_BASE_URL=https://auth.yyt.life \
--dart-define=YYT_AUTH_CHANNEL_ID=auth_…Or skip all four and run the playground with its Offline demo: it starts a fake gateway in the process and your client talks to it over the real transport. That is also how the SDK's own end-to-end tests run.
- Every close code and what the client does: Connection lifecycle.
- Every refusal you can get back: Errors.
- Announcements and a player's own record over HTTP, with the same token: Key-value store.
- Web, background,
dispose(): Flutter.