Game kits
Small libraries that add CEEJAY's Connect with Discord button to a game made with Swift, Kotlin, Godot or Flutter.
A kit lets a game link its player to their Discord account in your community. It does Connect and nothing else: it starts a connect session, returns the link, the code and the QR image to the game, polls for the result, waits for the player's confirmation, keeps the player signed in, renews the player session, reads the member card and disconnects. It draws nothing: the game shows the button. Rewards and the shop are not in the kits.
Every kit does what the Connect client of the TypeScript SDK does, request for request, with the same timings (polling for the result every 3 seconds, a player session renewed a minute before it ends, two retries after a failure) and the same four states of the button.
Coming soon
The kits are not published yet. Each one is a folder of the CEEJAY repository, under sdk/kits, with its own README, and you add it to your game from that folder. Their names are not final until the first release.
The kits
| Kit | For | Folder | Status |
|---|---|---|---|
Swift package CeejayConnect | iPhone, iPad, Mac | sdk/kits/swift | Built and tested on a Mac. The parts that exist only on iPhone and iPad are written but not compiled |
Kotlin library com.ceejay.connect | Android, and Java games on a computer | sdk/kits/kotlin | Built and tested (53 tests). The Android-only parts are written but not run |
Godot 4 addon ceejay_connect | Godot games on any platform | sdk/kits/godot | Built and tested on Godot 4.2 and 4.7 (63 tests) |
Flutter package ceejay_connect | Flutter games | sdk/kits/flutter | Built and tested (56 tests). The phone's secure storage is written but not run |
TypeScript, @communitystudios/ceejay/connect | Browser games, and games that run JavaScript | Connect client | Built and tested |
Three more packages have pages of their own. The Unity package and the Unreal plugin are previews: their logic is tested, the Unreal plugin builds in Unreal Engine 5.8, and neither has been run in a game yet.
| Package | For | Folder | Status |
|---|---|---|---|
C# and .NET, Ceejay.Connect | C# games, and a C# server with Ceejay | sdk/dotnet | Published on NuGet (454 tests) |
Unity, com.ceejay.connect | Unity games | sdk/unity/com.ceejay.connect | Preview. The core is the C# one above. The Unity layer is being checked in the editor |
Unreal Engine, CeejayConnect | Unreal games | sdk/unreal/CeejayConnect | Preview. The logic is tested (203 cases). The engine layer is being checked in Unreal |
A game made with another engine uses Connect for any engine, which gives the seven web requests with their exact headers and bodies.
How a player connects
The same in every kit:
The game starts the session for its player. The response has a link, a code and the URL of a QR image, and the kit stores the session on the device.
The game opens the link in a browser, or shows the code and the image on a screen with no browser. The player approves with their Discord account on CEEJAY's connect page.
The kit polls for the result every 3 seconds while the game is in the foreground, and immediately when the player returns. Once Discord returns the member's name, the kit passes it to the game. The game shows it with Confirm and Cancel, and the kit sends the player's choice. There is no default.
Both parties have approved. The kit stores the tokens in the device's secure store and moves to connected.
The connection is saved when both the Discord member and the game have approved. This confirmation matters: a code shown on a stream can be approved by anyone who reads it, so the player can reject a name that isn't theirs.
The same example in each kit
let connect = try CeejayConnect(publicKey: "ceejay_pk_…", storage: KeychainConnectStorage())
try await connect.restore() // when the game starts
let session = try await connect.start(StartParams(playerId: "p_19"))
showWaiting(code: session.code, qr: session.qrURL) // always show the code too
Task { try? await connect.open(session, using: WebAuthenticationSessionOpener()) }
let outcome = try await connect.waitForResult { member, _ in // asks every 3 seconds
await askThePlayer("Connect to \(member.displayName ?? "this Discord account")?")
}The same names in each kit
| What | Swift | Kotlin | Godot | Flutter |
|---|---|---|---|---|
| Start a session | start | start | start | start |
| Start from a code generated in Discord | startWithCode | startWithCode | start_with_code | startWithCode |
| Wait for the result and confirmation | waitForResult | waitForResult | wait_for_result | waitForResult |
| Poll once, send the confirmation, cancel | getResult, confirm, cancel | getResult, confirm, cancel | get_result, confirm, cancel | getResult, confirm, cancel |
| Is this player connected | getStatus | getStatus | get_status | getStatus |
| Restore state on launch | restore | restore | restore | restore |
| The player session for a request | playerSession | playerSession | player_session | playerSession |
| The member card | getProfile | getProfile | get_profile | getProfile |
| Disconnect | disconnect | disconnect | disconnect_player | disconnect |
| The state of the button | state, attention | state, attention | get_state, get_attention | state, attention |
| Where to send the player | joinUrl, manageUrl | joinUrl, manageUrl | get_join_url, get_manage_url | joinUrl, manageUrl |
| The open session | session | session | get_session | session |
| Subscribe to changes | onChange, stateUpdates | onChange, stateFlow | state_changed | onChange, stateChanges |
| Poll immediately | pollNow | pollNow | poll_now | pollNow |
The four states are the same everywhere: not connected, waiting, connected and needs attention. Needs attention has four reasons: the Discord account never joined the server, it left the server, Discord's membership screening is still open, or the game shares something new since the player last gave consent.
Opening the link
Never open the link in a web view inside the game: Discord blocks sign-in there. The kit never reads the window or the sheet it opened. It polls for the result, so nothing depends on a phone switching back to the game.
| Device | Open |
|---|---|
| iPhone, iPad | The system's web sign-in sheet (WebAuthenticationSessionOpener in the Swift kit), or Safari |
| Android | A browser tab (CustomTabsLinkOpener in the Kotlin kit) |
| Computer | The system browser |
| Browser game | A pop-up window opened by the player's own click |
| Console, television | Nothing. Show the code, the dashboard's URL followed by /connect, and the QR image |
What a kit stores
Two things must persist after the game is closed, and both are private to the device: the refresh token, and the open session with the secret that reads its result. Every kit writes them as one JSON string under one key, ceejay.connect, in the device's secure store: the Keychain on Apple platforms, the Keystore on Android. The Godot addon cannot reach either, and says so: it stores an encrypted file, and has an interface for a store of your own.
What a kit sends
A kit communicates only with your community's CEEJAY instance. It sends the two headers Authorization and Content-Type, and these values:
- the player ID your game gives it, and the player's name, language and age, if you pass them;
- the device's sign-in: the refresh token and the player session that CEEJAY issued to this device, and, if you pass one, the Firebase or Unity token, the Steam ticket or the connect pass that proves the player.
It sends nothing else: no advertising ID, no device ID, no location, no contacts, no analytics and no crash reports. The Discord sign-in happens in the browser on CEEJAY's page, not in your game. As with any request, CEEJAY's server sees the device's IP address.
This is what to put on the store's privacy form for the kit. Your own declarations depend on everything else your game does.
| Store | Declare |
|---|---|
| App Store, App Privacy | Identifiers, User ID: collected, linked to the user, used for App Functionality, not used for tracking. Contact Info, Name, if you pass the player name |
| Google Play, Data safety | Personal info, User IDs: collected, not shared, required for app functionality. Name, if you pass the player name |
Related
- Connect explains the flow, the confirmation from both the member and the game, and the proof levels.
- Connect for any engine gives the seven requests as plain HTTP.
- The Connect client of the TypeScript SDK is the reference every kit follows.
- Errors lists every code of Connect.

