Skip to content

Commit

Permalink
Merge pull request #64 from appwrite/dev
Browse files Browse the repository at this point in the history
release: apple
  • Loading branch information
loks0n authored Dec 18, 2024
2 parents f110d17 + 4bad87f commit 72771d3
Show file tree
Hide file tree
Showing 65 changed files with 200 additions and 350 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/autoclose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Auto-close External Pull Requests

on:
pull_request_target:
types: [opened, reopened]

jobs:
auto_close:
uses: appwrite/.github/.github/workflows/autoclose.yml@main
secrets:
GH_AUTO_CLOSE_PR_TOKEN: ${{ secrets.GH_AUTO_CLOSE_PR_TOKEN }}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Add the package to your `Package.swift` dependencies:

```swift
dependencies: [
.package(url: "git@github.com:appwrite/sdk-for-apple.git", from: "7.0.0"),
.package(url: "git@github.com:appwrite/sdk-for-apple.git", from: "7.1.0"),
],
```

Expand Down
16 changes: 8 additions & 8 deletions Sources/Appwrite/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ open class Client {
"x-sdk-name": "Apple",
"x-sdk-platform": "client",
"x-sdk-language": "apple",
"x-sdk-version": "7.0.0",
"x-sdk-version": "7.1.0",
"x-appwrite-response-format": "1.6.0"
]

Expand Down Expand Up @@ -464,23 +464,23 @@ open class Client {
if param is String
|| param is Int
|| param is Float
|| param is Double
|| param is Bool
|| param is [String]
|| param is [Int]
|| param is [Float]
|| param is [Double]
|| param is [Bool]
|| param is [String: Any]
|| param is [Int: Any]
|| param is [Float: Any]
|| param is [Double: Any]
|| param is [Bool: Any] {
encodedParams[key] = param
} else {
let value = try! (param as! Encodable).toJson()

let range = value.index(value.startIndex, offsetBy: 1)..<value.index(value.endIndex, offsetBy: -1)
let substring = value[range]

encodedParams[key] = substring
} else if let encodable = param as? Encodable {
encodedParams[key] = try encodable.toJson()
} else if let param = param {
encodedParams[key] = String(describing: param)
}
}

Expand Down
57 changes: 47 additions & 10 deletions Sources/Appwrite/OAuth/WebAuthComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import AsyncHTTPClient
import Foundation
import NIO

#if canImport(SwiftUI)
import SwiftUI
#if canImport(AuthenticationServices)
import AuthenticationServices
#endif

///
Expand All @@ -13,12 +13,10 @@ import SwiftUI
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, visionOS 1.0, *)
public class WebAuthComponent {

#if canImport(SwiftUI)
@Environment(\.openURL)
private static var openURL
#endif

private static var callbacks = [String: (Result<String, AppwriteError>) -> Void]()
#if canImport(AuthenticationServices)
private static var currentAuthSession: ASWebAuthenticationSession?
#endif

///
/// Authenticate Session with OAuth2
Expand All @@ -41,9 +39,29 @@ public class WebAuthComponent {
) {
callbacks[callbackScheme] = onComplete

#if canImport(SwiftUI)
openURL(url)
#endif
#if canImport(AuthenticationServices)
currentAuthSession = ASWebAuthenticationSession(
url: url,
callbackURLScheme: callbackScheme
) { callbackURL, error in
if let error = error {
cleanUp()
} else if let callbackURL = callbackURL {
// handle cookies here itself!
WebAuthComponent.handleIncomingCookie(from: callbackURL)
cleanUp()
}
}

if let session = currentAuthSession {
/// Indicates that the session should be a private session.
session.prefersEphemeralWebBrowserSession = true
session.presentationContextProvider = PresentationContextProvider.shared
session.start()
} else {
print("Failed to create ASWebAuthenticationSession")
}
#endif
}

///
Expand Down Expand Up @@ -130,5 +148,24 @@ public class WebAuthComponent {
callbacks.forEach { (_, callback) in
callback(.failure(AppwriteError(message: "User cancelled login.")))
}

#if canImport(AuthenticationServices)
currentAuthSession = nil
#endif
}
}

#if canImport(AuthenticationServices)
/// Presentation context for the ASWebAuthenticationSession.
class PresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
static let shared = PresentationContextProvider()

func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
if let mainWindow = OSApplication.shared.windows.first { $0.isKeyWindow } {
return mainWindow
}

return ASPresentationAnchor()
}
}
#endif
42 changes: 25 additions & 17 deletions Sources/Appwrite/Services/Account.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ open class Account: Service {
}

///
/// List Identities
/// List identities
///
/// Get the list of identities for the currently logged in user.
///
Expand Down Expand Up @@ -402,7 +402,7 @@ open class Account: Service {
}

///
/// Create Authenticator
/// Create authenticator
///
/// Add an authenticator app to be used as an MFA factor. Verify the
/// authenticator using the [verify
Expand Down Expand Up @@ -439,7 +439,7 @@ open class Account: Service {
}

///
/// Verify Authenticator
/// Verify authenticator
///
/// Verify an authenticator app after adding it using the [add
/// authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator)
Expand Down Expand Up @@ -480,7 +480,7 @@ open class Account: Service {
}

///
/// Verify Authenticator
/// Verify authenticator
///
/// Verify an authenticator app after adding it using the [add
/// authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator)
Expand All @@ -503,7 +503,7 @@ open class Account: Service {
}

///
/// Delete Authenticator
/// Delete authenticator
///
/// Delete an authenticator for a user by ID.
///
Expand Down Expand Up @@ -531,7 +531,7 @@ open class Account: Service {
}

