playkit-sdk 1.2.5 → 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 +564 -8
- package/dist/playkit-sdk.cjs.js.map +1 -1
- package/dist/playkit-sdk.d.ts +52 -1
- package/dist/playkit-sdk.esm.js +564 -8
- package/dist/playkit-sdk.esm.js.map +1 -1
- package/dist/playkit-sdk.umd.js +564 -8
- 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
|
*/
|
|
@@ -264,7 +264,7 @@ class TokenStorage {
|
|
|
264
264
|
* Will be removed in v2.0
|
|
265
265
|
*/
|
|
266
266
|
// i18n translations
|
|
267
|
-
const translations$
|
|
267
|
+
const translations$2 = {
|
|
268
268
|
en: {
|
|
269
269
|
signIn: 'Sign In / Register',
|
|
270
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!',
|
|
@@ -409,7 +409,7 @@ class AuthFlowManager extends EventEmitter {
|
|
|
409
409
|
* Get translated text
|
|
410
410
|
*/
|
|
411
411
|
t(key) {
|
|
412
|
-
return translations$
|
|
412
|
+
return translations$2[this.currentLanguage][key];
|
|
413
413
|
}
|
|
414
414
|
/**
|
|
415
415
|
* Start the authentication flow
|
|
@@ -1136,12 +1136,81 @@ class AuthFlowManager extends EventEmitter {
|
|
|
1136
1136
|
* 3. Poll /api/device-auth/poll until authorized or error
|
|
1137
1137
|
* 4. Return access token and refresh token
|
|
1138
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
|
+
};
|
|
1139
1206
|
class DeviceAuthFlowManager extends EventEmitter {
|
|
1140
1207
|
constructor(baseURL, gameId) {
|
|
1141
1208
|
super();
|
|
1142
1209
|
this.pollInterval = 5000; // Default 5 seconds
|
|
1143
1210
|
this.pollTimeoutId = null;
|
|
1144
1211
|
this.aborted = false;
|
|
1212
|
+
this.currentLanguage = 'en';
|
|
1213
|
+
this.currentModal = null;
|
|
1145
1214
|
this.baseURL = baseURL;
|
|
1146
1215
|
this.gameId = gameId;
|
|
1147
1216
|
}
|
|
@@ -1172,6 +1241,303 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1172
1241
|
const base64 = btoa(String.fromCharCode(...buffer));
|
|
1173
1242
|
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
|
|
1174
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
|
+
}
|
|
1175
1541
|
/**
|
|
1176
1542
|
* Default browser opener (works in browser environment)
|
|
1177
1543
|
* @private
|
|
@@ -1193,6 +1559,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1193
1559
|
*/
|
|
1194
1560
|
async startFlow(options = {}) {
|
|
1195
1561
|
this.aborted = false;
|
|
1562
|
+
this.currentLanguage = this.detectLanguage();
|
|
1196
1563
|
const scope = options.scope || 'player:play';
|
|
1197
1564
|
const openBrowser = options.openBrowser || this.defaultOpenBrowser.bind(this);
|
|
1198
1565
|
// Generate PKCE parameters
|
|
@@ -1216,7 +1583,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1216
1583
|
throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
|
|
1217
1584
|
}
|
|
1218
1585
|
const initData = await initResponse.json();
|
|
1219
|
-
const { session_id, auth_url, poll_interval, expires_in } = initData;
|
|
1586
|
+
const { session_id, auth_url, poll_interval, expires_in, game } = initData;
|
|
1220
1587
|
// Update poll interval from server
|
|
1221
1588
|
if (poll_interval) {
|
|
1222
1589
|
this.pollInterval = poll_interval * 1000;
|
|
@@ -1226,18 +1593,48 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1226
1593
|
options.onAuthUrl(auth_url);
|
|
1227
1594
|
}
|
|
1228
1595
|
this.emit('auth_url', auth_url);
|
|
1229
|
-
// Step 2:
|
|
1230
|
-
|
|
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
|
+
}
|
|
1231
1626
|
// Step 3: Poll for authorization
|
|
1232
1627
|
const expiresAt = Date.now() + (expires_in || 600) * 1000;
|
|
1233
1628
|
return new Promise((resolve, reject) => {
|
|
1234
1629
|
const poll = async () => {
|
|
1235
1630
|
if (this.aborted) {
|
|
1631
|
+
this.closeModal();
|
|
1236
1632
|
reject(new PlayKitError('Device auth flow was cancelled', 'CANCELLED'));
|
|
1237
1633
|
return;
|
|
1238
1634
|
}
|
|
1239
1635
|
// Check if session expired
|
|
1240
1636
|
if (Date.now() >= expiresAt) {
|
|
1637
|
+
this.showModalError('expired', () => { });
|
|
1241
1638
|
reject(new PlayKitError('Device auth session expired', 'EXPIRED'));
|
|
1242
1639
|
return;
|
|
1243
1640
|
}
|
|
@@ -1258,7 +1655,8 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1258
1655
|
this.pollTimeoutId = setTimeout(poll, this.pollInterval);
|
|
1259
1656
|
}
|
|
1260
1657
|
else if (pollData.status === 'authorized') {
|
|
1261
|
-
// Success!
|
|
1658
|
+
// Success! Close modal and return tokens
|
|
1659
|
+
this.closeModal();
|
|
1262
1660
|
if (options.onPollStatus) {
|
|
1263
1661
|
options.onPollStatus('authorized');
|
|
1264
1662
|
}
|
|
@@ -1287,6 +1685,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1287
1685
|
this.pollTimeoutId = setTimeout(poll, this.pollInterval);
|
|
1288
1686
|
}
|
|
1289
1687
|
else if (error === 'access_denied') {
|
|
1688
|
+
this.showModalError('denied', () => { });
|
|
1290
1689
|
if (options.onPollStatus) {
|
|
1291
1690
|
options.onPollStatus('denied');
|
|
1292
1691
|
}
|
|
@@ -1294,6 +1693,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1294
1693
|
reject(new PlayKitError(pollData.error_description || 'User denied authorization', 'ACCESS_DENIED'));
|
|
1295
1694
|
}
|
|
1296
1695
|
else if (error === 'expired_token') {
|
|
1696
|
+
this.showModalError('expired', () => { });
|
|
1297
1697
|
if (options.onPollStatus) {
|
|
1298
1698
|
options.onPollStatus('expired');
|
|
1299
1699
|
}
|
|
@@ -1301,6 +1701,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1301
1701
|
reject(new PlayKitError(pollData.error_description || 'Session expired', 'EXPIRED'));
|
|
1302
1702
|
}
|
|
1303
1703
|
else {
|
|
1704
|
+
this.showModalError('failed', () => { });
|
|
1304
1705
|
reject(new PlayKitError(pollData.error_description || 'Device auth failed', error || 'POLL_FAILED', pollResponse.status));
|
|
1305
1706
|
}
|
|
1306
1707
|
}
|
|
@@ -1320,6 +1721,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1320
1721
|
*/
|
|
1321
1722
|
cancel() {
|
|
1322
1723
|
this.aborted = true;
|
|
1724
|
+
this.closeModal();
|
|
1323
1725
|
if (this.pollTimeoutId) {
|
|
1324
1726
|
clearTimeout(this.pollTimeoutId);
|
|
1325
1727
|
this.pollTimeoutId = null;
|
|
@@ -1330,6 +1732,7 @@ class DeviceAuthFlowManager extends EventEmitter {
|
|
|
1330
1732
|
* Clean up resources
|
|
1331
1733
|
*/
|
|
1332
1734
|
destroy() {
|
|
1735
|
+
this.closeModal();
|
|
1333
1736
|
this.cancel();
|
|
1334
1737
|
this.removeAllListeners();
|
|
1335
1738
|
}
|
|
@@ -1607,6 +2010,7 @@ const translations = {
|
|
|
1607
2010
|
credits: '', // No longer using "Spark", display USD amount directly
|
|
1608
2011
|
rechargeButton: 'Recharge Now',
|
|
1609
2012
|
cancelButton: 'Cancel',
|
|
2013
|
+
dailyRefreshMessage: 'Your daily free {amount} credits have arrived!',
|
|
1610
2014
|
},
|
|
1611
2015
|
zh: {
|
|
1612
2016
|
title: '余额不足',
|
|
@@ -1615,6 +2019,7 @@ const translations = {
|
|
|
1615
2019
|
credits: '', // 不再使用"Spark",直接显示美元金额
|
|
1616
2020
|
rechargeButton: '立即充值',
|
|
1617
2021
|
cancelButton: '取消',
|
|
2022
|
+
dailyRefreshMessage: '你的每日免费积分 {amount} 已到账!',
|
|
1618
2023
|
},
|
|
1619
2024
|
'zh-TW': {
|
|
1620
2025
|
title: '餘額不足',
|
|
@@ -1623,6 +2028,7 @@ const translations = {
|
|
|
1623
2028
|
credits: '', // 不再使用"Spark",直接顯示美元金額
|
|
1624
2029
|
rechargeButton: '立即充值',
|
|
1625
2030
|
cancelButton: '取消',
|
|
2031
|
+
dailyRefreshMessage: '你的每日免費積分 {amount} 已到帳!',
|
|
1626
2032
|
},
|
|
1627
2033
|
ja: {
|
|
1628
2034
|
title: '残高不足',
|
|
@@ -1631,6 +2037,7 @@ const translations = {
|
|
|
1631
2037
|
credits: '', // "Spark"は使用しません、USD金額を直接表示
|
|
1632
2038
|
rechargeButton: '今すぐチャージ',
|
|
1633
2039
|
cancelButton: 'キャンセル',
|
|
2040
|
+
dailyRefreshMessage: '本日の無料クレジット {amount} が届きました!',
|
|
1634
2041
|
},
|
|
1635
2042
|
ko: {
|
|
1636
2043
|
title: '잔액 부족',
|
|
@@ -1639,6 +2046,7 @@ const translations = {
|
|
|
1639
2046
|
credits: '', // "Spark" 사용 안 함, USD 금액 직접 표시
|
|
1640
2047
|
rechargeButton: '지금 충전',
|
|
1641
2048
|
cancelButton: '취소',
|
|
2049
|
+
dailyRefreshMessage: '오늘의 무료 크레딧 {amount}이 도착했습니다!',
|
|
1642
2050
|
},
|
|
1643
2051
|
};
|
|
1644
2052
|
/**
|
|
@@ -1649,6 +2057,8 @@ class RechargeManager extends EventEmitter {
|
|
|
1649
2057
|
super();
|
|
1650
2058
|
this.modalContainer = null;
|
|
1651
2059
|
this.styleElement = null;
|
|
2060
|
+
this.toastElement = null;
|
|
2061
|
+
this.toastTimeout = null;
|
|
1652
2062
|
this.playerToken = playerToken;
|
|
1653
2063
|
this.rechargePortalUrl = rechargePortalUrl;
|
|
1654
2064
|
this.gameId = gameId;
|
|
@@ -1904,6 +2314,86 @@ class RechargeManager extends EventEmitter {
|
|
|
1904
2314
|
flex-direction: column;
|
|
1905
2315
|
}
|
|
1906
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
|
+
}
|
|
1907
2397
|
`;
|
|
1908
2398
|
document.head.appendChild(this.styleElement);
|
|
1909
2399
|
}
|
|
@@ -1965,6 +2455,55 @@ class RechargeManager extends EventEmitter {
|
|
|
1965
2455
|
updateToken(newToken) {
|
|
1966
2456
|
this.playerToken = newToken;
|
|
1967
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
|
+
}
|
|
1968
2507
|
/**
|
|
1969
2508
|
* Destroy the modal and clean up
|
|
1970
2509
|
*/
|
|
@@ -1973,6 +2512,14 @@ class RechargeManager extends EventEmitter {
|
|
|
1973
2512
|
this.modalContainer.remove();
|
|
1974
2513
|
this.modalContainer = null;
|
|
1975
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
|
+
}
|
|
1976
2523
|
if (this.styleElement) {
|
|
1977
2524
|
this.styleElement.remove();
|
|
1978
2525
|
this.styleElement = null;
|
|
@@ -1988,7 +2535,7 @@ const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
|
|
|
1988
2535
|
const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
|
|
1989
2536
|
class PlayerClient extends EventEmitter {
|
|
1990
2537
|
constructor(authManager, config, rechargeConfig = {}) {
|
|
1991
|
-
var _a, _b, _c;
|
|
2538
|
+
var _a, _b, _c, _d;
|
|
1992
2539
|
super();
|
|
1993
2540
|
this.playerInfo = null;
|
|
1994
2541
|
this.rechargeManager = null;
|
|
@@ -2001,6 +2548,7 @@ class PlayerClient extends EventEmitter {
|
|
|
2001
2548
|
balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
|
|
2002
2549
|
checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
|
|
2003
2550
|
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
|
|
2551
|
+
showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
|
|
2004
2552
|
};
|
|
2005
2553
|
}
|
|
2006
2554
|
/**
|
|
@@ -2057,6 +2605,13 @@ class PlayerClient extends EventEmitter {
|
|
|
2057
2605
|
// Emit daily refresh event if credits were refreshed
|
|
2058
2606
|
if ((_b = data.dailyRefresh) === null || _b === void 0 ? void 0 : _b.refreshed) {
|
|
2059
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
|
+
}
|
|
2060
2615
|
}
|
|
2061
2616
|
return this.playerInfo;
|
|
2062
2617
|
}
|
|
@@ -4678,6 +5233,7 @@ class PlayKitSDK extends EventEmitter {
|
|
|
4678
5233
|
this.playerClient.on('balance_low', (credits) => this.emit('balance_low', credits));
|
|
4679
5234
|
this.playerClient.on('balance_updated', (credits) => this.emit('balance_updated', credits));
|
|
4680
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));
|
|
4681
5237
|
}
|
|
4682
5238
|
/**
|
|
4683
5239
|
* Initialize the SDK
|