SDK
يوفّر هذا الـ SDK قناة اتصال آمنة ثنائية الاتجاه بين اللعبة والعميل الأصلي (iOS وAndroid)، لتمكين مطوري الطرف الثالث من استدعاء القدرات الأصلية.
اسم واجهة الـ API العامة: window.GameTokSDK
القدرات المتوفرة:
| الإجراء | الوصف |
|---|---|
| GET_PROFILE | جلب ملف المستخدم الحالي |
| GET_PROFILES | جلب ملفات مستخدمين متعددين دفعةً واحدة (بمصفوفة uid) |
| PURCHASE | بدء عملية شراء (مثل الشراء داخل التطبيق) |
| STORAGE_SET | كتابة تخزين مفاتيح/قيم محلي (إعدادات، تقدّم، إلخ) |
| STORAGE_GET | قراءة تخزين مفاتيح/قيم محلي (البيانات المحفوظة سابقًا) |
| ADD_SCORE | إرسال نتيجة (لوائح المتصدرين، مشاركة، إلخ) |
| GET_PERMISSION_MIC | طلب إذن الميكروفون (أندرويد) |
| TOPUP | شحن الرصيد عند نقص العملات داخل اللعبة؛ يفتح تدفق الشحن في العميل |
| ROUND_START | إشعار بداية الجولة (مرة عند بدء كل جولة؛ مرتبط بسياسات المنصة للجولات/الطاقة) |
| ROUND_END | إشعار نهاية الجولة (عند انتهاء الجولة؛ يجب أن يطابق round_id إشعار ROUND_START لنفس الجولة) |
| GAME_START | إشعار بداية الجولة · قناة نظيفة (بنفس معنى ROUND_START وبنفس الحمولة، دون دلالات الطاقة) |
| GAME_END | إشعار نهاية الجولة · قناة نظيفة (بنفس معنى ROUND_END؛ يجب أن يطابق round_id إشعار GAME_START لنفس الجولة) |
| GET_COUPON_TARGET_SCORE | جلب النتيجة المستهدفة لعرض نافذة القسيمة (يُعيد العميل قيمة النتيجة المحددة) |
| SHOW_COUPON_DIALOG | إخطار المنصة لعرض نافذة القسيمة (يُعرضها العميل في طبقة حاوية اللعبة) |
| SHOW_LEADERBOARD | إخطار المنصة لعرض لائحة المتصدرين (يعرضها العميل في طبقة حاوية اللعبة؛ fire-and-forget) |
| SHOW_ENERGY_INSUFFICIENT_DIALOG | الطاقة غير كافية؛ إخطار المنصة لعرض نافذة نقص الطاقة (fire-and-forget) |
| BRAND_ACTIVE_DETAIL_PAGE | الانتقال إلى صفحة تفاصيل نشاط العلامة التجارية (تبدأه اللعبة وينفّذ العميل الانتقال؛ fire-and-forget) |
| BRAND_DT_DETAIL_PAGE | الانتقال إلى صفحة تفاصيل DT للعلامة التجارية (تبدأه اللعبة وينفّذ العميل الانتقال؛ fire-and-forget) |
| bookHQ | حجز حدث مميّز (إرسال طلب حجز؛ قد يطلب العميل تسجيل الدخول أولًا) |
| onBookHQ | الاستماع لرد نتيجة حجز bookHQ |
| onAudioSuspend | الاستماع لحدث تعليق الصوت (يُطلق عندما تطلب المنصة من اللعبة الكتم) |
| offAudioSuspend | إزالة مستمع حدث تعليق الصوت |
| onAudioResume | الاستماع لحدث استئناف الصوت (يُطلق عندما تسمح المنصة للعبة باستعادة الصوت) |
| offAudioResume | إزالة مستمع حدث استئناف الصوت |
| onAutoStartGame | الاستماع لتعليمات العميل «بدء اللعبة» (نفس تأثير النقر على زر start داخل اللعبة) |
| offAutoStartGame | إزالة مستمع تعليمات «بدء اللعبة» |
| onExitAndResetGame | الاستماع لتعليمات العميل «إنهاء اللعبة وإعادة التعيين» (إنهاء اللعبة والعودة للحالة الأولية) |
| offExitAndResetGame | إزالة مستمع تعليمات «إنهاء اللعبة وإعادة التعيين» |
| REQUEST_EXIT | نُقر زر «العودة إلى الرئيسية» المدمج في اللعبة؛ إخطار المنصة بالعودة إلى الرئيسية (fire-and-forget، ثم ترسل المنصة EXIT_AND_RESET_GAME) |
| SHOW_MATCH_RESULT | إخطار نتيجة المباراة في KC League: تسليم نتيجة التسوية المُعادة من خادم المنصة كما هي إلى عميل المنصة لعرض نافذة التسوية (fire-and-forget) |
| SHOW_WPP_RESULT | إخطار نتيجة WPP: تسليم محتوى النتيجة كما هو إلى عميل المنصة لعرض نافذة النتيجة (fire-and-forget، لا يتحقق الـ SDK منه ولا يحلّله) |
أمثلة الدمج
يوضّح ما يلي كيفية تضمين الـ SDK في الصفحة واستدعاء كل قدرة.
1.1 تضمين السكربت
الطريقة الأولى: تحميل الحزمة عبر <script>
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
console.log('GameTokSDK version loaded:', !!window.GameTokSDK);
</script>التطوير المحلي (الصفحة غير مضمّنة في التطبيق، التصحيح في المتصفح): بعد تحميل الـ SDK استدعِ GameTokSDK.enableMock()؛ واجهات الـ Promise ستستخدم بيانات محاكاة مدمجة دون الحاجة للعميل الأصلي. لا تستدعِها في الإنتاج أو نسخ المتجر، وإلا سيرى المستخدمون بيانات وهمية.
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
GameTokSDK.enableMock();
</script>الاختبار على جهاز حقيقي (WebView داخل التطبيق): عند الحاجة للسجلات، بعد تحميل الـ SDK استدعِ GameTokSDK.enableDebug() (تُعيد Promise؛ تُحمّل vConsole في الصفحة وتُسجّل تبادلات الجسر مع الأصل). لا تستخدمها مع enableMock في آن واحد.
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
GameTokSDK.enableDebug();
</script>1.2 أمثلة الاستدعاء
تستخدم الأمثلة أسلوب then / catch مع الـ Promise. عند النجاح يُعاد كائن استجابة موحّد؛ عند الفشل يُرفض الـ Promise.
1.2.1 جلب الملف الشخصي
// مثال استدعاء
GameTokSDK.getProfile()
.then(({ action, error, data }) => {
console.log('تم جلب الملف الشخصي:', data);
})
.catch((e) => {
console.error('فشل جلب الملف الشخصي:', e);
});مثال الاستجابة (JSON):
{
"action": "GET_PROFILE",
"error": false,
"data": {
"uid": 1024780,
"avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
"userName": "Guest",
"userCoins": 0,
"level": 0,
"gameLevel": 0,
"rankImg": "",
"gender": 0,
"testAccount": false,
"guest": true
}
}الحقول الرئيسية:
| الحقل | النوع | الوصف |
|---|---|---|
| uid | number | المعرّف الفريد للمستخدم |
| avatar | string | رابط الصورة الرمزية |
| userName | string | اسم المستخدم / الاسم المعروض |
| userCoins | number | رصيد العملات الحالي للمستخدم |
| level | number | مستوى نمو المستخدم (نظام الحساب) |
| gameLevel | number | الرتبة (مبنية على مباريات اللعبة / التصنيف)؛ 0 تعني لا توجد رتبة (مثل لاعب جديد، لم يشارك في التصنيف) |
| rankImg | string | رابط صورة الرتبة؛ سلسلة فارغة عندما لا تكون للمستخدم رتبة |
| gender | number | تعداد الجنس: 0 = غير محدد، 1 = ذكر، 2 = أنثى |
| testAccount | boolean | ما إذا كان حساب اختبار / حساب مراجعة المنصة |
| guest | boolean | ما إذا كان ضيفًا (لم يسجّل دخوله بحساب حقيقي) |
1.2.2 جلب ملفات مستخدمين متعددة دفعةً واحدة
يُستخدم في لوائح المتصدرين، قوائم الأصدقاء، تسوية المباريات، أو أي سيناريو يحتاج لجلب معلومات أساسية لـعدة مستخدمين في آنٍ واحد، تجنبًا لاستدعاء getProfile بشكل متكرر.
/**
* جلب ملفات مستخدمين متعددة
* @param uids مطلوب؛ مصفوفة uid للاستعلام (يُنصح بـ 1–50 في كل استدعاء)
*/
GameTokSDK.getProfiles({ uids: [1234567, 1234568, 1234569] })
.then(({ action, error, data }) => {
console.log('نجح الجلب المتعدد:', data);
// data مصفوفة Profile؛ يمكن التكرار عليها أو تحويلها لـ Map حسب uid
const map = new Map(data.map(p => [p.uid, p]));
console.log('1234567 ->', map.get(1234567));
})
.catch((e) => {
console.error('فشل الجلب المتعدد:', e);
});المعاملات:
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
| uids | number[] | نعم | قائمة uid للاستعلام؛ الطول المُوصى به 1–50. إذا كانت مصفوفة فارغة، أو غير مصفوفة، أو تحتوي على عناصر غير صالحة (مثل "abc"، null، أعداد سالبة، كسور)، يُرفض الـ SDK فورًا بـ reject(SDKError)، رمز الخطأ INVALID_PARAMS = 1003، ولن يُرسل للأصل |
مثال الاستجابة (JSON):
{
"action": "GET_PROFILES",
"error": false,
"data": [
{
"uid": 1024780,
"avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
"userName": "Guest",
"userCoins": 0,
"level": 0,
"gameLevel": 0,
"rankImg": "",
"gender": 0,
"testAccount": false,
"guest": true
},
{
"uid": 1024781,
"avatar": "https://game-load-sa.lobah.net/avatar/2.jpg",
"userName": "Alice",
"userCoins": 200,
"level": 3,
"gameLevel": 5,
"rankImg": "https://game-load-sa.lobah.net/rank/5.png",
"gender": 2,
"testAccount": false,
"guest": false
}
]
}1.2.3 الشراء داخل التطبيق (مثل شراء عنصر)
/**
* شراء داخل التطبيق
* @param productId معرّف المنتج؛ يجب تعريفه مسبقًا في لوحة المطور (https://developer.lobah.net/)
*/
GameTokSDK.purchase({ productId: 'HAB.WATER.10.COINS' })
.then(({ data }) => {
// يُحدَّد النجاح أو الفشل من الرمز المُعاد هنا
console.log('اكتملت عملية الشراء:', data);
})
.catch((e) => {
console.error('خطأ:', e);
});مثال الاستجابة (JSON):
{
"action": "PURCHASE",
"error": false,
"data": {
"purchaseResultCode": 0,
"testAccount": false,
"userBalance": 22514
}
}قيم purchaseResultCode:
0: نجاح الشراء11:product_idغير صحيح12: رصيد العملات غير كافٍ13:reference_idمكرر أو غير صالح14: المستخدم ضيف؛ الضيوف لا يمكنهم الشراء16:product_idغير صحيح20: خطأ آخر
1.2.4 تخزين مفتاح/قيمة
/**
* تخزين مفتاح/قيمة
* @param key مفتاح مخصص
* @param value قيمة؛ قد تكون نصًا أو كائنًا
*/
GameTokSDK.storageSet({ key: 'settings', value: { theme: 'dark', volume: 0.8 } })
.then(() => {
console.log('تم التخزين بنجاح');
})
.catch((e) => {
console.error('فشل التخزين:', e);
});مثال الاستجابة (JSON):
{
"action": "STORAGE_SET",
"error": false,
"data": null
}1.2.5 قراءة مفتاح/قيمة
/**
* قراءة مفتاح/قيمة
* @param key المفتاح المعرّف مسبقًا
*/
GameTokSDK.storageGet({ key: 'settings' })
.then((result) => {
console.log('قراءة ناجحة:', result.data.value); // كائن أو نص
})
.catch((e) => {
console.error('فشلت القراءة:', e);
});مثال الاستجابة (JSON):
{
"action": "STORAGE_GET",
"error": false,
"data": {
"value": {
"theme": "dark",
"volume": 0.8
}
}
}1.2.6 إرسال النتيجة
/**
* حسب اللعبة قد تُرسل نتيجة، مرحلة، أو مستوى.
* للنتيجة: { score: 300, scoreType: 'score', remark: 'score' }
* للمستوى: { score: 1, scoreType: 'level', remark: 'level' }
* @param score القيمة المطلوب إرسالها؛ عدد صحيح موجب
* @param scoreType فئة البيانات (وصف يحدده العمل)
* @param remark ملاحظة اختيارية (قد تطابق scoreType)
*/
GameTokSDK.addScore({ score: 10, scoreType: 'score', remark: 'score' })
.then(() => {
console.log('تم إرسال النتيجة');
})
.catch((e) => {
console.error('فشل الإرسال:', e);
});مثال الاستجابة (JSON):
{
"action": "ADD_SCORE",
"error": false,
"data": null
}1.2.7 إذن الميكروفون
/**
* طلب إذن الميكروفون (أندرويد)
* للاستخدام داخل اللعبة لطلب الإذن (مثل الميزات الصوتية).
* بلا قيمة إرجاع؛ يُظهر فقط واجهة الأذونات الأصلية.
*/
GameTokSDK.getPermissionMic();1.2.8 الشحن (TOPUP)
مهم: إلغاء المستخدم، فشل الدفع، وما شابه ما زالت تُحلّ عبر then. داخل then افحص data.code لمعرفة النجاح. catch مخصّص لأعطال الـ SDK، انتهاء المهلة، وما شابه.
/**
* شحن الرصيد
* @param amount المبلغ (الوحدات حسب الاتفاق مع العميل)
*/
GameTokSDK.topup({ amount: 100 })
.then(({ action, error, data }) => {
const code = data && data.code;
if (code === 200) {
console.log('نجح الشحن:', data);
} else {
console.warn('الشحن غير مكتمل أو فشل، الرمز:', code, data);
}
})
.catch((e) => {
console.error('خطأ في طلب الشحن (مثل عدم توفر الـ SDK):', e);
});مثال الاستجابة (JSON، نجاح):
{
"action": "TOPUP",
"error": false,
"data": {
"code": 200,
"message": "ok"
}
}
قيم code:
200 نجاح
401 فشل الشحن (خطأ)
402 فشل الشحن (إلغاء من المستخدم)1.2.9 بداية الجولة (ROUND_START)
يُستدعى مرة واحدة بعد بدء الجولة فعليًا. round_id اختياري، ويُنصح بعدم تمريره: عند حذفه يولّد الـ SDK تلقائيًا معرّف جولة فريدًا عالميًا (بالبادئة round_) ويحتفظ به داخليًا، ويُرسل roundEnd() القيمة نفسها تلقائيًا. اختياريًا timestamp (بالميلي ثانية؛ إن حُذف يُستخدم الوقت الحالي). بلا إرجاع Promise — إرسال دون انتظار.
// الاستخدام الموصى به: بلا أي معاملات؛ يولّد الـ SDK round_id ويربطه بهذه الجولة تلقائيًا
GameTokSDK.roundStart();
// استخدام للتوافق: تمرير round_id بنفسك (يجب أن يكون فريدًا عالميًا لكل جولة ولا يُعاد استخدامه أبدًا عبر الجولات،
// ولا تستخدم أرقامًا تسلسلية متزايدة داخل الجلسة مثل 1، 2، 3 — فبعد تحديث الصفحة ستتعارض مع الجولات السابقة)
GameTokSDK.roundStart({ round_id: 'round-' + crypto.randomUUID() });عند الحاجة لقراءة معرّف الجولة الحالية (مثل تمريره إلى showEnergyInsufficientDialog للإسناد):
const roundId = GameTokSDK.getCurrentRoundId(); // له قيمة بعد roundStart، ويصبح null بعد roundEndإذا استُدعي
roundStartمرة أخرى دونroundEndللجولة السابقة، يُعدّ ذلك بداية جولة جديدة ويستبدل الـ SDK معرّف الجولة الحالية مباشرة (تُعامل الجولة القديمة كجولة لم تنتهِ بشكل طبيعي، ولدى المنصة تسامح مع ذلك).
1.2.10 نهاية الجولة (ROUND_END)
يُستدعى بعد انتهاء كل جولة (الفوز/الخسارة/انتهاء الوقت كلها تُعدّ نهاية). round_id اختياري: عند حذفه يُرسَل تلقائيًا نفس قيمة roundStart لهذه الجولة؛ وعند تمريره بنفسك يجب أن يطابق قيمة البداية. اختياريًا timestamp. بلا إرجاع Promise.
// الاستخدام الموصى به: بالاقتران مع roundStart() دون الاهتمام بـ round_id إطلاقًا
GameTokSDK.roundStart();
// ... منطق الجولة ...
GameTokSDK.roundEnd();
// استخدام للتوافق: تمرير صريح (يجب أن يطابق round_id الخاص بـ roundStart لهذه الجولة)
GameTokSDK.roundEnd({ round_id: myRoundId, timestamp: Date.now() });إذا لم تكن هناك جولة جارية (لم يُستدعَ roundStart قط، أو سبق استدعاء roundEnd) ولم يُمرَّر round_id صريحًا، يُحذّر الـ SDK في وحدة التحكم ولا يُرسل إلى الأصل.
1.2.10a بداية الجولة · قناة نظيفة (GAME_START)
بنفس معنى roundStart وعلى المستوى نفسه — يُستدعى أيضًا «مرة واحدة بعد بدء الجولة فعليًا»، وليس جلسة على مستوى أعلى.
سبب إضافة قناة ثانية: صار لدى ROUND_START / ROUND_END منطق أعمال مرتبط بهما على جانب المنصة (خصم الطاقة وغيره) ولم يعد من الممكن تغييرهما. أما GAME_START / GAME_END فيحملان المعنى نفسه تمامًا دون أي دلالات للطاقة، ليتمكن منطق الأعمال المستقبلي من الارتباط بقناة نظيفة.
الحمولة مطابقة حرفيًا لحمولة ROUND_START ({ round_id, timestamp }، وبنفس بادئة المعرّف round_)، ويختلف اسم الـ action فقط — أي أن كود التحليل الموجود على الجانب الأصلي يمكن إعادة استخدامه كما هو.
ROUND_START / ROUND_END | GAME_START / GAME_END | |
|---|---|---|
| المعنى | بداية الجولة / نهايتها | مطابق تمامًا |
| منطق الأعمال المرتبط على جانب المنصة | خصم الطاقة، الحجب بين الجولات، إلخ | لا شيء (نظيفة) |
| الحمولة | { round_id, timestamp } | مطابقة تمامًا |
| الحالة ودالة القراءة | getCurrentRoundId() | getCurrentGameId() |
| هل ستُزال؟ | تبقى دائمًا | مُضافة حديثًا |
القناتان مستقلتان: يُولَّد المعرّف في كل منهما على حدة ولا تتأثر إحدى الحالتين بالأخرى. وإذا استُدعيت القناتان لنفس الجولة فستحصل على قيمتين مختلفتين لـ round_id (يُفرَّق بينهما باسم الـ action).
الاستخدام مطابق لـ roundStart بندًا ببند. round_id اختياري، ويُنصح بعدم تمريره: عند حذفه يولّد الـ SDK تلقائيًا معرّف جولة فريدًا عالميًا (بالبادئة round_) ويحتفظ به داخليًا، ويُرسل gameEnd() القيمة نفسها تلقائيًا. اختياريًا timestamp (بالميلي ثانية؛ إن حُذف يُستخدم الوقت الحالي). بلا إرجاع Promise — إرسال دون انتظار.
// الاستخدام الموصى به: بلا أي معاملات؛ يولّد الـ SDK round_id ويربطه بهذه الجولة تلقائيًا
GameTokSDK.gameStart();
// استخدام للتوافق: تمرير round_id بنفسك (يجب أن يكون فريدًا عالميًا لكل جولة ولا يُعاد استخدامه أبدًا عبر الجولات،
// ولا تستخدم أرقامًا تسلسلية متزايدة داخل الجلسة مثل 1، 2، 3 — فبعد تحديث الصفحة ستتعارض مع الجولات السابقة)
GameTokSDK.gameStart({ round_id: 'round-' + crypto.randomUUID() });عند الحاجة لقراءة معرّف الجولة الحالية:
const gameRoundId = GameTokSDK.getCurrentGameId(); // له قيمة بعد gameStart، ويصبح null بعد gameEndإذا استُدعي
gameStartمرة أخرى دونgameEndللجولة السابقة، يُعدّ ذلك بداية جولة جديدة ويستبدل الـ SDK المعرّف الحالي مباشرة (تُعامل الجولة القديمة كجولة لم تنتهِ بشكل طبيعي، ولدى المنصة تسامح مع ذلك).
1.2.10b نهاية الجولة · قناة نظيفة (GAME_END)
يُستدعى بعد انتهاء الجولة (الفوز/الخسارة/انتهاء الوقت كلها تُعدّ نهاية)، بنفس معنى roundEnd. round_id اختياري: عند حذفه يُرسَل تلقائيًا نفس قيمة gameStart لهذه الجولة؛ وعند تمريره بنفسك يجب أن يطابق قيمة البداية. اختياريًا timestamp. بلا إرجاع Promise.
// الاستخدام الموصى به: بالاقتران مع gameStart() دون الاهتمام بـ round_id إطلاقًا
GameTokSDK.gameStart();
// ... منطق الجولة ...
GameTokSDK.gameEnd();
// استخدام للتوافق: تمرير صريح (يجب أن يطابق round_id الخاص بـ gameStart لهذه الجولة)
GameTokSDK.gameEnd({ round_id: myRoundId, timestamp: Date.now() });إذا لم تكن هناك جولة جارية على هذه القناة (لم يُستدعَ gameStart قط، أو سبق استدعاء gameEnd) ولم يُمرَّر round_id صريحًا، يُحذّر الـ SDK في وحدة التحكم ولا يُرسل إلى الأصل.
gameEnd()وroundEnd()لا يؤثر أحدهما في الآخر: استدعاءgameEnd()لا يُنهي الجولة التي فتحهاroundStart()، والعكس صحيح.
1.2.11 جلب النتيجة المستهدفة للقسيمة (GET_COUPON_TARGET_SCORE)
عند بدء اللعبة أو أثناء تشغيلها، يمكن للعبة الاستعلام من عميل المنصة عن النتيجة المستهدفة اللازمة لعرض نافذة القسيمة. بعد أن يُعيد العميل قيمة النتيجة، تستخدم اللعبة ذلك لتحديد ما إذا كانت ستستدعي showCouponDialog.
/**
* جلب النتيجة المستهدفة للقسيمة
* عادةً بلا معاملات إضافية
*/
GameTokSDK.getCouponTargetScore()
.then(({ action, error, data }) => {
console.log('النتيجة المستهدفة للقسيمة:', data.targetScore);
// عند وصول نتيجة اللعبة إلى data.targetScore، استدعِ showCouponDialog
})
.catch((e) => {
console.error('فشل جلب النتيجة المستهدفة للقسيمة:', e);
});مثال الاستجابة (JSON):
{
"action": "GET_COUPON_TARGET_SCORE",
"error": false,
"data": {
"targetScore": 5000
}
}شرح الحقول:
| الحقل | النوع | الوصف |
|---|---|---|
| targetScore | number | عتبة النتيجة المستهدفة لعرض نافذة القسيمة |
1.2.12 عرض نافذة القسيمة (SHOW_COUPON_DIALOG)
عندما تحتاج اللعبة إلى توجيه المستخدمين لاستلام أو استخدام قسيمة، استدعِ هذه الواجهة لإخطار عميل المنصة بعرض نافذة القسيمة. تُعرض النافذة من قِبل المنصة في طبقة حاوية اللعبة؛ ولا تحتاج اللعبة إلى التعامل مع واجهة النافذة. بلا إرجاع Promise — إرسال دون انتظار.
/**
* عرض نافذة القسيمة
* @param coupon_id اختياري، معرّف القسيمة؛ إن لم يُمرَّر، تقرر المنصة المحتوى المعروض
* @param scene اختياري، سيناريو التفعيل (يُعرَّف حسب العمل، مثل round_end أو level_up)
*/
GameTokSDK.showCouponDialog({
coupon_id: 'coupon-001',
scene: 'round_end',
});
// أو الاستدعاء بلا معاملات لعرض القسيمة الافتراضية للمنصة
GameTokSDK.showCouponDialog();مثال الحمولة المُرسَلة (JSON):
{
"action": "SHOW_COUPON_DIALOG",
"data": {
"coupon_id": "coupon-001",
"scene": "round_end"
}
}1.2.12a عرض لائحة المتصدرين (SHOW_LEADERBOARD)
عندما تحتاج اللعبة إلى توجيه المستخدم لعرض لائحة المتصدرين (مثل تسوية نهاية الجولة أو مدخل في الواجهة الرئيسية)، استدعِ هذه الواجهة لإخطار عميل المنصة بعرض لائحة المتصدرين. تُعرض اللائحة من قِبل المنصة في طبقة حاوية اللعبة؛ ولا تحتاج اللعبة إلى التعامل مع منطق الواجهة. بلا إرجاع Promise — إرسال دون انتظار.
/**
* عرض لائحة المتصدرين
* @param scene اختياري، سيناريو التفعيل (يُعرَّف حسب العمل، مثل round_end أو home)
*/
GameTokSDK.showLeaderboard({ scene: 'round_end' });
// أو الاستدعاء بلا معاملات
GameTokSDK.showLeaderboard();مثال الحمولة المُرسَلة (JSON):
{
"action": "SHOW_LEADERBOARD",
"data": {
"scene": "round_end"
}
}1.2.12b نافذة نقص الطاقة (SHOW_ENERGY_INSUFFICIENT_DIALOG)
عندما تعلم اللعبة بنقص الطاقة عبر getEnergy() / onEnergyUpdate() (انظر 1.2.12c) (مثل balance < cost_per_round، فلا يمكن بدء جولة جديدة)، تستدعي هذه الواجهة لتطلب من المنصة عرض نافذة نقص الطاقة في طبقة حاوية اللعبة (تتضمن توجيهات مثل تسجيل الحضور للحصول على طاقة، والذهاب للحصول على طاقة). لا تحكم اللعبة على قواعد الطاقة ولا تتعامل مع واجهة النافذة؛ مسؤوليتها التفعيل فقط. بلا إرجاع Promise — إرسال دون انتظار.
ملاحظة: تعرض المنصة نفسها هذه النافذة أيضًا من تلقاء نفسها في مواضع مثل خصم بداية الجولة وفحص نهاية الجولة؛ واستدعاء اللعبة هو تفعيل تكميلي (السيناريو النموذجي: ينقر اللاعب زر «ابدأ» بينما تعلم اللعبة مسبقًا أن الطاقة غير كافية، فتطلب النافذة بدلًا من عدم الاستجابة بصمت).
/**
* نافذة نقص الطاقة
* @param scene اختياري، سيناريو التفعيل (يُعرَّف حسب العمل، مثل round_start أو continue)
* @param round_id اختياري، معرّف الجولة المرتبطة لتسهيل الإسناد لدى المنصة (يمكن أخذه من getCurrentRoundId())
*/
GameTokSDK.showEnergyInsufficientDialog({
scene: 'round_start',
round_id: GameTokSDK.getCurrentRoundId() || undefined,
});
// أو الاستدعاء بلا معاملات
GameTokSDK.showEnergyInsufficientDialog();تدفق KC League: قبل بدء المباراة يتحقق خادم اللعبة لدى خادم المنصة عبر
entry-check؛ وعند إرجاعcanStart: falseاستدعِ هذه الواجهة نفسها مع إرفاق الحقول المُعادة من التحقق كما هي (قناة fire-and-forget تمرّر أي حقول JSON دون الحاجة إلى إصدار جديد من الـ SDK). بعد الاستدعاء تبقى اللعبة على شاشة الغلاف ويعود زر START قابلًا للنقر؛ وإذا أرادت المنصة بدء المباراة مباشرةً بعد شحن اللاعب/تسجيل حضوره، فسترسلAUTO_START_GAMEولا تحتاج اللعبة إلى إعادة المحاولة بنفسها.javascriptGameTokSDK.showEnergyInsufficientDialog({ scene: 'kc_entry_check', energyBalance: 0, // الرصيد المُعاد من entry-check، يُرفَق كما هو blockReason: 'NO_ENERGY' // القيمة التعدادية المُعادة من entry-check، تُرفَق كما هي });
مثال الحمولة المُرسَلة (JSON):
{
"action": "SHOW_ENERGY_INSUFFICIENT_DIALOG",
"data": {
"scene": "round_start",
"round_id": "round-1732000000-abc123"
}
}1.2.12c الاستعلام عن الطاقة ودفع تحديثاتها (getEnergy / onEnergyUpdate)
خصم الطاقة ونوافذها وشحنها كلها تتم من جانب المنصة؛ ولا تحتاج اللعبة سوى أمرين: عرض HUD الطاقة، وتعطيل زر «ابدأ» عند نقص الطاقة. مصدرا البيانات:
getEnergy()(طلب-استجابة): استعلام واحد مبدئي عند التهيئة؛onEnergyUpdate(handler)(دفع من المنصة): بعد كل تغيير في الطاقة (خصم بداية الجولة، تسجيل الحضور، الشحن، فحص نهاية الجولة، إلخ) تدفع المنصة أحدث قيمة، وتحدّث اللعبة الـ HUD بناءً عليها.
يُعيد/يدفع كلاهما نفس بنية البيانات:
| الحقل | النوع | الوصف |
|---|---|---|
| balance | number | null | رصيد الطاقة؛ يكون null عندما تكون ميزة الطاقة غير مفعّلة، وعندها يجب على اللعبة إخفاء واجهة الطاقة والعمل دون حدّ للطاقة |
| charge_enabled | boolean | ما إذا كان الخصم لكل جولة مفعّلًا لهذه اللعبة |
| cost_per_round | number | عدد وحدات الطاقة المستهلكة في كل جولة |
| reason | string | سبب التغيير (في الدفع فقط): initial / round_charge / round_end_check / checkin / recharge_check |
| seq | number | رقم تسلسلي متزايد رتيبًا؛ يجب على المستهلك تجاهل القيم القديمة غير المرتبة حسب seq (لا يُضمن وصول الدفعات بالترتيب) |
// التهيئة: استعلام واحد + تسجيل مستمع الدفع
const { data } = await GameTokSDK.getEnergy();
if (data.balance === null) {
hideEnergyHud(); // ميزة الطاقة غير مفعّلة؛ التراجع إلى وضع دون حدّ للطاقة
} else {
renderEnergyHud(data.balance, data.cost_per_round);
}
let lastSeq = -Infinity;
GameTokSDK.onEnergyUpdate((payload) => {
if (payload.seq <= lastSeq) return; // تجاهل القيم القديمة غير المرتبة
lastSeq = payload.seq;
renderEnergyHud(payload.balance, payload.cost_per_round);
setStartButtonEnabled(payload.balance === null || payload.balance >= payload.cost_per_round);
});تعويض مزامنة آخر قيمة: كما في أحداث الصوت، القيمة الافتراضية لـ options.sync في onEnergyUpdate هي true — إذا كان الـ SDK قد خزّن قيمة طاقة مؤقتًا عند التسجيل (من دفعة سابقة أو من استجابة getEnergy())، يُعاد إرسالها مرة واحدة بشكل غير متزامن، فلا يفوّت التسجيل المتأخر الحالة الحالية؛ مرّر { sync: false } لتعطيل ذلك. تتوفر أيضًا offEnergyUpdate / onceEnergyUpdate (بنفس توقيع أحداث الصوت)، إضافة إلى ذاكرة مؤقتة للقراءة فقط:
const { last, seq } = GameTokSDK.getEnergyState(); // last أحدث بيانات الطاقة (رتيبة حسب seq)، وتكون null عند عدم وجود بياناتالتصحيح المحلي (Mock): بعد enableMock() تُعيد getEnergy() القيمة المحاكاة المدمجة { balance: 100, charge_enabled: true, cost_per_round: 8 }، ويمكن تجاوزها بمعالج مخصص:
GameTokSDK.enableMock({ GET_ENERGY: () => ({ balance: 3, charge_enabled: true, cost_per_round: 8, seq: Date.now() }) });دفع ENERGY_UPDATE يأتي من المضيف ولا يتوفر في وضع Mock؛ أثناء التصحيح يمكن إطلاقه يدويًا من وحدة التحكم:
window.dispatchEvent(new MessageEvent('message', { data: JSON.stringify({
action: 'ENERGY_UPDATE',
data: { balance: 0, charge_enabled: true, cost_per_round: 8, reason: 'round_charge', seq: Date.now() }
})}));1.2.12d قناة السحب لعجلة الحظ / الصندوق الغامض
ينطبق فقط على قوالب مثل عجلة الحظ والصندوق الغامض التي «يحدد فيها الخادم النتيجة أولًا وتكتفي اللعبة بالعرض». تواصل ألعاب تحقيق النقاط استخدام
roundStart / addScore / roundEnd.
المسار الكامل:
- تحصل اللعبة على تكوين اللوحة عبر
getGameConfig()أوonGameConfig()؛ - بعد نقر اللاعب، تدخل اللعبة أولًا في الدوران الفارغ/التحضير، ثم تستدعي
requestDraw()؛ - تخصم المنصة الطاقة وتحدد الجائزة، ثم تدفع
DRAW_SETTLE؛ - تستدعي اللعبة دالتها
settle(prize_id)، وبعد استقرار الحركة تستدعيnotifyDrawResult()؛ - تعرض المنصة نافذة الجائزة، وبعد إغلاقها تطلب من اللعبة إعادة التعيين عبر
EXIT_AND_RESET_GAME.
// تكوين اللوحة: اطلبه مرة واحدة، واستمع لتحديثات التكوين اللاحقة
const { data: config } = await GameTokSDK.getGameConfig();
renderPrizes(config.prizes);
GameTokSDK.onGameConfig((nextConfig) => {
renderPrizes(nextConfig.prizes);
});
// موضع الاستقرار المُرسَل من المنصة: حدث لحظي، ولا تُعاد إرسال النتائج القديمة
GameTokSDK.onDrawSettle(({ prize_id, config_version, draw_id }) => {
if (config_version !== getCurrentConfigVersion()) {
// لا تعرض بالقوة تكوينًا قديمًا؛ أعِد جلب التكوين ودع منطق العمل يقرر إعادة المحاولة
GameTokSDK.getGameConfig().then(({ data }) => renderPrizes(data.prizes));
return;
}
game.settle(prize_id, () => {
GameTokSDK.notifyDrawResult({ prize_id, draw_id });
});
});
// نقر اللاعب: الدوران الفارغ أولًا، ثم إخطار المنصة بخصم الطاقة + تحديد الجائزة
function spin() {
game.spin(); // بلا موضع استقرار؛ يمكن الانتظار باستمرار
const roundId = GameTokSDK.requestDraw();
console.log('draw round:', roundId);
}
// بعد فشل الخصم أو انتهاء المهلة أو إغلاق نافذة الجائزة، تطلب المنصة العودة إلى وضع الانتظار
GameTokSDK.onExitAndResetGame(() => game.reset());
// زر «العودة إلى الرئيسية» المدمج في اللعبة: يكفي إخطار المنصة، وتنتظر إعادة التعيين EXIT_AND_RESET_GAME أعلاه (انظر 1.2.14a)
function onHomeButton() {
GameTokSDK.requestExit({ state: game.state });
}API:
| API | الاتجاه | الوصف |
|---|---|---|
getGameConfig(params?) | اللعبة→المنصة (طلب-استجابة) | جلب { config_version, game_type, activity_id, prizes[] } |
onGameConfig(handler, options?) | المنصة→اللعبة | دفع التكوين؛ sync افتراضيًا true، ويُعاد إرسال التكوين الحالي للتسجيل المتأخر |
offGameConfig / onceGameConfig | — | إلغاء / استماع لمرة واحدة |
getGameConfigState() | محلي في الـ SDK | قراءة آخر تكوين { last } |
requestDraw({ round_id?, timestamp? }) | اللعبة→المنصة | طلب الخصم + تحديد الجائزة عند النقر؛ يُعيد round_id الفعلي |
onDrawSettle(handler) | المنصة→اللعبة | الاستقرار على الجائزة بعد استقبال { prize_id, config_version, draw_id?, round_id? } |
offDrawSettle / onceDrawSettle | — | إلغاء / استماع لمرة واحدة؛ لا تُعاد إرسال النتائج القديمة |
notifyDrawResult({ prize_id, draw_id?, round_id? }) | اللعبة→المنصة | استقرت الحركة تمامًا، ويمكن للمنصة عرض نافذة الجائزة |
إذا تضمّنت استجابة getEnergy() حقل game_config مضمّنًا، يكتبه الـ SDK تلقائيًا في نفس ذاكرة التكوين المؤقتة؛ ويحصل عليه onGameConfig المسجَّل متأخرًا بالمزامنة أيضًا.
يُمنع استخدام weight من جانب العميل في السحب الرسمي. يجب أن يأتي prize_id من المنصة وأن يكون موجودًا في prizes[].id الحالية. لا يستخدم المسار الرئيسي لعجلة الحظ/الصندوق الغامض addScore أو getCouponTargetScore أو showCouponDialog عند تحقيق الهدف.
Mock:
GameTokSDK.enableMock(); // تُعيد getGameConfig() تكوين عجلة بست خانات مدمجًا1.2.13 مستمعو أحداث الصوت (onAudioSuspend / offAudioSuspend / onAudioResume / offAudioResume)
عندما تطلب المنصة من اللعبة الكتم (مثل دخول المستخدم غرفة بث مباشر، ورود مكالمة نظام، إلخ)، يُرسَل لها حدث تعليق الصوت؛ وعند السماح باستئناف الصوت، يُرسَل حدث الاستئناف. يجب على اللعبة إيقاف/استئناف جميع المؤثرات الصوتية والموسيقى الخلفية فور استقبال هذه الأحداث.
هذه واجهة مستمع أحداث، وليست واجهة Promise، ولا تُعيد أي قيمة.
// الاستماع لحدث تعليق الصوت
function handleAudioSuspend(payload) {
console.log('تم استقبال إشعار الكتم:', payload);
// كتم جميع المؤثرات الصوتية والموسيقى الخلفية في اللعبة
myGame.muteAll();
}
GameTokSDK.onAudioSuspend(handleAudioSuspend);
// الاستماع لحدث استئناف الصوت
function handleAudioResume(payload) {
console.log('تم استقبال إشعار استئناف الصوت:', payload);
// استئناف جميع المؤثرات الصوتية والموسيقى الخلفية في اللعبة
myGame.unmuteAll();
}
GameTokSDK.onAudioResume(handleAudioResume);إزالة المستمعين:
// إزالة مستمع تعليق الصوت (مرّر نفس مرجع الدالة المستخدمة عند التسجيل)
GameTokSDK.offAudioSuspend(handleAudioSuspend);
// إزالة مستمع استئناف الصوت
GameTokSDK.offAudioResume(handleAudioResume);معامل options.sync (مزامنة الحالة):
يدعم onAudioSuspend وonAudioResume معاملًا اختياريًا ثانيًا options، حيث يعالج sync (الافتراضي true) حالة تسجيل المستمع بعد انطلاق الحدث:
sync: true(الافتراضي) — إذا كان الصوت في حالة تعليق/استئناف عند تسجيل المستمع، سيُستدعى رد الاتصال فوريًا مرة واحدة في المهمة الدقيقة التالية، لضمان عدم إغفال اللعبة لتغييرات الحالة السابقة.sync: false— يستمع فقط للأحداث الجديدة اللاحقة؛ بلا مزامنة للحالة عند التسجيل.
// الافتراضي sync: true — إذا كان الصوت معلقًا بالفعل، يُطلق رد الاتصال فورًا مرة واحدة
GameTokSDK.onAudioSuspend((payload) => {
myGame.muteAll();
});
// تعطيل مزامنة الحالة، الاستماع للأحداث اللاحقة فقط
GameTokSDK.onAudioSuspend((payload) => {
myGame.muteAll();
}, { sync: false });الاستخدام الموصى به: سجّل المستمعين في أقرب وقت ممكن خلال مرحلة تهيئة اللعبة، مع إبقاء sync: true (الافتراضي)، بحيث تتمكن اللعبة من مزامنة الحالة بشكل صحيح حتى لو أرسلت المنصة تعليمات الكتم قبل التسجيل.
1.2.14 مستمعو أحداث التحكم في اللعبة (onAutoStartGame / offAutoStartGame / onExitAndResetGame / offExitAndResetGame)
يمكن للعميل إرسال نوعين من تعليمات التحكم إلى اللعبة (العميل => H5):
- AUTO_START_GAME: يُخطر اللعبة بالبدء تلقائيًا، بنفس تأثير النقر على زر start داخل اللعبة. له سيناريوان: ① ينقر اللاعب «العب مرة أخرى» في طبقة لائحة المتصدرين بالمنصة، فترسله المنصة بعد التحقق من الطاقة؛ ② البدء التلقائي عند الدخول — عندما يدخل اللاعب من صفحة ملصق البرنامج الصغير بالنقر على «ابدأ الآن»، ترسله المنصة مباشرة بعد جاهزية اللعبة، وقد يصل فور انتهاء تحميل اللعبة دون أي إجراء من اللاعب داخلها.
- EXIT_AND_RESET_GAME: يُخطر العميل اللعبة بالإنهاء والعودة للحالة الأولية (إنهاء الجولة الحالية فورًا، تنظيف العدّ التنازلي/الحركات، والعودة إلى الشاشة الأولية أو شاشة انتظار البدء).
⚠️ يجب على الألعاب المدمجة مع مسار الطاقة/لائحة المتصدرين تنفيذ هذين المستمعَين: تعتمد حماية المنصة من تسابق الطاقة (اكتشاف نقص الطاقة بعد بدء الجولة) على
EXIT_AND_RESET_GAMEلاستعادة الجولة، وتعتمد متابعة اللعب بعد تسجيل الحضور/الشحن والبدء التلقائي عند الدخول علىAUTO_START_GAMEلبدء الجولة؛ وعدم تنفيذهما يعني أن المنصة لن تستطيع إيقاف اللعبة عند نقص الطاقة.
هذه واجهة مستمع أحداث، وليست واجهة Promise، ولا تُعيد أي قيمة. يدعم AUTO_START_GAME إعادة إرسال لمرة واحدة عند التسجيل المتأخر: إذا وصلت التعليمات ولا يوجد مستمع بعد (مثل وصول البدء التلقائي عند الدخول قبل اكتمال تهيئة اللعبة)، يخزّن الـ SDK آخر تعليمات مؤقتًا، وعند تسجيل أول onAutoStartGame يُعيد إرسالها مرة واحدة بشكل غير متزامن ثم يمسحها — لذا يكفي تسجيل المستمع في مرحلة التهيئة كالمعتاد دون تفويت تعليمات الدخول. أما EXIT_AND_RESET_GAME فهي تعليمات لحظية خالصة بلا تعويض (تُرسَل أثناء الجولة فقط، حيث تكون اللعبة قد أكملت تهيئتها بالضرورة)؛ يُرجى تسجيل المستمع في أقرب وقت ممكن خلال مرحلة تهيئة اللعبة.
// الاستماع لتعليمات «بدء اللعبة»
function handleAutoStart(payload) {
console.log('تم استقبال تعليمات بدء اللعبة:', payload);
myGame.start(); // نفس النقر على زر start داخل اللعبة
}
GameTokSDK.onAutoStartGame(handleAutoStart);
// الاستماع لتعليمات «إنهاء اللعبة وإعادة التعيين»
function handleExitReset(payload) {
console.log('تم استقبال تعليمات الإنهاء وإعادة التعيين:', payload);
myGame.exitAndReset(); // إنهاء الجولة الحالية والعودة للحالة الأولية
}
GameTokSDK.onExitAndResetGame(handleExitReset);إزالة المستمعين:
// مرّر نفس مرجع الدالة المستخدمة عند التسجيل
GameTokSDK.offAutoStartGame(handleAutoStart);
GameTokSDK.offExitAndResetGame(handleExitReset);الاستماع مرة واحدة:
GameTokSDK.onceAutoStartGame((payload) => {
myGame.start();
});
GameTokSDK.onceExitAndResetGame((payload) => {
myGame.exitAndReset();
});صيغة الرسائل المُرسَلة من العميل:
{ "action": "AUTO_START_GAME" }{ "action": "EXIT_AND_RESET_GAME" }1.2.14a زر الرجوع المدمج في اللعبة (requestExit / REQUEST_EXIT)
إذا كانت واجهة اللعبة تحتوي على زر «العودة إلى الرئيسية / الخروج» خاص بها، فعند نقر اللاعب استدعِ requestExit() لإخطار المنصة «أريد العودة إلى الرئيسية». عند الاستقبال ترسل المنصة EXIT_AND_RESET_GAME، وتُنهي اللعبة الأمر في مستمع onExitAndResetGame القائم (إنهاء الجولة، تنظيف الحركات، العودة إلى شاشة انتظار البدء) — لا يغيّر requestExit() نفسه أي حالة للجولة، ولا يمسح round_id الحالي.
هذه واجهة fire-and-forget: بلا Promise، بلا قيمة إرجاع، وبلا إزالة للتكرار (تتولى المنصة إزالة تكرار النقرات المتعددة). جميع المعاملات اختيارية، وهي لأغراض ربط التشخيص فقط:
/**
* نقر اللاعب على زر «العودة إلى الرئيسية» داخل اللعبة
* @param reason اختياري، سبب التفعيل، الافتراضي 'player_button'
* @param state اختياري، حالة اللعبة عند النقر (مثل 'IDLE' / 'PLAY' / 'RESULT')، للتشخيص فقط
* @param timestamp اختياري، طابع زمني بالميلي ثانية؛ إن حُذف يُستخدم الوقت الحالي
*/
GameTokSDK.requestExit();
// مع معلومات التشخيص
GameTokSDK.requestExit({ reason: 'player_button', state: 'PLAY' });
// الإنهاء الفعلي ما زال يتم هنا (يجب تسجيله قبل الدمج، انظر 1.2.14)
GameTokSDK.onExitAndResetGame(() => myGame.exitAndReset());إذا كانت هناك جولة جارية (بعد
roundStartوقبلroundEnd)، يُرفق الـ SDK تلقائيًاround_idلتسهيل ربط المنصة بهذه الجولة؛ ولا حاجة لتمريره يدويًا.
مثال الحمولة المُرسَلة (JSON):
{
"action": "REQUEST_EXIT",
"data": {
"reason": "player_button",
"state": "PLAY",
"round_id": "round_xxx",
"timestamp": 1717000000000
}
}شرح الحقول:
| الحقل | النوع | الوصف |
|---|---|---|
| reason | string | سبب التفعيل، الافتراضي player_button |
| state | string | اختياري، حالة اللعبة عند النقر، للتشخيص فقط |
| round_id | string | اختياري، يُرفقه الـ SDK تلقائيًا عند النقر أثناء الجولة |
| timestamp | number | طابع زمني بالميلي ثانية، الافتراضي الوقت الحالي |
⚠️ لا تُعِد تعيين اللعبة بنفسك بعد
requestExit()— انتظر دائمًا وصولEXIT_AND_RESET_GAMEثم أنهِ الأمر، لتجنب عدم التزامن بين نافذة/تسوية المنصة وشاشة اللعبة.
1.2.14b إخطار نتيجة المباراة (showMatchResult / SHOW_MATCH_RESULT)
مخصّص لتدفقات مثل KC League حيث «لا تعرض اللعبة شاشة تسوية خاصة بها بعد انتهاء المباراة، بل تعرض المنصة نافذة التسوية». لا حاجة لاستدعائه في تدفق تسجيل النقاط العادي.
بعد انتهاء المباراة يُبلّغ خادم اللعبة خادم المنصة عبر POST /kc-league/match-result، ويُعيد خادم المنصة في الاستجابة نتيجة تسوية هذه المباراة result؛ يدفعها خادم اللعبة إلى عميل اللعبة، ثم يستدعي عميل اللعبة showMatchResult() لتسليم result كما هي إلى عميل المنصة لعرض نافذة التسوية. تُمرَّر القفزات الثلاث حرفيًا من البداية إلى النهاية: لا تحلّلها اللعبة، ولا يحلّلها الـ SDK، ولا يوجد أي اتفاق على الحقول، ويقرأها عميل المنصة في النهاية. وهكذا إذا احتاجت النافذة لاحقًا إلى حقول جديدة فلا حاجة لإصدار جديد من جهة اللعبة أو الـ SDK.
هذه واجهة fire-and-forget: بلا Promise، بلا قيمة إرجاع. يقرّر عميل المنصة بالكامل عرض النافذة وإغلاقها والوجهة التالية؛ عند نقر اللاعب «جولة أخرى» في النافذة → ترسل المنصة AUTO_START_GAME، وعند نقر «خروج» → ترسل EXIT_AND_RESET_GAME، وتستجيب اللعبة في المستمعات القائمة (انظر 1.2.14).
/**
* إخطار نتيجة المباراة
* @param matchId إلزامي، معرّف هذه المباراة (يولّده خادم اللعبة، ومطابق لما في طلب match-result)
* @param result إلزامي، كائن result من استجابة match-result، يُمرَّر كما هو؛ عند فشل الإبلاغ مرّر null
* @param reason اختياري، سبب كون result يساوي null: 'REPORT_FAILED' (أعادت المنصة غير 2xx) | 'REPORT_TIMEOUT' (انتهت المهلة)
* @param round_id اختياري، إن حُذف يُرفقه الـ SDK تلقائيًا (الجولة الحالية أو الأخيرة)، لا يُمرَّر عادةً
* @param timestamp اختياري، طابع زمني بالميلي ثانية؛ إن حُذف يُستخدم الوقت الحالي
*/
// التسلسل الموصى به: إخطار نهاية الجولة أولًا، ثم إخطار التسوية
GameTokSDK.roundEnd();
GameTokSDK.showMatchResult({
matchId: msg.matchId,
result: msg.platformResult, // كائن نتيجة المنصة المحمول كما هو في دفعة الخادم
});
// عند فشل الإبلاغ (لم يحصل خادم اللعبة على result)
GameTokSDK.showMatchResult({ matchId: msg.matchId, result: null, reason: 'REPORT_FAILED' });العلاقة بين round_id وmatchId: يولّد roundStart() قيمة round_id قبل الدخول إلى المطابقة، بينما لا يولّد خادم اللعبة matchId إلا بعد نجاح المطابقة، فالقيمتان غير متساويتين. يُرفق الـ SDK تلقائيًا round_id هذه الجولة (حتى عند الاستدعاء بعد roundEnd() — إذ يتذكّر الـ SDK الجولة الأخيرة)، وتستخدم المنصة الزوج round_id + matchId لربط خصم الطاقة وسجل التسوية لهذه المباراة، دون أي معالجة من جهة اللعبة.
مثال الحمولة المُرسَلة (JSON):
{
"action": "SHOW_MATCH_RESULT",
"data": {
"matchId": "sa-2041-Ckh2qj92Z",
"result": { "league": "L20260901", "self": { "rank": 1, "pointsDelta": 12 }, "participants": [] },
"round_id": "round-1788250600-abc123",
"timestamp": 1788250693456
}
}شرح الحقول:
| الحقل | النوع | الوصف |
|---|---|---|
| matchId | string | إلزامي، معرّف هذه المباراة |
| result | object | null | إلزامي، كائن التسوية المُعاد من خادم المنصة، يُمرَّر كما هو؛ null تعني فشل الإبلاغ |
| reason | string | اختياري، القيمة التعدادية لسبب كون result يساوي null |
| round_id | string | اختياري، إن حُذف يُرفقه الـ SDK تلقائيًا للجولة الحالية أو الأخيرة |
| timestamp | number | طابع زمني بالميلي ثانية، الافتراضي الوقت الحالي |
القيود:
- يجب أن يكون
resultكائنًا قابلًا للتسلسل إلى JSON بشكل خالص — لا يجوز أن يحتوي على دوال أوundefinedأو مراجع دائرية أوDate/Mapوما شابه، وإلا يرفض الـ SDK الإرسال ويطبعconsole.warn. - لا حدّ للحجم (نحو 2 KB فعليًا في غرفة من عشرة لاعبين، وسقف القناة بمستوى الميغابايت).
- عند غياب
matchId، أو عدم وجود مفتاحresultفي المعاملات (عند فشل الإبلاغ مرّرnullصراحةً)، يُحذّر الـ SDK ولا يُرسل. - بعد الاستدعاء لا تبدأ المباراة ولا تُعِد التعيين بنفسك؛ انتظر دائمًا وصول
AUTO_START_GAME/EXIT_AND_RESET_GAMEثم تصرّف، لتجنب عدم التزامن مع نافذة المنصة.
1.2.14c إخطار نتيجة WPP (showWppResult / SHOW_WPP_RESULT)
مخصّص لمسار WPP حيث لا تعرض اللعبة شاشة النتيجة الخاصة بها، وتتولّى المنصة عرض نافذة النتيجة.
يسلّم محتوى النتيجة كما هو إلى عميل المنصة لعرض نافذة النتيجة. لا يتحقق الـ SDK من الحقول ولا يحلّلها ولا يضيف أيًّا منها: لا توجد حقول إلزامية، ولا يُرفَق round_id أو timestamp تلقائيًا — ضَعْ هذه الحقول بنفسك إن احتجتها. تُحدَّد الحقول المُرسَلة باتفاق بين اللعبة وعميل المنصة، لذا لا حاجة لإصدار جديد من أي من الجهتين عند إضافة حقول أو حذفها لاحقًا.
هذه واجهة fire-and-forget: بلا Promise وبلا قيمة إرجاع. عرض النافذة وإغلاقها وكل ما يليها يقرّره عميل المنصة؛ فعند ضغط اللاعب على «جولة أخرى» تُرسل المنصة AUTO_START_GAME، وعند ضغطه على «خروج» تُرسل EXIT_AND_RESET_GAME، وتكتفي اللعبة بالاستجابة في مستمعاتها الحالية (انظر 1.2.14).
/**
* إخطار نتيجة WPP
* @param payload اختياري، محتوى النتيجة المُمرَّر كما هو إلى عميل المنصة، والحقول تحدّدها أنت
*/
GameTokSDK.showWppResult(msg.platformResult);مثال الإرسال (JSON):
{
"action": "SHOW_WPP_RESULT",
"data": { "self": { "rank": 1, "score": 3200 }, "participants": [] }
}شرح الحقول: لا توجد حقول متفق عليها. data هو نفسه الكائن الذي مرّرته عند الاستدعاء، ويقرأه العميل مباشرةً.
القيود:
- يجب أن يكون payload كائنًا قابلًا للتسلسل إلى JSON بشكل خالص — لا يجوز أن يحتوي على دوال أو
undefinedأو مراجع دائرية أوDate/Mapوما شابه، وإلا يرفض الـ SDK الإرسال ويطبعconsole.warn. - بعد الاستدعاء لا تبدأ المباراة ولا تُعِد التعيين بنفسك؛ انتظر دائمًا وصول
AUTO_START_GAME/EXIT_AND_RESET_GAMEثم تصرّف، لتجنب عدم التزامن مع نافذة المنصة.
1.2.15 حجز حدث مميّز (bookHQ / onBookHQ)
يُستخدم لحجز حدث مميّز (HQ). يُرسل استدعاء bookHQ() طلب الحجز إلى العميل، وعند استقباله قد يطلب العميل تسجيل الدخول أولًا (رمز التحقق عبر SMS / تفويض طرف ثالث، إلخ)؛ وقد يكون المسار بأكمله طويلًا وغير محدد المدة. بعد اكتماله تُعاد النتيجة عبر رد الاتصال onBookHQ.
يعتمد على «الإرسال + مستمع الأحداث» بدلًا من الـ Promise: لأن مسار تسجيل الدخول طويل وغير محدد المدة، صُمِّم bookHQ() كـ إرسال دون انتظار (fire-and-forget، بلا مهلة، بلا انتظار لرد الاتصال)، وتُسلَّم النتيجة بشكل غير متزامن عبر onBookHQ. بهذه الطريقة، حتى لو كان مسار تسجيل الدخول طويلًا جدًا فلن يُقطع بسبب انتهاء المهلة.
الاستخدام في خطوتين: سجّل مستمع onBookHQ أولًا، ثم استدعِ bookHQ للإرسال.
/**
* 1. سجّل رد الاتصال أولًا (مهما طال تسجيل دخول العميل لا يؤثر، ولن تنتهي المهلة)
* payload كائن مُوحّد؛ فحص النجاح: payload.success === true
*/
GameTokSDK.onBookHQ(function (payload) {
if (payload && payload.success === true) {
console.log('نجح الحجز');
// حدّث الواجهة إلى «تم الحجز»
} else {
console.log('فشل الحجز / ألغى المستخدم، يمكن إعادة المحاولة');
// أعِد الواجهة إلى قابلة للنقر
}
});
/**
* 2. أرسل عند نقر المستخدم على «حجز» (بلا حجب، بلا انتظار)
* @param pkId مطلوب، معرّف الحدث (رقم أو سلسلة رقمية)
* @param scene اختياري، معرّف السيناريو، الافتراضي "kcSwiper"
* @returns boolean — ما إذا كان الجسر متاحًا (ما إذا أُرسلت الرسالة / أُدرجت في الطابور)
*/
GameTokSDK.bookHQ({ pkId: 123, scene: 'kcSwiper' });إذا كنت تريد فقط «إرسال حجز دون الاهتمام بالنتيجة»، يمكنك استدعاء
bookHQ()وحده دون تسجيلonBookHQ. هناك أيضًا طريقتان مصاحبتان:offBookHQ(handler)(إزالة المستمع) وonceBookHQ(handler)(الاستماع مرة واحدة). نادرًا ما تُستخدمان — عادةً تسجّل صفحات الأنشطة مرة واحدة عند التهيئة.
مثال الحمولة المُرسَلة (JSON):
{
"action": "bookHQ",
"data": {
"pkId": 123,
"scene": "kcSwiper"
}
}حمولة رد العميل (ما يستقبله معالج onBookHQ، مُوحّدة على الطرفين):
{ "success": true }شرح الحقول:
| الحقل | النوع | الوصف |
|---|---|---|
| success | boolean | ما إذا نجح الحجز؛ true تعني النجاح، وfalse / غيابها تعني الفشل أو الإلغاء |
ملاحظة: تختلف مغلفات الرد من العميل قليلًا بين المنصتين (iOS يحمل الحمولة في
contentكسلسلة JSON، وAndroid فيdataككائن). يوحّد الـ SDK ذلك تلقائيًا، لذا يستقبل رد الاتصالonBookHQدائمًا كائنًا مُنظَّمًا؛ ولا يحتاج جانب العمل سوى فحصpayload.success.
1.2.16 الانتقال إلى صفحة تفاصيل العلامة التجارية (BRAND_ACTIVE_DETAIL_PAGE / BRAND_DT_DETAIL_PAGE)
بعد نقر زر داخل اللعبة، يمكن استدعاء الواجهات التالية لإخطار العميل بالانتقال إلى صفحة تفاصيل العلامة التجارية المقابلة. ينفّذ العميل الانتقال بنفسه بعد استقبال التعليمات، ولا تحتاج اللعبة إلى انتظار رد.
| الطريقة | action | الوصف |
|---|---|---|
GameTokSDK.brandActiveDetailPage(params) | BRAND_ACTIVE_DETAIL_PAGE | الانتقال إلى صفحة تفاصيل نشاط العلامة التجارية |
GameTokSDK.brandDtDetailPage(params) | BRAND_DT_DETAIL_PAGE | الانتقال إلى صفحة تفاصيل DT للعلامة التجارية |
كلاهما fire-and-forget (إرسال دون انتظار): بلا إرجاع Promise، بلا مهلة، بلا انتظار لرد الاتصال.
brandActiveDetailPage (BRAND_ACTIVE_DETAIL_PAGE)
/**
* الانتقال إلى صفحة تفاصيل نشاط العلامة التجارية
* @param gameId مطلوب، معرّف اللعبة
*/
GameTokSDK.brandActiveDetailPage({
gameId: 'game-001',
});مثال الحمولة المُرسَلة (JSON):
{
"action": "BRAND_ACTIVE_DETAIL_PAGE",
"data": {
"gameId": "game-001"
}
}شرح الحقول:
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
| gameId | string | number | نعم | معرّف اللعبة |
brandDtDetailPage (BRAND_DT_DETAIL_PAGE)
عند الانتقال إلى صفحة تفاصيل DT للعلامة التجارية، يجب تمرير الألوان الخمسة للوحة ألوان اللعبة ليعرض العميل الصفحة وفق أدوار التصميم. يُنصح بتمرير قيم الألوان كسلاسل CSS (مثل #RRGGBB).
/**
* الانتقال إلى صفحة تفاصيل DT للعلامة التجارية
* @param background مطلوب، لون الخلفية الرئيسي للعبة (مرجع سلسلة الألوان، الأغمق بين الثلاثة) — التصميم a
* @param surface مطلوب، لون البطاقة (اللوحات / placeholder فوق الخلفية، الأفتح بين الثلاثة) — التصميم b
* @param text مطلوب، لون النص 1 (لون تأكيد بارز من سلسلة مختلفة تمامًا عن الخلفية) — التصميم c
* @param accent مطلوب، اللون المساعد (تمييز القسائم / حالة التحديد، متوسط الإضاءة) — التصميم d
* @param text2 مطلوب، لون النص 2 (النص العام، أسود أو أبيض) — التصميم e
*/
GameTokSDK.brandDtDetailPage({
background: '#2D6A4F', // أخضر نعناعي (الخلفية الرئيسية، الأغمق)
surface: '#D8F3DC', // أخضر أفتح (البطاقة)
text: '#E85D04', // برتقالي محمرّ (نص التأكيد، سلسلة مختلفة)
accent: '#74C69D', // أخضر متوسط (تمييز مساعد)
text2: '#000000', // النص العام
});مثال الحمولة المُرسَلة (JSON):
{
"action": "BRAND_DT_DETAIL_PAGE",
"data": {
"background": "#2D6A4F",
"surface": "#D8F3DC",
"text": "#E85D04",
"accent": "#74C69D",
"text2": "#000000"
}
}شرح الحقول:
| الحقل | رمز التصميم | النوع | مطلوب | الوصف | مثال (لعبة خضراء) |
|---|---|---|---|---|---|
| background | a | string | نعم | لون الخلفية الرئيسي للعبة (مرجع سلسلة الألوان، الأغمق بين الثلاثة) | أخضر نعناعي |
| surface | b | string | نعم | لون البطاقة (اللوحات / placeholder فوق الخلفية، الأفتح بين الثلاثة) | أخضر أفتح |
| text | c | string | نعم | لون النص 1 (لون تأكيد بارز من سلسلة مختلفة تمامًا عن الخلفية، مثل البرتقالي المحمرّ على الأخضر أو الذهبي على البرقوقي الداكن) | برتقالي محمرّ / ذهبي |
| accent | d | string | نعم | اللون المساعد (تمييز القسائم / حالة التحديد، متوسط الإضاءة) | أخضر بين الاثنين |
| text2 | e | string | نعم | لون النص 2 (النص العام، أسود أو أبيض) | #000000 |
المعاملات وشكل الاستجابة
- أمثلة معاملات شائعة (مرجعية؛ التفاصيل لكل واجهة):
- getProfile: `` (غالبًا بلا معاملات إضافية)
- getProfiles:
{ uids: number[] }(مطلوب، مصفوفة غير فارغة؛ تُعيدProfile[]، نفس بنيةgetProfile.dataعلى شكل مصفوفة) - purchase:
{ productId: string } - storageSet:
{ key: string, value: any } - storageGet:
{ key: string } - addScore:
{ score: number, scoreType: string } - getPermissionMic: `` (غالبًا بلا معاملات إضافية)
- topup:
{ amount: number, ... }(حقول أخرى حسب بروتوكول العميل) - roundStart / roundEnd:
{ round_id: string, timestamp?: number } - gameStart / gameEnd:
{ round_id: string, timestamp?: number }(الحمولة مطابقة لـ roundStart / roundEnd؛ قناة نظيفة بنفس المعنى؛ جميعها اختيارية، إرسال دون انتظار، بلا قيمة إرجاع) - getCouponTargetScore: `` (عادةً بلا معاملات إضافية)
- showCouponDialog:
{ coupon_id?: string, scene?: string } - showLeaderboard:
{ scene?: string }(اختياري؛ إرسال دون انتظار، بلا قيمة إرجاع) - showEnergyInsufficientDialog:
{ scene?: string, round_id?: string }(اختياري؛ إرسال دون انتظار، بلا قيمة إرجاع) - brandActiveDetailPage:
{ gameId: string|number }(مطلوب؛ إرسال دون انتظار، بلا قيمة إرجاع) - brandDtDetailPage:
{ background: string, surface: string, text: string, accent: string, text2: string }(الألوان الخمسة للوحة الألوان مطلوبة؛ إرسال دون انتظار، بلا قيمة إرجاع) - onAudioSuspend / onAudioResume:
(handler: Function, options?: { sync?: boolean }) - offAudioSuspend / offAudioResume:
(handler: Function) - onAutoStartGame / onExitAndResetGame:
(handler: Function) - offAutoStartGame / offExitAndResetGame:
(handler: Function) - requestExit:
{ reason?: string, state?: string, timestamp?: number }(كلها اختيارية؛ إرسال دون انتظار، بلا قيمة إرجاع؛ يُرفَقround_idتلقائيًا أثناء الجولة) - showMatchResult:
{ matchId: string, result: object|null, reason?: string, round_id?: string, timestamp?: number }(matchIdوresultإلزاميان؛ إرسال دون انتظار، بلا قيمة إرجاع؛ يُرفَقround_idالجولة الحالية أو الأخيرة تلقائيًا) - showWppResult:
Record<string, any>(اختياري، بلا حقول متفق عليها؛ إرسال دون انتظار، بلا قيمة إرجاع؛ لا يتحقق الـ SDK ولا يحلّل ولا يضيفround_id/timestamp) - bookHQ:
{ pkId: number|string, scene?: string }(إرسال دون انتظار، تُعيد boolean؛ تُسلَّم النتيجة عبر رد الاتصالonBookHQ) - onBookHQ:
(handler: Function)(يستقبل المعالج{ success: boolean, ... }؛offBookHQ/onceBookHQطريقتان مصاحبتان نادرًا ما تُستخدمان)
- عند النجاح، الاستجابة الموحّدة:
{
action: string; // اسم الإجراء (مثل 'GET_PROFILE')
error: false; // false عند النجاح (عند الفشل يُرفض الـ Promise)
data: any; // حمولة الأصل؛ الحقول حسب الإجراء
}- سلوك الفشل:
topup: معظم نتائج العمل (نجاح/فشل/إلغاء) تُحلّ فيthenعبرdata.code؛catchمخصّص لأخطاء الـ SDK وانتهاء المهلة.- واجهات Promise الأخرى: عند الفشل يُرفض الـ Promise بكائن
Error؛ يُفضّل التعامل في.catch(تسجيل، إعادة محاولة، أو تنبيه).
ملاحظات مهمة
لا تستبدل ولا تحذف الكائنات العامة التالية وإلا يتعطّل الـ SDK:
window.GameTokSDK(نقطة الدخول الرئيسية)
مثال (لا تفعل هذا):
// خطر — يعطل الجسر مع iOS / Android
window.GameTokSDK = {};
window.GameTokSDK = null;
delete window.GameTokSDK;مثال كامل
// جلب الملف الشخصي
GameTokSDK.getProfile({})
.then((resp) => {
console.log('Profile:', resp.data);
})
.catch((e) => {
console.error('فشل جلب الملف الشخصي:', e);
});
// جلب ملفات مستخدمين متعددة (للوائح المتصدرين، قوائم الأصدقاء، إلخ)
GameTokSDK.getProfiles({ uids: [10001, 10002, 10003] })
.then(({ data }) => {
const map = new Map(data.map(p => [p.uid, p]));
console.log('ملف 10001:', map.get(10001));
})
.catch((e) => {
console.error('فشل الجلب المتعدد:', e);
});
// تخزين
GameTokSDK.storageSet({ key: 'config', value: { lang: 'ar' } })
.then(() => {
console.log('تم التخزين بنجاح');
})
.catch((e) => {
console.error('فشل التخزين:', e);
});
// قراءة
GameTokSDK.storageGet({ key: 'config' })
.then((resp) => {
console.log('Config:', resp.data.value);
})
.catch((e) => {
console.error('فشلت القراءة:', e);
});
// شراء
GameTokSDK.purchase({ productId: 'HAB.WATER.10.COINS' })
.then((resp) => {
console.log('Purchase:', resp.data);
})
.catch((e) => {
console.error('فشل الشراء:', e);
});
// نتيجة
GameTokSDK.addScore({ score: 5, scoreType: 'score' })
.then(() => {
console.log('Score added');
})
.catch((e) => {
console.error('فشل إرسال النتيجة:', e);
});
// الميكروفون (أندرويد، بلا إرجاع)
GameTokSDK.getPermissionMic();
// الشحن: افحص data.code في then (200 = نجاح)
GameTokSDK.topup({ amount: 100 })
.then(({ data }) => {
if (data && data.code === 200) {
console.log('نجح الشحن', data);
} else {
console.log('لم ينجح الشحن', data);
}
})
.catch((e) => console.error('خطأ في الشحن', e));
// إشعارات الجولة (نفس round_id)
const rid = 'r-' + Date.now();
GameTokSDK.roundStart({ round_id: rid });
GameTokSDK.roundEnd({ round_id: rid });
// إشعارات الجولة على القناة النظيفة (بنفس معنى السطرين أعلاه وبنفس الحمولة، دون دلالات الطاقة)
GameTokSDK.gameStart();
GameTokSDK.gameEnd();
// جلب النتيجة المستهدفة للقسيمة، ثم عرض النافذة عند الوصول إليها
GameTokSDK.getCouponTargetScore()
.then(({ data }) => {
console.log('النتيجة المستهدفة للقسيمة:', data.targetScore);
if (myGame.score >= data.targetScore) {
GameTokSDK.showCouponDialog({ scene: 'score_reached' });
}
})
.catch((e) => console.error('فشل جلب النتيجة المستهدفة للقسيمة', e));
// مستمعو أحداث الصوت (يُسجَّلان خلال تهيئة اللعبة)
GameTokSDK.onAudioSuspend((payload) => {
console.log('كتم', payload);
myGame.muteAll();
});
GameTokSDK.onAudioResume((payload) => {
console.log('استئناف الصوت', payload);
myGame.unmuteAll();
});
// حجز حدث مميّز: سجّل رد الاتصال أولًا ثم أرسل (إرسال دون انتظار، بلا مهلة)
GameTokSDK.onBookHQ((payload) => {
if (payload && payload.success === true) {
console.log('نجح الحجز');
} else {
console.log('فشل الحجز / إلغاء، يمكن إعادة المحاولة');
}
});
GameTokSDK.bookHQ({ pkId: 123, scene: 'kcSwiper' });
// زر «العودة إلى الرئيسية» المدمج في اللعبة: إخطار المنصة فقط، والإنهاء ينتظر EXIT_AND_RESET_GAME
document.getElementById('btn-home').onclick = () => GameTokSDK.requestExit({ state: 'PLAY' });
// الانتقال إلى صفحة تفاصيل العلامة التجارية (إرسال دون انتظار؛ حقول المعاملات قيد التحديد، تُمرَّر وفق البروتوكول النهائي)
GameTokSDK.brandActiveDetailPage({ gameId: 'game-001' });
GameTokSDK.brandDtDetailPage({
background: '#2D6A4F',
surface: '#D8F3DC',
text: '#E85D04',
accent: '#74C69D',
text2: '#000000',
});