///
/// Create MFA Challenge
/// Create MFA challenge
///
/// Begin the process of MFA verification after sign-in. Finish the flow with
/// [updateMfaChallenge](/docs/references/cloud/client-web/account#updateMfaChallenge)
Expand Down Expand Up @@ -568,7 +568,7 @@ open class Account: Service {
}

///
/// Create MFA Challenge (confirmation)
/// Create MFA challenge (confirmation)
///
/// Complete the MFA challenge by providing the one-time password. Finish the
/// process of MFA verification by providing the one-time password. To begin
Expand Down Expand Up @@ -604,7 +604,7 @@ open class Account: Service {
}

///
/// List Factors
/// List factors
///
/// List the factors available on the account to be used as a MFA challange.
///
Expand Down Expand Up @@ -635,7 +635,7 @@ open class Account: Service {
}

///
/// Get MFA Recovery Codes
/// Get MFA recovery codes
///
/// Get recovery codes that can be used as backup for MFA flow. Before getting
/// codes, they must be generated using
Expand Down Expand Up @@ -669,7 +669,7 @@ open class Account: Service {
}

///
/// Create MFA Recovery Codes
/// Create MFA recovery codes
///
/// Generate recovery codes as backup for MFA flow. It's recommended to
/// generate and show then immediately after user successfully adds their
Expand Down Expand Up @@ -704,7 +704,7 @@ open class Account: Service {
}

///
/// Regenerate MFA Recovery Codes
/// Regenerate MFA recovery codes
///
/// Regenerate recovery codes that can be used as backup for MFA flow. Before
/// regenerating codes, they must be first generated using
Expand Down Expand Up @@ -1350,12 +1350,16 @@ open class Account: Service {
let callbackScheme = "appwrite-callback-\(client.config["project"] ?? "")"

_ = try await withCheckedThrowingContinuation { continuation in
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
continuation.resume(with: result)
/// main thread for PresentationContextProvider
DispatchQueue.main.async {
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
continuation.resume(with: result)
}
}
}

return true

}

///
Expand Down Expand Up @@ -1849,12 +1853,16 @@ open class Account: Service {
let callbackScheme = "appwrite-callback-\(client.config["project"] ?? "")"

_ = try await withCheckedThrowingContinuation { continuation in
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
continuation.resume(with: result)
/// main thread for PresentationContextProvider
DispatchQueue.main.async {
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
continuation.resume(with: result)
}
}
}

return true

}

///
Expand Down
2 changes: 1 addition & 1 deletion Sources/Appwrite/Services/Locale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ open class Locale: Service {
}

///
/// List Locale Codes
/// List locale codes
///
/// List of all locale codes in [ISO
/// 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).
Expand Down
33 changes: 33 additions & 0 deletions Sources/Appwrite/Services/Realtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ open class Realtime : Service {

private let TYPE_ERROR = "error"
private let TYPE_EVENT = "event"
private let TYPE_PONG = "pong"
private let DEBOUNCE_NANOS = 1_000_000
private let HEARTBEAT_INTERVAL: UInt64 = 20_000_000_000 // 20 seconds in nanoseconds

private var socketClient: WebSocketClient? = nil
private var activeChannels = Set<String>()
private var activeSubscriptions = [Int: RealtimeCallback]()
private var heartbeatTask: Task<Void, Swift.Error>? = nil

let connectSync = DispatchQueue(label: "ConnectSync")

Expand All @@ -20,6 +23,29 @@ open class Realtime : Service {
private var subscriptionsCounter = 0
private var reconnect = true

private func startHeartbeat() {
stopHeartbeat()
heartbeatTask = Task {
do {
while !Task.isCancelled {
if let client = socketClient, client.isConnected {
client.send(text: #"{"type": "ping"}"#)
}
try await Task.sleep(nanoseconds: HEARTBEAT_INTERVAL)
}
} catch {
if !Task.isCancelled {
print("Heartbeat task failed: \(error.localizedDescription)")
}
}
}
}

private func stopHeartbeat() {
heartbeatTask?.cancel()
heartbeatTask = nil
}

private func createSocket() async throws {
guard activeChannels.count > 0 else {
reconnect = false
Expand Down Expand Up @@ -50,6 +76,8 @@ open class Realtime : Service {
}

private func closeSocket() async throws {
stopHeartbeat()

guard let client = socketClient,
let group = client.threadGroup else {
return
Expand Down Expand Up @@ -163,6 +191,7 @@ extension Realtime: WebSocketClientDelegate {

public func onOpen(channel: Channel) {
self.reconnectAttempts = 0
startHeartbeat()
}

public func onMessage(text: String) {
Expand All @@ -172,13 +201,16 @@ extension Realtime: WebSocketClientDelegate {
switch type {
case TYPE_ERROR: try! handleResponseError(from: json)
case TYPE_EVENT: handleResponseEvent(from: json)
case TYPE_PONG: break // Handle pong response if needed
default: break
}
}
}
}

public func onClose(channel: Channel, data: Data) async throws {
stopHeartbeat()

if (!reconnect) {
reconnect = true
return
Expand All @@ -196,6 +228,7 @@ extension Realtime: WebSocketClientDelegate {
}

public func onError(error: Swift.Error?, status: HTTPResponseStatus?) {
stopHeartbeat()
print(error?.localizedDescription ?? "Unknown error")
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Appwrite/Services/Storage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ open class Storage: Service {
}

///
/// Delete File
/// Delete file
///
/// Delete a file by its unique ID. Only users with write permissions have
/// access to delete this resource.
Expand Down
Loading

0 comments on commit 72771d3

Please sign in to comment.