Merge pull request #1278 from kielgillard/master

Use ASWebAuthenticationSession to obtain OAuth grant for Feedly production and sandbox.
This commit is contained in:
Maurice Parker 2019-11-10 03:14:13 -06:00 committed by GitHub
commit cd50e7c88f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 302 additions and 224 deletions

View File

@ -308,19 +308,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
}
}
public static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient {
let grantingType: OAuthAuthorizationGranting.Type
internal static func oauthAuthorizationClient(for type: AccountType) -> OAuthAuthorizationClient {
switch type {
case .feedly:
grantingType = FeedlyAccountDelegate.self
return FeedlyAccountDelegate.environment.oauthAuthorizationClient
default:
fatalError("\(type) does not support OAuth authorization code granting.")
fatalError("\(type) is not a client for OAuth authorization code granting.")
}
return grantingType.oauthAuthorizationClient
}
public static func oauthAuthorizationCodeGrantRequest(for type: AccountType, client: OAuthAuthorizationClient) -> URLRequest {
public static func oauthAuthorizationCodeGrantRequest(for type: AccountType) -> URLRequest {
let grantingType: OAuthAuthorizationGranting.Type
switch type {
case .feedly:
@ -329,7 +326,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
fatalError("\(type) does not support OAuth authorization code granting.")
}
return grantingType.oauthAuthorizationCodeGrantRequest(for: client)
return grantingType.oauthAuthorizationCodeGrantRequest()
}
public static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse,
@ -346,7 +343,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
fatalError("\(accountType) does not support OAuth authorization code granting.")
}
grantingType.requestOAuthAccessToken(with: response, client: client, transport: transport, completionHandler: completionHandler)
grantingType.requestOAuthAccessToken(with: response, transport: transport, completionHandler: completionHandler)
}
public func refreshAll(completion: @escaping (Result<Void, Error>) -> Void) {

View File

@ -120,6 +120,8 @@
9E85C8E82366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E72366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift */; };
9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */; };
9E85C8ED2367020700D0F1F7 /* FeedlyGetEntriesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E85C8EC2367020700D0F1F7 /* FeedlyGetEntriesService.swift */; };
9E964EB823754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E964E9E23754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift */; };
9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */; };
9EA3133B231E368100268BA0 /* FeedlyAccountDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */; };
9EAEC60C2332FE830085D7C9 /* FeedlyCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */; };
9EAEC60E2332FEC20085D7C9 /* FeedlyFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */; };
@ -320,6 +322,8 @@
9E85C8E72366FF4200D0F1F7 /* TestGetPagedStreamContentsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestGetPagedStreamContentsService.swift; sourceTree = "<group>"; };
9E85C8E9236700AD00D0F1F7 /* FeedlyGetEntriesOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetEntriesOperation.swift; sourceTree = "<group>"; };
9E85C8EC2367020700D0F1F7 /* FeedlyGetEntriesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyGetEntriesService.swift; sourceTree = "<group>"; };
9E964E9E23754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthAuthorizationClient+Feedly.swift"; sourceTree = "<group>"; };
9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthAccountAuthorizationOperation.swift; sourceTree = "<group>"; };
9EA3133A231E368100268BA0 /* FeedlyAccountDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedlyAccountDelegate.swift; sourceTree = "<group>"; };
9EAEC60B2332FE830085D7C9 /* FeedlyCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyCollection.swift; sourceTree = "<group>"; };
9EAEC60D2332FEC20085D7C9 /* FeedlyFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedlyFeed.swift; sourceTree = "<group>"; };
@ -616,7 +620,9 @@
9EE4CCF9234F106600FBAE4B /* FeedlyFeedContainerValidator.swift */,
9ECC9A84234DC16E009B5144 /* FeedlyAccountDelegateError.swift */,
9EC688EB232C583300A8D0A2 /* FeedlyAccountDelegate+OAuth.swift */,
9E964E9E23754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift */,
9EC688ED232C58E800A8D0A2 /* OAuthAuthorizationCodeGranting.swift */,
9E964EB923754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift */,
9E672395236F7E68000BE141 /* OAuthAcessTokenRefreshing.swift */,
9EC688E9232B973C00A8D0A2 /* FeedlyAPICaller.swift */,
9E510D6D234F16A8002E6F1A /* FeedlyAddFeedRequest.swift */,
@ -716,7 +722,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 8489350A1F62485000CEBD24 /* Build configuration list for PBXNativeTarget "Account" */;
buildPhases = (
9E964EBB2375512300A7AF2E /* Run Script: Update OAuthAuthorizationClient+Feedly.swift */,
848934F11F62484F00CEBD24 /* Sources */,
9E964EBC2375517100A7AF2E /* Run Script: Reset OAuthAuthorizationClient+Feedly.swift */,
848934F21F62484F00CEBD24 /* Frameworks */,
848934F31F62484F00CEBD24 /* Headers */,
848934F41F62484F00CEBD24 /* Resources */,
@ -889,6 +897,42 @@
shellPath = /bin/sh;
shellScript = "xcrun -sdk macosx swiftc -target x86_64-macosx10.11 ../../buildscripts/VerifyNoBuildSettings.swift -o $CONFIGURATION_TEMP_DIR/VerifyNoBS\n$CONFIGURATION_TEMP_DIR/VerifyNoBS ${PROJECT_NAME}.xcodeproj/project.pbxproj\n";
};
9E964EBB2375512300A7AF2E /* Run Script: Update OAuthAuthorizationClient+Feedly.swift */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Run Script: Update OAuthAuthorizationClient+Feedly.swift";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "FAILED=false\n\nif [ -z \"${FEEDLY_CLIENT_ID}\" ]; then\necho \"Missing Feedly Client ID\"\nFAILED=true\nfi\n\nif [ -z \"${FEEDLY_CLIENT_SECRET}\" ]; then\necho \"Missing Feedly Client Secret\"\nFAILED=true\nfi\n\nFEEDLY_CLIENT_SOURCE=\"${SRCROOT}/Feedly/OAuthAuthorizationClient+Feedly.swift\"\n\nif [ \"$FAILED\" = true ]; then\necho \"Missing Feedly client ID or secret. ${FEEDLY_CLIENT_SOURCE} not changed.\"\nexit 0\nfi\n\n# echo \"Substituting variables in: ${FEEDLY_CLIENT_SOURCE}\"\n\nif [ -e \"${FEEDLY_CLIENT_SOURCE}\" ]\nthen\n sed -i .tmp \"s|{FEEDLY_CLIENT_ID}|${FEEDLY_CLIENT_ID}|g; s|{FEEDLY_CLIENT_SECRET}|${FEEDLY_CLIENT_SECRET}|g\" $FEEDLY_CLIENT_SOURCE\n # echo \"`git diff ${FEEDLY_CLIENT_SOURCE}`\"\n rm -f \"${FEEDLY_CLIENT_SOURCE}.tmp\"\nelse\n echo \"File does not exist at ${FEEDLY_CLIENT_SOURCE}. Has it been moved or renamed?\"\n exit -1\nfi\n\necho \"All env values found!\"\n";
};
9E964EBC2375517100A7AF2E /* Run Script: Reset OAuthAuthorizationClient+Feedly.swift */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Run Script: Reset OAuthAuthorizationClient+Feedly.swift";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "git checkout \"${SRCROOT}/Feedly/OAuthAuthorizationClient+Feedly.swift\"\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -932,6 +976,7 @@
9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */,
9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */,
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */,
9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */,
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */,
9E1773D7234575AB0056A5A8 /* FeedlyTag.swift in Sources */,
@ -972,6 +1017,7 @@
9EC688EA232B973C00A8D0A2 /* FeedlyAPICaller.swift in Sources */,
9E1773D32345700F0056A5A8 /* FeedlyLink.swift in Sources */,
9EAEC62823331C350085D7C9 /* FeedlyCategory.swift in Sources */,
9E964EB823754AC400A7AF2E /* OAuthAuthorizationClient+Feedly.swift in Sources */,
9EF1B10923590E93000A486A /* FeedlyStreamIds.swift in Sources */,
84D09623217418DC00D77525 /* FeedbinTagging.swift in Sources */,
84CAD7161FDF2E22000F0755 /* FeedbinEntry.swift in Sources */,

