@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.
@@ -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
@@ -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.reduce(into: SPTScope()) { result, scopeString in
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 scopes.isEmpty {
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: scopes, options: .default, campaign: config.campaign)
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 = SPTScope(rawValue: scopes.joined(separator: " "))
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
- switch scopeString {
549
- case "playlist-read-private":
550
- return .playlistReadPrivate
551
- case "playlist-read-collaborative":
552
- return .playlistReadCollaborative
553
- case "playlist-modify-public":
554
- return .playlistModifyPublic
555
- case "playlist-modify-private":
556
- return .playlistModifyPrivate
557
- case "user-follow-read":
558
- return .userFollowRead
559
- case "user-follow-modify":
560
- return .userFollowModify
561
- case "user-library-read":
562
- return .userLibraryRead
563
- case "user-library-modify":
564
- return .userLibraryModify
565
- case "user-read-birthdate":
566
- return .userReadBirthDate
567
- case "user-read-email":
568
- return .userReadEmail
569
- case "user-read-private":
570
- return .userReadPrivate
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
- var scopes: [String] = []
673
- if contains(.playlistReadPrivate) { scopes.append("playlist-read-private") }
674
- if contains(.playlistReadCollaborative) { scopes.append("playlist-read-collaborative") }
675
- if contains(.playlistModifyPublic) { scopes.append("playlist-modify-public") }
676
- if contains(.playlistModifyPrivate) { scopes.append("playlist-modify-private") }
677
- if contains(.userFollowRead) { scopes.append("user-follow-read") }
678
- if contains(.userFollowModify) { scopes.append("user-follow-modify") }
679
- if contains(.userLibraryRead) { scopes.append("user-library-read") }
680
- if contains(.userLibraryModify) { scopes.append("user-library-modify") }
681
- if contains(.userReadBirthDate) { scopes.append("user-read-birthdate") }
682
- if contains(.userReadEmail) { scopes.append("user-read-email") }
683
- if contains(.userReadPrivate) { scopes.append("user-read-private") }
684
- if contains(.userTopRead) { scopes.append("user-top-read") }
685
- if contains(.ugcImageUpload) { scopes.append("ugc-image-upload") }
686
- if contains(.streaming) { scopes.append("streaming") }
687
- if contains(.appRemoteControl) { scopes.append("app-remote-control") }
688
- if contains(.userReadPlaybackState) { scopes.append("user-read-playback-state") }
689
- if contains(.userModifyPlaybackState) { scopes.append("user-modify-playback-state") }
690
- if contains(.userReadCurrentlyPlaying) { scopes.append("user-read-currently-playing") }
691
- if contains(.userReadRecentlyPlayed) { scopes.append("user-read-recently-played") }
692
- if contains(.openid) { scopes.append("openid") }
693
- 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 }
694
680
  }
695
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
@@ -91,14 +92,14 @@ class SpotifyOAuthView: ExpoView {
91
92
  activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor)
92
93
  ])
93
94
 
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()
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
- ) { [weak self] in
126
- self?.initiateAuthRequest(
127
- clientId: clientId,
128
- redirectUri: redirectUri,
129
- scopes: scopes,
130
- showDialog: showDialog,
131
- campaign: campaign
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.AUTH_TIMEOUT_INTERVAL, repeats: false) { [weak self] _ in
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
- webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.isLoading))
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.41",
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!"