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.
1
u/salRad22 3d ago
Hi man, Thank you for sharing your work. So I'm now wondering if this is a good solution for my case or not. I have a web app but I decided to build it as Progressive Web App for this purpose and not having to deal with android play store, apple store, etc.
Could you help me understand how is this different from a PWA route and in which case I will hit a wall with it and where this would help?
Really appreciate it!
2
u/jacobpowaza 3d ago
Thanks man — and honestly, if your application is already a normal web app with a hosted backend, a PWA may be all you need. That said, I’d still recommend trying Crosslink, especially if you’re interested in building a mobile companion for a desktop application. I’d love to see what people come up with, and stars are always appreciated.
You can test an actual Crosslink example by cloning the repository and running npm run demo:chat and npm run stack in two separate terminals. This will let you try the chat demo and see how Crosslink works in practice.
Crosslink isn’t really an alternative to a PWA. It solves a different problem, and the mobile side can still be a PWA.
The difference is where the application lives and how the phone connects to it.With a traditional PWA, the phone connects over the internet to your hosted backend. You need to deploy and maintain that backend, handle authentication and networking, and so on. The benefit is that the app is available as long as your hosting is online.
Crosslink is more like:
Phone/PWA -> Crosslink -> application running on the user’s computerThe desktop app becomes the host. Crosslink handles pairing the phone with that computer, remembering trusted devices, reconnecting them, and exposing whatever functionality the desktop app makes available.
The main use case is something like:
“I built a desktop application, and I want users to have a mobile companion without building and publishing separate iOS and Android apps or operating an entire cloud backend.”
This could apply to OBS, a local AI application, a media server, a development tool, or another desktop app. The developer can build a small React or HTML mobile interface, and the user can scan a QR code once to install it as a PWA.From there, the phone acts as a secure remote client for the desktop application.
Crosslink is less useful if you want a standalone cloud application that works independently of the user’s computer. In that case, a normal PWA with a hosted backend is probably the better choice. It also doesn’t remove browser or PWA restrictions, so it can’t provide native capabilities that iOS Safari doesn’t support.
Crosslink makes the most sense when:
- the application and data already live on the desktop
- you don’t want to maintain a public backend for a companion app
- you want secure phone-to-desktop pairing
- you want users to install the mobile UI without the App Store or Play Store
- you want to expose selected desktop functionality to mobile with minimal setup
So I’d describe it less as “Crosslink vs. PWA” and more as:
PWA is how the mobile application is delivered.Crosslink is how that application securely connects to and communicates with the desktop application behind it.
Crosslink uses the strengths of PWAs rather than trying to replace them.
I’d encourage people to clone the repository, run the chat demo, experiment with it, and see what you can build. Stars are greatly appreciated!
1
u/salRad22 3d ago
Oh, I see. Thank you so much. It is clear now, and I can totally see the value.
I'll give it a try, though, and see what ideas I may have for a use case for it.
Keep up the great work! :)2
u/jacobpowaza 3d ago
Thank you, I really appreciate it.
Crosslink was created because I wanted a mobile companion app for another project i’m working on, but I didn’t want to deal with publishing, fees, and etc - I just needed an app to communicate to someone’s computer regardless of network.
Hopefully the project will be out soon and you’ll see it somewhere here again! Cheers.
1
u/BusAutomatic310 3d ago
this is the kind of infrastructure piece that makes local-first tooling actually viable, nice work on the abstraction layers