Skip to content

Commit 72771d3

Browse files
authored
Merge pull request #64 from appwrite/dev
release: apple
2 parents f110d17 + 4bad87f commit 72771d3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+200
-350
lines changed

.github/workflows/autoclose.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
name: Auto-close External Pull Requests
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, reopened]
6+
7+
jobs:
8+
auto_close:
9+
uses: appwrite/.github/.github/workflows/autoclose.yml@main
10+
secrets:
11+
GH_AUTO_CLOSE_PR_TOKEN: ${{ secrets.GH_AUTO_CLOSE_PR_TOKEN }}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Add the package to your `Package.swift` dependencies:
3131

3232
```swift
3333
dependencies: [
34-
.package(url: "[email protected]:appwrite/sdk-for-apple.git", from: "7.0.0"),
34+
.package(url: "[email protected]:appwrite/sdk-for-apple.git", from: "7.1.0"),
3535
],
3636
```
3737

Sources/Appwrite/Client.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ open class Client {
2323
"x-sdk-name": "Apple",
2424
"x-sdk-platform": "client",
2525
"x-sdk-language": "apple",
26-
"x-sdk-version": "7.0.0",
26+
"x-sdk-version": "7.1.0",
2727
"x-appwrite-response-format": "1.6.0"
2828
]
2929

@@ -464,23 +464,23 @@ open class Client {
464464
if param is String
465465
|| param is Int
466466
|| param is Float
467+
|| param is Double
467468
|| param is Bool
468469
|| param is [String]
469470
|| param is [Int]
470471
|| param is [Float]
472+
|| param is [Double]
471473
|| param is [Bool]
472474
|| param is [String: Any]
473475
|| param is [Int: Any]
474476
|| param is [Float: Any]
477+
|| param is [Double: Any]
475478
|| param is [Bool: Any] {
476479
encodedParams[key] = param
477-
} else {
478-
let value = try! (param as! Encodable).toJson()
479-
480-
let range = value.index(value.startIndex, offsetBy: 1)..<value.index(value.endIndex, offsetBy: -1)
481-
let substring = value[range]
482-
483-
encodedParams[key] = substring
480+
} else if let encodable = param as? Encodable {
481+
encodedParams[key] = try encodable.toJson()
482+
} else if let param = param {
483+
encodedParams[key] = String(describing: param)
484484
}
485485
}
486486

Sources/Appwrite/OAuth/WebAuthComponent.swift

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import AsyncHTTPClient
22
import Foundation
33
import NIO
44

5-
#if canImport(SwiftUI)
6-
import SwiftUI
5+
#if canImport(AuthenticationServices)
6+
import AuthenticationServices
77
#endif
88

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

16-
#if canImport(SwiftUI)
17-
@Environment(\.openURL)
18-
private static var openURL
19-
#endif
20-
2116
private static var callbacks = [String: (Result<String, AppwriteError>) -> Void]()
17+
#if canImport(AuthenticationServices)
18+
private static var currentAuthSession: ASWebAuthenticationSession?
19+
#endif
2220

2321
///
2422
/// Authenticate Session with OAuth2
@@ -41,9 +39,29 @@ public class WebAuthComponent {
4139
) {
4240
callbacks[callbackScheme] = onComplete
4341

44-
#if canImport(SwiftUI)
45-
openURL(url)
46-
#endif
42+
#if canImport(AuthenticationServices)
43+
currentAuthSession = ASWebAuthenticationSession(
44+
url: url,
45+
callbackURLScheme: callbackScheme
46+
) { callbackURL, error in
47+
if let error = error {
48+
cleanUp()
49+
} else if let callbackURL = callbackURL {
50+
// handle cookies here itself!
51+
WebAuthComponent.handleIncomingCookie(from: callbackURL)
52+
cleanUp()
53+
}
54+
}
55+
56+
if let session = currentAuthSession {
57+
/// Indicates that the session should be a private session.
58+
session.prefersEphemeralWebBrowserSession = true
59+
session.presentationContextProvider = PresentationContextProvider.shared
60+
session.start()
61+
} else {
62+
print("Failed to create ASWebAuthenticationSession")
63+
}
64+
#endif
4765
}
4866

4967
///
@@ -130,5 +148,24 @@ public class WebAuthComponent {
130148
callbacks.forEach { (_, callback) in
131149
callback(.failure(AppwriteError(message: "User cancelled login.")))
132150
}
151+
152+
#if canImport(AuthenticationServices)
153+
currentAuthSession = nil
154+
#endif
155+
}
156+
}
157+
158+
#if canImport(AuthenticationServices)
159+
/// Presentation context for the ASWebAuthenticationSession.
160+
class PresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
161+
static let shared = PresentationContextProvider()
162+
163+
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
164+
if let mainWindow = OSApplication.shared.windows.first { $0.isKeyWindow } {
165+
return mainWindow
166+
}
167+
168+
return ASPresentationAnchor()
133169
}
134170
}
171+
#endif

Sources/Appwrite/Services/Account.swift

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ open class Account: Service {
214214
}
215215

216216
///
217-
/// List Identities
217+
/// List identities
218218
///
219219
/// Get the list of identities for the currently logged in user.
220220
///
@@ -402,7 +402,7 @@ open class Account: Service {
402402
}
403403

404404
///
405-
/// Create Authenticator
405+
/// Create authenticator
406406
///
407407
/// Add an authenticator app to be used as an MFA factor. Verify the
408408
/// authenticator using the [verify
@@ -439,7 +439,7 @@ open class Account: Service {
439439
}
440440

