Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Metropolis

metropolis

Async/await-based Matrix client library in Swift

MIT License Swift 5.5

🚧 This package is not production-ready 🚧

About

Metropolis is an asynchronous client library for Matrix Messaging Protocol written in pure Swift. It provides a low-level wrapper for client-server API integrated with the Swift type system.

What is Matrix?

Matrix is an open standard for interoperable, decentralised, real-time communication over IP. It can be used to power Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication - or anywhere you need a standard HTTP API for publishing and subscribing to data whilst tracking the conversation history.

Supported Features

  • Account Management
  • Session Management
  • Device Management
  • Room Directory
  • Room Participation
  • Presence
  • Media
  • User Data
  • User Directory
  • Full Text Search
  • Push Notifications
  • End-To-End Encryption

Installation

Swift Package Manager

Metropolis supports installation via Swift Package Manager. To add the package as your project's dependency, put the repository url into the dependencies section in Package.swift like this:

// swift-tools-version:5.5
import PackageDescription

let package = Package(
    name: "PackageName",
    dependencies: [
        .package(url: "https://github.com/picoHz/Metropolis.git", .branch("main"))
    ]
)

You can also add dependencies from Xcode. https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app

Usage

Note that this library is basically just a thin wrapper of Matrix client-server API and does not provide high-level functionalities, so you need to read the Matrix Specification to understand how these endpoints work.

Anonymous Access

Public endpoints are accessible without login.

let client = try MPClient(url: URL(string: "https://matrix.org")!)
try await client.checkUsernameAvailability(username: "metro")

Authorization

let client = try MPClient(url: URL(string: "https://matrix.org")!)

let method = MPPasswordAuth(
    identifier: .user(user: "metro"),
    password: "pass")

let authClient = try await client.login(method: method)

Custom Authorization Method

client.login() method accepts any objects that conform to MPAuthMethod protocol.

public protocol MPAuthMethod: Encodable {
    var type: String { get }
}
public struct CustomAuth: MPAuthMethod {
    let type: String = "com.example.custom"
    var customName: String
    var customList: [String]
}

let authClient = try await client.login(method: CustomAuth(customName: "aaa", customList: ["bbb"]))

The property names will be converted to snakecase. For example, CustomAuth will be encoded into the following JSON.

{
    "type": "com.example.custom",
    "custom_name": "aaa",
    "custom_list": ["bbb"]
}

User-Interactive Authentication

var interactiveAuth = try await authClient.changePassword(newPassword: "new_password")

guard case .failure(var state) = try await interactiveAuth.authenticate(method: method) {
    return
}

while true {
    if !state.flows[0].stages.contains("m.login.password") {
        // Password authentication is not supported.
        break
    }

    // Ask user to authenticate.
    let method = MPPasswordAuth(
        identifier: .user(user: "metro"),
        password: "old_password")

    switch try await interactiveAuth.authenticate(method: method, state: state) {
    case .success(_):
        break
    case .failure(let newState):
        // Authentication failed or needs additional authentication.
        state = newState
        continue
    }
}

Pagination

Paginated endpoints return a stream object that implements AsyncSequence.

let stream = try await client.getPublicRooms(limit: 100)

for try await chunk in stream {
    // Fetch results.
}

// Iterate manually.
var iter = stream.makeAsyncIterator()
let chunk = try await iter.next()

// Get an estimated room count from the iterator.
iter.totalRoomCountEstimate

// Resume the iteration using a pagination token.
let newStream = try await client.getPublicRooms(limit: 100, since: iter.nextBatch)

Backward Pagination

Public Room Directory supports bidirectional pagination. You can make a backward iterator using prevBatch token.

let stream = try await client.getPublicRooms(limit: 100)

var iter = stream.makeAsyncIterator()
while try await iter.next() != nil {}

let backwardStream = try await client.getPublicRooms(limit: 100, since: iter.prevBatch)
for try await chunk in backwardStream {
    // Fetch backward results.
}

Media Downloading and Uploading

Metropolis does not directly provide methods to download/upload media. Instead, it provides the following methods returning URLs for downloading or uploading so you can use your favorite HTTP client library to handle them.

public protocol MPMediaAPI {
    func getMediaDownloadUrl(serverName: String, mediaId: String) -> URL
    func getMediaThumbnailUrl(serverName: String, mediaId: String) -> URL
    func getMediaUploadUrl() -> URL
}

Handling Schema-free JSON Objects

Some endpoints use unstructured (a.k.a schema-free) JSON objects. Metropolis handles them with SwiftyJSON.

let profile: JSON = try await client.getProfile(userId: "@metro@example.com")

// Access as a JSON object
if let displayName = profile.dictionaryValue["displayname"].string {
    print(displayName)
}

// Decode as a struct
struct Profile: Decodable { var displayname: String }
let data = try JSONDecoder().decode(Profile.self, from: profile.rawData())
print(data.displayname)

Testing

To run tests, you need running a matrix instance on localhost:8080.

The following commands run up-to-date demo server instances. Please check Synapse Documantation for more information about Matrix servers.

git clone https://github.com/matrix-org/synapse.git
cd synapse

python3 -m venv ./env
source ./env/bin/activate
pip install -e ".[all,dev]"

./demo/start.sh

If there is already a running matrix instance on localhost:8080, you can simply run swift test.

License

Metropolis is released under the MIT license.

Headline Photo by Leonhard Niederwimmer on Unsplash

About

πŸ™οΈ Async/await-based Matrix client library in Swift

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages