serve.zone/swiftsupport
Swift for serve.zone. Its library Cloudly is a Swift client of Cloudly's typed requests: the contracts generated from the TypeScript of @serve.zone/interfaces, and CloudlyClient, which posts them on the TypedRequest client of api.global/swiftsupport. The serve.zone app (serve.zone/swiftapp) reads its Cloudly instances through it.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
Swift 6, iOS 17, macOS 14, tvOS 17, watchOS 10, visionOS 1, Linux. The package is in the Swift package registry of code.foss.global as servezone.swiftsupport, and the api.global/swiftsupport it depends on as apiglobal.swiftsupport, both readable without a sign-in. Map both scopes once, for the project or with --global for your user:
swift package-registry set --scope servezone https://code.foss.global/api/packages/serve.zone/swift
swift package-registry set --scope apiglobal https://code.foss.global/api/packages/api.global/swift
dependencies: [
.package(id: "servezone.swiftsupport", from: "1.0.0"),
],
targets: [
.target(name: "MyKit", dependencies: [.product(name: "Cloudly", package: "servezone.swiftsupport")]),
]
Releases are tagged vX.Y.Z, which SwiftPM reads as version X.Y.Z, and each tag is published to the registry. The archives are not signed, so SwiftPM warns on download.
Depend on the package by its registry id, not by its URL: SwiftPM names a package by the last part of its URL, so serve.zone/swiftsupport, api.global/swiftsupport, fin.cx/swiftsupport and modelprofile.com/swiftsupport are all the package swiftsupport there, and two of them in one dependency graph fail to resolve (Conflicting identity for swiftsupport).
Reading a Cloudly
import Cloudly
let cloudly = CloudlyClient(origin: URL(string: "https://cloudly.example.com")!)
let identity = try await cloudly.signIn(username: username, password: password) // the password is kept nowhere
let clusters = try await cloudly.clusters(identity)
let nodes = try await cloudly.nodes(identity, clusterId: clusters.first?.id)
let services = try await cloudly.services(identity)
let deployments = try await cloudly.deployments(identity)
let assignments = try await cloudly.runtimeAssignments(identity, serviceId: services[0].id)
let attempts = try await cloudly.logAttempts(identity, serviceId: services[0].id)
let page = try await cloudly.logs(identity, serviceId: services[0].id, assignmentId: attempts.attempts[0].assignmentId)
- Identity. Cloudly takes the identity in the body of every request, not in a header.
signInsigns in with an admin's username and password (adminLoginWithUsernameAndPassword); the identity it returns expires (identity.expiry, fromexpiresAtin epoch milliseconds), and a call with an expired identity throwsCloudlyClientError.identityExpiredwithout sending it. The identity holds a JWT: keep it in the Keychain, never in logs. - Errors. A call throws what
TypedRequestClientthrows:TypedRequestErrorfor Cloudly's refusals (its text is the guard's hint; Cloudly sends no code for an unauthenticated call),TypedTransportErrorfor an answer that is no envelope (such as a gateway's502),URLErrorandCancellationError. - Session. By default the client uses an ephemeral session without cookies and cache, and follows no redirect.
- Logs. Both log reads page with
cursor(nilfirst, then the page'snextCursor) andlimitup toCloudlyLogReads.maximumAttemptPageSizeandmaximumPageRecords, whichswift testholds against the installed interfaces. A log line'stimestampNsis text, since nanoseconds do not fit aDouble.
Not in this build
- Onebox. Onebox's contracts live in the onebox repository and are not published as a package, so they cannot be generated here yet; the module
Oneboxfollows once they are. - Device sign-in. The client signs in only with an admin's password. A device pairing in which Cloudly issues the app a revocable credential of its own is an upstream item of Cloudly.
- Writes. The client reads only.
Contracts
Sources/Cloudly/Generated/CloudlyContracts.swift is generated from the TypeScript of @serve.zone/interfaces (a development dependency, pinned to an exact version) by the generator of @api.global/swiftsupport, with contracts/cloudly.swiftsupport.config.json. Its methods name the requests the client sends; their types follow.
pnpm run generate # writes the file
pnpm run generate:check # exit 1 when it is out of date (pnpm test runs it first)
Generation needs @api.global/swiftsupport 1.2.1 or later: 1.2.0 stops at the import types of @serve.zone/interfaces and names its IRequest_ contracts wrong.
Numbers are TypeScript numbers, so they are Double in Swift. A string enum is open: a value this build does not know is .unknown(value), and where Cloudly itself sends unknown (a deployment's healthStatus) the case is .unknownValue.
Development
pnpm install # the generator and the interfaces
pnpm test # the drift check of the contracts, then swift test
xcodebuild -scheme servezone-swiftsupport -destination 'generic/platform=iOS Simulator' build
swift test decodes answers shaped as Cloudly sends them, runs the client against a stub Cloudly on a URLProtocol, and checks what is written by hand against the installed @serve.zone/interfaces (skipped without pnpm install).
.swiftpm/configuration/registries.json maps the apiglobal scope for this repository's own builds, so swift test needs no registry setup.
A pushed release tag runs .gitea/workflows/swift-registry.yml: scripts/publish-swift-registry.mjs uploads git archive of the tag to the registry, then reads it back without a sign-in and compares checksums. A version the registry already holds passes only with the same archive. The job token may not write packages, so the upload uses the organisation's Actions secret SWIFT_REGISTRY_TOKEN, an access token with package write scope. Workflow and script are copies of those in api.global/swiftsupport; keep them identical.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license.md file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.