playkit-sdk 1.2.11 → 1.2.13

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.11
2
+ * playkit-sdk v1.2.13
3
3
  * PlayKit SDK for JavaScript
4
4
  * @license SEE LICENSE IN LICENSE
5
5
  */
@@ -2398,53 +2398,56 @@
2398
2398
  *
2399
2399
  * @param options - Flow options
2400
2400
  * @returns Promise resolving to DeviceAuthResult with tokens
2401
- * @throws PlayKitError if a flow is already in progress
2402
2401
  */
2403
2402
  async startFlow(options = {}) {
2404
- // Prevent duplicate flows from running simultaneously
2405
- if (DeviceAuthFlowManager.isFlowInProgress) {
2406
- this.logger.warn('Device auth flow already in progress, ignoring duplicate call');
2407
- throw new PlayKitError('Device auth flow already in progress', 'FLOW_IN_PROGRESS');
2408
- }
2409
- DeviceAuthFlowManager.isFlowInProgress = true;
2403
+ // If a flow is already in progress, return the shared promise so all callers get the same result
2404
+ if (DeviceAuthFlowManager.currentFlowPromise) {
2405
+ this.logger.debug('Device auth flow already in progress, waiting for existing flow');
2406
+ return DeviceAuthFlowManager.currentFlowPromise;
2407
+ }
2408
+ // Store the flow promise so subsequent calls can await the same result
2409
+ const flowPromise = this.executeFlow(options);
2410
+ DeviceAuthFlowManager.currentFlowPromise = flowPromise;
2410
2411
  DeviceAuthFlowManager.activeInstance = this;
2412
+ try {
2413
+ return await flowPromise;
2414
+ }
2415
+ finally {
2416
+ // Clean up static state when flow completes (success or failure)
2417
+ DeviceAuthFlowManager.currentFlowPromise = null;
2418
+ DeviceAuthFlowManager.activeInstance = null;
2419
+ }
2420
+ }
2421
+ /**
2422
+ * Internal method that executes the actual device auth flow
2423
+ * @private
2424
+ */
2425
+ async executeFlow(options = {}) {
2411
2426
  this.aborted = false;
2412
2427
  this.currentLanguage = this.detectLanguage();
2413
2428
  const scope = options.scope || 'player:play';
2414
2429
  const openBrowser = options.openBrowser || this.defaultOpenBrowser.bind(this);
2415
- // Helper to reset static flags when flow completes early (before polling)
2416
- const resetFlowStateEarly = () => {
2417
- DeviceAuthFlowManager.isFlowInProgress = false;
2418
- DeviceAuthFlowManager.activeInstance = null;
2419
- };
2420
2430
  // Generate PKCE parameters (outside try block so codeVerifier is accessible for polling)
2421
2431
  const codeVerifier = this.generateCodeVerifier();
2422
2432
  const codeChallenge = await this.generateCodeChallenge(codeVerifier);
2423
- let initData;
2424
- try {
2425
- // Step 1: Initiate device auth session
2426
- const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2427
- method: 'POST',
2428
- headers: {
2429
- 'Content-Type': 'application/json',
2430
- },
2431
- body: JSON.stringify({
2432
- game_id: this.gameId,
2433
- code_challenge: codeChallenge,
2434
- code_challenge_method: 'S256',
2435
- scope,
2436
- }),
2437
- });
2438
- if (!initResponse.ok) {
2439
- const error = await initResponse.json().catch(() => ({ error_description: 'Failed to initiate device auth' }));
2440
- throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
2441
- }
2442
- initData = await initResponse.json();
2443
- }
2444
- catch (err) {
2445
- resetFlowStateEarly();
2446
- throw err;
2433
+ // Step 1: Initiate device auth session
2434
+ const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2435
+ method: 'POST',
2436
+ headers: {
2437
+ 'Content-Type': 'application/json',
2438
+ },
2439
+ body: JSON.stringify({
2440
+ game_id: this.gameId,
2441
+ code_challenge: codeChallenge,
2442
+ code_challenge_method: 'S256',
2443
+ scope,
2444
+ }),
2445
+ });
2446
+ if (!initResponse.ok) {
2447
+ const error = await initResponse.json().catch(() => ({ error_description: 'Failed to initiate device auth' }));
2448
+ throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
2447
2449
  }
2450
+ const initData = await initResponse.json();
2448
2451
  const { session_id, auth_url, poll_interval, expires_in, game } = initData;
