How to renew spotify session? - ios

I have an application that allow users to stream songs from spotify. So to achieve that I need to renew session from time to time whenever users want to stream song from spotify. I'm using latest spotify sdk (beta-9), and I'm currently following tutorial from https://www.youtube.com/watch?v=GeO00YdJ3cE. In that tutorial we need to refresh token swap but when I looked from https://developer.spotify.com/technologies/spotify-ios-sdk/tutorial/ there is no need to refresh token swap.
and I end up not using the token swap, when I refresh my session then play song with renewed session, I got below error:
Error Domain=com.spotify.ios-sdk.playback Code=8 "Login to Spotify failed because of invalid credentials." UserInfo=0x7f840bf807b0 {NSLocalizedDescription=Login to Spotify failed because of invalid credentials.}
And I'm using this code below, for renewing my session:
let userDefaults = NSUserDefaults.standardUserDefaults()
if let sessionObj : AnyObject = NSUserDefaults.standardUserDefaults().objectForKey("spotifySession") {
let sessionDataObj : NSData = sessionObj as! NSData
let session = NSKeyedUnarchiver.unarchiveObjectWithData(sessionDataObj) as! SPTSession
self.playUsingSession(session)
if !session.isValid() {
SPTAuth.defaultInstance().renewSession(session, callback: { (error : NSError!, newsession : SPTSession!) -> Void in
if error == nil {
let sessionData = NSKeyedArchiver.archivedDataWithRootObject(session)
userDefaults.setObject(sessionData, forKey: "spotifySession")
userDefaults.synchronize()
self.session = newsession
self.playUsingSession(newsession)
}else{
println("renew session having problerm >>>>> \(error)")
}
})
}else{
println("session is still valid")
self.playUsingSession(session)
}
}else{
spotifyLoginButton.hidden = false
}
and below code to stream spotify songs:
func playUsingSession(sessionObj:SPTSession!){
if spotifyPlayer == nil {
spotifyPlayer = SPTAudioStreamingController(clientId: kSpotifyClientID)
}
spotifyPlayer?.loginWithSession(sessionObj, callback: { (error : NSError!) -> Void in
if error != nil {
println("enabling playback got error : \(error)")
return
}
var spotifyTrackUri : NSURL = NSURL(string: "spotify:track:3FREWTEY2uFxOorJZMmZPX")!
self.spotifyPlayer!.playURIs([spotifyTrackUri], fromIndex: 0, callback: { (error : NSError!) -> Void in
if error != nil {
println("\(error)")
}
})
})
}
Do I still need to refresh token swap for latest sdk? Or is there something missing with my code?

By default, users need to login once per hour for apps using the Spotify SDK unless you use the Authorization Code flow. To use this flow you'll need to setup a server to handle token swap and refresh.
Setup a free server with this one-click-deploy to Heroku https://github.com/adamontherun/SpotifyTokenRefresh
Using the URL of the server created above add the following when configuring your SPTAuth.defaultInstance():
SPTAuth.defaultInstance().tokenSwapURL = URL(string: "https://YOURSERVERNAME.herokuapp.com/swap")
SPTAuth.defaultInstance().tokenRefreshURL = URL(string: "https://YOURSERVERNAME.herokuapp.com/refresh")
Before using your session check if it is valid:
if SPTAuth.defaultInstance().session.isValid()
and if it isn't call
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session, callback: { (error, session) in
if let session = session {
SPTAuth.defaultInstance().session = session
}
})

