From 35450d7aacea8b1453e39c9beee0e89d3921472e Mon Sep 17 00:00:00 2001 From: Leonardo Cardoso Date: Sun, 23 Sep 2018 19:25:26 +0200 Subject: [PATCH] Add accept header for some websites --- CHANGELOG.md | 8 +- .../Controllers/ViewController.swift | 216 +++++++++--------- Example/SwiftLinkPreviewExample/Info.plist | 2 +- .../Storyboards/Base.lproj/Main.storyboard | 6 +- README.md | 8 +- Sources/Info-macOS.plist | 2 +- Sources/Info-tvOS.plist | 2 +- Sources/Info-watchOS.plist | 2 +- Sources/Info.plist | 2 +- Sources/SwiftLinkPreview.swift | 133 ++++++----- SwiftLinkPreview.podspec | 2 +- SwiftLinkPreviewTests/Info-macOS.plist | 2 +- SwiftLinkPreviewTests/Info-tvOS.plist | 2 +- SwiftLinkPreviewTests/Info.plist | 2 +- 14 files changed, 205 insertions(+), 184 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d96a23..7a2897c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log #### 2.x Releases -- `2.3.x` Releases - [2.3.0](#230) +- `2.3.x` Releases - [2.3.0](#230) | [2.3.1](#231) - `2.2.x` Releases - [2.2.0](#220) - `2.1.x` Releases - [2.1.0](#210) - `2.0.x` Releases - [2.0.0](#200) | [2.0.1](#201) | [2.0.2](#202) | [2.0.3](#203) | [2.0.4](#204) | [2.0.5](#205) | [2.0.6](#206) | [2.0.7](#207) @@ -15,11 +15,15 @@ --- -## 2.4.0 +## 2.3.1(https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/2.3.1) +Released on 2018-09-23. #### Changed - Fixed missing Accept header + - Changed by [Maarten Billemont](https://github.com/lhunath) +- Example with Cocoapods + - Changed by [Leonardo Cardoso](https://github.com/LeonardoCardoso). ## [2.3.0](https://github.com/LeonardoCardoso/Swift-Link-Preview/releases/tag/2.3.0) Released on 2018-06-07. diff --git a/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift b/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift index 965ff44..97045a3 100644 --- a/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift +++ b/Example/SwiftLinkPreviewExample/Controllers/ViewController.swift @@ -12,7 +12,7 @@ import ImageSlideshow import SwiftLinkPreview class ViewController: UIViewController { - + // MARK: - Properties @IBOutlet private weak var centerLoadingActivityIndicatorView: UIActivityIndicatorView? @IBOutlet private weak var textField: UITextField? @@ -28,9 +28,11 @@ class ViewController: UIViewController { @IBOutlet private weak var previewDescription: UILabel? @IBOutlet private weak var detailedView: UIView? @IBOutlet private weak var favicon: UIImageView? - + // MARK: - Vars private var randomTexts: [String] = [ + "blinkist.com", + "uber.com", "tw.yahoo.com", "https://www.linkedin.com/", "www.youtube.com", @@ -56,38 +58,38 @@ class ViewController: UIViewController { "Also Vimeo https://vimeo.com/67992157", "Even with image itself https://lh6.googleusercontent.com/-aDALitrkRFw/UfQEmWPMQnI/AAAAAAAFOlQ/mDh1l4ej15k/w337-h697-no/db1969caa4ecb88ef727dbad05d5b5b3.jpg", - "Well, it's a gif! https://goo.gl/jKCPgp", + "Well, it's a gif! https://goo.gl/jKCPgp" ] private var result = SwiftLinkPreview.Response() private let placeholderImages = [ImageSource(image: UIImage(named: "Placeholder")!)] - + private let slp = SwiftLinkPreview(cache: InMemoryCache()) - + // MARK: - Life cycle override func viewDidLoad() { - + super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. - + self.showHideAll(hide: true) self.setUpSlideshow() - + } - + override func didReceiveMemoryWarning() { - + super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. - + } - + private func getRandomText() -> String { - + return randomTexts[Int(arc4random_uniform(UInt32(randomTexts.count)))] - + } - + private func startCrawling() { self.centerLoadingActivityIndicatorView?.startAnimating() @@ -95,15 +97,15 @@ class ViewController: UIViewController { self.showHideAll(hide: true) self.textField?.resignFirstResponder() self.indicator?.isHidden = false - + } - + private func endCrawling() { - + self.updateUI(enabled: true) - + } - + // Update UI private func showHideAll(hide: Bool) { @@ -111,217 +113,213 @@ class ViewController: UIViewController { self.detailedView?.isHidden = hide self.openWithButton?.isHidden = hide self.previewAreaLabel?.isHidden = !hide - + } - + private func updateUI(enabled: Bool) { self.indicator?.isHidden = enabled self.textField?.isEnabled = enabled self.randomTextButton?.isEnabled = enabled self.submitButton?.isEnabled = enabled - + } - + private func setData() { - + if let value: [String] = self.result[.images] as? [String] { - + if !value.isEmpty { - + var images: [InputSource] = [] for image in value { - + if let source = AlamofireSource(urlString: image) { - + images.append(source) - + } - + } - + self.setImage(images: images) - - + } else { - + self.setImage(image: self.result[.image] as? String) - + } - + } else { - + self.setImage(image: self.result[.image] as? String) - + } - + if let value: String = self.result[.title] as? String { - + self.previewTitle?.text = value.isEmpty ? "No title" : value - + } else { - + self.previewTitle?.text = "No title" - + } - + if let value: String = self.result[.canonicalUrl] as? String { - + self.previewCanonicalUrl?.text = value - + } - + if let value: String = self.result[.description] as? String { - + self.previewDescription?.text = value.isEmpty ? "No description" : value - + } else { - + self.previewTitle?.text = "No description" - + } - + if let value: String = self.result[.icon] as? String, let url = URL(string: value) { self.favicon?.af_setImage(withURL: url) } - + self.showHideAll(hide: false) self.endCrawling() - + } - + private func setImage(image: String?) { - + if let image: String = image { - + if !image.isEmpty { - + if let source = AlamofireSource(urlString: image) { - + self.setImage(images: [source]) - + } else { - + self.slideshow?.setImageInputs(placeholderImages) - + } - - + } else { - + self.slideshow?.setImageInputs(placeholderImages) - + } - + } else { - + self.slideshow?.setImageInputs(placeholderImages) - + } - + self.centerLoadingActivityIndicatorView?.stopAnimating() - + } - + private func setImage(images: [InputSource]?) { - + if let images = images { - + self.slideshow?.setImageInputs(images) - + } else { - + self.slideshow?.setImageInputs(placeholderImages) - + } self.centerLoadingActivityIndicatorView?.stopAnimating() - + } - + private func setUpSlideshow() { - + self.slideshow?.backgroundColor = UIColor.white self.slideshow?.slideshowInterval = 7.0 self.slideshow?.pageControlPosition = PageControlPosition.hidden self.slideshow?.contentScaleMode = .scaleAspectFill - + } - + // MARK: - Actions @IBAction func randomTextAction(_ sender: AnyObject) { - + textField?.text = getRandomText() - + } - + @IBAction func submitAction(_ sender: AnyObject) { - + guard textField?.text?.isEmpty == false else { - + Drop.down("Please, enter a text", state: .warning) return - + } - + self.startCrawling() let textFieldText = textField?.text ?? String() - + if let url = self.slp.extractURL(text: textFieldText), let cached = self.slp.cache.slp_getCachedResponse(url: url.absoluteString) { - + self.result = cached self.setData() result.forEach { print("\($0):", $1) } - + } else { self.slp.preview( textFieldText, onSuccess: { result in - + result.forEach { print("\($0):", $1) } self.result = result self.setData() - + }, onError: { error in - + print(error) self.endCrawling() - + Drop.down(error.description, state: .error) - + } ) } - + } - + @IBAction func openWithAction(_ sender: UIButton) { - + if let url = self.result[.finalUrl] as? URL { UIApplication.shared.open(url, options: [:], completionHandler: nil) } - + } - - + } // MARK: - UITextFieldDelegate extension ViewController: UITextFieldDelegate { - + func textFieldShouldReturn(_ textField: UITextField) -> Bool { - + self.submitAction(textField) self.textField?.resignFirstResponder() return true - + } - -} +} diff --git a/Example/SwiftLinkPreviewExample/Info.plist b/Example/SwiftLinkPreviewExample/Info.plist index 62fa77e..8e8d51a 100644 --- a/Example/SwiftLinkPreviewExample/Info.plist +++ b/Example/SwiftLinkPreviewExample/Info.plist @@ -19,7 +19,7 @@ CFBundleSignature ???? CFBundleVersion - 2.3.0 + 2.3.1 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/Example/SwiftLinkPreviewExample/Storyboards/Base.lproj/Main.storyboard b/Example/SwiftLinkPreviewExample/Storyboards/Base.lproj/Main.storyboard index 44c1f74..cc82224 100644 --- a/Example/SwiftLinkPreviewExample/Storyboards/Base.lproj/Main.storyboard +++ b/Example/SwiftLinkPreviewExample/Storyboards/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -63,7 +63,7 @@ - + diff --git a/README.md b/README.md index 90ddc52..8ab6da7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ > It makes a preview from an URL, grabbing all the information such as title, relevant texts and images. [![Platform](https://img.shields.io/badge/platform-iOS%20|%20macOS%20|%20watchOS%20|%20tvOS-orange.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#requirements-and-details) -[![CocoaPods](https://img.shields.io/badge/pod-v2.3.0-red.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#cocoapods) +[![CocoaPods](https://img.shields.io/badge/pod-v2.3.1-red.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#cocoapods) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#carthage) [![Swift Package Manager](https://img.shields.io/badge/SPM-compatible-orange.svg)](https://github.com/LeonardoCardoso/SwiftLinkPreview#swift-package-manager) [![Build Status](https://travis-ci.org/LeonardoCardoso/SwiftLinkPreview.svg?branch=master)](https://travis-ci.org/LeonardoCardoso/SwiftLinkPreview) @@ -59,7 +59,7 @@ To use **SwiftLinkPreview** as a pod package just add the following in your **Po target 'Your Target Name' do use_frameworks! // ... - pod 'SwiftLinkPreview', '~> 2.3.0' + pod 'SwiftLinkPreview', '~> 2.3.1' // ... end ``` @@ -70,7 +70,7 @@ To use **SwiftLinkPreview** as a Carthage module package just add the following ```ruby // ... - github "LeonardoCardoso/SwiftLinkPreview" ~> 2.3.0 + github "LeonardoCardoso/SwiftLinkPreview" ~> 2.3.1 // ... ``` @@ -85,7 +85,7 @@ let package = Package( name: "Your Target Name", dependencies: [ // ... - .Package(url: "https://github.com/LeonardoCardoso/SwiftLinkPreview.git", "2.3.0") + .Package(url: "https://github.com/LeonardoCardoso/SwiftLinkPreview.git", "2.3.1") // ... ] ) diff --git a/Sources/Info-macOS.plist b/Sources/Info-macOS.plist index 756aba3..d41bd41 100644 --- a/Sources/Info-macOS.plist +++ b/Sources/Info-macOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.3.0 + 2.3.1 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Info-tvOS.plist b/Sources/Info-tvOS.plist index 344518f..a5d169c 100644 --- a/Sources/Info-tvOS.plist +++ b/Sources/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.3.0 + 2.3.1 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Info-watchOS.plist b/Sources/Info-watchOS.plist index 344518f..a5d169c 100644 --- a/Sources/Info-watchOS.plist +++ b/Sources/Info-watchOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.3.0 + 2.3.1 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Info.plist b/Sources/Info.plist index 344518f..a5d169c 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.3.0 + 2.3.1 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/SwiftLinkPreview.swift b/Sources/SwiftLinkPreview.swift index 0b6a1bb..2b97b0b 100644 --- a/Sources/SwiftLinkPreview.swift +++ b/Sources/SwiftLinkPreview.swift @@ -27,7 +27,7 @@ open class Cancellable: NSObject { } open class SwiftLinkPreview: NSObject { - + public typealias Response = [SwiftLinkResponseKey: Any] // MARK: - Vars @@ -42,7 +42,7 @@ open class SwiftLinkPreview: NSObject { public static let defaultWorkQueue = DispatchQueue.global(qos: .userInitiated) // MARK: - Constructor - + //Swift-only init with default parameters @nonobjc public init(session: URLSession = URLSession.shared, workQueue: DispatchQueue = SwiftLinkPreview.defaultWorkQueue, responseQueue: DispatchQueue = DispatchQueue.main, cache: Cache = DisabledCache.instance) { self.workQueue = workQueue @@ -50,14 +50,14 @@ open class SwiftLinkPreview: NSObject { self.cache = cache self.session = session } - + //Objective-C init with default parameters @objc public override init() { let _session = URLSession.shared let _workQueue: DispatchQueue = SwiftLinkPreview.defaultWorkQueue let _responseQueue: DispatchQueue = DispatchQueue.main let _cache: Cache = DisabledCache.instance - + self.workQueue = _workQueue self.responseQueue = _responseQueue self.cache = _cache @@ -65,8 +65,8 @@ open class SwiftLinkPreview: NSObject { } //Objective-C init with paramaters. nil objects will default. Timeout values are ignored if InMemoryCache is disabled. - @objc public init(session: URLSession?, workQueue: DispatchQueue?, responseQueue: DispatchQueue? , disableInMemoryCache: Bool, cacheInvalidationTimeout: TimeInterval, cacheCleanupInterval: TimeInterval) { - + @objc public init(session: URLSession?, workQueue: DispatchQueue?, responseQueue: DispatchQueue?, disableInMemoryCache: Bool, cacheInvalidationTimeout: TimeInterval, cacheCleanupInterval: TimeInterval) { + let _session = session ?? URLSession.shared let _workQueue = workQueue ?? SwiftLinkPreview.defaultWorkQueue let _responseQueue = responseQueue ?? DispatchQueue.main @@ -77,19 +77,18 @@ open class SwiftLinkPreview: NSObject { self.cache = _cache self.session = _session } - - + // MARK: - Functions // Make preview //Swift-only preview function using Swift specific closure types @nonobjc @discardableResult open func preview(_ text: String, onSuccess: @escaping (Response) -> Void, onError: @escaping (PreviewError) -> Void) -> Cancellable { - + let cancellable = Cancellable() self.session = URLSession(configuration: self.session.configuration, delegate: self, // To handle redirects delegateQueue: self.session.delegateQueue) - + let successResponseQueue = { (response: Response) in if !cancellable.isCancelled { self.responseQueue.async { @@ -179,7 +178,7 @@ open class SwiftLinkPreview: NSObject { return url } - + //Objective-C wrapper for preview method. Core incompataility is use of Swift specific enum types in closures. //Returns a dictionary of rsults rather than enum for success, and an NSError object on error that encodes the local error description on error /* @@ -195,20 +194,19 @@ open class SwiftLinkPreview: NSObject { */ @objc @discardableResult open func previewLink(_ text: String, onSuccess: @escaping (Dictionary) -> Void, onError: @escaping (NSError) -> Void) -> Cancellable { - - func success (_ result: Response) -> Void { + + func success (_ result: Response) { var ResponseData = [String: Any]() for item in result { ResponseData.updateValue(item.value, forKey: item.key.rawValue) } onSuccess(ResponseData) } - - - func failure (_ theError: PreviewError) -> Void { + + func failure (_ theError: PreviewError) { var errorCode: Int errorCode = 1 - + switch theError { case .noURLHasBeenFound: errorCode = 1 @@ -219,12 +217,12 @@ open class SwiftLinkPreview: NSObject { case .parseError: errorCode = 4 } - + onError(NSError(domain: "SwiftLinkPreviewDomain", code: errorCode, userInfo: [NSLocalizedDescriptionKey: theError.description])) } - + return self.preview(text, onSuccess: success, onError: failure) } } @@ -299,10 +297,24 @@ extension SwiftLinkPreview { } // Extract HTML code and the information contained on it - fileprivate func extractInfo(response: Response, cancellable: Cancellable, completion: @escaping (Response) -> Void, onError: (PreviewError) -> ()) { + fileprivate func extractInfo(response: Response, cancellable: Cancellable, completion: @escaping (Response) -> Void, onError: @escaping (PreviewError) -> Void) { guard !cancellable.isCancelled, let url = response[.finalUrl] as? URL else { return } + func requestSync(sourceUrl: URL, request: URLRequest) -> (Bool, Data?, URLResponse?) { + + let (data, urlResponse, error) = session.synchronousDataTask(with: request ) + if let error = error { + if !cancellable.isCancelled { + let details = "\(sourceUrl.absoluteString): \(error.localizedDescription)" + onError( .cannotBeOpened( details ) ) + return (false, data, urlResponse) + } + } + return (true, data, urlResponse) + + } + if url.absoluteString.isImage() { var result = response @@ -313,10 +325,11 @@ extension SwiftLinkPreview { completion(result) } else { + guard let sourceUrl = url.scheme == "http" || url.scheme == "https" ? url: URL( string: "http://\(url)" ) - else { - if !cancellable.isCancelled { onError(.invalidURL(url.absoluteString)) } - return + else { + if !cancellable.isCancelled { onError(.invalidURL(url.absoluteString)) } + return } var request = URLRequest( url: sourceUrl ) request.addValue("text/html,application/xhtml+xml,application/xml", forHTTPHeaderField: "Accept") @@ -329,20 +342,32 @@ extension SwiftLinkPreview { } } if let data = data, let urlResponse = urlResponse, let encoding = urlResponse.textEncodingName, - let source = NSString( data: data, encoding: - CFStringConvertEncodingToNSStringEncoding( CFStringConvertIANACharSetNameToEncoding( encoding as CFString ) ) ) { + let source = NSString( data: data, encoding: + CFStringConvertEncodingToNSStringEncoding( CFStringConvertIANACharSetNameToEncoding( encoding as CFString ) ) ) { if !cancellable.isCancelled { self.parseHtmlString(source as String, response: response, completion: completion) } } else { - if !cancellable.isCancelled { - onError(.parseError(sourceUrl.absoluteString)) + do { + let data = try Data(contentsOf: sourceUrl) + var source: NSString? = nil + NSString.stringEncoding(for: data, encodingOptions: nil, convertedString: &source, usedLossyConversion: nil) + + if let source = source { + if !cancellable.isCancelled { + self.parseHtmlString(source as String, response: response, completion: completion) + } + } + } catch _ { + if !cancellable.isCancelled { + onError(.parseError(sourceUrl.absoluteString)) + } } } + } } - private func parseHtmlString(_ htmlString: String, response: Response, completion: @escaping (Response) -> Void) { completion(self.performPageCrawling(self.cleanSource(htmlString), response: response)) } @@ -351,7 +376,7 @@ extension SwiftLinkPreview { private func cleanSource(_ source: String) -> String { var source = source - + source = source.deleteTagByPattern(Regex.inlineStylePattern) source = source.deleteTagByPattern(Regex.inlineScriptPattern) source = source.deleteTagByPattern(Regex.scriptPattern) @@ -361,7 +386,6 @@ extension SwiftLinkPreview { } - // Perform the page crawiling private func performPageCrawling(_ htmlCode: String, response: Response) -> Response { var result = self.crawIcon(htmlCode, result: response) @@ -377,7 +401,6 @@ extension SwiftLinkPreview { return self.crawlImages(otherResponse.htmlCode, result: otherResponse.result) } - // Extract canonical URL internal func extractCanonicalURL(_ finalUrl: URL) -> String { @@ -420,7 +443,7 @@ extension SwiftLinkPreview { fileprivate func extractBaseUrl(_ url: String) -> String { return String(url.split(separator: "/", maxSplits: 1, omittingEmptySubsequences: true)[0]) - + } } @@ -434,10 +457,7 @@ extension SwiftLinkPreview { let metatags = Regex.pregMatchAll(htmlCode, regex: Regex.linkPattern, index: 1) - let filters = [ - { (link: String) -> Bool in link.range(of: "apple-touch") != nil }, - { (link: String) -> Bool in link.range(of: "shortcut") != nil }, - { (link: String) -> Bool in link.range(of: "icon") != nil } + let filters = [ { (link: String) -> Bool in link.range(of: "apple-touch") != nil }, { (link: String) -> Bool in link.range(of: "shortcut") != nil }, { (link: String) -> Bool in link.range(of: "icon") != nil } ] for filter in filters { @@ -627,49 +647,48 @@ extension SwiftLinkPreview { } else { return resultThirdSearch - + } - + } else { - + return resultFirstSearch - + } - + } - - + } - + } - + } - + // Get tag content private func getTagContent(_ tag: String, content: String, minimum: Int) -> String { - + let pattern = Regex.tagPattern(tag) - + let index = 2 let rawMatches = Regex.pregMatchAll(content, regex: pattern, index: index) - + let matches = rawMatches.filter({ $0.extendedTrim.tagsStripped.count >= minimum }) var result = matches.count > 0 ? matches[0] : "" - + if result.isEmpty { - + if let match = Regex.pregMatchFirst(content, regex: pattern, index: 2) { - + result = match.extendedTrim.tagsStripped - + } - + } - + return result - + } - + } extension SwiftLinkPreview: URLSessionDataDelegate { diff --git a/SwiftLinkPreview.podspec b/SwiftLinkPreview.podspec index 86465ec..3dc3d9a 100755 --- a/SwiftLinkPreview.podspec +++ b/SwiftLinkPreview.podspec @@ -7,7 +7,7 @@ Pod::Spec.new do |s| s.name = "SwiftLinkPreview" s.summary = "It makes a preview from an url, grabbing all the information such as title, relevant texts and images." s.requires_arc = true - s.version = "2.3.0" + s.version = "2.3.1" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Leonardo Cardoso" => "contact@leocardz.com" } s.homepage = "https://github.com/LeonardoCardoso/SwiftLinkPreview" diff --git a/SwiftLinkPreviewTests/Info-macOS.plist b/SwiftLinkPreviewTests/Info-macOS.plist index 8d62b1a..3dcd1f0 100644 --- a/SwiftLinkPreviewTests/Info-macOS.plist +++ b/SwiftLinkPreviewTests/Info-macOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.3.0 + 2.3.1 CFBundleSignature ???? CFBundleVersion diff --git a/SwiftLinkPreviewTests/Info-tvOS.plist b/SwiftLinkPreviewTests/Info-tvOS.plist index 8d62b1a..3dcd1f0 100644 --- a/SwiftLinkPreviewTests/Info-tvOS.plist +++ b/SwiftLinkPreviewTests/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.3.0 + 2.3.1 CFBundleSignature ???? CFBundleVersion diff --git a/SwiftLinkPreviewTests/Info.plist b/SwiftLinkPreviewTests/Info.plist index 8d62b1a..3dcd1f0 100644 --- a/SwiftLinkPreviewTests/Info.plist +++ b/SwiftLinkPreviewTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.3.0 + 2.3.1 CFBundleSignature ???? CFBundleVersion