@superfan-app/spotify-auth 0.1.70 → 0.1.72
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 +4 -0
- package/android/src/main/AndroidManifest.xml +10 -1
- package/android/src/main/java/expo/modules/spotifyauth/SpotifyAuthAuth.kt +231 -39
- package/android/src/main/java/expo/modules/spotifyauth/SpotifyAuthModule.kt +24 -5
- package/ios/SpotifyAuthAuth.swift +1 -3
- package/package.json +1 -1
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,21 @@ 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
|
+
context.packageManager.getPackageInfo("com.spotify.music", 0)
|
|
107
|
+
true
|
|
108
|
+
} catch (e: Exception) {
|
|
109
|
+
false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
92
113
|
private val clientID: String
|
|
93
114
|
get() = getMetaData("SpotifyClientID")
|
|
94
115
|
?: throw SpotifyAuthException.MissingConfiguration("SpotifyClientID in AndroidManifest meta-data")
|
|
@@ -112,6 +133,53 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
112
133
|
return scopesStr.split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
|
113
134
|
}
|
|
114
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Verify the app configuration and log any potential issues.
|
|
138
|
+
* Helps diagnose setup problems.
|
|
139
|
+
*/
|
|
140
|
+
private fun verifyConfiguration() {
|
|
141
|
+
try {
|
|
142
|
+
val context = appContext.reactContext
|
|
143
|
+
if (context == null) {
|
|
144
|
+
Log.e(TAG, "React context is null - app may not be fully initialized")
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check meta-data configuration
|
|
149
|
+
val configKeys = listOf(
|
|
150
|
+
"SpotifyClientID",
|
|
151
|
+
"SpotifyRedirectURL",
|
|
152
|
+
"SpotifyScopes",
|
|
153
|
+
"SpotifyTokenSwapURL",
|
|
154
|
+
"SpotifyTokenRefreshURL"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
var allConfigured = true
|
|
158
|
+
for (key in configKeys) {
|
|
159
|
+
val value = getMetaData(key)
|
|
160
|
+
if (value.isNullOrEmpty()) {
|
|
161
|
+
Log.e(TAG, "Missing or empty configuration: $key")
|
|
162
|
+
allConfigured = false
|
|
163
|
+
} else {
|
|
164
|
+
Log.d(TAG, "Configuration $key: ${if (key.contains("URL") || key.contains("ID")) value.take(20) + "..." else value}")
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (allConfigured) {
|
|
169
|
+
Log.d(TAG, "All required configuration values are present")
|
|
170
|
+
} else {
|
|
171
|
+
Log.e(TAG, "Some configuration values are missing - auth will likely fail")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if Spotify app is installed
|
|
175
|
+
val spotifyInstalled = isSpotifyInstalled()
|
|
176
|
+
Log.d(TAG, "Spotify app installed: $spotifyInstalled")
|
|
177
|
+
|
|
178
|
+
} catch (e: Exception) {
|
|
179
|
+
Log.e(TAG, "Error during configuration verification: ${e.message}", e)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
115
183
|
// endregion
|
|
116
184
|
|
|
117
185
|
// region Authentication Flow
|
|
@@ -122,19 +190,52 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
122
190
|
* and WebView fallback automatically.
|
|
123
191
|
*/
|
|
124
192
|
fun initAuth(config: AuthorizeConfig) {
|
|
193
|
+
secureLog("initAuth called with showDialog=${config.showDialog}")
|
|
194
|
+
|
|
195
|
+
// Verify configuration on first auth attempt
|
|
196
|
+
verifyConfiguration()
|
|
197
|
+
|
|
198
|
+
// Cancel any existing timeout
|
|
199
|
+
authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
|
|
200
|
+
|
|
125
201
|
try {
|
|
202
|
+
if (module == null) {
|
|
203
|
+
Log.e(TAG, "CRITICAL: Module reference is null when initAuth called")
|
|
204
|
+
throw SpotifyAuthException.SessionError("Module not properly initialized")
|
|
205
|
+
}
|
|
206
|
+
|
|
126
207
|
val activity = appContext.currentActivity
|
|
127
|
-
|
|
208
|
+
if (activity == null) {
|
|
209
|
+
Log.e(TAG, "CRITICAL: No current activity available for auth")
|
|
210
|
+
throw SpotifyAuthException.SessionError("No activity available")
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
secureLog("Current activity: ${activity.javaClass.simpleName}")
|
|
128
214
|
|
|
129
215
|
val clientId = clientID
|
|
130
216
|
val redirectUri = redirectURL
|
|
131
217
|
val scopeArray = scopes.toTypedArray()
|
|
132
218
|
|
|
219
|
+
val spotifyInstalled = isSpotifyInstalled()
|
|
220
|
+
secureLog("Configuration - ClientID: ${clientId.take(8)}..., RedirectURI: $redirectUri, Scopes: ${scopeArray.size}")
|
|
221
|
+
Log.d(TAG, "Spotify app installed: $spotifyInstalled (will use ${if (spotifyInstalled) "app-switch" else "WebView"} auth)")
|
|
222
|
+
|
|
223
|
+
if (!spotifyInstalled) {
|
|
224
|
+
Log.w(TAG, "Spotify app not detected. Will use WebView fallback. If WebView fails, check package visibility in AndroidManifest (<queries> tag)")
|
|
225
|
+
}
|
|
226
|
+
|
|
133
227
|
if (scopeArray.isEmpty()) {
|
|
228
|
+
Log.e(TAG, "No valid scopes found in configuration")
|
|
134
229
|
throw SpotifyAuthException.InvalidConfiguration("No valid scopes found in configuration")
|
|
135
230
|
}
|
|
136
231
|
|
|
232
|
+
if (isAuthenticating) {
|
|
233
|
+
Log.w(TAG, "Auth already in progress, ignoring duplicate request")
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
137
237
|
isAuthenticating = true
|
|
238
|
+
secureLog("Setting isAuthenticating to true")
|
|
138
239
|
|
|
139
240
|
val builder = AuthorizationRequest.Builder(
|
|
140
241
|
clientId,
|
|
@@ -145,6 +246,7 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
145
246
|
|
|
146
247
|
if (config.showDialog) {
|
|
147
248
|
builder.setShowDialog(true)
|
|
249
|
+
secureLog("Force-showing login dialog")
|
|
148
250
|
}
|
|
149
251
|
|
|
150
252
|
// Note: The Android Spotify auth-lib doesn't support a 'campaign' parameter
|
|
@@ -152,18 +254,42 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
152
254
|
|
|
153
255
|
val request = builder.build()
|
|
154
256
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
//
|
|
158
|
-
|
|
257
|
+
secureLog("Opening Spotify authorization activity with REQUEST_CODE=$REQUEST_CODE")
|
|
258
|
+
|
|
259
|
+
// Set a timeout to detect if the auth flow doesn't complete
|
|
260
|
+
authTimeoutHandler = Runnable {
|
|
261
|
+
if (isAuthenticating) {
|
|
262
|
+
Log.e(TAG, "Auth timeout - no response received after ${AUTH_TIMEOUT_MS}ms")
|
|
263
|
+
isAuthenticating = false
|
|
264
|
+
module?.onAuthorizationError(
|
|
265
|
+
SpotifyAuthException.AuthenticationFailed("Authorization timed out. Please try again.")
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
mainHandler.postDelayed(authTimeoutHandler!!, AUTH_TIMEOUT_MS)
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
// AuthorizationClient.openLoginActivity handles both flows:
|
|
273
|
+
// - If Spotify is installed: app-switch auth
|
|
274
|
+
// - If Spotify is not installed: opens a WebView with Spotify login
|
|
275
|
+
AuthorizationClient.openLoginActivity(activity, REQUEST_CODE, request)
|
|
276
|
+
secureLog("AuthorizationClient.openLoginActivity called successfully")
|
|
277
|
+
} catch (e: Exception) {
|
|
278
|
+
Log.e(TAG, "Failed to open authorization activity: ${e.message}", e)
|
|
279
|
+
throw SpotifyAuthException.AuthenticationFailed("Failed to open Spotify authorization: ${e.message}")
|
|
280
|
+
}
|
|
281
|
+
|
|
159
282
|
} catch (e: SpotifyAuthException) {
|
|
283
|
+
Log.e(TAG, "Auth initialization failed (SpotifyAuthException): ${e.message}")
|
|
160
284
|
isAuthenticating = false
|
|
161
|
-
|
|
285
|
+
authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
|
|
286
|
+
module?.onAuthorizationError(e) ?: Log.e(TAG, "Cannot send error - module is null")
|
|
162
287
|
} catch (e: Exception) {
|
|
288
|
+
Log.e(TAG, "Auth initialization failed (Exception): ${e.message}", e)
|
|
163
289
|
isAuthenticating = false
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
290
|
+
authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
|
|
291
|
+
val error = SpotifyAuthException.AuthenticationFailed(e.message ?: "Unknown error")
|
|
292
|
+
module?.onAuthorizationError(error) ?: Log.e(TAG, "Cannot send error - module is null")
|
|
167
293
|
}
|
|
168
294
|
}
|
|
169
295
|
|
|
@@ -172,43 +298,74 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
172
298
|
* Called by the module's OnActivityResult handler.
|
|
173
299
|
*/
|
|
174
300
|
fun handleActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
175
|
-
|
|
301
|
+
Log.d(TAG, "handleActivityResult called - requestCode=$requestCode, resultCode=$resultCode, hasData=${data != null}")
|
|
302
|
+
|
|
303
|
+
if (requestCode != REQUEST_CODE) {
|
|
304
|
+
Log.d(TAG, "Ignoring activity result - wrong request code (expected $REQUEST_CODE, got $requestCode)")
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Cancel the timeout
|
|
309
|
+
authTimeoutHandler?.let {
|
|
310
|
+
mainHandler.removeCallbacks(it)
|
|
311
|
+
secureLog("Auth timeout cancelled")
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!isAuthenticating) {
|
|
315
|
+
Log.w(TAG, "Received activity result but isAuthenticating was false")
|
|
316
|
+
}
|
|
176
317
|
|
|
177
318
|
isAuthenticating = false
|
|
319
|
+
secureLog("Setting isAuthenticating to false")
|
|
178
320
|
|
|
179
|
-
|
|
321
|
+
if (module == null) {
|
|
322
|
+
Log.e(TAG, "CRITICAL: Module is null in handleActivityResult - cannot send events to JS")
|
|
323
|
+
return
|
|
324
|
+
}
|
|
180
325
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
326
|
+
try {
|
|
327
|
+
val response = AuthorizationClient.getResponse(resultCode, data)
|
|
328
|
+
Log.d(TAG, "Spotify response type: ${response.type}")
|
|
329
|
+
|
|
330
|
+
when (response.type) {
|
|
331
|
+
AuthorizationResponse.Type.CODE -> {
|
|
332
|
+
val code = response.code
|
|
333
|
+
secureLog("Authorization code received, length=${code?.length ?: 0}")
|
|
334
|
+
if (code != null) {
|
|
335
|
+
exchangeCodeForToken(code)
|
|
336
|
+
} else {
|
|
337
|
+
Log.e(TAG, "Authorization code was null despite CODE response type")
|
|
338
|
+
module?.onAuthorizationError(
|
|
339
|
+
SpotifyAuthException.AuthenticationFailed("No authorization code received")
|
|
340
|
+
)
|
|
341
|
+
}
|
|
191
342
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
343
|
+
AuthorizationResponse.Type.ERROR -> {
|
|
344
|
+
val errorMsg = response.error ?: "Unknown error"
|
|
345
|
+
Log.e(TAG, "Spotify authorization error: $errorMsg")
|
|
346
|
+
if (errorMsg.contains("access_denied", ignoreCase = true) ||
|
|
347
|
+
errorMsg.contains("cancelled", ignoreCase = true)) {
|
|
348
|
+
module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
|
|
349
|
+
} else {
|
|
350
|
+
module?.onAuthorizationError(SpotifyAuthException.AuthorizationError(errorMsg))
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
AuthorizationResponse.Type.EMPTY -> {
|
|
354
|
+
Log.w(TAG, "Authorization returned EMPTY - user likely cancelled")
|
|
198
355
|
module?.onAuthorizationError(SpotifyAuthException.UserCancelled())
|
|
199
|
-
}
|
|
200
|
-
|
|
356
|
+
}
|
|
357
|
+
else -> {
|
|
358
|
+
Log.e(TAG, "Unexpected Spotify response type: ${response.type}")
|
|
359
|
+
module?.onAuthorizationError(
|
|
360
|
+
SpotifyAuthException.AuthenticationFailed("Unexpected response type: ${response.type}")
|
|
361
|
+
)
|
|
201
362
|
}
|
|
202
363
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
module?.onAuthorizationError(
|
|
209
|
-
SpotifyAuthException.AuthenticationFailed("Unexpected response type: ${response.type}")
|
|
210
|
-
)
|
|
211
|
-
}
|
|
364
|
+
} catch (e: Exception) {
|
|
365
|
+
Log.e(TAG, "Exception in handleActivityResult: ${e.message}", e)
|
|
366
|
+
module?.onAuthorizationError(
|
|
367
|
+
SpotifyAuthException.AuthenticationFailed("Error processing auth result: ${e.message}")
|
|
368
|
+
)
|
|
212
369
|
}
|
|
213
370
|
}
|
|
214
371
|
|
|
@@ -220,16 +377,29 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
220
377
|
* Exchange an authorization code for access + refresh tokens via the backend token swap URL.
|
|
221
378
|
*/
|
|
222
379
|
private fun exchangeCodeForToken(code: String) {
|
|
380
|
+
secureLog("Starting token exchange process")
|
|
381
|
+
|
|
382
|
+
if (module == null) {
|
|
383
|
+
Log.e(TAG, "CRITICAL: Module is null in exchangeCodeForToken")
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
223
387
|
executor.execute {
|
|
224
388
|
try {
|
|
225
389
|
val swapUrl = tokenSwapURL
|
|
226
390
|
val redirect = redirectURL
|
|
227
391
|
|
|
392
|
+
Log.d(TAG, "Token swap URL: ${swapUrl.take(30)}...")
|
|
393
|
+
Log.d(TAG, "Redirect URL: $redirect")
|
|
394
|
+
|
|
228
395
|
if (!swapUrl.startsWith("https://")) {
|
|
396
|
+
Log.e(TAG, "Token swap URL does not use HTTPS: $swapUrl")
|
|
229
397
|
throw SpotifyAuthException.InvalidConfiguration("Token swap URL must use HTTPS")
|
|
230
398
|
}
|
|
231
399
|
|
|
232
400
|
val url = URL(swapUrl)
|
|
401
|
+
Log.d(TAG, "Opening connection to token swap URL")
|
|
402
|
+
|
|
233
403
|
val connection = url.openConnection() as HttpURLConnection
|
|
234
404
|
connection.apply {
|
|
235
405
|
requestMethod = "POST"
|
|
@@ -240,14 +410,19 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
240
410
|
}
|
|
241
411
|
|
|
242
412
|
val body = "code=${Uri.encode(code)}&redirect_uri=${Uri.encode(redirect)}"
|
|
413
|
+
Log.d(TAG, "Sending token exchange request (code length: ${code.length})")
|
|
414
|
+
|
|
243
415
|
OutputStreamWriter(connection.outputStream).use { writer ->
|
|
244
416
|
writer.write(body)
|
|
245
417
|
writer.flush()
|
|
246
418
|
}
|
|
247
419
|
|
|
248
420
|
val responseCode = connection.responseCode
|
|
421
|
+
Log.d(TAG, "Token exchange response code: $responseCode")
|
|
422
|
+
|
|
249
423
|
if (responseCode !in 200..299) {
|
|
250
424
|
val errorMessage = extractErrorMessage(connection, responseCode)
|
|
425
|
+
Log.e(TAG, "Token exchange failed with status $responseCode: $errorMessage")
|
|
251
426
|
throw SpotifyAuthException.NetworkError(errorMessage)
|
|
252
427
|
}
|
|
253
428
|
|
|
@@ -255,6 +430,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
255
430
|
it.readText()
|
|
256
431
|
}
|
|
257
432
|
|
|
433
|
+
Log.d(TAG, "Token exchange response received (body length: ${responseBody.length})")
|
|
434
|
+
|
|
258
435
|
val parsed = parseTokenJSON(responseBody)
|
|
259
436
|
|
|
260
437
|
val refreshToken = parsed.refreshToken
|
|
@@ -262,6 +439,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
262
439
|
|
|
263
440
|
val expirationTime = System.currentTimeMillis() + (parsed.expiresIn * 1000).toLong()
|
|
264
441
|
|
|
442
|
+
Log.d(TAG, "Token exchange successful - expires in ${parsed.expiresIn} seconds")
|
|
443
|
+
|
|
265
444
|
val session = SpotifySessionData(
|
|
266
445
|
accessToken = parsed.accessToken,
|
|
267
446
|
refreshToken = refreshToken,
|
|
@@ -270,6 +449,7 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
270
449
|
)
|
|
271
450
|
|
|
272
451
|
mainHandler.post {
|
|
452
|
+
secureLog("Setting currentSession and sending success event to JS")
|
|
273
453
|
currentSession = session
|
|
274
454
|
}
|
|
275
455
|
|
|
@@ -388,8 +568,17 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
388
568
|
}
|
|
389
569
|
|
|
390
570
|
private fun securelyStoreToken(session: SpotifySessionData) {
|
|
571
|
+
secureLog("Storing token and sending to JS")
|
|
572
|
+
|
|
573
|
+
if (module == null) {
|
|
574
|
+
Log.e(TAG, "CRITICAL: Module is null in securelyStoreToken - cannot send token to JS")
|
|
575
|
+
return
|
|
576
|
+
}
|
|
577
|
+
|
|
391
578
|
// Send the token back to JS
|
|
392
579
|
val expiresIn = (session.expirationTime - System.currentTimeMillis()) / 1000.0
|
|
580
|
+
Log.d(TAG, "Sending access token to JS (expires in ${expiresIn}s)")
|
|
581
|
+
|
|
393
582
|
module?.onAccessTokenObtained(
|
|
394
583
|
session.accessToken,
|
|
395
584
|
session.refreshToken,
|
|
@@ -404,8 +593,9 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
404
593
|
getEncryptedPrefs()?.edit()
|
|
405
594
|
?.putString(PREF_REFRESH_TOKEN_KEY, session.refreshToken)
|
|
406
595
|
?.apply()
|
|
596
|
+
Log.d(TAG, "Refresh token stored securely")
|
|
407
597
|
} catch (e: Exception) {
|
|
408
|
-
Log.e(TAG, "Failed to store refresh token securely: ${e.message}")
|
|
598
|
+
Log.e(TAG, "Failed to store refresh token securely: ${e.message}", e)
|
|
409
599
|
}
|
|
410
600
|
}
|
|
411
601
|
}
|
|
@@ -449,6 +639,8 @@ class SpotifyAuthAuth private constructor(private val appContext: AppContext) {
|
|
|
449
639
|
}
|
|
450
640
|
|
|
451
641
|
fun cleanup() {
|
|
642
|
+
secureLog("Cleaning up SpotifyAuthAuth instance")
|
|
643
|
+
authTimeoutHandler?.let { mainHandler.removeCallbacks(it) }
|
|
452
644
|
refreshHandler.removeCallbacksAndMessages(null)
|
|
453
645
|
executor.shutdown()
|
|
454
646
|
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,7 @@ class SpotifyAuthModule : Module() {
|
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
OnActivityResult { _, payload ->
|
|
66
|
+
secureLog("OnActivityResult received in module - requestCode=${payload.requestCode}, resultCode=${payload.resultCode}")
|
|
61
67
|
spotifyAuth.handleActivityResult(payload.requestCode, payload.resultCode, payload.data)
|
|
62
68
|
}
|
|
63
69
|
|
|
@@ -85,7 +91,12 @@ class SpotifyAuthModule : Module() {
|
|
|
85
91
|
"scope" to scope,
|
|
86
92
|
"error" to null
|
|
87
93
|
)
|
|
88
|
-
|
|
94
|
+
try {
|
|
95
|
+
sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
|
|
96
|
+
secureLog("Success event sent to JS")
|
|
97
|
+
} catch (e: Exception) {
|
|
98
|
+
android.util.Log.e("SpotifyAuth", "Failed to send success event to JS: ${e.message}", e)
|
|
99
|
+
}
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
fun onSignOut() {
|
|
@@ -103,6 +114,8 @@ class SpotifyAuthModule : Module() {
|
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
fun onAuthorizationError(error: Exception) {
|
|
117
|
+
android.util.Log.d("SpotifyAuth", "onAuthorizationError called with: ${error.javaClass.simpleName}: ${error.message}")
|
|
118
|
+
|
|
106
119
|
// Skip sending error events for expected state transitions
|
|
107
120
|
if (error is SpotifyAuthException.SessionError) {
|
|
108
121
|
val msg = error.message ?: ""
|
|
@@ -125,7 +138,7 @@ class SpotifyAuthModule : Module() {
|
|
|
125
138
|
)
|
|
126
139
|
}
|
|
127
140
|
|
|
128
|
-
|
|
141
|
+
android.util.Log.e("SpotifyAuth", "Authorization error: ${errorData["message"] ?: "Unknown error"}")
|
|
129
142
|
|
|
130
143
|
val eventData = mapOf(
|
|
131
144
|
"success" to false,
|
|
@@ -136,7 +149,13 @@ class SpotifyAuthModule : Module() {
|
|
|
136
149
|
"scope" to null,
|
|
137
150
|
"error" to errorData
|
|
138
151
|
)
|
|
139
|
-
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
sendEvent(SPOTIFY_AUTH_EVENT_NAME, eventData)
|
|
155
|
+
secureLog("Error event sent to JS successfully")
|
|
156
|
+
} catch (e: Exception) {
|
|
157
|
+
android.util.Log.e("SpotifyAuth", "CRITICAL: Failed to send error event to JS: ${e.message}", e)
|
|
158
|
+
}
|
|
140
159
|
}
|
|
141
160
|
|
|
142
161
|
// endregion
|
|
@@ -594,7 +594,6 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate {
|
|
|
594
594
|
"user-modify-playback-state": .userModifyPlaybackState,
|
|
595
595
|
"user-read-currently-playing": .userReadCurrentlyPlaying,
|
|
596
596
|
"user-read-recently-played": .userReadRecentlyPlayed,
|
|
597
|
-
"user-read-playback-position": .userReadPlaybackPosition,
|
|
598
597
|
"openid": .openid
|
|
599
598
|
]
|
|
600
599
|
return scopeMapping[scopeString]
|
|
@@ -844,10 +843,9 @@ extension SPTScope {
|
|
|
844
843
|
(.userModifyPlaybackState, "user-modify-playback-state"),
|
|
845
844
|
(.userReadCurrentlyPlaying, "user-read-currently-playing"),
|
|
846
845
|
(.userReadRecentlyPlayed, "user-read-recently-played"),
|
|
847
|
-
(.userReadPlaybackPosition, "user-read-playback-position"),
|
|
848
846
|
(.openid, "openid")
|
|
849
847
|
]
|
|
850
|
-
|
|
848
|
+
|
|
851
849
|
return scopeMapping.filter { contains($0.0) }.map { $0.1 }
|
|
852
850
|
}
|
|
853
851
|
}
|