XMDT — Cocos Creator
An npm package, @alogame/kyc-sdk, that implements the whole XMDT flow in pure TypeScript — it draws its own screens with Cocos's own UI components and calls Ekycx's four session endpoints directly over HTTPS. Unlike the Android, iOS, and Unity SDKs, there is no native code, no .aar, no .xcframework, and nothing to embed into your build — the entire package is .ts source your project compiles the same way it compiles its own scripts.
This package has been typechecked against Cocos Creator 3.8's official API declarations, and its business logic has 119 automated tests, but it has not yet been run inside a real Cocos Creator project, on a real device, or in the Editor. Treat everything on this page as accurate-on-paper, not confirmed. If you're evaluating this for a real integration, ask before relying on it.
Everything on this page describes the dev environment (api-xmdt.dev.alogame.vn). Production has not been provisioned yet.
Server Integration covers the one call your server makes to mint a sessionToken. Your client never talks to Alogame's identity API directly and never sees your HMAC secret.
Requirements: Cocos Creator 3.8+.
Why this exists alongside the Android/iOS/Unity SDKs
The native SDKs (and the Unity package that wraps them) need a physical device to run at all — Cocos's Editor preview and simulator can't load native binaries. For a Cocos game, that means every single integration step — trying a new UI layout, handling a new Failed reason, anything — requires a full device build. This package trades some UI polish (see "What's different from the native SDKs" below) to run entirely inside Cocos Creator's own preview, the same way the rest of your game does.
1. Add the package
npm install @alogame/kyc-sdk
Cocos Creator resolves it like any other npm dependency in your project — no extension, no manual Project Settings changes.
2. Initialize
Once, at startup:
import { init } from "@alogame/kyc-sdk";
init("dev");
There is no way to point the SDK at a custom URL — env is the only switch, and only "dev" is usable today.
3. Set the game role
After you have a sessionToken from your server (see Server Integration):
import { setGameRole } from "@alogame/kyc-sdk";
setGameRole(
"player_account_name", // uid — must match the `username` your server minted the session for
sessionToken,
"server_01", // optional
"character_123", // optional
);
uid and sessionToken always arrive together from the same server response — never call this with one and not the other. Switching to a different uid clears any cached verified state for the previous one.
4. Show the screen
import { show } from "@alogame/kyc-sdk";
import type { AlogameKycListener, AlogameKycResult } from "@alogame/kyc-sdk";
function startXmdt(): void {
show(listener);
}
const listener: AlogameKycListener = {
onResult(result: AlogameKycResult) {
switch (result.kind) {
case "success":
break; // UI signal only — see the warning below
case "failed":
console.warn("XMDT failed:", result.reason, result.message);
break;
case "cancelled":
break; // Player closed the form — only possible when xmdtRequired was false
}
},
onSessionTokenNeeded(uid: string) {
// see "Refreshing an expired token" below
},
};
onResult is a UI signal, not proofsuccess means the player's device says the flow completed — a modified client can produce this callback without verifying anything. Before letting a player past anything that depends on XMDT, call POST /xmdt/session again from your server and trust the xmdtCompleted field on that response. See Server Integration.
onResult fires exactly once per show(), after the screen has fully closed — you can present your own UI in it directly. onSessionTokenNeeded may fire zero or more times before it.
The SDK owns its screen entirely — full name, date of birth, phone entry, OTP and consent are handled internally, and nothing typed there is ever visible to your game.
Calling show() again while a flow is already open is ignored (logged, not crashed).
If the session says the player already completed XMDT, show() short-circuits straight to success without presenting anything. That is intended — but when testing, it means each uid only exercises the flow once. Use a fresh uid per attempt.
Handling failed
switch (result.reason) {
case "notInitialized":
case "noGameRole":
// Integration bug on your side — init()/setGameRole() wasn't called
// first. Never show this to a player; fix the call order.
break;
case "sessionUnavailable":
case "sessionInvalid":
// Could not get a usable session token. Mint a fresh one and retry.
break;
case "otpAttemptsExceeded":
case "otpUnavailable":
case "rateLimited":
// An abuse limit was hit. Show a generic "try again later" — do not
// expose which limit it was.
break;
case "serviceUnavailable":
// Identity service was down after the player already retried inside
// the screen. Let them try again later.
break;
case "unknownError":
// Forward-compatibility catch-all. Never a crash.
break;
}
Recoverable problems — a mistyped OTP, a validation error, a transient network blip — are handled inside the SDK's screen and never reach onResult. Every reason above is one where the flow could not continue, not one where the player simply made a typo.
Refreshing an expired token
Unlike the Unity package, a JS closure has no boundary to cross, so tokenProvider is exposed directly:
init("dev", (uid, callback) => {
mintSessionToken(uid).then((token) => callback(token)).catch(() => callback(null));
});
If you don't pass a tokenProvider, respond to onSessionTokenNeeded instead:
import { provideSessionToken, abortPendingToken } from "@alogame/kyc-sdk";
onSessionTokenNeeded(uid: string) {
mintSessionToken(uid)
.then((token) => (token ? provideSessionToken(token) : abortPendingToken()))
.catch(() => abortPendingToken());
}
Respond with exactly one of the two paths. If you implement neither, a stale token ends the flow with sessionUnavailable rather than hanging.
What's different from the native SDKs
Nothing here is hidden or accidental — each is a disclosed trade-off for running without native code:
- No screenshot protection. The Android/iOS SDKs enable
FLAG_SECURE/an equivalent. This package doesn't: there's no legal requirement for it, and the screen only shows data the player is typing themselves. - No SMS auto-fill. The OTP code must be typed (or pasted — see below), never auto-detected from an incoming SMS.
- "Paste code" only works in Cocos Creator's browser-based Editor preview, not in a native build. Cocos's engine has no clipboard-read API at all outside the browser; on a native build, the player types the code manually.
- The 6-digit OTP code is a single input field, not six separate auto-advancing boxes like the native SDKs. Cocos's text input component has no confirmed way to move focus between fields programmatically.
- Vietnamese only — matching the native SDKs, which dropped English in an earlier product decision.
Sample
Not published yet — this package has not been run inside a real Cocos Creator project (see the warning at the top of this page).
Reference
| Member | Purpose |
|---|---|
init(env, tokenProvider?) | Call once before anything else |
setGameRole(uid, sessionToken, serverId?, roleId?) | Always both uid and sessionToken together |
setPrefill(fullName) | Optional — prefills the name field |
isVerified() | In-memory cache, UI state only — never a gate |
show(listener) | Presents the screen |
provideSessionToken(token) / abortPendingToken() | Answer onSessionTokenNeeded |
AlogameKycEnv | "dev" | "prod" ("prod" not yet available) |
AlogameKycResult | {kind: "success"} | {kind: "failed", reason, message?} | {kind: "cancelled"} |
AlogameKycFailReason | 9 closed values — see above |
AlogameKycListener | onResult (required), onSessionTokenNeeded (optional) |
isVerified() reads an in-memory cache from the most recent successful flow — not an authorization source, same caveat as onResult. Use it for UI state only, never as the gate itself.