@superfan-app/spotify-auth 0.1.41 → 0.1.43
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/ios/.swiftlint.yml +27 -0
- package/ios/SpotifyAuthAuth.swift +57 -71
- package/ios/SpotifyAuthModule.swift +6 -6
- package/ios/SpotifyOAuthView.swift +21 -20
- package/package.json +3 -2
- package/scripts/check-ios.sh +19 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
disabled_rules:
|
|
2
|
+
- trailing_whitespace
|
|
3
|
+
- line_length
|
|
4
|
+
- function_body_length
|
|
5
|
+
- file_length
|
|
6
|
+
- type_body_length
|
|
7
|
+
|
|
8
|
+
opt_in_rules:
|
|
9
|
+
- force_unwrapping
|
|
10
|
+
- empty_count
|
|
11
|
+
- empty_string
|
|
12
|
+
|
|
13
|
+
included:
|
|
14
|
+
- .
|
|
15
|
+
|
|
16
|
+
excluded:
|
|
17
|
+
- Pods
|
|
18
|
+
- Carthage
|
|
19
|
+
- vendor
|
|
20
|
+
|
|
21
|
+
analyzer_rules:
|
|
22
|
+
- explicit_self
|
|
23
|
+
- unused_import
|
|
24
|
+
- unused_declaration
|
|
25
|
+
|
|
26
|
+
force_cast: warning
|
|
27
|
+
force_try: warning
|
|
@@ -313,7 +313,7 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
|
|
|
313
313
|
let bodyString = params.map { "\($0)=\($1)" }.joined(separator: "&")
|
|
314
314
|
request.httpBody = bodyString.data(using: .utf8)
|
|
315
315
|
|
|
316
|
-
let task = URLSession.shared.dataTask(with: request) { [weak self] data,
|
|
316
|
+
let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
|
|
317
317
|
if let error = error {
|
|
318
318
|
self?.handleError(error, context: "token_refresh")
|
|
319
319
|
return
|
|
@@ -346,13 +346,14 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
// Get scopes from Info.plist and convert to SPTScope
|
|
349
|
-
let scopes = try self.scopes
|
|
349
|
+
let scopes = try self.scopes
|
|
350
|
+
let sptScopes = scopes.reduce(into: SPTScope()) { result, scopeString in
|
|
350
351
|
if let scope = stringToScope(scopeString: scopeString) {
|
|
351
352
|
result.insert(scope)
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
if
|
|
356
|
+
if sptScopes.isEmpty {
|
|
356
357
|
throw SpotifyAuthError.invalidConfiguration("No valid scopes found in configuration")
|
|
357
358
|
}
|
|
358
359
|
|
|
@@ -363,7 +364,7 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
|
|
|
363
364
|
if config.showDialog {
|
|
364
365
|
sessionManager.alwaysShowAuthorizationDialog = true
|
|
365
366
|
}
|
|
366
|
-
sessionManager.initiateSession(with:
|
|
367
|
+
sessionManager.initiateSession(with: sptScopes, options: .default, campaign: config.campaign)
|
|
367
368
|
} else {
|
|
368
369
|
// Use web auth as fallback
|
|
369
370
|
let webView = SpotifyOAuthView(appContext: nil)
|
|
@@ -399,7 +400,11 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
|
|
|
399
400
|
throw SpotifyAuthError.sessionError("Session manager not initialized")
|
|
400
401
|
}
|
|
401
402
|
let scopes = try self.scopes
|
|
402
|
-
let sptScopes =
|
|
403
|
+
let sptScopes = scopes.reduce(into: SPTScope()) { result, scopeString in
|
|
404
|
+
if let scope = stringToScope(scopeString: scopeString) {
|
|
405
|
+
result.insert(scope)
|
|
406
|
+
}
|
|
407
|
+
}
|
|
403
408
|
isAuthenticating = true
|
|
404
409
|
sessionManager.initiateSession(with: sptScopes, options: .default, campaign: nil)
|
|
405
410
|
} catch {
|
|
@@ -545,50 +550,29 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
|
|
|
545
550
|
// MARK: - Helpers
|
|
546
551
|
|
|
547
552
|
private func stringToScope(scopeString: String) -> SPTScope? {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
case "user-top-read":
|
|
572
|
-
return .userTopRead
|
|
573
|
-
case "ugc-image-upload":
|
|
574
|
-
return .ugcImageUpload
|
|
575
|
-
case "streaming":
|
|
576
|
-
return .streaming
|
|
577
|
-
case "app-remote-control":
|
|
578
|
-
return .appRemoteControl
|
|
579
|
-
case "user-read-playback-state":
|
|
580
|
-
return .userReadPlaybackState
|
|
581
|
-
case "user-modify-playback-state":
|
|
582
|
-
return .userModifyPlaybackState
|
|
583
|
-
case "user-read-currently-playing":
|
|
584
|
-
return .userReadCurrentlyPlaying
|
|
585
|
-
case "user-read-recently-played":
|
|
586
|
-
return .userReadRecentlyPlayed
|
|
587
|
-
case "openid":
|
|
588
|
-
return .openid
|
|
589
|
-
default:
|
|
590
|
-
return nil
|
|
591
|
-
}
|
|
553
|
+
let scopeMapping: [String: SPTScope] = [
|
|
554
|
+
"playlist-read-private": .playlistReadPrivate,
|
|
555
|
+
"playlist-read-collaborative": .playlistReadCollaborative,
|
|
556
|
+
"playlist-modify-public": .playlistModifyPublic,
|
|
557
|
+
"playlist-modify-private": .playlistModifyPrivate,
|
|
558
|
+
"user-follow-read": .userFollowRead,
|
|
559
|
+
"user-follow-modify": .userFollowModify,
|
|
560
|
+
"user-library-read": .userLibraryRead,
|
|
561
|
+
"user-library-modify": .userLibraryModify,
|
|
562
|
+
"user-read-birthdate": .userReadBirthDate,
|
|
563
|
+
"user-read-email": .userReadEmail,
|
|
564
|
+
"user-read-private": .userReadPrivate,
|
|
565
|
+
"user-top-read": .userTopRead,
|
|
566
|
+
"ugc-image-upload": .ugcImageUpload,
|
|
567
|
+
"streaming": .streaming,
|
|
568
|
+
"app-remote-control": .appRemoteControl,
|
|
569
|
+
"user-read-playback-state": .userReadPlaybackState,
|
|
570
|
+
"user-modify-playback-state": .userModifyPlaybackState,
|
|
571
|
+
"user-read-currently-playing": .userReadCurrentlyPlaying,
|
|
572
|
+
"user-read-recently-played": .userReadRecentlyPlayed,
|
|
573
|
+
"openid": .openid
|
|
574
|
+
]
|
|
575
|
+
return scopeMapping[scopeString]
|
|
592
576
|
}
|
|
593
577
|
|
|
594
578
|
private func handleError(_ error: Error, context: String) {
|
|
@@ -669,27 +653,29 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
|
|
|
669
653
|
extension SPTScope {
|
|
670
654
|
/// Converts an SPTScope value into an array of scope strings.
|
|
671
655
|
func scopesToStringArray() -> [String] {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
656
|
+
let scopeMapping: [(SPTScope, String)] = [
|
|
657
|
+
(.playlistReadPrivate, "playlist-read-private"),
|
|
658
|
+
(.playlistReadCollaborative, "playlist-read-collaborative"),
|
|
659
|
+
(.playlistModifyPublic, "playlist-modify-public"),
|
|
660
|
+
(.playlistModifyPrivate, "playlist-modify-private"),
|
|
661
|
+
(.userFollowRead, "user-follow-read"),
|
|
662
|
+
(.userFollowModify, "user-follow-modify"),
|
|
663
|
+
(.userLibraryRead, "user-library-read"),
|
|
664
|
+
(.userLibraryModify, "user-library-modify"),
|
|
665
|
+
(.userReadBirthDate, "user-read-birthdate"),
|
|
666
|
+
(.userReadEmail, "user-read-email"),
|
|
667
|
+
(.userReadPrivate, "user-read-private"),
|
|
668
|
+
(.userTopRead, "user-top-read"),
|
|
669
|
+
(.ugcImageUpload, "ugc-image-upload"),
|
|
670
|
+
(.streaming, "streaming"),
|
|
671
|
+
(.appRemoteControl, "app-remote-control"),
|
|
672
|
+
(.userReadPlaybackState, "user-read-playback-state"),
|
|
673
|
+
(.userModifyPlaybackState, "user-modify-playback-state"),
|
|
674
|
+
(.userReadCurrentlyPlaying, "user-read-currently-playing"),
|
|
675
|
+
(.userReadRecentlyPlayed, "user-read-recently-played"),
|
|
676
|
+
(.openid, "openid")
|
|
677
|
+
]
|
|
678
|
+
|
|
679
|
+
return scopeMapping.filter { contains($0.0) }.map { $0.1 }
|
|
694
680
|
}
|
|
695
681
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ExpoModulesCore
|
|
2
2
|
import SpotifyiOS
|
|
3
3
|
|
|
4
|
-
let
|
|
4
|
+
let spotifyAuthorizationEventName = "onSpotifyAuth"
|
|
5
5
|
|
|
6
6
|
#if DEBUG
|
|
7
7
|
func secureLog(_ message: String, sensitive: Bool = false) {
|
|
@@ -44,11 +44,11 @@ public class SpotifyAuthModule: Module {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
Constants([
|
|
47
|
-
"AuthEventName":
|
|
47
|
+
"AuthEventName": spotifyAuthorizationEventName
|
|
48
48
|
])
|
|
49
49
|
|
|
50
50
|
// Defines event names that the module can send to JavaScript.
|
|
51
|
-
Events(
|
|
51
|
+
Events(spotifyAuthorizationEventName)
|
|
52
52
|
|
|
53
53
|
// Called when JS starts observing the event.
|
|
54
54
|
OnStartObserving {
|
|
@@ -114,7 +114,7 @@ public class SpotifyAuthModule: Module {
|
|
|
114
114
|
"token": token,
|
|
115
115
|
"error": NSNull() // Use NSNull() instead of nil.
|
|
116
116
|
]
|
|
117
|
-
sendEvent(
|
|
117
|
+
sendEvent(spotifyAuthorizationEventName, eventData)
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
@objc
|
|
@@ -125,7 +125,7 @@ public class SpotifyAuthModule: Module {
|
|
|
125
125
|
"token": NSNull(), // Use NSNull() instead of nil.
|
|
126
126
|
"error": NSNull() // Use NSNull() instead of nil.
|
|
127
127
|
]
|
|
128
|
-
sendEvent(
|
|
128
|
+
sendEvent(spotifyAuthorizationEventName, eventData)
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
@objc
|
|
@@ -157,7 +157,7 @@ public class SpotifyAuthModule: Module {
|
|
|
157
157
|
"token": NSNull(),
|
|
158
158
|
"error": errorData
|
|
159
159
|
]
|
|
160
|
-
sendEvent(
|
|
160
|
+
sendEvent(spotifyAuthorizationEventName, eventData)
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
private func mapSpotifyError(_ error: SpotifyAuthError) -> [String: Any] {
|
|
@@ -41,7 +41,8 @@ class SpotifyOAuthView: ExpoView {
|
|
|
41
41
|
private var isAuthenticating = false
|
|
42
42
|
private var expectedRedirectScheme: String?
|
|
43
43
|
private var authTimeout: Timer?
|
|
44
|
-
private static let
|
|
44
|
+
private static let authTimeoutInterval: TimeInterval = 300 // 5 minutes
|
|
45
|
+
private var observerToken: NSKeyValueObservation?
|
|
45
46
|
|
|
46
47
|
required init(appContext: AppContext? = nil) {
|
|
47
48
|
// Generate a random state string for CSRF protection
|
|
@@ -91,14 +92,14 @@ class SpotifyOAuthView: ExpoView {
|
|
|
91
92
|
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor)
|
|
92
93
|
])
|
|
93
94
|
|
|
94
|
-
//
|
|
95
|
-
webView.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
// Setup modern KVO observation
|
|
96
|
+
observerToken = webView.observe(\.isLoading, options: [.new]) { [weak self] _, _ in
|
|
97
|
+
if let activityIndicator = self?.subviews.first(where: { $0 is UIActivityIndicatorView }) as? UIActivityIndicatorView {
|
|
98
|
+
if self?.webView.isLoading == true {
|
|
99
|
+
activityIndicator.startAnimating()
|
|
100
|
+
} else {
|
|
101
|
+
activityIndicator.stopAnimating()
|
|
102
|
+
}
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
}
|
|
@@ -122,20 +123,20 @@ class SpotifyOAuthView: ExpoView {
|
|
|
122
123
|
WKWebsiteDataStore.default().removeData(
|
|
123
124
|
ofTypes: [WKWebsiteDataTypeCookies, WKWebsiteDataTypeSessionStorage],
|
|
124
125
|
modifiedSince: Date(timeIntervalSince1970: 0)
|
|
125
|
-
) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
) { }
|
|
127
|
+
|
|
128
|
+
self.initiateAuthRequest(
|
|
129
|
+
clientId: clientId,
|
|
130
|
+
redirectUri: redirectUri,
|
|
131
|
+
scopes: scopes,
|
|
132
|
+
showDialog: showDialog,
|
|
133
|
+
campaign: campaign
|
|
134
|
+
)
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
private func startAuthTimeout() {
|
|
137
138
|
authTimeout?.invalidate()
|
|
138
|
-
authTimeout = Timer.scheduledTimer(withTimeInterval: Self.
|
|
139
|
+
authTimeout = Timer.scheduledTimer(withTimeInterval: Self.authTimeoutInterval, repeats: false) { [weak self] _ in
|
|
139
140
|
self?.handleTimeout()
|
|
140
141
|
}
|
|
141
142
|
}
|
|
@@ -192,7 +193,7 @@ class SpotifyOAuthView: ExpoView {
|
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
deinit {
|
|
195
|
-
|
|
196
|
+
observerToken?.invalidate()
|
|
196
197
|
authTimeout?.invalidate()
|
|
197
198
|
}
|
|
198
199
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superfan-app/spotify-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "Spotify OAuth module for Expo",
|
|
5
5
|
"main": "src/index.tsx",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"test": "expo-module test",
|
|
13
13
|
"prepare": "expo-module prepare",
|
|
14
14
|
"prepublishOnly": "expo-module prepublishOnly",
|
|
15
|
-
"expo-module": "expo-module"
|
|
15
|
+
"expo-module": "expo-module",
|
|
16
|
+
"check-ios": "./scripts/check-ios.sh"
|
|
16
17
|
},
|
|
17
18
|
"keywords": [
|
|
18
19
|
"react-native",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Exit on error
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
echo "Checking iOS code..."
|
|
7
|
+
|
|
8
|
+
# Install SwiftLint if not installed
|
|
9
|
+
if ! command -v swiftlint &> /dev/null; then
|
|
10
|
+
echo "SwiftLint not found. Installing..."
|
|
11
|
+
brew install swiftlint
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Run SwiftLint
|
|
15
|
+
cd ios
|
|
16
|
+
echo "Running SwiftLint..."
|
|
17
|
+
swiftlint lint --quiet
|
|
18
|
+
|
|
19
|
+
echo "iOS checks completed successfully!"
|