441441
///
442-
/// Verify Authenticator
442+
/// Verify authenticator
443443
///
444444
/// Verify an authenticator app after adding it using the [add
445445
/// authenticator](/docs/references/cloud/client-web/account#createMfaAuthenticator)
@@ -480,7 +480,7 @@ open class Account: Service {
480480
}
481481

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

505505
///
506-
/// Delete Authenticator
506+
/// Delete authenticator
507507
///
508508
/// Delete an authenticator for a user by ID.
509509
///
@@ -531,7 +531,7 @@ open class Account: Service {
531531
}
532532

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

570570
///
571-
/// Create MFA Challenge (confirmation)
571+
/// Create MFA challenge (confirmation)
572572
///
573573
/// Complete the MFA challenge by providing the one-time password. Finish the
574574
/// process of MFA verification by providing the one-time password. To begin
@@ -604,7 +604,7 @@ open class Account: Service {
604604
}
605605

606606
///
607-
/// List Factors
607+
/// List factors
608608
///
609609
/// List the factors available on the account to be used as a MFA challange.
610610
///
@@ -635,7 +635,7 @@ open class Account: Service {
635635
}
636636

637637
///
638-
/// Get MFA Recovery Codes
638+
/// Get MFA recovery codes
639639
///
640640
/// Get recovery codes that can be used as backup for MFA flow. Before getting
641641
/// codes, they must be generated using
@@ -669,7 +669,7 @@ open class Account: Service {
669669
}
670670

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

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

13521352
_ = try await withCheckedThrowingContinuation { continuation in
1353-
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
1354-
continuation.resume(with: result)
1353+
/// main thread for PresentationContextProvider
1354+
DispatchQueue.main.async {
1355+
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
1356+
continuation.resume(with: result)
1357+
}
13551358
}
13561359
}
1357-
1360+
13581361
return true
1362+
13591363
}
13601364

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

18511855
_ = try await withCheckedThrowingContinuation { continuation in
1852-
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
1853-
continuation.resume(with: result)
1856+
/// main thread for PresentationContextProvider
1857+
DispatchQueue.main.async {
1858+
WebAuthComponent.authenticate(url: url, callbackScheme: callbackScheme) { result in
1859+
continuation.resume(with: result)
1860+
}
18541861
}
18551862
}
1856-
1863+
18571864
return true
1865+
18581866
}
18591867

18601868
///

Sources/Appwrite/Services/Locale.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ open class Locale: Service {
4545
}
4646

4747
///
48-
/// List Locale Codes
48+
/// List locale codes
4949
///
5050
/// List of all locale codes in [ISO
5151
/// 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).

Sources/Appwrite/Services/Realtime.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ open class Realtime : Service {
77

88
private let TYPE_ERROR = "error"
99
private let TYPE_EVENT = "event"
10+
private let TYPE_PONG = "pong"
1011
private let DEBOUNCE_NANOS = 1_000_000
12+
private let HEARTBEAT_INTERVAL: UInt64 = 20_000_000_000 // 20 seconds in nanoseconds
1113

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

1619
let connectSync = DispatchQueue(label: "ConnectSync")
1720

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

26+
private func startHeartbeat() {
27+
stopHeartbeat()
28+
heartbeatTask = Task {
29+
do {
30+
while !Task.isCancelled {
31+
if let client = socketClient, client.isConnected {
32+
client.send(text: #"{"type": "ping"}"#)
33+
}
34+
try await Task.sleep(nanoseconds: HEARTBEAT_INTERVAL)
35+
}
36+
} catch {
37+
if !Task.isCancelled {
38+
print("Heartbeat task failed: \(error.localizedDescription)")
39+
}
40+
}
41+
}
42+
}
43+
44+
private func stopHeartbeat() {
45+
heartbeatTask?.cancel()
46+
heartbeatTask = nil
47+
}
48+
2349
private func createSocket() async throws {
2450
guard activeChannels.count > 0 else {
2551
reconnect = false
@@ -50,6 +76,8 @@ open class Realtime : Service {
5076
}
5177

5278
private func closeSocket() async throws {
79+
stopHeartbeat()
80+
5381
guard let client = socketClient,
5482
let group = client.threadGroup else {
5583
return
@@ -163,6 +191,7 @@ extension Realtime: WebSocketClientDelegate {
163191

164192
public func onOpen(channel: Channel) {
165193
self.reconnectAttempts = 0
194+
startHeartbeat()
166195
}
167196

168197
public func onMessage(text: String) {
@@ -172,13 +201,16 @@ extension Realtime: WebSocketClientDelegate {
172201
switch type {
173202
case TYPE_ERROR: try! handleResponseError(from: json)
174203
case TYPE_EVENT: handleResponseEvent(from: json)
204+
case TYPE_PONG: break // Handle pong response if needed
175205
default: break
176206
}
177207
}
178208
}
179209
}
180210

181211
public func onClose(channel: Channel, data: Data) async throws {
212+
stopHeartbeat()
213+
182214
if (!reconnect) {
183215
reconnect = true
184216
return
@@ -196,6 +228,7 @@ extension Realtime: WebSocketClientDelegate {
196228
}
197229

198230
public func onError(error: Swift.Error?, status: HTTPResponseStatus?) {
231+
stopHeartbeat()
199232
print(error?.localizedDescription ?? "Unknown error")
200233
}
201234

Sources/Appwrite/Services/Storage.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ open class Storage: Service {
200200
}
201201

202202
///
203-
/// Delete File
203+
/// Delete file
204204
///
205205
/// Delete a file by its unique ID. Only users with write permissions have
206206
/// access to delete this resource.

0 commit comments

Comments
 (0)