I recommend follow this tutorial: https://medium.com/#brianhans/getting-started-with-the-spotify-ios-sdk-435607216ecc and this one too: https://medium.com/#brianhans/spotify-ios-sdk-authentication-b2c35cd4affb
After you finished, you'll see that you create a file called "Constants.swift" just like this:
import Foundation
struct Constants {
static let clientID = "XXXXXXXXXXXXXXXXXXXX"
static let redirectURI = URL(string: "yourappname://")!
static let sessionKey = "spotifySessionKey"
}
Then, you can follow the steps in Heroku (Do not enter in panic is very simple):
https://github.com/adamontherun/SpotifyTokenRefresh
almost ready, when your server is "working", come back to your Xcode Project and add two static constants in your "Constants.swift" file, just like this:
import Foundation
struct Constants {
static let clientID = "XXXXXXXXXXXXXXXXXXXX"
static let redirectURI = URL(string: "yourappname://")!
static let sessionKey = "spotifySessionKey"
static let tokenSawp = URL(string: "https://yourappname.herokuapp.com/swap")
static let tokenRefresh = URL(string:"https://yourappname.herokuapp.com/refresh")
}
To finish, go to AppDelegate.swift and search "func setupSpotify()".. Add the new two constants, your function should look like this:
func setupSpotify() {
SPTAuth.defaultInstance().clientID = Constants.clientID
SPTAuth.defaultInstance().redirectURL = Constants.redirectURI
SPTAuth.defaultInstance().sessionUserDefaultsKey = Constants.sessionKey
SPTAuth.defaultInstance().tokenSwapURL = Constants.tokenSawp //new constant added
SPTAuth.defaultInstance().tokenRefreshURL = Constants.tokenRefresh //new constant added
SPTAuth.defaultInstance().requestedScopes = [SPTAuthStreamingScope]
do {
try SPTAudioStreamingController.sharedInstance().start(withClientId: Constants.clientID)
} catch {
fatalError("Couldn't start Spotify SDK")
}
}
As a last step, just add the SPTAuth.defaultInstance().renewSession in you signInSpotify function, should look like this:
#IBAction func SignInSpotify(_ sender: Any) {
if SPTAuth.defaultInstance().session == nil {
let appURL = SPTAuth.defaultInstance().spotifyAppAuthenticationURL()
let webURL = SPTAuth.defaultInstance().spotifyWebAuthenticationURL()!
// Before presenting the view controllers we are going to start watching for the notification
NotificationCenter.default.addObserver(self,
selector: #selector(receievedUrlFromSpotify(_:)),
name: NSNotification.Name.Spotify.authURLOpened,
object: nil)
if SPTAuth.supportsApplicationAuthentication() {
UIApplication.shared.open(appURL!, options: [:], completionHandler: nil)
} else {
let webVC = SFSafariViewController(url: webURL)
present(webVC, animated: true, completion: nil)
}
} else if SPTAuth.defaultInstance().session.isValid() == true {
print("YOUR SESSION IS VALID")
self.successfulLogin()
} else {
print("YOUR SESSION IS NOT VALID / NEED RENEW")
//Every 60 minutes the token need a renew https://github.com/spotify/ios-sdk
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session, callback: { (error, session) in
if let session = session {
SPTAuth.defaultInstance().session = session
self.successfulLogin()
print("RENEW OK")
}
if let error = error {
print("RENEW NOT OK \(error)")
}
})
}
}
Good Luck!

Related

AWS Transfer Utility uploads are slow and inconsistent in iOS

I have implemented the basic AWS transfer utility upload video (file) code in my app and this had been working for me flawlessly until recently the uploads got extremely slow and even stuck.
I tried changing many things in the AWS code like shifting from TransferUtilityUpload to TrasferUtility UploadUsing MultiPart, changing the AWSServiceConfiguration from AWSCognitoCredentialsProvider(using poolId & region) to AWSStaticCredentialsProvider (using Accesskey, secret key and region), enabling acceleration etc but nothing has helped to increase the upload speed. Apart from this, the uploads are very inconsistent. For example sometimes a 30sec video (size 180MB) gets uploaded in under 2 mins and then again same video takes more than 5 minutes/gets stuck in the same network (speed 150MBps or more)
Can someone please help me understand the issue and fix it?
Code snippets below.
Service Configuration
let credentialsProvider = AWSStaticCredentialsProvider(accessKey: "******", secretKey: "*****")
let configuration = AWSServiceConfiguration.init(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
AWS Upload function
private func uploadfile(fileSize: Int, fileUrl: URL, fileName: String, contenType: String, progress: progressBlock?, completion: completionBlock?) {
// Upload progress block
var previousUploadedBytes: Double = 0.0
let expression = AWSS3TransferUtilityMultiPartUploadExpression()
expression.progressBlock = {(task, awsProgress) in
if task.status == AWSS3TransferUtilityTransferStatusType.waiting {
task.cancel()
}
guard let uploadProgress = progress else { return }
DispatchQueue.main.async {
uploadProgress(awsProgress.fractionCompleted)
//CODE FOR UI UPDATES
//DO SOMETHING WITH THE PROGRESS
}
}
// Completion block
var completionHandler: AWSS3TransferUtilityMultiPartUploadCompletionHandlerBlock?
completionHandler = { (task, error) -> Void in
DispatchQueue.main.async(execute: {
if error == nil {
let url = AWSS3.default().configuration.endpoint.url
let publicURL : URL = (url?.appendingPathComponent(self.bucketName).appendingPathComponent(fileName))!
if let completionBlock = completion {
completionBlock(publicURL.absoluteString, nil)
}
} else {
if let completionBlock = completion {
completionBlock(nil, error)
}
}
})
}
//acceleration mode enabled
let serviceConfiguration = AWSServiceConfiguration(
region: .USEast1,
credentialsProvider: AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider
)
let transferUtilityConfiguration = AWSS3TransferUtilityConfiguration()
transferUtilityConfiguration.isAccelerateModeEnabled = true
AWSS3TransferUtility.register(
with: serviceConfiguration!,
transferUtilityConfiguration: transferUtilityConfiguration,
forKey: "transfer-acceleration"
)
// Start uploading using AWSS3TransferUtility
let awsTransferUtility = AWSS3TransferUtility.default()
awsTransferUtility.uploadUsingMultiPart(fileURL: fileUrl, bucket: bucketName, key: fileName, contentType: contenType, expression: expression, completionHandler: completionHandler).continueWith { (task) -> Any? in
if let error = task.error {
UploadHelper.sharedInstance.showSSLError = false
if (error as NSError).code == -1001 {
DispatchQueue.main.async {
UploadHelper.sharedInstance.noOfRetries = 0
UploadHelper.sharedInstance.changeToRetryUpload() // internal code to call for retry
}
} else if (error as NSError).code == -1009 {
DispatchQueue.main.async {
UploadHelper.sharedInstance.noOfRetries = 0
UploadHelper.sharedInstance.changeToRetryUpload() // internal code to call for retry
}
} else if (error as NSError).code == -1003 {
DispatchQueue.main.async {
UploadHelper.sharedInstance.noOfRetries = 0
UploadHelper.sharedInstance.changeToRetryUpload() // internal code to call for retry
}
} else if (error as NSError).code == -1200 {
DispatchQueue.main.async {
UploadHelper.sharedInstance.noOfRetries = 0
UploadHelper.sharedInstance.changeToRetryUpload() // internal code to call for retry
UploadHelper.sharedInstance.showSSLError = true
}
}
}
if let _ = task.result {
// your uploadTask
}
return nil
}
}

Auto login using UserDefaults() not working Swift 5

I have followed some tutorials and used their methods to implement auto login for my app, but once I relaunch the app after entering the credentials, the app does not log in.
var userDefaults = UserDefaults.standard
here I initiate the user defaults feature
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let safeData = data {
if let dataString = String(data: safeData, encoding: String.Encoding.utf8) {
print(dataString)
if dataString == "Hello" {
self.userDefaults.setValue(true, forKey: "UserIsLoggedIn")
DispatchQueue.main.async {
self.performSegue(withIdentifier: "loginSegue", sender: self)
}
} else {
DispatchQueue.main.async {
self.validationLabel.isHidden = false
self.validationLabel.text = " Username or password is incorrect. "
self.loginSuccessful = false
}
}
}
} else {
print(error ?? "Error with data API URLSession")
}
}.resume()
here, inside the API call. if the response from the API is "hello" which means the login was successful, i set the value to true with an identifier.
if userDefaults.value(forKey: "UserIsLoggedIn") as? Bool == true {
performSegue(withIdentifier: "loginSegue", sender: self)
} else {}
here in the view did load I use the userDefaults to perform the segue to the next screen for future launches.. but it is not working.
initiation
viewdidload
API call
var userDefaults = UserDefaults() seems wrong. Use let userDefaults = UserDefaults.standard instead. Also there's no need to make this a property of your class, simply use this within your methods whereever it's needed.
Swift 5. Auto login myApplication According woking & help you best my try
//First Time key
// According API Response success then add
if !UserDefaults.standard.bool(forKey:"isLogin") { // Success
UserDefaults.standard.set(true, forKey: "isLogin") // To do....
UserDefaults.standard.synchronize()
} else { // response fail
// To do ....
}
Have you checked if the value is being saved in your User Defaults file? If you are running your app on the simulator, try printing the file path to your User Defaults file and try locating it. It might not fix your immediate problem but it will hopefully give you an idea as to where the problem is coming from.
Try printing the location of your User Defaults file using the following line in your AppDelegate.swift
print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last! as String)

