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.
@@ -927,8 +927,8 @@ interface DeviceAuthInitResult {
927
927
  game?: GameInfo;
928
928
  }
929
929
  declare class DeviceAuthFlowManager extends EventEmitter {
930
- /** Static flag to prevent multiple flows running simultaneously */
931
- private static isFlowInProgress;
930
+ /** Shared promise for the current flow - allows multiple callers to await the same result */
931
+ private static currentFlowPromise;
932
932
  /** Reference to the currently active instance */
933
933
  private static activeInstance;
934
934
  private baseURL;
@@ -985,9 +985,13 @@ declare class DeviceAuthFlowManager extends EventEmitter {
985
985
  *
986
986
  * @param options - Flow options
987
987
  * @returns Promise resolving to DeviceAuthResult with tokens
988
- * @throws PlayKitError if a flow is already in progress
989
988
  */
990
989
  startFlow(options?: DeviceAuthFlowOptions): Promise<DeviceAuthResult>;
990
+ /**
991
+ * Internal method that executes the actual device auth flow
992
+ * @private
993
+ */
994
+ private executeFlow;
991
995
  /**
992
996
  * Cancel the ongoing device auth flow
993
997
  */
@@ -1063,6 +1067,10 @@ declare class AuthManager extends EventEmitter {
1063
1067
  private authFlowManager;
1064
1068
  private deviceAuthFlowManager;
1065
1069
  private logger;
1070
+ /** Shared promise for current device auth flow - allows multiple callers to await the same result */
1071
+ private currentDeviceAuthFlowPromise;
1072
+ /** Shared promise for current auth flow (startAuthFlow) - allows multiple callers to await the same result */
1073
+ private currentAuthFlowPromise;
1066
1074
  constructor(config: SDKConfig);
1067
1075
  /**
1068
1076
  * Initialize authentication
@@ -1075,6 +1083,11 @@ declare class AuthManager extends EventEmitter {
1075
1083
  * @deprecated 'headless' authentication is deprecated and will be removed in v2.0. Use 'device' instead.
1076
1084
  */
1077
1085
  startAuthFlow(authMethod?: 'device' | 'headless'): Promise<void>;
1086
+ /**
1087
+ * Internal method that executes the actual auth flow
1088
+ * @private
1089
+ */
1090
+ private executeAuthFlow;
1078
1091
  /**
1079
1092
  * Exchange JWT for player token
1080
1093
  */
@@ -1153,6 +1166,11 @@ declare class AuthManager extends EventEmitter {
1153
1166
  * ```
1154
1167
  */
1155
1168
  startDeviceAuthFlow(options?: DeviceAuthFlowOptions): Promise<DeviceAuthResult>;
1169
+ /**
1170
+ * Internal method that executes the actual device auth flow
1171
+ * @private
1172
+ */
1173
+ private executeDeviceAuthFlow;
1156
1174
  /**
1157
1175
  * Cancel ongoing device auth flow
1158
1176
  */
@@ -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
  */
@@ -2047,53 +2047,56 @@ class DeviceAuthFlowManager extends EventEmitter {
2047
2047
  *
2048
2048
  * @param options - Flow options
2049
2049
  * @returns Promise resolving to DeviceAuthResult with tokens
2050
- * @throws PlayKitError if a flow is already in progress
2051
2050
  */
2052
2051
  async startFlow(options = {}) {
2053
- // Prevent duplicate flows from running simultaneously
2054
- if (DeviceAuthFlowManager.isFlowInProgress) {
2055
- this.logger.warn('Device auth flow already in progress, ignoring duplicate call');
2056
- throw new PlayKitError('Device auth flow already in progress', 'FLOW_IN_PROGRESS');
2057
- }
2058
- DeviceAuthFlowManager.isFlowInProgress = true;
2052
+ // If a flow is already in progress, return the shared promise so all callers get the same result
2053
+ if (DeviceAuthFlowManager.currentFlowPromise) {
2054
+ this.logger.debug('Device auth flow already in progress, waiting for existing flow');
2055
+ return DeviceAuthFlowManager.currentFlowPromise;
2056
+ }
2057
+ // Store the flow promise so subsequent calls can await the same result
2058
+ const flowPromise = this.executeFlow(options);
2059
+ DeviceAuthFlowManager.currentFlowPromise = flowPromise;
2059
2060
  DeviceAuthFlowManager.activeInstance = this;
2061
+ try {
2062
+ return await flowPromise;
2063
+ }
2064
+ finally {
2065
+ // Clean up static state when flow completes (success or failure)
2066
+ DeviceAuthFlowManager.currentFlowPromise = null;
2067
+ DeviceAuthFlowManager.activeInstance = null;
2068
+ }
2069
+ }
2070
+ /**
2071
+ * Internal method that executes the actual device auth flow
2072
+ * @private
2073
+ */
2074
+ async executeFlow(options = {}) {
2060
2075
  this.aborted = false;
2061
2076
  this.currentLanguage = this.detectLanguage();
2062
2077
  const scope = options.scope || 'player:play';
2063
2078
  const openBrowser = options.openBrowser || this.defaultOpenBrowser.bind(this);
2064
- // Helper to reset static flags when flow completes early (before polling)
2065
- const resetFlowStateEarly = () => {
2066
- DeviceAuthFlowManager.isFlowInProgress = false;
2067
- DeviceAuthFlowManager.activeInstance = null;
2068
- };
2069
2079
  // Generate PKCE parameters (outside try block so codeVerifier is accessible for polling)
2070
2080
  const codeVerifier = this.generateCodeVerifier();
2071
2081
  const codeChallenge = await this.generateCodeChallenge(codeVerifier);
2072
- let initData;
2073
- try {
2074
- // Step 1: Initiate device auth session
2075
- const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2076
- method: 'POST',
2077
- headers: {
2078
- 'Content-Type': 'application/json',
2079
- },
2080
- body: JSON.stringify({
2081
- game_id: this.gameId,
2082
- code_challenge: codeChallenge,
2083
- code_challenge_method: 'S256',
2084
- scope,
2085
- }),
2086
- });
2087
- if (!initResponse.ok) {
2088
- const error = await initResponse.json().catch(() => ({ error_description: 'Failed to initiate device auth' }));
2089
- throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
2090
- }
2091
- initData = await initResponse.json();
2092
- }
2093
- catch (err) {
2094
- resetFlowStateEarly();
2095
- throw err;
2082
+ // Step 1: Initiate device auth session
2083
+ const initResponse = await fetch(`${this.baseURL}/api/device-auth/initiate`, {
2084
+ method: 'POST',
2085
+ headers: {
2086
+ 'Content-Type': 'application/json',
2087
+ },
2088
+ body: JSON.stringify({
2089
+ game_id: this.gameId,
2090
+ code_challenge: codeChallenge,
2091
+ code_challenge_method: 'S256',
2092
+ scope,
2093
+ }),
2094
+ });
2095
+ if (!initResponse.ok) {
2096
+ const error = await initResponse.json().catch(() => ({ error_description: 'Failed to initiate device auth' }));
2097
+ throw new PlayKitError(error.error_description || 'Failed to initiate device auth', error.error || 'INIT_FAILED', initResponse.status);
2096
2098
  }
2099
+ const initData = await initResponse.json();
2097
2100
  const { session_id, auth_url, poll_interval, expires_in, game } = initData;
2098
2101
  // Update poll interval from server
2099
2102
  if (poll_interval) {
@@ -2125,7 +2128,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2125
2128
  }
2126
2129
  catch (err) {
2127
2130
  this.closeModal();
2128
- resetFlowStateEarly();
2129
2131
  throw err;
2130
2132
  }
2131
2133
  // User clicked login - open browser (in user click context, won't be blocked)
@@ -2137,23 +2139,16 @@ class DeviceAuthFlowManager extends EventEmitter {
2137
2139
  }
2138
2140
  // Step 3: Poll for authorization
2139
2141
  const expiresAt = Date.now() + (expires_in || 600) * 1000;
2140
- // Helper to reset static flags when flow completes
2141
- const resetFlowState = () => {
2142
- DeviceAuthFlowManager.isFlowInProgress = false;
2143
- DeviceAuthFlowManager.activeInstance = null;
2144
- };
2145
2142
  return new Promise((resolve, reject) => {
2146
2143
  const poll = async () => {
2147
2144
  if (this.aborted) {
2148
2145
  this.closeModal();
2149
- resetFlowState();
2150
2146
  reject(new PlayKitError('Device auth flow was cancelled', 'CANCELLED'));
2151
2147
  return;
2152
2148
  }
2153
2149
  // Check if session expired
2154
2150
  if (Date.now() >= expiresAt) {
2155
2151
  this.showModalError('expired', () => { });
2156
- resetFlowState();
2157
2152
  reject(new PlayKitError('Device auth session expired', 'EXPIRED'));
2158
2153
  return;
2159
2154
  }
@@ -2181,7 +2176,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2181
2176
  }
2182
2177
  this.emit('poll_status', 'authorized');
2183
2178
  this.emit('authenticated', pollData);
2184
- resetFlowState();
2185
2179
  resolve({
2186
2180
  access_token: pollData.access_token,
2187
2181
  token_type: pollData.token_type,
@@ -2210,7 +2204,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2210
2204
  options.onPollStatus('denied');
2211
2205
  }
2212
2206
  this.emit('poll_status', 'denied');
2213
- resetFlowState();
2214
2207
  reject(new PlayKitError(pollData.error_description || 'User denied authorization', 'ACCESS_DENIED'));
2215
2208
  }
2216
2209
  else if (error === 'expired_token') {
@@ -2219,12 +2212,10 @@ class DeviceAuthFlowManager extends EventEmitter {
2219
2212
  options.onPollStatus('expired');
2220
2213
  }
2221
2214
  this.emit('poll_status', 'expired');
2222
- resetFlowState();
2223
2215
  reject(new PlayKitError(pollData.error_description || 'Session expired', 'EXPIRED'));
2224
2216
  }
2225
2217
  else {
2226
2218
  this.showModalError('failed', () => { });
2227
- resetFlowState();
2228
2219
  reject(new PlayKitError(pollData.error_description || 'Device auth failed', error || 'POLL_FAILED', pollResponse.status));
2229
2220
  }
2230
2221
  }
@@ -2249,11 +2240,6 @@ class DeviceAuthFlowManager extends EventEmitter {
2249
2240
  clearTimeout(this.pollTimeoutId);
2250
2241
  this.pollTimeoutId = null;
2251
2242
  }
2252
- // Reset static flags if this is the active instance
2253
- if (DeviceAuthFlowManager.activeInstance === this) {
2254
- DeviceAuthFlowManager.isFlowInProgress = false;
2255
- DeviceAuthFlowManager.activeInstance = null;
2256
- }
2257
2243
  this.emit('cancelled');
2258
2244
  }
2259
2245
  /**
@@ -2268,7 +2254,7 @@ class DeviceAuthFlowManager extends EventEmitter {
2268
2254
  * Check if a device auth flow is currently in progress (static method)
2269
2255
  */
2270
2256
  static isInProgress() {
2271
- return DeviceAuthFlowManager.isFlowInProgress;
2257
+ return DeviceAuthFlowManager.currentFlowPromise !== null;
2272
2258
  }
2273
2259
  /**
2274
2260
  * Initiate device auth without opening browser or showing UI.
@@ -2419,8 +2405,8 @@ class DeviceAuthFlowManager extends EventEmitter {
2419
2405
  });
2420
2406
  }
2421
2407
  }
2422
- /** Static flag to prevent multiple flows running simultaneously */
2423
- DeviceAuthFlowManager.isFlowInProgress = false;
2408
+ /** Shared promise for the current flow - allows multiple callers to await the same result */
2409
+ DeviceAuthFlowManager.currentFlowPromise = null;
2424
2410
  /** Reference to the currently active instance */
2425
2411
  DeviceAuthFlowManager.activeInstance = null;
2426
2412
 
@@ -2438,6 +2424,10 @@ class AuthManager extends EventEmitter {
2438
2424
  this.authFlowManager = null;
2439
2425
  this.deviceAuthFlowManager = null;
2440
2426
  this.logger = Logger.getLogger('AuthManager');
2427
+ /** Shared promise for current device auth flow - allows multiple callers to await the same result */
2428
+ this.currentDeviceAuthFlowPromise = null;
2429
+ /** Shared promise for current auth flow (startAuthFlow) - allows multiple callers to await the same result */
2430
+ this.currentAuthFlowPromise = null;
2441
2431
  this.config = config;
2442
2432
  // Create TokenStorage with appropriate mode for server vs browser environment
2443
2433
  this.storage = new TokenStorage({
@@ -2546,12 +2536,27 @@ class AuthManager extends EventEmitter {
2546
2536
  * @deprecated 'headless' authentication is deprecated and will be removed in v2.0. Use 'device' instead.
2547
2537
  */
2548
2538
  async startAuthFlow(authMethod = 'device') {
2549
- var _a, _b;
2550
- if (this.authFlowManager || this.deviceAuthFlowManager) {
2551
- // Already in progress
2552
- this.logger.warn('Auth flow already in progress, ignoring duplicate call');
2553
- return;
2539
+ // If a flow is already in progress, return the shared promise so all callers await the same result
2540
+ if (this.currentAuthFlowPromise) {
2541
+ this.logger.debug('Auth flow already in progress, waiting for existing flow');
2542
+ return this.currentAuthFlowPromise;
2543
+ }
2544
+ // Store the flow promise so subsequent calls can await the same result
2545
+ const flowPromise = this.executeAuthFlow(authMethod);
2546
+ this.currentAuthFlowPromise = flowPromise;
2547
+ try {
2548
+ return await flowPromise;
2549
+ }
2550
+ finally {
2551
+ this.currentAuthFlowPromise = null;
2554
2552
  }
2553
+ }
2554
+ /**
2555
+ * Internal method that executes the actual auth flow
2556
+ * @private
2557
+ */
2558
+ async executeAuthFlow(authMethod = 'device') {
2559
+ var _a, _b;
2555
2560
  // Deprecation warning for headless auth
2556
2561
  if (authMethod === 'headless') {
2557
2562
  this.logger.warn('"headless" authentication is deprecated and will be removed in v2.0. ' +
@@ -2804,10 +2809,27 @@ class AuthManager extends EventEmitter {
2804
2809
  * ```
2805
2810
  */
2806
2811
  async startDeviceAuthFlow(options = {}) {
2807
- var _a;
2808
- if (this.deviceAuthFlowManager) {
2809
- throw new PlayKitError('Device auth flow already in progress', 'FLOW_IN_PROGRESS');
2812
+ // If a flow is already in progress, return the shared promise so all callers get the same result
2813
+ if (this.currentDeviceAuthFlowPromise) {
2814
+ this.logger.debug('Device auth flow already in progress, waiting for existing flow');
2815
+ return this.currentDeviceAuthFlowPromise;
2816
+ }
2817
+ // Store the flow promise so subsequent calls can await the same result
2818
+ const flowPromise = this.executeDeviceAuthFlow(options);
2819
+ this.currentDeviceAuthFlowPromise = flowPromise;
2820
+ try {
2821
+ return await flowPromise;
2810
2822
  }
2823
+ finally {
2824
+ this.currentDeviceAuthFlowPromise = null;
2825
+ }
2826
+ }
2827
+ /**
2828
+ * Internal method that executes the actual device auth flow
2829
+ * @private
2830
+ */
2831
+ async executeDeviceAuthFlow(options = {}) {
2832
+ var _a;
2811
2833
  try {
2812
2834
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
2813
2835
  const result = await this.deviceAuthFlowManager.startFlow(options);
@@ -2860,8 +2882,10 @@ class AuthManager extends EventEmitter {
2860
2882
  * ```
2861
2883
  */
2862
2884
  async initiateDeviceAuth(scope = 'player:play') {
2885
+ // If there's an existing manager, clean it up first (allows restarting flow)
2863
2886
  if (this.deviceAuthFlowManager) {
2864
- throw new PlayKitError('Device auth already in progress', 'FLOW_IN_PROGRESS');
2887
+ this.logger.debug('Cleaning up existing device auth manager before initiating new flow');
2888
+ this.deviceAuthFlowManager.destroy();
2865
2889
  }
2866
2890
  this.deviceAuthFlowManager = new DeviceAuthFlowManager(this.baseURL, this.config.gameId);
2867
2891
  return this.deviceAuthFlowManager.initiateAuth(scope);