diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..05b8f44
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+.gitattributes eol=lf
+.gitignore eol=lf
+*.hh eol=lf
+*.json eol=lf
+*.plist eol=lf
+*.swift eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1177aee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.sw?
+.build/
+.vscode/
+Examples/FireBaseUI/Resources/google-services-desktop.json
diff --git a/Examples/FireBaseUI/FireBaseUI.exe.manifest b/Examples/FireBaseUI/FireBaseUI.exe.manifest
new file mode 100644
index 0000000..5a4e4f2
--- /dev/null
+++ b/Examples/FireBaseUI/FireBaseUI.exe.manifest
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+ PerMonitorV2
+ true
+
+
+
+ FireBaseUI
+
+
+
+
+
+
+
+
diff --git a/Examples/FireBaseUI/FireBaseUI.swift b/Examples/FireBaseUI/FireBaseUI.swift
new file mode 100644
index 0000000..7a61758
--- /dev/null
+++ b/Examples/FireBaseUI/FireBaseUI.swift
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import SwiftWin32
+import Foundation
+
+import firebase
+import FirebaseCore
+import FirebaseAuth
+
+extension Foundation.Bundle {
+ internal static var resources: URL {
+ Bundle.module.bundleURL.appendingPathComponent("Resources")
+ }
+}
+
+@main
+final class FireBaseUI: ApplicationDelegate {
+ func application(_ application: Application,
+ didFinishLaunchingWithOptions: [Application.LaunchOptionsKey:Any]?)
+ -> Bool {
+#if _runtime(_ObjC)
+ firebase.App.SetDefaultConfigPath(Bundle.resources.fileSystemRepresentation)
+#else
+ Bundle.resources.withUnsafeFileSystemRepresentation(firebase.App.SetDefaultConfigPath)
+#endif
+
+ FirebaseApp.configure()
+ return true
+ }
+}
diff --git a/Examples/FireBaseUI/FireBaseUIViewController.swift b/Examples/FireBaseUI/FireBaseUIViewController.swift
new file mode 100644
index 0000000..34bf22c
--- /dev/null
+++ b/Examples/FireBaseUI/FireBaseUIViewController.swift
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+import FirebaseCore
+import FirebaseAuth
+import SwiftWin32
+
+private final class FireBaseLogLevelPickerHandler {
+ private let levels = ["Default", "Verbose", "Debug", "Info", "Warning", "Error", "Assert"]
+}
+
+extension FireBaseLogLevelPickerHandler: PickerViewDataSource {
+ public func numberOfComponents(in pickerView: PickerView) -> Int {
+ 1
+ }
+
+ public func pickerView(_ pickerView: PickerView,
+ numberOfRowsInComponent component: Int) -> Int {
+ self.levels.count
+ }
+}
+
+extension FireBaseLogLevelPickerHandler: PickerViewDelegate {
+ public func pickerView(_ pickerView: PickerView, titleForRow row: Int,
+ forComponent component: Int) -> String? {
+ self.levels[row]
+ }
+
+ public func pickerView(_ pickerView: PickerView, didSelectRow row: Int,
+ inComponent component: Int) {
+ guard row > 0 else { return }
+ FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel(rawValue: CInt(row - 1)))
+ }
+}
+
+// MARK: - FireBaseUIViewController
+
+internal final class FireBaseUIViewController: ViewController {
+ fileprivate let firebaseLogHandler = FireBaseLogLevelPickerHandler()
+
+ var cboLogLevel = PickerView(frame: Rect(x: 136, y: 8, width: 256, height: 24))
+ var txtEmail = TextField(frame: Rect(x: 136, y: 46, width: 512, height: 24))
+ var txtPassword = TextField(frame: Rect(x: 136, y: 78, width: 512, height: 24))
+ var btnSignIn = Button(frame: Rect(x: 8, y: 116, width: 120, height: 32),
+ title: "Sign In")
+ var btnToken = Button(frame: Rect(x: 8, y: 180, width: 120, height: 32),
+ title: "Get Token")
+ var chkRefresh = Switch(frame: Rect(x: 8, y: 212, width: 648, height: 32),
+ title: "Force Token Refresh")
+ var txtToken: TextView = TextView(frame: Rect(x: 8, y: 244, width: 640, height: 48))
+
+ var btnCreate = Button(frame: Rect(x: 8, y: 324, width: 120, height: 32),
+ title: "Create User")
+ var btnVerify = Button(frame: Rect(x: 132, y: 324, width: 120, height: 32),
+ title: "Verify Email")
+ var btnReset = Button(frame: Rect(x: 256, y: 324, width: 156, height: 32),
+ title: "Reset Password")
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ self.title = "FireBase UI"
+ configureView()
+
+ if let user = Auth.auth().currentUser {
+ txtEmail.text = user.email
+ try? Auth.auth().signOut()
+ }
+ }
+
+ private func configureView() {
+ let lblLogLevel = Label(frame: Rect(x: 8, y: 8, width: 128, height: 24),
+ title: "Log Level:")
+ self.view?.addSubview(lblLogLevel)
+
+ cboLogLevel.dataSource = firebaseLogHandler
+ cboLogLevel.delegate = firebaseLogHandler
+ self.view?.addSubview(cboLogLevel)
+ cboLogLevel.reloadAllComponents()
+ cboLogLevel.selectRow(0, inComponent: 0, animated: false)
+
+ let lblEmail = Label(frame: Rect(x: 8, y: 46, width: 128, height: 20),
+ title: "Email Address:")
+ self.view?.addSubview(lblEmail)
+ self.view?.addSubview(txtEmail)
+
+ let lblPassword = Label(frame: Rect(x: 8, y: 78, width: 128, height: 20),
+ title: "Password:")
+ self.view?.addSubview(lblPassword)
+
+ txtPassword.isSecureTextEntry = true
+ self.view?.addSubview(txtPassword)
+
+ btnSignIn.addTarget(self, action: FireBaseUIViewController.signIn,
+ for: .primaryActionTriggered)
+ self.view?.addSubview(btnSignIn)
+
+ btnToken.addTarget(self, action: FireBaseUIViewController.getToken,
+ for: .primaryActionTriggered)
+ self.view?.addSubview(btnToken)
+
+ self.view?.addSubview(chkRefresh)
+
+ txtToken.editable = false
+ txtToken.font = .systemFont(ofSize: 9)
+ self.view?.addSubview(txtToken)
+
+ btnCreate.addTarget(self, action: FireBaseUIViewController.createUser,
+ for: .primaryActionTriggered)
+ self.view?.addSubview(btnCreate)
+
+ btnVerify.addTarget(self, action: FireBaseUIViewController.verifyEmail,
+ for: .primaryActionTriggered)
+ self.view?.addSubview(btnVerify)
+
+ btnReset.addTarget(self, action: FireBaseUIViewController.resetPassword,
+ for: .primaryActionTriggered)
+ self.view?.addSubview(btnReset)
+ }
+
+ private func signIn() {
+ guard let email = txtEmail.text, let password = txtPassword.text else {
+ print("email and password are required")
+ return
+ }
+
+ Task {
+ do {
+ _ = try await Auth.auth().signIn(withEmail: email, password: password)
+ } catch {
+ print("Error signing in: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ private func createUser() {
+ guard let email = txtEmail.text, let password = txtPassword.text else {
+ print("email and password are required")
+ return
+ }
+
+ Task {
+ do {
+ _ = try await Auth.auth().createUser(withEmail: email, password: password)
+ } catch {
+ print("Error signing in: \(error.localizedDescription)")
+ }
+ }
+ }
+
+ private func getToken() {
+ Task {
+ guard var user = Auth.auth().currentUser else {
+ print("user not logged in")
+ return
+ }
+
+ if chkRefresh.isOn {
+ do {
+ let result = try await user.getIDTokenResult(forcingRefresh: true)
+ txtToken.text = result.token
+ } catch {
+ print("Error refreshing token: \(error.localizedDescription)")
+ }
+ } else {
+ do {
+ txtToken.text = try await user.getIDToken()
+ } catch {
+ print("Error refreshing token: \(error.localizedDescription)")
+ }
+ }
+ }
+ }
+
+ private func verifyEmail() {
+ Task {
+ guard var user = Auth.auth().currentUser else {
+ print("user not logged in")
+ return
+ }
+
+ try await user.sendEmailVerification()
+ }
+ }
+
+ private func resetPassword() {
+ guard let email = txtEmail.text else {
+ print("email is required")
+ return
+ }
+
+ Task {
+ do {
+ _ = try await Auth.auth().sendPasswordReset(withEmail: email)
+ } catch {
+ print("Error sending password reset: \(error.localizedDescription)")
+ }
+ }
+ }
+}
diff --git a/Examples/FireBaseUI/Info.plist b/Examples/FireBaseUI/Info.plist
new file mode 100644
index 0000000..8969ce8
--- /dev/null
+++ b/Examples/FireBaseUI/Info.plist
@@ -0,0 +1,25 @@
+
+
+
+
+ CFBundleIdentifier
+ org.compnerd.SwiftFirebase.FireBaseUI
+ ApplicationSceneManifest
+
+ ApplicationSupportsMultipleScenes
+
+ SceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ SceneConfigurationName
+ Default Configuration
+ SceneDelegateClassName
+ FireBaseUI.SceneDelegate
+
+
+
+
+
+
diff --git a/Examples/FireBaseUI/Resources/google-services.json b/Examples/FireBaseUI/Resources/google-services.json
new file mode 100644
index 0000000..5b4e374
--- /dev/null
+++ b/Examples/FireBaseUI/Resources/google-services.json
@@ -0,0 +1,20 @@
+{
+ "project_info": {
+ "project_id": "test-do-not-use",
+ "firebase_url": "https://test-do-not-use.firebaseio.com",
+ "storage_bucket": "test-do-not-use.firebaseio.com"
+ },
+ "client": [
+ {
+ "api_key": [
+ { "current_key": "000000000000000000000000000000000000000" }
+ ],
+ "client_info": {
+ "mobilesdk_app_id": "1:999999999999:ios:0000000000000000",
+ "android_client_info": {
+ "package_name": "com.firebaseio.test-do-not-use"
+ }
+ }
+ }
+ ]
+}
diff --git a/Examples/FireBaseUI/SceneDelegate.swift b/Examples/FireBaseUI/SceneDelegate.swift
new file mode 100644
index 0000000..de154d9
--- /dev/null
+++ b/Examples/FireBaseUI/SceneDelegate.swift
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import SwiftWin32
+
+final class SceneDelegate: WindowSceneDelegate {
+ var window: Window?
+
+ func scene(_ scene: Scene, willConnectTo session: SceneSession,
+ options: Scene.ConnectionOptions) {
+ guard let windowScene = scene as? WindowScene else { return }
+
+ self.window = Window(windowScene: windowScene)
+ self.window?.rootViewController = FireBaseUIViewController()
+ self.window?.makeKeyAndVisible()
+ }
+}
diff --git a/Examples/FireBaseUI/SwiftWin32+Extensions.swift b/Examples/FireBaseUI/SwiftWin32+Extensions.swift
new file mode 100644
index 0000000..7014400
--- /dev/null
+++ b/Examples/FireBaseUI/SwiftWin32+Extensions.swift
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import SwiftWin32
+
+extension Button {
+ internal convenience init(frame: Rect, title: String) {
+ self.init(frame: frame)
+ self.setTitle(title, forState: .normal)
+ }
+}
+
+extension Label {
+ internal convenience init(frame: Rect, title: String) {
+ self.init(frame: frame)
+ self.text = title
+ }
+}
+
+extension Switch {
+ internal convenience init(frame: Rect, title: String) {
+ self.init(frame: frame)
+ self.title = title
+ }
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fc74ae9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2023, Saleem Abdulrasool
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Package.swift b/Package.swift
new file mode 100644
index 0000000..9610fd9
--- /dev/null
+++ b/Package.swift
@@ -0,0 +1,90 @@
+// swift-tools-version:5.9
+
+import PackageDescription
+
+let SwiftFirebase =
+ Package(name: "SwiftFirebase",
+ platforms: [
+ .macOS(.v13)
+ ],
+ products: [
+ .library(name: "FirebaseCore", targets: ["FirebaseCore"]),
+ .library(name: "FirebaseAuth", targets: ["FirebaseAuth"]),
+ .executable(name: "FireBaseUI", targets: ["FireBaseUI"]),
+ ],
+ dependencies: [
+ .package(url: "https://github.com/compnerd/swift-win32", branch: "main"),
+ ],
+ targets: [
+ .target(name: "firebase",
+ publicHeadersPath: "include",
+ cSettings: [
+ .unsafeFlags([
+ "-iapinotes-modules", "Sources/firebase/include",
+ ]),
+ .headerSearchPath("../../third_party/firebase-development/usr/include"),
+ ],
+ cxxSettings: [
+ .define("__swift__"),
+ .define("INTERNAL_EXPERIMENTAL"),
+ .define("_CRT_SECURE_NO_WARNINGS",
+ .when(platforms: [.windows])),
+ ]),
+ .target(name: "FirebaseCore",
+ dependencies: ["firebase"],
+ cxxSettings: [
+ .define("INTERNAL_EXPERIMENTAL"),
+ .define("_CRT_SECURE_NO_WARNINGS",
+ .when(platforms: [.windows])),
+ .headerSearchPath("../../third_party/firebase-development/usr/include"),
+ ],
+ swiftSettings: [
+ .interoperabilityMode(.Cxx),
+ ]),
+ .target(name: "FirebaseAuth",
+ dependencies: ["firebase", "FirebaseCore"],
+ cxxSettings: [
+ .define("INTERNAL_EXPERIMENTAL"),
+ .define("_CRT_SECURE_NO_WARNINGS",
+ .when(platforms: [.windows])),
+ .headerSearchPath("../../third_party/firebase-development/usr/include"),
+ ],
+ swiftSettings: [
+ .interoperabilityMode(.Cxx),
+ ]),
+ .executableTarget(name: "FireBaseUI",
+ dependencies: [
+ "FirebaseCore",
+ "FirebaseAuth",
+ .product(name: "SwiftWin32", package: "swift-win32"),
+ ],
+ path: "Examples/FireBaseUI",
+ resources: [.copy("Resources")],
+ packageAccess: false,
+ cSettings: [
+ .unsafeFlags([
+ "-iapinotes-modules", "Sources/firebase/include",
+ ]),
+ ],
+ cxxSettings: [
+ .define("INTERNAL_EXPERIMENTAL"),
+ .define("_CRT_SECURE_NO_WARNINGS", .when(platforms: [.windows])),
+ ],
+ swiftSettings: [
+ .interoperabilityMode(.Cxx),
+ .unsafeFlags(["-parse-as-library"])
+ ],
+ linkerSettings: [
+ .unsafeFlags([
+ "-Lthird_party/firebase-development/usr/libs/windows",
+ "-Lthird_party/firebase-development/usr/libs/windows/deps/app",
+ "-Lthird_party/firebase-development/usr/libs/windows/deps/app/external",
+ ]),
+ .linkedLibrary("crypto"),
+ .linkedLibrary("firebase_rest_lib"),
+ .linkedLibrary("flatbuffers"),
+ .linkedLibrary("libcurl"),
+ .linkedLibrary("ssl"),
+ .linkedLibrary("zlibstatic"),
+ ])
+ ])
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8c958d6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# SwiftFirebase
+
+Swift bindings for
+[firebase-cpp-sdk](https://github.com/firebase/firebase-cpp-sdk), loosely
+modelled after the iOS APIs. It serves as both exploration of the C++ Interop
+as well as a means of using Firebase on Windows from Swift.
+
+### Requirements
+
+Requires that the Firebase C++ SDK is installed. You can use the latest binary
+release or build it from source.
+
+The `google-services.json`` file must be updated with the proper values.
+Additionally, the authentication information which is currently hardcoded into
+the demo application must be updated.
+
+### Building
+
+The Swift support requires a couple of workarounds. The changes are available
+in the patches directory. They should be applied to
+[firebase-cpp-sdk](https://github.com/firebase/firebase-cpp-sdk) prior to
+building. For convenience, a build of firebase is expected in the top level
+under `third_party/firebase-development/usr`.
diff --git a/Sources/FirebaseAuth/FIRActionCodeOperation.swift b/Sources/FirebaseAuth/FIRActionCodeOperation.swift
new file mode 100644
index 0000000..5f3e3e6
--- /dev/null
+++ b/Sources/FirebaseAuth/FIRActionCodeOperation.swift
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+public enum ActionCodeOperation: Int {
+ case FIRActionCodeOperationUnknown
+ case FIRActionCodeOperationPasswordReset
+ case FIRActionCodeOperationVerifyEmail
+ case FIRActionCodeOperationRecoverEmail
+ case FIRActionCodeOperationEmailLink
+ case FIRActionCodeOperationVerifyAndChangeEmail
+ case FIRActionCodeOperationRevertSecondFactorAddition
+}
diff --git a/Sources/FirebaseAuth/FIRAuthTokenResult.swift b/Sources/FirebaseAuth/FIRAuthTokenResult.swift
new file mode 100644
index 0000000..6435fbd
--- /dev/null
+++ b/Sources/FirebaseAuth/FIRAuthTokenResult.swift
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+
+import Foundation
+
+public struct AuthTokenResult {
+ public let token: String
+
+ public let expirationDate: Date
+
+ public let authDate: Date
+
+ public let issuedAtDate: Date
+
+ public let signInProvider: String
+
+ public let signInSecondFactor: String
+
+ public let claims: [String:Any]
+
+ internal init(_ token: String) throws {
+ let components = token.components(separatedBy: ".")
+ // The JWT should have three components
+ guard components.count == 3 else {
+ throw NSError(domain: "com.google.firebase.auth",
+ code: AuthError.Code.malformedJWT.rawValue,
+ userInfo: [NSLocalizedDescriptionKey:"Failed to decode token"])
+ }
+
+ var payload = components[1].replacing("_", with: "/")
+ .replacing("-", with: "+")
+ // Pad to 4 character alignment for base64 decoding
+ if payload.count % 4 != 0 {
+ payload.append(String(repeating: "=", count: 4 - payload.count % 4))
+ }
+
+ guard let data = Data(base64Encoded: payload,
+ options: .ignoreUnknownCharacters) else {
+ throw NSError(domain: "com.google.firebase.auth",
+ code: AuthError.Code.malformedJWT.rawValue,
+ userInfo: [NSLocalizedDescriptionKey:"Failed to decode token payload"])
+ }
+
+ let options: JSONSerialization.ReadingOptions =
+ [.mutableContainers, .allowFragments]
+ guard let contents =
+ try? JSONSerialization.jsonObject(with: data, options: options)
+ as? [String:Any] else {
+ throw NSError(domain: "com.google.firebase.auth",
+ code: AuthError.Code.malformedJWT.rawValue,
+ userInfo: [NSLocalizedDescriptionKey:"Failed to deserialise token payload"])
+ }
+
+ // These are dates since 00:00:00 January 1 1970, as described by the
+ // Terminology section in the JWT spec.
+ // https://tools.ietf.org/html/rfc7519
+ guard let authDate = contents["auth_time"] as? TimeInterval,
+ let expirationDate = contents["exp"] as? TimeInterval,
+ let issueDate = contents["iat"] as? TimeInterval else {
+ throw NSError(domain: "com.google.firebase.auth",
+ code: AuthError.Code.malformedJWT.rawValue,
+ userInfo: [NSLocalizedDescriptionKey:"Missing fields in token payload"])
+ }
+
+ self.token = token
+ self.expirationDate = Date(timeIntervalSince1970: expirationDate)
+ self.authDate = Date(timeIntervalSince1970: authDate)
+ self.issuedAtDate = Date(timeIntervalSince1970: issueDate)
+ self.signInProvider = contents["sign_in_provider"] as? String ?? ""
+ self.signInSecondFactor = contents["sign_in_second_factor"] as? String ?? ""
+ self.claims = contents
+ }
+}
diff --git a/Sources/FirebaseAuth/FirebaseAuth+Swift.swift b/Sources/FirebaseAuth/FirebaseAuth+Swift.swift
new file mode 100644
index 0000000..01c42cc
--- /dev/null
+++ b/Sources/FirebaseAuth/FirebaseAuth+Swift.swift
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+import FirebaseCore
+
+import CxxShim
+import Foundation
+
+public typealias Auth = UnsafeMutablePointer
+
+@available(*, unavailable)
+public enum AuthAPNSTokenType: Int {
+ case FIRAuthAPNSTokenTypeUnknown
+ case FIRAuthAPNSTokenTypeSandbox
+ case FIRAuthAPNSTokenTypeProd
+}
+
+extension Auth {
+ public var app: FirebaseApp? {
+ self.pointee.__appUnsafe()
+ }
+
+ public var currentUser: User? {
+ let user = self.pointee.current_user()
+ guard user.is_valid() else { return nil }
+ return user
+ }
+
+ public var languageCode: String? {
+ get {
+ let code = String(self.pointee.language_code())
+ guard !code.isEmpty else { return nil }
+ return String(code)
+ }
+ set { self.pointee.set_language_code(newValue) }
+ }
+
+ // @available(*, unavailable)
+ // public var settings: AuthSettings? { get set }
+
+ // @available(*, unavailable)
+ // public var userAccessGroup: String? { get }
+
+ // @available(*, unavailable)
+ // public var shareAuthStateAcrossDevices: Bool { get set }
+
+ // @available(*, unavailable)
+ // public var tennantID: String? { get set }
+
+ // @available(*, unavailable)
+ // public var apnsToken: Data? { get set }
+
+ public static func auth() -> Auth {
+ // TODO(compnerd) convert this to an exception
+ guard let application = firebase.App.GetInstance() else {
+ fatalError("no default application")
+ }
+ return auth(app: application)
+ }
+
+ public static func auth(app: FirebaseApp) -> Auth {
+ firebase.auth.Auth.GetAuth(app, nil)
+ }
+
+ public func updateCurrentUser(_ user: User) async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func fetchSignInMethods(forEmail email: String) async throws
+ -> [String] {
+ typealias Promise = CheckedContinuation, any Error>
+ let providers = try await withCheckedThrowingContinuation { (continuation: Promise) in
+ let future = self.pointee.FetchProvidersForEmail(email)
+ withUnsafePointer(to: continuation) { continuation in
+ future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
+ let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
+ if future.pointee.error() == 0 {
+ pContinuation.pointee.resume(returning: .init(mutating: future.pointee.__resultUnsafe()))
+ } else {
+ let code = future.pointee.error()
+ let message = String(cString: future.pointee.__error_messageUnsafe()!)
+ pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
+ }
+ }, UnsafeMutableRawPointer(mutating: continuation))
+ }
+ future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
+ }
+ return providers.pointee.providers.compactMap(String.init)
+ }
+
+ public func signIn(withEmail email: String, password: String) async throws
+ -> AuthDataResult {
+ typealias Promise = CheckedContinuation
+ return try await withCheckedThrowingContinuation { (continuation: Promise) in
+ let future = self.pointee.SignInWithEmailAndPassword(email, password)
+ withUnsafePointer(to: continuation) { continuation in
+ future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
+ let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
+ if future.pointee.error() == 0 {
+ pContinuation.pointee.resume(returning: .init(mutating: future.pointee.__resultUnsafe()))
+ } else {
+ let code = future.pointee.error()
+ let message = String(cString: future.pointee.__error_messageUnsafe()!)
+ pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
+ }
+ }, UnsafeMutableRawPointer(mutating: continuation))
+ }
+ future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
+ }
+ }
+
+ public func signIn(withEmail email: String, link: String) async throws
+ -> AuthDataResult {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ // signInWithProvider:UIDelegate:completion:
+
+ // public func signIn(with credential: AuthCredential) async throws
+ // -> AuthDataResult {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public func signInAnonymously() async throws -> AuthDataResult {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func signIn(withCustomToken token: String) async throws
+ -> AuthDataResult {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func createUser(withEmail email: String, password: String) async throws
+ -> AuthDataResult {
+ typealias Promise = CheckedContinuation
+ return try await withCheckedThrowingContinuation { (continuation: Promise) in
+ let future = self.pointee.CreateUserWithEmailAndPassword(email, password)
+ withUnsafePointer(to: continuation) { continuation in
+ future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
+ let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
+ if future.pointee.error() == 0 {
+ pContinuation.pointee.resume(returning: .init(mutating: future.pointee.__resultUnsafe()))
+ } else {
+ let code = future.pointee.error()
+ let message = String(cString: future.pointee.__error_messageUnsafe()!)
+ pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
+ }
+ }, UnsafeMutableRawPointer(mutating: continuation))
+ }
+ future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
+ }
+ }
+
+ public func confirmPasswordReset(withCode code: String,
+ newPassword: String) async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ // public func checkActionCode(_ code: String) async throws -> ActionCodeInfo {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public func verifyPasswordResetCode(_ code: String) async throws -> String {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func applyActionCode(_ code: String) async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func sendPasswordReset(withEmail email: String) async throws {
+ typealias Promise = CheckedContinuation
+ return try await withCheckedThrowingContinuation { (continuation: Promise) in
+ let future = self.pointee.SendPasswordResetEmail(email)
+ withUnsafePointer(to: continuation) { continuation in
+ future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
+ let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
+ if future.pointee.error() == 0 {
+ pContinuation.pointee.resume()
+ } else {
+ let code = future.pointee.error()
+ let message = String(cString: future.pointee.__error_messageUnsafe()!)
+ pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
+ }
+ }, UnsafeMutableRawPointer(mutating: continuation))
+ }
+ future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
+ }
+ }
+
+ // public func sendPasswordReset(withEmail email: String,
+ // actionCodeSettings: ActionCodeSettings) async throws {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // public func sendSignInLink(toEmail email: String,
+ // actionCodeSettings: ActionCodeSettings) async throws {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public func signOut() throws {
+ self.pointee.SignOut()
+ }
+
+ public func isSignIn(withEmailLink link: String) -> Bool {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ // public func addStateDidChangeListener(_ listener: @escaping (Auth, User?) -> Void)
+ // -> AuthStateDidChangeListenerHandle {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // public func removeStateDidChangeListener(_ listenerHandle: AuthStateDidChangeListenerHandle) {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // public func addIDTokenDidChangeListener(_ listener: @escaping (Auth, User?) -> Void)
+ // -> IDTokenDidChangeListenerHandle {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // public func removeIDTokenDidChangeListener(_ listenerHandle: IDTokenDidChangeListenerHandle) {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public func useAppLangauge() {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func useEmulator(withHost host: String, port: Int) {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func canHandle(_ url: URL) -> Bool {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ @available(*, unavailable)
+ public func setAPNSToken(_ token: Data, type: AuthAPNSTokenType) {
+ }
+
+ public func canHandleNotification(_ userInfo: [AnyHashable:Any]) -> Bool {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func revokeToken(withAuthorizationCode authorizationCode: String) async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func useUserAccessGroup(_ accessGroup: String?) throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func getStoredUser(forAccessGroup accessGroup: String?) throws -> User? {
+ fatalError("\(#function) not yet implemented")
+ }
+}
diff --git a/Sources/FirebaseAuth/FirebaseAuthError.swift b/Sources/FirebaseAuth/FirebaseAuthError.swift
new file mode 100644
index 0000000..8979224
--- /dev/null
+++ b/Sources/FirebaseAuth/FirebaseAuthError.swift
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import Foundation
+
+public struct AuthError {
+}
+
+extension AuthError: Error {
+}
+
+extension AuthError {
+ public enum Code: Int {
+ case invalidCustomToken = 17000
+ case customTokenMismatch = 17002
+ case invalidCredential = 17004
+ case userDisabled = 17005
+ case operationNotAllowed = 17006
+ case emailAlreadyInUse = 17007
+ case invalidEmail = 17008
+ case wrongPassword = 17009
+ case tooManyRequests = 17010
+ case userNotFound = 17011
+ case accountExistsWithDifferentCredential = 17012
+ case requiresRecentLogin = 17014
+ case providerAlreadyLinked = 17015
+ case noSuchProvider = 17016
+ case invalidUserToken = 17017
+ case networkError = 17020
+ case userTokenExpired = 17021
+ case invalidAPIKey = 17023
+ case userMismatch = 17024
+ case credentialAlreadyInUse = 17025
+ case weakPassword = 17026
+ case appNotAuthorized = 17028
+ case expiredActionCode = 17029
+ case invalidActionCode = 17030
+ case invalidMessagePayload = 17031
+ case invalidSender = 17032
+ case invalidRecipientEmail = 17033
+ case missingEmail = 17034
+ case missingIosBundleID = 17036
+ case missingAndroidPackageName = 17037
+ case unauthorizedDomain = 17038
+ case invalidContinueURI = 17039
+ case missingContinueURI = 17040
+ case missingPhoneNumber = 17041
+ case invalidPhoneNumber = 17042
+ case missingVerificationCode = 17043
+ case invalidVerificationCode = 17044
+ case missingVerificationID = 17045
+ case invalidVerificationID = 17046
+ case missingAppCredential = 17047
+ case invalidAppCredential = 17048
+ case sessionExpired = 17051
+ case quotaExceeded = 17052
+ case missingAppToken = 17053
+ case notificationNotForwarded = 17054
+ case appNotVerified = 17055
+ case captchaCheckFailed = 17056
+ case webContextAlreadyPresented = 17057
+ case webContextCancelled = 17058
+ case appVerificationUserInteractionFailure = 17059
+ case invalidClientID = 17060
+ case webNetworkRequestFailed = 17061
+ case webInternalError = 17062
+ case webSignInUserInteractionFailure = 17063
+ case localPlayerNotAuthenticated = 17066
+ case nullUser = 17067
+ case dynamicLinkNotActivated = 17068
+ case invalidProviderID = 17071
+ case tenantIDMismatch = 17072
+ case unsupportedTenantOperation = 17073
+ case invalidDynamicLinkDomain = 17074
+ case rejectedCredential = 17075
+ case gameKitNotLinked = 17076
+ case secondFactorRequired = 17078
+ case missingMultiFactorSession = 17081
+ case missingMultiFactorInfo = 17082
+ case invalidMultiFactorSession = 17083
+ case multiFactorInfoNotFound = 17084
+ case adminRestrictedOperation = 17085
+ case unverifiedEmail = 17086
+ case secondFactorAlreadyEnrolled = 17087
+ case maximumSecondFactorCountExceeded = 17088
+ case unsupportedFirstFactor = 17089
+ case emailChangeNeedsVerification = 17090
+ case missingOrInvalidNonce = 17094
+ case blockingCloudFunctionError = 17105
+ case missingClientIdentifier = 17993
+ case keychainError = 17995
+ case internalError = 17999
+ case malformedJWT = 18000
+ }
+}
diff --git a/Sources/FirebaseAuth/FirebaseAuthResult+Swift.swift b/Sources/FirebaseAuth/FirebaseAuthResult+Swift.swift
new file mode 100644
index 0000000..aa8f1c4
--- /dev/null
+++ b/Sources/FirebaseAuth/FirebaseAuthResult+Swift.swift
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+
+public typealias AuthDataResult = UnsafeMutablePointer
+
+extension AuthDataResult {
+ public var user: User? {
+ let user = self.pointee.user
+ guard user.is_valid() else { return nil }
+ return user
+ }
+
+ // public var additionalUserInfo: AdditionalUserInfo? {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // public var credential: AuthCredential? {
+ // fatalError("\(#function) not yet implemented")
+ // }
+}
diff --git a/Sources/FirebaseAuth/FirebaseUser+Swift.swift b/Sources/FirebaseAuth/FirebaseUser+Swift.swift
new file mode 100644
index 0000000..c0a9c9e
--- /dev/null
+++ b/Sources/FirebaseAuth/FirebaseUser+Swift.swift
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import CxxStdlib
+import Foundation
+
+import firebase
+import FirebaseCore
+
+public typealias User = firebase.auth.User
+
+public protocol UserInfo {
+ var providerID: String { get }
+ var uid: String { get }
+ var displayName: String? { get }
+ var photoURL: URL? { get }
+ var email: String? { get }
+ var phoneNumber: String? { get }
+}
+
+extension User {
+ public var isAnonymous: Bool {
+ self.is_anonymous()
+ }
+
+ public var isEmailVerified: Bool {
+ self.is_email_verified()
+ }
+
+ public var refreshToken: String? {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ // public var providerData: [UserInfo] {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // public var metadata: UserMetadata {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public var tenantID: String? {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ // public var multiFactor: MultiFactor {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public func updateEmail(to email: String) async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func updatePassword(to password: String) async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ // public func updatePhoneNumber(_ credential: PhoneAuthCredential) async throws {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // public func createProfileChangeRequest() -> UserProfileChangeRequest {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public mutating func reload() async throws {
+ typealias Promise = CheckedContinuation
+ try await withCheckedThrowingContinuation { (continuation: Promise) in
+ let future = self.Reload()
+ withUnsafePointer(to: continuation) { continuation in
+ future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
+ let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
+ if future.pointee.error() == 0 {
+ pContinuation.pointee.resume()
+ } else {
+ let code = future.pointee.error()
+ let message = String(cString: future.pointee.__error_messageUnsafe()!)
+ pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
+ }
+ }, UnsafeMutableRawPointer(mutating: continuation))
+ }
+ future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
+ }
+ }
+
+ // public mutating func reauthenticate(with credential: AuthCredential) async throws
+ // -> AuthResult {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // -reauthenticateWithProvider:UIDelegate:completion:
+
+ public mutating func getIDTokenResult() async throws -> AuthTokenResult {
+ return try await getIDTokenResult(forcingRefresh: false)
+ }
+
+ public mutating func getIDTokenResult(forcingRefresh forceRefresh: Bool) async throws
+ -> AuthTokenResult {
+ return try await AuthTokenResult(idTokenForcingRefresh(forceRefresh))
+ }
+
+ public mutating func getIDToken() async throws -> String {
+ return try await idTokenForcingRefresh(false)
+ }
+
+ public mutating func idTokenForcingRefresh(_ forceRefresh: Bool) async throws
+ -> String {
+ typealias Promise = CheckedContinuation
+ return try await withCheckedThrowingContinuation { (continuation: Promise) in
+ let future = self.GetToken(forceRefresh)
+ withUnsafePointer(to: continuation) { continuation in
+ future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
+ let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
+ if future.pointee.error() == 0 {
+ pContinuation.pointee.resume(returning: String(future.pointee.__resultUnsafe().pointee))
+ } else {
+ let code = future.pointee.error()
+ let message = String(cString: future.pointee.__error_messageUnsafe()!)
+ pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
+ }
+ }, UnsafeMutableRawPointer(mutating: continuation))
+ }
+ future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
+ }
+ }
+
+ // public func link(with credential: AuthCredential) async throws
+ // -> AuthDataResult {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ // -linkWithProvider:UIDelegate:completion
+
+ public func unlink(from provider: String) async throws -> User {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public mutating func sendEmailVerification() async throws {
+ typealias Promise = CheckedContinuation
+ try await withCheckedThrowingContinuation { (continuation: Promise) in
+ let future = self.SendEmailVerification()
+ withUnsafePointer(to: continuation) { continuation in
+ future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
+ let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
+ if future.pointee.error() == 0 {
+ pContinuation.pointee.resume()
+ } else {
+ let code = future.pointee.error()
+ let message = String(cString: future.pointee.__error_messageUnsafe()!)
+ pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
+ }
+ }, UnsafeMutableRawPointer(mutating: continuation))
+ }
+ future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
+ }
+ }
+
+ // public func sendEmailVerification(with actionCodeSettings: ActionCodeSettings) async throws {
+ // fatalError("\(#function) not yet implemented")
+ // }
+
+ public func delete() async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public func sendEmailVerification(beforeUpdatingEmail email: String) async throws {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ // public func sendEmailVerification(beforeUpdatingEmail email: String,
+ // actionCodeSettings: ActionCodeSettings) async throws {
+ // fatalError("\(#function) not yet implemented")
+ // }
+}
+
+extension User: UserInfo {
+ public var providerID: String {
+ String(swift_firebase.swift_cxx_shims.firebase.auth.user_provider_id(self))
+ }
+
+ public var uid: String {
+ String(swift_firebase.swift_cxx_shims.firebase.auth.user_uid(self))
+ }
+
+ public var displayName: String? {
+ let name = String(swift_firebase.swift_cxx_shims.firebase.auth.user_display_name(self))
+ return name.isEmpty ? nil : name
+ }
+
+ public var photoURL: URL? {
+ let url = String(swift_firebase.swift_cxx_shims.firebase.auth.user_photo_url(self))
+ return url.isEmpty ? nil : URL(string: url)
+ }
+
+ public var email: String? {
+ let email = String(swift_firebase.swift_cxx_shims.firebase.auth.user_email(self))
+ return email.isEmpty ? nil : email
+ }
+
+ public var phoneNumber: String? {
+ let number = String(swift_firebase.swift_cxx_shims.firebase.auth.user_phone_number(self))
+ return number.isEmpty ? nil : number
+ }
+}
diff --git a/Sources/FirebaseCore/FirebaseApp+Swift.swift b/Sources/FirebaseCore/FirebaseApp+Swift.swift
new file mode 100644
index 0000000..1c6fea8
--- /dev/null
+++ b/Sources/FirebaseCore/FirebaseApp+Swift.swift
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+
+public typealias FirebaseApp = UnsafeMutablePointer
+
+extension FirebaseApp {
+ public static func configure() {
+ _ = firebase.App.Create()
+ }
+
+ public static func configure(options: FirebaseOptions) {
+ _ = firebase.App.Create(options.pointee)
+ }
+
+ public static func configure(name: String, options: FirebaseOptions) {
+ _ = firebase.App.Create(options.pointee, name)
+ }
+
+ public static func app() -> FirebaseApp? {
+ firebase.App.GetInstance()
+ }
+
+ public static func app(name: String) -> FirebaseApp? {
+ firebase.App.GetInstance(name)
+ }
+
+ public static var allApps: [String:FirebaseApp]? {
+ let applications = firebase.App.GetApps()
+ guard !applications.isEmpty else { return nil }
+ return .init(uniqueKeysWithValues: applications.compactMap {
+ guard let application = $0 else { return nil }
+ return (application.name, application)
+ })
+ }
+
+ public func delete() async -> Bool {
+ fatalError("\(#function) not yet implemented")
+ }
+
+ public var name: String {
+ String(cString: self.pointee.__nameUnsafe()!)
+ }
+
+ public var options: UnsafePointer {
+ // TODO(compnerd) ensure that the `FirebaseOptions` API is applied to this.
+ self.pointee.__optionsUnsafe()
+ }
+
+ public var isDataCollectionDefaultEnabled: Bool {
+ get { self.pointee.IsDataCollectionDefaultEnabled() }
+ set { self.pointee.SetDataCollectionDefaultEnabled(newValue) }
+ }
+}
diff --git a/Sources/FirebaseCore/FirebaseConfiguration.swift b/Sources/FirebaseCore/FirebaseConfiguration.swift
new file mode 100644
index 0000000..afced5c
--- /dev/null
+++ b/Sources/FirebaseCore/FirebaseConfiguration.swift
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+
+public class FirebaseConfiguration {
+ public static let shared: FirebaseConfiguration = FirebaseConfiguration()
+
+ public func setLoggerLevel(_ loggerLevel: FirebaseLoggerLevel) {
+ firebase.SetLogLevel(loggerLevel)
+ }
+}
diff --git a/Sources/FirebaseCore/FirebaseError.swift b/Sources/FirebaseCore/FirebaseError.swift
new file mode 100644
index 0000000..d759c9e
--- /dev/null
+++ b/Sources/FirebaseCore/FirebaseError.swift
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+public struct FirebaseError: Error {
+ public let code: CInt
+ public let message: String
+
+ package init(code: CInt, message: String) {
+ self.code = code
+ self.message = message
+ }
+}
diff --git a/Sources/FirebaseCore/FirebaseLogging+Swift.swift b/Sources/FirebaseCore/FirebaseLogging+Swift.swift
new file mode 100644
index 0000000..f0aabac
--- /dev/null
+++ b/Sources/FirebaseCore/FirebaseLogging+Swift.swift
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+
+public typealias FirebaseLoggerLevel = firebase.LogLevel
+
+// Workaround #67604 - Unable to import C++ enumerations
+extension firebase.LogLevel {
+ public static var kLogLevelVerbose: Self { .init(0) }
+ public static var kLogLevelDebug: Self { .init(1) }
+ public static var kLogLevelInfo: Self { .init(2) }
+ public static var kLogLevelWarning: Self { .init(3) }
+ public static var kLogLevelError: Self { .init(4) }
+ public static var kLogLevelAssert: Self { .init(5) }
+}
+
+extension firebase.LogLevel {
+ public static var verbose: Self { kLogLevelVerbose }
+ public static var debug: Self { kLogLevelDebug }
+ public static var info: Self { kLogLevelInfo }
+ public static var warning: Self { kLogLevelWarning }
+ public static var error: Self { kLogLevelError }
+ public static var assert: Self { kLogLevelAssert }
+}
+
+extension firebase.LogLevel {
+ public static var min: Self { .error }
+ public static var max: Self { .debug }
+}
+
+extension firebase.LogLevel: CustomStringConvertible {
+ public var description: String {
+ switch self {
+ case .verbose: return "Verbose"
+ case .debug: return "Debug"
+ case .info: return "Info"
+ case .warning: return "Warning"
+ case .error: return "Error"
+ case .assert: return "Assert"
+ default: fatalError("unknown value for firebase.LogLevel")
+ }
+ }
+}
diff --git a/Sources/FirebaseCore/FirebaseOptions+Swift.swift b/Sources/FirebaseCore/FirebaseOptions+Swift.swift
new file mode 100644
index 0000000..549422e
--- /dev/null
+++ b/Sources/FirebaseCore/FirebaseOptions+Swift.swift
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+import firebase
+
+import Foundation
+
+public typealias FirebaseOptions = UnsafeMutablePointer
+
+extension firebase.AppOptions: CustomDebugStringConvertible {
+ public var debugDescription: String {
+ """
+ AppOptions {
+ package_name_: "\(String(cString: self.__package_nameUnsafe()))"
+ api_key_: "\(String(cString: self.__api_keyUnsafe()))"
+ app_id_: "\(String(cString: self.__app_idUnsafe()))"
+ client_id_: "\(String(cString: self.__client_idUnsafe()))"
+ database_url_: "\(String(cString: self.__database_urlUnsafe()))"
+ ga_tracking_id_: "\(String(cString: self.__ga_tracking_idUnsafe()))"
+ fcm_sender_id_: "\(String(cString: self.__messaging_sender_idUnsafe()))"
+ storage_bucket_: "\(String(cString: self.__storage_bucketUnsafe()))"
+ project_id_: "\(String(cString: self.__project_idUnsafe()))"
+ }
+ """
+ }
+}
+
+extension FirebaseOptions {
+ public static func defaultOptions() -> FirebaseOptions? {
+ firebase.AppOptions.LoadDefault(nil)
+ }
+
+ public init?(contentsOfFile plistPath: String) {
+ fatalError("\(#function) NYI")
+ return nil
+ }
+
+ public init(googleAppID: String, gcmSenderID GCMSenderID: String) {
+ self = .allocate(capacity: 1)
+ self.initialize(to: firebase.AppOptions())
+ self.googleAppID = googleAppID
+ self.gcmSenderID = GCMSenderID
+ }
+
+ public var apiKey: String? {
+ get {
+ guard let value = self.pointee.__api_keyUnsafe() else { return nil }
+ return String(cString: value)
+ }
+ set { self.pointee.set_api_key(newValue) }
+ }
+
+ public var bundleID: String {
+ get {
+ guard let value = self.pointee.__package_nameUnsafe() else {
+ return Bundle.main.bundleIdentifier!
+ }
+ return String(cString: value)
+ }
+ set { self.pointee.set_package_name(newValue) }
+ }
+
+ public var clientID: String? {
+ get {
+ guard let value = self.pointee.__client_idUnsafe() else { return nil }
+ return String(cString: value)
+ }
+ set { self.pointee.set_client_id(newValue) }
+ }
+
+ public var trackingID: String? {
+ get {
+ guard let value = self.pointee.__ga_tracking_idUnsafe() else {
+ return nil
+ }
+ return String(cString: value)
+ }
+ set { self.pointee.set_ga_tracking_id(newValue) }
+ }
+
+ public var gcmSenderID: String {
+ get { String(cString: self.pointee.__messaging_sender_idUnsafe()!) }
+ set { self.pointee.set_messaging_sender_id(newValue) }
+ }
+
+ public var projectID: String? {
+ get {
+ guard let value = self.pointee.__project_idUnsafe() else { return nil }
+ return String(cString: value)
+ }
+ set { self.pointee.set_project_id(newValue) }
+ }
+
+ @available(*, unavailable)
+ public var androidClientID: String? { nil }
+
+ public var googleAppID: String? {
+ get {
+ guard let value = self.pointee.__app_idUnsafe() else { return nil }
+ return String(cString: value)
+ }
+ set { self.pointee.set_app_id(newValue) }
+ }
+
+ public var databaseURL: String? {
+ get {
+ guard let value = self.pointee.__database_urlUnsafe() else { return nil }
+ return String(cString: value)
+ }
+ set { self.pointee.set_database_url(newValue) }
+ }
+
+ @available(*, unavailable)
+ public var deepLinkURLScheme: String? { nil }
+
+ public var storageBucket: String? {
+ get {
+ guard let value = self.pointee.__storage_bucketUnsafe() else {
+ return nil
+ }
+ return String(cString: value)
+ }
+ set { self.pointee.set_storage_bucket(newValue) }
+ }
+
+ @available(*, unavailable)
+ public var appGroupId: String? { nil }
+}
diff --git a/Sources/firebase/include/FirebaseApp.hh b/Sources/firebase/include/FirebaseApp.hh
new file mode 100644
index 0000000..59e79c6
--- /dev/null
+++ b/Sources/firebase/include/FirebaseApp.hh
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef firebase_include_FirebaseApp_hh
+#define firebase_include_FirebaseApp_hh
+
+#include
+
+#endif
diff --git a/Sources/firebase/include/FirebaseAuth.hh b/Sources/firebase/include/FirebaseAuth.hh
new file mode 100644
index 0000000..cfaab77
--- /dev/null
+++ b/Sources/firebase/include/FirebaseAuth.hh
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+#ifndef firebase_include_FirebaseAuth_hh
+#define firebase_include_FirebaseAuth_hh
+
+#include
+
+namespace swift_firebase::swift_cxx_shims::firebase::auth {
+inline std::string
+user_display_name(const ::firebase::auth::User &user) noexcept {
+ return user.display_name();
+}
+
+inline std::string user_email(const ::firebase::auth::User &user) noexcept {
+ return user.email();
+}
+
+inline std::string
+user_phone_number(const ::firebase::auth::User &user) noexcept {
+ return user.phone_number();
+}
+
+inline std::string user_photo_url(const ::firebase::auth::User &user) noexcept {
+ return user.photo_url();
+}
+
+inline std::string
+user_provider_id(const ::firebase::auth::User &user) noexcept {
+ return user.provider_id();
+}
+
+inline std::string
+user_uid(const ::firebase::auth::User &user) noexcept {
+ return user.uid();
+}
+}
+
+#endif
+
diff --git a/Sources/firebase/include/FirebaseCore.hh b/Sources/firebase/include/FirebaseCore.hh
new file mode 100644
index 0000000..188d19b
--- /dev/null
+++ b/Sources/firebase/include/FirebaseCore.hh
@@ -0,0 +1,7 @@
+
+#ifndef firebase_include_FirebaseCore_hh
+#define firebase_include_FirebaseCore_hh
+
+#include
+
+#endif
diff --git a/Sources/firebase/include/FirebaseLogging.hh b/Sources/firebase/include/FirebaseLogging.hh
new file mode 100644
index 0000000..a47e81b
--- /dev/null
+++ b/Sources/firebase/include/FirebaseLogging.hh
@@ -0,0 +1,7 @@
+
+#ifndef firebase_include_FirebaseLogging_hh
+#define firebase_include_FirebaseLogging_hh
+
+#include
+
+#endif
diff --git a/Sources/firebase/include/firebase.apinotes b/Sources/firebase/include/firebase.apinotes
new file mode 100644
index 0000000..53d8675
--- /dev/null
+++ b/Sources/firebase/include/firebase.apinotes
@@ -0,0 +1,15 @@
+Name: firebase
+Enumerators:
+ - Name: kLogLevelVerbose
+ SwiftName: verbose
+ - Name: kLogLevelDebug
+ SwiftName: debug
+ - Name: kLogLevelInfo
+ SwiftName: info
+ - Name: kLogLevelWarning
+ SwiftName: warning
+ - Name: kLogLevelError
+ SwiftName: error
+ - Name: kLogLevelAssert
+ SwiftName: assert
+
diff --git a/Sources/firebase/include/module.modulemap b/Sources/firebase/include/module.modulemap
new file mode 100644
index 0000000..abd2c29
--- /dev/null
+++ b/Sources/firebase/include/module.modulemap
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: BSD-3-Clause
+
+module firebase [system] {
+ requires cplusplus
+
+ module app {
+ header "FirebaseApp.hh"
+ export *
+ link "firebase_app"
+ }
+
+ module auth {
+ header "FirebaseAuth.hh"
+ export *
+ link "firebase_auth"
+ }
+
+ module core {
+ header "FirebaseCore.hh"
+ export *
+ }
+
+ module logging {
+ header "FirebaseLogging.hh"
+ export *
+ }
+}
diff --git a/Sources/firebase/shims.cc b/Sources/firebase/shims.cc
new file mode 100644
index 0000000..d7a9f26
--- /dev/null
+++ b/Sources/firebase/shims.cc
@@ -0,0 +1,10 @@
+
+#include
+
+#if defined(_WIN32)
+namespace firebase {
+namespace auth {
+Auth::Auth(const Auth &) noexcept = default;
+}
+}
+#endif
diff --git a/patches/0001-Add-a-couple-of-workarounds-for-Swift-on-Windows.patch b/patches/0001-Add-a-couple-of-workarounds-for-Swift-on-Windows.patch
new file mode 100644
index 0000000..f07a8cb
--- /dev/null
+++ b/patches/0001-Add-a-couple-of-workarounds-for-Swift-on-Windows.patch
@@ -0,0 +1,126 @@
+From 140b2a4d33c50deca8a1ab9284f1897c07a1ce52 Mon Sep 17 00:00:00 2001
+From: Saleem Abdulrasool
+Date: Thu, 3 Aug 2023 21:40:01 -0700
+Subject: [PATCH] Add a couple of workarounds for Swift on Windows
+
+The C++ Interop efforts in Swift currently have some limitations. In
+particular, it cannot support trivial types with non-trivial
+destructors. As a workaround, provide a copy constructor which can be
+used by the Swift interop while using the regular semantics for all
+other cases.
+
+A second issue arises in the handling of futures. Unfortunately, it is
+not currently possible to pass an indirect block parameter which
+prevents the construction of a callback. Workaround this by providing
+an inline shim to use a direct parameter (i.e. indirect value through a
+pointer) which then allows a callback to be formed.
+
+Both of these items are being tracked upstream but seem to be
+potentially sufficient to enable the use of Swift for using the C++ SDK
+for desktop scenarios.
+---
+ app/src/include/firebase/future.h | 15 +++++++++++++++
+ auth/CMakeLists.txt | 1 +
+ auth/src/auth_swift.cc | 26 ++++++++++++++++++++++++++
+ auth/src/include/firebase/auth.h | 7 +++++++
+ 4 files changed, 49 insertions(+)
+ create mode 100644 auth/src/auth_swift.cc
+
+diff --git a/app/src/include/firebase/future.h b/app/src/include/firebase/future.h
+index 0d09fc07..9e325e3d 100644
+--- a/app/src/include/firebase/future.h
++++ b/app/src/include/firebase/future.h
+@@ -407,6 +407,11 @@ class Future : public FutureBase {
+ /// when you set up the callback.
+ typedef void (*TypedCompletionCallback)(const Future& result_data,
+ void* user_data);
++#if defined(__swift__)
++ // TODO(apple/swift#67662) indirect block parameters are unsupported
++ typedef void (*TypedCompletionCallback_SwiftWorkaround)(const Future* result_data,
++ void* user_data);
++#endif
+
+ /// Construct a future.
+ Future() {}
+@@ -464,6 +469,16 @@ class Future : public FutureBase {
+ inline void OnCompletion(TypedCompletionCallback callback,
+ void* user_data) const;
+
++#if defined(__swift__)
++ // TODO(apple/swift#67662) indirect block parameters are unsupported
++ inline void OnCompletion_SwiftWorkaround(TypedCompletionCallback_SwiftWorkaround callback,
++ void *user_data) const {
++ OnCompletion([callback, user_data](const Future& future) {
++ callback(&future, user_data);
++ });
++ }
++#endif
++
+ #if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN)
+ /// Register a single callback that will be called at most once, when the
+ /// future is completed.
+diff --git a/auth/CMakeLists.txt b/auth/CMakeLists.txt
+index 2dc7c4e2..8ee2a964 100644
+--- a/auth/CMakeLists.txt
++++ b/auth/CMakeLists.txt
+@@ -51,6 +51,7 @@ build_flatbuffers("${flatbuffer_schemas}"
+ # Common source files used by all platforms
+ set(common_SRCS
+ src/auth.cc
++ src/auth_swift.cc
+ src/credential.cc
+ src/common.cc
+ src/common.h
+diff --git a/auth/src/auth_swift.cc b/auth/src/auth_swift.cc
+new file mode 100644
+index 00000000..63248c18
+--- /dev/null
++++ b/auth/src/auth_swift.cc
+@@ -0,0 +1,26 @@
++/*
++ * Copyright 2016 Google LLC
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#define __swift__
++#include "auth/src/include/firebase/auth.h"
++
++#if FIREBASE_PLATFORM_WINDOWS
++namespace firebase {
++namespace auth {
++Auth::Auth(const Auth &) noexcept = default;
++}
++}
++#endif
+diff --git a/auth/src/include/firebase/auth.h b/auth/src/include/firebase/auth.h
+index bec3ce8f..71bdaa12 100644
+--- a/auth/src/include/firebase/auth.h
++++ b/auth/src/include/firebase/auth.h
+@@ -147,6 +147,13 @@ class Auth {
+
+ ~Auth();
+
++#if defined(__swift__)
++#if FIREBASE_PLATFORM_WINDOWS
++ // TODO(apple/swift#67288) support trivial C++ types with non-trivial dtors
++ Auth(const Auth &) noexcept;
++#endif
++#endif
++
+ /// Synchronously gets the cached current user, or returns an object where
+ /// is_valid() == false if there is none.
+ ///
+--
+2.39.2.windows.1
+