Skip to content

Commit 93ae6d8

Browse files
authored
Integrate UniFFI (#822)
- Swaps JWTKit token generator (used mostly in unit tests) with experimental FFI API - Adds misc infrastructure (logging etc.) that's already exposed from Rust This is a PoC to test the UniFFI bindgen, output, etc. in a real-world environment with little to no risk for core business logic (all `internal`) 🦀 Resolves #846
1 parent 07cc5a8 commit 93ae6d8

16 files changed

Lines changed: 152 additions & 188 deletions

File tree

.changes/uniffi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
minor type="added" "Experimental support and bindings for shared Rust crates using UniFFI"

.github/workflows/ci.yaml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,35 +59,34 @@ jobs:
5959
platform: "tvOS Simulator,name=Apple TV,OS=18.5"
6060

6161
# https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
62-
# Use latest-stable temporarily to avoid missing sims
6362
- os: macos-26
64-
xcode: latest-stable
63+
xcode: 26.1
6564
platform: "iOS Simulator,name=iPhone 17 Pro,OS=26.1"
6665
symbol-graph: true
6766
- os: macos-26
68-
xcode: latest-stable
67+
xcode: 26.1
6968
platform: "iOS Simulator,name=iPhone 17 Pro,OS=26.1"
7069
extension-api-only: true
7170
- os: macos-26
72-
xcode: latest
71+
xcode: 26.1
7372
platform: "macOS"
7473
symbol-graph: true
7574
- os: macos-26
76-
xcode: latest
75+
xcode: 26.1
7776
platform: "macOS"
7877
asan: true
7978
- os: macos-26
80-
xcode: latest
79+
xcode: 26.1
8180
platform: "macOS"
8281
tsan: true
8382
- os: macos-26
84-
xcode: latest
83+
xcode: 26.1
8584
platform: "macOS,variant=Mac Catalyst"
8685
- os: macos-26
87-
xcode: latest-stable
86+
xcode: 26.1
8887
platform: "visionOS Simulator,name=Apple Vision Pro,OS=26.1"
8988
- os: macos-26
90-
xcode: latest-stable
89+
xcode: 26.1
9190
platform: "tvOS Simulator,name=Apple TV,OS=26.1"
9291

9392
runs-on: ${{ matrix.os }}

.github/workflows/cocoapods-lint.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- os: macos-15
2020
xcode: 16.4
2121
- os: macos-26
22-
xcode: latest-stable
22+
xcode: 26.1
2323
runs-on: ${{ matrix.os }}
2424
timeout-minutes: 30
2525
steps:

.github/workflows/cocoapods-push.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
- name: Setup Xcode
2424
uses: maxim-lobanov/setup-xcode@v1
2525
with:
26-
xcode-version: latest-stable
26+
xcode-version: 26.1
2727
- name: Add Repo
2828
run: |
2929
pod repo add livekit https://github.com/livekit/podspecs.git

LiveKitClient.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ Pod::Spec.new do |spec|
1515
spec.source_files = "Sources/**/*"
1616

1717
spec.dependency("LiveKitWebRTC", "= 137.7151.10")
18+
spec.dependency("LiveKitUniFFI", "= 0.0.2")
1819
spec.dependency("SwiftProtobuf")
1920
spec.dependency("DequeModule", "= 1.1.4")
2021
spec.dependency("OrderedCollections", " = 1.1.4")
21-
spec.dependency("JWTKit", "= 4.13.5")
2222

2323
spec.resource_bundles = {"Privacy" => ["Sources/LiveKit/PrivacyInfo.xcprivacy"]}
2424

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ let package = Package(
2020
dependencies: [
2121
// LK-Prefixed Dynamic WebRTC XCFramework
2222
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.10"),
23+
.package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.2"),
2324
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"),
2425
.package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.3.0"),
25-
.package(url: "https://github.com/vapor/jwt-kit.git", from: "4.13.5"),
2626
// Only used for DocC generation
2727
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"),
2828
],
@@ -35,10 +35,10 @@ let package = Package(
3535
name: "LiveKit",
3636
dependencies: [
3737
.product(name: "LiveKitWebRTC", package: "webrtc-xcframework"),
38+
.product(name: "LiveKitUniFFI", package: "livekit-uniffi-xcframework"),
3839
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
3940
.product(name: "DequeModule", package: "swift-collections"),
4041
.product(name: "OrderedCollections", package: "swift-collections"),
41-
.product(name: "JWTKit", package: "jwt-kit"),
4242
"LKObjCHelpers",
4343
],
4444
exclude: [

Package@swift-6.0.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ let package = Package(
2121
dependencies: [
2222
// LK-Prefixed Dynamic WebRTC XCFramework
2323
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.10"),
24+
.package(url: "https://github.com/livekit/livekit-uniffi-xcframework.git", exact: "0.0.2"),
2425
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.31.0"),
2526
.package(url: "https://github.com/apple/swift-collections.git", "1.1.0" ..< "1.3.0"),
26-
.package(url: "https://github.com/vapor/jwt-kit.git", from: "4.13.5"),
2727
// Only used for DocC generation
2828
.package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.3.0"),
2929
],
@@ -36,10 +36,10 @@ let package = Package(
3636
name: "LiveKit",
3737
dependencies: [
3838
.product(name: "LiveKitWebRTC", package: "webrtc-xcframework"),
39+
.product(name: "LiveKitUniFFI", package: "livekit-uniffi-xcframework"),
3940
.product(name: "SwiftProtobuf", package: "swift-protobuf"),
4041
.product(name: "DequeModule", package: "swift-collections"),
4142
.product(name: "OrderedCollections", package: "swift-collections"),
42-
.product(name: "JWTKit", package: "jwt-kit"),
4343
"LKObjCHelpers",
4444
],
4545
exclude: [

Sources/LiveKit/Core/Room.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ public class Room: NSObject, @unchecked Sendable, ObservableObject, Loggable {
226226

227227
super.init()
228228
// log sdk & os versions
229-
log("sdk: \(LiveKitSDK.version), os: \(String(describing: Utils.os()))(\(Utils.osVersionString())), modelId: \(String(describing: Utils.modelIdentifier() ?? "unknown"))")
229+
log("sdk: \(LiveKitSDK.version), ffi: \(LiveKitSDK.ffiVersion), os: \(String(describing: Utils.os()))(\(Utils.osVersionString())), modelId: \(String(describing: Utils.modelIdentifier() ?? "unknown"))")
230230

231231
signalClient._delegate.set(delegate: self)
232232

Sources/LiveKit/LiveKit.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import Foundation
1818
internal import LiveKitWebRTC
19+
internal import LiveKitUniFFI
1920

2021
/// The open source platform for real-time communication.
2122
///
@@ -33,6 +34,7 @@ public class LiveKitSDK: NSObject, Loggable {
3334

3435
@objc(sdkVersion)
3536
public static let version = "2.10.2"
37+
static let ffiVersion = buildVersion()
3638

3739
fileprivate struct State {
3840
var logger: Logger = OSLogger()

Sources/LiveKit/Support/Logger.swift

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import OSLog
1818
internal import LiveKitWebRTC
19+
internal import LiveKitUniFFI
1920

2021
// MARK: - Logger
2122

@@ -108,6 +109,7 @@ public struct PrintLogger: Logger {
108109
/// A logger that logs to OSLog
109110
/// - Parameter minLevel: The minimum level to log
110111
/// - Parameter rtc: Whether to log WebRTC output
112+
/// - Parameter ffi: Whether to log Rust FFI output
111113
open class OSLogger: Logger, @unchecked Sendable {
112114
private static let subsystem = "io.livekit.sdk"
113115

@@ -118,15 +120,15 @@ open class OSLogger: Logger, @unchecked Sendable {
118120

119121
private let minLevel: LogLevel
120122

121-
public init(minLevel: LogLevel = .info, rtc: Bool = false) {
123+
public init(minLevel: LogLevel = .info, rtc: Bool = false, ffi: Bool = true) {
122124
self.minLevel = minLevel
123125

124-
guard rtc else { return }
126+
if rtc {
127+
startRTCLogForwarding(minLevel: minLevel)
128+
}
125129

126-
let rtcLog = OSLog(subsystem: Self.subsystem, category: "WebRTC")
127-
rtcLogger.severity = minLevel.rtcSeverity
128-
rtcLogger.start { message, severity in
129-
os_log("%{public}@", log: rtcLog, type: severity.osLogType, message)
130+
if ffi {
131+
startFFILogForwarding(minLevel: minLevel)
130132
}
131133
}
132134

@@ -171,6 +173,33 @@ open class OSLogger: Logger, @unchecked Sendable {
171173
os_log("%{public}@", log: getOSLog(for: type), type: level.osLogType, "\(type).\(function) \(message)\(metadata)")
172174
}
173175
}
176+
177+
private func startRTCLogForwarding(minLevel: LogLevel) {
178+
let rtcLog = OSLog(subsystem: Self.subsystem, category: "WebRTC")
179+
180+
rtcLogger.severity = minLevel.rtcSeverity
181+
rtcLogger.start { message, severity in
182+
os_log("%{public}@", log: rtcLog, type: severity.osLogType, message)
183+
}
184+
}
185+
186+
private func startFFILogForwarding(minLevel: LogLevel) {
187+
Task(priority: .utility) { [weak self] in
188+
guard self != nil else { return } // don't initialize global level when releasing
189+
logForwardBootstrap(level: minLevel.logForwardFilter)
190+
191+
let ffiLog = OSLog(subsystem: Self.subsystem, category: "FFI")
192+
let ffiStream = AsyncStream(unfolding: logForwardReceive)
193+
194+
for await entry in ffiStream {
195+
guard self != nil else { return }
196+
197+
let message = "\(entry.target) \(entry.message)"
198+
199+
os_log("%{public}@", log: ffiLog, type: entry.level.osLogType, message)
200+
}
201+
}
202+
}
174203
}
175204

176205
// MARK: - Loggable
@@ -236,6 +265,15 @@ public enum LogLevel: Int, Sendable, Comparable, CustomStringConvertible {
236265
}
237266
}
238267

268+
var logForwardFilter: LogForwardFilter {
269+
switch self {
270+
case .debug: .debug
271+
case .info: .info
272+
case .warning: .warn
273+
case .error: .error
274+
}
275+
}
276+
239277
@inlinable
240278
public static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
241279
lhs.rawValue < rhs.rawValue
@@ -263,3 +301,15 @@ extension LKRTCLoggingSeverity {
263301
}
264302
}
265303
}
304+
305+
extension LogForwardLevel {
306+
var osLogType: OSLogType {
307+
switch self {
308+
case .error: .error
309+
case .warn: .default
310+
case .info: .info
311+
case .debug, .trace: .debug
312+
@unknown default: .debug
313+
}
314+
}
315+
}

0 commit comments

Comments
 (0)