playkit-sdk 1.0.0-beta.2
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/LICENSE +86 -0
- package/README.md +244 -0
- package/dist/index.cjs.js +2800 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +817 -0
- package/dist/index.esm.js +2787 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.js +3151 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,2800 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* playkit-sdk v1.0.0-beta.2
|
|
3
|
+
* PlayKit SDK for JavaScript
|
|
4
|
+
* @license SEE LICENSE IN LICENSE
|
|
5
|
+
*/
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
9
|
+
|
|
10
|
+
var EventEmitter = require('eventemitter3');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Common types used across the SDK
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Error types that can be thrown by the SDK
|
|
17
|
+
*/
|
|
18
|
+
class PlayKitError extends Error {
|
|
19
|
+
constructor(message, code, statusCode) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.statusCode = statusCode;
|
|
23
|
+
this.name = 'PlayKitError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Token storage with encryption using Web Crypto API
|
|
29
|
+
* Stores tokens in localStorage with AES-128-GCM encryption
|
|
30
|
+
*/
|
|
31
|
+
const STORAGE_KEY_PREFIX = 'playkit_';
|
|
32
|
+
const SHARED_TOKEN_KEY = 'playkit_shared_token';
|
|
33
|
+
const ENCRYPTION_KEY_NAME = 'playkit_encryption_key';
|
|
34
|
+
class TokenStorage {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.encryptionKey = null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the encryption key
|
|
40
|
+
*/
|
|
41
|
+
async initialize() {
|
|
42
|
+
if (typeof window === 'undefined' || !window.crypto || !window.crypto.subtle) {
|
|
43
|
+
console.warn('Web Crypto API not available, encryption disabled');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Try to load existing key or generate new one
|
|
47
|
+
const storedKey = localStorage.getItem(ENCRYPTION_KEY_NAME);
|
|
48
|
+
if (storedKey) {
|
|
49
|
+
try {
|
|
50
|
+
const keyData = this.base64ToArrayBuffer(storedKey);
|
|
51
|
+
this.encryptionKey = await crypto.subtle.importKey('raw', keyData, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.warn('Failed to import encryption key, generating new one', error);
|
|
55
|
+
await this.generateNewKey();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
await this.generateNewKey();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate a new encryption key
|
|
64
|
+
*/
|
|
65
|
+
async generateNewKey() {
|
|
66
|
+
if (!crypto.subtle)
|
|
67
|
+
return;
|
|
68
|
+
this.encryptionKey = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 128 }, true, ['encrypt', 'decrypt']);
|
|
69
|
+
// Store the key
|
|
70
|
+
const exported = await crypto.subtle.exportKey('raw', this.encryptionKey);
|
|
71
|
+
localStorage.setItem(ENCRYPTION_KEY_NAME, this.arrayBufferToBase64(exported));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Encrypt data
|
|
75
|
+
*/
|
|
76
|
+
async encrypt(data) {
|
|
77
|
+
if (!this.encryptionKey || !crypto.subtle) {
|
|
78
|
+
// Fallback to plain storage if encryption unavailable
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
82
|
+
const encoded = new TextEncoder().encode(data);
|
|
83
|
+
const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, this.encryptionKey, encoded);
|
|
84
|
+
// Combine IV and encrypted data
|
|
85
|
+
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
|
86
|
+
combined.set(iv, 0);
|
|
87
|
+
combined.set(new Uint8Array(encrypted), iv.length);
|
|
88
|
+
return this.arrayBufferToBase64(combined);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Decrypt data
|
|
92
|
+
*/
|
|
93
|
+
async decrypt(encryptedData) {
|
|
94
|
+
if (!this.encryptionKey || !crypto.subtle) {
|
|
95
|
+
// Data not encrypted
|
|
96
|
+
return encryptedData;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const combined = this.base64ToArrayBuffer(encryptedData);
|
|
100
|
+
const iv = combined.slice(0, 12);
|
|
101
|
+
const encrypted = combined.slice(12);
|
|
102
|
+
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, this.encryptionKey, encrypted);
|
|
103
|
+
return new TextDecoder().decode(decrypted);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error('Decryption failed', error);
|
|
107
|
+
return encryptedData; // Return original if decryption fails
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Save auth state
|
|
112
|
+
*/
|
|
113
|
+
async saveAuthState(gameId, authState) {
|
|
114
|
+
const key = `${STORAGE_KEY_PREFIX}${gameId}_auth`;
|
|
115
|
+
const data = JSON.stringify(authState);
|
|
116
|
+
const encrypted = await this.encrypt(data);
|
|
117
|
+
localStorage.setItem(key, encrypted);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Load auth state
|
|
121
|
+
*/
|
|
122
|
+
async loadAuthState(gameId) {
|
|
123
|
+
const key = `${STORAGE_KEY_PREFIX}${gameId}_auth`;
|
|
124
|
+
const encrypted = localStorage.getItem(key);
|
|
125
|
+
if (!encrypted)
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
const decrypted = await this.decrypt(encrypted);
|
|
129
|
+
return JSON.parse(decrypted);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error('Failed to load auth state', error);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Save shared token (accessible by all DeveloperWorks games)
|
|
138
|
+
*/
|
|
139
|
+
async saveSharedToken(token) {
|
|
140
|
+
const encrypted = await this.encrypt(token);
|
|
141
|
+
localStorage.setItem(SHARED_TOKEN_KEY, encrypted);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Load shared token
|
|
145
|
+
*/
|
|
146
|
+
async loadSharedToken() {
|
|
147
|
+
const encrypted = localStorage.getItem(SHARED_TOKEN_KEY);
|
|
148
|
+
if (!encrypted)
|
|
149
|
+
return null;
|
|
150
|
+
try {
|
|
151
|
+
return await this.decrypt(encrypted);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
console.error('Failed to load shared token', error);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Clear auth state for a game
|
|
160
|
+
*/
|
|
161
|
+
clearAuthState(gameId) {
|
|
162
|
+
const key = `${STORAGE_KEY_PREFIX}${gameId}_auth`;
|
|
163
|
+
localStorage.removeItem(key);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Clear shared token
|
|
167
|
+
*/
|
|
168
|
+
clearSharedToken() {
|
|
169
|
+
localStorage.removeItem(SHARED_TOKEN_KEY);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Clear all PlayKit data
|
|
173
|
+
*/
|
|
174
|
+
clearAll() {
|
|
175
|
+
Object.keys(localStorage).forEach((key) => {
|
|
176
|
+
if (key.startsWith(STORAGE_KEY_PREFIX) || key === SHARED_TOKEN_KEY) {
|
|
177
|
+
localStorage.removeItem(key);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Utility: ArrayBuffer to Base64
|
|
183
|
+
*/
|
|
184
|
+
arrayBufferToBase64(buffer) {
|
|
185
|
+
const bytes = new Uint8Array(buffer);
|
|
186
|
+
let binary = '';
|
|
187
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
188
|
+
binary += String.fromCharCode(bytes[i]);
|
|
189
|
+
}
|
|
190
|
+
return btoa(binary);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Utility: Base64 to ArrayBuffer
|
|
194
|
+
*/
|
|
195
|
+
base64ToArrayBuffer(base64) {
|
|
196
|
+
const binary = atob(base64);
|
|
197
|
+
const bytes = new Uint8Array(binary.length);
|
|
198
|
+
for (let i = 0; i < binary.length; i++) {
|
|
199
|
+
bytes[i] = binary.charCodeAt(i);
|
|
200
|
+
}
|
|
201
|
+
return bytes.buffer;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Authentication Flow Manager
|
|
207
|
+
* Manages the headless authentication flow with automatic UI
|
|
208
|
+
*/
|
|
209
|
+
// i18n translations
|
|
210
|
+
const translations$1 = {
|
|
211
|
+
en: {
|
|
212
|
+
signIn: 'Sign In / Register',
|
|
213
|
+
signInSubtitle: 'If you don\'t have an account, we\'ll create one for you. Sign up and get free credits!',
|
|
214
|
+
email: 'Email',
|
|
215
|
+
phone: 'Phone',
|
|
216
|
+
emailPlaceholder: 'Enter your email address',
|
|
217
|
+
phonePlaceholder: 'Enter your phone number (+86 Only)',
|
|
218
|
+
sendCode: 'Send Code',
|
|
219
|
+
enterCode: 'Enter Code',
|
|
220
|
+
enterCodeSubtitle: "We've sent a 6-digit code to your",
|
|
221
|
+
verify: 'Verify',
|
|
222
|
+
pleaseEnterEmail: 'Please enter your email address',
|
|
223
|
+
pleaseEnterPhone: 'Please enter your phone number',
|
|
224
|
+
enterAllDigits: 'Please enter all 6 digits',
|
|
225
|
+
verificationFailed: 'Verification failed',
|
|
226
|
+
failedToSendCode: 'Failed to send code',
|
|
227
|
+
invalidCode: 'Invalid verification code',
|
|
228
|
+
},
|
|
229
|
+
zh: {
|
|
230
|
+
signIn: '登录/注册',
|
|
231
|
+
signInSubtitle: '如果您没有帐户,我们会为您自动注册。注册即送积分!',
|
|
232
|
+
email: '邮箱',
|
|
233
|
+
phone: '手机',
|
|
234
|
+
emailPlaceholder: '请输入邮箱地址',
|
|
235
|
+
phonePlaceholder: '请输入手机号(仅限 +86)',
|
|
236
|
+
sendCode: '发送验证码',
|
|
237
|
+
enterCode: '输入验证码',
|
|
238
|
+
enterCodeSubtitle: '我们已向您发送了 6 位验证码:',
|
|
239
|
+
verify: '验证',
|
|
240
|
+
pleaseEnterEmail: '请输入邮箱地址',
|
|
241
|
+
pleaseEnterPhone: '请输入手机号',
|
|
242
|
+
enterAllDigits: '请输入完整的 6 位验证码',
|
|
243
|
+
verificationFailed: '验证失败',
|
|
244
|
+
failedToSendCode: '发送验证码失败',
|
|
245
|
+
invalidCode: '验证码无效',
|
|
246
|
+
},
|
|
247
|
+
'zh-TW': {
|
|
248
|
+
signIn: '登入/註冊',
|
|
249
|
+
signInSubtitle: '如果您沒有帳戶,我們會為您自動註冊。註冊即送積分!',
|
|
250
|
+
email: '電子郵件',
|
|
251
|
+
phone: '手機',
|
|
252
|
+
emailPlaceholder: '請輸入電子郵件地址',
|
|
253
|
+
phonePlaceholder: '請輸入手機號碼(僅限 +86)',
|
|
254
|
+
sendCode: '發送驗證碼',
|
|
255
|
+
enterCode: '輸入驗證碼',
|
|
256
|
+
enterCodeSubtitle: '我們已向您發送了 6 位驗證碼:',
|
|
257
|
+
verify: '驗證',
|
|
258
|
+
pleaseEnterEmail: '請輸入電子郵件地址',
|
|
259
|
+
pleaseEnterPhone: '請輸入手機號碼',
|
|
260
|
+
enterAllDigits: '請輸入完整的 6 位驗證碼',
|
|
261
|
+
verificationFailed: '驗證失敗',
|
|
262
|
+
failedToSendCode: '發送驗證碼失敗',
|
|
263
|
+
invalidCode: '驗證碼無效',
|
|
264
|
+
},
|
|
265
|
+
ja: {
|
|
266
|
+
signIn: 'サインイン/登録',
|
|
267
|
+
signInSubtitle: 'アカウントをお持ちでない場合は、自動的に作成します。登録すると無料クレジットがもらえます!',
|
|
268
|
+
email: 'メール',
|
|
269
|
+
phone: '電話',
|
|
270
|
+
emailPlaceholder: 'メールアドレスを入力してください',
|
|
271
|
+
phonePlaceholder: '電話番号を入力してください(+86のみ)',
|
|
272
|
+
sendCode: '認証コードを送信',
|
|
273
|
+
enterCode: '認証コードを入力',
|
|
274
|
+
enterCodeSubtitle: '6桁の認証コードを送信しました:',
|
|
275
|
+
verify: '検証',
|
|
276
|
+
pleaseEnterEmail: 'メールアドレスを入力してください',
|
|
277
|
+
pleaseEnterPhone: '電話番号を入力してください',
|
|
278
|
+
enterAllDigits: '6桁すべて入力してください',
|
|
279
|
+
verificationFailed: '検証に失敗しました',
|
|
280
|
+
failedToSendCode: '認証コードの送信に失敗しました',
|
|
281
|
+
invalidCode: '認証コードが無効です',
|
|
282
|
+
},
|
|
283
|
+
ko: {
|
|
284
|
+
signIn: '로그인/가입',
|
|
285
|
+
signInSubtitle: '계정이 없으시면 자동으로 생성해 드립니다. 가입하면 무료 크레딧을 받으세요!',
|
|
286
|
+
email: '이메일',
|
|
287
|
+
phone: '전화',
|
|
288
|
+
emailPlaceholder: '이메일 주소를 입력하세요',
|
|
289
|
+
phonePlaceholder: '전화번호를 입력하세요(+86만 지원)',
|
|
290
|
+
sendCode: '인증 코드 전송',
|
|
291
|
+
enterCode: '인증 코드 입력',
|
|
292
|
+
enterCodeSubtitle: '6자리 인증 코드를 보냈습니다:',
|
|
293
|
+
verify: '확인',
|
|
294
|
+
pleaseEnterEmail: '이메일 주소를 입력하세요',
|
|
295
|
+
pleaseEnterPhone: '전화번호를 입력하세요',
|
|
296
|
+
enterAllDigits: '6자리를 모두 입력하세요',
|
|
297
|
+
verificationFailed: '인증 실패',
|
|
298
|
+
failedToSendCode: '인증 코드 전송 실패',
|
|
299
|
+
invalidCode: '인증 코드가 잘못되었습니다',
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
class AuthFlowManager extends EventEmitter {
|
|
303
|
+
constructor(baseURL = 'https://playkit.agentlandlab.com') {
|
|
304
|
+
super();
|
|
305
|
+
this.currentSessionId = null;
|
|
306
|
+
this.uiContainer = null;
|
|
307
|
+
this.isSuccess = false;
|
|
308
|
+
this.currentLanguage = 'en';
|
|
309
|
+
// UI Elements
|
|
310
|
+
this.modal = null;
|
|
311
|
+
this.identifierPanel = null;
|
|
312
|
+
this.verificationPanel = null;
|
|
313
|
+
this.loadingOverlay = null;
|
|
314
|
+
this.baseURL = baseURL;
|
|
315
|
+
this.currentLanguage = this.detectLanguage();
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Detect browser language (safe for Node.js environment)
|
|
319
|
+
*/
|
|
320
|
+
detectLanguage() {
|
|
321
|
+
// Check if running in browser environment
|
|
322
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
323
|
+
return 'en'; // Default to English in Node.js environment
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const browserLang = navigator.language.toLowerCase();
|
|
327
|
+
// Match language codes
|
|
328
|
+
if (browserLang.startsWith('zh-tw') || browserLang.startsWith('zh-hk')) {
|
|
329
|
+
return 'zh-TW'; // Traditional Chinese
|
|
330
|
+
}
|
|
331
|
+
else if (browserLang.startsWith('zh')) {
|
|
332
|
+
return 'zh'; // Simplified Chinese
|
|
333
|
+
}
|
|
334
|
+
else if (browserLang.startsWith('ja')) {
|
|
335
|
+
return 'ja'; // Japanese
|
|
336
|
+
}
|
|
337
|
+
else if (browserLang.startsWith('ko')) {
|
|
338
|
+
return 'ko'; // Korean
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
return 'en'; // Default to English
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
// Fallback to English if detection fails
|
|
346
|
+
return 'en';
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Get translated text
|
|
351
|
+
*/
|
|
352
|
+
t(key) {
|
|
353
|
+
return translations$1[this.currentLanguage][key];
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Start the authentication flow
|
|
357
|
+
* Returns a promise that resolves with the JWT token
|
|
358
|
+
*/
|
|
359
|
+
async startFlow() {
|
|
360
|
+
return new Promise((resolve, reject) => {
|
|
361
|
+
// Create and show UI
|
|
362
|
+
this.createUI();
|
|
363
|
+
this.showModal();
|
|
364
|
+
// Listen for success/failure
|
|
365
|
+
this.once('success', (token) => {
|
|
366
|
+
this.hideModal();
|
|
367
|
+
resolve(token);
|
|
368
|
+
});
|
|
369
|
+
this.once('error', (error) => {
|
|
370
|
+
this.hideModal();
|
|
371
|
+
reject(error);
|
|
372
|
+
});
|
|
373
|
+
// Set default auth type based on region
|
|
374
|
+
this.setDefaultAuthTypeByRegion().catch((err) => {
|
|
375
|
+
console.error('[PlayKit Auth] Failed to detect region:', err);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Create the authentication UI
|
|
381
|
+
*/
|
|
382
|
+
createUI() {
|
|
383
|
+
// Create modal container
|
|
384
|
+
this.modal = document.createElement('div');
|
|
385
|
+
this.modal.className = 'playkit-auth-modal';
|
|
386
|
+
this.modal.innerHTML = `
|
|
387
|
+
<div class="playkit-auth-overlay"></div>
|
|
388
|
+
<div class="playkit-auth-container">
|
|
389
|
+
<!-- Identifier Panel -->
|
|
390
|
+
<div class="playkit-auth-panel" id="playkit-identifier-panel">
|
|
391
|
+
<div class="playkit-auth-header">
|
|
392
|
+
<h2>${this.t('signIn')}</h2>
|
|
393
|
+
<p>${this.t('signInSubtitle')}</p>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<div class="playkit-auth-toggle">
|
|
397
|
+
<label class="playkit-toggle-option">
|
|
398
|
+
<input type="radio" name="auth-type" value="email" checked>
|
|
399
|
+
<span>${this.t('email')}</span>
|
|
400
|
+
</label>
|
|
401
|
+
<label class="playkit-toggle-option">
|
|
402
|
+
<input type="radio" name="auth-type" value="phone">
|
|
403
|
+
<span>${this.t('phone')}</span>
|
|
404
|
+
</label>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<div class="playkit-auth-input-group">
|
|
408
|
+
<div class="playkit-input-wrapper">
|
|
409
|
+
<svg class="playkit-input-icon" id="playkit-identifier-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
410
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
411
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
412
|
+
</svg>
|
|
413
|
+
<input
|
|
414
|
+
type="text"
|
|
415
|
+
id="playkit-identifier-input"
|
|
416
|
+
placeholder="${this.t('emailPlaceholder')}"
|
|
417
|
+
autocomplete="off"
|
|
418
|
+
>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<button class="playkit-auth-button" id="playkit-send-code-btn">
|
|
423
|
+
${this.t('sendCode')}
|
|
424
|
+
</button>
|
|
425
|
+
|
|
426
|
+
<div class="playkit-auth-error" id="playkit-error-text"></div>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<!-- Verification Panel -->
|
|
430
|
+
<div class="playkit-auth-panel" id="playkit-verification-panel" style="display: none;">
|
|
431
|
+
<div class="playkit-auth-header">
|
|
432
|
+
<button class="playkit-back-button" id="playkit-back-btn">
|
|
433
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
434
|
+
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
|
435
|
+
</svg>
|
|
436
|
+
</button>
|
|
437
|
+
<h2>${this.t('enterCode')}</h2>
|
|
438
|
+
<p>${this.t('enterCodeSubtitle')} <span id="playkit-identifier-display"></span></p>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<div class="playkit-auth-input-group">
|
|
442
|
+
<div class="playkit-code-inputs">
|
|
443
|
+
<input type="text" maxlength="1" class="playkit-code-input" data-index="0">
|
|
444
|
+
<input type="text" maxlength="1" class="playkit-code-input" data-index="1">
|
|
445
|
+
<input type="text" maxlength="1" class="playkit-code-input" data-index="2">
|
|
446
|
+
<input type="text" maxlength="1" class="playkit-code-input" data-index="3">
|
|
447
|
+
<input type="text" maxlength="1" class="playkit-code-input" data-index="4">
|
|
448
|
+
<input type="text" maxlength="1" class="playkit-code-input" data-index="5">
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<button class="playkit-auth-button" id="playkit-verify-btn">
|
|
453
|
+
${this.t('verify')}
|
|
454
|
+
</button>
|
|
455
|
+
|
|
456
|
+
<div class="playkit-auth-error" id="playkit-verify-error-text"></div>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<!-- Loading Overlay -->
|
|
460
|
+
<div class="playkit-loading-overlay" id="playkit-loading-overlay" style="display: none;">
|
|
461
|
+
<div class="playkit-spinner"></div>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
`;
|
|
465
|
+
// Add styles
|
|
466
|
+
this.addStyles();
|
|
467
|
+
// Append to body
|
|
468
|
+
document.body.appendChild(this.modal);
|
|
469
|
+
// Get references
|
|
470
|
+
this.identifierPanel = document.getElementById('playkit-identifier-panel');
|
|
471
|
+
this.verificationPanel = document.getElementById('playkit-verification-panel');
|
|
472
|
+
this.loadingOverlay = document.getElementById('playkit-loading-overlay');
|
|
473
|
+
// Setup event listeners
|
|
474
|
+
this.setupEventListeners();
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Add CSS styles to the page
|
|
478
|
+
*/
|
|
479
|
+
addStyles() {
|
|
480
|
+
const styleId = 'playkit-auth-styles';
|
|
481
|
+
if (document.getElementById(styleId))
|
|
482
|
+
return;
|
|
483
|
+
const style = document.createElement('style');
|
|
484
|
+
style.id = styleId;
|
|
485
|
+
style.textContent = `
|
|
486
|
+
.playkit-auth-modal {
|
|
487
|
+
position: fixed;
|
|
488
|
+
top: 0;
|
|
489
|
+
left: 0;
|
|
490
|
+
right: 0;
|
|
491
|
+
bottom: 0;
|
|
492
|
+
z-index: 999999;
|
|
493
|
+
display: flex;
|
|
494
|
+
justify-content: center;
|
|
495
|
+
align-items: center;
|
|
496
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.playkit-auth-overlay {
|
|
500
|
+
position: absolute;
|
|
501
|
+
top: 0;
|
|
502
|
+
left: 0;
|
|
503
|
+
right: 0;
|
|
504
|
+
bottom: 0;
|
|
505
|
+
background: rgba(0, 0, 0, 0.48);
|
|
506
|
+
backdrop-filter: blur(8px);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.playkit-auth-container {
|
|
510
|
+
position: relative;
|
|
511
|
+
background: #FFFFFF;
|
|
512
|
+
border-radius: 4px;
|
|
513
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
514
|
+
width: 90%;
|
|
515
|
+
max-width: 420px;
|
|
516
|
+
overflow: hidden;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.playkit-auth-panel {
|
|
520
|
+
padding: 40px 32px;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.playkit-auth-header {
|
|
524
|
+
text-align: center;
|
|
525
|
+
margin-bottom: 32px;
|
|
526
|
+
position: relative;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.playkit-auth-header h2 {
|
|
530
|
+
margin: 0 0 8px 0;
|
|
531
|
+
font-size: 28px;
|
|
532
|
+
font-weight: 600;
|
|
533
|
+
color: #000000;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.playkit-auth-header p {
|
|
537
|
+
margin: 0;
|
|
538
|
+
font-size: 14px;
|
|
539
|
+
color: #666666;
|
|
540
|
+
line-height: 1.5;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.playkit-back-button {
|
|
544
|
+
position: absolute;
|
|
545
|
+
left: 0;
|
|
546
|
+
top: 0;
|
|
547
|
+
background: transparent;
|
|
548
|
+
border: none;
|
|
549
|
+
cursor: pointer;
|
|
550
|
+
padding: 4px;
|
|
551
|
+
border-radius: 2px;
|
|
552
|
+
color: #666666;
|
|
553
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.playkit-back-button:hover {
|
|
557
|
+
background: #F6F6F6;
|
|
558
|
+
color: #000000;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.playkit-auth-toggle {
|
|
562
|
+
display: flex;
|
|
563
|
+
background: #F6F6F6;
|
|
564
|
+
border-radius: 2px;
|
|
565
|
+
padding: 2px;
|
|
566
|
+
margin-bottom: 24px;
|
|
567
|
+
gap: 2px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.playkit-toggle-option {
|
|
571
|
+
flex: 1;
|
|
572
|
+
display: flex;
|
|
573
|
+
justify-content: center;
|
|
574
|
+
align-items: center;
|
|
575
|
+
padding: 10px 16px;
|
|
576
|
+
border-radius: 2px;
|
|
577
|
+
cursor: pointer;
|
|
578
|
+
transition: background-color 0.15s ease;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.playkit-toggle-option input {
|
|
582
|
+
display: none;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.playkit-toggle-option span {
|
|
586
|
+
font-size: 14px;
|
|
587
|
+
font-weight: 500;
|
|
588
|
+
color: #666666;
|
|
589
|
+
transition: color 0.15s ease;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.playkit-toggle-option input:checked + span {
|
|
593
|
+
color: #FFFFFF;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.playkit-toggle-option:has(input:checked) {
|
|
597
|
+
background: #276EF1;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.playkit-auth-input-group {
|
|
601
|
+
margin-bottom: 24px;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.playkit-input-wrapper {
|
|
605
|
+
position: relative;
|
|
606
|
+
display: flex;
|
|
607
|
+
align-items: center;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.playkit-input-icon {
|
|
611
|
+
position: absolute;
|
|
612
|
+
left: 12px;
|
|
613
|
+
color: #999999;
|
|
614
|
+
pointer-events: none;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
.playkit-input-wrapper input {
|
|
618
|
+
width: 100%;
|
|
619
|
+
padding: 12px 12px 12px 44px;
|
|
620
|
+
border: 1px solid #CCCCCC;
|
|
621
|
+
border-radius: 2px;
|
|
622
|
+
font-size: 14px;
|
|
623
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
624
|
+
box-sizing: border-box;
|
|
625
|
+
background: #FFFFFF;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.playkit-input-wrapper input:hover {
|
|
629
|
+
border-color: #999999;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.playkit-input-wrapper input:focus {
|
|
633
|
+
outline: none;
|
|
634
|
+
border-color: #276EF1;
|
|
635
|
+
box-shadow: 0 0 0 3px rgba(39, 110, 241, 0.1);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.playkit-code-inputs {
|
|
639
|
+
display: flex;
|
|
640
|
+
gap: 8px;
|
|
641
|
+
justify-content: center;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.playkit-code-input {
|
|
645
|
+
width: 48px !important;
|
|
646
|
+
height: 56px;
|
|
647
|
+
text-align: center;
|
|
648
|
+
font-size: 24px;
|
|
649
|
+
font-weight: 600;
|
|
650
|
+
border: 1px solid #CCCCCC !important;
|
|
651
|
+
border-radius: 2px;
|
|
652
|
+
padding: 0 !important;
|
|
653
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
654
|
+
background: #FFFFFF;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
.playkit-code-input:hover {
|
|
658
|
+
border-color: #999999 !important;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.playkit-code-input:focus {
|
|
662
|
+
outline: none;
|
|
663
|
+
border-color: #276EF1 !important;
|
|
664
|
+
box-shadow: 0 0 0 3px rgba(39, 110, 241, 0.1);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.playkit-auth-button {
|
|
668
|
+
width: 100%;
|
|
669
|
+
padding: 12px 16px;
|
|
670
|
+
background: #276EF1;
|
|
671
|
+
color: #FFFFFF;
|
|
672
|
+
border: none;
|
|
673
|
+
border-radius: 2px;
|
|
674
|
+
font-size: 14px;
|
|
675
|
+
font-weight: 500;
|
|
676
|
+
cursor: pointer;
|
|
677
|
+
transition: background-color 0.15s ease;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.playkit-auth-button:hover:not(:disabled) {
|
|
681
|
+
background: #174EB6;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.playkit-auth-button:active:not(:disabled) {
|
|
685
|
+
background: #0F3A8A;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.playkit-auth-button:disabled {
|
|
689
|
+
background: #CCCCCC;
|
|
690
|
+
cursor: not-allowed;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.playkit-auth-error {
|
|
694
|
+
margin-top: 16px;
|
|
695
|
+
padding: 12px 16px;
|
|
696
|
+
background: #FEF0F0;
|
|
697
|
+
border: 1px solid #FDD;
|
|
698
|
+
border-radius: 2px;
|
|
699
|
+
color: #CC3333;
|
|
700
|
+
font-size: 13px;
|
|
701
|
+
text-align: left;
|
|
702
|
+
display: none;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.playkit-auth-error.show {
|
|
706
|
+
display: block;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
.playkit-loading-overlay {
|
|
710
|
+
position: absolute;
|
|
711
|
+
top: 0;
|
|
712
|
+
left: 0;
|
|
713
|
+
right: 0;
|
|
714
|
+
bottom: 0;
|
|
715
|
+
background: rgba(255, 255, 255, 0.96);
|
|
716
|
+
display: flex;
|
|
717
|
+
justify-content: center;
|
|
718
|
+
align-items: center;
|
|
719
|
+
border-radius: 4px;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.playkit-spinner {
|
|
723
|
+
width: 40px;
|
|
724
|
+
height: 40px;
|
|
725
|
+
border: 3px solid #F0F0F0;
|
|
726
|
+
border-top: 3px solid #276EF1;
|
|
727
|
+
border-radius: 50%;
|
|
728
|
+
animation: playkit-spin 0.8s linear infinite;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
@keyframes playkit-spin {
|
|
732
|
+
0% { transform: rotate(0deg); }
|
|
733
|
+
100% { transform: rotate(360deg); }
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@media (max-width: 480px) {
|
|
737
|
+
.playkit-auth-container {
|
|
738
|
+
width: 95%;
|
|
739
|
+
max-width: none;
|
|
740
|
+
border-radius: 2px;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.playkit-auth-panel {
|
|
744
|
+
padding: 32px 24px;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.playkit-code-input {
|
|
748
|
+
width: 40px !important;
|
|
749
|
+
height: 48px;
|
|
750
|
+
font-size: 20px;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.playkit-code-inputs {
|
|
754
|
+
gap: 6px;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
`;
|
|
758
|
+
document.head.appendChild(style);
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Setup event listeners
|
|
762
|
+
*/
|
|
763
|
+
setupEventListeners() {
|
|
764
|
+
var _a, _b, _c;
|
|
765
|
+
// Auth type toggle
|
|
766
|
+
const emailRadio = (_a = this.modal) === null || _a === void 0 ? void 0 : _a.querySelector('input[value="email"]');
|
|
767
|
+
const phoneRadio = (_b = this.modal) === null || _b === void 0 ? void 0 : _b.querySelector('input[value="phone"]');
|
|
768
|
+
const identifierInput = document.getElementById('playkit-identifier-input');
|
|
769
|
+
const identifierIcon = document.getElementById('playkit-identifier-icon');
|
|
770
|
+
const updateIcon = () => {
|
|
771
|
+
const isEmail = emailRadio === null || emailRadio === void 0 ? void 0 : emailRadio.checked;
|
|
772
|
+
identifierInput.placeholder = isEmail
|
|
773
|
+
? this.t('emailPlaceholder')
|
|
774
|
+
: this.t('phonePlaceholder');
|
|
775
|
+
// Update icon
|
|
776
|
+
if (isEmail) {
|
|
777
|
+
identifierIcon.innerHTML = `
|
|
778
|
+
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
|
779
|
+
<polyline points="22,6 12,13 2,6"></polyline>
|
|
780
|
+
`;
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
identifierIcon.innerHTML = `
|
|
784
|
+
<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
|
|
785
|
+
`;
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
emailRadio === null || emailRadio === void 0 ? void 0 : emailRadio.addEventListener('change', updateIcon);
|
|
789
|
+
phoneRadio === null || phoneRadio === void 0 ? void 0 : phoneRadio.addEventListener('change', updateIcon);
|
|
790
|
+
// Send code button
|
|
791
|
+
const sendCodeBtn = document.getElementById('playkit-send-code-btn');
|
|
792
|
+
sendCodeBtn === null || sendCodeBtn === void 0 ? void 0 : sendCodeBtn.addEventListener('click', () => this.onSendCodeClicked());
|
|
793
|
+
// Enter key in identifier input
|
|
794
|
+
identifierInput === null || identifierInput === void 0 ? void 0 : identifierInput.addEventListener('keypress', (e) => {
|
|
795
|
+
if (e.key === 'Enter') {
|
|
796
|
+
this.onSendCodeClicked();
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
// Code inputs
|
|
800
|
+
const codeInputs = (_c = this.modal) === null || _c === void 0 ? void 0 : _c.querySelectorAll('.playkit-code-input');
|
|
801
|
+
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input, index) => {
|
|
802
|
+
input.addEventListener('input', (e) => {
|
|
803
|
+
const target = e.target;
|
|
804
|
+
if (target.value.length === 1 && index < codeInputs.length - 1) {
|
|
805
|
+
codeInputs[index + 1].focus();
|
|
806
|
+
}
|
|
807
|
+
// Auto-submit when all 6 digits entered
|
|
808
|
+
if (index === 5 && target.value.length === 1) {
|
|
809
|
+
this.onVerifyClicked();
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
input.addEventListener('keydown', (e) => {
|
|
813
|
+
if (e.key === 'Backspace' && !input.value && index > 0) {
|
|
814
|
+
codeInputs[index - 1].focus();
|
|
815
|
+
}
|
|
816
|
+
});
|
|
817
|
+
// Paste support
|
|
818
|
+
input.addEventListener('paste', (e) => {
|
|
819
|
+
var _a;
|
|
820
|
+
e.preventDefault();
|
|
821
|
+
const pastedData = ((_a = e.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text')) || '';
|
|
822
|
+
const digits = pastedData.replace(/\D/g, '').slice(0, 6);
|
|
823
|
+
digits.split('').forEach((digit, i) => {
|
|
824
|
+
if (codeInputs[i]) {
|
|
825
|
+
codeInputs[i].value = digit;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
if (digits.length === 6) {
|
|
829
|
+
this.onVerifyClicked();
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
// Verify button
|
|
834
|
+
const verifyBtn = document.getElementById('playkit-verify-btn');
|
|
835
|
+
verifyBtn === null || verifyBtn === void 0 ? void 0 : verifyBtn.addEventListener('click', () => this.onVerifyClicked());
|
|
836
|
+
// Back button
|
|
837
|
+
const backBtn = document.getElementById('playkit-back-btn');
|
|
838
|
+
backBtn === null || backBtn === void 0 ? void 0 : backBtn.addEventListener('click', () => {
|
|
839
|
+
this.showIdentifierPanel();
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Handle send code button click
|
|
844
|
+
*/
|
|
845
|
+
async onSendCodeClicked() {
|
|
846
|
+
var _a;
|
|
847
|
+
this.clearError();
|
|
848
|
+
const identifierInput = document.getElementById('playkit-identifier-input');
|
|
849
|
+
const identifier = identifierInput.value.trim();
|
|
850
|
+
const emailRadio = (_a = this.modal) === null || _a === void 0 ? void 0 : _a.querySelector('input[value="email"]');
|
|
851
|
+
const type = emailRadio.checked ? 'email' : 'phone';
|
|
852
|
+
if (!identifier) {
|
|
853
|
+
this.showError(type === 'email' ? this.t('pleaseEnterEmail') : this.t('pleaseEnterPhone'));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const sendCodeBtn = document.getElementById('playkit-send-code-btn');
|
|
857
|
+
sendCodeBtn.disabled = true;
|
|
858
|
+
this.showLoading();
|
|
859
|
+
try {
|
|
860
|
+
const success = await this.sendVerificationCode(identifier, type);
|
|
861
|
+
if (success) {
|
|
862
|
+
// Store identifier for display
|
|
863
|
+
const displaySpan = document.getElementById('playkit-identifier-display');
|
|
864
|
+
if (displaySpan) {
|
|
865
|
+
displaySpan.textContent = type === 'email' ? identifier : identifier;
|
|
866
|
+
}
|
|
867
|
+
// Switch to verification panel
|
|
868
|
+
this.showVerificationPanel();
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
catch (error) {
|
|
872
|
+
this.showError(error instanceof Error ? error.message : this.t('failedToSendCode'));
|
|
873
|
+
}
|
|
874
|
+
finally {
|
|
875
|
+
this.hideLoading();
|
|
876
|
+
sendCodeBtn.disabled = false;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Handle verify button click
|
|
881
|
+
*/
|
|
882
|
+
async onVerifyClicked() {
|
|
883
|
+
var _a;
|
|
884
|
+
this.clearError('verify');
|
|
885
|
+
const codeInputs = (_a = this.modal) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.playkit-code-input');
|
|
886
|
+
const code = Array.from(codeInputs).map((input) => input.value).join('');
|
|
887
|
+
if (code.length !== 6) {
|
|
888
|
+
this.showError(this.t('enterAllDigits'), 'verify');
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
this.showLoading();
|
|
892
|
+
try {
|
|
893
|
+
const globalToken = await this.verifyCode(code);
|
|
894
|
+
this.emit('success', globalToken);
|
|
895
|
+
}
|
|
896
|
+
catch (error) {
|
|
897
|
+
this.showError(error instanceof Error ? error.message : this.t('verificationFailed'), 'verify');
|
|
898
|
+
this.hideLoading();
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Send verification code to backend
|
|
903
|
+
*/
|
|
904
|
+
async sendVerificationCode(identifier, type) {
|
|
905
|
+
const response = await fetch(`${this.baseURL}/api/auth/send-code`, {
|
|
906
|
+
method: 'POST',
|
|
907
|
+
headers: { 'Content-Type': 'application/json' },
|
|
908
|
+
body: JSON.stringify({ identifier, type }),
|
|
909
|
+
});
|
|
910
|
+
if (!response.ok) {
|
|
911
|
+
throw new PlayKitError(this.t('failedToSendCode'), 'SEND_CODE_ERROR', response.status);
|
|
912
|
+
}
|
|
913
|
+
const data = await response.json();
|
|
914
|
+
if (!data.success || !data.sessionId) {
|
|
915
|
+
throw new PlayKitError(this.t('failedToSendCode'), 'INVALID_RESPONSE');
|
|
916
|
+
}
|
|
917
|
+
this.currentSessionId = data.sessionId;
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Verify the code and get global token
|
|
922
|
+
*/
|
|
923
|
+
async verifyCode(code) {
|
|
924
|
+
if (!this.currentSessionId) {
|
|
925
|
+
throw new PlayKitError('No session ID available', 'NO_SESSION');
|
|
926
|
+
}
|
|
927
|
+
const response = await fetch(`${this.baseURL}/api/auth/verify-code`, {
|
|
928
|
+
method: 'POST',
|
|
929
|
+
headers: { 'Content-Type': 'application/json' },
|
|
930
|
+
body: JSON.stringify({
|
|
931
|
+
sessionId: this.currentSessionId,
|
|
932
|
+
code,
|
|
933
|
+
}),
|
|
934
|
+
});
|
|
935
|
+
if (!response.ok) {
|
|
936
|
+
throw new PlayKitError(this.t('invalidCode'), 'INVALID_CODE', response.status);
|
|
937
|
+
}
|
|
938
|
+
const data = await response.json();
|
|
939
|
+
if (!data.success || !data.globalToken) {
|
|
940
|
+
throw new PlayKitError(this.t('verificationFailed'), 'VERIFICATION_FAILED');
|
|
941
|
+
}
|
|
942
|
+
return data.globalToken;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Set default auth type based on user region
|
|
946
|
+
*/
|
|
947
|
+
async setDefaultAuthTypeByRegion() {
|
|
948
|
+
var _a;
|
|
949
|
+
try {
|
|
950
|
+
const response = await fetch(`${this.baseURL}/api/reachability`);
|
|
951
|
+
if (response.ok) {
|
|
952
|
+
const data = await response.json();
|
|
953
|
+
if (data.region === 'CN') {
|
|
954
|
+
const phoneRadio = (_a = this.modal) === null || _a === void 0 ? void 0 : _a.querySelector('input[value="phone"]');
|
|
955
|
+
if (phoneRadio) {
|
|
956
|
+
phoneRadio.checked = true;
|
|
957
|
+
phoneRadio.dispatchEvent(new Event('change'));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
catch (error) {
|
|
963
|
+
console.error('[PlayKit Auth] Failed to detect region:', error);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Show/hide panels
|
|
968
|
+
*/
|
|
969
|
+
showIdentifierPanel() {
|
|
970
|
+
var _a;
|
|
971
|
+
if (this.identifierPanel)
|
|
972
|
+
this.identifierPanel.style.display = 'block';
|
|
973
|
+
if (this.verificationPanel)
|
|
974
|
+
this.verificationPanel.style.display = 'none';
|
|
975
|
+
// Clear code inputs
|
|
976
|
+
const codeInputs = (_a = this.modal) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.playkit-code-input');
|
|
977
|
+
codeInputs === null || codeInputs === void 0 ? void 0 : codeInputs.forEach((input) => (input.value = ''));
|
|
978
|
+
}
|
|
979
|
+
showVerificationPanel() {
|
|
980
|
+
var _a;
|
|
981
|
+
if (this.identifierPanel)
|
|
982
|
+
this.identifierPanel.style.display = 'none';
|
|
983
|
+
if (this.verificationPanel)
|
|
984
|
+
this.verificationPanel.style.display = 'block';
|
|
985
|
+
// Focus first code input
|
|
986
|
+
const firstInput = (_a = this.modal) === null || _a === void 0 ? void 0 : _a.querySelector('.playkit-code-input');
|
|
987
|
+
firstInput === null || firstInput === void 0 ? void 0 : firstInput.focus();
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Show/hide loading
|
|
991
|
+
*/
|
|
992
|
+
showLoading() {
|
|
993
|
+
if (this.loadingOverlay)
|
|
994
|
+
this.loadingOverlay.style.display = 'flex';
|
|
995
|
+
}
|
|
996
|
+
hideLoading() {
|
|
997
|
+
if (this.loadingOverlay)
|
|
998
|
+
this.loadingOverlay.style.display = 'none';
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Show/hide error messages
|
|
1002
|
+
*/
|
|
1003
|
+
showError(message, panel = 'identifier') {
|
|
1004
|
+
const errorEl = panel === 'identifier'
|
|
1005
|
+
? document.getElementById('playkit-error-text')
|
|
1006
|
+
: document.getElementById('playkit-verify-error-text');
|
|
1007
|
+
if (errorEl) {
|
|
1008
|
+
errorEl.textContent = message;
|
|
1009
|
+
errorEl.classList.add('show');
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
clearError(panel = 'both') {
|
|
1013
|
+
if (panel === 'identifier' || panel === 'both') {
|
|
1014
|
+
const errorEl = document.getElementById('playkit-error-text');
|
|
1015
|
+
if (errorEl) {
|
|
1016
|
+
errorEl.textContent = '';
|
|
1017
|
+
errorEl.classList.remove('show');
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (panel === 'verify' || panel === 'both') {
|
|
1021
|
+
const errorEl = document.getElementById('playkit-verify-error-text');
|
|
1022
|
+
if (errorEl) {
|
|
1023
|
+
errorEl.textContent = '';
|
|
1024
|
+
errorEl.classList.remove('show');
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Show/hide modal
|
|
1030
|
+
*/
|
|
1031
|
+
showModal() {
|
|
1032
|
+
if (this.modal)
|
|
1033
|
+
this.modal.style.display = 'flex';
|
|
1034
|
+
}
|
|
1035
|
+
hideModal() {
|
|
1036
|
+
if (this.modal) {
|
|
1037
|
+
this.modal.style.display = 'none';
|
|
1038
|
+
// Remove from DOM after animation
|
|
1039
|
+
setTimeout(() => {
|
|
1040
|
+
var _a;
|
|
1041
|
+
(_a = this.modal) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1042
|
+
}, 300);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Clean up
|
|
1047
|
+
*/
|
|
1048
|
+
destroy() {
|
|
1049
|
+
var _a;
|
|
1050
|
+
(_a = this.modal) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1051
|
+
this.removeAllListeners();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Authentication manager
|
|
1057
|
+
* Handles JWT exchange and token management
|
|
1058
|
+
*/
|
|
1059
|
+
const DEFAULT_BASE_URL$3 = 'https://playkit.agentlandlab.com';
|
|
1060
|
+
const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
|
|
1061
|
+
class AuthManager extends EventEmitter {
|
|
1062
|
+
constructor(config) {
|
|
1063
|
+
super();
|
|
1064
|
+
this.authFlowManager = null;
|
|
1065
|
+
this.config = config;
|
|
1066
|
+
this.storage = new TokenStorage();
|
|
1067
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$3;
|
|
1068
|
+
this.authState = {
|
|
1069
|
+
isAuthenticated: false,
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Initialize authentication
|
|
1074
|
+
*/
|
|
1075
|
+
async initialize() {
|
|
1076
|
+
await this.storage.initialize();
|
|
1077
|
+
// Check for developer token (development mode)
|
|
1078
|
+
if (this.config.developerToken) {
|
|
1079
|
+
this.authState = {
|
|
1080
|
+
isAuthenticated: true,
|
|
1081
|
+
token: this.config.developerToken,
|
|
1082
|
+
tokenType: 'developer',
|
|
1083
|
+
};
|
|
1084
|
+
this.emit('authenticated', this.authState);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
// Try to load saved auth state
|
|
1088
|
+
const savedState = await this.storage.loadAuthState(this.config.gameId);
|
|
1089
|
+
if (savedState && savedState.token) {
|
|
1090
|
+
// Check if token is still valid
|
|
1091
|
+
if (savedState.expiresAt && Date.now() < savedState.expiresAt) {
|
|
1092
|
+
this.authState = savedState;
|
|
1093
|
+
this.emit('authenticated', this.authState);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
// Try to load shared token
|
|
1098
|
+
const sharedToken = await this.storage.loadSharedToken();
|
|
1099
|
+
if (sharedToken) {
|
|
1100
|
+
this.authState = {
|
|
1101
|
+
isAuthenticated: true,
|
|
1102
|
+
token: sharedToken,
|
|
1103
|
+
tokenType: 'player',
|
|
1104
|
+
};
|
|
1105
|
+
await this.storage.saveAuthState(this.config.gameId, this.authState);
|
|
1106
|
+
this.emit('authenticated', this.authState);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
// Check if player JWT was provided
|
|
1110
|
+
if (this.config.playerJWT) {
|
|
1111
|
+
await this.exchangeJWT(this.config.playerJWT);
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
// Not authenticated - trigger auto-login UI
|
|
1115
|
+
this.emit('unauthenticated');
|
|
1116
|
+
// Auto-start login flow in browser environment
|
|
1117
|
+
if (typeof window !== 'undefined') {
|
|
1118
|
+
await this.startAuthFlow();
|
|
1119
|
+
// If we reach here, authentication was successful
|
|
1120
|
+
// If it failed, startAuthFlow() will have thrown an error
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
// Node.js environment - cannot show UI, must provide token manually
|
|
1124
|
+
throw new PlayKitError('No authentication token provided. Please provide developerToken, playerJWT, or call login() manually.', 'NOT_AUTHENTICATED');
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Start the authentication flow UI
|
|
1129
|
+
*/
|
|
1130
|
+
async startAuthFlow() {
|
|
1131
|
+
var _a;
|
|
1132
|
+
if (this.authFlowManager) {
|
|
1133
|
+
// Already in progress
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
try {
|
|
1137
|
+
this.authFlowManager = new AuthFlowManager(this.baseURL);
|
|
1138
|
+
// Get global token from auth flow
|
|
1139
|
+
const globalToken = await this.authFlowManager.startFlow();
|
|
1140
|
+
// Exchange for player token
|
|
1141
|
+
await this.exchangeJWT(globalToken);
|
|
1142
|
+
// Clean up
|
|
1143
|
+
this.authFlowManager.destroy();
|
|
1144
|
+
this.authFlowManager = null;
|
|
1145
|
+
}
|
|
1146
|
+
catch (error) {
|
|
1147
|
+
// User canceled or error occurred
|
|
1148
|
+
(_a = this.authFlowManager) === null || _a === void 0 ? void 0 : _a.destroy();
|
|
1149
|
+
this.authFlowManager = null;
|
|
1150
|
+
// Re-emit error
|
|
1151
|
+
this.emit('error', error);
|
|
1152
|
+
throw error;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Exchange JWT for player token
|
|
1157
|
+
*/
|
|
1158
|
+
async exchangeJWT(jwt) {
|
|
1159
|
+
try {
|
|
1160
|
+
const response = await fetch(`${this.baseURL}${JWT_EXCHANGE_ENDPOINT}`, {
|
|
1161
|
+
method: 'POST',
|
|
1162
|
+
headers: {
|
|
1163
|
+
Authorization: `Bearer ${jwt}`,
|
|
1164
|
+
'Content-Type': 'application/json',
|
|
1165
|
+
},
|
|
1166
|
+
body: JSON.stringify({ gameId: this.config.gameId }),
|
|
1167
|
+
});
|
|
1168
|
+
if (!response.ok) {
|
|
1169
|
+
const error = await response.json().catch(() => ({ message: 'JWT exchange failed' }));
|
|
1170
|
+
throw new PlayKitError(error.message || 'JWT exchange failed', error.code, response.status);
|
|
1171
|
+
}
|
|
1172
|
+
const data = await response.json();
|
|
1173
|
+
const playerToken = data.playerToken || data.token;
|
|
1174
|
+
if (!playerToken) {
|
|
1175
|
+
throw new PlayKitError('No player token received from server');
|
|
1176
|
+
}
|
|
1177
|
+
// Calculate expiration (assume 24 hours if not provided)
|
|
1178
|
+
const expiresIn = data.expiresIn || 86400;
|
|
1179
|
+
const expiresAt = Date.now() + expiresIn * 1000;
|
|
1180
|
+
this.authState = {
|
|
1181
|
+
isAuthenticated: true,
|
|
1182
|
+
token: playerToken,
|
|
1183
|
+
tokenType: 'player',
|
|
1184
|
+
expiresAt,
|
|
1185
|
+
};
|
|
1186
|
+
// Save to storage
|
|
1187
|
+
await this.storage.saveAuthState(this.config.gameId, this.authState);
|
|
1188
|
+
await this.storage.saveSharedToken(playerToken);
|
|
1189
|
+
this.emit('authenticated', this.authState);
|
|
1190
|
+
return playerToken;
|
|
1191
|
+
}
|
|
1192
|
+
catch (error) {
|
|
1193
|
+
this.emit('error', error);
|
|
1194
|
+
throw error;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Get current authentication token
|
|
1199
|
+
*/
|
|
1200
|
+
getToken() {
|
|
1201
|
+
return this.authState.token;
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Get current authentication state
|
|
1205
|
+
*/
|
|
1206
|
+
getAuthState() {
|
|
1207
|
+
return Object.assign({}, this.authState);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Check if authenticated
|
|
1211
|
+
*/
|
|
1212
|
+
isAuthenticated() {
|
|
1213
|
+
return this.authState.isAuthenticated;
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Check if token is expired
|
|
1217
|
+
*/
|
|
1218
|
+
isTokenExpired() {
|
|
1219
|
+
if (!this.authState.expiresAt)
|
|
1220
|
+
return false;
|
|
1221
|
+
return Date.now() >= this.authState.expiresAt;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Logout and clear authentication
|
|
1225
|
+
*/
|
|
1226
|
+
async logout() {
|
|
1227
|
+
this.authState = {
|
|
1228
|
+
isAuthenticated: false,
|
|
1229
|
+
};
|
|
1230
|
+
this.storage.clearAuthState(this.config.gameId);
|
|
1231
|
+
this.emit('unauthenticated');
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Clear all stored data
|
|
1235
|
+
*/
|
|
1236
|
+
clearAll() {
|
|
1237
|
+
this.storage.clearAll();
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Translations for the recharge modal
|
|
1243
|
+
*/
|
|
1244
|
+
const translations = {
|
|
1245
|
+
en: {
|
|
1246
|
+
title: 'Insufficient Balance',
|
|
1247
|
+
message: 'Your current balance is not enough to complete this action.',
|
|
1248
|
+
currentBalance: 'Current Balance',
|
|
1249
|
+
credits: 'Credits',
|
|
1250
|
+
rechargeButton: 'Recharge Now',
|
|
1251
|
+
cancelButton: 'Cancel',
|
|
1252
|
+
},
|
|
1253
|
+
zh: {
|
|
1254
|
+
title: '余额不足',
|
|
1255
|
+
message: '您的当前余额不足以完成此操作。',
|
|
1256
|
+
currentBalance: '当前余额',
|
|
1257
|
+
credits: '积分',
|
|
1258
|
+
rechargeButton: '立即充值',
|
|
1259
|
+
cancelButton: '取消',
|
|
1260
|
+
},
|
|
1261
|
+
'zh-TW': {
|
|
1262
|
+
title: '餘額不足',
|
|
1263
|
+
message: '您的當前餘額不足以完成此操作。',
|
|
1264
|
+
currentBalance: '當前餘額',
|
|
1265
|
+
credits: '積分',
|
|
1266
|
+
rechargeButton: '立即充值',
|
|
1267
|
+
cancelButton: '取消',
|
|
1268
|
+
},
|
|
1269
|
+
ja: {
|
|
1270
|
+
title: '残高不足',
|
|
1271
|
+
message: '現在の残高ではこの操作を完了できません。',
|
|
1272
|
+
currentBalance: '現在の残高',
|
|
1273
|
+
credits: 'クレジット',
|
|
1274
|
+
rechargeButton: '今すぐチャージ',
|
|
1275
|
+
cancelButton: 'キャンセル',
|
|
1276
|
+
},
|
|
1277
|
+
ko: {
|
|
1278
|
+
title: '잔액 부족',
|
|
1279
|
+
message: '현재 잔액으로는 이 작업을 완료할 수 없습니다.',
|
|
1280
|
+
currentBalance: '현재 잔액',
|
|
1281
|
+
credits: '크레딧',
|
|
1282
|
+
rechargeButton: '지금 충전',
|
|
1283
|
+
cancelButton: '취소',
|
|
1284
|
+
},
|
|
1285
|
+
};
|
|
1286
|
+
/**
|
|
1287
|
+
* RechargeManager handles the recharge modal UI and recharge window opening
|
|
1288
|
+
*/
|
|
1289
|
+
class RechargeManager extends EventEmitter {
|
|
1290
|
+
constructor(playerToken, rechargePortalUrl = 'https://playkit.agentlandlab.com/playerPortal/recharge') {
|
|
1291
|
+
super();
|
|
1292
|
+
this.modalContainer = null;
|
|
1293
|
+
this.styleElement = null;
|
|
1294
|
+
this.playerToken = playerToken;
|
|
1295
|
+
this.rechargePortalUrl = rechargePortalUrl;
|
|
1296
|
+
this.language = this.detectLanguage();
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Detect user's preferred language
|
|
1300
|
+
*/
|
|
1301
|
+
detectLanguage() {
|
|
1302
|
+
const browserLang = navigator.language.toLowerCase();
|
|
1303
|
+
if (browserLang.startsWith('zh-tw') || browserLang.startsWith('zh-hk')) {
|
|
1304
|
+
return 'zh-TW';
|
|
1305
|
+
}
|
|
1306
|
+
else if (browserLang.startsWith('zh')) {
|
|
1307
|
+
return 'zh';
|
|
1308
|
+
}
|
|
1309
|
+
else if (browserLang.startsWith('ja')) {
|
|
1310
|
+
return 'ja';
|
|
1311
|
+
}
|
|
1312
|
+
else if (browserLang.startsWith('ko')) {
|
|
1313
|
+
return 'ko';
|
|
1314
|
+
}
|
|
1315
|
+
return 'en';
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Get translation text for current language
|
|
1319
|
+
*/
|
|
1320
|
+
t(key) {
|
|
1321
|
+
return translations[this.language][key];
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Build recharge URL with player token
|
|
1325
|
+
*/
|
|
1326
|
+
buildRechargeUrl() {
|
|
1327
|
+
return `${this.rechargePortalUrl}?playerToken=${encodeURIComponent(this.playerToken)}`;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Open recharge window in a new tab
|
|
1331
|
+
*/
|
|
1332
|
+
openRechargeWindow() {
|
|
1333
|
+
const url = this.buildRechargeUrl();
|
|
1334
|
+
window.open(url, '_blank');
|
|
1335
|
+
this.emit('recharge_opened');
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Show insufficient balance modal
|
|
1339
|
+
*/
|
|
1340
|
+
showInsufficientBalanceModal(options = {}) {
|
|
1341
|
+
return new Promise((resolve) => {
|
|
1342
|
+
var _a, _b;
|
|
1343
|
+
// If modal is already shown, don't show another
|
|
1344
|
+
if (this.modalContainer) {
|
|
1345
|
+
resolve();
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
this.injectStyles();
|
|
1349
|
+
this.createModal(options);
|
|
1350
|
+
this.emit('recharge_modal_shown');
|
|
1351
|
+
// Resolve when modal is dismissed
|
|
1352
|
+
const cleanup = () => {
|
|
1353
|
+
this.destroy();
|
|
1354
|
+
this.emit('recharge_modal_dismissed');
|
|
1355
|
+
resolve();
|
|
1356
|
+
};
|
|
1357
|
+
// Add event listeners for dismiss
|
|
1358
|
+
const cancelButton = (_a = this.modalContainer) === null || _a === void 0 ? void 0 : _a.querySelector('.playkit-recharge-cancel');
|
|
1359
|
+
if (cancelButton) {
|
|
1360
|
+
cancelButton.addEventListener('click', cleanup);
|
|
1361
|
+
}
|
|
1362
|
+
const overlay = (_b = this.modalContainer) === null || _b === void 0 ? void 0 : _b.querySelector('.playkit-recharge-overlay');
|
|
1363
|
+
if (overlay) {
|
|
1364
|
+
overlay.addEventListener('click', (e) => {
|
|
1365
|
+
if (e.target === overlay) {
|
|
1366
|
+
cleanup();
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Inject CSS styles for the modal
|
|
1374
|
+
*/
|
|
1375
|
+
injectStyles() {
|
|
1376
|
+
if (this.styleElement) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
this.styleElement = document.createElement('style');
|
|
1380
|
+
this.styleElement.textContent = `
|
|
1381
|
+
.playkit-recharge-overlay {
|
|
1382
|
+
position: fixed;
|
|
1383
|
+
top: 0;
|
|
1384
|
+
left: 0;
|
|
1385
|
+
right: 0;
|
|
1386
|
+
bottom: 0;
|
|
1387
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
1388
|
+
backdrop-filter: blur(4px);
|
|
1389
|
+
display: flex;
|
|
1390
|
+
justify-content: center;
|
|
1391
|
+
align-items: center;
|
|
1392
|
+
z-index: 999999;
|
|
1393
|
+
animation: playkit-recharge-fadeIn 0.2s ease-out;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
@keyframes playkit-recharge-fadeIn {
|
|
1397
|
+
from {
|
|
1398
|
+
opacity: 0;
|
|
1399
|
+
}
|
|
1400
|
+
to {
|
|
1401
|
+
opacity: 1;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
.playkit-recharge-modal {
|
|
1406
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1407
|
+
border-radius: 16px;
|
|
1408
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
1409
|
+
padding: 32px;
|
|
1410
|
+
max-width: 400px;
|
|
1411
|
+
width: 90%;
|
|
1412
|
+
position: relative;
|
|
1413
|
+
animation: playkit-recharge-slideUp 0.3s ease-out;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
@keyframes playkit-recharge-slideUp {
|
|
1417
|
+
from {
|
|
1418
|
+
transform: translateY(20px);
|
|
1419
|
+
opacity: 0;
|
|
1420
|
+
}
|
|
1421
|
+
to {
|
|
1422
|
+
transform: translateY(0);
|
|
1423
|
+
opacity: 1;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.playkit-recharge-title {
|
|
1428
|
+
font-size: 24px;
|
|
1429
|
+
font-weight: bold;
|
|
1430
|
+
color: #ffffff;
|
|
1431
|
+
margin: 0 0 16px 0;
|
|
1432
|
+
text-align: center;
|
|
1433
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.playkit-recharge-message {
|
|
1437
|
+
font-size: 16px;
|
|
1438
|
+
color: rgba(255, 255, 255, 0.9);
|
|
1439
|
+
margin: 0 0 24px 0;
|
|
1440
|
+
text-align: center;
|
|
1441
|
+
line-height: 1.5;
|
|
1442
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
.playkit-recharge-balance {
|
|
1446
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1447
|
+
border-radius: 12px;
|
|
1448
|
+
padding: 16px;
|
|
1449
|
+
margin: 0 0 24px 0;
|
|
1450
|
+
text-align: center;
|
|
1451
|
+
backdrop-filter: blur(10px);
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
.playkit-recharge-balance-label {
|
|
1455
|
+
font-size: 14px;
|
|
1456
|
+
color: rgba(255, 255, 255, 0.8);
|
|
1457
|
+
margin: 0 0 8px 0;
|
|
1458
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
.playkit-recharge-balance-value {
|
|
1462
|
+
font-size: 32px;
|
|
1463
|
+
font-weight: bold;
|
|
1464
|
+
color: #ffffff;
|
|
1465
|
+
margin: 0;
|
|
1466
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
.playkit-recharge-balance-unit {
|
|
1470
|
+
font-size: 16px;
|
|
1471
|
+
color: rgba(255, 255, 255, 0.8);
|
|
1472
|
+
margin-left: 8px;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.playkit-recharge-buttons {
|
|
1476
|
+
display: flex;
|
|
1477
|
+
gap: 12px;
|
|
1478
|
+
justify-content: center;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
.playkit-recharge-button {
|
|
1482
|
+
flex: 1;
|
|
1483
|
+
padding: 12px 24px;
|
|
1484
|
+
border: none;
|
|
1485
|
+
border-radius: 8px;
|
|
1486
|
+
font-size: 16px;
|
|
1487
|
+
font-weight: 600;
|
|
1488
|
+
cursor: pointer;
|
|
1489
|
+
transition: all 0.2s ease;
|
|
1490
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
.playkit-recharge-button-primary {
|
|
1494
|
+
background: #ffffff;
|
|
1495
|
+
color: #667eea;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
.playkit-recharge-button-primary:hover {
|
|
1499
|
+
transform: translateY(-2px);
|
|
1500
|
+
box-shadow: 0 8px 16px rgba(255, 255, 255, 0.2);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
.playkit-recharge-button-primary:active {
|
|
1504
|
+
transform: translateY(0);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
.playkit-recharge-button-secondary {
|
|
1508
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1509
|
+
color: #ffffff;
|
|
1510
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
.playkit-recharge-button-secondary:hover {
|
|
1514
|
+
background: rgba(255, 255, 255, 0.25);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.playkit-recharge-button-secondary:active {
|
|
1518
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
@media (max-width: 480px) {
|
|
1522
|
+
.playkit-recharge-modal {
|
|
1523
|
+
padding: 24px;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
.playkit-recharge-title {
|
|
1527
|
+
font-size: 20px;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
.playkit-recharge-message {
|
|
1531
|
+
font-size: 14px;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
.playkit-recharge-balance-value {
|
|
1535
|
+
font-size: 28px;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
.playkit-recharge-buttons {
|
|
1539
|
+
flex-direction: column;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
`;
|
|
1543
|
+
document.head.appendChild(this.styleElement);
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Create the modal DOM structure
|
|
1547
|
+
*/
|
|
1548
|
+
createModal(options) {
|
|
1549
|
+
this.modalContainer = document.createElement('div');
|
|
1550
|
+
this.modalContainer.className = 'playkit-recharge-overlay';
|
|
1551
|
+
const modal = document.createElement('div');
|
|
1552
|
+
modal.className = 'playkit-recharge-modal';
|
|
1553
|
+
// Title
|
|
1554
|
+
const title = document.createElement('h2');
|
|
1555
|
+
title.className = 'playkit-recharge-title';
|
|
1556
|
+
title.textContent = this.t('title');
|
|
1557
|
+
modal.appendChild(title);
|
|
1558
|
+
// Message
|
|
1559
|
+
const message = document.createElement('p');
|
|
1560
|
+
message.className = 'playkit-recharge-message';
|
|
1561
|
+
message.textContent = options.message || this.t('message');
|
|
1562
|
+
modal.appendChild(message);
|
|
1563
|
+
// Balance display (if provided)
|
|
1564
|
+
if (options.currentBalance !== undefined) {
|
|
1565
|
+
const balanceContainer = document.createElement('div');
|
|
1566
|
+
balanceContainer.className = 'playkit-recharge-balance';
|
|
1567
|
+
const balanceLabel = document.createElement('div');
|
|
1568
|
+
balanceLabel.className = 'playkit-recharge-balance-label';
|
|
1569
|
+
balanceLabel.textContent = this.t('currentBalance');
|
|
1570
|
+
balanceContainer.appendChild(balanceLabel);
|
|
1571
|
+
const balanceValue = document.createElement('div');
|
|
1572
|
+
balanceValue.className = 'playkit-recharge-balance-value';
|
|
1573
|
+
balanceValue.innerHTML = `${options.currentBalance}<span class="playkit-recharge-balance-unit">${this.t('credits')}</span>`;
|
|
1574
|
+
balanceContainer.appendChild(balanceValue);
|
|
1575
|
+
modal.appendChild(balanceContainer);
|
|
1576
|
+
}
|
|
1577
|
+
// Buttons
|
|
1578
|
+
const buttonsContainer = document.createElement('div');
|
|
1579
|
+
buttonsContainer.className = 'playkit-recharge-buttons';
|
|
1580
|
+
const rechargeButton = document.createElement('button');
|
|
1581
|
+
rechargeButton.className = 'playkit-recharge-button playkit-recharge-button-primary';
|
|
1582
|
+
rechargeButton.textContent = this.t('rechargeButton');
|
|
1583
|
+
rechargeButton.addEventListener('click', () => {
|
|
1584
|
+
this.openRechargeWindow();
|
|
1585
|
+
this.destroy();
|
|
1586
|
+
this.emit('recharge_modal_dismissed');
|
|
1587
|
+
});
|
|
1588
|
+
buttonsContainer.appendChild(rechargeButton);
|
|
1589
|
+
const cancelButton = document.createElement('button');
|
|
1590
|
+
cancelButton.className = 'playkit-recharge-button playkit-recharge-button-secondary playkit-recharge-cancel';
|
|
1591
|
+
cancelButton.textContent = this.t('cancelButton');
|
|
1592
|
+
buttonsContainer.appendChild(cancelButton);
|
|
1593
|
+
modal.appendChild(buttonsContainer);
|
|
1594
|
+
this.modalContainer.appendChild(modal);
|
|
1595
|
+
document.body.appendChild(this.modalContainer);
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Update player token (if it changes)
|
|
1599
|
+
*/
|
|
1600
|
+
updateToken(newToken) {
|
|
1601
|
+
this.playerToken = newToken;
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Destroy the modal and clean up
|
|
1605
|
+
*/
|
|
1606
|
+
destroy() {
|
|
1607
|
+
if (this.modalContainer) {
|
|
1608
|
+
this.modalContainer.remove();
|
|
1609
|
+
this.modalContainer = null;
|
|
1610
|
+
}
|
|
1611
|
+
if (this.styleElement) {
|
|
1612
|
+
this.styleElement.remove();
|
|
1613
|
+
this.styleElement = null;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Player client for managing player information and credits
|
|
1620
|
+
*/
|
|
1621
|
+
const DEFAULT_BASE_URL$2 = 'https://playkit.agentlandlab.com';
|
|
1622
|
+
const PLAYER_INFO_ENDPOINT = '/api/external/player-info';
|
|
1623
|
+
class PlayerClient extends EventEmitter {
|
|
1624
|
+
constructor(authManager, config, rechargeConfig = {}) {
|
|
1625
|
+
var _a, _b, _c;
|
|
1626
|
+
super();
|
|
1627
|
+
this.playerInfo = null;
|
|
1628
|
+
this.rechargeManager = null;
|
|
1629
|
+
this.balanceCheckInterval = null;
|
|
1630
|
+
this.authManager = authManager;
|
|
1631
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$2;
|
|
1632
|
+
this.rechargeConfig = {
|
|
1633
|
+
autoShowBalanceModal: (_a = rechargeConfig.autoShowBalanceModal) !== null && _a !== void 0 ? _a : true,
|
|
1634
|
+
balanceCheckInterval: (_b = rechargeConfig.balanceCheckInterval) !== null && _b !== void 0 ? _b : 30000,
|
|
1635
|
+
checkBalanceAfterApiCall: (_c = rechargeConfig.checkBalanceAfterApiCall) !== null && _c !== void 0 ? _c : true,
|
|
1636
|
+
rechargePortalUrl: rechargeConfig.rechargePortalUrl || 'https://playkit.agentlandlab.com/playerPortal/recharge',
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Get player information
|
|
1641
|
+
*/
|
|
1642
|
+
async getPlayerInfo() {
|
|
1643
|
+
const token = this.authManager.getToken();
|
|
1644
|
+
if (!token) {
|
|
1645
|
+
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
1646
|
+
}
|
|
1647
|
+
// If using developer token, return mock player info
|
|
1648
|
+
const authState = this.authManager.getAuthState();
|
|
1649
|
+
if (authState.tokenType === 'developer') {
|
|
1650
|
+
return {
|
|
1651
|
+
userId: 'developer',
|
|
1652
|
+
credits: 999999,
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
try {
|
|
1656
|
+
const response = await fetch(`${this.baseURL}${PLAYER_INFO_ENDPOINT}`, {
|
|
1657
|
+
method: 'GET',
|
|
1658
|
+
headers: {
|
|
1659
|
+
Authorization: `Bearer ${token}`,
|
|
1660
|
+
},
|
|
1661
|
+
});
|
|
1662
|
+
if (!response.ok) {
|
|
1663
|
+
const error = await response.json().catch(() => ({ message: 'Failed to get player info' }));
|
|
1664
|
+
throw new PlayKitError(error.message || 'Failed to get player info', error.code, response.status);
|
|
1665
|
+
}
|
|
1666
|
+
const data = await response.json();
|
|
1667
|
+
this.playerInfo = {
|
|
1668
|
+
userId: data.userId,
|
|
1669
|
+
credits: data.credits,
|
|
1670
|
+
};
|
|
1671
|
+
this.emit('player_info_updated', this.playerInfo);
|
|
1672
|
+
return this.playerInfo;
|
|
1673
|
+
}
|
|
1674
|
+
catch (error) {
|
|
1675
|
+
this.emit('error', error);
|
|
1676
|
+
throw error;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Get cached player info (without API call)
|
|
1681
|
+
*/
|
|
1682
|
+
getCachedPlayerInfo() {
|
|
1683
|
+
return this.playerInfo;
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* Refresh player info
|
|
1687
|
+
*/
|
|
1688
|
+
async refreshPlayerInfo() {
|
|
1689
|
+
return this.getPlayerInfo();
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Initialize recharge manager
|
|
1693
|
+
*/
|
|
1694
|
+
initializeRechargeManager() {
|
|
1695
|
+
const token = this.authManager.getToken();
|
|
1696
|
+
if (token && !this.rechargeManager) {
|
|
1697
|
+
this.rechargeManager = new RechargeManager(token, this.rechargeConfig.rechargePortalUrl);
|
|
1698
|
+
// Forward recharge events
|
|
1699
|
+
this.rechargeManager.on('recharge_opened', () => this.emit('recharge_opened'));
|
|
1700
|
+
this.rechargeManager.on('recharge_modal_shown', () => this.emit('recharge_modal_shown'));
|
|
1701
|
+
this.rechargeManager.on('recharge_modal_dismissed', () => this.emit('recharge_modal_dismissed'));
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Show insufficient balance modal
|
|
1706
|
+
*/
|
|
1707
|
+
async showInsufficientBalanceModal(customMessage) {
|
|
1708
|
+
var _a;
|
|
1709
|
+
this.initializeRechargeManager();
|
|
1710
|
+
if (!this.rechargeManager) {
|
|
1711
|
+
console.warn('RechargeManager not initialized. Cannot show modal.');
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const balance = (_a = this.playerInfo) === null || _a === void 0 ? void 0 : _a.credits;
|
|
1715
|
+
await this.rechargeManager.showInsufficientBalanceModal({
|
|
1716
|
+
currentBalance: balance,
|
|
1717
|
+
message: customMessage,
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Open recharge window in new tab
|
|
1722
|
+
*/
|
|
1723
|
+
openRechargeWindow() {
|
|
1724
|
+
this.initializeRechargeManager();
|
|
1725
|
+
if (!this.rechargeManager) {
|
|
1726
|
+
console.warn('RechargeManager not initialized. Cannot open recharge window.');
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
this.rechargeManager.openRechargeWindow();
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Enable automatic periodic balance checking
|
|
1733
|
+
*/
|
|
1734
|
+
enableAutoBalanceCheck(intervalMs) {
|
|
1735
|
+
var _a;
|
|
1736
|
+
const interval = (_a = intervalMs !== null && intervalMs !== void 0 ? intervalMs : this.rechargeConfig.balanceCheckInterval) !== null && _a !== void 0 ? _a : 30000;
|
|
1737
|
+
// Don't enable if interval is 0 or negative
|
|
1738
|
+
if (interval <= 0) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
// Clear existing interval if any
|
|
1742
|
+
this.disableAutoBalanceCheck();
|
|
1743
|
+
// Start periodic balance check
|
|
1744
|
+
this.balanceCheckInterval = setInterval(async () => {
|
|
1745
|
+
var _a, _b;
|
|
1746
|
+
try {
|
|
1747
|
+
const oldBalance = (_a = this.playerInfo) === null || _a === void 0 ? void 0 : _a.credits;
|
|
1748
|
+
await this.refreshPlayerInfo();
|
|
1749
|
+
const newBalance = (_b = this.playerInfo) === null || _b === void 0 ? void 0 : _b.credits;
|
|
1750
|
+
// Emit balance_updated event
|
|
1751
|
+
if (newBalance !== undefined) {
|
|
1752
|
+
this.emit('balance_updated', newBalance);
|
|
1753
|
+
// Check for low balance (less than 10 credits)
|
|
1754
|
+
if (newBalance < 10 && newBalance !== oldBalance) {
|
|
1755
|
+
this.emit('balance_low', newBalance);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
catch (error) {
|
|
1760
|
+
// Silently fail periodic checks to avoid spamming errors
|
|
1761
|
+
console.debug('Failed to check balance:', error);
|
|
1762
|
+
}
|
|
1763
|
+
}, interval);
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Disable automatic balance checking
|
|
1767
|
+
*/
|
|
1768
|
+
disableAutoBalanceCheck() {
|
|
1769
|
+
if (this.balanceCheckInterval) {
|
|
1770
|
+
clearInterval(this.balanceCheckInterval);
|
|
1771
|
+
this.balanceCheckInterval = null;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Check balance after API call (called internally by providers)
|
|
1776
|
+
*/
|
|
1777
|
+
async checkBalanceAfterApiCall() {
|
|
1778
|
+
if (!this.rechargeConfig.checkBalanceAfterApiCall) {
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
try {
|
|
1782
|
+
await this.refreshPlayerInfo();
|
|
1783
|
+
}
|
|
1784
|
+
catch (error) {
|
|
1785
|
+
// Silently fail to avoid disrupting the main flow
|
|
1786
|
+
console.debug('Failed to check balance after API call:', error);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Handle insufficient credits error (called by providers)
|
|
1791
|
+
*/
|
|
1792
|
+
async handleInsufficientCredits(error) {
|
|
1793
|
+
this.emit('insufficient_credits', error);
|
|
1794
|
+
// Auto-show modal if enabled
|
|
1795
|
+
if (this.rechargeConfig.autoShowBalanceModal) {
|
|
1796
|
+
await this.showInsufficientBalanceModal();
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Get recharge manager instance
|
|
1801
|
+
*/
|
|
1802
|
+
getRechargeManager() {
|
|
1803
|
+
this.initializeRechargeManager();
|
|
1804
|
+
return this.rechargeManager;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Clean up resources
|
|
1808
|
+
*/
|
|
1809
|
+
destroy() {
|
|
1810
|
+
this.disableAutoBalanceCheck();
|
|
1811
|
+
if (this.rechargeManager) {
|
|
1812
|
+
this.rechargeManager.destroy();
|
|
1813
|
+
this.rechargeManager = null;
|
|
1814
|
+
}
|
|
1815
|
+
this.removeAllListeners();
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
/**
|
|
1820
|
+
* Chat provider for HTTP communication with chat API
|
|
1821
|
+
*/
|
|
1822
|
+
const DEFAULT_BASE_URL$1 = 'https://playkit.agentlandlab.com';
|
|
1823
|
+
class ChatProvider {
|
|
1824
|
+
constructor(authManager, config) {
|
|
1825
|
+
this.authManager = authManager;
|
|
1826
|
+
this.config = config;
|
|
1827
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL$1;
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Set player client for balance checking
|
|
1831
|
+
*/
|
|
1832
|
+
setPlayerClient(playerClient) {
|
|
1833
|
+
this.playerClient = playerClient;
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Make a chat completion request (non-streaming)
|
|
1837
|
+
*/
|
|
1838
|
+
async chatCompletion(chatConfig) {
|
|
1839
|
+
var _a;
|
|
1840
|
+
const token = this.authManager.getToken();
|
|
1841
|
+
if (!token) {
|
|
1842
|
+
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
1843
|
+
}
|
|
1844
|
+
const model = chatConfig.model || this.config.defaultChatModel || 'gpt-4o-mini';
|
|
1845
|
+
const endpoint = `/ai/${this.config.gameId}/v1/chat`;
|
|
1846
|
+
const requestBody = {
|
|
1847
|
+
model,
|
|
1848
|
+
messages: chatConfig.messages,
|
|
1849
|
+
temperature: (_a = chatConfig.temperature) !== null && _a !== void 0 ? _a : 0.7,
|
|
1850
|
+
stream: false,
|
|
1851
|
+
max_tokens: chatConfig.maxTokens || null,
|
|
1852
|
+
seed: chatConfig.seed || null,
|
|
1853
|
+
stop: chatConfig.stop || null,
|
|
1854
|
+
top_p: chatConfig.topP || null,
|
|
1855
|
+
};
|
|
1856
|
+
try {
|
|
1857
|
+
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
1858
|
+
method: 'POST',
|
|
1859
|
+
headers: {
|
|
1860
|
+
Authorization: `Bearer ${token}`,
|
|
1861
|
+
'Content-Type': 'application/json',
|
|
1862
|
+
},
|
|
1863
|
+
body: JSON.stringify(requestBody),
|
|
1864
|
+
});
|
|
1865
|
+
if (!response.ok) {
|
|
1866
|
+
const error = await response.json().catch(() => ({ message: 'Chat request failed' }));
|
|
1867
|
+
const playKitError = new PlayKitError(error.message || 'Chat request failed', error.code, response.status);
|
|
1868
|
+
// Check for insufficient credits error
|
|
1869
|
+
if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
|
|
1870
|
+
if (this.playerClient) {
|
|
1871
|
+
await this.playerClient.handleInsufficientCredits(playKitError);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
throw playKitError;
|
|
1875
|
+
}
|
|
1876
|
+
const result = await response.json();
|
|
1877
|
+
// Check balance after successful API call
|
|
1878
|
+
if (this.playerClient) {
|
|
1879
|
+
this.playerClient.checkBalanceAfterApiCall().catch(() => {
|
|
1880
|
+
// Silently fail
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
return result;
|
|
1884
|
+
}
|
|
1885
|
+
catch (error) {
|
|
1886
|
+
if (error instanceof PlayKitError) {
|
|
1887
|
+
throw error;
|
|
1888
|
+
}
|
|
1889
|
+
throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_ERROR');
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Make a streaming chat completion request
|
|
1894
|
+
*/
|
|
1895
|
+
async chatCompletionStream(chatConfig) {
|
|
1896
|
+
var _a;
|
|
1897
|
+
const token = this.authManager.getToken();
|
|
1898
|
+
if (!token) {
|
|
1899
|
+
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
1900
|
+
}
|
|
1901
|
+
const model = chatConfig.model || this.config.defaultChatModel || 'gpt-4o-mini';
|
|
1902
|
+
const endpoint = `/ai/${this.config.gameId}/v1/chat`;
|
|
1903
|
+
const requestBody = {
|
|
1904
|
+
model,
|
|
1905
|
+
messages: chatConfig.messages,
|
|
1906
|
+
temperature: (_a = chatConfig.temperature) !== null && _a !== void 0 ? _a : 0.7,
|
|
1907
|
+
stream: true,
|
|
1908
|
+
max_tokens: chatConfig.maxTokens || null,
|
|
1909
|
+
seed: chatConfig.seed || null,
|
|
1910
|
+
stop: chatConfig.stop || null,
|
|
1911
|
+
top_p: chatConfig.topP || null,
|
|
1912
|
+
};
|
|
1913
|
+
try {
|
|
1914
|
+
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
1915
|
+
method: 'POST',
|
|
1916
|
+
headers: {
|
|
1917
|
+
Authorization: `Bearer ${token}`,
|
|
1918
|
+
'Content-Type': 'application/json',
|
|
1919
|
+
},
|
|
1920
|
+
body: JSON.stringify(requestBody),
|
|
1921
|
+
});
|
|
1922
|
+
if (!response.ok) {
|
|
1923
|
+
const error = await response.json().catch(() => ({ message: 'Chat stream request failed' }));
|
|
1924
|
+
const playKitError = new PlayKitError(error.message || 'Chat stream request failed', error.code, response.status);
|
|
1925
|
+
// Check for insufficient credits error
|
|
1926
|
+
if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
|
|
1927
|
+
if (this.playerClient) {
|
|
1928
|
+
await this.playerClient.handleInsufficientCredits(playKitError);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
throw playKitError;
|
|
1932
|
+
}
|
|
1933
|
+
if (!response.body) {
|
|
1934
|
+
throw new PlayKitError('Response body is null', 'NO_RESPONSE_BODY');
|
|
1935
|
+
}
|
|
1936
|
+
// Check balance after successful API call
|
|
1937
|
+
if (this.playerClient) {
|
|
1938
|
+
this.playerClient.checkBalanceAfterApiCall().catch(() => {
|
|
1939
|
+
// Silently fail
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
return response.body.getReader();
|
|
1943
|
+
}
|
|
1944
|
+
catch (error) {
|
|
1945
|
+
if (error instanceof PlayKitError) {
|
|
1946
|
+
throw error;
|
|
1947
|
+
}
|
|
1948
|
+
throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'CHAT_STREAM_ERROR');
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Generate structured output using JSON schema
|
|
1953
|
+
*/
|
|
1954
|
+
async generateStructured(schemaName, prompt, model, temperature) {
|
|
1955
|
+
var _a;
|
|
1956
|
+
const messages = [{ role: 'user', content: prompt }];
|
|
1957
|
+
const chatConfig = {
|
|
1958
|
+
messages,
|
|
1959
|
+
model: model || this.config.defaultChatModel,
|
|
1960
|
+
temperature: temperature !== null && temperature !== void 0 ? temperature : 0.7,
|
|
1961
|
+
};
|
|
1962
|
+
// Add schema information to the request
|
|
1963
|
+
// (Implementation depends on how the API handles structured output)
|
|
1964
|
+
const response = await this.chatCompletion(chatConfig);
|
|
1965
|
+
// Parse the response content as JSON
|
|
1966
|
+
const content = (_a = response.choices[0]) === null || _a === void 0 ? void 0 : _a.message.content;
|
|
1967
|
+
if (!content) {
|
|
1968
|
+
throw new PlayKitError('No content in response', 'NO_CONTENT');
|
|
1969
|
+
}
|
|
1970
|
+
try {
|
|
1971
|
+
return JSON.parse(content);
|
|
1972
|
+
}
|
|
1973
|
+
catch (error) {
|
|
1974
|
+
throw new PlayKitError('Failed to parse structured output', 'PARSE_ERROR');
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
/**
|
|
1980
|
+
* Image generation provider for HTTP communication with image API
|
|
1981
|
+
*/
|
|
1982
|
+
const DEFAULT_BASE_URL = 'https://playkit.agentlandlab.com';
|
|
1983
|
+
class ImageProvider {
|
|
1984
|
+
constructor(authManager, config) {
|
|
1985
|
+
this.authManager = authManager;
|
|
1986
|
+
this.config = config;
|
|
1987
|
+
this.baseURL = config.baseURL || DEFAULT_BASE_URL;
|
|
1988
|
+
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Set player client for balance checking
|
|
1991
|
+
*/
|
|
1992
|
+
setPlayerClient(playerClient) {
|
|
1993
|
+
this.playerClient = playerClient;
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Generate one or more images
|
|
1997
|
+
*/
|
|
1998
|
+
async generateImages(imageConfig) {
|
|
1999
|
+
const token = this.authManager.getToken();
|
|
2000
|
+
if (!token) {
|
|
2001
|
+
throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
|
|
2002
|
+
}
|
|
2003
|
+
const model = imageConfig.model || this.config.defaultImageModel || 'dall-e-3';
|
|
2004
|
+
const endpoint = `/ai/${this.config.gameId}/v1/image`;
|
|
2005
|
+
const requestBody = {
|
|
2006
|
+
model,
|
|
2007
|
+
prompt: imageConfig.prompt,
|
|
2008
|
+
n: imageConfig.n || 1,
|
|
2009
|
+
size: imageConfig.size || '1024x1024',
|
|
2010
|
+
seed: imageConfig.seed || null,
|
|
2011
|
+
};
|
|
2012
|
+
// Add optional quality and style if provided (for DALL-E models)
|
|
2013
|
+
if (imageConfig.quality) {
|
|
2014
|
+
requestBody.quality = imageConfig.quality;
|
|
2015
|
+
}
|
|
2016
|
+
if (imageConfig.style) {
|
|
2017
|
+
requestBody.style = imageConfig.style;
|
|
2018
|
+
}
|
|
2019
|
+
try {
|
|
2020
|
+
const response = await fetch(`${this.baseURL}${endpoint}`, {
|
|
2021
|
+
method: 'POST',
|
|
2022
|
+
headers: {
|
|
2023
|
+
Authorization: `Bearer ${token}`,
|
|
2024
|
+
'Content-Type': 'application/json',
|
|
2025
|
+
},
|
|
2026
|
+
body: JSON.stringify(requestBody),
|
|
2027
|
+
});
|
|
2028
|
+
if (!response.ok) {
|
|
2029
|
+
const error = await response.json().catch(() => ({ message: 'Image generation failed' }));
|
|
2030
|
+
const playKitError = new PlayKitError(error.message || 'Image generation failed', error.code, response.status);
|
|
2031
|
+
// Check for insufficient credits error
|
|
2032
|
+
if (error.code === 'INSUFFICIENT_CREDITS' || response.status === 402) {
|
|
2033
|
+
if (this.playerClient) {
|
|
2034
|
+
await this.playerClient.handleInsufficientCredits(playKitError);
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
throw playKitError;
|
|
2038
|
+
}
|
|
2039
|
+
const result = await response.json();
|
|
2040
|
+
// Check balance after successful API call
|
|
2041
|
+
if (this.playerClient) {
|
|
2042
|
+
this.playerClient.checkBalanceAfterApiCall().catch(() => {
|
|
2043
|
+
// Silently fail
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
return result;
|
|
2047
|
+
}
|
|
2048
|
+
catch (error) {
|
|
2049
|
+
if (error instanceof PlayKitError) {
|
|
2050
|
+
throw error;
|
|
2051
|
+
}
|
|
2052
|
+
throw new PlayKitError(error instanceof Error ? error.message : 'Unknown error', 'IMAGE_GENERATION_ERROR');
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
/******************************************************************************
|
|
2058
|
+
Copyright (c) Microsoft Corporation.
|
|
2059
|
+
|
|
2060
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
2061
|
+
purpose with or without fee is hereby granted.
|
|
2062
|
+
|
|
2063
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
2064
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
2065
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
2066
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
2067
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
2068
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
2069
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
2070
|
+
***************************************************************************** */
|
|
2071
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
function __values(o) {
|
|
2075
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
2076
|
+
if (m) return m.call(o);
|
|
2077
|
+
if (o && typeof o.length === "number") return {
|
|
2078
|
+
next: function () {
|
|
2079
|
+
if (o && i >= o.length) o = void 0;
|
|
2080
|
+
return { value: o && o[i++], done: !o };
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function __await(v) {
|
|
2087
|
+
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
function __asyncGenerator(thisArg, _arguments, generator) {
|
|
2091
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
2092
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
2093
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
2094
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
2095
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
2096
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
2097
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
2098
|
+
function fulfill(value) { resume("next", value); }
|
|
2099
|
+
function reject(value) { resume("throw", value); }
|
|
2100
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
function __asyncValues(o) {
|
|
2104
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
2105
|
+
var m = o[Symbol.asyncIterator], i;
|
|
2106
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
2107
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
2108
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
2112
|
+
var e = new Error(message);
|
|
2113
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
2114
|
+
};
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* Server-Sent Events (SSE) stream parser
|
|
2118
|
+
* Handles parsing of streaming text responses
|
|
2119
|
+
*/
|
|
2120
|
+
class StreamParser {
|
|
2121
|
+
/**
|
|
2122
|
+
* Parse SSE stream using ReadableStream
|
|
2123
|
+
*/
|
|
2124
|
+
static parseStream(reader) {
|
|
2125
|
+
return __asyncGenerator(this, arguments, function* parseStream_1() {
|
|
2126
|
+
const decoder = new TextDecoder();
|
|
2127
|
+
let buffer = '';
|
|
2128
|
+
try {
|
|
2129
|
+
while (true) {
|
|
2130
|
+
const { done, value } = yield __await(reader.read());
|
|
2131
|
+
if (done) {
|
|
2132
|
+
break;
|
|
2133
|
+
}
|
|
2134
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2135
|
+
// Split by newlines to process complete messages
|
|
2136
|
+
const lines = buffer.split('\n');
|
|
2137
|
+
// Keep the last incomplete line in buffer
|
|
2138
|
+
buffer = lines.pop() || '';
|
|
2139
|
+
for (const line of lines) {
|
|
2140
|
+
const trimmed = line.trim();
|
|
2141
|
+
if (!trimmed || trimmed.startsWith(':')) {
|
|
2142
|
+
// Empty line or comment, skip
|
|
2143
|
+
continue;
|
|
2144
|
+
}
|
|
2145
|
+
if (trimmed === 'data: [DONE]') {
|
|
2146
|
+
// End of stream
|
|
2147
|
+
return yield __await(void 0);
|
|
2148
|
+
}
|
|
2149
|
+
if (trimmed.startsWith('data: ')) {
|
|
2150
|
+
const data = trimmed.substring(6); // Remove 'data: ' prefix
|
|
2151
|
+
try {
|
|
2152
|
+
const parsed = JSON.parse(data);
|
|
2153
|
+
const text = this.extractTextFromChunk(parsed);
|
|
2154
|
+
if (text) {
|
|
2155
|
+
yield yield __await(text);
|
|
2156
|
+
}
|
|
2157
|
+
if (parsed.type === 'done' || parsed.finish_reason) {
|
|
2158
|
+
return yield __await(void 0);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
catch (error) {
|
|
2162
|
+
// If JSON parse fails, treat as plain text
|
|
2163
|
+
yield yield __await(data);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
finally {
|
|
2170
|
+
reader.releaseLock();
|
|
2171
|
+
}
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Extract text from a stream chunk
|
|
2176
|
+
* Supports multiple formats (UI Message Stream and OpenAI)
|
|
2177
|
+
*/
|
|
2178
|
+
static extractTextFromChunk(chunk) {
|
|
2179
|
+
var _a, _b;
|
|
2180
|
+
// UI Message Stream format: { type: "text-delta", delta: "..." }
|
|
2181
|
+
if (chunk.type === 'text-delta' && chunk.delta) {
|
|
2182
|
+
return chunk.delta;
|
|
2183
|
+
}
|
|
2184
|
+
// OpenAI format: { choices: [{ delta: { content: "..." } }] }
|
|
2185
|
+
if (chunk.choices && ((_b = (_a = chunk.choices[0]) === null || _a === void 0 ? void 0 : _a.delta) === null || _b === void 0 ? void 0 : _b.content)) {
|
|
2186
|
+
return chunk.choices[0].delta.content;
|
|
2187
|
+
}
|
|
2188
|
+
// Direct delta format
|
|
2189
|
+
if (chunk.delta) {
|
|
2190
|
+
return typeof chunk.delta === 'string' ? chunk.delta : chunk.delta.content || null;
|
|
2191
|
+
}
|
|
2192
|
+
return null;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Collect all chunks from a stream
|
|
2196
|
+
*/
|
|
2197
|
+
static async collectFullText(reader) {
|
|
2198
|
+
var _a, e_1, _b, _c;
|
|
2199
|
+
let fullText = '';
|
|
2200
|
+
try {
|
|
2201
|
+
for (var _d = true, _e = __asyncValues(this.parseStream(reader)), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
|
|
2202
|
+
_c = _f.value;
|
|
2203
|
+
_d = false;
|
|
2204
|
+
const chunk = _c;
|
|
2205
|
+
fullText += chunk;
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2208
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
2209
|
+
finally {
|
|
2210
|
+
try {
|
|
2211
|
+
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
|
|
2212
|
+
}
|
|
2213
|
+
finally { if (e_1) throw e_1.error; }
|
|
2214
|
+
}
|
|
2215
|
+
return fullText;
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Stream with callbacks
|
|
2219
|
+
*/
|
|
2220
|
+
static async streamWithCallbacks(reader, onChunk, onComplete, onError) {
|
|
2221
|
+
var _a, e_2, _b, _c;
|
|
2222
|
+
let fullText = '';
|
|
2223
|
+
try {
|
|
2224
|
+
try {
|
|
2225
|
+
for (var _d = true, _e = __asyncValues(this.parseStream(reader)), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
|
|
2226
|
+
_c = _f.value;
|
|
2227
|
+
_d = false;
|
|
2228
|
+
const chunk = _c;
|
|
2229
|
+
fullText += chunk;
|
|
2230
|
+
onChunk(chunk);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
2234
|
+
finally {
|
|
2235
|
+
try {
|
|
2236
|
+
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
|
|
2237
|
+
}
|
|
2238
|
+
finally { if (e_2) throw e_2.error; }
|
|
2239
|
+
}
|
|
2240
|
+
if (onComplete) {
|
|
2241
|
+
onComplete(fullText);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
catch (error) {
|
|
2245
|
+
if (onError && error instanceof Error) {
|
|
2246
|
+
onError(error);
|
|
2247
|
+
}
|
|
2248
|
+
else {
|
|
2249
|
+
throw error;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
/**
|
|
2256
|
+
* Chat client for AI text generation
|
|
2257
|
+
*/
|
|
2258
|
+
class ChatClient {
|
|
2259
|
+
constructor(provider, model) {
|
|
2260
|
+
this.provider = provider;
|
|
2261
|
+
this.model = model || 'gpt-4o-mini';
|
|
2262
|
+
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Generate text (non-streaming)
|
|
2265
|
+
*/
|
|
2266
|
+
async textGeneration(config) {
|
|
2267
|
+
const chatConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model });
|
|
2268
|
+
const response = await this.provider.chatCompletion(chatConfig);
|
|
2269
|
+
const choice = response.choices[0];
|
|
2270
|
+
if (!choice) {
|
|
2271
|
+
throw new Error('No choices in response');
|
|
2272
|
+
}
|
|
2273
|
+
return {
|
|
2274
|
+
content: choice.message.content,
|
|
2275
|
+
model: response.model,
|
|
2276
|
+
finishReason: choice.finish_reason,
|
|
2277
|
+
usage: response.usage
|
|
2278
|
+
? {
|
|
2279
|
+
promptTokens: response.usage.prompt_tokens,
|
|
2280
|
+
completionTokens: response.usage.completion_tokens,
|
|
2281
|
+
totalTokens: response.usage.total_tokens,
|
|
2282
|
+
}
|
|
2283
|
+
: undefined,
|
|
2284
|
+
id: response.id,
|
|
2285
|
+
created: response.created,
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
/**
|
|
2289
|
+
* Generate text with streaming
|
|
2290
|
+
*/
|
|
2291
|
+
async textGenerationStream(config) {
|
|
2292
|
+
const chatConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model });
|
|
2293
|
+
const reader = await this.provider.chatCompletionStream(chatConfig);
|
|
2294
|
+
await StreamParser.streamWithCallbacks(reader, config.onChunk, config.onComplete, config.onError);
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Generate structured output using a JSON schema
|
|
2298
|
+
*/
|
|
2299
|
+
async generateStructured(config) {
|
|
2300
|
+
const model = config.model || this.model;
|
|
2301
|
+
return await this.provider.generateStructured(config.schemaName, config.prompt, model, config.temperature);
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Simple chat with single message
|
|
2305
|
+
*/
|
|
2306
|
+
async chat(message, systemPrompt) {
|
|
2307
|
+
const messages = [];
|
|
2308
|
+
if (systemPrompt) {
|
|
2309
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
2310
|
+
}
|
|
2311
|
+
messages.push({ role: 'user', content: message });
|
|
2312
|
+
const result = await this.textGeneration({ messages });
|
|
2313
|
+
return result.content;
|
|
2314
|
+
}
|
|
2315
|
+
/**
|
|
2316
|
+
* Simple chat with streaming
|
|
2317
|
+
*/
|
|
2318
|
+
async chatStream(message, onChunk, onComplete, systemPrompt) {
|
|
2319
|
+
const messages = [];
|
|
2320
|
+
if (systemPrompt) {
|
|
2321
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
2322
|
+
}
|
|
2323
|
+
messages.push({ role: 'user', content: message });
|
|
2324
|
+
await this.textGenerationStream({
|
|
2325
|
+
messages,
|
|
2326
|
+
onChunk,
|
|
2327
|
+
onComplete,
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
/**
|
|
2333
|
+
* Image generation client
|
|
2334
|
+
*/
|
|
2335
|
+
/**
|
|
2336
|
+
* Implementation of GeneratedImage interface
|
|
2337
|
+
*/
|
|
2338
|
+
class GeneratedImageImpl {
|
|
2339
|
+
constructor(base64, originalPrompt, revisedPrompt, size) {
|
|
2340
|
+
this.base64 = base64;
|
|
2341
|
+
this.originalPrompt = originalPrompt;
|
|
2342
|
+
this.revisedPrompt = revisedPrompt;
|
|
2343
|
+
this.generatedAt = Date.now();
|
|
2344
|
+
this.size = size;
|
|
2345
|
+
}
|
|
2346
|
+
toDataURL() {
|
|
2347
|
+
return `data:image/png;base64,${this.base64}`;
|
|
2348
|
+
}
|
|
2349
|
+
async toHTMLImage() {
|
|
2350
|
+
return new Promise((resolve, reject) => {
|
|
2351
|
+
const img = new Image();
|
|
2352
|
+
img.onload = () => resolve(img);
|
|
2353
|
+
img.onerror = (e) => reject(new Error('Failed to load image'));
|
|
2354
|
+
img.src = this.toDataURL();
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
class ImageClient {
|
|
2359
|
+
constructor(provider, model) {
|
|
2360
|
+
this.provider = provider;
|
|
2361
|
+
this.model = model || 'dall-e-3';
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Generate a single image
|
|
2365
|
+
*/
|
|
2366
|
+
async generateImage(config) {
|
|
2367
|
+
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: 1 });
|
|
2368
|
+
const response = await this.provider.generateImages(imageConfig);
|
|
2369
|
+
const imageData = response.data[0];
|
|
2370
|
+
if (!imageData || !imageData.b64_json) {
|
|
2371
|
+
throw new Error('No image data in response');
|
|
2372
|
+
}
|
|
2373
|
+
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size);
|
|
2374
|
+
}
|
|
2375
|
+
/**
|
|
2376
|
+
* Generate multiple images
|
|
2377
|
+
*/
|
|
2378
|
+
async generateImages(config) {
|
|
2379
|
+
const imageConfig = Object.assign(Object.assign({}, config), { model: config.model || this.model, n: config.n || 1 });
|
|
2380
|
+
const response = await this.provider.generateImages(imageConfig);
|
|
2381
|
+
return response.data.map((imageData) => {
|
|
2382
|
+
if (!imageData.b64_json) {
|
|
2383
|
+
throw new Error('No image data in response');
|
|
2384
|
+
}
|
|
2385
|
+
return new GeneratedImageImpl(imageData.b64_json, config.prompt, imageData.revised_prompt, config.size);
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Simple image generation with just a prompt
|
|
2390
|
+
*/
|
|
2391
|
+
async generate(prompt, size) {
|
|
2392
|
+
return this.generateImage({ prompt, size });
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
/**
|
|
2397
|
+
* NPC Client for simplified conversation management
|
|
2398
|
+
* Automatically handles conversation history
|
|
2399
|
+
*/
|
|
2400
|
+
class NPCClient extends EventEmitter {
|
|
2401
|
+
constructor(chatClient, config) {
|
|
2402
|
+
var _a;
|
|
2403
|
+
super();
|
|
2404
|
+
this.chatClient = chatClient;
|
|
2405
|
+
this.systemPrompt = (config === null || config === void 0 ? void 0 : config.systemPrompt) || 'You are a helpful assistant.';
|
|
2406
|
+
this.temperature = (_a = config === null || config === void 0 ? void 0 : config.temperature) !== null && _a !== void 0 ? _a : 0.7;
|
|
2407
|
+
this.maxHistoryLength = (config === null || config === void 0 ? void 0 : config.maxHistoryLength) || 50;
|
|
2408
|
+
this.history = [];
|
|
2409
|
+
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Set the system prompt (NPC personality)
|
|
2412
|
+
*/
|
|
2413
|
+
setSystemPrompt(prompt) {
|
|
2414
|
+
this.systemPrompt = prompt;
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* Get the current system prompt
|
|
2418
|
+
*/
|
|
2419
|
+
getSystemPrompt() {
|
|
2420
|
+
return this.systemPrompt;
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Talk to the NPC (non-streaming)
|
|
2424
|
+
*/
|
|
2425
|
+
async talk(message) {
|
|
2426
|
+
// Add user message to history
|
|
2427
|
+
const userMessage = { role: 'user', content: message };
|
|
2428
|
+
this.history.push(userMessage);
|
|
2429
|
+
// Build messages array with system prompt
|
|
2430
|
+
const messages = [
|
|
2431
|
+
{ role: 'system', content: this.systemPrompt },
|
|
2432
|
+
...this.history,
|
|
2433
|
+
];
|
|
2434
|
+
// Generate response
|
|
2435
|
+
const result = await this.chatClient.textGeneration({
|
|
2436
|
+
messages,
|
|
2437
|
+
temperature: this.temperature,
|
|
2438
|
+
});
|
|
2439
|
+
// Add assistant response to history
|
|
2440
|
+
const assistantMessage = { role: 'assistant', content: result.content };
|
|
2441
|
+
this.history.push(assistantMessage);
|
|
2442
|
+
// Trim history if needed
|
|
2443
|
+
this.trimHistory();
|
|
2444
|
+
this.emit('response', result.content);
|
|
2445
|
+
return result.content;
|
|
2446
|
+
}
|
|
2447
|
+
/**
|
|
2448
|
+
* Talk to the NPC with streaming
|
|
2449
|
+
*/
|
|
2450
|
+
async talkStream(message, onChunk, onComplete) {
|
|
2451
|
+
// Add user message to history
|
|
2452
|
+
const userMessage = { role: 'user', content: message };
|
|
2453
|
+
this.history.push(userMessage);
|
|
2454
|
+
// Build messages array with system prompt
|
|
2455
|
+
const messages = [
|
|
2456
|
+
{ role: 'system', content: this.systemPrompt },
|
|
2457
|
+
...this.history,
|
|
2458
|
+
];
|
|
2459
|
+
// Generate response
|
|
2460
|
+
await this.chatClient.textGenerationStream({
|
|
2461
|
+
messages,
|
|
2462
|
+
temperature: this.temperature,
|
|
2463
|
+
onChunk,
|
|
2464
|
+
onComplete: (fullText) => {
|
|
2465
|
+
// Add assistant response to history
|
|
2466
|
+
const assistantMessage = { role: 'assistant', content: fullText };
|
|
2467
|
+
this.history.push(assistantMessage);
|
|
2468
|
+
// Trim history if needed
|
|
2469
|
+
this.trimHistory();
|
|
2470
|
+
this.emit('response', fullText);
|
|
2471
|
+
if (onComplete) {
|
|
2472
|
+
onComplete(fullText);
|
|
2473
|
+
}
|
|
2474
|
+
},
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Talk with structured output
|
|
2479
|
+
*/
|
|
2480
|
+
async talkStructured(message, schemaName) {
|
|
2481
|
+
// Add user message to history
|
|
2482
|
+
const userMessage = { role: 'user', content: message };
|
|
2483
|
+
this.history.push(userMessage);
|
|
2484
|
+
// Generate structured response
|
|
2485
|
+
const result = await this.chatClient.generateStructured({
|
|
2486
|
+
schemaName,
|
|
2487
|
+
prompt: message,
|
|
2488
|
+
messages: [{ role: 'system', content: this.systemPrompt }, ...this.history],
|
|
2489
|
+
temperature: this.temperature,
|
|
2490
|
+
});
|
|
2491
|
+
// Add a text representation to history
|
|
2492
|
+
const assistantMessage = {
|
|
2493
|
+
role: 'assistant',
|
|
2494
|
+
content: JSON.stringify(result),
|
|
2495
|
+
};
|
|
2496
|
+
this.history.push(assistantMessage);
|
|
2497
|
+
this.trimHistory();
|
|
2498
|
+
return result;
|
|
2499
|
+
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Get conversation history
|
|
2502
|
+
*/
|
|
2503
|
+
getHistory() {
|
|
2504
|
+
return [...this.history];
|
|
2505
|
+
}
|
|
2506
|
+
/**
|
|
2507
|
+
* Clear conversation history
|
|
2508
|
+
*/
|
|
2509
|
+
clearHistory() {
|
|
2510
|
+
this.history = [];
|
|
2511
|
+
this.emit('history_cleared');
|
|
2512
|
+
}
|
|
2513
|
+
/**
|
|
2514
|
+
* Save history to JSON string
|
|
2515
|
+
*/
|
|
2516
|
+
saveHistory() {
|
|
2517
|
+
return JSON.stringify({
|
|
2518
|
+
systemPrompt: this.systemPrompt,
|
|
2519
|
+
history: this.history,
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
/**
|
|
2523
|
+
* Load history from JSON string
|
|
2524
|
+
*/
|
|
2525
|
+
loadHistory(saveData) {
|
|
2526
|
+
try {
|
|
2527
|
+
const data = JSON.parse(saveData);
|
|
2528
|
+
this.systemPrompt = data.systemPrompt || this.systemPrompt;
|
|
2529
|
+
this.history = data.history || [];
|
|
2530
|
+
this.emit('history_loaded');
|
|
2531
|
+
return true;
|
|
2532
|
+
}
|
|
2533
|
+
catch (error) {
|
|
2534
|
+
console.error('Failed to load history', error);
|
|
2535
|
+
return false;
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Revert to a specific point in history
|
|
2540
|
+
*/
|
|
2541
|
+
revertToMessage(index) {
|
|
2542
|
+
if (index >= 0 && index < this.history.length) {
|
|
2543
|
+
this.history = this.history.slice(0, index + 1);
|
|
2544
|
+
this.emit('history_reverted', index);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
/**
|
|
2548
|
+
* Append a message to history manually
|
|
2549
|
+
*/
|
|
2550
|
+
appendMessage(message) {
|
|
2551
|
+
this.history.push(message);
|
|
2552
|
+
this.trimHistory();
|
|
2553
|
+
}
|
|
2554
|
+
/**
|
|
2555
|
+
* Trim history to max length
|
|
2556
|
+
*/
|
|
2557
|
+
trimHistory() {
|
|
2558
|
+
if (this.history.length > this.maxHistoryLength) {
|
|
2559
|
+
// Keep the most recent messages
|
|
2560
|
+
this.history = this.history.slice(-this.maxHistoryLength);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Get the number of messages in history
|
|
2565
|
+
*/
|
|
2566
|
+
getHistoryLength() {
|
|
2567
|
+
return this.history.length;
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2571
|
+
/**
|
|
2572
|
+
* Main SDK class - Entry point for PlayKit SDK
|
|
2573
|
+
*/
|
|
2574
|
+
class PlayKitSDK extends EventEmitter {
|
|
2575
|
+
constructor(config) {
|
|
2576
|
+
super();
|
|
2577
|
+
this.initialized = false;
|
|
2578
|
+
this.devTokenIndicator = null;
|
|
2579
|
+
this.config = Object.assign({ defaultChatModel: 'gpt-4o-mini', defaultImageModel: 'dall-e-3', debug: false }, config);
|
|
2580
|
+
// Initialize managers and providers
|
|
2581
|
+
this.authManager = new AuthManager(this.config);
|
|
2582
|
+
this.playerClient = new PlayerClient(this.authManager, this.config, this.config.recharge);
|
|
2583
|
+
this.chatProvider = new ChatProvider(this.authManager, this.config);
|
|
2584
|
+
this.imageProvider = new ImageProvider(this.authManager, this.config);
|
|
2585
|
+
// Connect providers to player client for balance checking
|
|
2586
|
+
this.chatProvider.setPlayerClient(this.playerClient);
|
|
2587
|
+
this.imageProvider.setPlayerClient(this.playerClient);
|
|
2588
|
+
// Forward authentication events
|
|
2589
|
+
this.authManager.on('authenticated', (authState) => {
|
|
2590
|
+
this.emit('authenticated', authState);
|
|
2591
|
+
if (this.config.debug) {
|
|
2592
|
+
console.log('[PlayKitSDK] Authenticated', authState);
|
|
2593
|
+
}
|
|
2594
|
+
});
|
|
2595
|
+
this.authManager.on('unauthenticated', () => {
|
|
2596
|
+
this.emit('unauthenticated');
|
|
2597
|
+
if (this.config.debug) {
|
|
2598
|
+
console.log('[PlayKitSDK] Not authenticated');
|
|
2599
|
+
}
|
|
2600
|
+
});
|
|
2601
|
+
this.authManager.on('error', (error) => {
|
|
2602
|
+
this.emit('error', error);
|
|
2603
|
+
if (this.config.debug) {
|
|
2604
|
+
console.error('[PlayKitSDK] Auth error', error);
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
// Forward recharge events
|
|
2608
|
+
this.playerClient.on('recharge_opened', () => this.emit('recharge_opened'));
|
|
2609
|
+
this.playerClient.on('recharge_modal_shown', () => this.emit('recharge_modal_shown'));
|
|
2610
|
+
this.playerClient.on('recharge_modal_dismissed', () => this.emit('recharge_modal_dismissed'));
|
|
2611
|
+
this.playerClient.on('insufficient_credits', (error) => this.emit('insufficient_credits', error));
|
|
2612
|
+
this.playerClient.on('balance_low', (credits) => this.emit('balance_low', credits));
|
|
2613
|
+
this.playerClient.on('balance_updated', (credits) => this.emit('balance_updated', credits));
|
|
2614
|
+
this.playerClient.on('player_info_updated', (info) => this.emit('player_info_updated', info));
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Initialize the SDK
|
|
2618
|
+
* Must be called before using any features
|
|
2619
|
+
*/
|
|
2620
|
+
async initialize() {
|
|
2621
|
+
if (this.initialized) {
|
|
2622
|
+
if (this.config.debug) {
|
|
2623
|
+
console.warn('[PlayKitSDK] Already initialized');
|
|
2624
|
+
}
|
|
2625
|
+
return;
|
|
2626
|
+
}
|
|
2627
|
+
try {
|
|
2628
|
+
await this.authManager.initialize();
|
|
2629
|
+
this.initialized = true;
|
|
2630
|
+
// Show developer token indicator if using developer token
|
|
2631
|
+
if (this.config.developerToken && typeof window !== 'undefined') {
|
|
2632
|
+
this.showDeveloperTokenIndicator();
|
|
2633
|
+
}
|
|
2634
|
+
this.emit('ready');
|
|
2635
|
+
if (this.config.debug) {
|
|
2636
|
+
console.log('[PlayKitSDK] Initialized successfully');
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
catch (error) {
|
|
2640
|
+
this.emit('error', error);
|
|
2641
|
+
throw error;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Show developer token indicator in top-left corner
|
|
2646
|
+
*/
|
|
2647
|
+
showDeveloperTokenIndicator() {
|
|
2648
|
+
if (this.devTokenIndicator) {
|
|
2649
|
+
return; // Already shown
|
|
2650
|
+
}
|
|
2651
|
+
// Create indicator element
|
|
2652
|
+
this.devTokenIndicator = document.createElement('div');
|
|
2653
|
+
this.devTokenIndicator.textContent = 'DeveloperToken';
|
|
2654
|
+
this.devTokenIndicator.style.cssText = `
|
|
2655
|
+
position: fixed;
|
|
2656
|
+
top: 10px;
|
|
2657
|
+
left: 10px;
|
|
2658
|
+
background-color: #dc2626;
|
|
2659
|
+
color: white;
|
|
2660
|
+
padding: 4px 12px;
|
|
2661
|
+
border-radius: 4px;
|
|
2662
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
2663
|
+
font-size: 12px;
|
|
2664
|
+
font-weight: 600;
|
|
2665
|
+
z-index: 999999;
|
|
2666
|
+
pointer-events: none;
|
|
2667
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
2668
|
+
`;
|
|
2669
|
+
document.body.appendChild(this.devTokenIndicator);
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Hide developer token indicator
|
|
2673
|
+
*/
|
|
2674
|
+
hideDeveloperTokenIndicator() {
|
|
2675
|
+
if (this.devTokenIndicator) {
|
|
2676
|
+
this.devTokenIndicator.remove();
|
|
2677
|
+
this.devTokenIndicator = null;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Check if SDK is initialized
|
|
2682
|
+
*/
|
|
2683
|
+
isInitialized() {
|
|
2684
|
+
return this.initialized;
|
|
2685
|
+
}
|
|
2686
|
+
/**
|
|
2687
|
+
* Check if authenticated
|
|
2688
|
+
*/
|
|
2689
|
+
isAuthenticated() {
|
|
2690
|
+
return this.authManager.isAuthenticated();
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Exchange JWT for player token
|
|
2694
|
+
*/
|
|
2695
|
+
async login(jwt) {
|
|
2696
|
+
return await this.authManager.exchangeJWT(jwt);
|
|
2697
|
+
}
|
|
2698
|
+
/**
|
|
2699
|
+
* Logout
|
|
2700
|
+
*/
|
|
2701
|
+
async logout() {
|
|
2702
|
+
await this.authManager.logout();
|
|
2703
|
+
this.hideDeveloperTokenIndicator();
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Get player information
|
|
2707
|
+
*/
|
|
2708
|
+
async getPlayerInfo() {
|
|
2709
|
+
return await this.playerClient.getPlayerInfo();
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Create a chat client
|
|
2713
|
+
*/
|
|
2714
|
+
createChatClient(model) {
|
|
2715
|
+
return new ChatClient(this.chatProvider, model || this.config.defaultChatModel);
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Create an image client
|
|
2719
|
+
*/
|
|
2720
|
+
createImageClient(model) {
|
|
2721
|
+
return new ImageClient(this.imageProvider, model || this.config.defaultImageModel);
|
|
2722
|
+
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Create an NPC client
|
|
2725
|
+
*/
|
|
2726
|
+
createNPCClient(config) {
|
|
2727
|
+
const chatClient = this.createChatClient(config === null || config === void 0 ? void 0 : config.model);
|
|
2728
|
+
return new NPCClient(chatClient, config);
|
|
2729
|
+
}
|
|
2730
|
+
/**
|
|
2731
|
+
* Get authentication manager (advanced usage)
|
|
2732
|
+
*/
|
|
2733
|
+
getAuthManager() {
|
|
2734
|
+
return this.authManager;
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* Get player client (advanced usage)
|
|
2738
|
+
*/
|
|
2739
|
+
getPlayerClient() {
|
|
2740
|
+
return this.playerClient;
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Enable or disable debug mode
|
|
2744
|
+
*/
|
|
2745
|
+
setDebug(enabled) {
|
|
2746
|
+
this.config.debug = enabled;
|
|
2747
|
+
}
|
|
2748
|
+
/**
|
|
2749
|
+
* Show insufficient balance modal
|
|
2750
|
+
*/
|
|
2751
|
+
async showInsufficientBalanceModal(customMessage) {
|
|
2752
|
+
return await this.playerClient.showInsufficientBalanceModal(customMessage);
|
|
2753
|
+
}
|
|
2754
|
+
/**
|
|
2755
|
+
* Open recharge window in new tab
|
|
2756
|
+
*/
|
|
2757
|
+
openRechargeWindow() {
|
|
2758
|
+
this.playerClient.openRechargeWindow();
|
|
2759
|
+
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Enable automatic periodic balance checking
|
|
2762
|
+
* @param intervalMs - Check interval in milliseconds (default: 30000)
|
|
2763
|
+
*/
|
|
2764
|
+
enableAutoBalanceCheck(intervalMs) {
|
|
2765
|
+
this.playerClient.enableAutoBalanceCheck(intervalMs);
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2768
|
+
* Disable automatic balance checking
|
|
2769
|
+
*/
|
|
2770
|
+
disableAutoBalanceCheck() {
|
|
2771
|
+
this.playerClient.disableAutoBalanceCheck();
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Get player's current cached balance
|
|
2775
|
+
*/
|
|
2776
|
+
getCachedBalance() {
|
|
2777
|
+
var _a;
|
|
2778
|
+
const playerInfo = this.playerClient.getCachedPlayerInfo();
|
|
2779
|
+
return (_a = playerInfo === null || playerInfo === void 0 ? void 0 : playerInfo.credits) !== null && _a !== void 0 ? _a : null;
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Refresh and get player's current balance
|
|
2783
|
+
*/
|
|
2784
|
+
async refreshBalance() {
|
|
2785
|
+
const playerInfo = await this.playerClient.refreshPlayerInfo();
|
|
2786
|
+
return playerInfo.credits;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
exports.AuthManager = AuthManager;
|
|
2791
|
+
exports.ChatClient = ChatClient;
|
|
2792
|
+
exports.ImageClient = ImageClient;
|
|
2793
|
+
exports.NPCClient = NPCClient;
|
|
2794
|
+
exports.PlayKitSDK = PlayKitSDK;
|
|
2795
|
+
exports.PlayerClient = PlayerClient;
|
|
2796
|
+
exports.RechargeManager = RechargeManager;
|
|
2797
|
+
exports.StreamParser = StreamParser;
|
|
2798
|
+
exports.TokenStorage = TokenStorage;
|
|
2799
|
+
exports.default = PlayKitSDK;
|
|
2800
|
+
//# sourceMappingURL=index.cjs.js.map
|