@superfan-app/spotify-auth 0.1.71 → 0.1.73

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.
@@ -82,6 +82,10 @@ def resolvedKotlinVersion() {
82
82
  return '2.0.21'
83
83
  }
84
84
 
85
+ repositories {
86
+ mavenCentral()
87
+ }
88
+
85
89
  dependencies {
86
90
  implementation project(':expo-modules-core')
87
91
 
@@ -1,4 +1,13 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
- <!-- No permissions needed at the library level.
2
+ <!-- Required for token exchange with backend -->
3
+ <uses-permission android:name="android.permission.INTERNET" />
4
+
5
+ <!-- Package visibility for Android 11+ (API 30+) -->
6
+ <!-- Required for the app to detect and launch Spotify -->
7
+ <queries>
8
+ <package android:name="com.spotify.music" />
9
+ </queries>
10
+
11
+ <!-- No other permissions needed at the library level.
3
12
  The config plugin will add intent-filters to the host app's manifest. -->
4
13
  </manifest>
@@ -50,8 +50,18 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
50
50
 
51
51
  /** Weak-ish reference to the module for sending events back to JS. */
52
52
  var module: SpotifyAuthModule? = null
53
+ set(value) {
54
+ field = value
55
+ if (value != null) {
56
+ secureLog("Module reference set successfully")
57
+ } else {
58
+ Log.w(TAG, "Module reference set to null")
59
+ }
60
+ }
53
61
 
54
62
  private var isAuthenticating = false
63
+ private var authTimeoutHandler: Runnable? = null
64
+ private val AUTH_TIMEOUT_MS = 60_000L // 60 seconds timeout
55
65
  private var currentSession: SpotifySessionData? = null
56
66
  set(value) {
57
67
  field = value
@@ -85,10 +95,28 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
85
95
  )
86
96
  ai.metaData?.getString(key)
87
97
  } catch (e: Exception) {
98
+ Log.e(TAG, "Failed to get meta-data for key $key: ${e.message}")
88
99
  null
89
100
  }
90
101
  }
91
102
 
103
+ private fun isSpotifyInstalled(): Boolean {
104
+ val context = appContext.reactContext ?: return false
105
+ return try {
106
+ val packageInfo = context.packageManager.getPackageInfo("com.spotify.music", 0)
107
+ Log.d(TAG, "Spotify app detected: com.spotify.music (version: ${packageInfo.versionName})")
108
+ true
109
+ } catch (e: PackageManager.NameNotFoundException) {
110
+ Log.d(TAG, "Spotify app NOT detected: ${e.message}")
111
+ Log.d(TAG, "If Spotify IS installed, this may be a package visibility issue (Android 11+)")
112
+ Log.d(TAG, "Ensure <queries><package android:name=\"com.spotify.music\"/></queries> is in merged manifest")
113
+ false
114
+ } catch (e: Exception) {
115
+ Log.e(TAG, "Error checking for Spotify app: ${e.message}", e)
116
+ false
117
+ }
118
+ }
119
+
92
120
  private val clientID: String
93
121
  get() = getMetaData("SpotifyClientID")
94
122
  ?: throw SpotifyAuthException.MissingConfiguration("SpotifyClientID in AndroidManifest meta-data")
@@ -112,6 +140,53 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
112
140
  return scopesStr.split(",").map { it.trim() }.filter { it.isNotEmpty() }
113
141
  }
114
142
 
143
+ /**
144
+ * Verify the app configuration and log any potential issues.
145
+ * Helps diagnose setup problems.
146
+ */
147
+ private fun verifyConfiguration() {
148
+ try {
149
+ val context = appContext.reactContext
150
+ if (context == null) {
151
+ Log.e(TAG, "React context is null - app may not be fully initialized")
152
+ return
153
+ }
154
+
155
+ // Check meta-data configuration
156
+ val configKeys = listOf(
157
+ "SpotifyClientID",
158
+ "SpotifyRedirectURL",
159
+ "SpotifyScopes",
160
+ "SpotifyTokenSwapURL",
161
+ "SpotifyTokenRefreshURL"
162
+ )
163
+
164
+ var allConfigured = true
165
+ for (key in configKeys) {
166
+ val value = getMetaData(key)
167
+ if (value.isNullOrEmpty()) {
168
+ Log.e(TAG, "Missing or empty configuration: $key")
169
+ allConfigured = false
170
+ } else {
171
+ Log.d(TAG, "Configuration $key: ${if (key.contains("URL") || key.contains("ID")) value.take(20) + "..." else value}")
172
+ }
173
+ }
174
+
175
+ if (allConfigured) {
176
+ Log.d(TAG, "All required configuration values are present")
177
+ } else {
178
+ Log.e(TAG, "Some configuration values are missing - auth will likely fail")
179
+ }
180
+
181
+ // Check if Spotify app is installed
182
+ val spotifyInstalled = isSpotifyInstalled()
183
+ Log.d(TAG, "Spotify app installed: $spotifyInstalled")
184
+
185
+ } catch (e: Exception) {
186
+ Log.e(TAG, "Error during configuration verification: ${e.message}", e)
187
+ }
188
+ }
189
+
115
190
  // endregion
