Accounts and localization
Start with SDK availability and errors. The examples below use the injected global and never request account credentials from the game.
Sign in after a player action
Call checkSession() immediately before an account action. If it returns false, call login() from an explicit player action. The host owns the login UI. cancelled is a normal decision; transport, popup, and account synchronization failures reject.
After success, continue the current action directly. Do not reload, poll, or perform another session check for the same action. Check the session again for a later independent action. Disable the initiating button while a request is pending.
<button id="player-button">Show player</button>
<output id="player-info" aria-live="polite"></output>async function readPlayerAfterClick() {
const sdk = window.combos;
if (!sdk) return null;
const { methods } = await sdk.init();
if (!methods.includes("getUserInfo")) return null;
if (!(await sdk.checkSession())) {
const result = await sdk.login();
if (result.status !== "success") return null;
}
return sdk.getUserInfo();
}
const button = document.querySelector<HTMLButtonElement>("#player-button");
const output = document.querySelector<HTMLOutputElement>("#player-info");
button?.addEventListener("click", async () => {
if (!output) return;
button.disabled = true;
try {
const player = await readPlayerAfterClick();
output.textContent = player
? player.nickName || "Player"
: "Player information is unavailable.";
} catch {
output.textContent = "Unable to read player information. Please try again.";
} finally {
button.disabled = false;
}
});The example’s button and messages are sample game UI; adapt them to your game’s languages. The helper returns null for an absent capability or cancelled login, and leaves actual failures for the click handler to catch.
Read player information
getUserInfo() takes no arguments. Call it after a session check or successful login. A logged-out request rejects with session_required.
| Field | Meaning |
|---|---|
appId | The current game’s app identity |
openId | The player’s identity within that app |
nickName | Display name; may be an empty string |
avatarUrl | Avatar URL; may be an empty string |
locale | Player’s saved public language, such as en or zh-CN; current product language when none is saved |
Provide display fallbacks. Keep app-scoped identity scoped to its app; do not treat this display response as a credential or proof of authorization for your own backend. It contains no platform user ID, email, or Cloud Ability session.
Translate visible game copy
Render source text first, then ask for translations. sourceLocale describes that source text, not the desired target language. The host chooses its current product language. sourceTexts contains the exact source strings or templates; translation keys use those same strings.
<button data-source-text="Play"></button>
<button data-source-text="Pause"></button>async function localizeLabels() {
const nodes = [...document.querySelectorAll<HTMLElement>("[data-source-text]")];
const sourceTexts = nodes.map(node => node.dataset.sourceText ?? "");
nodes.forEach((node, index) => { node.textContent = sourceTexts[index]; });
const sdk = window.combos;
if (!sdk) return;
try {
const { methods } = await sdk.init();
if (!methods.includes("requestI18n")) return;
const result = await sdk.requestI18n({ sourceLocale: "en", sourceTexts });
if (!result.needed) return;
nodes.forEach((node, index) => {
const source = sourceTexts[index];
node.textContent = result.translations[source] || source;
});
} catch {
// Keep the source copy on screen.
}
}
void localizeLabels();The result is either { needed: false } or { needed: true, locale, translations }. Keep source copy for skipped translation, missing keys, unsupported hosts, and failures. Invalid SDK result payloads also become needed: false. Apply returned text as text, not raw HTML. Translate stable templates before inserting dynamic values, and update sourceLocale if you change the example’s English source strings.
Optional Cloud Ability reference
The current Web host does not advertise these four methods. Only use them when init().methods explicitly includes the method. This contract does not enable AI access just by publishing a game.
type Authorization = {
decision: "AUTHORIZATION_DECISION_GRANTED" | "AUTHORIZATION_DECISION_DENIED";
decidedAtMs: number;
};
type AuthSetting = Partial<Record<"ai_model", Authorization>>;
type CloudAbilitySession = { session: string; expiresAtMs: number };| Method | Parameters and result |
|---|---|
getSetting() | No arguments; resolves to AuthSetting. A missing scope means never asked |
authorize({ scope: "ai_model" }) | The host owns the permission prompt; resolves to Authorization |
openSetting() | No arguments; opens host settings and resolves to AuthSetting |
getCloudAbilitySession() | No arguments; resolves to a short-lived CloudAbilitySession |
AUTHORIZATION_DECISION_DENIED is a normal authorization outcome, not a transport failure. Do not write the UNSPECIFIED zero value. Times are in milliseconds. Never send user, device, or game IDs to these methods. Keep any returned session only in memory, respect expiresAtMs, discard it when the play ends, and never put it in storage, logs, or URLs.