SnapSDK integration not working properly, Snapshot shows error: Something went wrong. Please try again later

I followed all the steps from documentation and integrated SnapSDK in my iOS app but when I click the share button in my app it directs me to the snapchat but ends up with an error saying
"Something went wrong please try again later".
private func didTapSnapchatShare(cell: FeedTableViewCell){
print("Share button tapped ")
cell.pauseVideoAndAnimation()
showProgressIndicator(view: self.view)
var watermarkStr = ""
if let userName = cell.cellDataSource?.user?.name {
watermarkStr = userName
}
let promptImage = cell.promptView?.asImage()
cell.slPlayer?.exportVideo(withWatermarkString: watermarkStr, promptImage: promptImage, completionHandler: { status, filePathURL in
DispatchQueue.main.async {
hideProgressIndicator(view: self.view)
if status, let filePathURL = filePathURL {
let url = URL(string: "snapchat://")!
if(!UIApplication.shared.canOpenURL(url)){
if let reviewURL = URL(string: "https://itunes.apple.com/us/app/snapchat/id447188370?mt=8"), UIApplication.shared.canOpenURL(reviewURL) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(reviewURL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(reviewURL)
}
return
}
}
let video = SCSDKSnapVideo(videoUrl:filePathURL)
let videoContent = SCSDKVideoSnapContent.init(snapVideo: video)
let api = SCSDKSnapAPI(content: videoContent)
api.startSnapping(completionHandler: { (error: Error?) in
print(error?.localizedDescription)
})
}
}
})
}
Check if your file path url is correct or not this happens when we are not using the correct file path sdk will not be able to find the image.

How to create framework and use it into Project Appdelegate correctly ? in Swift

