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
  */
@@ -2051,53 +2051,56 @@ class DeviceAuthFlowManager extends EventEmitter {
2051
2051
  *
2052
2052
  * @param options - Flow options
2053
2053
  * @returns Promise resolving to DeviceAuthResult with tokens
2054
- * @throws PlayKitError if a flow is already in progress
2055
2054
  */
2056
2055
  async startFlow(options = {}) {
2057
- // Prevent duplicate flows from running simultaneously
2058
- if (DeviceAuthFlowManager.isFlowInProgress) {
2059
- this.logger.warn('Device auth flow already in progress, ignoring duplicate call');
2060
- throw new PlayKitError('Device auth flow already in progress', 'FLOW_IN_PROGRESS');
2061
- }
2062
- DeviceAuthFlowManager.isFlowInProgress = true;
2056
+ // If a flow is already in progress, return the shared promise so all callers get the same result
2057
+ if (DeviceAuthFlowManager.currentFlowPromise) {
2058
+ this.logger.debug('Device auth flow already in progress, waiting for existing flow');
2059
+ return DeviceAuthFlowManager.currentFlowPromise;
2060
+ }
2061
+ // Store the flow promise so subsequent calls can await the same result
2062
+ const flowPromise = this.executeFlow(options);
2063
+ DeviceAuthFlowManager.currentFlowPromise = flowPromise;
2063
2064
  DeviceAuthFlowManager.activeInstance = this;
2065
+ try {
2066
+ return await flowPromise;
2067
+ }
2068
+ finally {
2069
+ // Clean up static state when flow completes (success or failure)
2070
+ DeviceAuthFlowManager.currentFlowPromise = null;
2071
+ DeviceAuthFlowManager.activeInstance = null;
2072
+ }
2073
+ }
2074
+ /**
2075
+ * Internal method that executes the actual device auth flow
2076
+ * @private
2077
+ */
2078
+ async executeFlow(options = {}) {
2064
2079
  this.aborted = false;
2065
2080
  this.currentLanguage = this.detectLanguage();
2066
2081
  const scope = options.scope || 'player:play';
2067
2082
  const openBrowser = options.openBrowser || this.defaultOpenBrowser.bind(this);
2068
- // Helper to reset static flags when flow completes early (before polling)
2069
- const resetFlowStateEarly = () => {
2070
- DeviceAuthFlowManager.isFlowInProgress = false;
2071
- DeviceAuthFlowManager.activeInstance = null;
2072
- };
2073
2083
  // Generate PKCE parameters (outside try block so codeVerifier is accessible for polling)
2074
2084
  const codeVerifier = this.generateCodeVerifier();
2075
2085
  const codeChallenge = await this.generateCodeChallenge(codeVerifier);
2076
- let initData;
2077
- try {
2078
- // Step 1: Initiate device auth session
2079
- const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2080
- method: 'POST',
2081
- headers: {
2082
- 'Content-Type': 'application/json',
2083
- },
2084
- body: JSON.stringify({
2085
- game_id: this.gameId,
2086
- code_challenge: codeChallenge,
2087
- code_challenge_method: 'S256',
2088
- scope,
2089
- }),
2090
- });
2091
- if (!initResponse.ok) {
2092
- const error = await initResponse.json().catch(() => ({ error_description: 'Failed to initiate device auth' }));
2093
- throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
2094
- }
2095
- initData = await initResponse.json();
2096
- }
2097
- catch (err) {
2098
- resetFlowStateEarly();
2099
- throw err;
2086
+ // Step 1: Initiate device auth session
2087
+ const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2088
+ method: 'POST',
2089
+ headers: {
2090
+ 'Content-Type': 'application/json',
2091
+ },
2092
+ body: JSON.stringify({
2093
+ game_id: this.gameId,
2094
+ code_challenge: codeChallenge,
2095
+ code_challenge_method: 'S256',
2096
+ scope,
2097
+ }),
2098
+ });
2099
+ if (!initResponse.ok) {
2100
+ const error = await initResponse.json().catch(() => ({ error_description: 'Failed to initiate device auth' }));
2101
+ throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
2100
2102
  }
2103
+ const initData = await initResponse.json();
2101
2104
  const { session_id, auth_url, poll_interval, expires_in, game } = initData;
2102
2105
  // Update poll interval from server
2103
2106
  if (poll_interval) {
@@ -2129,7 +2132,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2129
2132
  }
2130
2133
  catch (err) {
2131
2134
  this.closeModal();
2132
- resetFlowStateEarly();
2133
2135
  throw err;
2134
2136
  }
