playkit-sdk 1.2.8-beta.2 → 1.2.8-beta.4

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.2
2
+ * playkit-sdk v1.2.8-beta.4
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
@@ -2394,6 +2394,7 @@ class DeviceAuthFlowManager extends EventEmitter {
2394
2394
  // @ts-ignore - replaced at build time
2395
2395
  const DEFAULT_BASE_URL$5 = "https://api.playkit.ai";
2396
2396
  const JWT_EXCHANGE_ENDPOINT = '/api/external/exchange-jwt';
2397
+ const TOKEN_REFRESH_ENDPOINT = '/api/auth/refresh';
2397
2398
  class AuthManager extends EventEmitter {
2398
2399
  constructor(config) {
2399
2400
  super();
@@ -2445,6 +2446,21 @@ class AuthManager extends EventEmitter {
2445
2446
  this.emit('authenticated', this.authState);
2446
2447
  return;
2447
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
+ }
2448
2464
  }
2449
2465
  // Check if player JWT was provided
2450
2466
  if (this.config.playerJWT) {
@@ -2494,12 +2510,14 @@ class AuthManager extends EventEmitter {
2494
2510
  const result = await this.deviceAuthFlowManager.startFlow({
2495
2511
  scope: 'player:play',
2496
2512
  });
2497
- // Update auth state with the player token
2513
+ // Update auth state with the player token and refresh token
2498
2514
  this.authState = {
2499
2515
  isAuthenticated: true,
2500
2516
  token: result.access_token,
2501
2517
  tokenType: 'player',
2502
2518
  expiresAt: Date.now() + result.expires_in * 1000,
2519
+ refreshToken: result.refresh_token,
2520
+ refreshExpiresAt: Date.now() + result.refresh_expires_in * 1000,
2503
2521
  };
2504
2522
  // Save to storage
2505
2523
  await this.storage.saveAuthState(this.config.gameId, this.authState);
@@ -2598,6 +2616,58 @@ class AuthManager extends EventEmitter {
2598
2616
  return false;
2599
2617
  return Date.now() >= this.authState.expiresAt;
2600
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
+ }
2601
2671
  /**
2602
2672
  * Logout and clear authentication
2603
2673
  */
@@ -2639,12 +2709,14 @@ class AuthManager extends EventEmitter {
2639
2709
  try {
2640
2710
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
2641
2711
  const result = await this.deviceAuthFlowManager.startFlow(options);
2642
- // Update auth state with the token
2712
+ // Update auth state with the token and refresh token
2643
2713
  this.authState = {
2644
2714
  isAuthenticated: true,
2645
2715
  token: result.access_token,
2646
2716
  tokenType: result.scope === 'developer:full' ? 'developer' : 'player',
2647
2717
  expiresAt: Date.now() + result.expires_in * 1000,
2718
+ refreshToken: result.refresh_token,
2719
+ refreshExpiresAt: Date.now() + result.refresh_expires_in * 1000,
2648
2720
  };
2649
2721
  // Save to storage
2650
2722
  await this.storage.saveAuthState(this.config.gameId, this.authState);
@@ -2707,12 +2779,14 @@ class AuthManager extends EventEmitter {
2707
2779
  }
2708
2780
  try {
2709
2781
  const result = await this.deviceAuthFlowManager.pollForToken(sessionId, codeVerifier, options);
2710
- // Update auth state with the token
2782
+ // Update auth state with the token and refresh token
2711
2783
  this.authState = {
2712
2784
  isAuthenticated: true,
2713
2785
  token: result.access_token,
2714
2786
  tokenType: result.scope === 'developer:full' ? 'developer' : 'player',
2715
2787
  expiresAt: Date.now() + result.expires_in * 1000,
2788
+ refreshToken: result.refresh_token,
2789
+ refreshExpiresAt: Date.now() + result.refresh_expires_in * 1000,
2716
2790
  };
2717
2791
  // Save to storage
2718
2792
  await this.storage.saveAuthState(this.config.gameId, this.authState);
@@ -2725,6 +2799,91 @@ class AuthManager extends EventEmitter {
2725
2799
  this.deviceAuthFlowManager = null;
2726
2800
  }
2727
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
+ }
2728
2887
  }
2729
2888
 
2730
2889
  /**
@@ -3578,6 +3737,8 @@ class ChatProvider {
3578
3737
  */
3579
3738
  async chatCompletion(chatConfig) {
3580
3739
  var _a;
3740
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
3741
+ await this.authManager.ensureValidToken();
3581
3742
  const token = this.authManager.getToken();
3582
3743
  if (!token) {
3583
3744
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3635,6 +3796,8 @@ class ChatProvider {
3635
3796
  */
3636
3797
  async chatCompletionStream(chatConfig) {
3637
3798
  var _a;
3799
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
3800
+ await this.authManager.ensureValidToken();
3638
3801
  const token = this.authManager.getToken();
3639
3802
  if (!token) {
3640
3803
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3823,6 +3986,8 @@ class ChatProvider {
3823
3986
  */
3824
3987
  async generateStructured(schemaName, prompt, model, temperature, schema, schemaDescription) {
3825
3988
  var _a;
3989
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
3990
+ await this.authManager.ensureValidToken();
3826
3991
  const token = this.authManager.getToken();
3827
3992
  if (!token) {
3828
3993
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -3912,6 +4077,8 @@ class ImageProvider {
3912
4077
  * Generate one or more images
3913
4078
  */
3914
4079
  async generateImages(imageConfig) {
4080
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
4081
+ await this.authManager.ensureValidToken();
3915
4082
  const token = this.authManager.getToken();
3916
4083
  if (!token) {
3917
4084
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -4023,6 +4190,8 @@ class TranscriptionProvider {
4023
4190
  * Transcribe audio to text
4024
4191
  */
4025
4192
  async transcribe(transcriptionConfig) {
4193
+ // Ensure token is valid, auto-refresh if needed (browser mode only)
4194
+ await this.authManager.ensureValidToken();
4026
4195
  const token = this.authManager.getToken();
4027
4196
  if (!token) {
4028
4197
  throw new PlayKitError('Not authenticated', 'NOT_AUTHENTICATED');
@@ -5970,6 +6139,10 @@ class PlayKitSDK extends EventEmitter {
5970
6139
  this.emit('error', error);
5971
6140
  this.logger.error('Auth error', error);
5972
6141
  });
6142
+ this.authManager.on('token_refreshed', (authState) => {
6143
+ this.emit('token_refreshed', authState);
6144
+ this.logger.debug('Token refreshed', authState);
6145
+ });
5973
6146
  // Forward recharge events
5974
6147
  this.playerClient.on('recharge_opened', () => this.emit('recharge_opened'));
5975
6148
  this.playerClient.on('recharge_modal_shown', () => this.emit('recharge_modal_shown'));
@@ -5979,6 +6152,7 @@ class PlayKitSDK extends EventEmitter {
5979
6152
  this.playerClient.on('balance_updated', (credits) => this.emit('balance_updated', credits));
5980
6153
  this.playerClient.on('player_info_updated', (info) => this.emit('player_info_updated', info));
5981
6154
  this.playerClient.on('daily_credits_refreshed', (result) => this.emit('daily_credits_refreshed', result));
6155
+ this.playerClient.on('nickname_changed', (nickname) => this.emit('nickname_changed', nickname));
5982
6156
  }
5983
6157
  /**
5984
6158
  * Initialize the SDK
@@ -6270,6 +6444,25 @@ class PlayKitSDK extends EventEmitter {
6270
6444
  return playerInfo.credits;
6271
6445
  }
6272
6446
  // ============================================================
6447
+ // Player Profile Methods
6448
+ // ============================================================
6449
+ /**
6450
+ * Get player's nickname (cached)
6451
+ * @returns Nickname or null if not set
6452
+ */
6453
+ getNickname() {
6454
+ return this.playerClient.getNickname();
6455
+ }
6456
+ /**
6457
+ * Set player's nickname for the current game
6458
+ * @param nickname - 1-16 characters (letters, numbers, Chinese, underscores, spaces)
6459
+ * @returns SetNicknameResponse with success status and gameId
6460
+ * @throws PlayKitError if validation fails or token type is invalid
6461
+ */
6462
+ async setNickname(nickname) {
6463
+ return await this.playerClient.setNickname(nickname);
6464
+ }
6465
+ // ============================================================
6273
6466
  // Headless Device Auth Methods (for terminal/CLI environments)
6274
6467
  // ============================================================
6275
6468
  /**
@@ -6317,6 +6510,45 @@ class PlayKitSDK extends EventEmitter {
6317
6510
  cancelLogin() {
6318
6511
  this.authManager.cancelDeviceAuthFlow();
6319
6512
  }
6513
+ // ============================================================
6514
+ // Token Refresh Methods
6515
+ // ============================================================
6516
+ /**
6517
+ * Check if the current token is expired
6518
+ */
6519
+ isTokenExpired() {
6520
+ return this.authManager.isTokenExpired();
6521
+ }
6522
+ /**
6523
+ * Check if the access token can be refreshed
6524
+ * @returns true if a valid refresh token exists
6525
+ */
6526
+ canRefreshToken() {
6527
+ return this.authManager.canRefresh();
6528
+ }
6529
+ /**
6530
+ * Manually refresh the access token using the stored refresh token.
6531
+ *
6532
+ * Note: In browser mode, token refresh is handled automatically before API calls.
6533
+ * This method is useful for:
6534
+ * - Proactively refreshing tokens before they expire
6535
+ * - Server-side applications managing their own token lifecycle
6536
+ *
6537
+ * @returns Promise resolving to TokenRefreshResult with new tokens
6538
+ * @throws PlayKitError if no refresh token is available or refresh fails
6539
+ *
6540
+ * @example
6541
+ * ```ts
6542
+ * // Manual refresh before a long operation
6543
+ * if (sdk.isTokenExpired() && sdk.canRefreshToken()) {
6544
+ * const result = await sdk.refreshToken();
6545
+ * console.log('Token refreshed, expires in:', result.expiresIn, 'seconds');
6546
+ * }
6547
+ * ```
6548
+ */
6549
+ async refreshToken() {
6550
+ return this.authManager.refreshToken();
6551
+ }
6320
6552
  }
6321
6553
 
6322
6554
  /**