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.
- package/dist/playkit-sdk.cjs.js +933 -117
- package/dist/playkit-sdk.cjs.js.map +1 -1
- package/dist/playkit-sdk.d.ts +440 -9
- package/dist/playkit-sdk.esm.js +927 -118
- package/dist/playkit-sdk.esm.js.map +1 -1
- package/dist/playkit-sdk.umd.js +933 -117
- package/dist/playkit-sdk.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/playkit-sdk.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* playkit-sdk v1.2.8-beta.
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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 || !
|
|
733
|
+
if (!this.encryptionKey || !isWebCryptoAvailable()) {
|
|
160
734
|
// Fallback to plain storage if encryption unavailable
|
|
161
735
|
return data;
|
|
162
736
|
}
|
|
163
|
-
const iv =
|
|
164
|
-
const encoded =
|
|
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 || !
|
|
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
|
|
759
|
+
return textDecode(decrypted);
|
|
186
760
|
}
|
|
187
761
|
catch (error) {
|
|
188
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
797
|
+
this.storage.removeItem(key);
|
|
224
798
|
}
|
|
225
799
|
/**
|
|
226
800
|
* Clear all PlayKit data
|
|
227
801
|
*/
|
|
228
802
|
clearAll() {
|
|
229
|
-
|
|
803
|
+
const keys = this.storage.keys();
|
|
804
|
+
keys.forEach((key) => {
|
|
230
805
|
if (key.startsWith(STORAGE_KEY_PREFIX)) {
|
|
231
|
-
|
|
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
|
-
|
|
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
|
|
251
|
-
|
|
252
|
-
|
|
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 =
|
|
1223
|
-
|
|
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
|
|
1232
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5763
|
+
this.logger.warn('Cannot compact: NPC is null');
|
|
5009
5764
|
return false;
|
|
5010
5765
|
}
|
|
5011
5766
|
if (!this.chatClientFactory) {
|
|
5012
|
-
|
|
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
|
-
|
|
5773
|
+
this.logger.info('Skipping compaction: not enough messages');
|
|
5019
5774
|
return false;
|
|
5020
5775
|
}
|
|
5021
5776
|
try {
|
|
5022
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5386
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|