mirror of
https://github.com/appwrite/sdk-for-apple.git
synced 2026-04-07 19:17:50 +00:00
Merge pull request #108 from appwrite/dev
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
# Change Log
|
||||
|
||||
## 16.0.0
|
||||
|
||||
* [BREAKING] Changed `$sequence` type from `Int` to `String` for `Row` and `Document` models
|
||||
* Added impersonation support: `setImpersonateUserId()`, `setImpersonateUserEmail()`, `setImpersonateUserPhone()` on `Client`
|
||||
* Added `impersonator` and `impersonatorUserId` optional fields to `User` model
|
||||
* Updated `Log` model field descriptions to clarify impersonation behavior for `userId`, `userEmail`, `userName`
|
||||
* Fixed `NIOFoundationCompat` import to be conditional with `#if canImport` for platform compatibility
|
||||
* Fixed `ByteBuffer` to `Data` conversion to use `readableBytesView` throughout (Client and WebSocket handler)
|
||||
* Fixed `ByteBuffer(data:)` calls replaced with `ByteBuffer(bytes:)` for file/data loading
|
||||
* Updated `X-Appwrite-Response-Format` header to `1.9.0`
|
||||
* Updated API version badge to `1.9.0` and compatibility note to server version `1.9.x` in README
|
||||
|
||||
## 15.0.0
|
||||
|
||||
* Breaking: RealtimeChannel API required explicit IDs and threw errors.
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://travis-ci.com/appwrite/sdk-generator)
|
||||
[](https://twitter.com/appwrite)
|
||||
[](https://appwrite.io/discord)
|
||||
|
||||
**This SDK is compatible with Appwrite server version latest. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-apple/releases).**
|
||||
**This SDK is compatible with Appwrite server version 1.9.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-apple/releases).**
|
||||
|
||||
Appwrite is an open-source backend as a service server that abstracts and simplifies complex and repetitive development tasks behind a very simple to use REST API. Appwrite aims to help you develop your apps faster and in a more secure way. Use the Apple SDK to integrate your app with the Appwrite server to easily start interacting with all of Appwrite backend APIs and tools. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)
|
||||
|
||||
@@ -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: "15.0.0"),
|
||||
.package(url: "git@github.com:appwrite/sdk-for-apple.git", from: "16.0.0"),
|
||||
],
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import NIO
|
||||
import NIOCore
|
||||
#if canImport(NIOFoundationCompat)
|
||||
import NIOFoundationCompat
|
||||
#endif
|
||||
import NIOSSL
|
||||
import Foundation
|
||||
import AsyncHTTPClient
|
||||
@@ -24,8 +26,8 @@ open class Client {
|
||||
"x-sdk-name": "Apple",
|
||||
"x-sdk-platform": "client",
|
||||
"x-sdk-language": "apple",
|
||||
"x-sdk-version": "15.0.0",
|
||||
"x-appwrite-response-format": "1.8.0"
|
||||
"x-sdk-version": "16.0.0",
|
||||
"x-appwrite-response-format": "1.9.0"
|
||||
]
|
||||
|
||||
internal var config: [String: String] = [:]
|
||||
@@ -162,6 +164,51 @@ open class Client {
|
||||
return self
|
||||
}
|
||||
|
||||
///
|
||||
/// Set ImpersonateUserId
|
||||
///
|
||||
/// Impersonate a user by ID on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data.
|
||||
///
|
||||
/// @param String value
|
||||
///
|
||||
/// @return Client
|
||||
///
|
||||
open func setImpersonateUserId(_ value: String) -> Client {
|
||||
config["impersonateuserid"] = value
|
||||
_ = addHeader(key: "X-Appwrite-Impersonate-User-Id", value: value)
|
||||
return self
|
||||
}
|
||||
|
||||
///
|
||||
/// Set ImpersonateUserEmail
|
||||
///
|
||||
/// Impersonate a user by email on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data.
|
||||
///
|
||||
/// @param String value
|
||||
///
|
||||
/// @return Client
|
||||
///
|
||||
open func setImpersonateUserEmail(_ value: String) -> Client {
|
||||
config["impersonateuseremail"] = value
|
||||
_ = addHeader(key: "X-Appwrite-Impersonate-User-Email", value: value)
|
||||
return self
|
||||
}
|
||||
|
||||
///
|
||||
/// Set ImpersonateUserPhone
|
||||
///
|
||||
/// Impersonate a user by phone on an already user-authenticated request. Requires the current request to be authenticated as a user with impersonator capability; X-Appwrite-Key alone is not sufficient. Impersonator users are intentionally granted users.read so they can discover a target before impersonation begins. Internal audit logs still attribute actions to the original impersonator and record the impersonated target only in internal audit payload data.
|
||||
///
|
||||
/// @param String value
|
||||
///
|
||||
/// @return Client
|
||||
///
|
||||
open func setImpersonateUserPhone(_ value: String) -> Client {
|
||||
config["impersonateuserphone"] = value
|
||||
_ = addHeader(key: "X-Appwrite-Impersonate-User-Phone", value: value)
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
/// Set self signed
|
||||
@@ -378,7 +425,7 @@ open class Client {
|
||||
if data.readableBytes == 0 {
|
||||
return true as! T
|
||||
}
|
||||
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any]
|
||||
let dict = try JSONSerialization.jsonObject(with: Data(data.readableBytesView)) as? [String: Any]
|
||||
|
||||
return converter?(dict!) ?? dict! as! T
|
||||
}
|
||||
@@ -388,7 +435,7 @@ open class Client {
|
||||
var responseString = ""
|
||||
|
||||
do {
|
||||
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any]
|
||||
let dict = try JSONSerialization.jsonObject(with: Data(data.readableBytesView)) as? [String: Any]
|
||||
|
||||
message = dict?["message"] as? String ?? response.status.reasonPhrase
|
||||
type = dict?["type"] as? String ?? ""
|
||||
@@ -420,9 +467,9 @@ open class Client {
|
||||
|
||||
switch(input.sourceType) {
|
||||
case "path":
|
||||
input.data = ByteBuffer(data: try! Data(contentsOf: URL(fileURLWithPath: input.path)))
|
||||
input.data = ByteBuffer(bytes: try! Data(contentsOf: URL(fileURLWithPath: input.path)))
|
||||
case "data":
|
||||
input.data = ByteBuffer(data: input.data as! Data)
|
||||
input.data = ByteBuffer(bytes: input.data as! Data)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import Foundation
|
||||
import NIO
|
||||
#if canImport(NIOFoundationCompat)
|
||||
import NIOFoundationCompat
|
||||
#endif
|
||||
import NIOHTTP1
|
||||
import NIOWebSocket
|
||||
|
||||
@@ -18,7 +21,7 @@ class MessageHandler {
|
||||
self.client = client
|
||||
self.buffer = ByteBufferAllocator().buffer(capacity: 0)
|
||||
}
|
||||
|
||||
|
||||
private func unmaskedData(frame: WebSocketFrame) -> ByteBuffer {
|
||||
var frameData = frame.data
|
||||
if let maskingKey = frame.maskKey {
|
||||
@@ -31,7 +34,7 @@ class MessageHandler {
|
||||
extension MessageHandler: ChannelInboundHandler, RemovableChannelHandler {
|
||||
|
||||
typealias InboundIn = WebSocketFrame
|
||||
|
||||
|
||||
public func channelRead(context: ChannelHandlerContext, data: NIOAny) {
|
||||
let frame = self.unwrapInboundIn(data)
|
||||
switch frame.opcode {
|
||||
@@ -56,18 +59,14 @@ extension MessageHandler: ChannelInboundHandler, RemovableChannelHandler {
|
||||
case .binary:
|
||||
let data = unmaskedData(frame: frame)
|
||||
if frame.fin {
|
||||
guard let binaryData = data.getData(at: 0, length: data.readableBytes) else {
|
||||
return
|
||||
}
|
||||
let binaryData = Data(data.readableBytesView)
|
||||
if let delegate = client.delegate {
|
||||
try! delegate.onMessage(data: binaryData)
|
||||
} else {
|
||||
client.onBinaryMessage(binaryData)
|
||||
}
|
||||
} else {
|
||||
guard let binaryData = data.getData(at: 0, length: data.readableBytes) else {
|
||||
return
|
||||
}
|
||||
let binaryData = Data(data.readableBytesView)
|
||||
binaryBuffer = binaryData
|
||||
}
|
||||
case .continuation:
|
||||
@@ -92,9 +91,7 @@ extension MessageHandler: ChannelInboundHandler, RemovableChannelHandler {
|
||||
}
|
||||
} else {
|
||||
if frame.fin {
|
||||
guard let binaryData = data.getData(at: 0, length: data.readableBytes) else {
|
||||
return
|
||||
}
|
||||
let binaryData = Data(data.readableBytesView)
|
||||
binaryBuffer.append(binaryData)
|
||||
if let delegate = client.delegate {
|
||||
try! delegate.onMessage(data: binaryBuffer)
|
||||
@@ -102,9 +99,7 @@ extension MessageHandler: ChannelInboundHandler, RemovableChannelHandler {
|
||||
client.onBinaryMessage(binaryBuffer)
|
||||
}
|
||||
} else {
|
||||
guard let binaryData = data.getData(at: 0, length: data.readableBytes) else {
|
||||
return
|
||||
}
|
||||
let binaryData = Data(data.readableBytesView)
|
||||
binaryBuffer.append(binaryData)
|
||||
}
|
||||
}
|
||||
@@ -114,18 +109,18 @@ extension MessageHandler: ChannelInboundHandler, RemovableChannelHandler {
|
||||
}
|
||||
let data = frame.data
|
||||
if !client.closeSent {
|
||||
client.close(data: frame.data.getData(at: 0, length: frame.data.readableBytes) ?? Data())
|
||||
client.close(data: Data(frame.data.readableBytesView))
|
||||
}
|
||||
if let delegate = client.delegate {
|
||||
delegate.onClose(channel: context.channel, data: data.getData(at: 0, length: data.readableBytes)!)
|
||||
delegate.onClose(channel: context.channel, data: Data(data.readableBytesView))
|
||||
} else {
|
||||
client.onClose(context.channel, data.getData(at: 0, length: data.readableBytes)!)
|
||||
client.onClose(context.channel, Data(data.readableBytesView))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func errorCaught(context: ChannelHandlerContext, error: Swift.Error) {
|
||||
if client.delegate != nil {
|
||||
try! client.delegate?.onError(error: error, status: nil)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Foundation
|
||||
import NIO
|
||||
#if canImport(NIOFoundationCompat)
|
||||
import NIOFoundationCompat
|
||||
#endif
|
||||
import NIOHTTP1
|
||||
import NIOWebSocket
|
||||
import Dispatch
|
||||
import NIOFoundationCompat
|
||||
import NIOSSL
|
||||
|
||||
public let WEBSOCKET_LOCKER_QUEUE = "SyncLocker"
|
||||
|
||||
@@ -18,7 +18,7 @@ open class Document<T : Codable>: Codable {
|
||||
/// Document ID.
|
||||
public let id: String
|
||||
/// Document sequence ID.
|
||||
public let sequence: Int
|
||||
public let sequence: String
|
||||
/// Collection ID.
|
||||
public let collectionId: String
|
||||
/// Database ID.
|
||||
@@ -34,7 +34,7 @@ open class Document<T : Codable>: Codable {
|
||||
|
||||
init(
|
||||
id: String,
|
||||
sequence: Int,
|
||||
sequence: String,
|
||||
collectionId: String,
|
||||
databaseId: String,
|
||||
createdAt: String,
|
||||
@@ -56,7 +56,7 @@ open class Document<T : Codable>: Codable {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.id = try container.decode(String.self, forKey: .id)
|
||||
self.sequence = try container.decode(Int.self, forKey: .sequence)
|
||||
self.sequence = try container.decode(String.self, forKey: .sequence)
|
||||
self.collectionId = try container.decode(String.self, forKey: .collectionId)
|
||||
self.databaseId = try container.decode(String.self, forKey: .databaseId)
|
||||
self.createdAt = try container.decode(String.self, forKey: .createdAt)
|
||||
@@ -94,7 +94,7 @@ open class Document<T : Codable>: Codable {
|
||||
public static func from(map: [String: Any] ) -> Document {
|
||||
return Document(
|
||||
id: map["$id"] as? String ?? "",
|
||||
sequence: map["$sequence"] as? Int ?? 0,
|
||||
sequence: map["$sequence"] as? String ?? "",
|
||||
collectionId: map["$collectionId"] as? String ?? "",
|
||||
databaseId: map["$databaseId"] as? String ?? "",
|
||||
createdAt: map["$createdAt"] as? String ?? "",
|
||||
|
||||
@@ -30,11 +30,11 @@ open class Log: Codable {
|
||||
|
||||
/// Event name.
|
||||
public let event: String
|
||||
/// User ID.
|
||||
/// User ID of the actor recorded for this log. During impersonation, this is the original impersonator, not the impersonated target user.
|
||||
public let userId: String
|
||||
/// User Email.
|
||||
/// User email of the actor recorded for this log. During impersonation, this is the original impersonator.
|
||||
public let userEmail: String
|
||||
/// User Name.
|
||||
/// User name of the actor recorded for this log. During impersonation, this is the original impersonator.
|
||||
public let userName: String
|
||||
/// API mode when event triggered.
|
||||
public let mode: String
|
||||
|
||||
@@ -18,7 +18,7 @@ open class Row<T : Codable>: Codable {
|
||||
/// Row ID.
|
||||
public let id: String
|
||||
/// Row sequence ID.
|
||||
public let sequence: Int
|
||||
public let sequence: String
|
||||
/// Table ID.
|
||||
public let tableId: String
|
||||
/// Database ID.
|
||||
@@ -34,7 +34,7 @@ open class Row<T : Codable>: Codable {
|
||||
|
||||
init(
|
||||
id: String,
|
||||
sequence: Int,
|
||||
sequence: String,
|
||||
tableId: String,
|
||||
databaseId: String,
|
||||
createdAt: String,
|
||||
@@ -56,7 +56,7 @@ open class Row<T : Codable>: Codable {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
self.id = try container.decode(String.self, forKey: .id)
|
||||
self.sequence = try container.decode(Int.self, forKey: .sequence)
|
||||
self.sequence = try container.decode(String.self, forKey: .sequence)
|
||||
self.tableId = try container.decode(String.self, forKey: .tableId)
|
||||
self.databaseId = try container.decode(String.self, forKey: .databaseId)
|
||||
self.createdAt = try container.decode(String.self, forKey: .createdAt)
|
||||
@@ -94,7 +94,7 @@ open class Row<T : Codable>: Codable {
|
||||
public static func from(map: [String: Any] ) -> Row {
|
||||
return Row(
|
||||
id: map["$id"] as! String,
|
||||
sequence: map["$sequence"] as! Int,
|
||||
sequence: map["$sequence"] as! String,
|
||||
tableId: map["$tableId"] as! String,
|
||||
databaseId: map["$databaseId"] as! String,
|
||||
createdAt: map["$createdAt"] as! String,
|
||||
|
||||
@@ -24,6 +24,8 @@ open class User<T : Codable>: Codable {
|
||||
case prefs = "prefs"
|
||||
case targets = "targets"
|
||||
case accessedAt = "accessedAt"
|
||||
case impersonator = "impersonator"
|
||||
case impersonatorUserId = "impersonatorUserId"
|
||||
}
|
||||
|
||||
/// User ID.
|
||||
@@ -64,6 +66,10 @@ open class User<T : Codable>: Codable {
|
||||
public let targets: [Target]
|
||||
/// Most recent access date in ISO 8601 format. This attribute is only updated again after 24 hours.
|
||||
public let accessedAt: String
|
||||
/// Whether the user can impersonate other users.
|
||||
public let impersonator: Bool?
|
||||
/// ID of the original actor performing the impersonation. Present only when the current request is impersonating another user. Internal audit logs attribute the action to this user, while the impersonated target is recorded only in internal audit payload data.
|
||||
public let impersonatorUserId: String?
|
||||
|
||||
init(
|
||||
id: String,
|
||||
@@ -84,7 +90,9 @@ open class User<T : Codable>: Codable {
|
||||
mfa: Bool,
|
||||
prefs: Preferences<T>,
|
||||
targets: [Target],
|
||||
accessedAt: String
|
||||
accessedAt: String,
|
||||
impersonator: Bool?,
|
||||
impersonatorUserId: String?
|
||||
) {
|
||||
self.id = id
|
||||
self.createdAt = createdAt
|
||||
@@ -105,6 +113,8 @@ open class User<T : Codable>: Codable {
|
||||
self.prefs = prefs
|
||||
self.targets = targets
|
||||
self.accessedAt = accessedAt
|
||||
self.impersonator = impersonator
|
||||
self.impersonatorUserId = impersonatorUserId
|
||||
}
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
@@ -129,6 +139,8 @@ open class User<T : Codable>: Codable {
|
||||
self.prefs = try container.decode(Preferences<T>.self, forKey: .prefs)
|
||||
self.targets = try container.decode([Target].self, forKey: .targets)
|
||||
self.accessedAt = try container.decode(String.self, forKey: .accessedAt)
|
||||
self.impersonator = try container.decodeIfPresent(Bool.self, forKey: .impersonator)
|
||||
self.impersonatorUserId = try container.decodeIfPresent(String.self, forKey: .impersonatorUserId)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@@ -153,6 +165,8 @@ open class User<T : Codable>: Codable {
|
||||
try container.encode(prefs, forKey: .prefs)
|
||||
try container.encode(targets, forKey: .targets)
|
||||
try container.encode(accessedAt, forKey: .accessedAt)
|
||||
try container.encodeIfPresent(impersonator, forKey: .impersonator)
|
||||
try container.encodeIfPresent(impersonatorUserId, forKey: .impersonatorUserId)
|
||||
}
|
||||
|
||||
public func toMap() -> [String: Any] {
|
||||
@@ -175,7 +189,9 @@ open class User<T : Codable>: Codable {
|
||||
"mfa": mfa as Any,
|
||||
"prefs": prefs.toMap() as Any,
|
||||
"targets": targets.map { $0.toMap() } as Any,
|
||||
"accessedAt": accessedAt as Any
|
||||
"accessedAt": accessedAt as Any,
|
||||
"impersonator": impersonator as Any,
|
||||
"impersonatorUserId": impersonatorUserId as Any
|
||||
]
|
||||
}
|
||||
|
||||
@@ -199,7 +215,9 @@ open class User<T : Codable>: Codable {
|
||||
mfa: map["mfa"] as! Bool,
|
||||
prefs: Preferences.from(map: map["prefs"] as! [String: Any]),
|
||||
targets: (map["targets"] as! [[String: Any]]).map { Target.from(map: $0) },
|
||||
accessedAt: map["accessedAt"] as! String
|
||||
accessedAt: map["accessedAt"] as! String,
|
||||
impersonator: map["impersonator"] as? Bool,
|
||||
impersonatorUserId: map["impersonatorUserId"] as? String
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ extension ExampleView {
|
||||
fileId: fileId
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
self.downloadedImage = Image(data: Data(buffer: data))
|
||||
self.downloadedImage = Image(data: Data(data.readableBytesView))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@@ -87,7 +87,7 @@ class ViewController: UIViewController {
|
||||
bucketId: bucketId,
|
||||
fileId: fileId
|
||||
)
|
||||
let data = response.getData(at: 0, length: response.readableBytes)!
|
||||
let data = Data(response.readableBytesView)
|
||||
self.image.image = UIImage(data: data)
|
||||
} catch {
|
||||
self.response = String(describing: error)
|
||||
|
||||
Reference in New Issue
Block a user