Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
103 changes: 94 additions & 9 deletions .github/workflows/reusable-bindings-build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,23 @@ jobs:
task bindings:build:all TARGET=${{ matrix.platform.target }} PROFILE=release
- name: Strip and rename artifacts
run: |
ls -lah ../../target/${{ matrix.platform.target }}/release/

# Split target and use in filename
# Cargo output path: zigbuild uses versioned target (e.g. .2.28) for linux-gnu;
# native build with --target uses unversioned path. Detect which exists.
TARGET="${{ matrix.platform.target }}"
if [[ "$TARGET" == *"-linux-gnu" ]]; then
if [ -d "../../target/${TARGET}.2.28/release" ]; then
CARGO_TARGET="${TARGET}.2.28"
else
CARGO_TARGET="$TARGET"
fi
else
CARGO_TARGET="$TARGET"
fi

RELEASE_DIR="../../target/${CARGO_TARGET}/release"
ls -lah "$RELEASE_DIR/"

# Split target (logical name) for library filename
ARCH=$(echo "$TARGET" | cut -d'-' -f1)
OS=$(echo "$TARGET" | cut -d'-' -f3)
ABI=$(echo "$TARGET" | cut -d'-' -f4)
Expand All @@ -172,20 +185,23 @@ jobs:
LIB_NAME="slim_bindings_${ARCH}_${OS}"
fi

# Handle both libslim_bindings* (Unix) and slim_bindings* (Windows)
for file in ../../target/${{ matrix.platform.target }}/release/libslim_bindings* ../../target/${{ matrix.platform.target }}/release/slim_bindings.*; do
# Rename in place in the cargo output directory
for file in "$RELEASE_DIR"/libslim_bindings* "$RELEASE_DIR"/slim_bindings.*; do
if [ -f "$file" ]; then
filename=$(basename "$file")

# Replace slim_bindings with arch_os_abi version, keeping any existing prefix
new_filename="${filename/slim_bindings/${LIB_NAME}}"

if [ "$filename" != "$new_filename" ]; then
mv "$file" "../../target/${{ matrix.platform.target }}/release/${new_filename}"
mv "$file" "$RELEASE_DIR/${new_filename}"
fi
fi
done

# If cargo used a versioned target, move renamed files to the path the rest of the workflow expects
if [ "$CARGO_TARGET" != "$TARGET" ]; then
mkdir -p "../../target/${TARGET}/release"
mv "$RELEASE_DIR"/*slim_bindings* "../../target/${TARGET}/release/"
fi

echo "📁 Renamed files:"
ls -lah ../../target/${{ matrix.platform.target }}/release/*slim_bindings*
- name: Upload libs
Expand Down Expand Up @@ -408,3 +424,72 @@ jobs:
run: |
echo "✅ Python wheel verification completed successfully on ${{ matrix.platform.target }}"
echo "The wheel can be safely published."

node-bindings-test:
name: Test Node.js Bindings
runs-on: ubuntu-24.04
needs:
- build-libraries
defaults:
run:
shell: bash
working-directory: ./data-plane/bindings/node
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
workspace: ./data-plane

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install Task
run: |
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin

- name: Install uniffi-bindgen-node
run: |
rustup install stable --no-self-update 2>/dev/null || true
rustup run stable cargo install --git https://github.com/livekit/uniffi-bindgen-node.git --tag [email protected]

- name: Download libraries
uses: actions/download-artifact@v4
with:
pattern: bindings-x86_64-unknown-linux-gnu
path: ./data-plane/target/x86_64-unknown-linux-gnu/release/
merge-multiple: true

- name: List downloaded artifacts
run: |
echo "📦 Downloaded artifacts:"
find ../../target/x86_64-unknown-linux-gnu/release/ -type f -ls

- name: Generate Node.js bindings
run: |
task generate TARGET=x86_64-unknown-linux-gnu

- name: Install test dependencies
run: |
task install:tests

- name: Install catchsegv (for backtrace on SIGSEGV)
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq glibc-tools

- name: Run Node.js integration tests
env:
RUST_BACKTRACE: full
run: |
echo "🧪 Running Node.js integration tests..."
catchsegv task test:integration

- name: Test result summary
if: success()
run: |
echo "✅ Node.js bindings integration tests completed successfully"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ dmypy.json
# VS Code
.vscode/

# Cursor
.cursor/

# Ropeproject
.ropeproject

Expand Down
2 changes: 2 additions & 0 deletions data-plane/_typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ extend-exclude = [
"**/*.pem",
"**/*.yaml",
"bindings/go/generated/**",
"**/*.pbxproj",
"**/*.pbxproj.bak",
]
30 changes: 30 additions & 0 deletions data-plane/bindings/node/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Generated bindings
generated/

# TypeScript
dist/
*.tsbuildinfo

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Test coverage
coverage/
.nyc_output/

# Logs
*.log
79 changes: 79 additions & 0 deletions data-plane/bindings/node/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# SLIM Node.js Bindings

Node.js bindings for SLIM using UniFFI.
Bindings generated with [uniffi-bindgen-node](https://github.com/livekit/uniffi-bindgen-node).

## Prerequisites

- Rust toolchain
- Node.js >= 18
- [Task](https://taskfile.dev/)

## Usage

### 1. Generate Bindings

```bash
task generate
```

### 2. Run P2P Examples

```bash
# Terminal 1: Start the server
task example:server

# Terminal 2: Start Alice (receiver)
task example:alice

# Terminal 3: Start Bob (sender)
task example:bob
```

### Available Commands

```bash
task generate # Generate bindings
task clean # Clean build artifacts
task example:server # Run server
task example:alice # Run Alice receiver
task example:bob # Run Bob sender
```

## Build Process

The bindings generation includes patching to fix compatibility issues between `uniffi-bindgen-node` (code generator) and `uniffi-bindgen-react-native` (runtime library):

- **Naming fixes**: `FfiConverterBytes` → `FfiConverterArrayBuffer`
- **Buffer wrapping**: Proper RustBuffer allocation for byte arrays
- **Pointer conversions**: BigInt → Number for FFI calls
- **Error handling**: Enhanced error extraction from Rust

## FFI Type Conversions

The generated bindings use `bigint` in TypeScript signatures for u64 types, but the underlying FFI layer returns JavaScript `number` types at runtime. Application code handles conversions at API boundaries:

```typescript
// connectAsync returns a number at runtime, despite bigint type signature
const connId = await service.connectAsync(config);

// Convert to BigInt when passing to methods expecting bigint
await app.subscribeAsync(name, BigInt(connId));

// When using with methods that need number (for direct FFI calls)
app.setRoute(name, Number(connId));
```

**Key conversions:**
- `connectAsync` returns `number` → convert to `BigInt()` for TypeScript bigint parameters
- When calling FFI methods with u64 params → convert to `Number()` if passing bigint

This explicit conversion at API boundaries ensures type safety and makes FFI requirements visible.


## Resources

- [uniffi-bindgen-node](https://github.com/livekit/uniffi-bindgen-node)
- [UniFFI Documentation](https://mozilla.github.io/uniffi-rs/)
- [ffi-rs](https://www.npmjs.com/package/ffi-rs)
- [SLIM Core](https://github.com/agntcy/slim)
Loading
Loading