1- i try to create framework for my project but i didnt get true way to call my methods in another project inside appdelegate;
2- Using framework in another project App Transport Security warning !
my example framework codes under below;
myFramework.swift
import UIKit
var loginUrl = "http://bla/login.php"
let prefs = UserDefaults.standard
public class myFramework: NSObject {
public override init (){
print("Started.")
}
public func doSomething(){
print("works")
}
public func login(secret : String)
{
print("Login Request")
let post_data: NSDictionary = NSMutableDictionary()
post_data.setValue(secret, forKey: "secret")
let url:URL = URL(string: loginUrl)!
let session = URLSession.shared
let request = NSMutableURLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
var paramString = ""
for (key, value) in post_data
{
paramString = paramString + (key as! String) + "=" + (value as! String) + "&"
}
request.httpBody = paramString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(
data, response, error) in
guard let _:Data = data, let _:URLResponse = response , error == nil else {
return
}
let json: Any?
do
{
json = try JSONSerialization.jsonObject(with: data!, options: [])
}
catch
{
return
}
guard let server_response = json as? NSDictionary else
{
return
}
if let data_block = server_response["login"] as? NSDictionary
{
if let checksuccess = data_block["success"] as? Bool
{
if checksuccess == true {
if let getUserId = data_block["userId"] {
print("Service Connected")
print(getUserId)
prefs.set(secret, forKey: "secret")
prefs.set(getUserId, forKey: "userId")
}
}else{
if let getMessage = data_block["message"] {
print(getMessage)
}
}
DispatchQueue.main.async(execute: self.LoginDone)
}
}
})
task.resume()
}
public func LoginDone()
{
}
}
Calling another project inside Appdelegate file.
import myFramework
myFramework().login(secret : "234234234234")
but i want to use `myframework without ()
must be;
myFramework.login(secret : "234234234234")
1- How can i do it?
(My all framework codes inside myFramework.swift)
2- When my framework using another project says me app App Transport Security warning , how can i fix it in my framework ? Warning message under below.
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file
Thank you !
In regards to your first question, if you are set on scoping your functions inside of a class, make your functions static or class functions like so:
class myFramework: NSObject {
public class func login(secret : String)
{
...
}
}
Now in code, you can call it like this:
import myFramework
myFramework.login("234234234234")
For your second question, see this post:
App Transport Security has blocked a cleartext HTTP resource
You don't need to put your functions inside a public class. Just make them public.
public func login() {
// insert code here
}
public func secondFunction() {
internalFunction()
}
internal func internalFunction() {
}
In your app:
include MyFramework
login()
secondFunction()
Note that you should name your frameworks like you do classes - capitalized.
You don't need the prefix your function calls with MyFramework.
Your app can see login() and secondFunction(), but cannot see internalFunction() as it's declared internal.
EDIT: Just saw your second question. Click on your framework target in the app explorer. Under General, DeploymentInfo, you'll see a checkbox labelled Allow App Extension API Only - check it. (I make this mistake often too!)

How to get Swift to interact with a Webview?

I'm building a basic iOS app with Xcode that mainly just contains a webview with my web app inside.
I was wondering if there was a decent way to save the users username to the devices storage when logging in so that it can be automatically entered when opening the app next time. Since the app is a webview, I don't believe there is a way to keep the user logged in (like other major apps do, such as Facebook), so I think that auto filling the username will be beneficial for them.
I found this question and answer that could possibly solve my problem, although it's in good ol' Objective C.
My current attempt, that does absolutely nothing:
let savedUsername = "testusername"
let loadUsernameJS = "document.getElementById(\"mainLoginUsername\").value = " + savedUsername + ";"
self.Webview.stringByEvaluatingJavaScriptFromString(loadUsernameJS)
Is this a possibility with Swift?
for storing the password you should use the keychain, specifically web credentials. if done right, this will allow your app to use any existing keychain entries entered via Safari and will also allow Safari to access the password if saved via your app.
Code for setting and retrieving provided below:
private let domain = "www.youdomain.com"
func saveWebCredentials(username: String, password: String, completion: Bool -> Void) {
SecAddSharedWebCredential(domain, username, password) { error in
guard error == nil else { print("error saving credentials: \(error)"); return completion(false) }
completion(true)
}
}
func getExistingWebCredentials(completion: ((String, String)?, error: String?) -> Void) {
SecRequestSharedWebCredential(domain, nil) { credentials, error in
// make sure we got the credentials array back
guard let credentials = credentials else { return completion(nil, error: String(CFErrorCopyDescription(error))) }
// make sure there is at least one credential
let count = CFArrayGetCount(credentials)
guard count > 0 else { return completion(nil, error: "no credentials stored") }
// extract the username and password from the credentials dict
let credentialDict = unsafeBitCast(CFArrayGetValueAtIndex(credentials, 0), CFDictionaryRef.self)
let username = CFDictionaryGetValue(credentialDict, unsafeBitCast(kSecAttrAccount, UnsafePointer.self))
let password = CFDictionaryGetValue(credentialDict, unsafeBitCast(kSecSharedPassword, UnsafePointer.self))
// return via completion block
completion((String(unsafeBitCast(username, CFStringRef.self)), String(unsafeBitCast(password, CFStringRef.self))), error: nil)
}
}
which is used like this:
// save the credentials
saveWebCredentials("hello", password: "world", completion: { success in
// retrieve the credentials
getExistingWebCredentials { credentials, error in
guard let credentials = credentials else { print("Error: \(error)"); return }
print("got username: \(credentials.0) password: \(credentials.1)")
}
})
UPDATE
Recommend switching to using a WKWebView so you can easily pull out the response headers. Here is boilerplate code:
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView(frame: self.view.bounds)
webView.navigationDelegate = self
self.view.addSubview(webView)
webView.loadRequest(NSURLRequest(URL: NSURL(string: "https://www.google.com")!))
}
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {
// make sure the response is a NSHTTPURLResponse
guard let response = navigationResponse.response as? NSHTTPURLResponse else { return decisionHandler(.Allow) }
// get the response headers
let headers = response.allHeaderFields
print("got headers: \(headers)")
// allow the request to continue
decisionHandler(.Allow);
}
}
You code is not working because you did not wrap savedUsername with quotes.
You should have this instead:
let loadUsernameJS = "document.getElementById(\"mainLoginUsername\").value = \"\(savedUsername)\";"
Also, this library might help you.
You are not passing a string to JavaScript, you should encapsulate the variable in additional quotes
let loadUsernameJS = "document.getElementById(\"mainLoginUsername\").value = \"" + savedUsername + "\";"
or
let loadUsernameJS = "document.getElementById('mainLoginUsername').value = '" + savedUsername + "';"

Resources