Skip to content

feat: Apple SDK update for version 17.1.0#111

Closed
ChiragAgg5k wants to merge 6 commits into
mainfrom
dev
Closed

feat: Apple SDK update for version 17.1.0#111
ChiragAgg5k wants to merge 6 commits into
mainfrom
dev

Conversation

@ChiragAgg5k
Copy link
Copy Markdown
Member

@ChiragAgg5k ChiragAgg5k commented May 7, 2026

This PR contains updates to the SDK for version 17.1.0.

What's Changed

  • Added: Added setCookie() method to Client for forwarding incoming Cookie headers in server-side runtimes
  • Added: Added setCompression() method to Client to toggle automatic response decompression
  • Added: Added fusionauth, keycloak, and kick OAuth providers to OAuthProvider enum
  • Updated: Updated X-Appwrite-Response-Format header to 1.9.4

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR updates the Apple SDK to version 17.1.0, adding setCookie() and setCompression() to Client, three new OAuth providers (fusionauth, keycloak, kick), a userPhone field on Membership, and a significant Realtime refactor that moves to client-generated subscription IDs with WebSocket subscribe/unsubscribe messages instead of URL-encoded channels.

  • Realtime overhaul: subscriptions now use ID.unique() client-side IDs; RealtimeSubscription gains explicit unsubscribe(), update(), and close() methods; print() calls replaced with stderr diagnostics.
  • Converter signatures: all service converter closures widened from (Any) -> T to (Any) throws -> T, enabling proper error propagation through the call chain.
  • Model & enum additions: Membership.userPhone and three new OAuth providers added to track server API 1.9.4.

Confidence Score: 3/5

The Realtime reconnect path has a silent failure mode that can leave all subscriptions undelivered after a disconnect.

The handleResponseConnected guard exits early without re-subscribing channels if the server's connected message arrives with a non-dictionary data field. Because this is the only place active subscriptions are re-registered on reconnect, the failure is completely silent. The Membership.userPhone force-cast is a secondary concern that follows the existing pattern.

Sources/Appwrite/Services/Realtime.swift — specifically handleResponseConnected; Sources/AppwriteModels/Membership.swift — the userPhone force-cast in from().

Important Files Changed

Filename Overview
Sources/Appwrite/Services/Realtime.swift Major protocol refactor: subscriptions now use client-generated IDs and WebSocket messages; reconnect re-subscription guard condition is too strict and can silently drop all subscriptions
Sources/Appwrite/Client.swift Adds setCookie() and setCompression() methods; changes converter closures to throwing; compression header tracking logic looks correct
Sources/Appwrite/Models/RealtimeModels.swift Adds RealtimeSubscriptionUpdate and refactors RealtimeSubscription with separate unsubscribe/update/close action closures; straightforward model change
Sources/AppwriteModels/Membership.swift Adds non-optional userPhone field with force-cast in from() — can crash if server omits the field when privacy is toggled
Sources/AppwriteEnums/OAuthProvider.swift Adds fusionauth, keycloak, and kick OAuth providers in alphabetical order; clean addition
Sources/Appwrite/Services/Account.swift Converter closures updated to (Any) throws -> T signature across all methods; no logic changes

Reviews (1): Last reviewed commit: "chore: update Apple SDK to 17.1.0" | Re-trigger Greptile

Comment on lines 351 to +360
private func handleResponseConnected(from json: [String: Any]) {
guard let data = json["data"] as? [String: Any],
let subscriptions = data["subscriptions"] as? [String: String] else {
guard json["data"] is [String: Any] else {
return
}

// Store subscription ID mappings from backend
// Format: { "0": "sub_a1f9", "1": "sub_b83c", ... }
slotToSubscriptionId.removeAll()
subscriptionIdToSlot.removeAll()
for (slotStr, subscriptionId) in subscriptions {
if let slot = Int(slotStr) {
slotToSubscriptionId[slot] = subscriptionId
subscriptionIdToSlot[subscriptionId] = slot
}

for subscriptionId in activeSubscriptions.keys {
enqueuePendingSubscribe(subscriptionId: subscriptionId)
}
sendPendingSubscribes()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Guard condition silently blocks reconnect re-subscription

The guard json["data"] is [String: Any] was carried over from the old protocol, but the new implementation never reads the content of data — it's only checking the type. If the server ever sends a connected message where data is absent, null, or any non-dictionary type (e.g., a protocol version bump), the guard exits early and sendPendingSubscribes() is never called. All active subscriptions would then be silently lost for the lifetime of that reconnected socket — callers continue running with no events delivered and no error surfaced.

Comment on lines +111 to +113
private func generateUniqueSubscriptionId() throws -> String {
let attempts = activeSubscriptions.count + 1
for _ in 0..<attempts {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 generateUniqueSubscriptionId loop bound is unexpectedly tight

The loop runs at most activeSubscriptions.count + 1 times. With 0 active subscriptions that's a single attempt; with any collision (however improbable with UUIDs) the function throws and the subscribe call propagates an error to the caller. The bound should be at least a fixed minimum (e.g., 10) so a freshly-created Realtime instance always has more than one chance.

Suggested change
private func generateUniqueSubscriptionId() throws -> String {
let attempts = activeSubscriptions.count + 1
for _ in 0..<attempts {
private func generateUniqueSubscriptionId() throws -> String {
let attempts = max(activeSubscriptions.count + 1, 10)
for _ in 0..<attempts {

userId: map["userId"] as! String,
userName: map["userName"] as! String,
userEmail: map["userEmail"] as! String,
userPhone: map["userPhone"] as! String,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Non-optional userPhone force-cast may crash when privacy is toggled

The field is documented as "Hide this attribute by toggling membership privacy in the Console." If the server omits or returns null for userPhone when privacy is enabled, map["userPhone"] as! String will produce a runtime crash. The existing userEmail field follows the same pattern, but adding another non-optional force-cast field that can be hidden amplifies the risk. Consider decoding as String? (or at least using as? String ?? "") and making the property optional to match the server's contract when privacy is on.

@ChiragAgg5k
Copy link
Copy Markdown
Member Author

Closing — changes not substantial enough to warrant a release.

@ChiragAgg5k ChiragAgg5k closed this May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants