XMDT — iOS
Native SDK, no WebView. Swift Package, module AlogameKycKit (the entry class is AlogameKycSdk — see Initialize — but the thing you import is AlogameKycKit). Independent of SDK v2 — do not mix with OegSdkV2 imports or expect them to share any state.
Everything on this page describes the dev environment (api-xmdt.dev.alogame.vn). Production has not been provisioned yet — do not point a live build at it.
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.
Deployment target: iOS 15+. Works from both Swift and Objective-C — see Objective-C below for the Objective-C-facing API.
1. Add the package
Xcode → File → Add Package Dependencies…
https://github.com/alo-game/alogame-kyc-sdk-ios
Under Dependency Rule, pick Up to Next Major starting at 0.2.0 (or any later published tag — see releases). When the picker asks which product to add to your target, choose AlogameKycKit.
0.1.xSPM's "Up to Next Major" treats the leading non-zero component of a 0.x version as the major — a rule pinned at 0.1.1 resolves to >=0.1.1, <0.2.0 and will never pick up 0.2.0 (the Objective-C release) on its own. Bump your version rule's lower bound to 0.2.0 to get it.
This is a separate, public release repo that ships a prebuilt .xcframework per tag — not the private gitlab.oeg.vn source repo (which your Xcode has no access to anyway). Each release is built by scripts/deploy_ios_spm.sh in the dev repo.
The package declares zero third-party dependencies — only Foundation, UIKit, and SafariServices link, nothing that can conflict with your app's own dependency tree.
2. Initialize
Once, at app start:
import AlogameKycKit
AlogameKycSdk.shared.initialize(
AlogameKycConfig(env: .dev)
)
There is no way to point the SDK at a custom URL — env is the only switch, and only .dev is usable today (see Overview).
3. Set the game role
After you have a sessionToken from your server (see Server Integration):
AlogameKycSdk.shared.setGameRole(
uid: "player_account_name", // must match the `username` your server minted the session for
sessionToken: sessionToken,
serverId: "server_01", // optional — omit if not applicable
roleId: "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 AlogameKycKit
class YourViewController: UIViewController, AlogameKycListener {
func startXmdt() {
AlogameKycSdk.shared.show(from: self, listener: self)
}
func onResult(_ result: AlogameKycResult) {
switch result {
case .success:
break // UI signal only — see the warning below before unlocking anything
case .failed(let reason, let message):
print("XMDT failed: \(reason)" + (message.map { " (\($0))" } ?? ""))
case .cancelled:
break // Player closed the form — only possible when xmdtRequired was false
}
}
func onSessionTokenNeeded(uid: String) {
// see "Refreshing an expired token" below
}
}
onResult is a UI signal, not proof.success here means the player's device says the flow completed — a modified client can produce this callback without ever 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 instead. See Server Integration.
AlogameKycListener is held weak by the SDK — a strong reference from a singleton to your view controller would be a retain cycle you can't see or break, so make sure whatever you pass conforms and stays alive for the duration of the flow (a view controller already presenting the screen naturally does).
The SDK owns its screen entirely — full name, date of birth, phone entry, OTP, and consent are all handled internally, and nothing typed there is ever visible to your app. onResult fires exactly once per show() call, on the main queue, after the screen has already been dismissed — safe to present your own UI immediately.
Calling show() again while a flow is already open is ignored (logged, not crashed).
Handling .failed
switch reason {
case .notInitialized, .noGameRole:
// Integration bug on your side — initialize()/setGameRole() wasn't
// called first. Never show this to a player; fix the call order.
break
case .sessionUnavailable, .sessionInvalid:
// Could not get a usable session token. Mint a fresh one and retry
// show() from scratch.
break
case .otpAttemptsExceeded, .otpUnavailable, .rateLimited:
// The player (or their network) hit an abuse limit. Show a generic
// "try again later" — do not expose which limit was hit.
break
case .serviceUnavailable:
// Alogame's identity service was down after the player already
// retried once inside the screen. Let them try show() again later.
break
case .unknownError:
// Forward-compatibility catch-all. Never a crash.
break
}
Recoverable problems (bad OTP entry, a validation error, a transient network blip) are handled inside the SDK's own screen and never reach onResult at all — every .failed reason above is one where the flow could not continue, not one where the player simply made a typo.
Refreshing an expired token
Two ways to supply a fresh sessionToken when the one from setGameRole goes stale mid-flow — pick one:
Closure-based (works if your app can hold a closure across the SDK boundary):
AlogameKycSdk.shared.initialize(
AlogameKycConfig(env: .dev, tokenProvider: { uid, callback in
// Call your server's mint endpoint again for this uid, then:
callback(newSessionTokenOrNil)
})
)
Delegate-based (for a bridge/engine layer that can't pass a closure across a language boundary — leave tokenProvider unset and implement instead):
func onSessionTokenNeeded(uid: String) {
// Mint a new token for `uid`, then:
AlogameKycSdk.shared.provideSessionToken(newToken)
// or, if you can't get one:
AlogameKycSdk.shared.abortPendingToken()
}
If you don't implement either, a stale token ends the flow with .sessionUnavailable rather than hanging.
Orientation
The screen supports both portrait and landscape — nothing to configure on your side.
Objective-C
Same package, same AlogameKycSdk singleton — nothing separate to add. AlogameKycResult itself can't cross into Objective-C (it's a Swift enum carrying a different payload per case — .failed(reason:, message:) — and Objective-C has no equivalent of that), so the Objective-C-facing surface uses a small parallel listener protocol plus two Int-backed enums instead:
#import <AlogameKycKit/AlogameKycKit-Swift.h>
@interface MyKycListener : NSObject <AlogameKycObjcListener>
@end
@implementation MyKycListener
- (void)onResultWithStatus:(AlogameKycResultStatus)status
reason:(AlogameKycFailReasonCode)reason
message:(NSString * _Nullable)message {
switch (status) {
case AlogameKycResultStatusSuccess:
break; // UI signal only — see the warning above before unlocking anything
case AlogameKycResultStatusFailed:
NSLog(@"XMDT failed, reason=%ld", (long)reason);
break;
case AlogameKycResultStatusCancelled:
break;
}
}
- (void)onSessionTokenNeededForUid:(NSString *)uid {
// see "Refreshing an expired token" above
}
@end
[[AlogameKycSdk shared] initializeWithEnv:AlogameKycEnvDev tokenProvider:nil];
[[AlogameKycSdk shared] setGameRoleWithUid:@"player_account_name"
sessionToken:sessionToken
serverId:nil
roleId:nil];
MyKycListener *listener = [MyKycListener new];
[[AlogameKycSdk shared] showFromViewController:self objcListener:listener];
AlogameKycSdk.shared, showFromViewController:objcListener:, initializeWithEnv:tokenProvider:, and the enums/protocol above are confirmed by a real xcodebuild build-and-test run against the iOS Simulator SDK, including the exact generated AlogameKycKit-Swift.h header the snippets above are taken from. What that run does not cover is a live Objective-C consumer app calling into it end to end — if anything here doesn't match what you see, tell us.
Reference
| Type | Purpose |
|---|---|
AlogameKycSdk.shared | The entire public API — initialize, setGameRole, setPrefill, isVerified, show, provideSessionToken, abortPendingToken |
AlogameKycConfig(env:tokenProvider:) | Passed once to initialize |
AlogameKycEnv | .dev | .prod (.prod not yet available) |
AlogameKycResult | .success | .failed(reason:message:) | .cancelled |
AlogameKycFailReason | 9 closed cases — see above |
AlogameKycListener | onResult(_:), optional onSessionTokenNeeded(uid:) |
AlogameKycObjcListener | Objective-C-facing listener — onResultWithStatus:reason:message:, optional onSessionTokenNeededForUid: |
AlogameKycResultStatus | Int-backed mirror of AlogameKycResult — Success | Failed | Cancelled |
AlogameKycFailReasonCode | Int-backed mirror of AlogameKycFailReason, same 9 cases |
AlogameKycSdk.shared.setPrefill(_:) is optional — if your login layer already has the player's name (e.g. from Sign in with Apple), pass it here before show() to prefill the form field. It's held in memory only and never sent anywhere until the player has seen and can edit it.
fullName only arrives onceASAuthorizationAppleIDCredential.fullName is populated only on the first authorization for a given Apple ID. Capture it there and pass it to setPrefill — it's gone on every subsequent login until the player revokes your app in Settings.
AlogameKycSdk.shared.isVerified reads an in-memory cache from the most recent successful flow — not an authorization source, same caveat as onResult above. Use it for UI state only (e.g. hiding a "Verify now" button), never as the gate itself.