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.
@@ -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