2135
2137
  // User clicked login - open browser (in user click context, won't be blocked)
@@ -2141,23 +2143,16 @@ class DeviceAuthFlowManager extends EventEmitter {
2141
2143
  }
2142
2144
  // Step 3: Poll for authorization
2143
2145
  const expiresAt = Date.now() + (expires_in || 600) * 1000;
2144
- // Helper to reset static flags when flow completes
2145
- const resetFlowState = () => {
2146
- DeviceAuthFlowManager.isFlowInProgress = false;
2147
- DeviceAuthFlowManager.activeInstance = null;
2148
- };
2149
2146
  return new Promise((resolve, reject) => {
2150
2147
  const poll = async () => {
2151
2148
  if (this.aborted) {
2152
2149
  this.closeModal();
2153
- resetFlowState();
2154
2150
  reject(new PlayKitError('Device auth flow was cancelled', 'CANCELLED'));
2155
2151
  return;
2156
2152
  }
2157
2153
  // Check if session expired
2158
2154
  if (Date.now() >= expiresAt) {
2159
2155
  this.showModalError('expired', () => { });
2160
- resetFlowState();
2161
2156
  reject(new PlayKitError('Device auth session expired', 'EXPIRED'));
2162
2157
  return;
2163
2158
  }
@@ -2185,7 +2180,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2185
2180
  }
2186
2181
  this.emit('poll_status', 'authorized');
2187
2182
  this.emit('authenticated', pollData);
2188
- resetFlowState();
2189
2183
  resolve({
2190
2184
  access_token: pollData.access_token,
2191
2185
  token_type: pollData.token_type,
@@ -2214,7 +2208,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2214
2208
  options.onPollStatus('denied');
2215
2209
  }
2216
2210
  this.emit('poll_status', 'denied');
2217
- resetFlowState();
2218
2211
  reject(new PlayKitError(pollData.error_description || 'User denied authorization', 'ACCESS_DENIED'));
2219
2212
  }
2220
2213
  else if (error === 'expired_token') {
@@ -2223,12 +2216,10 @@ class DeviceAuthFlowManager extends EventEmitter {
2223
2216
  options.onPollStatus('expired');
2224
2217
  }
2225
2218
  this.emit('poll_status', 'expired');
2226
- resetFlowState();
2227
2219
  reject(new PlayKitError(pollData.error_description || 'Session expired', 'EXPIRED'));
2228
2220
  }
2229
2221
  else {
2230
2222
  this.showModalError('failed', () => { });
2231
- resetFlowState();
2232
2223
  reject(new PlayKitError(pollData.error_description || 'Device auth failed', error || 'POLL_FAILED', pollResponse.status));
2233
2224
  }
2234
2225
  }
@@ -2253,11 +2244,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2253
2244
  clearTimeout(this.pollTimeoutId);
2254
2245
  this.pollTimeoutId = null;
2255
2246
  }
2256
- // Reset static flags if this is the active instance
2257
- if (DeviceAuthFlowManager.activeInstance === this) {
2258
- DeviceAuthFlowManager.isFlowInProgress = false;
2259
- DeviceAuthFlowManager.activeInstance = null;
2260
- }
2261
2247
  this.emit('cancelled');
2262
2248
  }
2263
2249
  /**
@@ -2272,7 +2258,7 @@ class DeviceAuthFlowManager extends EventEmitter {
2272
2258
  * Check if a device auth flow is currently in progress (static method)
2273
2259
  */
2274
2260
  static isInProgress() {
2275
- return DeviceAuthFlowManager.isFlowInProgress;
2261
+ return DeviceAuthFlowManager.currentFlowPromise !== null;
2276
2262
  }
2277
2263
  /**
2278
2264
  * Initiate device auth without opening browser or showing UI.
@@ -2423,8 +2409,8 @@ class DeviceAuthFlowManager extends EventEmitter {
2423
2409
  });
2424
2410
  }
2425
2411
  }
2426
- /** Static flag to prevent multiple flows running simultaneously */
2427
- DeviceAuthFlowManager.isFlowInProgress = false;
2412
+ /** Shared promise for the current flow - allows multiple callers to await the same result */
2413
+ DeviceAuthFlowManager.currentFlowPromise = null;
2428
2414
  /** Reference to the currently active instance */
2429
2415
  DeviceAuthFlowManager.activeInstance = null;
2430
2416
 
