diff --git a/packages/twitter/README.md b/packages/twitter/README.md index 13572c13..1c021e91 100644 --- a/packages/twitter/README.md +++ b/packages/twitter/README.md @@ -19,32 +19,26 @@ Here is how callbacks would look like: ### iOS - -Configure Info.Plist like below, replace `consumerKey` tag with your own key: - ```xml CFBundleURLTypes CFBundleURLSchemes - twitterkit- + yourscheme> -LSApplicationQueriesSchemes - - twitter - twitterauth - ``` ```ts import { Twitter, TwitterSignIn } from '@nativescript/twitter'; -TwitterSignIn.init(TWITTER_COMSUMER_KEY, TWITTER_CONSUMER_SECRET); +Twitter.callback = 'yourscheme://'; + +Twitter.init(TWITTER_COMSUMER_KEY, TWITTER_CONSUMER_SECRET); -Twitter.logIn() +TwitterSignIn.logIn() .then((session) => { // session.authToken // session.authTokenSecret diff --git a/packages/twitter/index.android.ts b/packages/twitter/index.android.ts index 40f957d3..b04d7191 100644 --- a/packages/twitter/index.android.ts +++ b/packages/twitter/index.android.ts @@ -63,6 +63,19 @@ export class TwitterUser implements ITwitterUser { get android() { return this.native; } + + toJSON() { + return { + formattedScreenName: this.formattedScreenName, + isProtected: this.isProtected, + isVerified: this.isVerified, + name: this.name, + profileImageUrl: this.profileImageUrl, + profileUrl: this.profileUrl, + screenName: this.screenName, + userId: this.userId + } + } } export class Twitter { @@ -180,7 +193,7 @@ export class TwitterSignIn { } sessionManager.clearActiveSession(); - } catch (e) {} + } catch (e) { } } static getCurrentUser() { diff --git a/packages/twitter/index.d.ts b/packages/twitter/index.d.ts index 70dbecd3..6a33858c 100644 --- a/packages/twitter/index.d.ts +++ b/packages/twitter/index.d.ts @@ -39,6 +39,7 @@ export declare class Session implements ISession { } export declare class Twitter { + static callback: string; static init(consumerKey: string, consumerSecret: string); } diff --git a/packages/twitter/index.ios.ts b/packages/twitter/index.ios.ts index 7ce55ea5..dbeda664 100644 --- a/packages/twitter/index.ios.ts +++ b/packages/twitter/index.ios.ts @@ -1,3 +1,4 @@ +import { Application, Device } from '@nativescript/core'; import { ISession, ITwitterUser } from './common'; export class TwitterError extends Error { @@ -14,9 +15,10 @@ export class TwitterError extends Error { } let appDelegateInitialized = false; -let appDelegate: TwitterAppDelegateImpl; -@NativeClass +let appDelegate; + @ObjCClass(UIApplicationDelegate) +@NativeClass class TwitterAppDelegateImpl extends UIResponder implements UIApplicationDelegate { static get sharedInstance() { if (!appDelegate) { @@ -26,14 +28,27 @@ class TwitterAppDelegateImpl extends UIResponder implements UIApplicationDelegat } applicationOpenURLOptions(app: UIApplication, url: NSURL, options: NSDictionary): boolean { - return TWTRTwitter.sharedInstance().applicationOpenURLOptions(app, url, options); + TNSTwitter.handleOpenURL(url.absoluteString, Twitter.callback, false); + return true; + } + +} + + +if (parseFloat(Device.sdkVersion) >= 13) { + (TwitterAppDelegateImpl).prototype.applicationConfigurationForConnectingSceneSessionOptions = (application: UIApplication, connectingSceneSession: UISceneSession, options: UISceneConnectionOptions) => { + return UISceneConfiguration.configurationWithNameSessionRole( + "Default Configuration", + connectingSceneSession.role + ) } } + export class TwitterUser implements ITwitterUser { - #native: TWTRUser; - static fromNative(user: TWTRUser) { - if (user instanceof TWTRUser) { + #native: any; + static fromNative(user: any) { + if (user) { const usr = new TwitterUser(); usr.#native = user; return usr; @@ -42,15 +57,15 @@ export class TwitterUser implements ITwitterUser { } get formattedScreenName(): string { - return this.native.formattedScreenName; + return this.native.screen_name; } get isProtected(): boolean { - return this.native.isProtected; + return this.native.protected; } get isVerified(): boolean { - return this.isVerified; + return this.native.verified; } get name(): string { @@ -58,19 +73,19 @@ export class TwitterUser implements ITwitterUser { } get profileImageUrl(): string { - return this.native.profileImageURL; + return this.native.profile_image_url_https; } get profileUrl(): string { - return this.native.profileURL?.absoluteString; + return this.native.url; } get screenName(): string { - return this.native.screenName; + return this.native.screen_name; } get userId(): string { - return this.native.userID; + return this.native.id_str; } get native() { @@ -80,12 +95,26 @@ export class TwitterUser implements ITwitterUser { get ios() { return this.native; } + + + toJSON() { + return { + formattedScreenName: this.formattedScreenName, + isProtected: this.isProtected, + isVerified: this.isVerified, + name: this.name, + profileImageUrl: this.profileImageUrl, + profileUrl: this.profileUrl, + screenName: this.screenName, + userId: this.userId + } + } } export class Session implements ISession { - #native: TWTRSession; - static fromNative(ts: TWTRSession) { - if (ts instanceof TWTRSession) { + #native: any; + static fromNative(ts) { + if (ts) { const session = new Session(); session.#native = ts; return session; @@ -117,9 +146,12 @@ export class Session implements ISession { } } + export class Twitter { + static _twitter: TNSTwitter; + static callback: string; static init(consumerKey: string, consumerSecret: string) { - TWTRTwitter.sharedInstance().startWithConsumerKeyConsumerSecret(consumerKey, consumerSecret); + this._twitter = TNSTwitter.alloc().init(consumerKey, consumerSecret); if (!appDelegateInitialized) { GULAppDelegateSwizzler.proxyOriginalDelegate(); @@ -132,38 +164,79 @@ export class Twitter { export class TwitterSignIn { static logIn() { return new Promise((resolve, reject) => { - TWTRTwitter.sharedInstance().logInWithCompletion((session, error) => { - if (session) { - resolve(Session.fromNative(session)); + Twitter._twitter.authorizeWithBrowser(this.topViewController, Twitter.callback, (user, error) => { + if (user) { + try { + resolve(Session.fromNative(JSON.parse(user))); + } catch (e) { + reject(TwitterError.fromNative(e)); + } } else { reject(TwitterError.fromNative(error)); } - }); + }) }); } - static logOut() { - const store = TWTRTwitter.sharedInstance().sessionStore; - if (store) { - const userId = store.session()?.userID; - store.logOutUserID?.(userId); - } - } + + static logOut() { } static getCurrentUser(): Promise { return new Promise((resolve, reject) => { - const store = TWTRTwitter.sharedInstance().sessionStore; - if (store) { - const userId = store.session()?.userID || ''; - TWTRAPIClient.clientWithCurrentUser().loadUserWithIDCompletion(userId, (user, error) => { - if (error) { - reject(TwitterError.fromNative(error)); - } else { - resolve(TwitterUser.fromNative(user)); + Twitter._twitter.verifyUser(false, false, true, (user, error) => { + if (error) { + reject(TwitterError.fromNative(error)); + } else { + try { + resolve(TwitterUser.fromNative(JSON.parse(user))); + } catch (e) { + reject(TwitterError.fromNative(e)); } - }); - } else { - resolve(null); - } + + } + }) }); } + + + + private static get topViewController(): UIViewController | undefined { + const root = this.rootViewController; + if (!root) { + return undefined; + } + return this.findTopViewController(root); + } + + private static get rootViewController(): UIViewController | undefined { + const keyWindow = UIApplication.sharedApplication.keyWindow; + return keyWindow ? keyWindow.rootViewController : undefined; + } + + private static findTopViewController(root: UIViewController): UIViewController | undefined { + const presented = root.presentedViewController; + if (presented != null) { + return this.findTopViewController(presented); + } + if (root instanceof UISplitViewController) { + const last = root.viewControllers.lastObject; + if (last == null) { + return root; + } + return this.findTopViewController(last); + } else if (root instanceof UINavigationController) { + const top = root.topViewController; + if (top == null) { + return root; + } + return this.findTopViewController(top); + } else if (root instanceof UITabBarController) { + const selected = root.selectedViewController; + if (selected == null) { + return root; + } + return this.findTopViewController(selected); + } else { + return root; + } + } } diff --git a/packages/twitter/package.json b/packages/twitter/package.json index 44b3f34c..e10c821b 100644 --- a/packages/twitter/package.json +++ b/packages/twitter/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/twitter", - "version": "1.0.0-alpha.3", + "version": "1.0.0-alpha.7", "description": "Twitter for your NativeScript applications", "main": "index", "typings": "index.d.ts", diff --git a/packages/twitter/platforms/ios/Podfile b/packages/twitter/platforms/ios/Podfile index 9d5fc5c9..bafb039d 100644 --- a/packages/twitter/platforms/ios/Podfile +++ b/packages/twitter/platforms/ios/Podfile @@ -1,2 +1,3 @@ -pod 'TwitterKit5' +platform :ios, '10.0' +pod 'Swifter', :git => 'https://github.com/mattdonnelly/Swifter.git', :tag => "2.5.0" pod 'GoogleUtilities', '~> 7.5' \ No newline at end of file diff --git a/packages/twitter/platforms/ios/src/TNSTwitter.swift b/packages/twitter/platforms/ios/src/TNSTwitter.swift new file mode 100644 index 00000000..65a73209 --- /dev/null +++ b/packages/twitter/platforms/ios/src/TNSTwitter.swift @@ -0,0 +1,106 @@ +import Foundation +import Swifter +import SafariServices +import AuthenticationServices + + + +struct TNSSession: Codable { + var authToken: String? + var authTokenSecret: String? + var userName: String? + var userId: String? + + init(token: Credential.OAuthAccessToken){ + authToken = token.key + authTokenSecret = token.secret + userName = token.screenName + userId = token.userID + } +} + + + +@objcMembers +@objc(TNSTwitter) +public class TNSTwitter: NSObject { + private var swifter: Swifter + private var controller: UIViewController? + private let encoder = JSONEncoder() + + public static func handleOpenURL(_ url: String,_ callbackURL: String,_ isSSO: Bool) -> Bool { + return Swifter.handleOpenURL(URL(string: url)!, callbackURL: URL(string: callbackURL)!, isSSO: isSSO) + } + + public init?(_ consumerKey: String, _ consumerSecret: String) { + guard !consumerKey.isEmpty || !consumerSecret.isEmpty else {return nil} + swifter = Swifter(consumerKey: consumerKey, consumerSecret: consumerSecret) + } + + + private func handleSession(_ token: Credential.OAuthAccessToken, _ callback: @escaping ((String?, NSError?) -> Void)){ + let session = TNSSession(token: token) + do { + callback(String(data: try self.encoder.encode(session), encoding: .utf8) ?? "", nil) + }catch { + callback(nil, error as NSError) + } + } + + public func verifyUser( + _ includeEntities: Bool, + _ skipStatus: Bool, + _ includeEmail: Bool,_ callback: @escaping ((String?, NSError?) -> Void)) { + swifter.verifyAccountCredentials(includeEntities: includeEntities, skipStatus: skipStatus, includeEmail: includeEmail) { user in + callback(String(describing: user), nil) + } failure: { error in + callback(nil, error as NSError) + } + + } + + public func authorizeWithSSO(_ callback: @escaping ((String?, NSError?) -> Void)){ + swifter.authorizeSSO { token in + self.handleSession(token, callback) + } failure: { error in + callback(nil, error as NSError) + } + } + + public func authorizeWithBrowser(_ controller: UIViewController,_ callbackURL: String, _ callback: @escaping ((String?, NSError?) -> Void)){ + self.controller = controller + if #available(iOS 13.0, *) { + swifter.authorize(withProvider: self, callbackURL: URL(string: callbackURL)!) { token, response in + self.controller = nil + self.handleSession(token!, callback) + } failure: {error in + self.controller = nil + callback(nil, error as NSError) + } + }else { + swifter.authorize(withCallback: URL(string: callbackURL)!, presentingFrom: controller) { token, response in + self.controller = nil + self.handleSession(token!, callback) + } failure: {error in + self.controller = nil + callback(nil, error as NSError) + } + } + } + +} + + +extension TNSTwitter: SFSafariViewControllerDelegate { + @nonobjc func safariViewControllerDidFinish(_ controller: SFSafariViewController) { + controller.dismiss(animated: true, completion: nil) + } +} + + +@available(iOS 13.0, *) +extension TNSTwitter: ASWebAuthenticationPresentationContextProviding { + public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { + return controller!.view.window! + } +} diff --git a/packages/twitter/src-native/TwitterDemo/.idea/compiler.xml b/packages/twitter/src-native/TwitterDemo/.idea/compiler.xml index 61a9130c..fb7f4a8a 100644 --- a/packages/twitter/src-native/TwitterDemo/.idea/compiler.xml +++ b/packages/twitter/src-native/TwitterDemo/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/packages/twitter/src-native/TwitterDemo/.idea/gradle.xml b/packages/twitter/src-native/TwitterDemo/.idea/gradle.xml index 85b7f0a0..0c43e68a 100644 --- a/packages/twitter/src-native/TwitterDemo/.idea/gradle.xml +++ b/packages/twitter/src-native/TwitterDemo/.idea/gradle.xml @@ -7,6 +7,7 @@