2449
2452
  // Update poll interval from server
2450
2453
  if (poll_interval) {
@@ -2476,7 +2479,6 @@
2476
2479
  }
2477
2480
  catch (err) {
2478
2481
  this.closeModal();
2479
- resetFlowStateEarly();
2480
2482
  throw err;
2481
2483
  }
2482
2484
  // User clicked login - open browser (in user click context, won't be blocked)
@@ -2488,23 +2490,16 @@
2488
2490
  }
2489
2491
  // Step 3: Poll for authorization
2490
2492
  const expiresAt = Date.now() + (expires_in || 600) * 1000;
2491
- // Helper to reset static flags when flow completes
2492
- const resetFlowState = () => {
2493
- DeviceAuthFlowManager.isFlowInProgress = false;
2494
- DeviceAuthFlowManager.activeInstance = null;
2495
- };
2496
2493
  return new Promise((resolve, reject) => {
2497
2494
  const poll = async () => {
2498
2495
  if (this.aborted) {
2499
2496
  this.closeModal();
2500
- resetFlowState();
2501
2497
  reject(new PlayKitError('Device auth flow was cancelled', 'CANCELLED'));
2502
2498
  return;
2503
2499
  }
2504
2500
  // Check if session expired
2505
2501
  if (Date.now() >= expiresAt) {
2506
2502
  this.showModalError('expired', () => { });
2507
- resetFlowState();
2508
2503
  reject(new PlayKitError('Device auth session expired', 'EXPIRED'));
2509
2504
  return;
2510
2505
  }
@@ -2532,7 +2527,6 @@
2532
2527
  }
2533
2528
  this.emit('poll_status', 'authorized');
2534
2529
  this.emit('authenticated', pollData);
2535
- resetFlowState();
2536
2530
  resolve({
2537
2531
  access_token: pollData.access_token,
2538
2532
  token_type: pollData.token_type,
@@ -2561,7 +2555,6 @@
2561
2555
  options.onPollStatus('denied');
2562
2556
  }
2563
2557
  this.emit('poll_status', 'denied');
2564
- resetFlowState();
2565
2558
  reject(new PlayKitError(pollData.error_description || 'User denied authorization', 'ACCESS_DENIED'));
2566
2559
  }
2567
2560
  else if (error === 'expired_token') {
@@ -2570,12 +2563,10 @@
2570
2563
  options.onPollStatus('expired');
2571
2564
  }
2572
2565
  this.emit('poll_status', 'expired');
2573
- resetFlowState();
2574
2566
  reject(new PlayKitError(pollData.error_description || 'Session expired', 'EXPIRED'));
2575
2567
  }
2576
2568
  else {
2577
2569
  this.showModalError('failed', () => { });
2578
- resetFlowState();
2579
2570
  reject(new PlayKitError(pollData.error_description || 'Device auth failed', error || 'POLL_FAILED', pollResponse.status));
2580
2571
  }
2581
2572
  }
@@ -2600,11 +2591,6 @@
2600
2591
  clearTimeout(this.pollTimeoutId);
2601
2592
  this.pollTimeoutId = null;
2602
2593
  }
2603
- // Reset static flags if this is the active instance
2604
- if (DeviceAuthFlowManager.activeInstance === this) {
2605
- DeviceAuthFlowManager.isFlowInProgress = false;
2606
- DeviceAuthFlowManager.activeInstance = null;
2607
- }
2608
2594
  this.emit('cancelled');
2609
2595
  }
2610
2596
  /**
@@ -2619,7 +2605,7 @@
2619
2605
  * Check if a device auth flow is currently in progress (static method)
2620
2606
  */
2621
2607
  static isInProgress() {
2622
- return DeviceAuthFlowManager.isFlowInProgress;
2608
+ return DeviceAuthFlowManager.currentFlowPromise !== null;
2623
2609
  }
2624
2610
  /**
2625
2611
  * Initiate device auth without opening browser or showing UI.
@@ -2770,8 +2756,8 @@
2770
2756
  });
2771
2757
  }
2772
2758
  }
2773
- /** Static flag to prevent multiple flows running simultaneously */
2774
- DeviceAuthFlowManager.isFlowInProgress = false;
2759
+ /** Shared promise for the current flow - allows multiple callers to await the same result */
2760
+ DeviceAuthFlowManager.currentFlowPromise = null;
2775
2761
  /** Reference to the currently active instance */
2776
2762
  DeviceAuthFlowManager.activeInstance = null;
