playkit-sdk 1.2.5 → 1.2.7

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * playkit-sdk v1.2.5
2
+ * playkit-sdk v1.2.7
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$1 = {
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$1[this.currentLanguage][key];
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,241 @@ 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 - dark bg-black/80 style
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.8);
1288
+ display: flex;
1289
+ align-items: center;
1290
+ justify-content: center;
1291
+ z-index: 999999;
1292
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
1293
+ `;
1294
+ // Create modal card - square corners, shadow-xl style
1295
+ const card = document.createElement('div');
1296
+ card.style.cssText = `
1297
+ background: #fff;
1298
+ border: 1px solid rgba(0, 0, 0, 0.1);
1299
+ padding: 24px;
1300
+ max-width: 320px;
1301
+ width: 90%;
1302
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
1303
+ text-align: center;
1304
+ `;
1305
+ // Subtitle / status text
1306
+ const subtitle = document.createElement('p');
1307
+ subtitle.id = 'playkit-modal-subtitle';
1308
+ subtitle.textContent = this.t('loginWithPlayKit');
1309
+ subtitle.style.cssText = `
1310
+ margin: 0 0 20px;
1311
+ font-size: 14px;
1312
+ color: #666;
1313
+ `;
1314
+ card.appendChild(subtitle);
1315
+ // Loading spinner (hidden initially)
1316
+ const spinner = document.createElement('div');
1317
+ spinner.id = 'playkit-modal-spinner';
1318
+ spinner.style.cssText = `
1319
+ display: none;
1320
+ width: 24px;
1321
+ height: 24px;
1322
+ margin: 0 auto 16px;
1323
+ border: 2px solid #e5e7eb;
1324
+ border-top-color: #171717;
1325
+ border-radius: 50%;
1326
+ animation: playkit-spin 1s linear infinite;
1327
+ `;
1328
+ card.appendChild(spinner);
1329
+ // Add keyframes for spinner
1330
+ const style = document.createElement('style');
1331
+ style.textContent = `
1332
+ @keyframes playkit-spin {
1333
+ to { transform: rotate(360deg); }
1334
+ }
1335
+ `;
1336
+ document.head.appendChild(style);
1337
+ // Login button - square corners, simple dark style
1338
+ const loginBtn = document.createElement('button');
1339
+ loginBtn.id = 'playkit-modal-login-btn';
1340
+ loginBtn.textContent = this.t('loginToPlay');
1341
+ loginBtn.style.cssText = `
1342
+ width: 100%;
1343
+ padding: 10px 16px;
1344
+ font-size: 14px;
1345
+ font-weight: 500;
1346
+ color: white;
1347
+ background: #171717;
1348
+ border: none;
1349
+ cursor: pointer;
1350
+ transition: background 0.2s ease;
1351
+ `;
1352
+ loginBtn.onmouseenter = () => {
1353
+ loginBtn.style.background = '#404040';
1354
+ };
1355
+ loginBtn.onmouseleave = () => {
1356
+ loginBtn.style.background = '#171717';
1357
+ };
1358
+ loginBtn.onmousedown = () => {
1359
+ loginBtn.style.background = '#0a0a0a';
1360
+ };
1361
+ loginBtn.onmouseup = () => {
1362
+ loginBtn.style.background = '#404040';
1363
+ };
1364
+ loginBtn.onclick = () => {
1365
+ // Switch to waiting state
1366
+ loginBtn.style.display = 'none';
1367
+ spinner.style.display = 'block';
1368
+ subtitle.textContent = this.t('loginInNewWindow');
1369
+ cancelBtn.style.display = 'block';
1370
+ resolveClicked();
1371
+ };
1372
+ card.appendChild(loginBtn);
1373
+ // Cancel button (hidden initially) - outline style
1374
+ const cancelBtn = document.createElement('button');
1375
+ cancelBtn.id = 'playkit-modal-cancel-btn';
1376
+ cancelBtn.textContent = this.t('cancel');
1377
+ cancelBtn.style.cssText = `
1378
+ display: none;
1379
+ width: 100%;
1380
+ margin-top: 8px;
1381
+ padding: 10px 16px;
1382
+ font-size: 14px;
1383
+ font-weight: 500;
1384
+ color: #666;
1385
+ background: transparent;
1386
+ border: 1px solid #e5e7eb;
1387
+ cursor: pointer;
1388
+ transition: all 0.2s ease;
1389
+ `;
1390
+ cancelBtn.onmouseenter = () => {
1391
+ cancelBtn.style.background = '#f5f5f5';
1392
+ cancelBtn.style.borderColor = '#d4d4d4';
1393
+ };
1394
+ cancelBtn.onmouseleave = () => {
1395
+ cancelBtn.style.background = 'transparent';
1396
+ cancelBtn.style.borderColor = '#e5e7eb';
1397
+ };
1398
+ cancelBtn.onclick = () => {
1399
+ this.closeModal();
1400
+ resolveCancelled();
1401
+ };
1402
+ card.appendChild(cancelBtn);
1403
+ overlay.appendChild(card);
1404
+ document.body.appendChild(overlay);
1405
+ this.currentModal = overlay;
1406
+ return { clicked, cancelled };
1407
+ }
1408
+ /**
1409
+ * Close the login modal
1410
+ * @private
1411
+ */
1412
+ closeModal() {
1413
+ if (this.currentModal) {
1414
+ this.currentModal.remove();
1415
+ this.currentModal = null;
1416
+ }
1417
+ }
1418
+ /**
1419
+ * Show error state in the modal
1420
+ * @private
1421
+ */
1422
+ showModalError(type, onClose) {
1423
+ var _a;
1424
+ if (!this.currentModal)
1425
+ return;
1426
+ const subtitle = this.currentModal.querySelector('#playkit-modal-subtitle');
1427
+ const spinner = this.currentModal.querySelector('#playkit-modal-spinner');
1428
+ const cancelBtn = this.currentModal.querySelector('#playkit-modal-cancel-btn');
1429
+ const card = this.currentModal.querySelector('div > div');
1430
+ if (!subtitle || !card)
1431
+ return;
1432
+ // Get error messages based on type
1433
+ let titleKey;
1434
+ let descKey;
1435
+ let iconColor;
1436
+ switch (type) {
1437
+ case 'denied':
1438
+ titleKey = 'loginDenied';
1439
+ descKey = 'loginDeniedDesc';
1440
+ iconColor = '#ef4444'; // red-500
1441
+ break;
1442
+ case 'expired':
1443
+ titleKey = 'sessionExpired';
1444
+ descKey = 'sessionExpiredDesc';
1445
+ iconColor = '#f59e0b'; // amber-500
1446
+ break;
1447
+ default:
1448
+ titleKey = 'loginFailed';
1449
+ descKey = 'loginFailedDesc';
1450
+ iconColor = '#ef4444'; // red-500
1451
+ }
1452
+ // Hide spinner
1453
+ if (spinner)
1454
+ spinner.style.display = 'none';
1455
+ // Create error title
1456
+ const errorTitle = document.createElement('h3');
1457
+ errorTitle.textContent = this.t(titleKey);
1458
+ errorTitle.style.cssText = `
1459
+ margin: 0 0 8px;
1460
+ font-size: 14px;
1461
+ font-weight: 600;
1462
+ color: ${iconColor};
1463
+ `;
1464
+ // Update subtitle with error description
1465
+ subtitle.textContent = this.t(descKey);
1466
+ subtitle.style.color = '#666';
1467
+ // Insert error title before subtitle
1468
+ (_a = subtitle.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(errorTitle, subtitle);
1469
+ // Update cancel button to close button
1470
+ if (cancelBtn) {
1471
+ cancelBtn.textContent = this.t('close');
1472
+ cancelBtn.style.display = 'block';
1473
+ cancelBtn.onclick = () => {
1474
+ this.closeModal();
1475
+ onClose();
1476
+ };
1477
+ }
1478
+ }
1175
1479
  /**
1176
1480
  * Default browser opener (works in browser environment)
1177
1481
  * @private
@@ -1193,6 +1497,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1193
1497
  */
1194
1498
  async startFlow(options = {}) {
1195
1499
  this.aborted = false;
1500
+ this.currentLanguage = this.detectLanguage();
1196
1501
  const scope = options.scope || 'player:play';
1197
1502
  const openBrowser = options.openBrowser || this.defaultOpenBrowser.bind(this);
1198
1503
  // Generate PKCE parameters
@@ -1216,7 +1521,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1216
1521
  throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
1217
1522
  }
1218
1523
  const initData = await initResponse.json();
1219
- const { session_id, auth_url, poll_interval, expires_in } = initData;
1524
+ const { session_id, auth_url, poll_interval, expires_in, game } = initData;
1220
1525
  // Update poll interval from server
1221
1526
  if (poll_interval) {
1222
1527
  this.pollInterval = poll_interval * 1000;
@@ -1226,18 +1531,48 @@ class DeviceAuthFlowManager extends EventEmitter {
1226
1531
  options.onAuthUrl(auth_url);
1227
1532
  }
1228
1533
  this.emit('auth_url', auth_url);
1229
- // Step 2: Open browser for user authorization
1230
- await openBrowser(auth_url);
1534
+ // Step 2: Show login modal and open browser for user authorization
1535
+ // In browser environment, show modal first to ensure popup is opened from user click context
1536
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
1537
+ const gameInfo = game || {
1538
+ id: this.gameId,
1539
+ name: 'Game',
1540
+ icon: null,
1541
+ description: null,
1542
+ };
1543
+ const modalResult = this.showLoginModal(gameInfo);
1544
+ // Wait for user to click login or cancel
1545
+ try {
1546
+ await Promise.race([
1547
+ modalResult.clicked,
1548
+ modalResult.cancelled.then(() => {
1549
+ throw new PlayKitError('User cancelled', 'CANCELLED');
1550
+ }),
1551
+ ]);
1552
+ }
1553
+ catch (err) {
1554
+ this.closeModal();
1555
+ throw err;
1556
+ }
1557
+ // User clicked login - open browser (in user click context, won't be blocked)
1558
+ await openBrowser(auth_url);
1559
+ }
1560
+ else {
1561
+ // Non-browser environment - open directly
1562
+ await openBrowser(auth_url);
1563
+ }
1231
1564
  // Step 3: Poll for authorization
1232
1565
  const expiresAt = Date.now() + (expires_in || 600) * 1000;
1233
1566
  return new Promise((resolve, reject) => {
1234
1567
  const poll = async () => {
1235
1568
  if (this.aborted) {
1569
+ this.closeModal();
1236
1570
  reject(new PlayKitError('Device auth flow was cancelled', 'CANCELLED'));
1237
1571
  return;
1238
1572
  }
1239
1573
  // Check if session expired
1240
1574
  if (Date.now() >= expiresAt) {
1575
+ this.showModalError('expired', () => { });
1241
1576
  reject(new PlayKitError('Device auth session expired', 'EXPIRED'));
1242
1577
  return;
1243
1578
  }
@@ -1258,7 +1593,8 @@ class DeviceAuthFlowManager extends EventEmitter {
1258
1593
  this.pollTimeoutId = setTimeout(poll, this.pollInterval);
1259
1594
  }
1260
1595
  else if (pollData.status === 'authorized') {
1261
- // Success!
1596
+ // Success! Close modal and return tokens
1597
+ this.closeModal();
1262
1598
  if (options.onPollStatus) {
1263
1599
  options.onPollStatus('authorized');
1264
1600
  }
@@ -1287,6 +1623,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1287
1623
  this.pollTimeoutId = setTimeout(poll, this.pollInterval);
1288
1624
  }
1289
1625
  else if (error === 'access_denied') {
1626
+ this.showModalError('denied', () => { });
1290
1627
  if (options.onPollStatus) {
1291
1628
  options.onPollStatus('denied');
1292
1629
  }
@@ -1294,6 +1631,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1294
1631
  reject(new PlayKitError(pollData.error_description || 'User denied authorization', 'ACCESS_DENIED'));
1295
1632
  }
1296
1633
  else if (error === 'expired_token') {
1634
+ this.showModalError('expired', () => { });
1297
1635
  if (options.onPollStatus) {
1298
1636
  options.onPollStatus('expired');
1299
1637
  }
@@ -1301,6 +1639,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1301
1639
  reject(new PlayKitError(pollData.error_description || 'Session expired', 'EXPIRED'));
1302
1640
  }
1303
1641
  else {
1642
+ this.showModalError('failed', () => { });
1304
1643
  reject(new PlayKitError(pollData.error_description || 'Device auth failed', error || 'POLL_FAILED', pollResponse.status));
1305
1644
  }
1306
1645
  }
@@ -1320,6 +1659,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1320
1659
  */
1321
1660
  cancel() {
1322
1661
  this.aborted = true;
1662
+ this.closeModal();
1323
1663
  if (this.pollTimeoutId) {
1324
1664
  clearTimeout(this.pollTimeoutId);
1325
1665
  this.pollTimeoutId = null;
@@ -1330,6 +1670,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1330
1670
  * Clean up resources
1331
1671
  */
1332
1672
  destroy() {
1673
+ this.closeModal();
1333
1674
  this.cancel();
1334
1675
  this.removeAllListeners();
1335
1676
  }
@@ -1607,6 +1948,7 @@ const translations = {
1607
1948
  credits: '', // No longer using "Spark", display USD amount directly
1608
1949
  rechargeButton: 'Recharge Now',
1609
1950
  cancelButton: 'Cancel',
1951
+ dailyRefreshMessage: 'Your daily free {amount} credits have arrived!',
1610
1952
  },
1611
1953
  zh: {
1612
1954
  title: '余额不足',
@@ -1615,6 +1957,7 @@ const translations = {
1615
1957
  credits: '', // 不再使用"Spark",直接显示美元金额
1616
1958
  rechargeButton: '立即充值',
1617
1959
  cancelButton: '取消',
1960
+ dailyRefreshMessage: '你的每日免费积分 {amount} 已到账!',
1618
1961
  },
1619
1962
  'zh-TW': {
1620
1963
  title: '餘額不足',
@@ -1623,6 +1966,7 @@ const translations = {
1623
1966
  credits: '', // 不再使用"Spark",直接顯示美元金額
1624
1967
  rechargeButton: '立即充值',
1625
1968
  cancelButton: '取消',
1969
+ dailyRefreshMessage: '你的每日免費積分 {amount} 已到帳!',
1626
1970
  },
1627
1971
  ja: {
1628
1972
  title: '残高不足',
@@ -1631,6 +1975,7 @@ const translations = {
1631
1975
  credits: '', // "Spark"は使用しません、USD金額を直接表示
1632
1976
  rechargeButton: '今すぐチャージ',
1633
1977
  cancelButton: 'キャンセル',
1978
+ dailyRefreshMessage: '本日の無料クレジット {amount} が届きました!',
1634
1979
  },
1635
1980
  ko: {
1636
1981
  title: '잔액 부족',
@@ -1639,6 +1984,7 @@ const translations = {
1639
1984
  credits: '', // "Spark" 사용 안 함, USD 금액 직접 표시
1640
1985
  rechargeButton: '지금 충전',
1641
1986
  cancelButton: '취소',
1987
+ dailyRefreshMessage: '오늘의 무료 크레딧 {amount}이 도착했습니다!',
1642
1988
  },
1643
1989
  };
1644
1990
  /**
@@ -1649,6 +1995,8 @@ class RechargeManager extends EventEmitter {
1649
1995
  super();
1650
1996
  this.modalContainer = null;
1651
1997
  this.styleElement = null;
1998
+ this.toastElement = null;
1999
+ this.toastTimeout = null;
1652
2000
  this.playerToken = playerToken;
1653
2001
  this.rechargePortalUrl = rechargePortalUrl;
1654
2002
  this.gameId = gameId;
@@ -1904,6 +2252,86 @@ class RechargeManager extends EventEmitter {
1904
2252
  flex-direction: column;
1905
2253
  }
1906
2254
  }
2255
+
2256
+ /* Daily Refresh Toast Styles */
2257
+ .playkit-daily-refresh-toast {
2258
+ position: fixed;
2259
+ top: 20px;
2260
+ right: 20px;
2261
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
2262
+ border-radius: 12px;
2263
+ box-shadow: 0 10px 40px rgba(16, 185, 129, 0.3), 0 4px 12px rgba(0, 0, 0, 0.1);
2264
+ padding: 16px 20px;
2265
+ min-width: 240px;
2266
+ max-width: 320px;
2267
+ z-index: 999998;
2268
+ animation: playkit-toast-slideIn 0.3s ease-out;
2269
+ display: flex;
2270
+ align-items: flex-start;
2271
+ gap: 12px;
2272
+ }
2273
+
2274
+ .playkit-daily-refresh-toast.hiding {
2275
+ animation: playkit-toast-fadeOut 0.3s ease-out forwards;
2276
+ }
2277
+
2278
+ @keyframes playkit-toast-slideIn {
2279
+ from {
2280
+ transform: translateX(100%);
2281
+ opacity: 0;
2282
+ }
2283
+ to {
2284
+ transform: translateX(0);
2285
+ opacity: 1;
2286
+ }
2287
+ }
2288
+
2289
+ @keyframes playkit-toast-fadeOut {
2290
+ from {
2291
+ transform: translateX(0);
2292
+ opacity: 1;
2293
+ }
2294
+ to {
2295
+ transform: translateX(100%);
2296
+ opacity: 0;
2297
+ }
2298
+ }
2299
+
2300
+ .playkit-toast-icon {
2301
+ width: 24px;
2302
+ height: 24px;
2303
+ background: rgba(255, 255, 255, 0.2);
2304
+ border-radius: 50%;
2305
+ display: flex;
2306
+ align-items: center;
2307
+ justify-content: center;
2308
+ flex-shrink: 0;
2309
+ }
2310
+
2311
+ .playkit-toast-icon svg {
2312
+ width: 14px;
2313
+ height: 14px;
2314
+ color: #ffffff;
2315
+ }
2316
+
2317
+ .playkit-toast-message {
2318
+ flex: 1;
2319
+ font-size: 14px;
2320
+ font-weight: 500;
2321
+ color: #ffffff;
2322
+ line-height: 1.4;
2323
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
2324
+ }
2325
+
2326
+ @media (max-width: 480px) {
2327
+ .playkit-daily-refresh-toast {
2328
+ top: 10px;
2329
+ right: 10px;
2330
+ left: 10px;
2331
+ min-width: auto;
2332
+ max-width: none;
2333
+ }
2334
+ }
1907
2335
  `;
