Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
64f9c96
Init livekit-uniffi crate
ladvoc Oct 16, 2025
9f85820
Configure
ladvoc Oct 16, 2025
24110d9
Expose access token module
ladvoc Oct 16, 2025
794c5d2
Add placeholder config file
ladvoc Oct 16, 2025
5736d45
Allow omitting token options
ladvoc Oct 16, 2025
ea7a068
Add python test
ladvoc Oct 16, 2025
310b632
Use fully qualified names for macros
ladvoc Oct 18, 2025
627b91f
Don't commit dylib and pycache
ladvoc Oct 18, 2025
eef2ad7
Implement log forwarding
ladvoc Oct 18, 2025
734245e
Expose build info
ladvoc Oct 19, 2025
5fbdedd
Allow multiple invocations
ladvoc Oct 21, 2025
c658395
Change log level in example
ladvoc Oct 21, 2025
82f38c8
Remove debug
ladvoc Oct 21, 2025
f0173ba
Introduce Makefile
ladvoc Nov 19, 2025
df14e25
License check
ladvoc Nov 20, 2025
b3835e3
Handle multi-platform builds
ladvoc Nov 20, 2025
e07b2d5
Regenerate bindings
ladvoc Nov 20, 2025
48385c5
Update README
ladvoc Nov 20, 2025
c004438
Fix typo
ladvoc Nov 20, 2025
ba06af5
Format
ladvoc Nov 20, 2025
3faab50
Parse unverified JWT (#756)
pblazej Nov 3, 2025
bc14007
Fix tier3 warnings
pblazej Nov 20, 2025
8612dd0
Move to cargo-swift
pblazej Dec 11, 2025
0249813
Merge branch 'main' into ladvoc/uniffi-trial
pblazej Dec 11, 2025
c6a5f55
Expose room config
pblazej Dec 11, 2025
3f9e0fd
Platforms
pblazej Dec 12, 2025
347b046
Local Package.swift
pblazej Dec 12, 2025
5c5af74
Remote Package.swift
pblazej Dec 12, 2025
4d82c9d
Fmt
pblazej Dec 12, 2025
5a4283a
Remove packages and generated code
pblazej Dec 12, 2025
e1df4af
Readme
pblazej Dec 12, 2025
729bc10
Static lib
pblazej Dec 12, 2025
af133b4
feat: add node bindgen packaging code
1egoman Dec 12, 2025
e9e72c1
fix: swap license header block format to please hawkeye
1egoman Dec 12, 2025
5f0c9da
fix: remove undefined fields thanks to https://github.com/livekit/uni…
1egoman Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 310 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"livekit-api",
"livekit-protocol",
"livekit-ffi",
"livekit-uniffi",
"livekit-runtime",
"libwebrtc",
"soxr-sys",
Expand All @@ -26,7 +27,7 @@ members = [
"examples/screensharing",
"examples/send_bytes",
"examples/webhooks",
"examples/wgpu_room",
"examples/wgpu_room"
]

[workspace.dependencies]
Expand Down
2 changes: 2 additions & 0 deletions licenserc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ excludes = [
"NOTICE",
".github",
"*.toml",
"!Makefile.toml",
"*.txt",
# Generated files
"livekit-protocol/src/livekit.rs",
"livekit-protocol/src/livekit.serde.rs",
"livekit-uniffi/packages"
]

[mapping.DOUBLESLASH_STYLE]
Expand Down
66 changes: 61 additions & 5 deletions livekit-api/src/access_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ pub struct Claims {
pub room_config: Option<livekit_protocol::RoomConfiguration>,
}

impl Claims {
pub fn from_unverified(token: &str) -> Result<Self, AccessTokenError> {
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
validation.validate_exp = true;
validation.validate_nbf = true;
validation.set_required_spec_claims::<String>(&[]);
validation.insecure_disable_signature_validation();

let token =
jsonwebtoken::decode::<Claims>(token, &DecodingKey::from_secret(&[]), &validation)?;

Ok(token.claims)
}
}

#[derive(Clone)]
pub struct AccessToken {
api_key: String,
Expand Down Expand Up @@ -299,10 +314,6 @@ impl TokenVerifier {
pub fn verify(&self, token: &str) -> Result<Claims, AccessTokenError> {
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
validation.validate_exp = true;
#[cfg(test)] // FIXME: TEST_TOKEN is expired, TODO: generate TEST_TOKEN at test runtime
{
validation.validate_exp = false;
}
validation.validate_nbf = true;
validation.set_issuer(&[&self.api_key]);

Expand All @@ -320,7 +331,7 @@ impl TokenVerifier {
mod tests {
use std::time::Duration;

use super::{AccessToken, TokenVerifier, VideoGrants};
use super::{AccessToken, Claims, TokenVerifier, VideoGrants};

const TEST_API_KEY: &str = "myapikey";
const TEST_API_SECRET: &str = "thiskeyistotallyunsafe";
Expand Down Expand Up @@ -383,4 +394,49 @@ mod tests {
claims
);
}

#[test]
fn test_unverified_token() {
let claims = Claims::from_unverified(TEST_TOKEN).expect("Failed to parse token");

assert_eq!(claims.sub, "identity");
assert_eq!(claims.name, "name");
assert_eq!(claims.iss, TEST_API_KEY);
assert_eq!(
claims.room_config,
Some(livekit_protocol::RoomConfiguration {
agents: vec![livekit_protocol::RoomAgentDispatch {
agent_name: "test-agent".to_string(),
metadata: "test-metadata".to_string(),
}],
..Default::default()
})
);

let token = AccessToken::with_api_key(TEST_API_KEY, TEST_API_SECRET)
.with_ttl(Duration::from_secs(60))
.with_identity("test")
.with_name("test")
.with_grants(VideoGrants {
room_join: true,
room: "test-room".to_string(),
..Default::default()
})
.to_jwt()
.unwrap();

let claims = Claims::from_unverified(&token).expect("Failed to parse fresh token");
assert_eq!(claims.sub, "test");
assert_eq!(claims.name, "test");
assert_eq!(claims.video.room, "test-room");
assert!(claims.video.room_join);

let parts: Vec<&str> = token.split('.').collect();
let malformed_token = format!("{}.{}.wrongsignature", parts[0], parts[1]);

let claims = Claims::from_unverified(&malformed_token)
.expect("Failed to parse token with wrong signature");
assert_eq!(claims.sub, "test");
assert_eq!(claims.name, "test");
}
}
2 changes: 1 addition & 1 deletion livekit-api/src/test_token.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibmFtZSIsInZpZGVvIjp7InJvb21Kb2luIjp0cnVlLCJyb29tIjoibXktcm9vbSIsImNhblB1Ymxpc2giOnRydWUsImNhblN1YnNjcmliZSI6dHJ1ZSwiY2FuUHVibGlzaERhdGEiOnRydWV9LCJyb29tQ29uZmlnIjp7ImFnZW50cyI6W3siYWdlbnROYW1lIjoidGVzdC1hZ2VudCIsIm1ldGFkYXRhIjoidGVzdC1tZXRhZGF0YSJ9XX0sInN1YiI6ImlkZW50aXR5IiwiaXNzIjoibXlhcGlrZXkiLCJuYmYiOjE3NDE4Njk2NzksImV4cCI6MTc0MTg5MTI3OX0.9_eOcZ1RNaRXxwdU36LTYoxsHtv_IhfhLk-kTd8MDY4
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibmFtZSIsInZpZGVvIjp7InJvb21Kb2luIjp0cnVlLCJyb29tIjoibXktcm9vbSIsImNhblB1Ymxpc2giOnRydWUsImNhblN1YnNjcmliZSI6dHJ1ZSwiY2FuUHVibGlzaERhdGEiOnRydWV9LCJyb29tQ29uZmlnIjp7ImFnZW50cyI6W3siYWdlbnROYW1lIjoidGVzdC1hZ2VudCIsIm1ldGFkYXRhIjoidGVzdC1tZXRhZGF0YSJ9XX0sInN1YiI6ImlkZW50aXR5IiwiaXNzIjoibXlhcGlrZXkiLCJuYmYiOjE3NDE4Njk2NzksImV4cCI6NDEwMjQ0NDgwMH0.hmKovgw6sDbQUFZxAQ9Xjb9-VUzeBw1A6URTXFuCLMU
2 changes: 2 additions & 0 deletions livekit-uniffi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
generated/
packages/
34 changes: 34 additions & 0 deletions livekit-uniffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[lib]
crate-type = ["cdylib", "staticlib", "lib"]

[package]
name = "livekit-uniffi"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
description = "Experimental FFI interface using UniFFI"
repository = "https://github.com/livekit/rust-sdks"
readme = "README.md"

[dependencies]
livekit-protocol = { workspace = true }
livekit-api = { workspace = true }
uniffi = { version = "0.30.0", features = ["cli", "scaffolding-ffi-buffer-fns"] }
log = "0.4.28"
tokio = { version = "1.48.0", features = ["sync"] }
once_cell = "1.21.3"

[build-dependencies]
uniffi = { version = "0.30.0", features = ["build", "scaffolding-ffi-buffer-fns"] }

[[bin]]
name = "uniffi-bindgen"
path = "bindgen.rs"

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = "symbols"
debug = false
206 changes: 206 additions & 0 deletions livekit-uniffi/Makefile.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Copyright 2025 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[env]
PACKAGES_DIR = "./packages"
SUPPORT_DIR = "./support"
LIB_NAME = "lib${CARGO_MAKE_CRATE_FS_NAME}"
TARGET_DIR = "${CARGO_MAKE_CRATE_TARGET_DIRECTORY}"
SPM_NAME = "LiveKitUniFFI"

[config]
skip_core_tasks = true

# MARK: - Build

[tasks.build.env.TARGET]
value = "${CARGO_MAKE_RUST_TARGET_TRIPLE}"
condition = { env_not_set = ["TARGET"] }
# Set to target triple of the current platform if not explicitly set.

[tasks.build]
clear = true
script_runner = "@duckscript"
script = """
echo "TARGET: ${TARGET}"

if not ${TIER_3}
# Ensure target is installed
exec rustup target add ${TARGET}
exec cargo build --release --target ${TARGET}
else
# Ensure nightly toolchain is installed
exec rustup component add rust-src --toolchain "nightly-${CARGO_MAKE_RUST_TARGET_TRIPLE}"
exec cargo +nightly build -Zbuild-std=std,panic_abort --release --target ${TARGET}
end
"""

# MARK: - Build, specific platforms

[tasks.build-windows-x64]
extend = "build"
env = { TARGET = "x86_64-pc-windows-msvc" }

[tasks.build-windows-aarch64]
extend = "build"
env = { TARGET = "aarch64-pc-windows-msvc" }

[tasks.build-linux-aarch64]
extend = "build"
env = { TARGET = "aarch64-unknown-linux-gnu" }

[tasks.build-linux-x64]
extend = "build"
env = { TARGET = "x86_64-unknown-linux-gnu" }

[tasks.build-android-aarch64]
extend = "build"
env = { TARGET = "aarch64-linux-android" }

[tasks.build-android-armv7]
extend = "build"
env = { TARGET = "armv7-linux-androideabi" }

[tasks.build-android-x64]
extend = "build"
env = { TARGET = "x86_64-linux-android" }

# MARK: - Build, platform groups

[tasks.build-windows-platforms]
description = "Build for Windows"
dependencies = [
"build-windows-x64",
"build-windows-aarch64"
]

[tasks.build-linux-platforms]
description = "Build for generic Linux and Android"
dependencies = [
"build-linux-aarch64",
"build-linux-x64",
"build-android-aarch64",
"build-android-armv7",
"build-android-x64"
]

# MARK: - Bindgen common

[tasks.locate-lib.env.LIB_EXT]
source = "${CARGO_MAKE_RUST_TARGET_OS}"
mapping = { linux = "so", macos = "dylib", windows = "dll" }

[tasks.locate-lib]
private = true
script_runner = "@duckscript"
script = """
path = join_path ${TARGET_DIR} ${TARGET} release/ ${LIB_NAME}.${LIB_EXT}
set_env LIB_PATH "${path}"
"""

[tasks.uniffi-bindgen]
private = true
description = "Generate bindings for core UniFFI languages"
dependencies = ["build", "locate-lib"]
script_runner = "@duckscript"
script = """
out_dir = join_path ${PACKAGES_DIR} ${LANG}
exec cargo run --bin uniffi-bindgen generate \
--language ${LANG} \
--out-dir ${out_dir} \
--library ${LIB_PATH}
"""

[tasks.bindgen]
dependencies = [
"bindgen-kotlin",
"bindgen-python"
]

# MARK: - Kotlin

[tasks.bindgen-kotlin]
private = false
extend = "uniffi-bindgen"
env = { LANG = "kotlin" }

# MARK: - Python

[tasks.bindgen-python]
private = false
extend = "uniffi-bindgen"
env = { LANG = "python" }

# MARK: - Swift

[tasks.swift-xcframework.env]
SWIFT_RELEASE_FLAG = { value = "--release", condition = { profiles = ["release"] } }

[tasks.swift-xcframework]
private = true
install_crate = { crate_name = "cargo-swift", version = "0.10.0", binary = "cargo-swift", test_arg = "--help" }
install_crate_args = ["--force"]
script_runner = "@shell"
script = """
cargo swift package \
--accept-all \
--name ${SPM_NAME} \
--xcframework-name Rust${SPM_NAME} \
--platforms macos \
--platforms ios \
--platforms maccatalyst \
--platforms visionos \
--platforms tvos \
--lib-type dynamic \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And one more thing @ladvoc static release is still ~40MB whereas dylib ~1.8MB.

Dynamic one won't pass AppStore validation (we'd need to wrap it in a Framework).

90426: Invalid Swift Support. The SwiftSupport folder is missing. Rebuild your app using the current public (GM) version of Xcode and resubmit it.

I'd rather go for stripping the static one further, any ideas?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iOS in --release profile:

Ar Archive - 47 MB

Copy link

@pblazej pblazej Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looks like DEAD_CODE_STRIPPING does the job and the bundle size increase is negligible (0.1 MB), moved back to static 729bc10

It also passes 🍏 validation 👍

I'm still open to optimization ideas 😄

${SWIFT_RELEASE_FLAG}
"""

[tasks.swift-generate-manifest.env]
SPM_URL = { value = "https://dummy.com" }
SPM_CHECKSUM = { value = "1234" }

[tasks.swift-generate-manifest]
private = true
install_crate = { crate_name = "tera-cli", binary = "tera", test_arg = "--version" }
script_runner = "@shell"
script = """
tera --env --file ${SUPPORT_DIR}/${LANG}/Package.swift.tera > ${SPM_NAME}/Package.swift
tera --env --file ${SUPPORT_DIR}/${LANG}/[email protected] > ${SPM_NAME}/[email protected]
"""

[tasks.swift-move-to-packages]
private = true
script_runner = "@shell"
script = """
out_dir="${PACKAGES_DIR}/${LANG}"
rm -rf "${out_dir}"
mkdir -p "${out_dir}"
mv "${SPM_NAME}" "${out_dir}"
"""

[tasks.swift-package-flow]
private = true
dependencies = [
"swift-xcframework",
"swift-generate-manifest",
"swift-move-to-packages"
]

[tasks.swift-package]
description = "Generate Swift package using cargo-swift"
env = { LANG = "swift" }
run_task = "swift-package-flow"

# Add other bindgen tasks here, following the same pattern
# as above for the task name (e.g., "bindgen-<lang>").
22 changes: 22 additions & 0 deletions livekit-uniffi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# LiveKit UniFFI

Experimental FFI interface using [UniFFI](https://mozilla.github.io/uniffi-rs/latest/).

At this stage in development, this interface will not attempt to replace the existing FFI interface defined in [_livekit-ffi_](../livekit-ffi/). Instead, it will focus on exposing core business logic that can be cleanly modularized and adopted by client SDKs incrementally.

## Functionality exposed

- [x] Access token generation and verification

## Tasks

Binding generation and multi-platform builds are handled by [_cargo-make_](https://github.com/sagiegurari/cargo-make)—please install on your system before proceeding. For a full list of available tasks, see [_Makefile.toml_](./Makefile.toml) or run `cargo make --list-all-steps`. The most important tasks are summarized below:

### Swift

Generate Swift bindings and build a multi-platform XCFramework:
```
cargo make xcframework
```

TODO: other languages
Loading