playkit-sdk 1.2.3 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/playkit-sdk.cjs.js +676 -19
- package/dist/playkit-sdk.cjs.js.map +1 -1
- package/dist/playkit-sdk.d.ts +153 -8
- package/dist/playkit-sdk.esm.js +675 -20
- package/dist/playkit-sdk.esm.js.map +1 -1
- package/dist/playkit-sdk.umd.js +676 -19
- package/dist/playkit-sdk.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/playkit-sdk.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* playkit-sdk v1.2.
|
|
2
|
+
* playkit-sdk v1.2.6
|
|
3
3
|
* PlayKit SDK for JavaScript
|
|
4
4
|
* @license SEE LICENSE IN LICENSE
|
|
5
5
|
*/
|
|
@@ -12,6 +12,41 @@ var EventEmitter = require('eventemitter3');
|
|
|
12
12
|
/**
|
|
13
13
|
* Common types used across the SDK
|
|
14
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* Helper to create a text message
|
|
17
|
+
*/
|
|
18
|
+
function createTextMessage(role, text) {
|
|
19
|
+
return { role, content: text };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Helper to create a multimodal message with text and images
|
|
23
|
+
*/
|
|
24
|
+
function createMultimodalMessage(role, text, images, audios) {
|
|
25
|
+
const content = [];
|
|
26
|
+
// Add text part first
|
|
27
|
+
if (text) {
|
|
28
|
+
content.push({ type: 'text', text });
|
|
29
|
+
}
|
|
30
|
+
// Add image parts
|
|
31
|
+
if (images) {
|
|
32
|
+
for (const img of images) {
|
|
33
|
+
content.push({
|
|
34
|
+
type: 'image_url',
|
|
35
|
+
image_url: { url: img.url, detail: img.detail },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Add audio parts
|
|
40
|
+
if (audios) {
|
|
41
|
+
for (const audio of audios) {
|
|
42
|
+
content.push({
|
|
43
|
+
type: 'input_audio',
|
|
44
|
+
input_audio: { data: audio.data, format: audio.format },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { role, content };
|
|
49
|
+
}
|
|
15
50
|
/**
|
|
16
51
|
* Error types that can be thrown by the SDK
|
|
17
52
|
*/
|
|
@@ -229,7 +264,7 @@ class TokenStorage {
|
|
|
229
264
|
* Will be removed in v2.0
|
|
230
265
|
*/
|
|
231
266
|
// i18n translations
|
|
232
|
-
const translations$
|
|
267
|
+
const translations$2 = {
|
|
233
268
|
en: {
|
|
234
269
|
signIn: 'Sign In / Register',
|
|
235
270
|
signInSubtitle: 'This game uses PlayKit for cost management.\nIf you don\'t have an account, we\'ll automatically register you. Sign up and get free AI game credits!',
|
|
@@ -374,7 +409,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
374
409
|
* Get translated text
|
|
375
410
|
*/
|
|
376
411
|
t(key) {
|
|
377
|
-
return translations$
|
|
412
|
+
return translations$2[this.currentLanguage][key];
|
|
378
413
|
}
|
|
379
414
|
/**
|
|
380
415
|
* Start the authentication flow
|
|
@@ -1101,12 +1136,81 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1101
1136
|
* 3. Poll /api/device-auth/poll until authorized or error
|
|
1102
1137
|
* 4. Return access token and refresh token
|
|
1103
1138
|
*/
|
|
1139
|
+
const translations$1 = {
|
|
1140
|
+
en: {
|
|
1141
|
+
loginToPlay: 'Login to Play',
|
|
1142
|
+
loginWithPlayKit: 'uses PlayKit for secure login',
|
|
1143
|
+
loginInNewWindow: 'Please complete login in the opened window',
|
|
1144
|
+
cancel: 'Cancel',
|
|
1145
|
+
close: 'Close',
|
|
1146
|
+
loginDenied: 'Login Denied',
|
|
1147
|
+
loginDeniedDesc: 'You have denied the authorization request',
|
|
1148
|
+
sessionExpired: 'Session Expired',
|
|
1149
|
+
sessionExpiredDesc: 'The login session has timed out. Please try again',
|
|
1150
|
+
loginFailed: 'Login Failed',
|
|
1151
|
+
loginFailedDesc: 'An error occurred during login. Please try again',
|
|
1152
|
+
},
|
|
1153
|
+
zh: {
|
|
1154
|
+
loginToPlay: '登录开始游玩',
|
|
1155
|
+
loginWithPlayKit: '使用 PlayKit 进行安全登录',
|
|
1156
|
+
loginInNewWindow: '请在打开的窗口中完成登录',
|
|
1157
|
+
cancel: '取消',
|
|
1158
|
+
close: '关闭',
|
|
1159
|
+
loginDenied: '登录被拒绝',
|
|
1160
|
+
loginDeniedDesc: '您拒绝了授权请求',
|
|
1161
|
+
sessionExpired: '会话已过期',
|
|
1162
|
+
sessionExpiredDesc: '登录会话已超时,请重试',
|
|
1163
|
+
loginFailed: '登录失败',
|
|
1164
|
+
loginFailedDesc: '登录时发生错误,请重试',
|
|
1165
|
+
},
|
|
1166
|
+
'zh-TW': {
|
|
1167
|
+
loginToPlay: '登入開始遊玩',
|
|
1168
|
+
loginWithPlayKit: '使用 PlayKit 進行安全登入',
|
|
1169
|
+
loginInNewWindow: '請在開啟的視窗中完成登入',
|
|
1170
|
+
cancel: '取消',
|
|
1171
|
+
close: '關閉',
|
|
1172
|
+
loginDenied: '登入被拒絕',
|
|
1173
|
+
loginDeniedDesc: '您拒絕了授權請求',
|
|
1174
|
+
sessionExpired: '會話已過期',
|
|
1175
|
+
sessionExpiredDesc: '登入會話已逾時,請重試',
|
|
1176
|
+
loginFailed: '登入失敗',
|
|
1177
|
+
loginFailedDesc: '登入時發生錯誤,請重試',
|
|
1178
|
+
},
|
|
1179
|
+
ja: {
|
|
1180
|
+
loginToPlay: 'ログインしてプレイ',
|
|
1181
|
+
loginWithPlayKit: 'PlayKit で安全にログイン',
|
|
1182
|
+
loginInNewWindow: '開いたウィンドウでログインを完了してください',
|
|
1183
|
+
cancel: 'キャンセル',
|
|
1184
|
+
close: '閉じる',
|
|
1185
|
+
loginDenied: 'ログインが拒否されました',
|
|
1186
|
+
loginDeniedDesc: '認証リクエストが拒否されました',
|
|
1187
|
+
sessionExpired: 'セッション期限切れ',
|
|
1188
|
+
sessionExpiredDesc: 'ログインセッションがタイムアウトしました。もう一度お試しください',
|
|
1189
|
+
loginFailed: 'ログイン失敗',
|
|
1190
|
+
loginFailedDesc: 'ログイン中にエラーが発生しました。もう一度お試しください',
|
|
1191
|
+
},
|
|
1192
|
+
ko: {
|
|
1193
|
+
loginToPlay: '로그인하여 플레이',
|
|
1194
|
+
loginWithPlayKit: 'PlayKit으로 안전하게 로그인',
|
|
1195
|
+
loginInNewWindow: '열린 창에서 로그인을 완료해 주세요',
|
|
1196
|
+
cancel: '취소',
|
|
1197
|
+
close: '닫기',
|
|
1198
|
+
loginDenied: '로그인 거부됨',
|
|
1199
|
+
loginDeniedDesc: '인증 요청을 거부하셨습니다',
|
|
1200
|
+
sessionExpired: '세션 만료',
|
|
1201
|
+
sessionExpiredDesc: '로그인 세션이 만료되었습니다. 다시 시도해 주세요',
|
|
1202
|
+
loginFailed: '로그인 실패',
|
|
1203
|
+
loginFailedDesc: '로그인 중 오류가 발생했습니다. 다시 시도해 주세요',
|
|
1204
|
+
},
|
|
1205
|
+
};
|
|
1104
1206
|
class DeviceAuthFlowManager extends EventEmitter {
|
|
1105
1207
|
constructor(baseURL, gameId) {
|
|
1106
1208
|
super();
|
|
1107
1209
|
this.pollInterval = 5000; // Default 5 seconds
|
|
1108
1210
|
this.pollTimeoutId = null;
|
|
1109
1211
|
this.aborted = false;
|
|
1212
|
+
this.currentLanguage = 'en';
|
|
1213
|
+
this.currentModal = null;
|
|
1110
1214
|
this.baseURL = baseURL;
|
|
1111
1215
|
this.gameId = gameId;
|
|
1112
1216
|
}
|
|
@@ -1137,6 +1241,303 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1137
1241
|
const base64 = btoa(String.fromCharCode(...buffer));
|
|
1138
1242
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
1139
1243
|
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Detect browser language and return matching translation key
|
|
1246
|
+
* @private
|
|
1247
|
+
*/
|
|
1248
|
+
detectLanguage() {
|
|
1249
|
+
if (typeof navigator === 'undefined')
|
|
1250
|
+
return 'en';
|
|
1251
|
+
const lang = navigator.language || navigator.userLanguage || 'en';
|
|
1252
|
+
// Check exact match first (e.g., 'zh-TW')
|
|
1253
|
+
if (translations$1[lang])
|
|
1254
|
+
return lang;
|
|
1255
|
+
// Check base language (e.g., 'zh' from 'zh-CN')
|
|
1256
|
+
const baseLang = lang.split('-')[0];
|
|
1257
|
+
if (translations$1[baseLang])
|
|
1258
|
+
return baseLang;
|
|
1259
|
+
return 'en';
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Get translation for a key
|
|
1263
|
+
* @private
|
|
1264
|
+
*/
|
|
1265
|
+
t(key) {
|
|
1266
|
+
var _a;
|
|
1267
|
+
return ((_a = translations$1[this.currentLanguage]) === null || _a === void 0 ? void 0 : _a[key]) || translations$1.en[key];
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Show login modal and return promises for user interaction
|
|
1271
|
+
* @private
|
|
1272
|
+
*/
|
|
1273
|
+
showLoginModal(gameInfo) {
|
|
1274
|
+
let resolveClicked;
|
|
1275
|
+
let resolveCancelled;
|
|
1276
|
+
const clicked = new Promise((resolve) => { resolveClicked = resolve; });
|
|
1277
|
+
const cancelled = new Promise((resolve) => { resolveCancelled = resolve; });
|
|
1278
|
+
// Create modal overlay
|
|
1279
|
+
const overlay = document.createElement('div');
|
|
1280
|
+
overlay.id = 'playkit-login-modal';
|
|
1281
|
+
overlay.style.cssText = `
|
|
1282
|
+
position: fixed;
|
|
1283
|
+
top: 0;
|
|
1284
|
+
left: 0;
|
|
1285
|
+
right: 0;
|
|
1286
|
+
bottom: 0;
|
|
1287
|
+
background: rgba(0, 0, 0, 0.48);
|
|
1288
|
+
backdrop-filter: blur(8px);
|
|
1289
|
+
-webkit-backdrop-filter: blur(8px);
|
|
1290
|
+
display: flex;
|
|
1291
|
+
align-items: center;
|
|
1292
|
+
justify-content: center;
|
|
1293
|
+
z-index: 999999;
|
|
1294
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
1295
|
+
`;
|
|
1296
|
+
// Create modal card
|
|
1297
|
+
const card = document.createElement('div');
|
|
1298
|
+
card.style.cssText = `
|
|
1299
|
+
background: rgba(255, 255, 255, 0.95);
|
|
1300
|
+
backdrop-filter: blur(20px);
|
|
1301
|
+
-webkit-backdrop-filter: blur(20px);
|
|
1302
|
+
border-radius: 16px;
|
|
1303
|
+
padding: 32px;
|
|
1304
|
+
max-width: 400px;
|
|
1305
|
+
width: 90%;
|
|
1306
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
1307
|
+
text-align: center;
|
|
1308
|
+
`;
|
|
1309
|
+
// Game icon
|
|
1310
|
+
const iconContainer = document.createElement('div');
|
|
1311
|
+
iconContainer.style.cssText = `
|
|
1312
|
+
width: 56px;
|
|
1313
|
+
height: 56px;
|
|
1314
|
+
margin: 0 auto 16px;
|
|
1315
|
+
border-radius: 12px;
|
|
1316
|
+
overflow: hidden;
|
|
1317
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
1318
|
+
`;
|
|
1319
|
+
if (gameInfo.icon) {
|
|
1320
|
+
const icon = document.createElement('img');
|
|
1321
|
+
icon.src = gameInfo.icon;
|
|
1322
|
+
icon.alt = gameInfo.name;
|
|
1323
|
+
icon.style.cssText = 'width: 100%; height: 100%; object-fit: cover;';
|
|
1324
|
+
iconContainer.appendChild(icon);
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
// Default game icon
|
|
1328
|
+
iconContainer.style.cssText += `
|
|
1329
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1330
|
+
display: flex;
|
|
1331
|
+
align-items: center;
|
|
1332
|
+
justify-content: center;
|
|
1333
|
+
color: white;
|
|
1334
|
+
font-size: 24px;
|
|
1335
|
+
`;
|
|
1336
|
+
iconContainer.textContent = '🎮';
|
|
1337
|
+
}
|
|
1338
|
+
card.appendChild(iconContainer);
|
|
1339
|
+
// Game name
|
|
1340
|
+
const gameName = document.createElement('h2');
|
|
1341
|
+
gameName.textContent = gameInfo.name;
|
|
1342
|
+
gameName.style.cssText = `
|
|
1343
|
+
margin: 0 0 8px;
|
|
1344
|
+
font-size: 20px;
|
|
1345
|
+
font-weight: 600;
|
|
1346
|
+
color: #1a1a1a;
|
|
1347
|
+
`;
|
|
1348
|
+
card.appendChild(gameName);
|
|
1349
|
+
// Subtitle
|
|
1350
|
+
const subtitle = document.createElement('p');
|
|
1351
|
+
subtitle.id = 'playkit-modal-subtitle';
|
|
1352
|
+
subtitle.textContent = this.t('loginWithPlayKit');
|
|
1353
|
+
subtitle.style.cssText = `
|
|
1354
|
+
margin: 0 0 24px;
|
|
1355
|
+
font-size: 14px;
|
|
1356
|
+
color: #666;
|
|
1357
|
+
`;
|
|
1358
|
+
card.appendChild(subtitle);
|
|
1359
|
+
// Loading spinner (hidden initially)
|
|
1360
|
+
const spinner = document.createElement('div');
|
|
1361
|
+
spinner.id = 'playkit-modal-spinner';
|
|
1362
|
+
spinner.style.cssText = `
|
|
1363
|
+
display: none;
|
|
1364
|
+
width: 32px;
|
|
1365
|
+
height: 32px;
|
|
1366
|
+
margin: 0 auto 16px;
|
|
1367
|
+
border: 3px solid #e5e7eb;
|
|
1368
|
+
border-top-color: #3b82f6;
|
|
1369
|
+
border-radius: 50%;
|
|
1370
|
+
animation: playkit-spin 1s linear infinite;
|
|
1371
|
+
`;
|
|
1372
|
+
card.appendChild(spinner);
|
|
1373
|
+
// Add keyframes for spinner
|
|
1374
|
+
const style = document.createElement('style');
|
|
1375
|
+
style.textContent = `
|
|
1376
|
+
@keyframes playkit-spin {
|
|
1377
|
+
to { transform: rotate(360deg); }
|
|
1378
|
+
}
|
|
1379
|
+
`;
|
|
1380
|
+
document.head.appendChild(style);
|
|
1381
|
+
// Login button
|
|
1382
|
+
const loginBtn = document.createElement('button');
|
|
1383
|
+
loginBtn.id = 'playkit-modal-login-btn';
|
|
1384
|
+
loginBtn.textContent = this.t('loginToPlay');
|
|
1385
|
+
loginBtn.style.cssText = `
|
|
1386
|
+
width: 100%;
|
|
1387
|
+
padding: 12px 24px;
|
|
1388
|
+
font-size: 16px;
|
|
1389
|
+
font-weight: 500;
|
|
1390
|
+
color: white;
|
|
1391
|
+
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
|
1392
|
+
border: none;
|
|
1393
|
+
border-radius: 8px;
|
|
1394
|
+
cursor: pointer;
|
|
1395
|
+
transition: all 0.2s ease;
|
|
1396
|
+
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3);
|
|
1397
|
+
`;
|
|
1398
|
+
loginBtn.onmouseenter = () => {
|
|
1399
|
+
loginBtn.style.boxShadow = '0 6px 8px -2px rgba(59, 130, 246, 0.4)';
|
|
1400
|
+
loginBtn.style.transform = 'translateY(-1px)';
|
|
1401
|
+
};
|
|
1402
|
+
loginBtn.onmouseleave = () => {
|
|
1403
|
+
loginBtn.style.boxShadow = '0 4px 6px -1px rgba(59, 130, 246, 0.3)';
|
|
1404
|
+
loginBtn.style.transform = 'translateY(0)';
|
|
1405
|
+
};
|
|
1406
|
+
loginBtn.onmousedown = () => {
|
|
1407
|
+
loginBtn.style.transform = 'scale(0.98)';
|
|
1408
|
+
};
|
|
1409
|
+
loginBtn.onmouseup = () => {
|
|
1410
|
+
loginBtn.style.transform = 'translateY(-1px)';
|
|
1411
|
+
};
|
|
1412
|
+
loginBtn.onclick = () => {
|
|
1413
|
+
// Switch to waiting state
|
|
1414
|
+
loginBtn.style.display = 'none';
|
|
1415
|
+
spinner.style.display = 'block';
|
|
1416
|
+
subtitle.textContent = this.t('loginInNewWindow');
|
|
1417
|
+
cancelBtn.style.display = 'block';
|
|
1418
|
+
resolveClicked();
|
|
1419
|
+
};
|
|
1420
|
+
card.appendChild(loginBtn);
|
|
1421
|
+
// Cancel button (hidden initially)
|
|
1422
|
+
const cancelBtn = document.createElement('button');
|
|
1423
|
+
cancelBtn.id = 'playkit-modal-cancel-btn';
|
|
1424
|
+
cancelBtn.textContent = this.t('cancel');
|
|
1425
|
+
cancelBtn.style.cssText = `
|
|
1426
|
+
display: none;
|
|
1427
|
+
width: 100%;
|
|
1428
|
+
margin-top: 12px;
|
|
1429
|
+
padding: 12px 24px;
|
|
1430
|
+
font-size: 16px;
|
|
1431
|
+
font-weight: 500;
|
|
1432
|
+
color: #666;
|
|
1433
|
+
background: #f3f4f6;
|
|
1434
|
+
border: none;
|
|
1435
|
+
border-radius: 8px;
|
|
1436
|
+
cursor: pointer;
|
|
1437
|
+
transition: all 0.2s ease;
|
|
1438
|
+
`;
|
|
1439
|
+
cancelBtn.onmouseenter = () => {
|
|
1440
|
+
cancelBtn.style.background = '#e5e7eb';
|
|
1441
|
+
};
|
|
1442
|
+
cancelBtn.onmouseleave = () => {
|
|
1443
|
+
cancelBtn.style.background = '#f3f4f6';
|
|
1444
|
+
};
|
|
1445
|
+
cancelBtn.onclick = () => {
|
|
1446
|
+
this.closeModal();
|
|
1447
|
+
resolveCancelled();
|
|
1448
|
+
};
|
|
1449
|
+
card.appendChild(cancelBtn);
|
|
1450
|
+
overlay.appendChild(card);
|
|
1451
|
+
document.body.appendChild(overlay);
|
|
1452
|
+
this.currentModal = overlay;
|
|
1453
|
+
return { clicked, cancelled };
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Close the login modal
|
|
1457
|
+
* @private
|
|
1458
|
+
*/
|
|
1459
|
+
closeModal() {
|
|
1460
|
+
if (this.currentModal) {
|
|
1461
|
+
this.currentModal.remove();
|
|
1462
|
+
this.currentModal = null;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Show error state in the modal
|
|
1467
|
+
* @private
|
|
1468
|
+
*/
|
|
1469
|
+
showModalError(type, onClose) {
|
|
1470
|
+
var _a, _b;
|
|
1471
|
+
if (!this.currentModal)
|
|
1472
|
+
return;
|
|
1473
|
+
const subtitle = this.currentModal.querySelector('#playkit-modal-subtitle');
|
|
1474
|
+
const spinner = this.currentModal.querySelector('#playkit-modal-spinner');
|
|
1475
|
+
const cancelBtn = this.currentModal.querySelector('#playkit-modal-cancel-btn');
|
|
1476
|
+
const card = this.currentModal.querySelector('div > div');
|
|
1477
|
+
if (!subtitle || !card)
|
|
1478
|
+
return;
|
|
1479
|
+
// Get error messages based on type
|
|
1480
|
+
let titleKey;
|
|
1481
|
+
let descKey;
|
|
1482
|
+
let iconColor;
|
|
1483
|
+
switch (type) {
|
|
1484
|
+
case 'denied':
|
|
1485
|
+
titleKey = 'loginDenied';
|
|
1486
|
+
descKey = 'loginDeniedDesc';
|
|
1487
|
+
iconColor = '#ef4444'; // red-500
|
|
1488
|
+
break;
|
|
1489
|
+
case 'expired':
|
|
1490
|
+
titleKey = 'sessionExpired';
|
|
1491
|
+
descKey = 'sessionExpiredDesc';
|
|
1492
|
+
iconColor = '#f59e0b'; // amber-500
|
|
1493
|
+
break;
|
|
1494
|
+
default:
|
|
1495
|
+
titleKey = 'loginFailed';
|
|
1496
|
+
descKey = 'loginFailedDesc';
|
|
1497
|
+
iconColor = '#ef4444'; // red-500
|
|
1498
|
+
}
|
|
1499
|
+
// Hide spinner
|
|
1500
|
+
if (spinner)
|
|
1501
|
+
spinner.style.display = 'none';
|
|
1502
|
+
// Create error icon
|
|
1503
|
+
const errorIcon = document.createElement('div');
|
|
1504
|
+
errorIcon.style.cssText = `
|
|
1505
|
+
width: 48px;
|
|
1506
|
+
height: 48px;
|
|
1507
|
+
margin: 0 auto 16px;
|
|
1508
|
+
border-radius: 50%;
|
|
1509
|
+
background: ${iconColor}15;
|
|
1510
|
+
display: flex;
|
|
1511
|
+
align-items: center;
|
|
1512
|
+
justify-content: center;
|
|
1513
|
+
font-size: 24px;
|
|
1514
|
+
`;
|
|
1515
|
+
errorIcon.textContent = type === 'expired' ? '⏱' : '✕';
|
|
1516
|
+
// Create error title
|
|
1517
|
+
const errorTitle = document.createElement('h3');
|
|
1518
|
+
errorTitle.textContent = this.t(titleKey);
|
|
1519
|
+
errorTitle.style.cssText = `
|
|
1520
|
+
margin: 0 0 8px;
|
|
1521
|
+
font-size: 18px;
|
|
1522
|
+
font-weight: 600;
|
|
1523
|
+
color: ${iconColor};
|
|
1524
|
+
`;
|
|
1525
|
+
// Update subtitle with error description
|
|
1526
|
+
subtitle.textContent = this.t(descKey);
|
|
1527
|
+
subtitle.style.color = '#666';
|
|
1528
|
+
// Insert error elements before subtitle
|
|
1529
|
+
(_a = subtitle.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(errorIcon, subtitle);
|
|
1530
|
+
(_b = subtitle.parentNode) === null || _b === void 0 ? void 0 : _b.insertBefore(errorTitle, subtitle);
|
|
1531
|
+
// Update cancel button to close button
|
|
1532
|
+
if (cancelBtn) {
|
|
1533
|
+
cancelBtn.textContent = this.t('close');
|
|
1534
|
+
cancelBtn.style.display = 'block';
|
|
1535
|
+
cancelBtn.onclick = () => {
|
|
1536
|
+
this.closeModal();
|
|
1537
|
+
onClose();
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1140
1541
|
/**
|
|
1141
1542
|
* Default browser opener (works in browser environment)
|
|
1142
1543
|
* @private
|
|
@@ -1158,6 +1559,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1158
1559
|
*/
|
|
1159
1560
|
async startFlow(options = {}) {
|
|
1160
1561
|
this.aborted = false;
|
|
1562
|
+
this.currentLanguage = this.detectLanguage();
|
|
1161
1563
|
const scope = options.scope || 'player:play';
|
|
1162
1564
|
const openBrowser = options.openBrowser || this.defaultOpenBrowser.bind(this);
|
|
1163
1565
|
// Generate PKCE parameters
|
|
@@ -1181,7 +1583,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1181
1583
|
throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
|
|
1182
1584
|
}
|
|
1183
1585
|
const initData = await initResponse.json();
|
|
1184
|
-
const { session_id, auth_url, poll_interval, expires_in } = initData;
|
|
1586
|
+
const { session_id, auth_url, poll_interval, expires_in, game } = initData;
|
|
1185
1587
|
// Update poll interval from server
|
|
1186
1588
|
if (poll_interval) {
|
|
1187
1589
|
this.pollInterval = poll_interval * 1000;
|
|
@@ -1191,18 +1593,48 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1191
1593
|
options.onAuthUrl(auth_url);
|
|
1192
1594
|
}
|
|
1193
1595
|
this.emit('auth_url', auth_url);
|
|
1194
|
-
// Step 2:
|
|
1195
|
-
|
|
1596
|
+
// Step 2: Show login modal and open browser for user authorization
|
|
1597
|
+
// In browser environment, show modal first to ensure popup is opened from user click context
|
|
1598
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
1599
|
+
const gameInfo = game || {
|
|
1600
|
+
id: this.gameId,
|
|
1601
|
+
name: 'Game',
|
|
1602
|
+
icon: null,
|
|
1603
|
+
description: null,
|
|
1604
|
+
};
|
|
1605
|
+
const modalResult = this.showLoginModal(gameInfo);
|
|
1606
|
+
// Wait for user to click login or cancel
|
|
1607
|
+
try {
|
|
1608
|
+
await Promise.race([
|
|
1609
|
+
modalResult.clicked,
|
|
1610
|
+
modalResult.cancelled.then(() => {
|
|
1611
|
+
throw new PlayKitError('User cancelled', 'CANCELLED');
|
|
1612
|
+
}),
|
|
1613
|
+
]);
|
|
1614
|
+
}
|
|
1615
|
+
catch (err) {
|
|
1616
|
+
this.closeModal();
|
|
1617
|
+
throw err;
|
|
1618
|
+
}
|
|
1619
|
+
// User clicked login - open browser (in user click context, won't be blocked)
|
|
1620
|
+
await openBrowser(auth_url);
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
// Non-browser environment - open directly
|
|
1624
|
+
await openBrowser(auth_url);
|
|
1625
|
+
}
|
|
1196
1626
|
// Step 3: Poll for authorization
|
|
1197
1627
|
const expiresAt = Date.now() + (expires_in || 600) * 1000;
|
|
1198
1628
|
return new Promise((resolve, reject) => {
|
|
1199
1629
|
const poll = async () => {
|
|
1200
1630
|
if (this.aborted) {
|
|
1631
|
+
this.closeModal();
|
|
1201
1632
|
reject(new PlayKitError('Device auth flow was cancelled', 'CANCELLED'));
|
|
1202
1633
|
return;
|
|
1203
1634
|
}
|
|
1204
1635
|
// Check if session expired
|
|
1205
1636
|
if (Date.now() >= expiresAt) {
|
|
1637
|
+
this.showModalError('expired', () => { });
|
|
1206
1638
|
reject(new PlayKitError('Device auth session expired', 'EXPIRED'));
|
|
1207
1639
|
return;
|
|
1208
1640
|
}
|
|
@@ -1223,7 +1655,8 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1223
1655
|
this.pollTimeoutId = setTimeout(poll, this.pollInterval);
|
|
1224
1656
|
}
|
|
1225
1657
|
else if (pollData.status === 'authorized') {
|
|
1226
|
-
// Success!
|
|
1658
|
+
// Success! Close modal and return tokens
|
|
1659
|
+
this.closeModal();
|
|
1227
1660
|
if (options.onPollStatus) {
|
|
1228
1661
|
options.onPollStatus('authorized');
|
|
1229
1662
|
}
|
|
@@ -1252,6 +1685,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1252
1685
|
this.pollTimeoutId = setTimeout(poll, this.pollInterval);
|
|
1253
1686
|
}
|
|
1254
1687
|
else if (error === 'access_denied') {
|
|
1688
|
+
this.showModalError('denied', () => { });
|
|
1255
1689
|
if (options.onPollStatus) {
|
|
1256
1690
|
options.onPollStatus('denied');
|
|
1257
1691
|
}
|
|
@@ -1259,6 +1693,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1259
1693
|
reject(new PlayKitError(pollData.error_description || 'User denied authorization', 'ACCESS_DENIED'));
|
|
1260
1694
|
}
|
|
1261
1695
|
else if (error === 'expired_token') {
|
|
1696
|
+
this.showModalError('expired', () => { });
|
|
1262
1697
|
if (options.onPollStatus) {
|
|
1263
1698
|
options.onPollStatus('expired');
|
|
1264
1699
|
}
|
|
@@ -1266,6 +1701,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1266
1701
|
reject(new PlayKitError(pollData.error_description || 'Session expired', 'EXPIRED'));
|
|
1267
1702
|
}
|
|
1268
1703
|
else {
|
|
1704
|
+
this.showModalError('failed', () => { });
|
|
1269
1705
|
reject(new PlayKitError(pollData.error_description || 'Device auth failed', error || 'POLL_FAILED', pollResponse.status));
|
|
1270
1706
|
}
|
|
1271
1707
|
}
|
|
@@ -1285,6 +1721,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1285
1721
|
*/
|
|
1286
1722
|
cancel() {
|
|
1287
1723
|
this.aborted = true;
|
|
1724
|
+
this.closeModal();
|
|
1288
1725
|
if (this.pollTimeoutId) {
|
|
1289
1726
|
clearTimeout(this.pollTimeoutId);
|
|
1290
1727
|
this.pollTimeoutId = null;
|
|
@@ -1295,6 +1732,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1295
1732
|
* Clean up resources
|
|
1296
1733
|
*/
|
|
1297
1734
|
destroy() {
|
|
1735
|
+
this.closeModal();
|
|
1298
1736
|
this.cancel();
|
|
1299
1737
|
this.removeAllListeners();
|
|
1300
1738
|
}
|
|
@@ -1572,6 +2010,7 @@ const translations = {
|
|
|
1572
2010
|
credits: '', // No longer using "Spark", display USD amount directly
|
|
1573
2011
|
rechargeButton: 'Recharge Now',
|
|
1574
2012
|
cancelButton: 'Cancel',
|
|
2013
|
+
dailyRefreshMessage: 'Your daily free {amount} credits have arrived!',
|
|
1575
2014
|
},
|
|
1576
2015
|
zh: {
|
|
1577
2016
|
title: '余额不足',
|
|
@@ -1580,6 +2019,7 @@ const translations = {
|
|
|
1580
2019
|
credits: '', // 不再使用"Spark",直接显示美元金额
|
|
1581
2020
|
rechargeButton: '立即充值',
|
|
1582
2021
|
cancelButton: '取消',
|
|
2022
|
+
dailyRefreshMessage: '你的每日免费积分 {amount} 已到账!',
|
|
1583
2023
|
},
|
|
1584
2024
|
'zh-TW': {
|
|
1585
2025
|
title: '餘額不足',
|
|
@@ -1588,6 +2028,7 @@ const translations = {
|
|
|
1588
2028
|
credits: '', // 不再使用"Spark",直接顯示美元金額
|
|
1589
2029
|
rechargeButton: '立即充值',
|
|
1590
2030
|
cancelButton: '取消',
|
|
2031
|
+
dailyRefreshMessage: '你的每日免費積分 {amount} 已到帳!',
|
|
1591
2032
|
},
|
|
1592
2033
|
ja: {
|
|
1593
2034
|
title: '残高不足',
|
|
@@ -1596,6 +2037,7 @@ const translations = {
|
|
|
1596
2037
|
credits: '', // "Spark"は使用しません、USD金額を直接表示
|
|
1597
2038
|
rechargeButton: '今すぐチャージ',
|
|
1598
2039
|
cancelButton: 'キャンセル',
|
|
2040
|
+
dailyRefreshMessage: '本日の無料クレジット {amount} が届きました!',
|
|
1599
2041
|
},
|
|
1600
2042
|
ko: {
|
|
1601
2043
|
title: '잔액 부족',
|
|
@@ -1604,18 +2046,22 @@ const translations = {
|
|
|
1604
2046
|
credits: '', // "Spark" 사용 안 함, USD 금액 직접 표시
|
|
1605
2047
|
rechargeButton: '지금 충전',
|
|
1606
2048
|
cancelButton: '취소',
|
|
2049
|
+
dailyRefreshMessage: '오늘의 무료 크레딧 {amount}이 도착했습니다!',
|
|
1607
2050
|
},
|
|
1608
2051
|
};
|
|
1609
2052
|
/**
|
|
1610
2053
|
* RechargeManager handles the recharge modal UI and recharge window opening
|
|
1611
2054
|
*/
|
|
1612
2055
|
class RechargeManager extends EventEmitter {
|
|
1613
|
-
constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge') {
|
|
2056
|
+
constructor(playerToken, rechargePortalUrl = 'https://playkit.ai/recharge', gameId) {
|
|
1614
2057
|
super();
|
|
1615
2058
|
this.modalContainer = null;
|
|
1616
2059
|
this.styleElement = null;
|
|
2060
|
+
this.toastElement = null;
|
|
2061
|
+
this.toastTimeout = null;
|
|
1617
2062
|
this.playerToken = playerToken;
|
|
1618
2063
|
this.rechargePortalUrl = rechargePortalUrl;
|
|
2064
|
+
this.gameId = gameId;
|
|
1619
2065
|
this.language = this.detectLanguage();
|
|
1620
2066
|
}
|
|
1621
2067
|
/**
|
|
@@ -1644,10 +2090,15 @@ class RechargeManager extends EventEmitter {
|
|
|
1644
2090
|
return translations[this.language][key];
|
|
1645
2091
|
}
|
|
1646
2092
|
/**
|
|
1647
|
-
* Build recharge URL with player token
|
|
2093
|
+
* Build recharge URL with player token and gameId
|
|
1648
2094
|
*/
|
|
1649
2095
|
buildRechargeUrl() {
|
|
1650
|
-
|
|
2096
|
+
let url = `${this.rechargePortalUrl}?playerToken=${encodeURIComponent(this.playerToken)}`;
|
|
2097
|
+
// Add gameId to URL so recharge page can fetch correct owner's wallet
|
|
2098
|
+
if (this.gameId) {
|
|
2099
|
+
url += `&gameId=${encodeURIComponent(this.gameId)}`;
|
|
2100
|
+
}
|
|
2101
|
+
return url;
|
|
1651
2102
|
}
|
|
1652
2103
|
/**
|
|
1653
2104
|
* Open recharge window in a new tab
|
|
@@ -1863,6 +2314,86 @@ class RechargeManager extends EventEmitter {
|
|
|
1863
2314
|
flex-direction: column;
|
|
1864
2315
|
}
|
|
1865
2316
|
}
|
|
2317
|
+
|
|
2318
|
+
/* Daily Refresh Toast Styles */
|
|
2319
|
+
.playkit-daily-refresh-toast {
|
|
2320
|
+
position: fixed;
|
|
2321
|
+
top: 20px;
|
|
2322
|
+
right: 20px;
|
|
2323
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
2324
|
+
border-radius: 12px;
|
|
2325
|
+
box-shadow: 0 10px 40px rgba(16, 185, 129, 0.3), 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
2326
|
+
padding: 16px 20px;
|
|
2327
|
+
min-width: 240px;
|
|
2328
|
+
max-width: 320px;
|
|
2329
|
+
z-index: 999998;
|
|
2330
|
+
animation: playkit-toast-slideIn 0.3s ease-out;
|
|
2331
|
+
display: flex;
|
|
2332
|
+
align-items: flex-start;
|
|
2333
|
+
gap: 12px;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
.playkit-daily-refresh-toast.hiding {
|
|
2337
|
+
animation: playkit-toast-fadeOut 0.3s ease-out forwards;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
@keyframes playkit-toast-slideIn {
|
|
2341
|
+
from {
|
|
2342
|
+
transform: translateX(100%);
|
|
2343
|
+
opacity: 0;
|
|
2344
|
+
}
|
|
2345
|
+
to {
|
|
2346
|
+
transform: translateX(0);
|
|
2347
|
+
opacity: 1;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
@keyframes playkit-toast-fadeOut {
|
|
2352
|
+
from {
|
|
2353
|
+
transform: translateX(0);
|
|
2354
|
+
opacity: 1;
|
|
2355
|
+
}
|
|
2356
|
+
to {
|
|
2357
|
+
transform: translateX(100%);
|
|
2358
|
+
opacity: 0;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
.playkit-toast-icon {
|
|
2363
|
+
width: 24px;
|
|
2364
|
+
height: 24px;
|
|
2365
|
+
background: rgba(255, 255, 255, 0.2);
|
|
2366
|
+
border-radius: 50%;
|
|
2367
|
+
display: flex;
|
|
2368
|
+
align-items: center;
|
|
2369
|
+
justify-content: center;
|
|
2370
|
+
flex-shrink: 0;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
.playkit-toast-icon svg {
|
|
2374
|
+
width: 14px;
|
|
2375
|
+
height: 14px;
|
|
2376
|
+
color: #ffffff;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
.playkit-toast-message {
|
|
2380
|
+
flex: 1;
|
|
2381
|
+
font-size: 14px;
|
|
2382
|
+
font-weight: 500;
|
|
2383
|
+
color: #ffffff;
|
|
2384
|
+
line-height: 1.4;
|
|
2385
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
@media (max-width: 480px) {
|
|
2389
|
+
.playkit-daily-refresh-toast {
|
|
2390
|
+
top: 10px;
|
|
2391
|
+
right: 10px;
|
|
2392
|
+
left: 10px;
|
|
2393
|
+
min-width: auto;
|
|
2394
|
+
max-width: none;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
1866
2397
|
`;
|
|
1867
2398
|
document.head.appendChild(this.styleElement);
|
|
1868
2399
|
}
|
|
@@ -1924,6 +2455,55 @@ class RechargeManager extends EventEmitter {
|
|
|
1924
2455
|
updateToken(newToken) {
|
|
1925
2456
|
this.playerToken = newToken;
|
|
1926
2457
|
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Show daily refresh toast notification
|
|
2460
|
+
*/
|
|
2461
|
+
showDailyRefreshToast(result) {
|
|
2462
|
+
// Don't show if already showing
|
|
2463
|
+
if (this.toastElement) {
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
this.injectStyles();
|
|
2467
|
+
// Create toast element
|
|
2468
|
+
this.toastElement = document.createElement('div');
|
|
2469
|
+
this.toastElement.className = 'playkit-daily-refresh-toast';
|
|
2470
|
+
// Icon
|
|
2471
|
+
const icon = document.createElement('div');
|
|
2472
|
+
icon.className = 'playkit-toast-icon';
|
|
2473
|
+
icon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
|
|
2474
|
+
this.toastElement.appendChild(icon);
|
|
2475
|
+
// Message
|
|
2476
|
+
const message = document.createElement('div');
|
|
2477
|
+
message.className = 'playkit-toast-message';
|
|
2478
|
+
message.textContent = this.t('dailyRefreshMessage').replace('{amount}', String(result.amountAdded));
|
|
2479
|
+
this.toastElement.appendChild(message);
|
|
2480
|
+
document.body.appendChild(this.toastElement);
|
|
2481
|
+
// Auto-hide after 3 seconds
|
|
2482
|
+
this.toastTimeout = setTimeout(() => {
|
|
2483
|
+
this.hideToast();
|
|
2484
|
+
}, 3000);
|
|
2485
|
+
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Hide the toast with fade-out animation
|
|
2488
|
+
*/
|
|
2489
|
+
hideToast() {
|
|
2490
|
+
if (!this.toastElement) {
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
// Add hiding class for animation
|
|
2494
|
+
this.toastElement.classList.add('hiding');
|
|
2495
|
+
// Remove after animation completes
|
|
2496
|
+
setTimeout(() => {
|
|
2497
|
+
if (this.toastElement) {
|
|
2498
|
+
this.toastElement.remove();
|
|
2499
|
+
this.toastElement = null;
|
|
2500
|
+
}
|
|
2501
|
+
}, 300);
|
|
2502
|
+
if (this.toastTimeout) {
|
|
2503
|
+
clearTimeout(this.toastTimeout);
|
|
2504
|
+
this.toastTimeout = null;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
1927
2507
|
/**
|
|
1928
2508
|
* Destroy the modal and clean up
|
|
1929
2509
|
*/
|
|
@@ -1932,6 +2512,14 @@ class RechargeManager extends EventEmitter {
|
|
|
1932
2512
|
this.modalContainer.remove();
|
|
1933
2513
|
this.modalContainer = null;
|
|
1934
2514
|
}
|
|
2515
|
+
if (this.toastElement) {
|
|
2516
|
+
this.toastElement.remove();
|
|
2517
|
+
this.toastElement = null;
|
|
2518
|
+
}
|
|
2519
|
+
if (this.toastTimeout) {
|
|
2520
|
+
clearTimeout(this.toastTimeout);
|
|
2521
|
+
this.toastTimeout = null;
|
|
2522
|
+
}
|
|
1935
2523
|
if (this.styleElement) {
|
|
1936
2524
|
this.styleElement.remove();
|
|
1937
2525
|
this.styleElement = null;
|
|
@@ -1947,18 +2535,20 @@ const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
|
|
|
1947
2535
|
const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
|
|
1948
2536
|
class PlayerClient extends EventEmitter {
|
|
1949
2537
|
constructor(authManager, config, rechargeConfig = {}) {
|
|
1950
|
-
var _a, _b, _c;
|
|
2538
|
+
var _a, _b, _c, _d;
|
|
1951
2539
|
super();
|
|
1952
2540
|
this.playerInfo = null;
|
|
1953
2541
|
this.rechargeManager = null;
|
|
1954
2542
|
this.balanceCheckInterval = null;
|
|
1955
2543
|
this.authManager = authManager;
|
|
1956
2544
|
this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
|
|
2545
|
+
this.gameId = config.gameId;
|
|
1957
2546
|
this.rechargeConfig = {
|
|
1958
2547
|
autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
|
|
1959
2548
|
balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
|
|
1960
2549
|
checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
|
|
1961
2550
|
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
|
|
2551
|
+
showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
|
|
1962
2552
|
};
|
|
1963
2553
|
}
|
|
1964
2554
|
/**
|
|
@@ -1979,11 +2569,16 @@ class PlayerClient extends EventEmitter {
|
|
|
1979
2569
|
};
|
|
1980
2570
|
}
|
|
1981
2571
|
try {
|
|
2572
|
+
// Build headers with X-Game-Id to support Global Developer Token
|
|
2573
|
+
const headers = {
|
|
2574
|
+
Authorization: `Bearer ${token}`,
|
|
2575
|
+
};
|
|
2576
|
+
if (this.gameId) {
|
|
2577
|
+
headers['X-Game-Id'] = this.gameId;
|
|
2578
|
+
}
|
|
1982
2579
|
const response = await fetch(`${this.baseURL}${PLAYER_INFO_ENDPOINT}`, {
|
|
1983
2580
|
method: 'GET',
|
|
1984
|
-
headers
|
|
1985
|
-
Authorization: `Bearer ${token}`,
|
|
1986
|
-
},
|
|
2581
|
+
headers,
|
|
1987
2582
|
});
|
|
1988
2583
|
if (!response.ok) {
|
|
1989
2584
|
const error = await response.json().catch(() => ({ message: 'Failed to get player info' }));
|
|
@@ -2010,6 +2605,13 @@ class PlayerClient extends EventEmitter {
|
|
|
2010
2605
|
// Emit daily refresh event if credits were refreshed
|
|
2011
2606
|
if ((_b = data.dailyRefresh) === null || _b === void 0 ? void 0 : _b.refreshed) {
|
|
2012
2607
|
this.emit('daily_credits_refreshed', data.dailyRefresh);
|
|
2608
|
+
// Show toast notification if enabled
|
|
2609
|
+
if (this.rechargeConfig.showDailyRefreshToast !== false) {
|
|
2610
|
+
this.initializeRechargeManager();
|
|
2611
|
+
if (this.rechargeManager) {
|
|
2612
|
+
this.rechargeManager.showDailyRefreshToast(data.dailyRefresh);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2013
2615
|
}
|
|
2014
2616
|
return this.playerInfo;
|
|
2015
2617
|
}
|
|
@@ -2100,7 +2702,7 @@ class PlayerClient extends EventEmitter {
|
|
|
2100
2702
|
initializeRechargeManager() {
|
|
2101
2703
|
const token = this.authManager.getToken();
|
|
2102
2704
|
if (token && !this.rechargeManager) {
|
|
2103
|
-
this.rechargeManager = new RechargeManager(token, this.rechargeConfig.rechargePortalUrl);
|
|
2705
|
+
this.rechargeManager = new RechargeManager(token, this.rechargeConfig.rechargePortalUrl, this.gameId);
|
|
2104
2706
|
// Forward recharge events
|
|
2105
2707
|
this.rechargeManager.on('recharge_opened', () => this.emit('recharge_opened'));
|
|
2106
2708
|
this.rechargeManager.on('recharge_modal_shown', () => this.emit('recharge_modal_shown'));
|
|
@@ -2585,11 +3187,18 @@ class ImageProvider {
|
|
|
2585
3187
|
const endpoint = `/ai/${this.config.gameId}/v2/image`;
|
|
2586
3188
|
const requestBody = {
|
|
2587
3189
|
model,
|
|
2588
|
-
prompt: imageConfig.prompt,
|
|
2589
3190
|
n: imageConfig.n || 1,
|
|
2590
3191
|
size: imageConfig.size || '1024x1024',
|
|
2591
3192
|
seed: imageConfig.seed || null,
|
|
2592
3193
|
};
|
|
3194
|
+
// Add prompt if provided
|
|
3195
|
+
if (imageConfig.prompt) {
|
|
3196
|
+
requestBody.prompt = imageConfig.prompt;
|
|
3197
|
+
}
|
|
3198
|
+
// Add input images for img2img
|
|
3199
|
+
if (imageConfig.images && imageConfig.images.length > 0) {
|
|
3200
|
+
requestBody.images = imageConfig.images.map(img => img.data);
|
|
3201
|
+
}
|
|
2593
3202
|
// Add optional quality and style if provided (for DALL-E models)
|
|
2594
3203
|
if (imageConfig.quality) {
|
|
2595
3204
|
requestBody.quality = imageConfig.quality;
|
|
@@ -2597,6 +3206,10 @@ class ImageProvider {
|
|
|
2597
3206
|
if (imageConfig.style) {
|
|
2598
3207
|
requestBody.style = imageConfig.style;
|
|
2599
3208
|
}
|
|
3209
|
+
// Add transparent option for background removal
|
|
3210
|
+
if (imageConfig.transparent) {
|
|
3211
|
+
requestBody.transparent = true;
|
|
3212
|
+
}
|
|
2600
3213
|
try {
|
|
2601
3214
|
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
2602
3215
|
method: 'POST',
|
|
@@ -3200,12 +3813,14 @@ class ChatClient {
|
|
|
3200
3813
|
* Implementation of GeneratedImage interface
|
|
3201
3814
|
*/
|
|
3202
3815
|
class GeneratedImageImpl {
|
|
3203
|
-
constructor(base64, originalPrompt, revisedPrompt, size) {
|
|
3816
|
+
constructor(base64, originalPrompt, revisedPrompt, size, originalBase64, transparentSuccess) {
|
|
3204
3817
|
this.base64 = base64;
|
|
3205
3818
|
this.originalPrompt = originalPrompt;
|
|
3206
3819
|
this.revisedPrompt = revisedPrompt;
|
|
3207
3820
|
this.generatedAt = Date.now();
|
|
3208
3821
|
this.size = size;
|
|
3822
|
+
this.originalBase64 = originalBase64;
|
|
3823
|
+
this.transparentSuccess = transparentSuccess;
|
|
3209
3824
|
}
|
|
3210
3825
|
toDataURL() {
|
|
3211
3826
|
return `data:image/png;base64,${this.base64}`;
|
|
@@ -3234,7 +3849,7 @@ class ImageClient {
|
|
|
3234
3849
|
if (!imageData || !imageData.b64_json) {
|
|
3235
3850
|
throw new Error('No image data in response');
|
|
3236
3851
|
}
|
|
3237
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size);
|
|
3852
|
+
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
3238
3853
|
}
|
|
3239
3854
|
/**
|
|
3240
3855
|
* Generate multiple images
|
|
@@ -3246,7 +3861,7 @@ class ImageClient {
|
|
|
3246
3861
|
if (!imageData.b64_json) {
|
|
3247
3862
|
throw new Error('No image data in response');
|
|
3248
3863
|
}
|
|
3249
|
-
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size);
|
|
3864
|
+
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size, imageData.b64_json_original, imageData.transparent_success);
|
|
3250
3865
|
});
|
|
3251
3866
|
}
|
|
3252
3867
|
/**
|
|
@@ -3255,6 +3870,45 @@ class ImageClient {
|
|
|
3255
3870
|
async generate(prompt, size) {
|
|
3256
3871
|
return this.generateImage({ prompt, size });
|
|
3257
3872
|
}
|
|
3873
|
+
/**
|
|
3874
|
+
* Image-to-image generation
|
|
3875
|
+
* @param images - Input images (base64 encoded)
|
|
3876
|
+
* @param prompt - Optional prompt to guide the generation
|
|
3877
|
+
* @param options - Additional generation options
|
|
3878
|
+
*/
|
|
3879
|
+
async img2img(images, prompt, options) {
|
|
3880
|
+
return this.generateImage(Object.assign({ images,
|
|
3881
|
+
prompt }, options));
|
|
3882
|
+
}
|
|
3883
|
+
/**
|
|
3884
|
+
* Convert a data URL to ImageInput format
|
|
3885
|
+
* @param dataUrl - Data URL (e.g., 'data:image/png;base64,...')
|
|
3886
|
+
*/
|
|
3887
|
+
static dataUrlToImageInput(dataUrl) {
|
|
3888
|
+
const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
|
|
3889
|
+
if (!match) {
|
|
3890
|
+
throw new Error('Invalid data URL format');
|
|
3891
|
+
}
|
|
3892
|
+
return {
|
|
3893
|
+
mediaType: match[1],
|
|
3894
|
+
data: match[2],
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Convert a File/Blob to ImageInput format (browser only)
|
|
3899
|
+
* @param file - File or Blob object
|
|
3900
|
+
*/
|
|
3901
|
+
static async fileToImageInput(file) {
|
|
3902
|
+
return new Promise((resolve, reject) => {
|
|
3903
|
+
const reader = new FileReader();
|
|
3904
|
+
reader.onload = () => {
|
|
3905
|
+
const result = reader.result;
|
|
3906
|
+
resolve(ImageClient.dataUrlToImageInput(result));
|
|
3907
|
+
};
|
|
3908
|
+
reader.onerror = () => reject(new Error('Failed to read file'));
|
|
3909
|
+
reader.readAsDataURL(file);
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3258
3912
|
}
|
|
3259
3913
|
|
|
3260
3914
|
/**
|
|
@@ -4579,6 +5233,7 @@ class PlayKitSDK extends EventEmitter {
|
|
|
4579
5233
|
this.playerClient.on('balance_low', (credits) => this.emit('balance_low', credits));
|
|
4580
5234
|
this.playerClient.on('balance_updated', (credits) => this.emit('balance_updated', credits));
|
|
4581
5235
|
this.playerClient.on('player_info_updated', (info) => this.emit('player_info_updated', info));
|
|
5236
|
+
this.playerClient.on('daily_credits_refreshed', (result) => this.emit('daily_credits_refreshed', result));
|
|
4582
5237
|
}
|
|
4583
5238
|
/**
|
|
4584
5239
|
* Initialize the SDK
|
|
@@ -4859,6 +5514,8 @@ exports.SchemaLibrary = SchemaLibrary;
|
|
|
4859
5514
|
exports.StreamParser = StreamParser;
|
|
4860
5515
|
exports.TokenStorage = TokenStorage;
|
|
4861
5516
|
exports.TranscriptionClient = TranscriptionClient;
|
|
5517
|
+
exports.createMultimodalMessage = createMultimodalMessage;
|
|
5518
|
+
exports.createTextMessage = createTextMessage;
|
|
4862
5519
|
exports.default = PlayKitSDK;
|
|
4863
5520
|
exports.defaultContextManager = defaultContextManager;
|
|
4864
5521
|
exports.defaultSchemaLibrary = defaultSchemaLibrary;
|