@@ -2442,6 +2428,10 @@ class AuthManager extends EventEmitter {
2442
2428
  this.authFlowManager = null;
2443
2429
  this.deviceAuthFlowManager = null;
2444
2430
  this.logger = Logger.getLogger('AuthManager');
2431
+ /** Shared promise for current device auth flow - allows multiple callers to await the same result */
2432
+ this.currentDeviceAuthFlowPromise = null;
2433
+ /** Shared promise for current auth flow (startAuthFlow) - allows multiple callers to await the same result */
2434
+ this.currentAuthFlowPromise = null;
2445
2435
  this.config = config;
2446
2436
  // Create TokenStorage with appropriate mode for server vs browser environment
2447
2437
  this.storage = new TokenStorage({
@@ -2550,12 +2540,27 @@ class AuthManager extends EventEmitter {
2550
2540
  * @deprecated 'headless' authentication is deprecated and will be removed in v2.0. Use 'device' instead.
2551
2541
  */
2552
2542
  async startAuthFlow(authMethod = 'device') {
2553
- var _a, _b;
2554
- if (this.authFlowManager || this.deviceAuthFlowManager) {
2555
- // Already in progress
2556
- this.logger.warn('Auth flow already in progress, ignoring duplicate call');
2557
- return;
2543
+ // If a flow is already in progress, return the shared promise so all callers await the same result
2544
+ if (this.currentAuthFlowPromise) {
2545
+ this.logger.debug('Auth flow already in progress, waiting for existing flow');
2546
+ return this.currentAuthFlowPromise;
2547
+ }
2548
+ // Store the flow promise so subsequent calls can await the same result
2549
+ const flowPromise = this.executeAuthFlow(authMethod);
2550
+ this.currentAuthFlowPromise = flowPromise;
2551
+ try {
2552
+ return await flowPromise;
2553
+ }
2554
+ finally {
2555
+ this.currentAuthFlowPromise = null;
2558
2556
  }
2557
+ }
2558
+ /**
2559
+ * Internal method that executes the actual auth flow
2560
+ * @private
2561
+ */
2562
+ async executeAuthFlow(authMethod = 'device') {
2563
+ var _a, _b;
2559
2564
  // Deprecation warning for headless auth
2560
2565
  if (authMethod === 'headless') {
2561
2566
  this.logger.warn('"headless" authentication is deprecated and will be removed in v2.0. ' +
@@ -2808,10 +2813,27 @@ class AuthManager extends EventEmitter {
2808
2813
  * ```
2809
2814
  */
2810
2815
  async startDeviceAuthFlow(options = {}) {
2811
- var _a;
2812
- if (this.deviceAuthFlowManager) {
2813
- throw new PlayKitError('Device auth flow already in progress', 'FLOW_IN_PROGRESS');
2816
+ // If a flow is already in progress, return the shared promise so all callers get the same result
2817
+ if (this.currentDeviceAuthFlowPromise) {
2818
+ this.logger.debug('Device auth flow already in progress, waiting for existing flow');
2819
+ return this.currentDeviceAuthFlowPromise;
2820
+ }
2821
+ // Store the flow promise so subsequent calls can await the same result
2822
+ const flowPromise = this.executeDeviceAuthFlow(options);
2823
+ this.currentDeviceAuthFlowPromise = flowPromise;
2824
+ try {
2825
+ return await flowPromise;
2814
2826
  }
2827
+ finally {
2828
+ this.currentDeviceAuthFlowPromise = null;
2829
+ }
2830
+ }
2831
+ /**
2832
+ * Internal method that executes the actual device auth flow
2833
+ * @private
2834
+ */
2835
+ async executeDeviceAuthFlow(options = {}) {
2836
+ var _a;
2815
2837
  try {
2816
2838
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
2817
2839
  const result = await this.deviceAuthFlowManager.startFlow(options);
@@ -2864,8 +2886,10 @@ class AuthManager extends EventEmitter {
2864
2886
  * ```
2865
2887
  */
2866
2888
  async initiateDeviceAuth(scope = 'player:play') {
2889
+ // If there's an existing manager, clean it up first (allows restarting flow)
2867
2890
  if (this.deviceAuthFlowManager) {
2868
- throw new PlayKitError('Device auth already in progress', 'FLOW_IN_PROGRESS');
2891
+ this.logger.debug('Cleaning up existing device auth manager before initiating new flow');
2892
+ this.deviceAuthFlowManager.destroy();
2869
2893
  }
2870
2894
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
2871
2895
  return this.deviceAuthFlowManager.initiateAuth(scope);