Building Current (#1) · CurrentAI disclosure: No LLMs were used in the process of writing this blog post.<br>LLMs were used in the process of making the software described in this blog post, but it is not "vibe-coded".
Current is a website (and very soon, a macOS/iOS app) that makes it really simple to send someone a file of any size.<br>It is constructed out of Rust, TypeScript, and Swift, and builds on the fabulous work of the iroh team.
The Problem
I run a DJ collective, and part of doing that involves juggling tens of gigabytes of video files: recorded sets, promotional content, etc. Often times, some of the content will be recorded on a camera that is not owned by me, and we each take our own equipment home.
Then, the only practical ways to get these types of files are:
Meet up in person and copy to my hard drive using a fancy USB-C cable
Wait multiple hours while the files upload to cloud storage (that I am paying a subscription for), then download them
When I'm done editing a video, it's sometimes small enough to send to my teammates over iMessage so they can quickly take a look. But even then, iMessage still compresses the video aggressively, so I have to deliver videos to the person who will upload them using cloud storage again.
I can't let people upload this to Instagram.<br>All of this is inconvenient, slow, and requires me to pay for hundreds of gigabytes of cloud storage.
The Solution
For a while, I had been aware of a tool from n0-computer called sendme that kind of solves this problem.<br>It can be used to send a file from one Internet-connected computer to nearly any other with zero configuration.
However, sendme is a terminal application. People who are not programmers (or similar) will not use this. And it won't work on a phone, which is where a lot of the files I want to move originate.
Current is a web/native interface for the iroh-blobs protocol that powers sendme. It consists of the following components:
current_core: A Rust crate that wraps iroh-blobs, tracks state for transfers, and exposes FFI bindings
@current/core: A Bun package which wraps current_core after it has been compiled to WebAssembly
@current/web: A TanStack Start website which uses @current/core to transfer files
Current-macOS, Current-iOS: A cross-platform Swift UI built over current_core (not released yet)
The Trials
This blog post is for those who are curious about how this software was made. Here, I will provide an overview of what I have done, and write more blog posts in the future with deep dives about specific things that I learned from each technical challenge.
1. Complex FFI across multiple targets
The FFI interface of current_core is not so simple. To provide a pleasant user experience, we need to support:
Detailed progress reporting: users need to know whether the transfer is working, and how long it's going to take
Cancellation: users might change their mind about allowing a transfer to finish
Additionally, current_core needs to compile to both WebAssembly and to aarch64, and we need somewhat different FFI conventions for these architectures.
To handle the task of creating a consistent FFI API across these platforms, I picked BoltFFI. It also promised better performance than wasm-bindgen, but I was mainly concerned with trying to dodge the iteration and maintenance burden of developing two separate FFI APIs, along with their corresponding bindings in TypeScript, Swift, and other languages.
The API that I landed on looks something like this:
pub struct Transfer {<br>// these fields are public in Rust, but Transfer is opaque to foreign code<br>id: TransferId,<br>cancel: CancellationToken,<br>callbacks: Arcdyn TransferCallbacks>,<br>// ...
impl Transfer {<br>pub fn id(&self) -> TransferId;<br>pub fn cancel(&self);<br>pub fn is_cancelled(&self) -> bool;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Display)]<br>pub struct TransferId {<br>// Newtypes aren't easy to represent 1:1 in other languages, so I just ended up<br>// using a single-member struct<br>pub inner: u64,
#[derive(Clone)]<br>pub struct Sender {<br>// ...
impl Sender {<br>pub async fn new(endpoint: Endpoint) -> ResultSelf, TransferError>;
/// import from one filesystem root (file or directory tree), build ticket, publish until `unpublish`<br>#[cfg(not(target_arch = "wasm32"))]<br>pub async fn publish_paths(<br>&self,<br>paths: VecString>,<br>sender_callbacks: Arcdyn SenderCallbacks>,<br>cancel: OptionCancelToken>,<br>) -> ResultTicket, TransferError>;
/// import from a host-driven stream source (e.g. wasm file inputs), then publish like `publish_paths`<br>pub async fn publish_streams(<br>&self,<br>source: Boxdyn StreamSource>,<br>sender_callbacks: Arcdyn SenderCallbacks>,<br>cancel: OptionCancelToken>,<br>) -> ResultTicket, TransferError>;
/// stop serving: reject new blob connections until next publish; closes active QUIC connections<br>pub fn unpublish(&self);
/// router shutdown + blob dir cleanup; call before `Endpoint::close` on app exit<br>pub async fn shutdown(&self) ->...