116
191
 
117
192
  // region Authentication Flow
@@ -122,19 +197,52 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
122
197
  * and WebView fallback automatically.
123
198
  */
124
199
  fun initAuth(config: AuthorizeConfig) {
200
+ secureLog("initAuth called with showDialog=${config.showDialog}")
201
+
202
+ // Verify configuration on first auth attempt
203
+ verifyConfiguration()
204
+
205
+ // Cancel any existing timeout
206
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
207
+
125
208
  try {
209
+ if (module == null) {
210
+ Log.e(TAG, "CRITICAL: Module reference is null when initAuth called")
211
+ throw SpotifyAuthException.SessionError("Module not properly initialized")
212
+ }
213
+
126
214
  val activity = appContext.currentActivity
127
- ?: throw SpotifyAuthException.SessionError("No activity available")
215
+ if (activity == null) {
216
+ Log.e(TAG, "CRITICAL: No current activity available for auth")
217
+ throw SpotifyAuthException.SessionError("No activity available")
218
+ }
219
+
220
+ secureLog("Current activity: ${activity.javaClass.simpleName}")
128
221
 
129
222
  val clientId = clientID
130
223
  val redirectUri = redirectURL
131
224
  val scopeArray = scopes.toTypedArray()
132
225
 
226
+ val spotifyInstalled = isSpotifyInstalled()
227
+ secureLog("Configuration - ClientID: ${clientId.take(8)}..., RedirectURI: $redirectUri, Scopes: ${scopeArray.size}")
228
+ Log.d(TAG, "Spotify app installed: $spotifyInstalled (will use ${if (spotifyInstalled) "app-switch" else "WebView"} auth)")
229
+
230
+ if (!spotifyInstalled) {
231
+ Log.w(TAG, "Spotify app not detected. Will use WebView fallback. If WebView fails, check package visibility in AndroidManifest (<queries> tag)")
232
+ }
233
+
133
234
  if (scopeArray.isEmpty()) {
235
+ Log.e(TAG, "No valid scopes found in configuration")
134
236
  throw SpotifyAuthException.InvalidConfiguration("No valid scopes found in configuration")
135
237
  }
136
238
 
239
+ if (isAuthenticating) {
240
+ Log.w(TAG, "Auth already in progress, ignoring duplicate request")
241
+ return
242
+ }
243
+
137
244
  isAuthenticating = true
245
+ secureLog("Setting isAuthenticating to true")
138
246
 
139
247
  val builder = AuthorizationRequest.Builder(
140
248
  clientId,
@@ -145,6 +253,7 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
145
253
 
146
254
  if (config.showDialog) {
147
255
  builder.setShowDialog(true)
256
+ secureLog("Force-showing login dialog")
148
257
  }
149
258
 
150
259
  // Note: The Android Spotify auth-lib doesn't support a 'campaign' parameter
@@ -152,18 +261,54 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
152
261
 
153
262
  val request = builder.build()
154
263
 
155
- // AuthorizationClient.openLoginActivity handles both flows:
156
- // - If Spotify is installed: app-switch auth
157
- // - If Spotify is not installed: opens a WebView with Spotify login
158
- AuthorizationClient.openLoginActivity(activity, REQUEST_CODE, request)
264
+ secureLog("Opening Spotify authorization activity with REQUEST_CODE=$REQUEST_CODE")
265
+
266
+ // === ENHANCED DEBUG LOGGING ===
267
+ Log.d(TAG, "=== SPOTIFY AUTH DEBUG ===")
268
+ Log.d(TAG, "Auth flow type: ${if (spotifyInstalled) "APP_SWITCH" else "WEBVIEW"}")
269
+ Log.d(TAG, "Client ID: ${clientId.take(10)}...")
270
+ Log.d(TAG, "Redirect URI: $redirectUri")
271
+ Log.d(TAG, "Response Type: CODE")
272
+ Log.d(TAG, "Scopes: ${scopeArray.joinToString(",")}")
273
+ Log.d(TAG, "Package name: ${appContext.reactContext?.packageName}")
274
+ Log.d(TAG, "Activity: ${activity.javaClass.name}")
275
+ Log.d(TAG, "Activity launchMode: ${activity.packageManager.getActivityInfo(activity.componentName, 0).launchMode}")
276
+ Log.d(TAG, "========================")
277
+
278
+ // Set a timeout to detect if the auth flow doesn't complete
279
+ authTimeoutHandler = Runnable {
280
+ if (isAuthenticating) {
281
+ Log.e(TAG, "Auth timeout - no response received after ${AUTH_TIMEOUT_MS}ms")
282
+ isAuthenticating = false
283
+ module?.onAuthorizationError(
284
+ SpotifyAuthException.AuthenticationFailed("Authorization timed out. Please try again.")
285
+ )
286
+ }
287
+ }
288
+ mainHandler.postDelayed(authTimeoutHandler!!, AUTH_TIMEOUT_MS)
289
+
290
+ try {
291
+ // AuthorizationClient.openLoginActivity handles both flows:
292
+ // - If Spotify is installed: app-switch auth
293
+ // - If Spotify is not installed: opens a WebView with Spotify login
294
+ AuthorizationClient.openLoginActivity(activity, REQUEST_CODE, request)
295
+ secureLog("AuthorizationClient.openLoginActivity called successfully")
296
+ } catch (e: Exception) {
297
+ Log.e(TAG, "Failed to open authorization activity: ${e.message}", e)
298
+ throw SpotifyAuthException.AuthenticationFailed("Failed to open Spotify authorization: ${e.message}")
299
+ }
300
+
159
301
  } catch (e: SpotifyAuthException) {
302
+ Log.e(TAG, "Auth initialization failed (SpotifyAuthException): ${e.message}")
160
303
  isAuthenticating = false
161
- module?.onAuthorizationError(e)
304
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
305
+ module?.onAuthorizationError(e) ?: Log.e(TAG, "Cannot send error - module is null")
162
306
  } catch (e: Exception) {
307
+ Log.e(TAG, "Auth initialization failed (Exception): ${e.message}", e)
163
308
  isAuthenticating = false
164
- module?.onAuthorizationError(
165
- SpotifyAuthException.AuthenticationFailed(e.message ?: "Unknown error")
166
- )
309
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
310
+ val error = SpotifyAuthException.AuthenticationFailed(e.message ?: "Unknown error")
311
+ module?.onAuthorizationError(error) ?: Log.e(TAG, "Cannot send error - module is null")
167
312
  }
168
313
  }
169
314
 
@@ -172,43 +317,96 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
172
317
  * Called by the module's OnActivityResult handler.
173
318
  */
174
319
  fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
175
- if (requestCode != REQUEST_CODE) return
320
+ Log.d(TAG, "handleActivityResult called - requestCode=$requestCode, resultCode=$resultCode, hasData=${data != null}")
321
+
322
+ // === ENHANCED DEBUG LOGGING FOR ACTIVITY RESULT ===
323
+ if (data != null) {
324
+ Log.d(TAG, "Intent data URI: ${data.data}")
325
+ Log.d(TAG, "Intent action: ${data.action}")
326
+ Log.d(TAG, "Intent extras keys: ${data.extras?.keySet()?.joinToString() ?: "none"}")
327
+ data.extras?.let { extras ->
328
+ for (key in extras.keySet()) {
329
+ val value = extras.get(key)
330
+ if (key.contains("token", ignoreCase = true) ||
331
+ key.contains("code", ignoreCase = true) ||
332
+ key.contains("secret", ignoreCase = true)) {
333
+ Log.d(TAG, " $key: [REDACTED]")
334
+ } else {
335
+ Log.d(TAG, " $key: $value")
336
+ }
337
+ }
338
+ }
339
+ } else {
340
+ Log.w(TAG, "Intent data is NULL - callback may not have fired correctly")
341
+ Log.w(TAG, "This often indicates an intent filter configuration issue")
342
+ }
343
+
344
+ if (requestCode != REQUEST_CODE) {
345
+ Log.d(TAG, "Ignoring activity result - wrong request code (expected $REQUEST_CODE, got $requestCode)")
346
+ return
347
+ }
348
+
349
+ // Cancel the timeout
350
+ authTimeoutHandler?.let {
351
+ mainHandler.removeCallbacks(it)
352
+ secureLog("Auth timeout cancelled")
353
+ }
354
+
355
+ if (!isAuthenticating) {
356
+ Log.w(TAG, "Received activity result but isAuthenticating was false")
357
+ }
176
358
 
177
359
  isAuthenticating = false
360
+ secureLog("Setting isAuthenticating to false")
178
361
 
179
- val response = AuthorizationClient.getResponse(resultCode, data)
362
+ if (module == null) {
363
+ Log.e(TAG, "CRITICAL: Module is null in handleActivityResult - cannot send events to JS")
364
+ return
365
+ }
180
366
 
181
- when (response.type) {
182
- AuthorizationResponse.Type.CODE -> {
183
- secureLog("Authorization code received")
184
- val code = response.code
185
- if (code != null) {
186
- exchangeCodeForToken(code)
187
- } else {
188
- module?.onAuthorizationError(
189
- SpotifyAuthException.AuthenticationFailed("No authorization code received")
190
- )
367
+ try {
368
+ val response = AuthorizationClient.getResponse(resultCode, data)
369
+ Log.d(TAG, "Spotify response type: ${response.type}")
370
+
371
+ when (response.type) {
372
+ AuthorizationResponse.Type.CODE -> {
373
+ val code = response.code
374
+ secureLog("Authorization code received, length=${code?.length ?: 0}")
375
+ if (code != null) {
376
+ exchangeCodeForToken(code)
377
+ } else {
378
+ Log.e(TAG, "Authorization code was null despite CODE response type")
379
+ module?.onAuthorizationError(
380
+ SpotifyAuthException.AuthenticationFailed("No authorization code received")
381
+ )
382
+ }
191
383
  }
192
- }
193
- AuthorizationResponse.Type.ERROR -> {
194
- val errorMsg = response.error ?: "Unknown error"
195
- secureLog("Authorization error: $errorMsg")
196
- if (errorMsg.contains("access_denied", ignoreCase = true) ||
197
- errorMsg.contains("cancelled", ignoreCase = true)) {
384
+ AuthorizationResponse.Type.ERROR -> {
385
+ val errorMsg = response.error ?: "Unknown error"
386
+ Log.e(TAG, "Spotify authorization error: $errorMsg")
387
+ if (errorMsg.contains("access_denied", ignoreCase = true) ||
388
+ errorMsg.contains("cancelled", ignoreCase = true)) {
389
+ module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
390
+ } else {
391
+ module?.onAuthorizationError(SpotifyAuthException.AuthorizationError(errorMsg))
392
+ }
393
+ }
394
+ AuthorizationResponse.Type.EMPTY -> {
395
+ Log.w(TAG, "Authorization returned EMPTY - user likely cancelled")
198
396
  module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
199
- } else {
200
- module?.onAuthorizationError(SpotifyAuthException.AuthorizationError(errorMsg))
397
+ }
398
+ else -> {
399
+ Log.e(TAG, "Unexpected Spotify response type: ${response.type}")
400
+ module?.onAuthorizationError(
401
+ SpotifyAuthException.AuthenticationFailed("Unexpected response type: ${response.type}")
402
+ )
201
403
  }
202
404
  }
203
- AuthorizationResponse.Type.EMPTY -> {
204
- secureLog("Authorization was cancelled or returned empty")
205
- module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
206
- }
207
- else -> {
208
- module?.onAuthorizationError(
209
- SpotifyAuthException.AuthenticationFailed("Unexpected response type: ${response.type}")
210
- )
211
- }
405
+ } catch (e: Exception) {
406
+ Log.e(TAG, "Exception in handleActivityResult: ${e.message}", e)
407
+ module?.onAuthorizationError(
408
+ SpotifyAuthException.AuthenticationFailed("Error processing auth result: ${e.message}")
409
+ )
212
410
  }
213
411
  }
214
412
 
@@ -220,16 +418,29 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
220
418
  * Exchange an authorization code for access + refresh tokens via the backend token swap URL.
221
419
  */
222
420
  private fun exchangeCodeForToken(code: String) {
421
+ secureLog("Starting token exchange process")
422
+
423
+ if (module == null) {
424
+ Log.e(TAG, "CRITICAL: Module is null in exchangeCodeForToken")
425
+ return
426
+ }
427
+
223
428
  executor.execute {
224
429
  try {
225
430
  val swapUrl = tokenSwapURL
226
431
  val redirect = redirectURL
227
432
 
433
+ Log.d(TAG, "Token swap URL: ${swapUrl.take(30)}...")
434
+ Log.d(TAG, "Redirect URL: $redirect")
435
+
228
436
  if (!swapUrl.startsWith("https://")) {
437
+ Log.e(TAG, "Token swap URL does not use HTTPS: $swapUrl")
229
438
  throw SpotifyAuthException.InvalidConfiguration("Token swap URL must use HTTPS")
230
439
  }
231
440
 
232
441
  val url = URL(swapUrl)
442
+ Log.d(TAG, "Opening connection to token swap URL")
443
+
233
444
  val connection = url.openConnection() as HttpURLConnection
234
445
  connection.apply {
235
446
  requestMethod = "POST"
@@ -240,14 +451,19 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
240
451
  }
241
452
 
242
453
  val body = "code=${Uri.encode(code)}&redirect_uri=${Uri.encode(redirect)}"
454
+ Log.d(TAG, "Sending token exchange request (code length: ${code.length})")
455
+
243
456
  OutputStreamWriter(connection.outputStream).use { writer ->
244
457
  writer.write(body)
245
458
  writer.flush()
246
459
  }
247
460
 
248
461
  val responseCode = connection.responseCode
462
+ Log.d(TAG, "Token exchange response code: $responseCode")
463
+
249
464
  if (responseCode !in 200..299) {
250
465
  val errorMessage = extractErrorMessage(connection, responseCode)
466
+ Log.e(TAG, "Token exchange failed with status $responseCode: $errorMessage")
251
467
  throw SpotifyAuthException.NetworkError(errorMessage)
252
468
  }
253
469
 
@@ -255,6 +471,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
255
471
  it.readText()
256
472
  }
257
473
 
474
+ Log.d(TAG, "Token exchange response received (body length: ${responseBody.length})")
475
+
258
476
  val parsed = parseTokenJSON(responseBody)
259
477
 
260
478
  val refreshToken = parsed.refreshToken
@@ -262,6 +480,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
262
480
 
263
481
  val expirationTime = System.currentTimeMillis() + (parsed.expiresIn * 1000).toLong()
264
482
 
483
+ Log.d(TAG, "Token exchange successful - expires in ${parsed.expiresIn} seconds")
484
+
265
485
  val session = SpotifySessionData(
266
486
  accessToken = parsed.accessToken,
267
487
  refreshToken = refreshToken,
@@ -270,6 +490,7 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
270
490
  )
271
491
 
272
492
  mainHandler.post {
493
+ secureLog("Setting currentSession and sending success event to JS")
273
494
  currentSession = session
274
495
  }
275
496
 
@@ -388,8 +609,17 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
388
609
  }
389
610
 
390
611
  private fun securelyStoreToken(session: SpotifySessionData) {
612
+ secureLog("Storing token and sending to JS")
613
+
614
+ if (module == null) {
615
+ Log.e(TAG, "CRITICAL: Module is null in securelyStoreToken - cannot send token to JS")
616
+ return
617
+ }
618
+
391
619
  // Send the token back to JS
392
620
  val expiresIn = (session.expirationTime - System.currentTimeMillis()) / 1000.0
621
+ Log.d(TAG, "Sending access token to JS (expires in ${expiresIn}s)")
622
+
393
623
  module?.onAccessTokenObtained(
394
624
  session.accessToken,
395
625
  session.refreshToken,
@@ -404,8 +634,9 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
404
634
  getEncryptedPrefs()?.edit()
405
635
  ?.putString(PREF_REFRESH_TOKEN_KEY, session.refreshToken)
406
636
  ?.apply()
637
+ Log.d(TAG, "Refresh token stored securely")
407
638
  } catch (e: Exception) {
408
- Log.e(TAG, "Failed to store refresh token securely: ${e.message}")
639
+ Log.e(TAG, "Failed to store refresh token securely: ${e.message}", e)
409
640
  }
410
641
  }
411
642
  }
@@ -449,6 +680,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
449
680
  }
450
681
 
451
682
  fun cleanup() {
683
+ secureLog("Cleaning up SpotifyAuthAuth instance")
684
+ authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
452
685
  refreshHandler.removeCallbacksAndMessages(null)
453
686
  executor.shutdown()
454
687
  instance = null
@@ -24,8 +24,13 @@ class SpotifyAuthModule : Module() {
24
24
  Name("SpotifyAuth")
25
25
 
26
26
  OnCreate {
27
- spotifyAuth.module = this@SpotifyAuthModule
28
- secureLog("Module initialized")
27
+ secureLog("SpotifyAuthModule OnCreate called")
28
+ try {
29
+ spotifyAuth.module = this@SpotifyAuthModule
30
+ secureLog("Module initialized and linked to SpotifyAuthAuth")
31
+ } catch (e: Exception) {
32
+ android.util.Log.e("SpotifyAuth", "Failed to initialize module: ${e.message}", e)
33
+ }
29
34
  }
30
35
 
31
36
  Constants(
@@ -58,6 +63,17 @@ class SpotifyAuthModule : Module() {
58
63
  }
59
64
 
60
65
  OnActivityResult { _, payload ->
66
+ secureLog("OnActivityResult received in module - requestCode=${payload.requestCode}, resultCode=${payload.resultCode}")
67
+ android.util.Log.d("SpotifyAuth", "=== MODULE ACTIVITY RESULT ===")
68
+ android.util.Log.d("SpotifyAuth", "Request code: ${payload.requestCode}")
69
+ android.util.Log.d("SpotifyAuth", "Result code: ${payload.resultCode}")
70
+ android.util.Log.d("SpotifyAuth", "Has data: ${payload.data != null}")
71
+ if (payload.data != null) {
72
+ android.util.Log.d("SpotifyAuth", "Data scheme: ${payload.data?.scheme}")
73
+ android.util.Log.d("SpotifyAuth", "Data host: ${payload.data?.data?.host}")
74
+ android.util.Log.d("SpotifyAuth", "Data path: ${payload.data?.data?.path}")
75
+ }
76
+ android.util.Log.d("SpotifyAuth", "============================")
61
77
  spotifyAuth.handleActivityResult(payload.requestCode, payload.resultCode, payload.data)
62
78
  }
63
79
 
@@ -85,7 +101,12 @@ class SpotifyAuthModule : Module() {
85
101
  "scope" to scope,
86
102
  "error" to null
87
103
  )
88
- sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
104
+ try {
105
+ sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
106
+ secureLog("Success event sent to JS")
107
+ } catch (e: Exception) {
108
+ android.util.Log.e("SpotifyAuth", "Failed to send success event to JS: ${e.message}", e)
109
+ }
89
110
  }
90
111
 
91
112
  fun onSignOut() {
@@ -103,6 +124,8 @@ class SpotifyAuthModule : Module() {
103
124
  }
104
125
 
105
126
  fun onAuthorizationError(error: Exception) {
127
+ android.util.Log.d("SpotifyAuth", "onAuthorizationError called with: ${error.javaClass.simpleName}: ${error.message}")
128
+
106
129
  // Skip sending error events for expected state transitions
107
130
  if (error is SpotifyAuthException.SessionError) {
108
131
  val msg = error.message ?: ""
@@ -125,7 +148,7 @@ class SpotifyAuthModule : Module() {
125
148
  )
126
149
  }
127
150
 
128
- secureLog("Authorization error: ${errorData["message"] ?: "Unknown error"}")
151
+ android.util.Log.e("SpotifyAuth", "Authorization error: ${errorData["message"] ?: "Unknown error"}")
129
152
 
130
153
  val eventData = mapOf(
131
154
  "success" to false,
@@ -136,7 +159,13 @@ class SpotifyAuthModule : Module() {
136
159
  "scope" to null,
137
160
  "error" to errorData
138
161
  )
139
- sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
162
+
163
+ try {
164
+ sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
165
+ secureLog("Error event sent to JS successfully")
166
+ } catch (e: Exception) {
167
+ android.util.Log.e("SpotifyAuth", "CRITICAL: Failed to send error event to JS: ${e.message}", e)
168
+ }
140
169
  }
141
170
 
142
171
  // endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superfan-app/spotify-auth",
3
- "version": "0.1.71",
3
+ "version": "0.1.73",
4
4
  "description": "Spotify OAuth module for Expo",
5
5
  "main": "src/index.tsx",
6
6
  "types": "build/index.d.ts",