@superfan-app/spotify-auth 0.1.42 → 0.1.44

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.
@@ -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, response, error in
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
@@ -550,50 +550,29 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
550
550
  // MARK: - Helpers
551
551
 
552
552
  private func stringToScope(scopeString: String) -> SPTScope? {
553
- switch scopeString {
554
- case "playlist-read-private":
555
- return .playlistReadPrivate
556
- case "playlist-read-collaborative":
557
- return .playlistReadCollaborative
558
- case "playlist-modify-public":
559
- return .playlistModifyPublic
560
- case "playlist-modify-private":
561
- return .playlistModifyPrivate
562
- case "user-follow-read":
563
- return .userFollowRead
564
- case "user-follow-modify":
565
- return .userFollowModify
566
- case "user-library-read":
567
- return .userLibraryRead
568
- case "user-library-modify":
569
- return .userLibraryModify
570
- case "user-read-birthdate":
571
- return .userReadBirthDate
572
- case "user-read-email":
573
- return .userReadEmail
574
- case "user-read-private":
575
- return .userReadPrivate
576
- case "user-top-read":
577
- return .userTopRead
578
- case "ugc-image-upload":
579
- return .ugcImageUpload
580
- case "streaming":
581
- return .streaming
582
- case "app-remote-control":
583
- return .appRemoteControl
584
- case "user-read-playback-state":
585
- return .userReadPlaybackState
586
- case "user-modify-playback-state":
587
- return .userModifyPlaybackState
588
- case "user-read-currently-playing":
589
- return .userReadCurrentlyPlaying
590
- case "user-read-recently-played":
591
- return .userReadRecentlyPlayed
592
- case "openid":
593
- return .openid
594
- default:
595
- return nil
596
- }
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]
597
576
  }
598
577
 
599
578
  private func handleError(_ error: Error, context: String) {
@@ -674,27 +653,29 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
674
653
  extension SPTScope {
675
654
  /// Converts an SPTScope value into an array of scope strings.
676
655
  func scopesToStringArray() -> [String] {
677
- var scopes: [String] = []
678
- if contains(.playlistReadPrivate) { scopes.append("playlist-read-private") }
679
- if contains(.playlistReadCollaborative) { scopes.append("playlist-read-collaborative") }
680
- if contains(.playlistModifyPublic) { scopes.append("playlist-modify-public") }
681
- if contains(.playlistModifyPrivate) { scopes.append("playlist-modify-private") }
682
- if contains(.userFollowRead) { scopes.append("user-follow-read") }
683
- if contains(.userFollowModify) { scopes.append("user-follow-modify") }
684
- if contains(.userLibraryRead) { scopes.append("user-library-read") }
685
- if contains(.userLibraryModify) { scopes.append("user-library-modify") }
686
- if contains(.userReadBirthDate) { scopes.append("user-read-birthdate") }
687
- if contains(.userReadEmail) { scopes.append("user-read-email") }
688
- if contains(.userReadPrivate) { scopes.append("user-read-private") }
689
- if contains(.userTopRead) { scopes.append("user-top-read") }
690
- if contains(.ugcImageUpload) { scopes.append("ugc-image-upload") }
691
- if contains(.streaming) { scopes.append("streaming") }
692
- if contains(.appRemoteControl) { scopes.append("app-remote-control") }
693
- if contains(.userReadPlaybackState) { scopes.append("user-read-playback-state") }
694
- if contains(.userModifyPlaybackState) { scopes.append("user-modify-playback-state") }
695
- if contains(.userReadCurrentlyPlaying) { scopes.append("user-read-currently-playing") }
696
- if contains(.userReadRecentlyPlayed) { scopes.append("user-read-recently-played") }
697
- if contains(.openid) { scopes.append("openid") }
698
- return scopes
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 }
699
680
  }
700
681
  }
@@ -1,7 +1,7 @@
1
1
  import ExpoModulesCore
2
2
  import SpotifyiOS
3
3
 
4
- let SPOTIFY_AUTHORIZATION_EVENT_NAME = "onSpotifyAuth"
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": SPOTIFY_AUTHORIZATION_EVENT_NAME,
47
+ "AuthEventName": spotifyAuthorizationEventName
48
48
  ])
49
49
 
50
50
  // Defines event names that the module can send to JavaScript.
51
- Events(SPOTIFY_AUTHORIZATION_EVENT_NAME)
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(SPOTIFY_AUTHORIZATION_EVENT_NAME, eventData)
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(SPOTIFY_AUTHORIZATION_EVENT_NAME, eventData)
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(SPOTIFY_AUTHORIZATION_EVENT_NAME, eventData)
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 AUTH_TIMEOUT_INTERVAL: TimeInterval = 300 // 5 minutes
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
@@ -56,50 +57,68 @@ class SpotifyOAuthView: ExpoView {
56
57
  }
57
58
 