1908
2336
  document.head.appendChild(this.styleElement);
1909
2337
  }
@@ -1965,6 +2393,55 @@ class RechargeManager extends EventEmitter {
1965
2393
  updateToken(newToken) {
1966
2394
  this.playerToken = newToken;
1967
2395
  }
2396
+ /**
2397
+ * Show daily refresh toast notification
2398
+ */
2399
+ showDailyRefreshToast(result) {
2400
+ // Don't show if already showing
2401
+ if (this.toastElement) {
2402
+ return;
2403
+ }
2404
+ this.injectStyles();
2405
+ // Create toast element
2406
+ this.toastElement = document.createElement('div');
2407
+ this.toastElement.className = 'playkit-daily-refresh-toast';
2408
+ // Icon
2409
+ const icon = document.createElement('div');
2410
+ icon.className = 'playkit-toast-icon';
2411
+ 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>`;
2412
+ this.toastElement.appendChild(icon);
2413
+ // Message
2414
+ const message = document.createElement('div');
2415
+ message.className = 'playkit-toast-message';
2416
+ message.textContent = this.t('dailyRefreshMessage').replace('{amount}', String(result.amountAdded));
2417
+ this.toastElement.appendChild(message);
2418
+ document.body.appendChild(this.toastElement);
2419
+ // Auto-hide after 3 seconds
2420
+ this.toastTimeout = setTimeout(() => {
2421
+ this.hideToast();
2422
+ }, 3000);
2423
+ }
2424
+ /**
2425
+ * Hide the toast with fade-out animation
2426
+ */
2427
+ hideToast() {
2428
+ if (!this.toastElement) {
2429
+ return;
2430
+ }
2431
+ // Add hiding class for animation
2432
+ this.toastElement.classList.add('hiding');
2433
+ // Remove after animation completes
2434
+ setTimeout(() => {
2435
+ if (this.toastElement) {
2436
+ this.toastElement.remove();
2437
+ this.toastElement = null;
2438
+ }
2439
+ }, 300);
2440
+ if (this.toastTimeout) {
2441
+ clearTimeout(this.toastTimeout);
2442
+ this.toastTimeout = null;
2443
+ }
2444
+ }
1968
2445
  /**
1969
2446
  * Destroy the modal and clean up
1970
2447
  */
@@ -1973,6 +2450,14 @@ class RechargeManager extends EventEmitter {
1973
2450
  this.modalContainer.remove();
1974
2451
  this.modalContainer = null;
1975
2452
  }
2453
+ if (this.toastElement) {
2454
+ this.toastElement.remove();
2455
+ this.toastElement = null;
2456
+ }
2457
+ if (this.toastTimeout) {
2458
+ clearTimeout(this.toastTimeout);
2459
+ this.toastTimeout = null;
2460
+ }
1976
2461
  if (this.styleElement) {
1977
2462
  this.styleElement.remove();
1978
2463
  this.styleElement = null;
@@ -1988,7 +2473,7 @@ const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
1988
2473
  const SET_NICKNAME_ENDPOINT = '/api/external/set-game-player-nickname';
1989
2474
  class PlayerClient extends EventEmitter {
1990
2475
  constructor(authManager, config, rechargeConfig = {}) {
1991
- var _a, _b, _c;
2476
+ var _a, _b, _c, _d;
1992
2477
  super();
1993
2478
  this.playerInfo = null;
1994
2479
  this.rechargeManager = null;
@@ -2001,6 +2486,7 @@ class PlayerClient extends EventEmitter {
2001
2486
  balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
2002
2487
  checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
2003
2488
  rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.ai/recharge',
2489
+ showDailyRefreshToast: (_d = rechargeConfig.showDailyRefreshToast) !== null && _d !== void 0 ? _d : true,
2004
2490
  };
2005
2491
  }
2006
2492
  /**
@@ -2057,6 +2543,13 @@ class PlayerClient extends EventEmitter {
2057
2543
  // Emit daily refresh event if credits were refreshed
2058
2544
  if ((_b = data.dailyRefresh) === null || _b === void 0 ? void 0 : _b.refreshed) {
2059
2545
  this.emit('daily_credits_refreshed', data.dailyRefresh);
2546
+ // Show toast notification if enabled
2547
+ if (this.rechargeConfig.showDailyRefreshToast !== false) {
2548
+ this.initializeRechargeManager();
2549
+ if (this.rechargeManager) {
2550
+ this.rechargeManager.showDailyRefreshToast(data.dailyRefresh);
2551
+ }
2552
+ }
2060
2553
  }
2061
2554
  return this.playerInfo;
2062
2555
  }
@@ -4678,6 +5171,7 @@ class PlayKitSDK extends EventEmitter {
4678
5171
  this.playerClient.on('balance_low', (credits) => this.emit('balance_low', credits));
4679
5172
  this.playerClient.on('balance_updated', (credits) => this.emit('balance_updated', credits));
4680
5173
  this.playerClient.on('player_info_updated', (info) => this.emit('player_info_updated', info));
5174
+ this.playerClient.on('daily_credits_refreshed', (result) => this.emit('daily_credits_refreshed', result));
4681
5175
  }
4682
5176
  /**
4683
5177
  * Initialize the SDK