View File

@ -130,7 +130,7 @@ class FeedlySyncAllOperationTests: XCTestCase {
OperationQueue.main.addOperation(syncAll)
waitForExpectations(timeout: 2)
waitForExpectations(timeout: 5)
}
func performInitialSync() {

View File

@ -31,20 +31,10 @@ final class FeedlyAPICaller {
var oauthAuthorizationClient: OAuthAuthorizationClient {
switch self {
case .cloud:
/// Models private NetNewsWire client secrets.
/// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code
return OAuthAuthorizationClient(id: "{FEEDLY-ID}",
redirectUri: "{FEEDLY-REDIRECT-URI}",
state: nil,
secret: "{FEEDLY-SECRET}")
case .sandbox:
/// Models public sandbox API values found at:
/// https://groups.google.com/forum/#!topic/feedly-cloud/WwQWMgDmOuw
return OAuthAuthorizationClient(id: "sandbox",
redirectUri: "http://localhost",
state: nil,
secret: "ReVGXA6WekanCxbf")
return .feedlySandboxClient
case .cloud:
return .feedlyCloudClient
}
}
}

View File

@ -27,11 +27,8 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
private static let oauthAuthorizationGrantScope = "https://cloud.feedly.com/subscriptions"
static var oauthAuthorizationClient: OAuthAuthorizationClient {
return environment.oauthAuthorizationClient
}
static func oauthAuthorizationCodeGrantRequest(for client: OAuthAuthorizationClient) -> URLRequest {
static func oauthAuthorizationCodeGrantRequest() -> URLRequest {
let client = environment.oauthAuthorizationClient
let authorizationRequest = OAuthAuthorizationRequest(clientId: client.id,
redirectUri: client.redirectUri,
scope: oauthAuthorizationGrantScope,
@ -40,7 +37,8 @@ extension FeedlyAccountDelegate: OAuthAuthorizationGranting {
return FeedlyAPICaller.authorizationCodeUrlRequest(for: authorizationRequest, baseUrlComponents: baseURLComponents)
}
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, client: OAuthAuthorizationClient, transport: Transport, completionHandler: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completionHandler: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ()) {
let client = environment.oauthAuthorizationClient
let request = OAuthAccessTokenRequest(authorizationResponse: response,
scope: oauthAuthorizationGrantScope,
client: client)

View File

@ -17,6 +17,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
/// Feedly has a sandbox API and a production API.
/// This property is referred to when clients need to know which environment it should be pointing to.
/// The value of this proptery must match any `OAuthAuthorizationClient` used.
static var environment: FeedlyAPICaller.API {
#if DEBUG
// https://developer.feedly.com/v3/developer/
@ -24,7 +25,6 @@ final class FeedlyAccountDelegate: AccountDelegate {
return .cloud
}
return .sandbox
#else
return .cloud
#endif
@ -53,6 +53,8 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
let oauthAuthorizationClient: OAuthAuthorizationClient
var accountMetadata: AccountMetadata?
var refreshProgress = DownloadProgress(numberOfTasks: 0)
@ -97,6 +99,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("Sync.sqlite3")
self.database = SyncDatabase(databaseFilePath: databaseFilePath)
self.oauthAuthorizationClient = api.oauthAuthorizationClient
}
// MARK: Account API
@ -484,8 +487,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
func accountDidInitialize(_ account: Account) {
credentials = try? account.retrieveCredentials(type: .oauthAccessToken)
let client = FeedlyAccountDelegate.oauthAuthorizationClient
let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: client, log: log)
let refreshAccessToken = FeedlyRefreshAccessTokenOperation(account: account, service: self, oauthClient: oauthAuthorizationClient, log: log)
operationQueue.addOperation(refreshAccessToken)
}

View File

@ -0,0 +1,184 @@
//
// OAuthAccountAuthorizationOperation.swift
// NetNewsWire
//
// Created by Kiel Gillard on 8/11/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import Foundation
import AuthenticationServices
public protocol OAuthAccountAuthorizationOperationDelegate: class {
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error)
}
public final class OAuthAccountAuthorizationOperation: Operation, ASWebAuthenticationPresentationContextProviding {
public weak var presentationAnchor: ASPresentationAnchor?
public weak var delegate: OAuthAccountAuthorizationOperationDelegate?
private let accountType: AccountType
private let oauthClient: OAuthAuthorizationClient
private var session: ASWebAuthenticationSession?
public init(accountType: AccountType) {
self.accountType = accountType
self.oauthClient = Account.oauthAuthorizationClient(for: accountType)
}
override public func main() {
assert(Thread.isMainThread)
assert(presentationAnchor != nil, "\(self) outlived presentation anchor.")
guard !isCancelled else {
didFinish()
return
}
let request = Account.oauthAuthorizationCodeGrantRequest(for: accountType)
guard let url = request.url else {
return DispatchQueue.main.async {
self.didEndAuthentication(url: nil, error: URLError(.badURL))
}
}
guard let redirectUri = URL(string: oauthClient.redirectUri), let scheme = redirectUri.scheme else {
assertionFailure("Could not get callback URL scheme from \(oauthClient.redirectUri)")
return DispatchQueue.main.async {
self.didEndAuthentication(url: nil, error: URLError(.badURL))
}
}
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: scheme) { url, error in
DispatchQueue.main.async { [weak self] in
self?.didEndAuthentication(url: url, error: error)
}
}
self.session = session
session.presentationContextProvider = self
session.start()
}
override public func cancel() {
session?.cancel()
super.cancel()
}
private func didEndAuthentication(url: URL?, error: Error?) {
guard !isCancelled else {
didFinish()
return
}
do {
guard let url = url else {
if let error = error {
throw error
}
throw URLError(.badURL)
}
let response = try OAuthAuthorizationResponse(url: url, client: oauthClient)
Account.requestOAuthAccessToken(with: response, client: oauthClient, accountType: accountType, completionHandler: didEndRequestingAccessToken(_:))
} catch is ASWebAuthenticationSessionError {
didFinish() // Primarily, cancellation.
} catch {
didFinish(error)
}
}
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
guard let anchor = presentationAnchor else {
fatalError("\(self) has outlived presentation anchor.")
}
return anchor
}
private func didEndRequestingAccessToken(_ result: Result<OAuthAuthorizationGrant, Error>) {
guard !isCancelled else {
didFinish()
return
}
switch result {
case .success(let tokenResponse):
saveAccount(for: tokenResponse)
case .failure(let error):
didFinish(error)
}
}
private func saveAccount(for grant: OAuthAuthorizationGrant) {
// TODO: Find an already existing account for this username?
let account = AccountManager.shared.createAccount(type: .feedly)
do {
// Store the refresh token first because it sends this token to the account delegate.
if let token = grant.refreshToken {
try account.storeCredentials(token)
}
// Now store the access token because we want the account delegate to use it.
try account.storeCredentials(grant.accessToken)
didFinish()
} catch {
didFinish(error)
}
}
// MARK: Managing Operation State
private func didFinish() {
assert(Thread.isMainThread)
assert(!isFinished, "Finished operation is attempting to finish again.")
self.isExecutingOperation = false
self.isFinishedOperation = true
}
private func didFinish(_ error: Error) {
assert(Thread.isMainThread)
assert(!isFinished, "Finished operation is attempting to finish again.")
delegate?.oauthAccountAuthorizationOperation(self, didFailWith: error)
didFinish()
}
override public func start() {
isExecutingOperation = true
DispatchQueue.main.async {
self.main()
}
}
override public var isExecuting: Bool {
return isExecutingOperation
}
private var isExecutingOperation = false {
willSet {
willChangeValue(for: \.isExecuting)
}
didSet {
didChangeValue(for: \.isExecuting)
}
}
override public var isFinished: Bool {
return isFinishedOperation
}
private var isFinishedOperation = false {
willSet {
willChangeValue(for: \.isFinished)
}
didSet {
didChangeValue(for: \.isFinished)
}
}
}

