playkit-sdk 1.2.8-beta.1 → 1.2.8-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * playkit-sdk v1.2.8-beta.1
2
+ * playkit-sdk v1.2.8-beta.3
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
@@ -107,33 +107,607 @@ function npcActionToTool(action) {
107
107
  };
108
108
  }
109
109
 
110
+ /**
111
+ * PlayKit SDK Logger Module
112
+ *
113
+ * Provides a configurable logging system that allows:
114
+ * - Controlling whether logs are sent to console
115
+ * - Configuring where logs are sent
116
+ * - Allowing external packages to hook into PlayKit logs
117
+ * - Ensuring PlayKit output doesn't go directly to console
118
+ */
119
+ /**
120
+ * Log level enumeration
121
+ * Lower values = higher priority
122
+ */
123
+ exports.LogLevel = void 0;
124
+ (function (LogLevel) {
125
+ /** Disable all logging */
126
+ LogLevel[LogLevel["NONE"] = 0] = "NONE";
127
+ /** Error level - only errors */
128
+ LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
129
+ /** Warn level - warnings and errors */
130
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
131
+ /** Info level - info, warnings, and errors */
132
+ LogLevel[LogLevel["INFO"] = 3] = "INFO";
133
+ /** Debug level - all logs */
134
+ LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
135
+ })(exports.LogLevel || (exports.LogLevel = {}));
136
+ /**
137
+ * PlayKit Logger
138
+ *
139
+ * Features:
140
+ * - Multiple log levels (debug, info, warn, error)
141
+ * - Configurable log output destinations
142
+ * - Allows external code to register handlers to receive logs
143
+ * - Can completely disable console output
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * import { Logger, LogLevel } from 'playkit-sdk';
148
+ *
149
+ * // Get a logger for your module
150
+ * const logger = Logger.getLogger('MyModule');
151
+ *
152
+ * // Log messages
153
+ * logger.debug('Debug message');
154
+ * logger.info('Info message');
155
+ * logger.warn('Warning message');
156
+ * logger.error('Error message', error);
157
+ * ```
158
+ */
159
+ class Logger {
160
+ constructor(source) {
161
+ this.source = source;
162
+ }
163
+ // ===== Static configuration methods =====
164
+ /**
165
+ * Get or create a Logger instance for the specified source
166
+ * @param source The source/module identifier
167
+ * @returns Logger instance
168
+ */
169
+ static getLogger(source) {
170
+ if (!Logger.instances.has(source)) {
171
+ Logger.instances.set(source, new Logger(source));
172
+ }
173
+ return Logger.instances.get(source);
174
+ }
175
+ /**
176
+ * Set the global log level
177
+ * @param level The log level to set
178
+ */
179
+ static setGlobalLevel(level) {
180
+ Logger.globalLevel = level;
181
+ }
182
+ /**
183
+ * Get the current global log level
184
+ * @returns The current global log level
185
+ */
186
+ static getGlobalLevel() {
187
+ return Logger.globalLevel;
188
+ }
189
+ /**
190
+ * Enable or disable console output
191
+ * @param enabled Whether to enable console output
192
+ */
193
+ static setConsoleEnabled(enabled) {
194
+ Logger.consoleEnabled = enabled;
195
+ }
196
+ /**
197
+ * Check if console output is enabled
198
+ * @returns Whether console output is enabled
199
+ */
200
+ static isConsoleEnabled() {
201
+ return Logger.consoleEnabled;
202
+ }
203
+ /**
204
+ * Add a log handler
205
+ * @param handler The handler to add
206
+ */
207
+ static addHandler(handler) {
208
+ if (!Logger.handlers.includes(handler)) {
209
+ Logger.handlers.push(handler);
210
+ }
211
+ }
212
+ /**
213
+ * Remove a log handler
214
+ * @param handler The handler to remove
215
+ * @returns Whether the handler was found and removed
216
+ */
217
+ static removeHandler(handler) {
218
+ const index = Logger.handlers.indexOf(handler);
219
+ if (index !== -1) {
220
+ Logger.handlers.splice(index, 1);
221
+ return true;
222
+ }
223
+ return false;
224
+ }
225
+ /**
226
+ * Clear all log handlers
227
+ */
228
+ static clearHandlers() {
229
+ Logger.handlers = [];
230
+ }
231
+ /**
232
+ * Get all registered handlers
233
+ * @returns Array of registered handlers
234
+ */
235
+ static getHandlers() {
236
+ return Logger.handlers;
237
+ }
238
+ /**
239
+ * Configure the logging system (convenience method)
240
+ * @param config Log configuration
241
+ */
242
+ static configure(config) {
243
+ if (config.level !== undefined) {
244
+ Logger.setGlobalLevel(config.level);
245
+ }
246
+ if (config.consoleEnabled !== undefined) {
247
+ Logger.setConsoleEnabled(config.consoleEnabled);
248
+ }
249
+ if (config.handlers) {
250
+ Logger.clearHandlers();
251
+ config.handlers.forEach((h) => Logger.addHandler(h));
252
+ }
253
+ }
254
+ /**
255
+ * Reset the logger to default state
256
+ * Useful for testing
257
+ */
258
+ static reset() {
259
+ Logger.globalLevel = exports.LogLevel.WARN;
260
+ Logger.consoleEnabled = true;
261
+ Logger.handlers = [];
262
+ Logger.instances.clear();
263
+ }
264
+ // ===== Instance methods =====
265
+ /**
266
+ * Set the log level for this Logger instance
267
+ * Set to undefined to use the global level
268
+ * @param level The log level or undefined
269
+ */
270
+ setLevel(level) {
271
+ this.localLevel = level;
272
+ }
273
+ /**
274
+ * Get the effective log level for this Logger
275
+ * @returns The effective log level
276
+ */
277
+ getEffectiveLevel() {
278
+ var _a;
279
+ return (_a = this.localLevel) !== null && _a !== void 0 ? _a : Logger.globalLevel;
280
+ }
281
+ /**
282
+ * Log a debug message
283
+ * @param message The message to log
284
+ * @param data Additional data to log
285
+ */
286
+ debug(message, ...data) {
287
+ this.log(exports.LogLevel.DEBUG, 'debug', message, data);
288
+ }
289
+ /**
290
+ * Log an info message
291
+ * @param message The message to log
292
+ * @param data Additional data to log
293
+ */
294
+ info(message, ...data) {
295
+ this.log(exports.LogLevel.INFO, 'info', message, data);
296
+ }
297
+ /**
298
+ * Log a warning message
299
+ * @param message The message to log
300
+ * @param data Additional data to log
301
+ */
302
+ warn(message, ...data) {
303
+ this.log(exports.LogLevel.WARN, 'warn', message, data);
304
+ }
305
+ /**
306
+ * Log an error message
307
+ * @param message The message to log
308
+ * @param data Additional data to log
309
+ */
310
+ error(message, ...data) {
311
+ this.log(exports.LogLevel.ERROR, 'error', message, data);
312
+ }
313
+ /**
314
+ * Internal log method
315
+ */
316
+ log(level, levelName, message, data) {
317
+ const effectiveLevel = this.getEffectiveLevel();
318
+ // Check log level
319
+ if (level > effectiveLevel) {
320
+ return;
321
+ }
322
+ const entry = {
323
+ level,
324
+ levelName,
325
+ source: this.source,
326
+ message,
327
+ data: data.length > 0 ? data : undefined,
328
+ timestamp: new Date(),
329
+ };
330
+ // Output to console
331
+ if (Logger.consoleEnabled) {
332
+ this.logToConsole(entry);
333
+ }
334
+ // Call all handlers
335
+ for (const handler of Logger.handlers) {
336
+ try {
337
+ handler.handle(entry);
338
+ }
339
+ catch (e) {
340
+ // Prevent handler errors from affecting the logging system
341
+ if (Logger.consoleEnabled) {
342
+ console.error('[Logger] Handler error:', e);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ /**
348
+ * Output to console
349
+ */
350
+ logToConsole(entry) {
351
+ const prefix = `[${entry.source}]`;
352
+ const args = entry.data ? [prefix, entry.message, ...entry.data] : [prefix, entry.message];
353
+ switch (entry.levelName) {
354
+ case 'debug':
355
+ console.log(...args);
356
+ break;
357
+ case 'info':
358
+ console.info(...args);
359
+ break;
360
+ case 'warn':
361
+ console.warn(...args);
362
+ break;
363
+ case 'error':
364
+ console.error(...args);
365
+ break;
366
+ }
367
+ }
368
+ }
369
+ Logger.globalLevel = exports.LogLevel.WARN;
370
+ Logger.handlers = [];
371
+ Logger.consoleEnabled = true;
372
+ Logger.instances = new Map();
373
+ /**
374
+ * Buffer log handler
375
+ * Stores logs in memory for later retrieval
376
+ */
377
+ class BufferLogHandler {
378
+ constructor(maxSize = 1000) {
379
+ this.buffer = [];
380
+ this.maxSize = maxSize;
381
+ }
382
+ handle(entry) {
383
+ this.buffer.push(entry);
384
+ // Remove oldest logs when exceeding max size
385
+ while (this.buffer.length > this.maxSize) {
386
+ this.buffer.shift();
387
+ }
388
+ }
389
+ /**
390
+ * Get all buffered logs
391
+ * @returns Array of log entries
392
+ */
393
+ getEntries() {
394
+ return this.buffer;
395
+ }
396
+ /**
397
+ * Clear the buffer
398
+ */
399
+ clear() {
400
+ this.buffer = [];
401
+ }
402
+ /**
403
+ * Get logs by level
404
+ * @param level The log level to filter by
405
+ * @returns Array of matching log entries
406
+ */
407
+ getEntriesByLevel(level) {
408
+ return this.buffer.filter((e) => e.level === level);
409
+ }
410
+ /**
411
+ * Get logs by source
412
+ * @param source The source to filter by
413
+ * @returns Array of matching log entries
414
+ */
415
+ getEntriesBySource(source) {
416
+ return this.buffer.filter((e) => e.source === source);
417
+ }
418
+ }
419
+ /**
420
+ * Callback log handler
421
+ * Allows handling logs via a callback function
422
+ */
423
+ class CallbackLogHandler {
424
+ constructor(callback) {
425
+ this.callback = callback;
426
+ }
427
+ handle(entry) {
428
+ this.callback(entry);
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Cross-platform storage abstraction layer
434
+ * Provides a unified interface for storage operations that works in both
435
+ * browser and Node.js environments.
436
+ */
437
+ /**
438
+ * Browser storage implementation using localStorage
439
+ */
440
+ class BrowserStorage {
441
+ getItem(key) {
442
+ return localStorage.getItem(key);
443
+ }
444
+ setItem(key, value) {
445
+ localStorage.setItem(key, value);
446
+ }
447
+ removeItem(key) {
448
+ localStorage.removeItem(key);
449
+ }
450
+ keys() {
451
+ return Object.keys(localStorage);
452
+ }
453
+ }
454
+ /**
455
+ * In-memory storage implementation for server/Node.js environments
456
+ */
457
+ class MemoryStorage {
458
+ constructor() {
459
+ this.store = new Map();
460
+ }
461
+ getItem(key) {
462
+ var _a;
463
+ return (_a = this.store.get(key)) !== null && _a !== void 0 ? _a : null;
464
+ }
465
+ setItem(key, value) {
466
+ this.store.set(key, value);
467
+ }
468
+ removeItem(key) {
469
+ this.store.delete(key);
470
+ }
471
+ keys() {
472
+ return Array.from(this.store.keys());
473
+ }
474
+ /**
475
+ * Clear all items from memory storage
476
+ */
477
+ clear() {
478
+ this.store.clear();
479
+ }
480
+ }
481
+ /**
482
+ * Check if localStorage is available in the current environment
483
+ */
484
+ function isLocalStorageAvailable() {
485
+ if (typeof localStorage === 'undefined') {
486
+ return false;
487
+ }
488
+ try {
489
+ const testKey = '__playkit_storage_test__';
490
+ localStorage.setItem(testKey, 'test');
491
+ localStorage.removeItem(testKey);
492
+ return true;
493
+ }
494
+ catch (_a) {
495
+ return false;
496
+ }
497
+ }
498
+ /**
499
+ * Create a storage instance based on the environment or mode
500
+ * @param mode - SDK mode ('browser' | 'server')
501
+ * @param customStorage - Optional custom storage implementation
502
+ */
503
+ function createStorage(mode = 'browser', customStorage) {
504
+ // Use custom storage if provided
505
+ if (customStorage) {
506
+ return customStorage;
507
+ }
508
+ // Server mode always uses memory storage
509
+ if (mode === 'server') {
510
+ return new MemoryStorage();
511
+ }
512
+ // Browser mode - use localStorage if available, otherwise memory
513
+ if (isLocalStorageAvailable()) {
514
+ return new BrowserStorage();
515
+ }
516
+ return new MemoryStorage();
517
+ }
518
+
519
+ /**
520
+ * Cross-platform cryptographic utilities
521
+ * Provides unified crypto operations that work in both browser and Node.js environments.
522
+ */
523
+ /**
524
+ * Check if we're running in a Node.js environment
525
+ */
526
+ function isNodeEnvironment() {
527
+ return typeof process !== 'undefined' &&
528
+ process.versions != null &&
529
+ process.versions.node != null;
530
+ }
531
+ /**
532
+ * Get random bytes - works in both browser and Node.js
533
+ * @param length - Number of random bytes to generate
534
+ * @returns Uint8Array of random bytes
535
+ */
536
+ function getRandomBytes(length) {
537
+ if (isNodeEnvironment()) {
538
+ // Node.js environment
539
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
540
+ const nodeCrypto = require('crypto');
541
+ return new Uint8Array(nodeCrypto.randomBytes(length));
542
+ }
543
+ else if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
544
+ // Browser environment
545
+ const array = new Uint8Array(length);
546
+ crypto.getRandomValues(array);
547
+ return array;
548
+ }
549
+ else {
550
+ throw new Error('No secure random number generator available');
551
+ }
552
+ }
553
+ /**
554
+ * Compute SHA-256 hash - works in both browser and Node.js
555
+ * @param data - Data to hash (Uint8Array or string)
556
+ * @returns Promise resolving to hash as Uint8Array
557
+ */
558
+ async function sha256(data) {
559
+ const inputData = typeof data === 'string' ? textEncode(data) : data;
560
+ if (isNodeEnvironment()) {
561
+ // Node.js environment
562
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
563
+ const nodeCrypto = require('crypto');
564
+ const hash = nodeCrypto.createHash('sha256').update(Buffer.from(inputData)).digest();
565
+ return new Uint8Array(hash);
566
+ }
567
+ else if (typeof crypto !== 'undefined' && crypto.subtle) {
568
+ // Browser environment
569
+ const hashBuffer = await crypto.subtle.digest('SHA-256', inputData);
570
+ return new Uint8Array(hashBuffer);
571
+ }
572
+ else {
573
+ throw new Error('No SHA-256 implementation available');
574
+ }
575
+ }
576
+ /**
577
+ * Base64 encode binary data - works in both browser and Node.js
578
+ * @param data - Data to encode (Uint8Array or ArrayBuffer)
579
+ * @returns Base64 encoded string
580
+ */
581
+ function base64Encode(data) {
582
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
583
+ if (isNodeEnvironment()) {
584
+ // Node.js environment
585
+ return Buffer.from(bytes).toString('base64');
586
+ }
587
+ else if (typeof btoa !== 'undefined') {
588
+ // Browser environment
589
+ let binary = '';
590
+ for (let i = 0; i < bytes.byteLength; i++) {
591
+ binary += String.fromCharCode(bytes[i]);
592
+ }
593
+ return btoa(binary);
594
+ }
595
+ else {
596
+ throw new Error('No Base64 encoder available');
597
+ }
598
+ }
599
+ /**
600
+ * Base64 decode string - works in both browser and Node.js
601
+ * @param base64 - Base64 encoded string
602
+ * @returns Decoded data as Uint8Array
603
+ */
604
+ function base64Decode(base64) {
605
+ if (isNodeEnvironment()) {
606
+ // Node.js environment
607
+ return new Uint8Array(Buffer.from(base64, 'base64'));
608
+ }
609
+ else if (typeof atob !== 'undefined') {
610
+ // Browser environment
611
+ const binary = atob(base64);
612
+ const bytes = new Uint8Array(binary.length);
613
+ for (let i = 0; i < binary.length; i++) {
614
+ bytes[i] = binary.charCodeAt(i);
615
+ }
616
+ return bytes;
617
+ }
618
+ else {
619
+ throw new Error('No Base64 decoder available');
620
+ }
621
+ }
622
+ /**
623
+ * Base64 URL encode - safe for URLs (no +, /, or = characters)
624
+ * @param data - Data to encode (Uint8Array)
625
+ * @returns URL-safe Base64 encoded string
626
+ */
627
+ function base64URLEncode(data) {
628
+ const base64 = base64Encode(data);
629
+ return base64
630
+ .replace(/\+/g, '-')
631
+ .replace(/\//g, '_')
632
+ .replace(/=/g, '');
633
+ }
634
+ /**
635
+ * Encode text to UTF-8 bytes - works in both browser and Node.js
636
+ * @param text - Text to encode
637
+ * @returns UTF-8 encoded bytes as Uint8Array
638
+ */
639
+ function textEncode(text) {
640
+ if (typeof TextEncoder !== 'undefined') {
641
+ // Browser or modern Node.js
642
+ return new TextEncoder().encode(text);
643
+ }
644
+ else if (isNodeEnvironment()) {
645
+ // Older Node.js
646
+ return new Uint8Array(Buffer.from(text, 'utf-8'));
647
+ }
648
+ else {
649
+ throw new Error('No text encoder available');
650
+ }
651
+ }
652
+ /**
653
+ * Decode UTF-8 bytes to text - works in both browser and Node.js
654
+ * @param data - UTF-8 encoded bytes (Uint8Array or ArrayBuffer)
655
+ * @returns Decoded text string
656
+ */
657
+ function textDecode(data) {
658
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
659
+ if (typeof TextDecoder !== 'undefined') {
660
+ // Browser or modern Node.js
661
+ return new TextDecoder().decode(bytes);
662
+ }
663
+ else if (isNodeEnvironment()) {
664
+ // Older Node.js
665
+ return Buffer.from(bytes).toString('utf-8');
666
+ }
667
+ else {
668
+ throw new Error('No text decoder available');
669
+ }
670
+ }
671
+ /**
672
+ * Check if Web Crypto API is available
673
+ * @returns true if Web Crypto subtle API is available
674
+ */
675
+ function isWebCryptoAvailable() {
676
+ return typeof crypto !== 'undefined' &&
677
+ crypto.subtle != null;
678
+ }
679
+
110
680
  /**
111
681
  * Token storage with encryption using Web Crypto API
112
- * Stores tokens in localStorage with AES-128-GCM encryption
682
+ * Stores tokens with AES-128-GCM encryption when available
683
+ * Works in both browser (localStorage) and server (memory) environments
113
684
  */
114
685
  const STORAGE_KEY_PREFIX = 'playkit_';
115
686
  const ENCRYPTION_KEY_NAME = 'playkit_encryption_key';
116
687
  class TokenStorage {
117
- constructor() {
688
+ constructor(options = {}) {
118
689
  this.encryptionKey = null;
690
+ this.logger = Logger.getLogger('TokenStorage');
691
+ this.storage = options.storage || createStorage(options.mode || 'browser');
119
692
  }
120
693
  /**
121
694
  * Initialize the encryption key
122
695
  */
123
696
  async initialize() {
124
- if (typeof window === 'undefined' || !window.crypto || !window.crypto.subtle) {
125
- console.warn('Web Crypto API not available, encryption disabled');
697
+ // Skip encryption if Web Crypto API is not available
698
+ if (!isWebCryptoAvailable()) {
699
+ this.logger.warn('Web Crypto API not available, encryption disabled');
126
700
  return;
127
701
  }
128
702
  // Try to load existing key or generate new one
129
- const storedKey = localStorage.getItem(ENCRYPTION_KEY_NAME);
703
+ const storedKey = this.storage.getItem(ENCRYPTION_KEY_NAME);
130
704
  if (storedKey) {
131
705
  try {
132
706
  const keyData = this.base64ToArrayBuffer(storedKey);
133
707
  this.encryptionKey = await crypto.subtle.importKey('raw', keyData, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt']);
134
708
  }
135
709
  catch (error) {
136
- console.warn('Failed to import encryption key, generating new one', error);
710
+ this.logger.warn('Failed to import encryption key, generating new one', error);
137
711
  await this.generateNewKey();
138
712
  }
139
713
  }
@@ -145,24 +719,24 @@ class TokenStorage {
145
719
  * Generate a new encryption key
146
720
  */
147
721
  async generateNewKey() {
148
- if (!crypto.subtle)
722
+ if (!isWebCryptoAvailable())
149
723
  return;
150
724
  this.encryptionKey = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 128 }, true, ['encrypt', 'decrypt']);
151
725
  // Store the key
152
726
  const exported = await crypto.subtle.exportKey('raw', this.encryptionKey);
153
- localStorage.setItem(ENCRYPTION_KEY_NAME, this.arrayBufferToBase64(exported));
727
+ this.storage.setItem(ENCRYPTION_KEY_NAME, this.arrayBufferToBase64(exported));
154
728
  }
155
729
  /**
156
730
  * Encrypt data
157
731
  */
158
732
  async encrypt(data) {
159
- if (!this.encryptionKey || !crypto.subtle) {
733
+ if (!this.encryptionKey || !isWebCryptoAvailable()) {
160
734
  // Fallback to plain storage if encryption unavailable
161
735
  return data;
162
736
  }
163
- const iv = crypto.getRandomValues(new Uint8Array(12));
164
- const encoded = new TextEncoder().encode(data);
165
- const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, this.encryptionKey, encoded);
737
+ const iv = getRandomBytes(12);
738
+ const encoded = textEncode(data);
739
+ const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv }, this.encryptionKey, encoded);
166
740
  // Combine IV and encrypted data
167
741
  const combined = new Uint8Array(iv.length + encrypted.byteLength);
168
742
  combined.set(iv, 0);
@@ -173,7 +747,7 @@ class TokenStorage {
173
747
  * Decrypt data
174
748
  */
175
749
  async decrypt(encryptedData) {
176
- if (!this.encryptionKey || !crypto.subtle) {
750
+ if (!this.encryptionKey || !isWebCryptoAvailable()) {
177
751
  // Data not encrypted
178
752
  return encryptedData;
179
753
  }
@@ -182,10 +756,10 @@ class TokenStorage {
182
756
  const iv = combined.slice(0, 12);
183
757
  const encrypted = combined.slice(12);
184
758
  const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, this.encryptionKey, encrypted);
185
- return new TextDecoder().decode(decrypted);
759
+ return textDecode(decrypted);
186
760
  }
187
761
  catch (error) {
188
- console.error('Decryption failed', error);
762
+ this.logger.error('Decryption failed', error);
189
763
  return encryptedData; // Return original if decryption fails
190
764
  }
191
765
  }
@@ -196,14 +770,14 @@ class TokenStorage {
196
770
  const key = `${STORAGE_KEY_PREFIX}${gameId}_auth`;
197
771
  const data = JSON.stringify(authState);
198
772
  const encrypted = await this.encrypt(data);
199
- localStorage.setItem(key, encrypted);
773
+ this.storage.setItem(key, encrypted);
200
774
  }
201
775
  /**
202
776
  * Load auth state
203
777
  */
204
778
  async loadAuthState(gameId) {
205
779
  const key = `${STORAGE_KEY_PREFIX}${gameId}_auth`;
206
- const encrypted = localStorage.getItem(key);
780
+ const encrypted = this.storage.getItem(key);
207
781
  if (!encrypted)
208
782
  return null;
209
783
  try {
@@ -211,7 +785,7 @@ class TokenStorage {
211
785
  return JSON.parse(decrypted);
212
786
  }
213
787
  catch (error) {
214
- console.error('Failed to load auth state', error);
788
+ this.logger.error('Failed to load auth state', error);
215
789
  return null;
216
790
  }
217
791
  }
@@ -220,39 +794,38 @@ class TokenStorage {
220
794
  */
221
795
  clearAuthState(gameId) {
222
796
  const key = `${STORAGE_KEY_PREFIX}${gameId}_auth`;
223
- localStorage.removeItem(key);
797
+ this.storage.removeItem(key);
224
798
  }
225
799
  /**
226
800
  * Clear all PlayKit data
227
801
  */
228
802
  clearAll() {
229
- Object.keys(localStorage).forEach((key) => {
803
+ const keys = this.storage.keys();
804
+ keys.forEach((key) => {
230
805
  if (key.startsWith(STORAGE_KEY_PREFIX)) {
231
- localStorage.removeItem(key);
806
+ this.storage.removeItem(key);
232
807
  }
233
808
  });
234
809
  }
810
+ /**
811
+ * Get the underlying storage instance
812
+ */
813
+ getStorage() {
814
+ return this.storage;
815
+ }
235
816
  /**
236
817
  * Utility: ArrayBuffer to Base64
237
818
  */
238
819
  arrayBufferToBase64(buffer) {
239
- const bytes = new Uint8Array(buffer);
240
- let binary = '';
241
- for (let i = 0; i < bytes.byteLength; i++) {
242
- binary += String.fromCharCode(bytes[i]);
243
- }
244
- return btoa(binary);
820
+ return base64Encode(new Uint8Array(buffer));
245
821
  }
246
822
  /**
247
823
  * Utility: Base64 to ArrayBuffer
248
824
  */
249
825
  base64ToArrayBuffer(base64) {
250
- const binary = atob(base64);
251
- const bytes = new Uint8Array(binary.length);
252
- for (let i = 0; i < binary.length; i++) {
253
- bytes[i] = binary.charCodeAt(i);
254
- }
255
- return bytes.buffer;
826
+ const decoded = base64Decode(base64);
827
+ // Copy to a new ArrayBuffer to ensure proper type
828
+ return decoded.buffer.slice(decoded.byteOffset, decoded.byteOffset + decoded.byteLength);
256
829
  }
257
830
  }
258
831
 
@@ -1211,6 +1784,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1211
1784
  this.aborted = false;
1212
1785
  this.currentLanguage = 'en';
1213
1786
  this.currentModal = null;
1787
+ this.logger = Logger.getLogger('DeviceAuth');
1214
1788
  this.baseURL = baseURL;
1215
1789
  this.gameId = gameId;
1216
1790
  }
@@ -1219,27 +1793,16 @@ class DeviceAuthFlowManager extends EventEmitter {
1219
1793
  * @private
1220
1794
  */
1221
1795
  generateCodeVerifier() {
1222
- const array = new Uint8Array(32);
1223
- crypto.getRandomValues(array);
1224
- return this.base64URLEncode(array);
1796
+ const array = getRandomBytes(32);
1797
+ return base64URLEncode(array);
1225
1798
  }
1226
1799
  /**
1227
1800
  * Generate PKCE code challenge from verifier
1228
1801
  * @private
1229
1802
  */
1230
1803
  async generateCodeChallenge(verifier) {
1231
- const encoder = new TextEncoder();
1232
- const data = encoder.encode(verifier);
1233
- const hash = await crypto.subtle.digest('SHA-256', data);
1234
- return this.base64URLEncode(new Uint8Array(hash));
1235
- }
1236
- /**
1237
- * Base64 URL encode
1238
- * @private
1239
- */
1240
- base64URLEncode(buffer) {
1241
- const base64 = btoa(String.fromCharCode(...buffer));
1242
- return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
1804
+ const hash = await sha256(verifier);
1805
+ return base64URLEncode(hash);
1243
1806
  }
1244
1807
  /**
1245
1808
  * Detect browser language and return matching translation key
@@ -1486,7 +2049,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1486
2049
  }
1487
2050
  else {
1488
2051
  // In Node.js, we can't open browser by default
1489
- console.log('Please open this URL in your browser:', url);
2052
+ this.logger.info('Please open this URL in your browser:', url);
1490
2053
  }
1491
2054
  }
1492
2055
  /**
@@ -1646,7 +2209,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1646
2209
  }
1647
2210
  catch (err) {
1648
2211
  // Network error, retry after interval
1649
- console.warn('[DeviceAuth] Poll network error, retrying...', err);
2212
+ this.logger.warn('Poll network error, retrying...', err);
1650
2213
  this.pollTimeoutId = setTimeout(poll, this.pollInterval);
1651
2214
  }
1652
2215
  };
@@ -1814,7 +2377,7 @@ class DeviceAuthFlowManager extends EventEmitter {
1814
2377
  }
1815
2378
  catch (err) {
1816
2379
  // Network error, retry after interval
1817
- console.warn('[DeviceAuth] Poll network error, retrying...', err);
2380
+ this.logger.warn('Poll network error, retrying...', err);
1818
2381
  this.pollTimeoutId = setTimeout(poll, pollIntervalMs);
1819
2382
  }
1820
2383
  };
@@ -1831,13 +2394,18 @@ class DeviceAuthFlowManager extends EventEmitter {
1831
2394
  // @ts-ignore - replaced at build time
1832
2395
  const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
1833
2396
  const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
2397
+ const TOKEN_REFRESH_ENDPOINT = '/api/auth/refresh';
1834
2398
  class AuthManager extends EventEmitter {
1835
2399
  constructor(config) {
1836
2400
  super();
1837
2401
  this.authFlowManager = null;
1838
2402
  this.deviceAuthFlowManager = null;
2403
+ this.logger = Logger.getLogger('AuthManager');
1839
2404
  this.config = config;
1840
- this.storage = new TokenStorage();
2405
+ // Create TokenStorage with appropriate mode for server vs browser environment
2406
+ this.storage = new TokenStorage({
2407
+ mode: config.mode === 'server' ? 'server' : 'browser',
2408
+ });
1841
2409
  this.baseURL = config.baseURL || DEFAULT_BASE_URL$5;
1842
2410
  this.authState = {
1843
2411
  isAuthenticated: false,
@@ -1878,6 +2446,21 @@ class AuthManager extends EventEmitter {
1878
2446
  this.emit('authenticated', this.authState);
1879
2447
  return;
1880
2448
  }
2449
+ // Token expired, but check if we can refresh (browser mode only)
2450
+ if (this.config.mode !== 'server' &&
2451
+ savedState.refreshToken &&
2452
+ (!savedState.refreshExpiresAt || Date.now() < savedState.refreshExpiresAt)) {
2453
+ this.logger.debug('Access token expired, attempting refresh');
2454
+ this.authState = savedState; // Load state with refresh token
2455
+ try {
2456
+ await this.refreshToken();
2457
+ return; // Successfully refreshed
2458
+ }
2459
+ catch (error) {
2460
+ this.logger.warn('Token refresh failed during initialization', error);
2461
+ // Continue to re-authentication
2462
+ }
2463
+ }
1881
2464
  }
1882
2465
  // Check if player JWT was provided
1883
2466
  if (this.config.playerJWT) {
@@ -1917,7 +2500,7 @@ class AuthManager extends EventEmitter {
1917
2500
  }
1918
2501
  // Deprecation warning for headless auth
1919
2502
  if (authMethod === 'headless') {
1920
- console.warn('[PlayKit] "headless" authentication is deprecated and will be removed in v2.0. ' +
2503
+ this.logger.warn('"headless" authentication is deprecated and will be removed in v2.0. ' +
1921
2504
  'Please migrate to "device" authentication.');
1922
2505
  }
1923
2506
  try {
@@ -1927,12 +2510,14 @@ class AuthManager extends EventEmitter {
1927
2510
  const result = await this.deviceAuthFlowManager.startFlow({
1928
2511
  scope: 'player:play',
1929
2512
  });
1930
- // Update auth state with the player token
2513
+ // Update auth state with the player token and refresh token
1931
2514
  this.authState = {
1932
2515
  isAuthenticated: true,
1933
2516
  token: result.access_token,
1934
2517
  tokenType: 'player',
1935
2518
  expiresAt: Date.now() + result.expires_in * 1000,
2519
+ refreshToken: result.refresh_token,
2520
+ refreshExpiresAt: Date.now() + result.refresh_expires_in * 1000,
1936
2521
  };
1937
2522
  // Save to storage
1938
2523
  await this.storage.saveAuthState(this.config.gameId, this.authState);
@@ -2031,6 +2616,58 @@ class AuthManager extends EventEmitter {
2031
2616
  return false;
2032
2617
  return Date.now() >= this.authState.expiresAt;
2033
2618
  }
2619
+ /**
2620
+ * Check if token is about to expire (within threshold)
2621
+ * @param thresholdMs - Threshold in milliseconds (default: 5 minutes)
2622
+ */
2623
+ isTokenExpiringSoon(thresholdMs = 5 * 60 * 1000) {
2624
+ if (!this.authState.expiresAt)
2625
+ return false;
2626
+ return Date.now() >= this.authState.expiresAt - thresholdMs;
2627
+ }
2628
+ /**
2629
+ * Ensure the access token is valid, refreshing if needed (browser mode only).
2630
+ * Call this before making API requests to automatically handle token refresh.
2631
+ *
2632
+ * In server mode, this method does nothing (tokens should be managed externally).
2633
+ *
2634
+ * @param thresholdMs - Refresh if token expires within this time (default: 5 minutes)
2635
+ * @returns Promise that resolves when token is valid
2636
+ * @throws PlayKitError if token cannot be refreshed and is expired
2637
+ */
2638
+ async ensureValidToken(thresholdMs = 5 * 60 * 1000) {
2639
+ // Skip auto-refresh in server mode
2640
+ if (this.config.mode === 'server') {
2641
+ return;
2642
+ }
2643
+ // Skip if not authenticated
2644
+ if (!this.authState.isAuthenticated || !this.authState.token) {
2645
+ return;
2646
+ }
2647
+ // Check if token needs refresh
2648
+ if (!this.isTokenExpiringSoon(thresholdMs)) {
2649
+ return; // Token is still valid
2650
+ }
2651
+ // Try to refresh if possible
2652
+ if (this.canRefresh()) {
2653
+ this.logger.debug('Token expiring soon, refreshing automatically');
2654
+ try {
2655
+ await this.refreshToken();
2656
+ }
2657
+ catch (error) {
2658
+ this.logger.warn('Auto-refresh failed', error);
2659
+ // If refresh fails and token is already expired, throw
2660
+ if (this.isTokenExpired()) {
2661
+ throw error;
2662
+ }
2663
+ // Otherwise, continue with potentially expired token
2664
+ }
2665
+ }
2666
+ else if (this.isTokenExpired()) {
2667
+ // Token expired and cannot refresh
2668
+ throw new PlayKitError('Access token has expired and no refresh token is available', 'TOKEN_EXPIRED');
2669
+ }
2670
+ }
2034
2671
  /**
2035
2672
  * Logout and clear authentication
2036
2673
  */
@@ -2072,12 +2709,14 @@ class AuthManager extends EventEmitter {
2072
2709
  try {
2073
2710
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
2074
2711
  const result = await this.deviceAuthFlowManager.startFlow(options);
2075
- // Update auth state with the token
2712
+ // Update auth state with the token and refresh token
2076
2713
  this.authState = {
2077
2714
  isAuthenticated: true,
2078
2715
  token: result.access_token,
2079
2716
  tokenType: result.scope === 'developer:full' ? 'developer' : 'player',
2080
2717
  expiresAt: Date.now() + result.expires_in * 1000,
2718
+ refreshToken: result.refresh_token,
2719
+ refreshExpiresAt: Date.now() + result.refresh_expires_in * 1000,
2081
2720
  };
2082
2721
  // Save to storage
2083
2722
  await this.storage.saveAuthState(this.config.gameId, this.authState);
@@ -2140,12 +2779,14 @@ class AuthManager extends EventEmitter {
2140
2779
  }
2141
2780
  try {
2142
2781
  const result = await this.deviceAuthFlowManager.pollForToken(sessionId, codeVerifier, options);
2143
- // Update auth state with the token
2782
+ // Update auth state with the token and refresh token
2144
2783
  this.authState = {
2145
2784
  isAuthenticated: true,
2146
2785
  token: result.access_token,
2147
2786
  tokenType: result.scope === 'developer:full' ? 'developer' : 'player',
2148
2787
  expiresAt: Date.now() + result.expires_in * 1000,
2788
+ refreshToken: result.refresh_token,
2789
+ refreshExpiresAt: Date.now() + result.refresh_expires_in * 1000,
2149
2790
  };
2150
2791
  // Save to storage
2151
2792
  await this.storage.saveAuthState(this.config.gameId, this.authState);
@@ -2158,6 +2799,91 @@ class AuthManager extends EventEmitter {
2158
2799
  this.deviceAuthFlowManager = null;
2159
2800
  }
2160
2801
  }
2802
+ /**
2803
+ * Check if the current session can be refreshed
2804
+ * @returns true if a valid refresh token exists and has not expired
2805
+ */
2806
+ canRefresh() {
2807
+ if (!this.authState.refreshToken)
2808
+ return false;
2809
+ if (!this.authState.refreshExpiresAt)
2810
+ return true; // No expiry info, assume valid
2811
+ return Date.now() < this.authState.refreshExpiresAt;
2812
+ }
2813
+ /**
2814
+ * Refresh the access token using the stored refresh token
2815
+ *
2816
+ * @returns Promise resolving to TokenRefreshResult with new tokens
2817
+ * @throws PlayKitError if no refresh token is available or refresh fails
2818
+ *
2819
+ * @example
2820
+ * ```ts
2821
+ * if (sdk.isTokenExpired() && authManager.canRefresh()) {
2822
+ * const result = await authManager.refreshToken();
2823
+ * console.log('New token expires in:', result.expiresIn, 'seconds');
2824
+ * }
2825
+ * ```
2826
+ */
2827
+ async refreshToken() {
2828
+ if (!this.authState.refreshToken) {
2829
+ throw new PlayKitError('No refresh token available. Please re-authenticate.', 'NO_REFRESH_TOKEN');
2830
+ }
2831
+ if (this.authState.refreshExpiresAt && Date.now() >= this.authState.refreshExpiresAt) {
2832
+ throw new PlayKitError('Refresh token has expired. Please re-authenticate.', 'REFRESH_TOKEN_EXPIRED');
2833
+ }
2834
+ try {
2835
+ this.logger.debug('Refreshing access token');
2836
+ const response = await fetch(`${this.baseURL}${TOKEN_REFRESH_ENDPOINT}`, {
2837
+ method: 'POST',
2838
+ headers: {
2839
+ 'Content-Type': 'application/json',
2840
+ },
2841
+ body: JSON.stringify({
2842
+ refresh_token: this.authState.refreshToken,
2843
+ }),
2844
+ });
2845
+ if (!response.ok) {
2846
+ const error = await response.json().catch(() => ({ error_description: 'Token refresh failed' }));
2847
+ // If refresh token is invalid, clear auth state
2848
+ if (response.status === 401) {
2849
+ this.logger.warn('Refresh token invalid, clearing auth state');
2850
+ await this.logout();
2851
+ throw new PlayKitError(error.error_description || 'Refresh token is invalid or expired', 'REFRESH_TOKEN_INVALID', response.status);
2852
+ }
2853
+ throw new PlayKitError(error.error_description || 'Token refresh failed', error.error || 'REFRESH_FAILED', response.status);
2854
+ }
2855
+ const data = await response.json();
2856
+ // Update auth state with new tokens
2857
+ this.authState = {
2858
+ isAuthenticated: true,
2859
+ token: data.access_token,
2860
+ tokenType: data.scope === 'developer:full' ? 'developer' : 'player',
2861
+ expiresAt: Date.now() + data.expires_in * 1000,
2862
+ refreshToken: data.refresh_token,
2863
+ refreshExpiresAt: Date.now() + data.refresh_expires_in * 1000,
2864
+ };
2865
+ // Save to storage
2866
+ await this.storage.saveAuthState(this.config.gameId, this.authState);
2867
+ this.logger.info('Access token refreshed successfully');
2868
+ this.emit('authenticated', this.authState);
2869
+ this.emit('token_refreshed', this.authState);
2870
+ return {
2871
+ accessToken: data.access_token,
2872
+ tokenType: data.token_type,
2873
+ expiresIn: data.expires_in,
2874
+ refreshToken: data.refresh_token,
2875
+ refreshExpiresIn: data.refresh_expires_in,
2876
+ scope: data.scope,
2877
+ };
2878
+ }
2879
+ catch (error) {
2880
+ if (error instanceof PlayKitError) {
2881
+ throw error;
2882
+ }
2883
+ this.logger.error('Token refresh failed', error);
2884
+ throw new PlayKitError('Token refresh failed due to network error', 'REFRESH_NETWORK_ERROR');
2885
+ }
2886
+ }
2161
2887
  }
2162
2888
 
2163
2889
  /**
@@ -2229,6 +2955,10 @@ class RechargeManager extends EventEmitter {
2229
2955
  * Detect user's preferred language
2230
2956
  */
2231
2957
  detectLanguage() {
2958
+ // Return default language if navigator is not available (server environment)
2959
+ if (typeof navigator === 'undefined') {
2960
+ return 'en';
2961
+ }
2232
2962
  const browserLang = navigator.language.toLowerCase();
2233
2963
  if (browserLang.startsWith('zh-tw') || browserLang.startsWith('zh-hk')) {
2234
2964
  return 'zh-TW';
@@ -2701,6 +3431,7 @@ class PlayerClient extends EventEmitter {
2701
3431
  this.playerInfo = null;
2702
3432
  this.rechargeManager = null;
2703
3433
  this.balanceCheckInterval = null;
3434
+ this.logger = Logger.getLogger('PlayerClient');
2704
3435
  this.authManager = authManager;
2705
3436
  this.baseURL = config.baseURL || DEFAULT_BASE_URL$4;
2706
3437
  this.gameId = config.gameId;
@@ -2877,7 +3608,7 @@ class PlayerClient extends EventEmitter {
2877
3608
  var _a;
2878
3609
  this.initializeRechargeManager();
2879
3610
  if (!this.rechargeManager) {
2880
- console.warn('RechargeManager not initialized. Cannot show modal.');
3611
+ this.logger.warn('RechargeManager not initialized. Cannot show modal.');
2881
3612
  return;
2882
3613
  }
2883
3614
  const balance = (_a = this.playerInfo) === null || _a === void 0 ? void 0 : _a.credits;
@@ -2892,7 +3623,7 @@ class PlayerClient extends EventEmitter {
2892
3623
  openRechargeWindow() {
2893
3624
  this.initializeRechargeManager();
2894
3625
  if (!this.rechargeManager) {
2895
- console.warn('RechargeManager not initialized. Cannot open recharge window.');
3626
+ this.logger.warn('RechargeManager not initialized. Cannot open recharge window.');
2896
3627
  return;
2897
3628
  }
2898
3629
  this.rechargeManager.openRechargeWindow();
@@ -2927,7 +3658,7 @@ class PlayerClient extends EventEmitter {
2927
3658
  }
2928
3659
  catch (error) {
2929
3660
  // Silently fail periodic checks to avoid spamming errors
2930
- console.debug('Failed to check balance:', error);
3661
+ this.logger.debug('Failed to check balance:', error);
2931
3662
  }
2932
3663
  }, interval);
2933
3664
  }
@@ -2952,7 +3683,7 @@ class PlayerClient extends EventEmitter {
2952
3683
  }
2953
3684
  catch (error) {
2954
3685
  // Silently fail to avoid disrupting the main flow
2955
- console.debug('Failed to check balance after API call:', error);
3686
+ this.logger.debug('Failed to check balance after API call:', error);
2956
3687
  }
2957
3688
  }
2958
3689
  /**
@@ -3006,6 +3737,8 @@ class ChatProvider {
3006
3737
  */
3007
3738
  async chatCompletion(chatConfig) {
3008
3739
  var _a;
3740
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
3741
+ await this.authManager.ensureValidToken();
3009
3742
  const token = this.authManager.getToken();
3010
3743
  if (!token) {
3011
3744
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3063,6 +3796,8 @@ class ChatProvider {
3063
3796
  */
3064
3797
  async chatCompletionStream(chatConfig) {
3065
3798
  var _a;
3799
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
3800
+ await this.authManager.ensureValidToken();
3066
3801
  const token = this.authManager.getToken();
3067
3802
  if (!token) {
3068
3803
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3251,6 +3986,8 @@ class ChatProvider {
3251
3986
  */
3252
3987
  async generateStructured(schemaName, prompt, model, temperature, schema, schemaDescription) {
3253
3988
  var _a;
3989
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
3990
+ await this.authManager.ensureValidToken();
3254
3991
  const token = this.authManager.getToken();
3255
3992
  if (!token) {
3256
3993
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3340,6 +4077,8 @@ class ImageProvider {
3340
4077
  * Generate one or more images
3341
4078
  */
3342
4079
  async generateImages(imageConfig) {
4080
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
4081
+ await this.authManager.ensureValidToken();
3343
4082
  const token = this.authManager.getToken();
3344
4083
  if (!token) {
3345
4084
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3451,6 +4190,8 @@ class TranscriptionProvider {
3451
4190
  * Transcribe audio to text
3452
4191
  */
3453
4192
  async transcribe(transcriptionConfig) {
4193
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
4194
+ await this.authManager.ensureValidToken();
3454
4195
  const token = this.authManager.getToken();
3455
4196
  if (!token) {
3456
4197
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3575,13 +4316,25 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
3575
4316
  * Server-Sent Events (SSE) stream parser
3576
4317
  * Handles parsing of streaming text responses
3577
4318
  */
4319
+ /**
4320
+ * Create a cross-platform text decoder
4321
+ */
4322
+ function createDecoder() {
4323
+ if (typeof TextDecoder !== 'undefined') {
4324
+ return new TextDecoder();
4325
+ }
4326
+ // Fallback for environments without TextDecoder
4327
+ return {
4328
+ decode: (data, _options) => textDecode(data),
4329
+ };
4330
+ }
3578
4331
  class StreamParser {
3579
4332
  /**
3580
4333
  * Parse SSE stream using ReadableStream
3581
4334
  */
3582
4335
  static parseStream(reader) {
3583
4336
  return __asyncGenerator(this, arguments, function* parseStream_1() {
3584
- const decoder = new TextDecoder();
4337
+ const decoder = createDecoder();
3585
4338
  let buffer = '';
3586
4339
  try {
3587
4340
  while (true) {
@@ -4155,6 +4908,7 @@ class NPCClient extends EventEmitter {
4155
4908
  var _a, _b, _c;
4156
4909
  super();
4157
4910
  this._isTalking = false;
4911
+ this.logger = Logger.getLogger('NPCClient');
4158
4912
  this.chatClient = chatClient;
4159
4913
  // Support both characterDesign and legacy systemPrompt
4160
4914
  this.characterDesign = (config === null || config === void 0 ? void 0 : config.characterDesign) || (config === null || config === void 0 ? void 0 : config.systemPrompt) || 'You are a helpful assistant.';
@@ -4192,7 +4946,7 @@ class NPCClient extends EventEmitter {
4192
4946
  * This method is kept for backwards compatibility.
4193
4947
  */
4194
4948
  setSystemPrompt(prompt) {
4195
- console.warn('[NPCClient] setSystemPrompt is deprecated. Use setCharacterDesign instead.');
4949
+ this.logger.warn('setSystemPrompt is deprecated. Use setCharacterDesign instead.');
4196
4950
  this.setCharacterDesign(prompt);
4197
4951
  }
4198
4952
  /**
@@ -4211,7 +4965,7 @@ class NPCClient extends EventEmitter {
4211
4965
  */
4212
4966
  setMemory(memoryName, memoryContent) {
4213
4967
  if (!memoryName) {
4214
- console.warn('[NPCClient] Memory name cannot be empty');
4968
+ this.logger.warn('Memory name cannot be empty');
4215
4969
  return;
4216
4970
  }
4217
4971
  if (!memoryContent) {
@@ -4287,7 +5041,7 @@ class NPCClient extends EventEmitter {
4287
5041
  var _a;
4288
5042
  const predictionNum = count !== null && count !== void 0 ? count : this.predictionCount;
4289
5043
  if (this.history.length < 2) {
4290
- console.log('[NPCClient] Not enough conversation history to generate predictions');
5044
+ this.logger.info('Not enough conversation history to generate predictions');
4291
5045
  return [];
4292
5046
  }
4293
5047
  try {
@@ -4296,7 +5050,7 @@ class NPCClient extends EventEmitter {
4296
5050
  .reverse()
4297
5051
  .find(m => m.role === 'assistant')) === null || _a === void 0 ? void 0 : _a.content;
4298
5052
  if (!lastNpcMessage) {
4299
- console.log('[NPCClient] No NPC message found to generate predictions from');
5053
+ this.logger.info('No NPC message found to generate predictions from');
4300
5054
  return [];
4301
5055
  }
4302
5056
  // Build recent history (last 6 non-system messages)
@@ -4328,7 +5082,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4328
5082
  model: this.fastModel,
4329
5083
  });
4330
5084
  if (!result.content) {
4331
- console.warn('[NPCClient] Failed to generate predictions: empty response');
5085
+ this.logger.warn('Failed to generate predictions: empty response');
4332
5086
  return [];
4333
5087
  }
4334
5088
  // Parse JSON response
@@ -4339,7 +5093,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4339
5093
  return predictions;
4340
5094
  }
4341
5095
  catch (error) {
4342
- console.error('[NPCClient] Error generating predictions:', error);
5096
+ this.logger.error('Error generating predictions:', error);
4343
5097
  return [];
4344
5098
  }
4345
5099
  }
@@ -4352,7 +5106,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4352
5106
  const startIndex = response.indexOf('[');
4353
5107
  const endIndex = response.lastIndexOf(']');
4354
5108
  if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
4355
- console.warn('[NPCClient] Could not find JSON array in prediction response');
5109
+ this.logger.warn('Could not find JSON array in prediction response');
4356
5110
  return this.extractPredictionsFromText(response, expectedCount);
4357
5111
  }
4358
5112
  const jsonArray = response.substring(startIndex, endIndex + 1);
@@ -4365,7 +5119,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4365
5119
  return [];
4366
5120
  }
4367
5121
  catch (error) {
4368
- console.warn('[NPCClient] Failed to parse predictions JSON:', error);
5122
+ this.logger.warn('Failed to parse predictions JSON:', error);
4369
5123
  return this.extractPredictionsFromText(response, expectedCount);
4370
5124
  }
4371
5125
  }
@@ -4409,7 +5163,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4409
5163
  return;
4410
5164
  // Fire and forget - don't block the main response
4411
5165
  this.generateReplyPredictions().catch(err => {
4412
- console.error('[NPCClient] Background prediction generation failed:', err);
5166
+ this.logger.error('Background prediction generation failed:', err);
4413
5167
  });
4414
5168
  }
4415
5169
  // ===== Main API - Talk Methods =====
@@ -4491,7 +5245,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4491
5245
  * @deprecated Use talkWithActions instead for NPC decision-making with actions
4492
5246
  */
4493
5247
  async talkStructured(message, schemaName) {
4494
- console.warn('[NPCClient] talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
5248
+ this.logger.warn('talkStructured is deprecated. Use talkWithActions instead for NPC decision-making with actions.');
4495
5249
  // Add user message to history
4496
5250
  const userMessage = { role: 'user', content: message };
4497
5251
  this.history.push(userMessage);
@@ -4762,7 +5516,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4762
5516
  */
4763
5517
  appendChatMessage(role, content) {
4764
5518
  if (!role || !content) {
4765
- console.warn('[NPCClient] Role and content cannot be empty');
5519
+ this.logger.warn('Role and content cannot be empty');
4766
5520
  return;
4767
5521
  }
4768
5522
  this.appendMessage({ role: role, content });
@@ -4813,7 +5567,7 @@ Output ONLY a JSON array of ${predictionNum} strings, nothing else:
4813
5567
  return true;
4814
5568
  }
4815
5569
  catch (error) {
4816
- console.error('[NPCClient] Failed to load history:', error);
5570
+ this.logger.error('Failed to load history:', error);
4817
5571
  return false;
4818
5572
  }
4819
5573
  }
@@ -4839,6 +5593,7 @@ class AIContextManager extends EventEmitter {
4839
5593
  this.npcStates = new Map();
4840
5594
  this.autoCompactTimer = null;
4841
5595
  this.chatClientFactory = null;
5596
+ this.logger = Logger.getLogger('AIContextManager');
4842
5597
  this.config = {
4843
5598
  enableAutoCompact: (_a = config === null || config === void 0 ? void 0 : config.enableAutoCompact) !== null && _a !== void 0 ? _a : false,
4844
5599
  autoCompactMinMessages: (_b = config === null || config === void 0 ? void 0 : config.autoCompactMinMessages) !== null && _b !== void 0 ? _b : 20,
@@ -5005,21 +5760,21 @@ class AIContextManager extends EventEmitter {
5005
5760
  */
5006
5761
  async compactConversation(npc) {
5007
5762
  if (!npc) {
5008
- console.warn('[AIContextManager] Cannot compact: NPC is null');
5763
+ this.logger.warn('Cannot compact: NPC is null');
5009
5764
  return false;
5010
5765
  }
5011
5766
  if (!this.chatClientFactory) {
5012
- console.error('[AIContextManager] Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
5767
+ this.logger.error('Cannot compact: No chat client factory set. Call setChatClientFactory() first.');
5013
5768
  return false;
5014
5769
  }
5015
5770
  const history = npc.getHistory();
5016
5771
  const nonSystemMessages = history.filter(m => m.role !== 'system');
5017
5772
  if (nonSystemMessages.length < 2) {
5018
- console.log('[AIContextManager] Skipping compaction: not enough messages');
5773
+ this.logger.info('Skipping compaction: not enough messages');
5019
5774
  return false;
5020
5775
  }
5021
5776
  try {
5022
- console.log(`[AIContextManager] Starting compaction (${nonSystemMessages.length} messages)`);
5777
+ this.logger.info(`Starting compaction (${nonSystemMessages.length} messages)`);
5023
5778
  // Build conversation text for summarization
5024
5779
  const conversationText = nonSystemMessages
5025
5780
  .map(m => `${m.role}: ${m.content}`)
@@ -5044,7 +5799,7 @@ ${conversationText}`;
5044
5799
  });
5045
5800
  if (!result.content) {
5046
5801
  const error = 'Empty response from summarization';
5047
- console.error(`[AIContextManager] Compaction failed: ${error}`);
5802
+ this.logger.error(`Compaction failed: ${error}`);
5048
5803
  this.emit('compactionFailed', npc, error);
5049
5804
  return false;
5050
5805
  }
@@ -5057,13 +5812,13 @@ ${conversationText}`;
5057
5812
  state.isCompacted = true;
5058
5813
  state.compactionCount++;
5059
5814
  }
5060
- console.log(`[AIContextManager] Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
5815
+ this.logger.info(`Compaction completed. Summary: ${result.content.substring(0, 100)}...`);
5061
5816
  this.emit('npcCompacted', npc);
5062
5817
  return true;
5063
5818
  }
5064
5819
  catch (error) {
5065
5820
  const errorMessage = error instanceof Error ? error.message : String(error);
5066
- console.error(`[AIContextManager] Compaction error: ${errorMessage}`);
5821
+ this.logger.error(`Compaction error: ${errorMessage}`);
5067
5822
  this.emit('compactionFailed', npc, errorMessage);
5068
5823
  return false;
5069
5824
  }
@@ -5077,7 +5832,7 @@ ${conversationText}`;
5077
5832
  if (eligibleNpcs.length === 0) {
5078
5833
  return 0;
5079
5834
  }
5080
- console.log(`[AIContextManager] Compacting ${eligibleNpcs.length} eligible NPCs`);
5835
+ this.logger.info(`Compacting ${eligibleNpcs.length} eligible NPCs`);
5081
5836
  let successCount = 0;
5082
5837
  for (const npc of eligibleNpcs) {
5083
5838
  const success = await this.compactConversation(npc);
@@ -5117,7 +5872,7 @@ ${conversationText}`;
5117
5872
  for (const npc of eligibleNpcs) {
5118
5873
  // Fire and forget - don't block
5119
5874
  this.compactConversation(npc).catch(err => {
5120
- console.error('[AIContextManager] Auto-compact error:', err);
5875
+ this.logger.error('Auto-compact error:', err);
5121
5876
  });
5122
5877
  }
5123
5878
  }
@@ -5164,6 +5919,7 @@ const defaultContextManager = AIContextManager.getInstance();
5164
5919
  class SchemaLibrary {
5165
5920
  constructor(initialSchemas) {
5166
5921
  this.schemas = new Map();
5922
+ this.logger = Logger.getLogger('SchemaLibrary');
5167
5923
  if (initialSchemas) {
5168
5924
  for (const entry of initialSchemas) {
5169
5925
  this.addSchema(entry);
@@ -5183,7 +5939,7 @@ class SchemaLibrary {
5183
5939
  throw new Error(`[SchemaLibrary] Schema '${entry.name}' must have a schema definition`);
5184
5940
  }
5185
5941
  if (this.schemas.has(entry.name)) {
5186
- console.warn(`[SchemaLibrary] Schema '${entry.name}' already exists, overwriting`);
5942
+ this.logger.warn(`Schema '${entry.name}' already exists, overwriting`);
5187
5943
  }
5188
5944
  // Validate JSON schema structure
5189
5945
  if (!this.isValidSchema(entry.schema)) {
@@ -5351,6 +6107,9 @@ class PlayKitSDK extends EventEmitter {
5351
6107
  this.initialized = false;
5352
6108
  this.devTokenIndicator = null;
5353
6109
  this.config = Object.assign({ defaultChatModel: 'gpt-4o-mini', defaultImageModel: 'dall-e-3', debug: false }, config);
6110
+ // Initialize logging system
6111
+ this.initializeLogging(this.config);
6112
+ this.logger = Logger.getLogger('PlayKitSDK');
5354
6113
  // Initialize managers and providers
5355
6114
  this.authManager = new AuthManager(this.config);
5356
6115
  this.playerClient = new PlayerClient(this.authManager, this.config, this.config.recharge);
@@ -5370,21 +6129,19 @@ class PlayKitSDK extends EventEmitter {
5370
6129
  // Forward authentication events
5371
6130
  this.authManager.on('authenticated', (authState) => {
5372
6131
  this.emit('authenticated', authState);
5373
- if (this.config.debug) {
5374
- console.log('[PlayKitSDK] Authenticated', authState);
5375
- }
6132
+ this.logger.debug('Authenticated', authState);
5376
6133
  });
5377
6134
  this.authManager.on('unauthenticated', () => {
5378
6135
  this.emit('unauthenticated');
5379
- if (this.config.debug) {
5380
- console.log('[PlayKitSDK] Not authenticated');
5381
- }
6136
+ this.logger.debug('Not authenticated');
5382
6137
  });
5383
6138
  this.authManager.on('error', (error) => {
5384
6139
  this.emit('error', error);
5385
- if (this.config.debug) {
5386
- console.error('[PlayKitSDK] Auth error', error);
5387
- }
6140
+ this.logger.error('Auth error', error);
6141
+ });
6142
+ this.authManager.on('token_refreshed', (authState) => {
6143
+ this.emit('token_refreshed', authState);
6144
+ this.logger.debug('Token refreshed', authState);
5388
6145
  });
5389
6146
  // Forward recharge events
5390
6147
  this.playerClient.on('recharge_opened', () => this.emit('recharge_opened'));
@@ -5402,9 +6159,7 @@ class PlayKitSDK extends EventEmitter {
5402
6159
  */
5403
6160
  async initialize() {
5404
6161
  if (this.initialized) {
5405
- if (this.config.debug) {
5406
- console.warn('[PlayKitSDK] Already initialized');
5407
- }
6162
+ this.logger.warn('Already initialized');
5408
6163
  return;
5409
6164
  }
5410
6165
  try {
@@ -5418,28 +6173,20 @@ class PlayKitSDK extends EventEmitter {
5418
6173
  if (this.authManager.isAuthenticated()) {
5419
6174
  try {
5420
6175
  await this.playerClient.getPlayerInfo();
5421
- if (this.config.debug) {
5422
- console.log('[PlayKitSDK] Token validated and user info fetched');
5423
- }
6176
+ this.logger.debug('Token validated and user info fetched');
5424
6177
  }
5425
6178
  catch (error) {
5426
6179
  // If token is invalid, logout and restart auth flow
5427
- if (this.config.debug) {
5428
- console.error('[PlayKitSDK] Token validation failed:', error);
5429
- }
6180
+ this.logger.error('Token validation failed:', error);
5430
6181
  await this.authManager.logout();
5431
6182
  // Auto-restart login flow in browser environment
5432
6183
  if (typeof window !== 'undefined') {
5433
- if (this.config.debug) {
5434
- console.log('[PlayKitSDK] Restarting authentication flow...');
5435
- }
6184
+ this.logger.debug('Restarting authentication flow...');
5436
6185
  const authMethod = this.config.authMethod || 'device';
5437
6186
  await this.authManager.startAuthFlow(authMethod);
5438
6187
  // Retry getting player info after re-authentication
5439
6188
  await this.playerClient.getPlayerInfo();
5440
- if (this.config.debug) {
5441
- console.log('[PlayKitSDK] Re-authentication successful, token validated');
5442
- }
6189
+ this.logger.debug('Re-authentication successful, token validated');
5443
6190
  }
5444
6191
  else {
5445
6192
  throw new Error('Token validation failed: ' + (error instanceof Error ? error.message : String(error)));
@@ -5447,9 +6194,7 @@ class PlayKitSDK extends EventEmitter {
5447
6194
  }
5448
6195
  }
5449
6196
  this.emit('ready');
5450
- if (this.config.debug) {
5451
- console.log('[PlayKitSDK] Initialized successfully');
5452
- }
6197
+ this.logger.debug('Initialized successfully');
5453
6198
  }
5454
6199
  catch (error) {
5455
6200
  this.emit('error', error);
@@ -5512,15 +6257,11 @@ class PlayKitSDK extends EventEmitter {
5512
6257
  // Verify token validity and fetch user info
5513
6258
  try {
5514
6259
  await this.playerClient.getPlayerInfo();
5515
- if (this.config.debug) {
5516
- console.log('[PlayKitSDK] Login successful, token validated and user info fetched');
5517
- }
6260
+ this.logger.debug('Login successful, token validated and user info fetched');
5518
6261
  }
5519
6262
  catch (error) {
5520
6263
  // If token is invalid, logout and re-throw error
5521
- if (this.config.debug) {
5522
- console.error('[PlayKitSDK] Token validation failed after login:', error);
5523
- }
6264
+ this.logger.error('Token validation failed after login:', error);
5524
6265
  await this.authManager.logout();
5525
6266
  throw new Error('Token validation failed: ' + (error instanceof Error ? error.message : String(error)));
5526
6267
  }
@@ -5628,9 +6369,38 @@ class PlayKitSDK extends EventEmitter {
5628
6369
  }
5629
6370
  /**
5630
6371
  * Enable or disable debug mode
6372
+ * @deprecated Use configureLogging() instead
5631
6373
  */
5632
6374
  setDebug(enabled) {
5633
6375
  this.config.debug = enabled;
6376
+ Logger.setGlobalLevel(enabled ? exports.LogLevel.DEBUG : exports.LogLevel.WARN);
6377
+ }
6378
+ /**
6379
+ * Configure the logging system
6380
+ * @param config Logging configuration
6381
+ */
6382
+ configureLogging(config) {
6383
+ Logger.configure(config);
6384
+ }
6385
+ /**
6386
+ * Get a logger instance for external use
6387
+ * @param source The source/module identifier
6388
+ */
6389
+ static getLogger(source) {
6390
+ return Logger.getLogger(source);
6391
+ }
6392
+ /**
6393
+ * Initialize logging system based on config
6394
+ */
6395
+ initializeLogging(config) {
6396
+ // Handle legacy debug option for backwards compatibility
6397
+ if (config.debug !== undefined && config.logging === undefined) {
6398
+ Logger.setGlobalLevel(config.debug ? exports.LogLevel.DEBUG : exports.LogLevel.WARN);
6399
+ }
6400
+ // Apply new logging config
6401
+ if (config.logging) {
6402
+ Logger.configure(config.logging);
6403
+ }
5634
6404
  }
5635
6405
  /**
5636
6406
  * Show insufficient balance modal
@@ -5720,6 +6490,45 @@ class PlayKitSDK extends EventEmitter {
5720
6490
  cancelLogin() {
5721
6491
  this.authManager.cancelDeviceAuthFlow();
5722
6492
  }
6493
+ // ============================================================
6494
+ // Token Refresh Methods
6495
+ // ============================================================
6496
+ /**
6497
+ * Check if the current token is expired
6498
+ */
6499
+ isTokenExpired() {
6500
+ return this.authManager.isTokenExpired();
6501
+ }
6502
+ /**
6503
+ * Check if the access token can be refreshed
6504
+ * @returns true if a valid refresh token exists
6505
+ */
6506
+ canRefreshToken() {
6507
+ return this.authManager.canRefresh();
6508
+ }
6509
+ /**
6510
+ * Manually refresh the access token using the stored refresh token.
6511
+ *
6512
+ * Note: In browser mode, token refresh is handled automatically before API calls.
6513
+ * This method is useful for:
6514
+ * - Proactively refreshing tokens before they expire
6515
+ * - Server-side applications managing their own token lifecycle
6516
+ *
6517
+ * @returns Promise resolving to TokenRefreshResult with new tokens
6518
+ * @throws PlayKitError if no refresh token is available or refresh fails
6519
+ *
6520
+ * @example
6521
+ * ```ts
6522
+ * // Manual refresh before a long operation
6523
+ * if (sdk.isTokenExpired() && sdk.canRefreshToken()) {
6524
+ * const result = await sdk.refreshToken();
6525
+ * console.log('Token refreshed, expires in:', result.expiresIn, 'seconds');
6526
+ * }
6527
+ * ```
6528
+ */
6529
+ async refreshToken() {
6530
+ return this.authManager.refreshToken();
6531
+ }
5723
6532
  }
5724
6533
 
5725
6534
  /**
@@ -5868,9 +6677,14 @@ const defaultTokenValidator = new TokenValidator();
5868
6677
  exports.AIContextManager = AIContextManager;
5869
6678
  exports.AuthFlowManager = AuthFlowManager;
5870
6679
  exports.AuthManager = AuthManager;
6680
+ exports.BrowserStorage = BrowserStorage;
6681
+ exports.BufferLogHandler = BufferLogHandler;
6682
+ exports.CallbackLogHandler = CallbackLogHandler;
5871
6683
  exports.ChatClient = ChatClient;
5872
6684
  exports.DeviceAuthFlowManager = DeviceAuthFlowManager;
5873
6685
  exports.ImageClient = ImageClient;
6686
+ exports.Logger = Logger;
6687
+ exports.MemoryStorage = MemoryStorage;
5874
6688
  exports.NPCClient = NPCClient;
5875
6689
  exports.PlayKitSDK = PlayKitSDK;
5876
6690
  exports.PlayerClient = PlayerClient;
@@ -5881,9 +6695,11 @@ exports.TokenStorage = TokenStorage;
5881
6695
  exports.TokenValidator = TokenValidator;
5882
6696
  exports.TranscriptionClient = TranscriptionClient;
5883
6697
  exports.createMultimodalMessage = createMultimodalMessage;
6698
+ exports.createStorage = createStorage;
5884
6699
  exports.createTextMessage = createTextMessage;
5885
6700
  exports.default = PlayKitSDK;
5886
6701
  exports.defaultContextManager = defaultContextManager;
5887
6702
  exports.defaultSchemaLibrary = defaultSchemaLibrary;
5888
6703
  exports.defaultTokenValidator = defaultTokenValidator;
6704
+ exports.isLocalStorageAvailable = isLocalStorageAvailable;
5889
6705
  //# sourceMappingURL=playkit-sdk.cjs.js.map