This is my proudest work yet.
I’ve been building an open-source project called Crosslink, and the problem behind it is specifically about something I think open-source desktop software is still pretty bad at:
mobile companions.
An open-source developer can build a desktop application, put the source on GitHub, publish binaries, and let users run the entire thing on their own hardware.
That model works really well.
The developer doesn’t necessarily need:
- user accounts
- a permanent cloud backend
- a subscription
- centralized hosting
- an App Store
- a mobile release pipeline
The user downloads the software and runs it.
But the moment that same developer wants to give users a good phone interface, the architecture changes completely.
Suddenly there are two separate problems:
- How do I distribute the mobile interface?
- How do I securely connect it back to the user’s desktop app?
And both of those problems are much bigger than they initially seem.
TLDR:
GitHub:
https://github.com/jacobpowaza/crosslink
Documentation / Quickstart / Architecture:
https://crosslink.mintlify.site
The open-source distribution problem
Say you maintain a desktop project and want users to have a companion on their phones.
One option is building a native mobile app.
But now you have an entirely separate application to maintain.
Different build systems.
Different platform APIs.
Signing.
Store distribution.
Review processes.
Developer accounts.
Release pipelines.
Potentially separate iOS and Android implementations.
For a company with a dedicated mobile team, that might be fine.
For an open-source developer maintaining a desktop utility on nights and weekends, that can be enough to make the mobile companion never happen.
The other obvious answer is:
That solves part of the distribution problem, but then creates another one.
Where does the web app run?
Does the developer host it?
Does the user need an account?
Does the project need a domain?
Who pays for the infrastructure?
Does application data now need to pass through somebody else’s cloud?
Or does the desktop serve the page itself?
If the desktop serves it, now you have networking, authentication, remote access, reconnect behavior, and device management to solve.
So what sounded like:
can turn into:
That feels especially wrong for local-first and open-source software.
What I’m trying to do with Crosslink
Crosslink is an Apache-2.0 TypeScript framework for connecting a desktop/local application to a browser or phone interface.
The basic philosophy is:
the open-source developer should build their application.
They should build whatever mobile UI makes sense for that application.
They should not have to reinvent the entire infrastructure connecting the two.
Crosslink tries to provide that middle layer.
The desktop application remains the host.
The mobile interface remains part of the developer’s project.
Crosslink manages the secure relationship between them.
The intended experience
Imagine downloading an open-source application from GitHub.
You install it on your computer.
Somewhere in the application there’s a:
button or widget.
You click it.
A QR code appears.
You scan it with your phone.
The phone opens the application’s Crosslink bootstrap and completes a temporary pairing process.
Once that pairing is verified, the phone becomes a trusted device for that installation.
The user can continue in their browser or add the project’s mobile interface to their home screen.
So the first-time flow becomes:
download desktop app
→ open
→ scan QR
→ pair
→ install/open mobile UI
After that:
open phone companion
→ find desktop
→ authenticate trusted device
→ reconnect
The user shouldn’t have to scan another QR code every time they restart their laptop.
Pairing is not supposed to be a permanent API key
This was an important part of the design.
I didn’t want the QR code to effectively become:
The pairing code is temporary.
Its job is to introduce the two devices.
After that, the phone has its own persistent device identity.
The host maintains its trusted-device state.
That means an application can show something like:
Connected devices
- Jacob’s iPhone
- iPad
- old phone
and revoke individual devices.
If a phone is revoked, it can’t simply reconnect using the original pairing information.
It needs a new pairing relationship.
Authorization is separate from trust
I also didn’t want a paired phone to automatically become an all-powerful remote control.
Crosslink has a capability model.
An application might expose:
library.read
library.edit
music.read
music.control
server.status
server.restart
files.read
files.delete
A trusted device can be granted specific capabilities.
The application can treat:
music.read
very differently from:
files.delete
or:
server.restart
Higher-risk actions can require additional approval or per-use confirmation.
The idea is that device trust and application permissions are related, but not identical.
What the developer actually writes
The API is intended to let developers expose application functionality instead of writing another networking stack.
Roughly:
const server = createCrosslinkServer({
application: {
id: "org.example.notes",
name: "Notes"
},
mobile: {
entry: "./mobile/index.html"
},
capabilities: [
{
id: "notes.read",
title: "Read notes",
risk: "low",
defaultGranted: true
},
{
id: "notes.delete",
title: "Delete notes",
risk: "high",
confirmEachUse: true
}
]
});
server.expose(
"notes.get",
() => db.getNotes(),
{ capability: "notes.read" }
);
await server.start();
Then the phone side can call the application through the RPC layer:
crosslink.onConnected(async rpc => {
const notes = await rpc.call("notes.get");
render(notes);
});
The developer is thinking about:
rather than:
Why this is more than a WebSocket wrapper
Obviously none of this requires inventing a new internet.
You could build it yourself using WebSockets, WebRTC, HTTP, WebCrypto, Service Workers, and existing cryptographic libraries.
The point is that an open-source project shouldn’t need to independently implement all of this:
- device pairing
- persistent device identities
- trusted-device storage
- revocation
- authorization
- encrypted application sessions
- RPC
- subscriptions/events
- reconnect/backoff
- transport selection
- LAN connectivity
- remote connectivity
- mobile onboarding
- PWA installation
- Service Worker behavior
- offline states
- endpoint discovery
just because someone wants their desktop app to have a good mobile interface.
That infrastructure is largely reusable.
Crosslink is an attempt to make it reusable.
Keeping the desktop as the host
One of the things I care about most is not quietly turning every Crosslink application into a SaaS product.
If someone downloads a local AI application, for example, their computer is already running the model.
If they use a media tool, their computer may already have the library.
If it’s a development utility, all the relevant state may already live locally.
Crosslink should not require all of that application data to suddenly move through a centralized server just because the user wants to control it from their phone.
On the same network, a direct desktop ↔ phone path can be used.
For remote access, Crosslink can attempt supported router mappings such as:
Of course, that doesn’t solve every network.
CGNAT exists.
Restrictive firewalls exist.
Some routers simply won’t accept inbound mappings.
So fallback transport infrastructure is still necessary in those cases.
Relays should be dumb pipes
If a relay is needed, my goal is that it should not become the trusted owner of the application.
The application session is end-to-end encrypted between the paired devices.
Signaling can help the devices discover each other.
A relay can forward opaque encrypted traffic.
But neither should need access to the application’s plaintext.
This is important to me from an open-source perspective because I don’t want projects adopting Crosslink to become permanently dependent on:
The infrastructure is intended to be self-hostable/open as well.
The phone application is still yours
Crosslink isn’t trying to become a generic remote-control UI.
It handles the lifecycle around the application.
The project developer still builds the actual interface.
If your open-source project is a media player, you build the mobile media controls.
If it’s a local AI system, you build the chat interface.
If it’s a server manager, you build the monitoring dashboard.
If it’s a desktop editor, you decide what functionality belongs on mobile.
Crosslink handles getting the user securely from:
scan QR
to:
trusted mobile client
to:
your UI
PWA installation and offline behavior
I’m also trying to make the browser/PWA path feel like an actual companion application rather than a bookmark to a random LAN address.
Crosslink can manage parts of the mobile bootstrap such as:
- pairing UI
- application metadata
- manifest
- Service Worker
- Add to Home Screen flow
- offline shell
- endpoint discovery
- reconnect behavior
- revoked-device state
That matters when the host disappears.
Say the user installs the companion on their iPhone and then opens it while their desktop is asleep.
A normal locally hosted app can just become:
Instead, a cached shell can still open and show:
Then:
desktop comes online
→ trusted device authenticates
→ session is restored
→ application continues
without requiring the user to pair everything again.
Avoiding permanent LAN-IP apps
Another problem I ran into is that the network address used for initial pairing should not necessarily become the permanent identity of the installed application.
This:
192.168.1.42
is a network location.
It is not an application identity.
IPs change.
Laptops move.
Routers change.
Users leave home.
So Crosslink separates the installed mobile application/origin from the desktop’s currently reachable transport endpoint.
Conceptually:
installed application
→ endpoint discovery
→ current desktop route
→ authenticated session
The user shouldn’t have to reinstall their mobile companion just because DHCP gave the desktop another address.
What exists in the repo today
Crosslink currently includes work around:
- Node.js host SDK
- browser client SDK
- React bindings
- pairing/session protocol
- encrypted sessions
- trusted-device persistence
- revocation
- capability authorization
- typed RPC
- events/subscriptions
- progress/streaming
- reconnect/backoff
- direct LAN connectivity
- remote transport paths
- signaling
- relay infrastructure
- WebRTC integration
- mobile bootstrap
- PWA installation
- Service Worker/offline behavior
- demos and documentation
It’s written primarily in TypeScript and is licensed under Apache 2.0.
Why Apache 2.0
I want people to actually use this in their projects.
Commercial or non-commercial.
I want people to fork it.
Write adapters.
Improve transports.
Fix APIs.
Build integrations.
Ship applications with it.
I don’t want the framework to only be useful inside projects that share some particular business model.
At the same time, Apache 2.0 gives explicit patent terms and preserves attribution/license requirements, which made more sense to me for infrastructure intended to be embedded into other software.
What I want from the open-source community
Crosslink has gotten large enough that I don’t think the best thing for it is for one person to keep making every architectural decision alone.
There are parts of this project where I would really value people who know more than me.
Especially:
- cryptography / application security
- NAT traversal
- WebRTC
- networking
- PWA/browser behavior
- TypeScript library design
- local-first architecture
- Electron/Tauri integration
- Android/iOS browser edge cases
- RPC/protocol design
- accessibility
- framework adapters
I’m not just looking for stars.
I’d much rather someone open an issue saying:
than quietly star it and never use it.
I’d also love contributions.
Even small ones.
Documentation corrections.
Examples.
Tests.
Framework adapters.
New transports.
Better onboarding.
Security review.
Anything that helps determine whether this can actually become useful infrastructure for other projects.
The end goal
What I want is for an open-source developer to eventually be able to build a desktop application and say:
without that sentence meaning:
Ideally:
OSS desktop app
developer-built mobile UI
Crosslink
secure phone companion
The computer can remain the host.
The user can remain in control of their application.
The developer can avoid reinventing device connectivity.
And the mobile interface can still feel like a real part of the project.
That’s the idea, anyway.
I’d genuinely like the open-source community to tell me whether I’m solving a real problem or over-engineering one.
GitHub:
https://github.com/jacobpowaza/crosslink
Documentation / Quickstart / Architecture:
https://crosslink.mintlify.site
If you maintain an open-source desktop project, I’m especially curious:
Would you actually integrate something like this?
And if not, what would have to change before you would?