y8-js-sdk
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseY8 JavaScript SDK
Y8 JavaScript SDK
Integration Workflow
集成工作流
Follow these steps in order. Each step includes a validation checkpoint before proceeding.
- Load the SDK — inject the script and poll for .
window.ID- ✓ Verify:
typeof window.ID !== 'undefined'
- ✓ Verify:
- Subscribe to before calling
id.init.ID.init({ appId })- ✓ Verify: the callback fires; if it never fires, check the
id.init.appId
- ✓ Verify: the
- Fetch login status inside to populate the player profile.
id.init- ✓ Verify: means the player is signed in.
data.status === 'ok'
- ✓ Verify:
- Load AdSense in parallel with login status inside .
id.init- ✓ Verify: script loads and
adsbygooglefires.onReady
- ✓ Verify:
- Gate storage, achievements, and leaderboards on .
profile.isAuthorized === true- ✓ Verify before each API call: player is authorized or prompt login first.
- Invalidate (set to
cachedUserData) after a successfulnullcall so the new session reads its own cloud state.ID.login
请按顺序执行以下步骤。每一步都包含一个用于确认是否可以继续的验证检查点。
- 加载 SDK — 注入脚本并轮询检查 。
window.ID- ✓ 验证:
typeof window.ID !== 'undefined'
- ✓ 验证:
- 调用 前,先订阅
ID.init({ appId })。id.init- ✓ 验证:回调被触发;如果始终未触发,请检查
id.init。appId
- ✓ 验证:
- 在 内获取登录状态,以填充玩家资料。
id.init- ✓ 验证:表示玩家已登录。
data.status === 'ok'
- ✓ 验证:
- 在 内获取登录状态的同时并行加载 AdSense。
id.init- ✓ 验证:脚本成功加载,并且
adsbygoogle被触发。onReady
- ✓ 验证:
- 仅当 时,才允许访问存储、成就和排行榜。
profile.isAuthorized === true- ✓ 每次调用 API 前进行验证:玩家已获得授权,或先提示玩家登录。
- 成功调用 后,使
ID.login失效(设为cachedUserData),使新会话能够读取自己的云端状态。null
Installation
安装
javascript
const SDK_URL = 'https://cdn.y8.com/api/sdk.js';
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error('Failed to load ' + src));
document.head.appendChild(script);
});
}
function waitFor(globalName, timeoutMs = 10000) {
return new Promise((resolve, reject) => {
const started = Date.now();
const tick = () => {
if (typeof window[globalName] !== 'undefined') {
resolve(window[globalName]);
return;
}
if (Date.now() - started > timeoutMs) {
reject(new Error(globalName + ' is not available'));
return;
}
setTimeout(tick, 50);
};
tick();
});
}javascript
const SDK_URL = 'https://cdn.y8.com/api/sdk.js';
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error('Failed to load ' + src));
document.head.appendChild(script);
});
}
function waitFor(globalName, timeoutMs = 10000) {
return new Promise((resolve, reject) => {
const started = Date.now();
const tick = () => {
if (typeof window[globalName] !== 'undefined') {
resolve(window[globalName]);
return;
}
if (Date.now() - started > timeoutMs) {
reject(new Error(globalName + ' is not available'));
return;
}
setTimeout(tick, 50);
};
tick();
});
}Initialization
初始化
Subscribe to first, then call . The (a.k.a. Game API Key) is issued by Y8 when you register the game. Once fires, fetch login status and load AdSense in parallel.
id.initID.init({ appId })appIdid.initjavascript
async function initY8({ gameId, adSenseId, channelId }) {
if (!gameId) {
throw new Error('gameId (Y8 appId) is required');
}
await loadScript(SDK_URL);
const sdk = await waitFor('ID'); // ✓ checkpoint: window.ID exists
return new Promise((resolve, reject) => {
sdk.Event.subscribe('id.init', () => {
// ✓ checkpoint: id.init fired — SDK is ready
loadAdsByGoogle({ adSenseId, channelId })
.then((showAd) => {
sdk.getLoginStatus((data) => {
const profile = readPlayerInfo(data);
resolve({ sdk, showAd, profile });
});
})
.catch(reject);
});
sdk.init({
appId: gameId
});
});
}先订阅 ,然后调用 。(也称 Game API Key)由 Y8 在注册游戏时签发。 触发后,并行获取登录状态和加载 AdSense。
id.initID.init({ appId })appIdid.initjavascript
async function initY8({ gameId, adSenseId, channelId }) {
if (!gameId) {
throw new Error('gameId (Y8 appId) is required');
}
await loadScript(SDK_URL);
const sdk = await waitFor('ID'); // ✓ checkpoint: window.ID exists
return new Promise((resolve, reject) => {
sdk.Event.subscribe('id.init', () => {
// ✓ checkpoint: id.init fired — SDK is ready
loadAdsByGoogle({ adSenseId, channelId })
.then((showAd) => {
sdk.getLoginStatus((data) => {
const profile = readPlayerInfo(data);
resolve({ sdk, showAd, profile });
});
})
.catch(reject);
});
sdk.init({
appId: gameId
});
});
}Player / Authorization
玩家 / 授权
ID.getLoginStatus(callback)ID.login(callback)javascript
function readPlayerInfo(data) {
const profile = {
isAuthorized: false,
id: null,
name: null,
locale: null,
photos: []
};
if (!data || data.status !== 'ok') {
return profile;
}
const details = data.authResponse.details;
const { pid, locale, nickname, first_name: firstName, last_name: lastName, avatars } = details;
profile.isAuthorized = true;
profile.id = pid || null;
profile.locale = locale || null;
profile.name = [firstName, lastName].filter((x) => !!x).join(' ') || nickname || null;
const { thumb_url: photoSmall, medium_url: photoMedium, large_url: photoLarge } = avatars || {};
if (photoSmall) profile.photos.push(photoSmall);
if (photoMedium) profile.photos.push(photoMedium);
if (photoLarge) profile.photos.push(photoLarge);
return profile;
}
function authorizePlayer(sdk) {
return new Promise((resolve, reject) => {
sdk.login((response) => {
const profile = readPlayerInfo(response);
if (response && response.status === 'ok') {
resolve(profile);
} else {
reject(new Error('Y8 login was cancelled or failed'));
}
});
});
}ID.getLoginStatus(callback)ID.login(callback)javascript
function readPlayerInfo(data) {
const profile = {
isAuthorized: false,
id: null,
name: null,
locale: null,
photos: []
};
if (!data || data.status !== 'ok') {
return profile;
}
const details = data.authResponse.details;
const { pid, locale, nickname, first_name: firstName, last_name: lastName, avatars } = details;
profile.isAuthorized = true;
profile.id = pid || null;
profile.locale = locale || null;
profile.name = [firstName, lastName].filter((x) => !!x).join(' ') || nickname || null;
const { thumb_url: photoSmall, medium_url: photoMedium, large_url: photoLarge } = avatars || {};
if (photoSmall) profile.photos.push(photoSmall);
if (photoMedium) profile.photos.push(photoMedium);
if (photoLarge) profile.photos.push(photoLarge);
return profile;
}
function authorizePlayer(sdk) {
return new Promise((resolve, reject) => {
sdk.login((response) => {
const profile = readPlayerInfo(response);
if (response && response.status === 'ok') {
resolve(profile);
} else {
reject(new Error('Y8 login was cancelled or failed'));
}
});
});
}Storage
存储
Cloud-backed user data is accessed through . Store all game data as a single JSON blob under one key — read once, mutate locally, write back.
ID.api()After login: setso the newly authorized player reads their own cloud state.cachedUserData = null
javascript
const USERDATA_KEY = 'userData';
const NOT_FOUND_ERROR = 'Key not found';
let cachedUserData = null;
function loadUserData(sdk) {
return new Promise((resolve, reject) => {
if (cachedUserData) {
resolve(cachedUserData);
return;
}
sdk.api('user_data/retrieve', 'POST', { key: USERDATA_KEY }, (response) => {
if (response.error && response.error !== NOT_FOUND_ERROR) {
reject(response);
return;
}
let userData = {};
try {
if (response.jsondata) {
userData = JSON.parse(response.jsondata);
}
} catch (e) {
// Leave userData as an empty object on parse failure
}
cachedUserData = userData;
resolve(userData);
});
});
}
function saveUserData(sdk, userData) {
return new Promise((resolve, reject) => {
const payload = { key: USERDATA_KEY, value: JSON.stringify(userData) };
sdk.api('user_data/submit', 'POST', payload, (response) => {
if (response.status === 'ok') {
cachedUserData = userData;
resolve();
} else {
reject(response);
}
});
});
}
function getStorageValue(sdk, key) {
return loadUserData(sdk).then((userData) => userData[key] ?? null);
}
function setStorageValue(sdk, key, value) {
return loadUserData(sdk).then((userData) => {
const next = { ...userData, [key]: value };
return saveUserData(sdk, next);
});
}
function deleteStorageValue(sdk, key) {
return loadUserData(sdk).then((userData) => {
const next = { ...userData };
delete next[key];
return saveUserData(sdk, next);
});
}通过 访问云端用户数据。将所有游戏数据作为单个 JSON blob 存储在一个键下——读取一次,在本地修改,然后写回。
ID.api()登录后: 将设为cachedUserData,使刚获得授权的玩家能够读取自己的云端状态。null
javascript
const USERDATA_KEY = 'userData';
const NOT_FOUND_ERROR = 'Key not found';
let cachedUserData = null;
function loadUserData(sdk) {
return new Promise((resolve, reject) => {
if (cachedUserData) {
resolve(cachedUserData);
return;
}
sdk.api('user_data/retrieve', 'POST', { key: USERDATA_KEY }, (response) => {
if (response.error && response.error !== NOT_FOUND_ERROR) {
reject(response);
return;
}
let userData = {};
try {
if (response.jsondata) {
userData = JSON.parse(response.jsondata);
}
} catch (e) {
// Leave userData as an empty object on parse failure
}
cachedUserData = userData;
resolve(userData);
});
});
}
function saveUserData(sdk, userData) {
return new Promise((resolve, reject) => {
const payload = { key: USERDATA_KEY, value: JSON.stringify(userData) };
sdk.api('user_data/submit', 'POST', payload, (response) => {
if (response.status === 'ok') {
cachedUserData = userData;
resolve();
} else {
reject(response);
}
});
});
}
function getStorageValue(sdk, key) {
return loadUserData(sdk).then((userData) => userData[key] ?? null);
}
function setStorageValue(sdk, key, value) {
return loadUserData(sdk).then((userData) => {
const next = { ...userData, [key]: value };
return saveUserData(sdk, next);
});
}
function deleteStorageValue(sdk, key) {
return loadUserData(sdk).then((userData) => {
const next = { ...userData };
delete next[key];
return saveUserData(sdk, next);
});
}Advertisement
广告
adsbygoogle.pushadBreaktype: 'start'type: 'reward'data-ad-channeljavascript
const ADS_ID = '6129580795478709';
function loadAdsByGoogle({ adSenseId, channelId }) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
script.setAttribute('crossorigin', 'anonymous');
script.setAttribute('data-ad-frequency-hint', '180s');
script.setAttribute('data-ad-client', channelId ? `ca-pub-${ADS_ID}` : adSenseId);
if (channelId) {
script.setAttribute('data-ad-channel', channelId);
} else {
script.setAttribute('data-ad-host', `ca-host-pub-${ADS_ID}`);
}
script.addEventListener('load', () => {
window.adsbygoogle = window.adsbygoogle || [];
window.adsbygoogle.push({
preloadAdBreaks: 'on',
sound: 'on',
onReady: () => {}
});
resolve((adOptions) => window.adsbygoogle.push(adOptions));
});
script.addEventListener('error', () => reject(new Error('adsbygoogle failed to load')));
document.head.appendChild(script);
});
}adsbygoogle.pushadBreaktype: 'start'type: 'reward'data-ad-channeljavascript
const ADS_ID = '6129580795478709';
function loadAdsByGoogle({ adSenseId, channelId }) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
script.setAttribute('crossorigin', 'anonymous');
script.setAttribute('data-ad-frequency-hint', '180s');
script.setAttribute('data-ad-client', channelId ? `ca-pub-${ADS_ID}` : adSenseId);
if (channelId) {
script.setAttribute('data-ad-channel', channelId);
} else {
script.setAttribute('data-ad-host', `ca-host-pub-${ADS_ID}`);
}
script.addEventListener('load', () => {
window.adsbygoogle = window.adsbygoogle || [];
window.adsbygoogle.push({
preloadAdBreaks: 'on',
sound: 'on',
onReady: () => {}
});
resolve((adOptions) => window.adsbygoogle.push(adOptions));
});
script.addEventListener('error', () => reject(new Error('adsbygoogle failed to load')));
document.head.appendChild(script);
});
}Interstitial
插屏广告
javascript
function showInterstitial(showAd, { onOpened, onClosed, onFailed } = {}) {
if (!showAd) {
onFailed?.('ads_not_ready');
return;
}
showAd({
type: 'start',
name: 'start-game',
beforeAd: () => {
onOpened?.();
},
afterAd: () => {
onClosed?.();
},
adBreakDone: (placementInfo) => {
if (placementInfo.breakStatus !== 'viewed') {
onFailed?.(placementInfo.breakStatus);
}
}
});
}javascript
function showInterstitial(showAd, { onOpened, onClosed, onFailed } = {}) {
if (!showAd) {
onFailed?.('ads_not_ready');
return;
}
showAd({
type: 'start',
name: 'start-game',
beforeAd: () => {
onOpened?.();
},
afterAd: () => {
onClosed?.();
},
adBreakDone: (placementInfo) => {
if (placementInfo.breakStatus !== 'viewed') {
onFailed?.(placementInfo.breakStatus);
}
}
});
}Rewarded
激励广告
beforeReward(showAdFn)showAdFn(0)adViewedadDismissedjavascript
function showRewarded(showAd, { onOpened, onRewarded, onClosed, onFailed } = {}) {
if (!showAd) {
onFailed?.('ads_not_ready');
return;
}
showAd({
type: 'reward',
name: 'rewarded Ad',
beforeAd: () => {
onOpened?.();
},
afterAd: () => {
onClosed?.();
},
beforeReward: (showAdFn) => {
showAdFn(0);
},
adDismissed: () => {
onFailed?.('dismissed');
},
adViewed: () => {
onRewarded?.();
},
adBreakDone: (placementInfo) => {
if (
placementInfo.breakStatus === 'frequencyCapped' ||
placementInfo.breakStatus === 'other'
) {
onFailed?.(placementInfo.breakStatus);
}
}
});
}beforeReward(showAdFn)showAdFn(0)adViewedadDismissedjavascript
function showRewarded(showAd, { onOpened, onRewarded, onClosed, onFailed } = {}) {
if (!showAd) {
onFailed?.('ads_not_ready');
return;
}
showAd({
type: 'reward',
name: 'rewarded Ad',
beforeAd: () => {
onOpened?.();
},
afterAd: () => {
onClosed?.();
},
beforeReward: (showAdFn) => {
showAdFn(0);
},
adDismissed: () => {
onFailed?.('dismissed');
},
adViewed: () => {
onRewarded?.();
},
adBreakDone: (placementInfo) => {
if (
placementInfo.breakStatus === 'frequencyCapped' ||
placementInfo.breakStatus === 'other'
) {
onFailed?.(placementInfo.breakStatus);
}
}
});
}Achievements
成就
Y8 native achievements live under . Both (display name) and (stable id) are required.
ID.GameAPI.AchievementsachievementachievementkeyY8 原生成就位于 下。(显示名称)和 (稳定 id)均为必填项。
ID.GameAPI.AchievementsachievementachievementkeyUnlock
解锁
javascript
function unlockAchievement(sdk, options) {
if (!options || !options.achievement || !options.achievementkey) {
return Promise.reject(new Error('achievement and achievementkey are required'));
}
return new Promise((resolve) => {
sdk.GameAPI.Achievements.save(options, (data) => {
resolve(data);
});
});
}javascript
function unlockAchievement(sdk, options) {
if (!options || !options.achievement || !options.achievementkey) {
return Promise.reject(new Error('achievement and achievementkey are required'));
}
return new Promise((resolve) => {
sdk.GameAPI.Achievements.save(options, (data) => {
resolve(data);
});
});
}List achievements (custom query)
获取成就列表(自定义查询)
listCustomplayerjavascript
function getAchievementsList(sdk, options = {}) {
return new Promise((resolve, reject) => {
sdk.GameAPI.Achievements.listCustom(options, (data) => {
if (!data.success) {
reject(new Error(data.errorcode));
return;
}
const list = data.achievements.map(({ player, ...achievement }) => ({
...achievement,
playerid: player.playerid,
playername: player.playername,
lastupdated: player.lastupdated,
date: player.date,
rdate: player.rdate
}));
resolve(list);
});
});
}listCustomplayerjavascript
function getAchievementsList(sdk, options = {}) {
return new Promise((resolve, reject) => {
sdk.GameAPI.Achievements.listCustom(options, (data) => {
if (!data.success) {
reject(new Error(data.errorcode));
return;
}
const list = data.achievements.map(({ player, ...achievement }) => ({
...achievement,
playerid: player.playerid,
playername: player.playername,
lastupdated: player.lastupdated,
date: player.date,
rdate: player.rdate
}));
resolve(list);
});
});
}Native popup
原生弹窗
ID.GameAPI.Achievements.list(options)javascript
function showAchievementsPopup(sdk, options = {}) {
sdk.GameAPI.Achievements.list(options);
}ID.GameAPI.Achievements.list(options)javascript
function showAchievementsPopup(sdk, options = {}) {
sdk.GameAPI.Achievements.list(options);
}Leaderboards
排行榜
Each board is identified by a string id configured in the Y8 dashboard.
table每个排行榜都由在 Y8 dashboard 中配置的字符串 id 标识。
tableSubmit a score
提交分数
javascript
function submitScore(sdk, tableId, score) {
return new Promise((resolve, reject) => {
const options = {
table: tableId,
points: score
};
sdk.GameAPI.Leaderboards.save(options, ({ success, errormessage }) => {
if (success) {
resolve();
} else {
reject(new Error(errormessage || 'leaderboard_save_failed'));
}
});
});
}javascript
function submitScore(sdk, tableId, score) {
return new Promise((resolve, reject) => {
const options = {
table: tableId,
points: score
};
sdk.GameAPI.Leaderboards.save(options, ({ success, errormessage }) => {
if (success) {
resolve();
} else {
reject(new Error(errormessage || 'leaderboard_save_failed'));
}
});
});
}Fetch entries
获取条目
Valid values: (default), , , .
mode'alltime''daily''weekly''monthly'javascript
function getLeaderboardEntries(sdk, tableId) {
return new Promise((resolve, reject) => {
const options = {
table: tableId,
mode: 'alltime'
};
sdk.GameAPI.Leaderboards.listCustom(options, ({ scores, success, errormessage }) => {
if (!success) {
reject(new Error(errormessage || 'leaderboard_list_failed'));
return;
}
const entries = scores.map((entry) => ({
id: entry.playerid,
name: entry.playername,
score: entry.points,
rank: entry.rank,
photo: null
}));
resolve(entries);
});
});
}mode'alltime''daily''weekly''monthly'javascript
function getLeaderboardEntries(sdk, tableId) {
return new Promise((resolve, reject) => {
const options = {
table: tableId,
mode: 'alltime'
};
sdk.GameAPI.Leaderboards.listCustom(options, ({ scores, success, errormessage }) => {
if (!success) {
reject(new Error(errormessage || 'leaderboard_list_failed'));
return;
}
const entries = scores.map((entry) => ({
id: entry.playerid,
name: entry.playername,
score: entry.points,
rank: entry.rank,
photo: null
}));
resolve(entries);
});
});
}