diff --git a/.jazzy.yaml b/.jazzy.yaml index 12cf62eba..3b3dff1ab 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -31,10 +31,13 @@ custom_categories: - name: Authorization Backends children: + - AuthorizationCodeFlowBackend - AuthorizationCodeFlowClientBackend - AuthorizationCodeFlowProxyBackend + - AuthorizationCodeFlowPKCEBackend - AuthorizationCodeFlowPKCEClientBackend - AuthorizationCodeFlowPKCEProxyBackend + - ClientCredentialsFlowBackend - ClientCredentialsFlowClientBackend - ClientCredentialsFlowProxyBackend diff --git a/Sources/SpotifyAPITestUtilities/MiscellaneousUtilities.swift b/Sources/SpotifyAPITestUtilities/MiscellaneousUtilities.swift index aa47bb274..31a83494b 100644 --- a/Sources/SpotifyAPITestUtilities/MiscellaneousUtilities.swift +++ b/Sources/SpotifyAPITestUtilities/MiscellaneousUtilities.swift @@ -198,7 +198,7 @@ public extension URLSession { var request = request request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData - return URLSession.shared.dataTaskPublisher(for: request) + return URLSession.shared.dataTaskPublisher(for: request) .mapError { $0 as Error } .map { data, response -> (data: Data, response: HTTPURLResponse) in guard let httpURLResponse = response as? HTTPURLResponse else { diff --git a/Sources/SpotifyWebAPI/API/SpotifyAPI+Tracks.swift b/Sources/SpotifyWebAPI/API/SpotifyAPI+Tracks.swift index 83a544eb7..78803f364 100644 --- a/Sources/SpotifyWebAPI/API/SpotifyAPI+Tracks.swift +++ b/Sources/SpotifyWebAPI/API/SpotifyAPI+Tracks.swift @@ -227,7 +227,7 @@ public extension SpotifyAPI { empty array will immediately cause an empty array of results to be returned without a network request being made. - Returns: Results are returned in the order requested. If the audio - features for a track is not found, `nil` is returned in the + features for a track is not found, `nil` is returned in the appropriate position. Duplicate ids in the request will result in duplicate results in the response. diff --git a/Sources/SpotifyWebAPI/API/SpotifyAPI.swift b/Sources/SpotifyWebAPI/API/SpotifyAPI.swift index 43479ce71..ce984063c 100644 --- a/Sources/SpotifyWebAPI/API/SpotifyAPI.swift +++ b/Sources/SpotifyWebAPI/API/SpotifyAPI.swift @@ -44,7 +44,7 @@ public class SpotifyAPI: Coda It is this property that you should encode to data using a `JSONEncoder` in order to save it to persistent storage. This prevents the user from having - to login again everytime the app is quit and relaunched. See this + to login again every time the app is quit and relaunched. See this [article][1] for more information. Assigning a new authorization manager to this property causes @@ -69,12 +69,12 @@ public class SpotifyAPI: Coda } /** - A function that gets called everytime this class—and only this class—needs + A function that gets called every time this class—and only this class—needs to make a network request. Use this function if you need to use a custom networking client. The `url` and `httpMethod` properties of the `URLRequest` parameter are guaranteed to - be non-`nil`. No guarantees are made about which thread this function will + be non-`nil`. No guarantees are made about which thread this function will be called on. By default, `URLSession` will be used for the network requests. @@ -178,13 +178,12 @@ public class SpotifyAPI: Coda information. It is this property that you should encode to data using a `JSONEncoder` in order to save it to persistent storage. See this [article][2] for more information. - - networkAdaptor: A function that gets called everytime this class—and + - networkAdaptor: A function that gets called every time this class—and only this class—needs to make a network request. The - `authorizationManager` will **NOT** use this function. Instead, - you must configure a network adaptor for it as well. Use this + `authorizationManager` will **NOT** use this function. Use this function if you need to use a custom networking client. The `url` and `httpMethod` properties of the `URLRequest` parameter are - guaranteed to be non-`nil`. No guarantees are made about which + guaranteed to be non-`nil`. No guarantees are made about which thread this function will be called on. The default is `nil`, in which case `URLSession` will be used for the network requests. diff --git a/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManager.swift b/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManager.swift index 503421b45..d20e2fb13 100644 --- a/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManager.swift +++ b/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManager.swift @@ -67,7 +67,7 @@ import FoundationNetworking information to persistent storage. See this [article][3] for more information. [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow - [2]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + [2]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce [3]: https://github.com/Peter-Schorn/SpotifyAPI/wiki/Saving-authorization-information-to-persistent-storage. */ public class AuthorizationCodeFlowBackendManager: @@ -263,8 +263,8 @@ public extension AuthorizationCodeFlowBackendManager { - Parameters: - redirectURI: The location that Spotify will redirect to after the user authorizes or denies authorization for your app. Usually, this - should be a custom URL scheme that redirects to a location in your - app. This URI needs to have been entered in the Redirect URI + should contain a custom URL scheme that redirects to a location in + your app. This URI needs to have been entered in the Redirect URI whitelist that you specified when you [registered your application][2]. - showDialog: Whether or not to force the user to approve the app again @@ -611,7 +611,7 @@ public extension AuthorizationCodeFlowBackendManager { information to persistent storage. See this [article][3] for more information. [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow - [2]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + [2]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce [3]: https://github.com/Peter-Schorn/SpotifyAPI/wiki/Saving-authorization-information-to-persistent-storage. */ public final class AuthorizationCodeFlowManager: diff --git a/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManagerBase.swift b/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManagerBase.swift index 8a277a3a6..d891f7f2a 100644 --- a/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManagerBase.swift +++ b/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowManagerBase.swift @@ -333,7 +333,7 @@ public extension AuthorizationCodeFlowManagerBase { */ func accessTokenIsExpired(tolerance: Double = 120) -> Bool { return self.updateAuthInfoQueue.sync { - return accessTokenIsExpiredNOTThreadSafe(tolerance: tolerance) + return accessTokenIsExpiredNOTThreadSafe(tolerance: tolerance) } } @@ -382,7 +382,7 @@ extension AuthorizationCodeFlowManagerBase { /// This method should **ALWAYS** be called within /// `updateAuthInfoDispatchQueue`, or the thread-safety guarantees /// of this class will be violated. - func accessTokenIsExpiredNOTThreadSafe(tolerance: Double = 120) -> Bool { + func accessTokenIsExpiredNOTThreadSafe(tolerance: Double = 120) -> Bool { if (self._accessToken == nil) != (self._expirationDate == nil) { let expirationDateString = self._expirationDate? .description(with: .current) ?? "nil" diff --git a/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowPKCEManager.swift b/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowPKCEManager.swift index df8bbaee3..11bf70041 100644 --- a/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowPKCEManager.swift +++ b/Sources/SpotifyWebAPI/Authorization/AuthorizationCodeFlowPKCEManager.swift @@ -19,7 +19,7 @@ import Logging The authorization code flow with PKCE is the best option for mobile and desktop applications where it is unsafe to store your client secret. It provides an - additional layer of security compared to the [authorization code flow][2]. For + additional layer of security compared to the [Authorization Code Flow][2]. For further information about this flow, see [IETF RFC-7636][3]. # Backend @@ -291,8 +291,8 @@ public extension AuthorizationCodeFlowPKCEBackendManager { - Parameters: - redirectURI: The location that Spotify will redirect to after the user authorizes or denies authorization for your app. Usually, this - should be a custom URL scheme that redirects to a location in your - app. This URI needs to have been entered in the Redirect URI + should contain a custom URL scheme that redirects to a location in + your app. This URI needs to have been entered in the Redirect URI whitelist that you specified when you [registered your application][4]. - codeChallenge: The code challenge. See above. @@ -624,14 +624,14 @@ public extension AuthorizationCodeFlowPKCEBackendManager { The authorization code flow with PKCE is the best option for mobile and desktop applications where it is unsafe to store your client secret. It provides an - additional layer of security compared to the [authorization code flow][2]. For + additional layer of security compared to the [Authorization Code Flow][2]. For further information about this flow, see [IETF RFC-7636][3]. This class stores the client id locally. Consider using `AuthorizationCodeFlowPKCEBackendManager`, which allows you to setup a custom backend server that can store these sensitive credentials and which communicates with Spotify on your behalf in - order to retrieve the authorization information. + order to retrieve the authorization information. # Authorization @@ -709,8 +709,8 @@ public final class AuthorizationCodeFlowPKCEManager: } /** - Creates an authorization manager for the - [Authorization Code Flow with Proof Key for Code Exchange][1]. + Creates an authorization manager for the [Authorization Code Flow with + Proof Key for Code Exchange][1]. **In general, only use this initializer if you have retrieved the** **authorization information from an external source.** Otherwise, use diff --git a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowClientBackend.swift b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowClientBackend.swift index 18893a56d..8bc48d927 100644 --- a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowClientBackend.swift +++ b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowClientBackend.swift @@ -17,7 +17,10 @@ import OpenCombineFoundation authorization information and refresh the access token using the [Authorization Code Flow][1]. - Compare with `AuthorizationCodeFlowProxyBackend`. + If you are communicating with a custom backend server, then use + `AuthorizationCodeFlowProxyBackend` instead, which does not send the `clientId` + and `clientSecret` in network requests because these values should be securely + stored on your backend server. Usually you should not need to create instances of this type directly. `AuthorizationCodeFlowManager` uses this type internally by inheriting from diff --git a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEClientBackend.swift b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEClientBackend.swift index beb530df5..8ece39ee8 100644 --- a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEClientBackend.swift +++ b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEClientBackend.swift @@ -17,7 +17,10 @@ import OpenCombineFoundation authorization information and refresh the access token using the [Authorization Code Flow with Proof Key for Code Exchange][1]. - Compare with `AuthorizationCodeFlowPKCEProxyBackend`. + If you are communicating with a custom backend server, then use + `AuthorizationCodeFlowPKCEProxyBackend` instead, which does not send the + `clientId` in network requests because this value should be securely stored on + your backend server. Usually you should not need to create instances of this type directly. `AuthorizationCodeFlowPKCEManager` uses this type internally by inheriting from @@ -25,7 +28,7 @@ import OpenCombineFoundation [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce */ -public struct AuthorizationCodeFlowPKCEClientBackend: AuthorizationCodeFlowPKCEBackend { +public struct AuthorizationCodeFlowPKCEClientBackend: AuthorizationCodeFlowPKCEBackend { /// The logger for this struct. public static var logger = Logger( diff --git a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEProxyBackend.swift b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEProxyBackend.swift index 308721377..ca734fe69 100644 --- a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEProxyBackend.swift +++ b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowPKCEProxyBackend.swift @@ -18,10 +18,22 @@ import OpenCombineFoundation authorization information and refresh the access token using the [Authorization Code Flow with Proof Key for Code Exchange][1]. - Compare with `AuthorizationCodeFlowPKCEClientBackend`. + This server must have the following endpoints: - This type requires a custom backend server that can store your client secret - and redirect URI. + * `tokensURL`: Accepts a post request with the authorization code and coder + verifier in the body in x-www-form-urlencoded format and must return the + authorization information. See + `self.requestAccessAndRefreshTokens(code:codeVerifier:redirectURIWithQuery:)` + for more information. + + * `tokenRefreshURL`: Accepts a post request with the refresh token in the body + in x-www-form-urlencoded format and which must return the authorization + information. See `self.refreshTokens(refreshToken:)` for more information. + + In contrast with `AuthorizationCodeFlowPKCEClientBackend`, which can be used if + you are communicating directly with Spotify, this type does not send the + `clientSecret` in network requests because this value should be securely + stored on your backend server. [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce */ @@ -44,8 +56,13 @@ public struct AuthorizationCodeFlowPKCEProxyBackend: AuthorizationCodeFlowPKCEBa authorization code and coder verifier in the body in x-www-form-urlencoded format and which must return the authorization information. + The [/authorization-code-flow-pkce/retrieve-tokens][1] endpoint of + SpotifyAPIServer can be used for this URL. + See `self.requestAccessAndRefreshTokens(code:codeVerifier:redirectURIWithQuery:)` for more information. + + [1]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flow-pkceretrieve-tokens */ public let tokensURL: URL @@ -54,7 +71,12 @@ public struct AuthorizationCodeFlowPKCEProxyBackend: AuthorizationCodeFlowPKCEBa refresh token in the body in x-www-form-urlencoded format and which must return the authorization information. + The [/authorization-code-flow-pkce/refresh-tokens][1] endpoint of + SpotifyAPIServer can be used for this URL. + See `self.refreshTokens(refreshToken:)` for more information. + + [1]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flow-pkcerefresh-tokens */ public let tokenRefreshURL: URL @@ -97,23 +119,28 @@ public struct AuthorizationCodeFlowPKCEProxyBackend: AuthorizationCodeFlowPKCEBa - tokensURL: The URL to your custom backend server that accepts a post request with the authorization code and coder verifier in the body in "x-www-form-urlencoded" format and which must return the - authorization information. See + authorization information. The + [/authorization-code-flow-pkce/retrieve-tokens][3] endpoint of + SpotifyAPIServer can be used for this URL. See `self.requestAccessAndRefreshTokens(code:codeVerifier:redirectURIWithQuery:)` for more information. - tokenRefreshURL: The URL to your custom backend server that accepts a post request with the refresh token in the body in "x-www-form-urlencoded" format and which must return the - authorization information. See - `self.refreshTokens(refreshToken:)` for more - information. + authorization information. The + [/authorization-code-flow-pkce/refresh-tokens][4] endpoint of + SpotifyAPIServer can be used for this URL. See + `self.refreshTokens(refreshToken:)` for more information. - decodeServerError: A hook for decoding an error produced by your backend server into an error type, which will then be thrown to downstream subscribers. Do not use this function to decode the documented error objects produced by Spotify, such as `SpotifyAuthenticationError`. This will be done elsewhere. - [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce [2]: https://developer.spotify.com/documentation/general/guides/app-settings/#register-your-app + [3]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flow-pkceretrieve-tokens + [4]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flow-pkcerefresh-tokens */ public init( clientId: String, diff --git a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowProxyBackend.swift b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowProxyBackend.swift index 5aa14cb2b..a615b1db4 100644 --- a/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowProxyBackend.swift +++ b/Sources/SpotifyWebAPI/Authorization/Backends/AuthorizationCodeFlowProxyBackend.swift @@ -15,14 +15,28 @@ import OpenCombineFoundation /** Communicates with a backend server that you setup in order to retrieve the - authorization information and refresh the access token using the [Authorization + authorization information and refresh the access token using the [Authorization Code Flow][1]. - Compare with `AuthorizationCodeFlowClientBackend`. - - This type requires a custom backend server that can store your client secret - and redirect URI. It conforms to the ["Token Swap and Refresh"][2] standard - used in the Spotify iOS SDK. + This server must have the following endpoints: + + * `tokensURL`: Accepts a post request with the authorization code in the body + in x-www-form-urlencoded format and must return the authorization + information. See + `self.requestAccessAndRefreshTokens(code:redirectURIWithQuery:)` for more + information. + + * `tokenRefreshURL`: Accepts a post request with the refresh token in the body + in x-www-form-urlencoded format and must return the authorization + information. See `self.refreshTokens(refreshToken:)` for more information. + + In contrast with `AuthorizationCodeFlowClientBackend`, which can be used if you + are communicating directly with Spotify, this type does not send the + `clientId`, or `clientSecret` in network requests because these values should + be securely stored on your backend server. + + This conforms to the ["Token Swap and Refresh"][2] standard used in the Spotify + iOS SDK. [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow [2]: https://developer.spotify.com/documentation/ios/guides/token-swap-and-refresh/ @@ -46,8 +60,13 @@ public struct AuthorizationCodeFlowProxyBackend: AuthorizationCodeFlowBackend { authorization code in the body in x-www-form-urlencoded format and which must return the authorization information. + The [/authorization-code-flow/retrieve-tokens][1] endpoint of + SpotifyAPIServer can be used for this URL. + See `self.requestAccessAndRefreshTokens(code:redirectURIWithQuery:)` for more information. + + [1]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flowretrieve-tokens */ public let tokensURL: URL @@ -56,7 +75,12 @@ public struct AuthorizationCodeFlowProxyBackend: AuthorizationCodeFlowBackend { refresh token in the body in x-www-form-urlencoded format and which must return the authorization information. + The [/authorization-code-flow/refresh-tokens][1] endpoint of + SpotifyAPIServer can be used for this URL. + See `self.refreshTokens(refreshToken:)` for more information. + + [1]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flowrefresh-tokens */ public let tokenRefreshURL: URL @@ -79,7 +103,7 @@ public struct AuthorizationCodeFlowProxyBackend: AuthorizationCodeFlowBackend { # Thread Safety - No guarantees are made about which thread this function will be called on. + No guarantees are made about which thread this function will be called on. Do not mutate this property while a request is being made for the authorization information. */ @@ -98,12 +122,16 @@ public struct AuthorizationCodeFlowProxyBackend: AuthorizationCodeFlowBackend { application][3]. - tokensURL: The URL to a server that accepts a post request with the authorization code in the body in "x-www-form-urlencoded" format - and which must return the authorization information. See - `self.requestAccessAndRefreshTokens(code:redirectURIWithQuery:)` for more - information. + and which must return the authorization information. The + [/authorization-code-flow/retrieve-tokens][4] endpoint of + SpotifyAPIServer can be used for this URL. See + `self.requestAccessAndRefreshTokens(code:redirectURIWithQuery:)` + for more information. - tokenRefreshURL: The URL to a server that accepts a post request with the refresh token in the body in "x-www-form-urlencoded" format and - which must return the new authorization information. See + which must return the new authorization information. The + [/authorization-code-flow/refresh-tokens][5] endpoint of + SpotifyAPIServer can be used for this URL. See `self.refreshTokens(refreshToken:)` for more information. - decodeServerError: A hook for decoding an error produced by your backend server into an error type, which will then be thrown to @@ -114,6 +142,8 @@ public struct AuthorizationCodeFlowProxyBackend: AuthorizationCodeFlowBackend { [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow [2]: https://developer.spotify.com/documentation/ios/guides/token-swap-and-refresh/ [3]: https://developer.spotify.com/documentation/general/guides/app-settings/#register-your-app + [4]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flowretrieve-tokens + [5]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-authorization-code-flowrefresh-tokens */ public init( clientId: String, @@ -130,7 +160,7 @@ public struct AuthorizationCodeFlowProxyBackend: AuthorizationCodeFlowBackend { /** Exchanges an authorization code for the access and refresh tokens. - After validating the `redirectURIWithQuery`, + After validating the `redirectURIWithQuery`, `AuthorizationCodeFlowBackendManager.requestAccessAndRefreshTokens(redirectURIWithQuery:state:)`, calls this method in order to retrieve the authorization information. diff --git a/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowClientBackend.swift b/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowClientBackend.swift index a13974875..6a7a93b47 100644 --- a/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowClientBackend.swift +++ b/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowClientBackend.swift @@ -14,9 +14,12 @@ import OpenCombineFoundation /** Communicates *directly* with the Spotify web API in order to retrieve the - authorization information using the [Client Credentials Flow][1]. + authorization information using the [Client Credentials Flow][1]. - Compare with `ClientCredentialsFlowProxyBackend`. + If you are communicating with a custom backend server, then use + `ClientCredentialsFlowProxyBackend` instead, which does not send the `clientId` + and `clientSecret` in network requests because these values should be securely + stored on your backend server. Usually you should not need to create instances of this type directly. `ClientCredentialsFlowManager` uses this type internally by inheriting from @@ -64,7 +67,7 @@ public struct ClientCredentialsFlowClientBackend: ClientCredentialsFlowBackend { - clientSecret: The client secret that you received when you [registered your application][2]. - [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce + [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow [2]: https://developer.spotify.com/documentation/general/guides/app-settings/#register-your-app */ public init(clientId: String, clientSecret: String) { diff --git a/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowProxyBackend.swift b/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowProxyBackend.swift index 31966ab53..358a51aad 100644 --- a/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowProxyBackend.swift +++ b/Sources/SpotifyWebAPI/Authorization/Backends/ClientCredentialsFlowProxyBackend.swift @@ -14,13 +14,17 @@ import OpenCombineFoundation /** Communicates with a backend server that you setup in order to retrieve the - authorization information using the [Client Credentials Flow][1]. - - Compare with `ClientCredentialsFlowClientBackend`. - - This type requires a custom backend server that can store your client id and - client secret. + authorization information using the [Client Credentials Flow][1]. + The server must have an endpoint (`tokensURL`) that accepts a post request + for the authorization information from Spotify. See + `self.makeClientCredentialsTokensRequest()` for more information. + + In contrast with `ClientCredentialsFlowClientBackend`, which can be used if you + are communicating directly with Spotify, this type does not send the + `clientId`, or `clientSecret` in network requests because these values should + be securely stored on your backend server. + [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow */ public struct ClientCredentialsFlowProxyBackend: ClientCredentialsFlowBackend { @@ -36,7 +40,12 @@ public struct ClientCredentialsFlowProxyBackend: ClientCredentialsFlowBackend { "grant_type" with the value set to "client_credentials" in x-www-form-urlencoded format. + The [/client-credentials-tokens][1] endpoint of SpotifyAPIServer can be + used for this URL. + See `self.makeClientCredentialsTokensRequest()` for more information. + + [1]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-client-credentials-tokens */ public let tokensURL: URL @@ -58,7 +67,7 @@ public struct ClientCredentialsFlowProxyBackend: ClientCredentialsFlowBackend { # Thread Safety - No guarantees are made about which thread this function will be called on. + No guarantees are made about which thread this function will be called on. Do not mutate this property while a request is being made for the authorization information. */ @@ -72,8 +81,10 @@ public struct ClientCredentialsFlowProxyBackend: ClientCredentialsFlowBackend { - tokensURL: The URL to your custom backend server that accepts a post request for the authorization information. The body will contain a key called "grant_type" with the value set to "client_credentials" - in x-www-form-urlencoded format. See - `self.makeClientCredentialsTokensRequest()` for more information. + in x-www-form-urlencoded format. The + [/client-credentials-tokens][2] endpoint of SpotifyAPIServer can be + used for this URL. See `self.makeClientCredentialsTokensRequest()` + for more information. - decodeServerError: A hook for decoding an error produced by your backend server into an error type, which will then be thrown to downstream subscribers. Do not use this function to decode the @@ -81,6 +92,7 @@ public struct ClientCredentialsFlowProxyBackend: ClientCredentialsFlowBackend { `SpotifyAuthenticationError`. This will be done elsewhere. [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow + [2]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-client-credentials-tokens */ public init( tokensURL: URL, @@ -125,7 +137,11 @@ public struct ClientCredentialsFlowProxyBackend: ClientCredentialsFlowBackend { Read about the underlying request that must be made to Spotify in order to retrieve this data [here][1]. + The [/client-credentials-tokens][2] endpoint of SpotifyAPIServer can be + used for this URL. + [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#:~:text=the%20request%20is%20sent%20to%20the%20%2Fapi%2Ftoken%20endpoint%20of%20the%20accounts%20service%3A + [2]: https://github.com/Peter-Schorn/SpotifyAPIServer#post-client-credentials-tokens */ public func makeClientCredentialsTokensRequest( ) -> AnyPublisher<(data: Data, response: HTTPURLResponse), Error> { diff --git a/Sources/SpotifyWebAPI/Authorization/ClientCredentialsFlowManager.swift b/Sources/SpotifyWebAPI/Authorization/ClientCredentialsFlowManager.swift index 8ccf5c592..4bc9f3fc2 100644 --- a/Sources/SpotifyWebAPI/Authorization/ClientCredentialsFlowManager.swift +++ b/Sources/SpotifyWebAPI/Authorization/ClientCredentialsFlowManager.swift @@ -196,7 +196,7 @@ public class ClientCredentialsFlowBackendManager Bool { + func accessTokenIsExpiredNOTThreadSafe(tolerance: Double = 120) -> Bool { if (self._accessToken == nil) != (self._expirationDate == nil) { let expirationDateString = self._expirationDate? .description(with: .current) ?? "nil" @@ -717,7 +717,7 @@ extension ClientCredentialsFlowBackendManager { `ClientCredentialsFlowBackendManager`, which allows you to setup a custom backend server that can store these sensitive credentials and which communicates with Spotify on your behalf in order to - retrieve the authorization information. + retrieve the authorization information. # Authorization diff --git a/Sources/SpotifyWebAPI/Authorization/SpotifyAuthorizationManager.swift b/Sources/SpotifyWebAPI/Authorization/SpotifyAuthorizationManager.swift index e24d51fe6..03abbdce9 100644 --- a/Sources/SpotifyWebAPI/Authorization/SpotifyAuthorizationManager.swift +++ b/Sources/SpotifyWebAPI/Authorization/SpotifyAuthorizationManager.swift @@ -89,7 +89,7 @@ public protocol SpotifyAuthorizationManager: Codable { Returns `true` if `accessToken` is not `nil` and the application is authorized for the specified scopes, else `false`. - - Parameter scopes: A set of [Spotify Authorization Scopes][1]. + - Parameter scopes: A set of [Spotify Authorization Scopes][1]. [1]: https://developer.spotify.com/documentation/general/guides/scopes/ */ diff --git a/Sources/SpotifyWebAPI/Authorization/SpotifyScopeAuthorizationManager.swift b/Sources/SpotifyWebAPI/Authorization/SpotifyScopeAuthorizationManager.swift index 27db47bfc..90ea42984 100644 --- a/Sources/SpotifyWebAPI/Authorization/SpotifyScopeAuthorizationManager.swift +++ b/Sources/SpotifyWebAPI/Authorization/SpotifyScopeAuthorizationManager.swift @@ -9,6 +9,5 @@ import Foundation `ClientCredentialsFlowBackendManager` is not a conforming type because it does not support authorization scopes. - [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce */ public protocol SpotifyScopeAuthorizationManager: SpotifyAuthorizationManager { } diff --git a/Sources/SpotifyWebAPI/Errors/SpotifyGeneralError.swift b/Sources/SpotifyWebAPI/Errors/SpotifyGeneralError.swift index f83227386..ebea1fd17 100644 --- a/Sources/SpotifyWebAPI/Errors/SpotifyGeneralError.swift +++ b/Sources/SpotifyWebAPI/Errors/SpotifyGeneralError.swift @@ -103,16 +103,10 @@ public enum SpotifyGeneralError { `SpotifyError`, `SpotifyPlayerError`). Contains the http response metadata and data from the server. - */ case httpError(HTTPURLResponse, Data) - /** - Some other error. - - The first string will be used for `description`. `localizedDescription` - will be used for `errorDescription`. - */ + /// Some other error. case other( String, localizedDescription: String = "An unexpected error occurred." @@ -127,13 +121,12 @@ extension SpotifyGeneralError: LocalizedError { switch self { case .unauthorized(_): return """ - Authorization has not been granted for this \ - request. + Authorization has not been granted for this request. """ case .invalidState(_, _): return """ - The authorization request has expired or is \ - otherwise invalid. + The authorization request has expired or is otherwise \ + invalid. """ case .identifierParsingError(_): return "An internal error occurred." @@ -168,11 +161,13 @@ extension SpotifyGeneralError: CustomStringConvertible { """ case .invalidState(let supplied, let received): return """ - \(Self.self).invalidState(supplied: "\(supplied as Any)", \ - received: "\(received as Any)") + \(Self.self).invalidState(supplied: \(supplied as Any), \ + received: \(received as Any)) """ case .identifierParsingError(let message): - return "SpotifyGeneralError.identifierParsingError: \(message)" + return """ + \(Self.self).identifierParsingError:("\(message)") + """ case .insufficientScope(let required, let authorized): return """ \(Self.self).insufficientScope(required: \ @@ -186,9 +181,18 @@ extension SpotifyGeneralError: CustomStringConvertible { \(received.map(\.rawValue))) """ case .topLevelKeyNotFound(let key, let dict): + // Prevent the description of `dict` from containing + // `AnyHashable("value")` for each key. Instead, it will just be + // `"value"`. + let topLevelStringDict: [String: Any] = dict.reduce( + into: [:] + ) { dict, item in + let keyString = String(describing: item.key) + dict[keyString] = item.value + } return """ \(Self.self).topLevelKeyNotFound(key: "\(key)", \ - dict: \(dict)) + dict: \(topLevelStringDict)) """ case .httpError(let response, let data): let dataString = String(data: data, encoding: .utf8) @@ -197,9 +201,10 @@ extension SpotifyGeneralError: CustomStringConvertible { \(Self.self).httpError(HTTPURLResponse: \(response), \ Data: \(dataString)) """ - case .other(let message, _): + case .other(let message, let localizedDescription): return """ - \(Self.self).other("\(message)") + \(Self.self).other("\(message)", localizedDescription: \ + "\(localizedDescription)") """ } } diff --git a/Sources/SpotifyWebAPI/Object Model/Audio Analysis Objects/AudioFeatures.swift b/Sources/SpotifyWebAPI/Object Model/Audio Analysis Objects/AudioFeatures.swift index ad8c88c28..3c111ece9 100644 --- a/Sources/SpotifyWebAPI/Object Model/Audio Analysis Objects/AudioFeatures.swift +++ b/Sources/SpotifyWebAPI/Object Model/Audio Analysis Objects/AudioFeatures.swift @@ -225,7 +225,7 @@ public struct AudioFeatures: Codable, Hashable { sound more negative (e.g. sad, depressed, angry). - tempo: The overall estimated tempo of a track in beats per minute (BPM). In musical terminology, tempo is the speed or pace of a - given piece and derives directly from the average beat duration. + given piece and derives directly from the average beat duration. - uri: The Spotify URI for the track. - id: The Spotify ID for the track. - trackHref: A link to the Web API endpoint providing full details of the diff --git a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/PKCETokensRequest.swift b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/PKCETokensRequest.swift index 652726a86..e8bbeb252 100644 --- a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/PKCETokensRequest.swift +++ b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/PKCETokensRequest.swift @@ -34,8 +34,14 @@ public struct PKCETokensRequest: Hashable { /// URL. public let codeVerifier: String - /// The redirect URI. This is sent in the request for validation only. There - /// will be no further redirection to this location. + /** + The redirect URI. This is sent in the request for validation only. There + will be no further redirection to this location. + + This must be the same URI provided when creating the authorization URL that + was used to request the authorization code (as opposed to any of your + whitelisted redirect URIs). + */ public let redirectURI: URL /** @@ -68,7 +74,9 @@ public struct PKCETokensRequest: Hashable { redirect URI. - redirectURI: The redirect URI. This is sent in the request for validation only. There will be no further redirection to this - location. + location. This must be the same URI provided when creating the + authorization URL that was used to request the authorization code + (as opposed to any of your whitelisted redirect URIs). - clientId: The client id that you received when you [registered your application][2]. - codeVerifier: The code verifier that you generated when creating the diff --git a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyPKCETokensRequest.swift b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyPKCETokensRequest.swift index e05bbfd28..3b8c4e67a 100644 --- a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyPKCETokensRequest.swift +++ b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyPKCETokensRequest.swift @@ -34,12 +34,18 @@ public struct ProxyPKCETokensRequest: Hashable { /// URL. public let codeVerifier: String - /// The redirect URI. This is sent in the request for validation only. There - /// will be no further redirection to this location. + /** + The redirect URI. This is sent in the request for validation only. There + will be no further redirection to this location. + + This must be the same URI provided when creating the authorization URL that + was used to request the authorization code (as opposed to any of your + whitelisted redirect URIs). + */ public let redirectURI: URL /** - Creates an instance that is used to retrieve the authorization information + Creates an instance that is used to retrieve the authorization information using the [Authorization Code Flow with Proof Key for Code Exchange][1]. When creating a type that conforms to `AuthorizationCodeFlowPKCEBackend` @@ -64,7 +70,9 @@ public struct ProxyPKCETokensRequest: Hashable { authorization URL. - redirectURI: The redirect URI. This is sent in the request for validation only. There will be no further redirection to this - location. + location. This must be the same URI provided when creating the + authorization URL that was used to request the authorization code + (as opposed to any of your whitelisted redirect URIs). [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce */ diff --git a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyTokensRequest.swift b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyTokensRequest.swift index 729d93cde..9c743afb0 100644 --- a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyTokensRequest.swift +++ b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/ProxyTokensRequest.swift @@ -30,8 +30,14 @@ public struct ProxyTokensRequest: Hashable { /// URI. public let code: String - /// The redirect URI. This is sent in the request for validation only. There - /// will be no further redirection to this location. + /** + The redirect URI. This is sent in the request for validation only. There + will be no further redirection to this location. + + This must be the same URI provided when creating the authorization URL that + was used to request the authorization code (as opposed to any of your + whitelisted redirect URIs). + */ public let redirectURI: URL /** @@ -57,7 +63,9 @@ public struct ProxyTokensRequest: Hashable { redirect URI. - redirectURI: The redirect URI. This is sent in the request for validation only. There will be no further redirection to this - location. + location. This must be the same URI provided when creating the + authorization URL that was used to request the authorization code + (as opposed to any of your whitelisted redirect URIs). [1]: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow */ diff --git a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/TokensRequest.swift b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/TokensRequest.swift index 596bbf482..e2c6a578b 100644 --- a/Sources/SpotifyWebAPI/Object Model/Authorization Objects/TokensRequest.swift +++ b/Sources/SpotifyWebAPI/Object Model/Authorization Objects/TokensRequest.swift @@ -29,8 +29,14 @@ public struct TokensRequest: Hashable { /// URI. public let code: String - /// The redirect URI. This is sent in the request for validation only. There - /// will be no further redirection to this location. + /** + The redirect URI. This is sent in the request for validation only. There + will be no further redirection to this location. + + This must be the same URI provided when creating the authorization URL that + was used to request the authorization code (as opposed to any of your + whitelisted redirect URIs). + */ public let redirectURI: URL /** @@ -70,7 +76,9 @@ public struct TokensRequest: Hashable { redirect URI. - redirectURI: The redirect URI. This is sent in the request for validation only. There will be no further redirection to this - location. + location. This must be the same URI provided when creating the + authorization URL that was used to request the authorization code + (as opposed to any of your whitelisted redirect URIs). - clientId: The client id that you received when you [registered your application][2]. - clientSecret: The client secret that you received when you [registered diff --git a/Sources/SpotifyWebAPI/URLExtensions/URLComponentsExtensions.swift b/Sources/SpotifyWebAPI/URLExtensions/URLComponentsExtensions.swift index f7ad490ff..5a6207145 100644 --- a/Sources/SpotifyWebAPI/URLExtensions/URLComponentsExtensions.swift +++ b/Sources/SpotifyWebAPI/URLExtensions/URLComponentsExtensions.swift @@ -107,13 +107,8 @@ public extension URLComponents { /// Removes the trailing slash in the path component, if it exists. mutating func removeTrailingSlashInPath() { if self.path.hasSuffix("/") { - let lastCharacterIndex = self.path.index( - before: self.path.endIndex - ) - self.path.replaceSubrange( - lastCharacterIndex...lastCharacterIndex, - with: "" - ) + let index = self.path.index(before: self.path.endIndex) + self.path.remove(at: index) } } diff --git a/docs/Audio Analysis Objects.html b/docs/Audio Analysis Objects.html index 958826b29..117584a1c 100644 --- a/docs/Audio Analysis Objects.html +++ b/docs/Audio Analysis Objects.html @@ -81,18 +81,27 @@