2777
2763
 
@@ -2789,6 +2775,10 @@
2789
2775
  this.authFlowManager = null;
2790
2776
  this.deviceAuthFlowManager = null;
2791
2777
  this.logger = Logger.getLogger('AuthManager');
2778
+ /** Shared promise for current device auth flow - allows multiple callers to await the same result */
2779
+ this.currentDeviceAuthFlowPromise = null;
2780
+ /** Shared promise for current auth flow (startAuthFlow) - allows multiple callers to await the same result */
2781
+ this.currentAuthFlowPromise = null;
2792
2782
  this.config = config;
2793
2783
  // Create TokenStorage with appropriate mode for server vs browser environment
2794
2784
  this.storage = new TokenStorage({
@@ -2897,12 +2887,27 @@
2897
2887
  * @deprecated 'headless' authentication is deprecated and will be removed in v2.0. Use 'device' instead.
2898
2888
  */
2899
2889
  async startAuthFlow(authMethod = 'device') {
2900
- var _a, _b;
2901
- if (this.authFlowManager || this.deviceAuthFlowManager) {
2902
- // Already in progress
2903
- this.logger.warn('Auth flow already in progress, ignoring duplicate call');
2904
- return;
2890
+ // If a flow is already in progress, return the shared promise so all callers await the same result
2891
+ if (this.currentAuthFlowPromise) {
2892
+ this.logger.debug('Auth flow already in progress, waiting for existing flow');
2893
+ return this.currentAuthFlowPromise;
2894
+ }
2895
+ // Store the flow promise so subsequent calls can await the same result
2896
+ const flowPromise = this.executeAuthFlow(authMethod);
2897
+ this.currentAuthFlowPromise = flowPromise;
2898
+ try {
2899
+ return await flowPromise;
2900
+ }
2901
+ finally {
2902
+ this.currentAuthFlowPromise = null;
2905
2903
  }
2904
+ }
2905
+ /**
2906
+ * Internal method that executes the actual auth flow
2907
+ * @private
2908
+ */
2909
+ async executeAuthFlow(authMethod = 'device') {
2910
+ var _a, _b;
2906
2911
  // Deprecation warning for headless auth
2907
2912
  if (authMethod === 'headless') {
2908
2913
  this.logger.warn('"headless" authentication is deprecated and will be removed in v2.0. ' +
@@ -3155,10 +3160,27 @@
3155
3160
  * ```
3156
3161
  */
3157
3162
  async startDeviceAuthFlow(options = {}) {
3158
- var _a;
3159
- if (this.deviceAuthFlowManager) {
3160
- throw new PlayKitError('Device auth flow already in progress', 'FLOW_IN_PROGRESS');
3163
+ // If a flow is already in progress, return the shared promise so all callers get the same result
3164
+ if (this.currentDeviceAuthFlowPromise) {
3165
+ this.logger.debug('Device auth flow already in progress, waiting for existing flow');
3166
+ return this.currentDeviceAuthFlowPromise;
3167
+ }
3168
+ // Store the flow promise so subsequent calls can await the same result
3169
+ const flowPromise = this.executeDeviceAuthFlow(options);
3170
+ this.currentDeviceAuthFlowPromise = flowPromise;
3171
+ try {
3172
+ return await flowPromise;
3161
3173
  }
3174
+ finally {
3175
+ this.currentDeviceAuthFlowPromise = null;
3176
+ }
3177
+ }
3178
+ /**
3179
+ * Internal method that executes the actual device auth flow
3180
+ * @private
3181
+ */
3182
+ async executeDeviceAuthFlow(options = {}) {
3183
+ var _a;
3162
3184
  try {
3163
3185
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
3164
3186
  const result = await this.deviceAuthFlowManager.startFlow(options);
@@ -3211,8 +3233,10 @@
3211
3233
  * ```
3212
3234
  */
3213
3235
  async initiateDeviceAuth(scope = 'player:play') {
3236
+ // If there's an existing manager, clean it up first (allows restarting flow)
3214
3237
  if (this.deviceAuthFlowManager) {
3215
- throw new PlayKitError('Device auth already in progress', 'FLOW_IN_PROGRESS');
3238
+ this.logger.debug('Cleaning up existing device auth manager before initiating new flow');
3239
+ this.deviceAuthFlowManager.destroy();
3216
3240
  }
3217
3241
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
3218
3242
  return this.deviceAuthFlowManager.initiateAuth(scope);