Skip to content

Commit 564b710

Browse files
feat: support for 0.13.0
1 parent 0e6cf1b commit 564b710

Some content is hidden

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

63 files changed

+1545
-532
lines changed

Package.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import PackageDescription
44

55
let package = Package(
66
name: "Appwrite",
7+
platforms: [
8+
.iOS("15.0"),
9+
.macOS("11.0"),
10+
.watchOS("6.0"),
11+
.tvOS("13.0"),
12+
],
713
products: [
814
.library(
915
name: "Appwrite",

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
![Swift Package Manager](https://img.shields.io/github/v/release/appwrite/sdk-for-swift.svg?color=green&style=flat-square)
44
![License](https://img.shields.io/github/license/appwrite/sdk-for-swift.svg?style=flat-square)
5-
![Version](https://img.shields.io/badge/api%20version-0.12.1-blue.svg?style=flat-square)
5+
![Version](https://img.shields.io/badge/api%20version-0.13.0-blue.svg?style=flat-square)
66
[![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator)
77
[![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite)
88
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord)
99

10-
**This SDK is compatible with Appwrite server version 0.12.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-swift/releases).**
10+
**This SDK is compatible with Appwrite server version 0.13.x. For older versions, please check [previous releases](https://github.com/appwrite/sdk-for-swift/releases).**
1111

1212
> This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)
1313
@@ -19,13 +19,13 @@ Appwrite is an open-source backend as a service server that abstract and simplif
1919

2020
### Xcode with Swift Package Manager
2121

22-
The Appwrite Swift SDK is available via multiple package managers, including Swift Package Manager. In order to use the Appwrite Swift SDK from Xcode, select File > Swift Packages > **Add Package Dependency**
22+
The Appwrite Swift SDK is available via Swift Package Manager. In order to use the Appwrite Swift SDK from Xcode, select File > **Add Packages**
2323

24-
In the dialog that appears, enter the Appwrite Swift SDK [package URL](git@github.com:appwrite/sdk-for-swift.git) and click **Next**.
24+
In the dialog that appears, enter the Appwrite Swift SDK [package URL](git@github.com:appwrite/sdk-for-swift.git) in the search field. Once found, select `sdk-for-apple`.
2525

26-
Once the repository information is loaded, add your version rules and click **Next** again.
26+
On the right, select your version rules and ensure your desired target is selected in the **Add to Project** field.
2727

28-
On the final screen, make sure you see `Appwrite` as a product selected for your target:
28+
Now click add package and you're done!
2929

3030
### Swift Package Manager
3131

Sources/Appwrite/Client.swift

Lines changed: 115 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,28 @@ let CRLF = "\r\n"
1212
open class Client {
1313

1414
// MARK: Properties
15+
public static var chunkSize = 5 * 1024 * 1024 // 5MB
1516

1617
open var endPoint = "https://HOSTNAME/v1"
1718

1819
open var endPointRealtime: String? = nil
1920

2021
open var headers: [String: String] = [
2122
"content-type": "",
22-
"x-sdk-version": "appwrite:swift:0.2.1", "X-Appwrite-Response-Format": "0.12.0"
23+
"x-sdk-version": "appwrite:swift:0.2.1", "X-Appwrite-Response-Format": "0.13.0"
2324
]
2425

2526
open var config: [String: String] = [:]
2627

28+
open var selfSigned: Bool = false
29+
2730
open var http: HTTPClient
2831

2932
private static let boundaryChars =
3033
"abcdefghijklmnopqrstuvwxyz1234567890"
3134

35+
private static let boundary = randomBoundary()
36+
3237
private static var eventLoopGroupProvider =
3338
HTTPClient.EventLoopGroupProvider.createNew
3439

@@ -151,6 +156,7 @@ open class Client {
151156
/// @return Client
152157
///
153158
open func setSelfSigned(_ status: Bool = true) -> Client {
159+
self.selfSigned = status
154160
try! http.syncShutdown()
155161
http = Client.createHTTP(selfSigned: status)
156162
return self
@@ -281,7 +287,9 @@ open class Client {
281287
method: .RAW(value: method)
282288
)
283289
} catch {
284-
completion?(Result.failure(AppwriteError(message: error.localizedDescription)))
290+
completion?(Result.failure(AppwriteError(
291+
message: error.localizedDescription
292+
)))
285293
return
286294
}
287295

@@ -296,7 +304,9 @@ open class Client {
296304
do {
297305
try buildBody(for: &request, with: validParams)
298306
} catch let error {
299-
completion?(Result.failure(AppwriteError(message: error.localizedDescription)))
307+
completion?(Result.failure(AppwriteError(
308+
message: error.localizedDescription
309+
)))
300310
return
301311
}
302312

@@ -314,7 +324,7 @@ open class Client {
314324
with params: [String: Any?]
315325
) throws {
316326
if request.headers["content-type"][0] == "multipart/form-data" {
317-
buildMultipart(&request, with: params)
327+
buildMultipart(&request, with: params, chunked: !request.headers["content-range"].isEmpty)
318328
} else {
319329
try buildJSON(&request, with: params)
320330
}
@@ -349,7 +359,10 @@ open class Client {
349359
}
350360

351361
switch result {
352-
case .failure(let error): print(error)
362+
case .failure(let error):
363+
completion(.failure(AppwriteError(
364+
message: error.localizedDescription
365+
)))
353366
case .success(var response):
354367
switch response.status.code {
355368
case 0..<400:
@@ -376,20 +389,32 @@ open class Client {
376389
}
377390
default:
378391
var message = ""
392+
var type = ""
393+
394+
if response.body == nil {
395+
completion(.failure(AppwriteError(
396+
message: "Unknown error with status code \(response.status.code)",
397+
code: Int(response.status.code)
398+
)))
399+
return
400+
}
379401

380402
do {
381403
let dict = try JSONSerialization
382404
.jsonObject(with: response.body!) as? [String: Any]
383405

384406
message = dict?["message"] as? String
385407
?? response.status.reasonPhrase
408+
409+
type = dict?["type"] as? String ?? ""
386410
} catch {
387411
message = response.body!.readString(length: response.body!.readableBytes)!
388412
}
389413

390414
let error = AppwriteError(
391415
message: message,
392-
code: Int(response.status.code)
416+
code: Int(response.status.code),
417+
type: type
393418
)
394419

395420
completion(.failure(error))
@@ -398,7 +423,83 @@ open class Client {
398423
}
399424
}
400425

401-
private func randomBoundary() -> String {
426+
func chunkedUpload<T>(
427+
path: String,
428+
headers: inout [String: String],
429+
params: inout [String: Any?],
430+
paramName: String,
431+
convert: (([String: Any]) -> T)? = nil,
432+
onProgress: ((UploadProgress) -> Void)? = nil,
433+
completion: ((Result<T, AppwriteError>) -> Void)? = nil
434+
) {
435+
let file = params[paramName] as! File
436+
let size = file.buffer.readableBytes
437+
438+
if size < Client.chunkSize {
439+
call(
440+
method: "POST",
441+
path: path,
442+
headers: headers,
443+
params: params,
444+
convert: convert,
445+
completion: completion
446+
)
447+
return
448+
}
449+
450+
var input = file.buffer
451+
var offset = 0
452+
var result = [String:Any]()
453+
let group = DispatchGroup()
454+
455+
while offset < size {
456+
let slice = input.readSlice(length: Client.chunkSize)
457+
?? input.readSlice(length: Int(size - offset))
458+
459+
params[paramName] = File(
460+
name: file.name,
461+
buffer: slice!
462+
)
463+
464+
headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size))/\(size)"
465+
466+
group.enter()
467+
468+
call(
469+
method: "POST",
470+
path: path,
471+
headers: headers,
472+
params: params,
473+
convert: { return $0 }
474+
) { response in
475+
switch response {
476+
case let .success(map):
477+
result = map
478+
group.leave()
479+
case let .failure(error):
480+
completion?(.failure(error))
481+
return
482+
}
483+
}
484+
485+
group.wait()
486+
487+
offset += Client.chunkSize
488+
headers["x-appwrite-id"] = result["$id"] as? String
489+
onProgress?(UploadProgress(
490+
id: result["$id"] as? String ?? "",
491+
progress: Double(min(offset, size))/Double(size) * 100.0,
492+
sizeUploaded: min(offset, size),
493+
chunksTotal: result["chunksTotal"] as? Int ?? -1,
494+
chunksUploaded: result["chunksUploaded"] as? Int ?? -1
495+
))
496+
}
497+
498+
completion?(.success(convert!(result)))
499+
}
500+
501+
502+
private static func randomBoundary() -> String {
402503
var string = ""
403504
for _ in 0..<16 {
404505
string.append(Client.boundaryChars.randomElement()!)
@@ -417,11 +518,12 @@ open class Client {
417518

418519
private func buildMultipart(
419520
_ request: inout HTTPClient.Request,
420-
with params: [String: Any?] = [:]
521+
with params: [String: Any?] = [:],
522+
chunked: Bool = false
421523
) {
422524
func addPart(name: String, value: Any) {
423525
bodyBuffer.writeString(DASHDASH)
424-
bodyBuffer.writeString(boundary)
526+
bodyBuffer.writeString(Client.boundary)
425527
bodyBuffer.writeString(CRLF)
426528
bodyBuffer.writeString("Content-Disposition: form-data; name=\"\(name)\"")
427529

@@ -443,7 +545,6 @@ open class Client {
443545
bodyBuffer.writeString(CRLF)
444546
}
445547

446-
let boundary = randomBoundary()
447548
var bodyBuffer = ByteBuffer()
448549

449550
for (key, value) in params {
@@ -462,13 +563,15 @@ open class Client {
462563
}
463564

464565
bodyBuffer.writeString(DASHDASH)
465-
bodyBuffer.writeString(boundary)
566+
bodyBuffer.writeString(Client.boundary)
466567
bodyBuffer.writeString(DASHDASH)
467568
bodyBuffer.writeString(CRLF)
468569

469570
request.headers.remove(name: "content-type")
571+
if !chunked {
470572
request.headers.add(name: "Content-Length", value: bodyBuffer.readableBytes.description)
471-
request.headers.add(name: "Content-Type", value: "multipart/form-data;boundary=\"\(boundary)\"")
573+
}
574+
request.headers.add(name: "Content-Type", value: "multipart/form-data;boundary=\"\(Client.boundary)\"")
472575
request.body = .byteBuffer(bodyBuffer)
473576
}
474577

Sources/Appwrite/Models/AppwriteError.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ open class AppwriteError : Swift.Error, Decodable {
44

55
public let message: String
66
public let code: Int?
7+
public let type: String?
78

8-
init(message: String, code: Int? = nil) {
9+
init(message: String, code: Int? = nil, type: String? = nil) {
910
self.message = message
1011
self.code = code
12+
self.type = type
1113
}
1214
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
public class UploadProgress {
2+
public let id: String
3+
public let progress: Double
4+
public let sizeUploaded: Int
5+
public let chunksTotal: Int
6+
public let chunksUploaded: Int
7+
8+
public init(id: String, progress: Double, sizeUploaded: Int, chunksTotal: Int, chunksUploaded: Int) {
9+
self.id = id
10+
self.progress = progress
11+
self.sizeUploaded = sizeUploaded
12+
self.chunksTotal = chunksTotal
13+
self.chunksUploaded = chunksUploaded
14+
}
15+
}

Sources/Appwrite/Services/Account.swift

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,8 @@ open class Account: Service {
490490

491491
path = path.replacingOccurrences(
492492
of: "{sessionId}",
493-
with: sessionId )
493+
with: sessionId
494+
)
494495

495496
let params: [String: Any?] = [:]
496497

@@ -512,12 +513,51 @@ open class Account: Service {
512513
)
513514
}
514515

516+
///
517+
/// Update Session (Refresh Tokens)
518+
///
519+
/// @param String sessionId
520+
/// @throws Exception
521+
/// @return array
522+
///
523+
open func updateSession(
524+
sessionId: String,
525+
completion: ((Result<AppwriteModels.Session, AppwriteError>) -> Void)? = nil
526+
) {
527+
var path: String = "/account/sessions/{sessionId}"
528+
529+
path = path.replacingOccurrences(
530+
of: "{sessionId}",
531+
with: sessionId
532+
)
533+
534+
let params: [String: Any?] = [:]
535+
536+
let headers: [String: String] = [
537+
"content-type": "application/json"
538+
]
539+
540+
let convert: ([String: Any]) -> AppwriteModels.Session = { dict in
541+
return AppwriteModels.Session.from(map: dict)
542+
}
543+
544+
client.call(
545+
method: "PATCH",
546+
path: path,
547+
headers: headers,
548+
params: params,
549+
convert: convert,
550+
completion: completion
551+
)
552+
}
553+
515554
///
516555
/// Delete Account Session
517556
///
518557
/// Use this endpoint to log out the currently logged in user from all their
519558
/// account sessions across all of their different devices. When using the
520-
/// option id argument, only the session unique ID provider will be deleted.
559+
/// Session ID argument, only the unique session ID provided is deleted.
560+
///
521561
///
522562
/// @param String sessionId
523563
/// @throws Exception
@@ -531,7 +571,8 @@ open class Account: Service {
531571

532572
path = path.replacingOccurrences(
533573
of: "{sessionId}",
534-
with: sessionId )
574+
with: sessionId
575+
)
535576

536577
let params: [String: Any?] = [:]
537578

0 commit comments

Comments
 (0)