@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.
package/android/build.gradle
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
-
<!--
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
}
|
|
200
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|