58
59
  private func setupWebView() {
60
+ // Ensure we're on the main thread for UI setup
61
+ guard Thread.isMainThread else {
62
+ DispatchQueue.main.async { [weak self] in
63
+ self?.setupWebView()
64
+ }
65
+ return
66
+ }
67
+
59
68
  // Create a configuration that prevents data persistence
60
- let config = WKWebViewConfiguration()
61
- let prefs = WKWebpagePreferences()
62
- prefs.allowsContentJavaScript = true
63
- config.defaultWebpagePreferences = prefs
64
-
65
- // Ensure cookies and data are not persisted
66
- let dataStore = WKWebsiteDataStore.nonPersistent()
67
- config.websiteDataStore = dataStore
68
-
69
- webView = WKWebView(frame: .zero, configuration: config)
70
- webView.navigationDelegate = self
71
- webView.allowsBackForwardNavigationGestures = true
72
- webView.customUserAgent = "SpotifyAuth-iOS/1.0" // Custom UA to identify our app
73
-
74
- // Add loading indicator
75
- let activityIndicator = UIActivityIndicatorView(style: .medium)
76
- activityIndicator.translatesAutoresizingMaskIntoConstraints = false
77
- activityIndicator.hidesWhenStopped = true
78
-
79
- addSubview(webView)
80
- addSubview(activityIndicator)
81
-
82
- // Setup constraints
83
- webView.translatesAutoresizingMaskIntoConstraints = false
84
- NSLayoutConstraint.activate([
85
- webView.topAnchor.constraint(equalTo: topAnchor),
86
- webView.leadingAnchor.constraint(equalTo: leadingAnchor),
87
- webView.trailingAnchor.constraint(equalTo: trailingAnchor),
88
- webView.bottomAnchor.constraint(equalTo: bottomAnchor),
69
+ let config: WKWebViewConfiguration = {
70
+ let configuration = WKWebViewConfiguration()
71
+ configuration.processPool = WKProcessPool() // Create a new process pool
72
+ let prefs = WKWebpagePreferences()
73
+ prefs.allowsContentJavaScript = true
74
+ configuration.defaultWebpagePreferences = prefs
89
75
 
90
- activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
91
- activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor)
92
- ])
93
-
94
- // Start observing loading state
95
- webView.addObserver(self, forKeyPath: #keyPath(WKWebView.isLoading), options: .new, context: nil)
96
- }
97
-
98
- override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
99
- if keyPath == #keyPath(WKWebView.isLoading) {
100
- if let activityIndicator = subviews.first(where: { $0 is UIActivityIndicatorView }) as? UIActivityIndicatorView {
101
- webView.isLoading ? activityIndicator.startAnimating() : activityIndicator.stopAnimating()
76
+ // Ensure cookies and data are not persisted
77
+ let dataStore = WKWebsiteDataStore.nonPersistent()
78
+ configuration.websiteDataStore = dataStore
79
+ return configuration
80
+ }()
81
+
82
+ // Initialize webview on main thread with error handling
83
+ do {
84
+ webView = WKWebView(frame: .zero, configuration: config)
85
+ webView.navigationDelegate = self
86
+ webView.allowsBackForwardNavigationGestures = true
87
+ webView.customUserAgent = "SpotifyAuth-iOS/1.0" // Custom UA to identify our app
88
+
89
+ // Add loading indicator
90
+ let activityIndicator = UIActivityIndicatorView(style: .medium)
91
+ activityIndicator.translatesAutoresizingMaskIntoConstraints = false
92
+ activityIndicator.hidesWhenStopped = true
93
+
94
+ addSubview(webView)
95
+ addSubview(activityIndicator)
96
+
97
+ // Setup constraints
98
+ webView.translatesAutoresizingMaskIntoConstraints = false
99
+ NSLayoutConstraint.activate([
100
+ webView.topAnchor.constraint(equalTo: topAnchor),
101
+ webView.leadingAnchor.constraint(equalTo: leadingAnchor),
102
+ webView.trailingAnchor.constraint(equalTo: trailingAnchor),
103
+ webView.bottomAnchor.constraint(equalTo: bottomAnchor),
104
+
105
+ activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
106
+ activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor)
107
+ ])
108
+
109
+ // Setup modern KVO observation
110
+ observerToken = webView.observe(\.isLoading, options: [.new]) { [weak self] _, _ in
111
+ if let activityIndicator = self?.subviews.first(where: { $0 is UIActivityIndicatorView }) as? UIActivityIndicatorView {
112
+ if self?.webView.isLoading == true {
113
+ activityIndicator.startAnimating()
114
+ } else {
115
+ activityIndicator.stopAnimating()
116
+ }
117
+ }
102
118
  }
119
+ } catch {
120
+ secureLog("Failed to setup WebView: \(error.localizedDescription)")
121
+ delegate?.oauthView(self, didFailWithError: SpotifyOAuthError.authorizationError("Failed to initialize web view"))
103
122
  }
104
123
  }
105
124
 
@@ -122,20 +141,20 @@ class SpotifyOAuthView: ExpoView {
122
141
  WKWebsiteDataStore.default().removeData(
123
142
  ofTypes: [WKWebsiteDataTypeCookies, WKWebsiteDataTypeSessionStorage],
124
143
  modifiedSince: Date(timeIntervalSince1970: 0)
125
- ) { [weak self] in
126
- self?.initiateAuthRequest(
127
- clientId: clientId,
128
- redirectUri: redirectUri,
129
- scopes: scopes,
130
- showDialog: showDialog,
131
- campaign: campaign
132
- )
133
- }
144
+ ) { }
145
+
146
+ self.initiateAuthRequest(
147
+ clientId: clientId,
148
+ redirectUri: redirectUri,
149
+ scopes: scopes,
150
+ showDialog: showDialog,
151
+ campaign: campaign
152
+ )
134
153
  }
135
154
 
136
155
  private func startAuthTimeout() {
137
156
  authTimeout?.invalidate()
138
- authTimeout = Timer.scheduledTimer(withTimeInterval: Self.AUTH_TIMEOUT_INTERVAL, repeats: false) { [weak self] _ in
157
+ authTimeout = Timer.scheduledTimer(withTimeInterval: Self.authTimeoutInterval, repeats: false) { [weak self] _ in
139
158
  self?.handleTimeout()
140
159
  }
141
160
  }
@@ -192,7 +211,7 @@ class SpotifyOAuthView: ExpoView {
192
211
  }
193
212
 
194
213
  deinit {
195
- webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.isLoading))
214
+ observerToken?.invalidate()
196
215
  authTimeout?.invalidate()
197
216
  }
198
217
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superfan-app/spotify-auth",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
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!"