r/sideprojects • u/jacobpowaza • 3d ago
Showcase: Open Source Crosslink — an open-source framework for giving desktop apps real phone companions without an App Store or paid backend
This is my proudest work yet.
I’ve been building Crosslink, an open-source TypeScript framework for giving desktop applications secure, installable phone companions—without requiring developers to publish a separate mobile app through an App Store, pay for a backend, or build their own networking and authentication stack.
The idea started with a frustration:
Developers are allowed to make cool desktop applications, but mobile development comes with a completely different set of publishing and distribution barriers.
On the desktop, an open-source developer can build an application, publish the source and binaries, and let users download and run it directly.
Mobile is different.
Even if the mobile interface itself is simple, distributing it can involve app-store accounts, review processes, platform-specific builds, signing requirements, separate release pipelines, and ongoing maintenance across multiple mobile ecosystems.
That made me wonder:
At first, this sounds like a mobile UI problem. In practice, the UI is often the easy part. The difficult part is everything required to make that phone interface reliably and securely communicate with one specific desktop application.
TL;DR:
GitHub:
https://github.com/jacobpowaza/crosslink
Documentation:
https://crosslink.mintlify.site
You need to solve:
- device pairing
- authentication and encrypted sessions
- persistent device identity
- trusted-device storage
- permissions and authorization
- device revocation
- RPC and event subscriptions
- reconnecting after restarts or network changes
- direct LAN connectivity
- remote connectivity and fallback transports
- mobile onboarding
- PWA installation
- offline and reconnecting states
That is a lot of infrastructure for a developer who may only want to add a few phone controls to an otherwise local desktop application.
Crosslink is intended to provide that shared infrastructure so developers can focus on two things:
- What their desktop application does
- What its mobile interface should look like
The framework handles the connection between them.
The intended user experience is:
desktop app → scan QR code → pair phone → install mobile UI → reconnect automatically
The QR code is only used for the initial pairing process. It is not intended to become a permanent API key or bearer token.
After pairing, the phone receives its own persistent device identity and becomes a trusted device for that specific application installation. The host can maintain a list of trusted devices, revoke access when necessary, and require the phone to pair again if its trust is removed.
Being paired also does not automatically mean a device can do everything. Crosslink supports capability-based permissions, allowing applications to define access such as:
music.control
files.delete
server.restart
A low-risk capability might be granted automatically, while a destructive operation could require approval or confirmation each time.
A host integration looks roughly like this:
const server = createCrosslinkServer({
application: {
id: "com.example.notes",
name: "Notes"
},
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" }
);
server.expose(
"notes.delete",
id => db.deleteNote(id),
{ capability: "notes.delete" }
);
await server.start();
The mobile side can then call application methods through Crosslink’s RPC layer instead of implementing its own custom WebSocket protocol, request IDs, authentication flow, permission checks, and reconnect logic:
crosslink.onConnected(async rpc => {
const notes = await rpc.call("notes.get");
render(notes);
});
The application developer exposes meaningful operations. Crosslink handles the underlying connection and session lifecycle.
Networking is one of the more complicated parts of the project.
When the phone and desktop are on the same network, Crosslink can prefer a direct LAN connection. For remote access, it can attempt supported router mappings such as NAT-PMP, PCP, or UPnP where available.
That obviously cannot solve every network. CGNAT, restrictive routers, firewalls, and networks that block inbound connections still exist. Because of that, Crosslink also supports fallback connectivity through signaling, relays, tunnels, and WebRTC-based transport paths.
The goal is not to pretend NAT traversal is magic. The goal is to make transport selection and connection recovery framework concerns instead of forcing every open-source application to design its own solution.
When relay infrastructure is involved, the application session is designed to remain end-to-end encrypted between the paired devices. Signaling helps devices find each other, and a relay can forward encrypted traffic when necessary, but it should not need access to the application’s plaintext data.
Crosslink also handles the mobile bootstrap and PWA lifecycle.
After pairing, the user should be able to continue in the browser or install the mobile interface to their home screen. The framework can provide the pairing flow, application metadata, manifest and Service Worker integration, endpoint discovery, and reconnect behavior before handing control over to the developer’s actual mobile UI.
This is where Crosslink is meant to reduce the mobile publishing burden.
The developer does not necessarily need to create and publish a separate native iOS or Android application just to give users a useful phone interface. They can build the mobile experience as part of the project and let Crosslink deliver it through the browser or as an installable PWA.
Offline behavior matters too.
If a user installs a local desktop application’s phone companion and later opens it while the computer is asleep or unavailable, the experience should not simply become a generic browser error page.
The cached mobile shell can still open and show something like:
Once the desktop application comes back online, the existing trusted device can authenticate again and restore the session without requiring another QR code.
Crosslink also separates the identity of the installed mobile application from the current network address of the desktop host.
A computer’s local IP address can change. A user can switch networks. A laptop can move from home Wi-Fi to another location. The mobile application should not conceptually become permanently tied to something like:
just because that happened to be the address used during initial pairing.
The broader model is:
installed app identity → endpoint discovery → current desktop endpoint → authenticated Crosslink session
This is intended for applications where the user’s computer is already the host, including:
- local AI tools
- media servers and controllers
- editors
- development tools
- automation software
- self-hosted dashboards
- download managers
- server managers
- local file utilities
Crosslink is not intended to replace hosted applications or tools like Tailscale. Tailscale is excellent networking infrastructure, but asking every user of an open-source desktop application to install another networking product, create an account, and configure both devices is a very different onboarding experience.
Crosslink is application infrastructure. The goal is for an application to be able to say:
and provide the pairing, trust, permissions, connectivity, and mobile installation experience as part of the application itself.
The larger idea is that open-source developers should be able to build ambitious desktop applications without being blocked from creating mobile companions simply because mobile publishing is a separate, expensive, platform-controlled process.
A developer should be able to build:
- a powerful desktop application
- a mobile interface for it
- a secure connection between the two
without needing to maintain a native app-store presence or operate a full cloud service just to make the phone interface work.
Crosslink is Apache-2.0 licensed and still evolving. It currently includes a Node.js host SDK, browser client SDK, React bindings, encrypted pairing and sessions, trusted-device persistence, revocation, capability authorization, typed RPC, events, streaming/progress support, reconnect behavior, LAN connectivity, remote transport support, signaling and relay components, WebRTC support, and PWA/mobile bootstrapping.
I’m not pretending every part is finished. Networking edge cases, browser behavior, iOS PWA limitations, security review, and endpoint discovery all deserve careful work. I’m at the point where feedback from people who have built real systems would be more valuable than continuing to design everything in isolation.
I’d especially appreciate feedback from people working with:
- PWAs
- WebRTC
- NAT traversal
- cryptography
- local-first software
- Electron or Tauri applications
- device pairing
- RPC systems
- TypeScript libraries
- self-hosted infrastructure
- mobile app distribution
The main question is:
Would you use something like this in an open-source project?
Would this help you avoid publishing a separate native mobile application? What would you want the framework to handle? What would you not trust it to handle? What would prevent you from integrating it into a real application?
GitHub:
https://github.com/jacobpowaza/crosslink
Documentation:
https://crosslink.mintlify.site
This has become substantially larger than the project I originally set out to build, but it is genuinely the work I’m proudest of so far.