View File

@ -0,0 +1,34 @@
//
// OAuthAuthorizationClient+NetNewsWire.swift
// Account
//
// Created by Kiel Gillard on 8/11/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import Foundation
extension OAuthAuthorizationClient {
static var feedlyCloudClient: OAuthAuthorizationClient {
/// Models private NetNewsWire client secrets.
/// These placeholders are substitued at build time using a Run Script phase with build settings.
/// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code
return OAuthAuthorizationClient(id: "{FEEDLY_CLIENT_ID}",
redirectUri: "netnewswire://auth/feedly",
state: nil,
secret: "{FEEDLY_CLIENT_SECRET}")
}
static var feedlySandboxClient: OAuthAuthorizationClient {
/// We use this funky redirect URI because ASWebAuthenticationSession will try to load http://localhost URLs.
/// See https://developer.feedly.com/v3/sandbox/ for more information.
/// The return value models public sandbox API values found at:
/// https://groups.google.com/forum/#!topic/feedly-cloud/WwQWMgDmOuw
/// They are due to expire on November 30 2019.
return OAuthAuthorizationClient(id: "sandbox",
redirectUri: "urn:ietf:wg:oauth:2.0:oob",
state: nil,
secret: "ReVGXA6WekanCxbf")
}
}

View File

@ -61,7 +61,7 @@ public struct OAuthAuthorizationResponse {
public extension OAuthAuthorizationResponse {
init(url: URL, client: OAuthAuthorizationClient) throws {
guard let host = url.host, client.redirectUri.contains(host) else {
guard let scheme = url.scheme, client.redirectUri.hasPrefix(scheme) else {
throw URLError(.unsupportedURL)
}
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
@ -165,10 +165,8 @@ public protocol OAuthAuthorizationCodeGrantRequesting {
}
protocol OAuthAuthorizationGranting: AccountDelegate {
static func oauthAuthorizationCodeGrantRequest() -> URLRequest
static var oauthAuthorizationClient: OAuthAuthorizationClient { get }
static func oauthAuthorizationCodeGrantRequest(for client: OAuthAuthorizationClient) -> URLRequest
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, client: OAuthAuthorizationClient, transport: Transport, completionHandler: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
static func requestOAuthAccessToken(with response: OAuthAuthorizationResponse, transport: Transport, completionHandler: @escaping (Result<OAuthAuthorizationGrant, Error>) -> ())
}

View File

@ -9,7 +9,7 @@ PROVISIONING_PROFILE_SPECIFIER =
#include? "../../../SharedXcodeSettings/DeveloperSettings.xcconfig"
SDKROOT = macosx
MACOSX_DEPLOYMENT_TARGET = 10.14
MACOSX_DEPLOYMENT_TARGET = 10.15
IPHONEOS_DEPLOYMENT_TARGET = 13.0
SUPPORTED_PLATFORMS = macosx iphoneos iphonesimulator
@ -18,7 +18,6 @@ SWIFT_VERSION = 5.1
COMBINE_HIDPI_IMAGES = YES
COPY_PHASE_STRIP = NO
MACOSX_DEPLOYMENT_TARGET = 10.14
ALWAYS_SEARCH_USER_PATHS = NO
CURRENT_PROJECT_VERSION = 1
VERSION_INFO_PREFIX =

View File

@ -101,9 +101,10 @@ extension AccountsAddViewController: NSTableViewDelegate {
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsReaderAPIWindowController
case .feedly:
let accountsFeedlyWindowController = AccountsFeedlyWebWindowController()
accountsFeedlyWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsFeedlyWindowController
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
addAccount.delegate = self
addAccount.presentationAnchor = self.view.window!
OperationQueue.main.addOperation(addAccount)
default:
break
}
@ -113,3 +114,12 @@ extension AccountsAddViewController: NSTableViewDelegate {
}
}
// MARK: OAuthAccountAuthorizationOperationDelegate
extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate {
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) {
view.window?.presentError(error)
}
}

View File

@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14865.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14865.1"/>
<plugIn identifier="com.apple.WebKit2IBPlugin" version="14865.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AccountsFeedlyWebWindowController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="webView" destination="W4c-Xp-rpq" id="l11-5B-8yc"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="708"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="708"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<wkWebView wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="W4c-Xp-rpq">
<rect key="frame" x="0.0" y="61" width="480" height="647"/>
<wkWebViewConfiguration key="configuration">
<audiovisualMediaTypes key="mediaTypesRequiringUserActionForPlayback" none="YES"/>
<wkPreferences key="preferences"/>
</wkWebViewConfiguration>
<connections>
<outlet property="navigationDelegate" destination="-2" id="wAp-Oh-5EK"/>
</connections>
</wkWebView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PD2-Zk-3yM">
<rect key="frame" x="384" y="13" width="82" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="IEi-N0-sbw">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="5BT-to-e4W"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="W4c-Xp-rpq" secondAttribute="trailing" id="D9j-IU-BZj"/>
<constraint firstAttribute="bottom" secondItem="PD2-Zk-3yM" secondAttribute="bottom" constant="20" symbolic="YES" id="Qdc-tu-9kO"/>
<constraint firstItem="W4c-Xp-rpq" firstAttribute="top" secondItem="se5-gp-TjO" secondAttribute="top" id="V7Q-kM-JDA"/>
<constraint firstAttribute="trailing" secondItem="PD2-Zk-3yM" secondAttribute="trailing" constant="20" symbolic="YES" id="bQS-L4-jbx"/>
<constraint firstItem="W4c-Xp-rpq" firstAttribute="leading" secondItem="se5-gp-TjO" secondAttribute="leading" id="ec6-U0-t8X"/>
<constraint firstItem="PD2-Zk-3yM" firstAttribute="top" secondItem="W4c-Xp-rpq" secondAttribute="bottom" constant="20" symbolic="YES" id="zlA-8I-aKr"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
</connections>
<point key="canvasLocation" x="134" y="556"/>
</window>
</objects>
</document>

View File

@ -1,100 +0,0 @@
//
// AccountsFeedlyWebWindowController.swift
// NetNewsWire
//
// Created by Kiel Gillard on 30/8/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import Cocoa
import Account
import WebKit
class AccountsFeedlyWebWindowController: NSWindowController, WKNavigationDelegate {
@IBOutlet private weak var webView: WKWebView!
private weak var hostWindow: NSWindow?
convenience init() {
self.init(windowNibName: NSNib.Name("AccountsFeedlyWeb"))
}
// MARK: API
func runSheetOnWindow(_ hostWindow: NSWindow, completionHandler handler: ((NSApplication.ModalResponse) -> Void)? = nil) {
self.hostWindow = hostWindow
hostWindow.beginSheet(window!, completionHandler: handler)
beginAuthorization()
}
// MARK: Requesting an Access Token
let client = Account.oauthAuthorizationClient(for: .feedly)
private func beginAuthorization() {
let request = Account.oauthAuthorizationCodeGrantRequest(for: .feedly, client: client)
webView.load(request)
}
private func requestAccessToken(for response: OAuthAuthorizationResponse) {
Account.requestOAuthAccessToken(with: response, client: client, accountType: .feedly) { [weak self] result in
switch result {
case .success(let tokenResponse):
self?.saveAccount(for: tokenResponse)
case .failure(let error):
NSApplication.shared.presentError(error)
}
}
}
// MARK: Actions
@IBAction func cancel(_ sender: Any) {
hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
do {
guard let url = navigationAction.request.url else { return }
let response = try OAuthAuthorizationResponse(url: url, client: client)
requestAccessToken(for: response)
// No point the web view trying to load this.
return decisionHandler(.cancel)
} catch let error as OAuthAuthorizationErrorResponse {
NSApplication.shared.presentError(error)
} catch {
print(error)
}
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print(error)
}
private func saveAccount(for grant: OAuthAuthorizationGrant) {
// TODO: Find an already existing account for this username?
let account = AccountManager.shared.createAccount(type: .feedly)
do {
// Store the refresh token first because it sends this token to the account delegate.
if let token = grant.refreshToken {
try account.storeCredentials(token)
}
// Now store the access token because we want the account delegate to use it.
try account.storeCredentials(grant.accessToken)
self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK)
} catch {
NSApplication.shared.presentError(error)
}
}
}

View File

@ -276,7 +276,6 @@
65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EB92031649C00BC20B7 /* SmartFeedPasteboardWriter.swift */; };
65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; };
65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; };
65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; };
65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; };
65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5127B236222B4849006D641D /* DetailKeyboardDelegate.swift */; };
65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */; };
@ -307,7 +306,6 @@
65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; };
65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; };
65ED3FEB235DEF6C0081F399 /* (null) in Sources */ = {isa = PBXBuildFile; };
65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; };
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
@ -407,7 +405,6 @@
65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5127B237222B4849006D641D /* DetailKeyboardShortcuts.plist */; };
65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; };
65ED4052235DEF6C0081F399 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; };
65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; };
65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; };
65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; };
65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */; };
@ -615,8 +612,6 @@
84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; };
84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; };
84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */; };
9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */; };
B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; };
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; };
D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; };
@ -1538,8 +1533,6 @@
84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.applescript; path = establishMainWindowStartingState.applescript; sourceTree = "<group>"; };
84F9EAE4213660A100CF2DE4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = "<group>"; };
9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsFeedlyWebWindowController.swift; sourceTree = "<group>"; };
9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedlyWeb.xib; sourceTree = "<group>"; };
B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = "<group>"; };
B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = "<group>"; };
B528F81D23333C7E00E735DD /* page.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = page.html; sourceTree = "<group>"; };
@ -2510,8 +2503,6 @@
5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */,
5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */,
5144EA4F227B8E4500D19003 /* AccountsFeedbinWindowController.swift */,
9EA33BB82318F8C10097B644 /* AccountsFeedlyWeb.xib */,
9EA33BB72318F8C10097B644 /* AccountsFeedlyWebWindowController.swift */,
55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */,
55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */,
5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */,
@ -3359,7 +3350,6 @@
65ED4050235DEF6C0081F399 /* DetailKeyboardShortcuts.plist in Resources */,
65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */,
65ED4052235DEF6C0081F399 /* template.html in Resources */,
65ED4053235DEF6C0081F399 /* AccountsFeedlyWeb.xib in Resources */,
65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */,
65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */,
65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */,
@ -3445,7 +3435,6 @@
5127B23A222B4849006D641D /* DetailKeyboardShortcuts.plist in Resources */,
845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */,
848362FF2262A30E00DA1D35 /* template.html in Resources */,
9EA33BBA2318F8C10097B644 /* AccountsFeedlyWeb.xib in Resources */,
848363082262A3DD00DA1D35 /* Main.storyboard in Resources */,
51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */,
84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */,
@ -3762,7 +3751,6 @@
65ED3FC9235DEF6C0081F399 /* SmartFeedPasteboardWriter.swift in Sources */,
65ED3FCA235DEF6C0081F399 /* SmartFeedsController.swift in Sources */,
65ED3FCB235DEF6C0081F399 /* SidebarViewController.swift in Sources */,
65ED3FCC235DEF6C0081F399 /* AccountsFeedlyWebWindowController.swift in Sources */,
65ED3FCD235DEF6C0081F399 /* SidebarOutlineView.swift in Sources */,
65ED3FCE235DEF6C0081F399 /* DetailKeyboardDelegate.swift in Sources */,
65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */,
@ -3793,7 +3781,6 @@
65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */,
65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */,
65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */,
65ED3FEB235DEF6C0081F399 /* (null) in Sources */,
65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */,
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */,
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */,
@ -4053,7 +4040,6 @@
849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */,
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */,
849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */,
9EA33BB92318F8C10097B644 /* AccountsFeedlyWebWindowController.swift in Sources */,
849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */,
5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */,
8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */,