Skip to content

Twitter sign-in flow #1242

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public protocol PhoneAuthProviderProtocol {
@MainActor func verifyPhoneNumber(phoneNumber: String) async throws -> String
}

public protocol TwitterProviderProtocol {
@MainActor func signInWithTwitter() async throws -> AuthCredential
}

public enum AuthenticationState {
case unauthenticated
case authenticating
Expand Down Expand Up @@ -63,12 +67,14 @@ public final class AuthService {
public init(configuration: AuthConfiguration = AuthConfiguration(), auth: Auth = Auth.auth(),
googleProvider: GoogleProviderProtocol? = nil,
facebookProvider: FacebookProviderProtocol? = nil,
phoneAuthProvider: PhoneAuthProviderProtocol? = nil) {
phoneAuthProvider: PhoneAuthProviderProtocol? = nil,
twitterProvider: TwitterProviderProtocol? = nil) {
self.auth = auth
self.configuration = configuration
self.googleProvider = googleProvider
self.facebookProvider = facebookProvider
self.phoneAuthProvider = phoneAuthProvider
self.twitterProvider = twitterProvider
string = StringUtils(bundle: configuration.customStringsBundle ?? Bundle.module)
listenerManager = AuthListenerManager(auth: auth, authEnvironment: self)
}
Expand All @@ -88,6 +94,7 @@ public final class AuthService {
private let googleProvider: GoogleProviderProtocol?
private let facebookProvider: FacebookProviderProtocol?
private let phoneAuthProvider: PhoneAuthProviderProtocol?
private let twitterProvider: TwitterProviderProtocol?

private var safeGoogleProvider: GoogleProviderProtocol {
get throws {
Expand Down Expand Up @@ -119,6 +126,16 @@ public final class AuthService {
}
}

private var safeTwitterProvider: TwitterProviderProtocol {
get throws {
guard let provider = twitterProvider else {
throw AuthServiceError
.notConfiguredProvider("`TwitterProviderSwift` has not been configured")
}
return provider
}
}

private func safeActionCodeSettings() throws -> ActionCodeSettings {
// email sign-in requires action code settings
guard let actionCodeSettings = configuration
Expand Down Expand Up @@ -318,6 +335,16 @@ public extension AuthService {
}
}

// MARK: - Twitter Sign In

public extension AuthService {
func signInWithTwitter() async throws {
let credential = try await safeTwitterProvider
.signInWithTwitter()
try await signIn(credentials: credential)
}
}

// MARK: - Phone Auth Sign In

public extension AuthService {
Expand Down
8 changes: 8 additions & 0 deletions FirebaseSwiftUI/FirebaseTwitterSwiftUI/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import FirebaseAuth
import FirebaseAuthSwiftUI

public class TwitterProviderSwift: TwitterProviderProtocol {
let scopes: [String]
let shortName = "Twitter"
let providerId = "twitter.com"

public init(scopes: [String]? = nil) {
self.scopes = scopes ?? ["user.readwrite"]
}

@MainActor public func signInWithTwitter() async throws -> AuthCredential {
let provider = OAuthProvider(providerID: providerId)
return try await withCheckedThrowingContinuation { continuation in
provider.getCredentialWith(nil) { credential, error in
if let error {
continuation
.resume(throwing: AuthServiceError.signInFailed(underlying: error))
} else if let credential {
continuation.resume(returning: credential)
} else {
continuation
.resume(throwing: AuthServiceError
.invalidCredentials("Twitter did not provide a valid AuthCredential"))
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

import FirebaseAuthSwiftUI
import SwiftUI

@MainActor
public struct SignInWithTwitterButton {
@Environment(AuthService.self) private var authService

public init() {}

private func signInWithTwitter() async {
do {
try await authService.signInWithTwitter()
} catch {}
}
}

extension SignInWithTwitterButton: View {
public var body: some View {
Button(action: {
Task {
try await signInWithTwitter()
}
}) {
if authService.authenticationState != .authenticating {
HStack {
Image("ic_twitter_black", bundle: .module)
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.padding(.leading, 8)

Text(authService.authenticationFlow == .login
? "Login with Twitter"
: "Sign up with Twitter")
.foregroundColor(.white)
.fontWeight(.semibold)
.padding(.vertical, 10)
.frame(maxWidth: .infinity)
}
.background(Color(red: 29 / 255, green: 161 / 255, blue: 242 / 255))
.cornerRadius(8)
.accessibilityLabel("Sign in with Twitter")
} else {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@testable import FirebaseTwitterSwiftUI
import Testing

@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
19 changes: 19 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ let package = Package(
name: "FirebasePhoneAuthSwiftUI",
targets: ["FirebasePhoneAuthSwiftUI"]
),
.library(
name: "FirebaseTwitterSwiftUI",
targets: ["FirebaseTwitterSwiftUI"]
),
],
dependencies: [
.package(
Expand Down Expand Up @@ -311,5 +315,20 @@ let package = Package(
dependencies: ["FirebasePhoneAuthSwiftUI"],
path: "FirebaseSwiftUI/FirebasePhoneAuthSwiftUI/Tests/"
),
.target(
name: "FirebaseTwitterSwiftUI",
dependencies: [
"FirebaseAuthSwiftUI",
],
path: "FirebaseSwiftUI/FirebaseTwitterSwiftUI/Sources",
resources: [
.process("Assets"),
]
),
.testTarget(
name: "FirebaseTwitterSwiftUITests",
dependencies: ["FirebaseTwitterSwiftUI"],
path: "FirebaseSwiftUI/FirebaseTwitterSwiftUI/Tests/"
),
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
4607CC9C2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4607CC9B2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI */; };
4607CC9E2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4607CC9D2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI */; };
4670DEA72D9EA9E100E0D36A /* FirebaseFacebookSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4670DEA62D9EA9E100E0D36A /* FirebaseFacebookSwiftUI */; };
4674FC322DB12C5400299796 /* FirebaseTwitterSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4674FC312DB12C5400299796 /* FirebaseTwitterSwiftUI */; };
46C4EAB32DA801B200FC878B /* FirebasePhoneAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 46C4EAB22DA801B200FC878B /* FirebasePhoneAuthSwiftUI */; };
46CB7B252D773F2100F1FD0A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 46CB7B242D773F2100F1FD0A /* GoogleService-Info.plist */; };
46F89C392D64B04E000F8BC0 /* FirebaseAuthSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 46F89C382D64B04E000F8BC0 /* FirebaseAuthSwiftUI */; };
Expand Down Expand Up @@ -81,6 +82,7 @@
46F89C4D2D64BB9B000F8BC0 /* FirebaseAuthSwiftUI in Frameworks */,
4607CC9E2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI in Frameworks */,
46C4EAB32DA801B200FC878B /* FirebasePhoneAuthSwiftUI in Frameworks */,
4674FC322DB12C5400299796 /* FirebaseTwitterSwiftUI in Frameworks */,
4607CC9C2D9BFE29009EC3F5 /* FirebaseAuthSwiftUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -157,6 +159,7 @@
4607CC9D2D9BFE29009EC3F5 /* FirebaseGoogleSwiftUI */,
4670DEA62D9EA9E100E0D36A /* FirebaseFacebookSwiftUI */,
46C4EAB22DA801B200FC878B /* FirebasePhoneAuthSwiftUI */,
4674FC312DB12C5400299796 /* FirebaseTwitterSwiftUI */,
);
productName = FirebaseSwiftUIExample;
productReference = 46F89C082D64A86C000F8BC0 /* FirebaseSwiftUIExample.app */;
Expand Down Expand Up @@ -637,6 +640,11 @@
package = 4607CC9A2D9BFE29009EC3F5 /* XCLocalSwiftPackageReference "../../../../firebaseUI-ios" */;
productName = FirebaseFacebookSwiftUI;
};
4674FC312DB12C5400299796 /* FirebaseTwitterSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
package = 4607CC9A2D9BFE29009EC3F5 /* XCLocalSwiftPackageReference "../../../../firebaseUI-ios" */;
productName = FirebaseTwitterSwiftUI;
};
46C4EAB22DA801B200FC878B /* FirebasePhoneAuthSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
package = 4607CC9A2D9BFE29009EC3F5 /* XCLocalSwiftPackageReference "../../../../firebaseUI-ios" */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import FirebaseCore
import FirebaseFacebookSwiftUI
import FirebaseGoogleSwiftUI
import FirebasePhoneAuthSwiftUI
import FirebaseTwitterSwiftUI
import SwiftData
import SwiftUI

Expand Down Expand Up @@ -92,18 +93,21 @@ struct ContentView: View {
)
let facebookProvider = FacebookProviderSwift()
let phoneAuthProvider = PhoneAuthProviderSwift()
let twitterProvider = TwitterProviderSwift()
authService = AuthService(
configuration: configuration,
googleProvider: googleProvider,
facebookProvider: facebookProvider,
phoneAuthProvider: phoneAuthProvider
phoneAuthProvider: phoneAuthProvider,
twitterProvider: twitterProvider
)
}

var body: some View {
AuthPickerView {
SignInWithGoogleButton()
SignInWithFacebookButton()
SignInWithTwitterButton()
PhoneAuthButtonView()
}.environment(authService)
}
Expand Down