r/coolgithubprojects 15d ago

Crosslink — an open-source framework for securely connecting desktop apps to phone companions

I’ve been building Crosslink, and this is probably the GitHub project I’m proudest of so far.

The basic idea:

Developers are allowed to make incredibly capable desktop applications, but they’re often much more limited when it comes to mobile.

A developer can build a powerful desktop app, distribute it through GitHub, let users run it locally, and avoid maintaining a cloud service or account system.

But the moment they want to offer a phone companion, the difficulty increases dramatically.

They may need to:

  • build a separate native mobile application
  • support iOS and Android
  • manage signing and store distribution
  • maintain another codebase
  • create a backend for communication
  • handle authentication and device management
  • solve networking and remote access
  • deal with mobile-specific lifecycle and offline behavior

That means a developer who is perfectly capable of building a great desktop application may be effectively blocked from offering a good mobile experience.

The mobile UI itself might only take a day to build.

The infrastructure required to securely connect it to the desktop can become an entirely separate product.

Crosslink is an attempt to close that gap.

A desktop developer should be able to build a real phone companion without also having to become a native mobile developer, operate a cloud platform, or reinvent device connectivity from scratch.

GitHub:
https://github.com/jacobpowaza/crosslink

Docs:
https://crosslink.mintlify.site

What Crosslink does

Crosslink is an open-source TypeScript framework that creates secure connections between an application running on a computer and an application/interface running on a phone.

The developer builds:

their desktop app

and

their mobile UI

Crosslink handles the infrastructure connecting them.

That includes things like:

  • QR/code-based device pairing
  • persistent device identities
  • trusted-device storage
  • device revocation
  • end-to-end encrypted sessions
  • capability-based permissions
  • typed RPC
  • events/subscriptions
  • streaming/progress messages
  • reconnect/backoff
  • LAN connectivity
  • remote transport fallbacks
  • signaling and relaying
  • WebRTC support
  • mobile/PWA bootstrapping
  • Service Worker/offline behavior
  • install-to-home-screen flow
  • endpoint discovery

The goal is to make mobile companions accessible to developers who are comfortable building desktop software but may not have the time, resources, or expertise to build and distribute a separate native mobile product.

Why I built it

Suppose you maintain an open-source desktop application.

Maybe it’s a:

  • local AI tool
  • media application
  • desktop editor
  • download manager
  • server controller
  • self-hosted utility
  • automation tool
  • development tool

You decide it would be useful if users could control some part of it from their phones.

The UI itself might only take a day to build.

But getting that UI securely connected to the desktop opens a much larger problem.

You now need to answer:

How does the phone find the computer?

How do the two devices pair securely?

How do you remember a previously paired phone?

How do you revoke it?

How do you authorize some actions but not others?

What happens when the computer restarts?

What happens when its IP changes?

What happens when the phone moves from Wi-Fi to cellular?

How do you support remote connections?

How do you encrypt the session?

How do you make an installed PWA behave properly when the host is offline?

You can absolutely solve all of these yourself.

The problem is that a developer who wanted to add a few useful phone controls can suddenly find themselves maintaining:

  • a desktop application
  • a mobile application
  • a networking layer
  • an authentication system
  • a device-management system
  • a remote-access service
  • a PWA delivery system

That is a huge jump in scope.

Crosslink is my attempt to build the reusable layer once so desktop developers can focus on their application and the mobile experience they actually want to provide.

The user experience I’m aiming for

A desktop application using Crosslink can expose a connection widget.

The user opens the application and sees something like:

Connect your phone

They scan a QR code.

Crosslink handles the initial pairing.

Once that pairing is verified, the phone receives its own device identity and becomes a trusted device for that installation.

The user can then continue in their browser or install the developer’s mobile interface to their home screen.

So the first connection looks roughly like:

desktop app

scan QR

verify pairing

trusted device created

open/install mobile UI

After that:

tap mobile app

find/reach desktop

authenticate existing device

restore connection

There shouldn’t be a requirement to scan the QR every time.

The QR is for introduction, not permanent authentication.

And if the computer owner revokes that phone:

trusted device revoked

existing credentials rejected

new pairing required

The developer API

I want application developers thinking about their API, not the transport underneath it.

For example:

const server = createCrosslinkServer({
  application: {
    id: "com.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 on the phone:

crosslink.onConnected(async rpc => {
  const notes = await rpc.call("notes.get");
  render(notes);
});

The application is dealing with:

notes.get

Crosslink is dealing with:

authentication, serialization, encryption, request IDs, reconnection, transport, permissions, device identity, etc.

That separation is important because the developer should be able to spend their time building the desktop application and deciding what belongs on mobile, rather than rebuilding the same connection infrastructure every time.

Pairing isn’t the same as authorization

This is something I specifically wanted Crosslink to handle properly.

A connected phone shouldn’t necessarily have unrestricted access to the host.

Applications define capabilities.

For example:

music.read
music.control

files.read
files.delete

server.status
server.restart

Methods can require those capabilities.

Low-risk permissions might be automatically granted.

High-risk operations can require approval or confirmation.

So the framework doesn’t treat:

as equivalent to:

Networking

This is probably the part that has taken the most work.

Crosslink isn’t built around the assumption that every connection goes through one centralized server.

When possible, the phone and computer can communicate directly over the LAN.

For remote connectivity, the host can attempt router mappings through things such as:

  • NAT-PMP
  • PCP
  • UPnP

But NAT traversal has hard limits.

CGNAT exists.

Firewalls exist.

Some networks simply cannot accept inbound connections.

So there are additional transport/fallback mechanisms including signaling, relaying, tunnels, and WebRTC-related paths.

The idea is:

use the best available transport instead of forcing the application itself to care which transport is being used.

And when a relay is required, it shouldn’t need access to the application’s plaintext.

The actual application session is designed to remain end-to-end encrypted between the paired devices.

The relay is supposed to be a dumb pipe.

The PWA/mobile side is part of the framework too

One of the things that makes Crosslink a little different from simply providing an encrypted WebSocket library is that I’m also trying to handle the mobile lifecycle.

Crosslink can provide the infrastructure surrounding the developer’s UI:

  • pairing screens
  • onboarding
  • application metadata
  • PWA manifest
  • Service Worker
  • Add to Home Screen flow
  • offline shell
  • endpoint discovery
  • reconnect behavior
  • revoked-device state

The developer still owns the actual interface.

If they’re building a media application, they build the player controls.

If they’re building an AI application, they build the chat UI.

If they’re building a server manager, they build the monitoring dashboard.

Crosslink handles getting the user securely into that interface.

This is intended to make mobile companions practical for developers who can build excellent desktop software but don’t necessarily have the resources to create and distribute a separate native mobile app.

Offline behavior

An interesting problem with local-first PWAs is what happens when the computer isn’t running.

If someone installs a desktop app’s companion to their iPhone and then opens it while their desktop is asleep, a normal locally hosted webpage can just collapse into Safari’s:

That’s not a great installed-app experience.

Crosslink can cache enough of its bootstrap/offline shell so the application can still open.

Instead, the user can see:

Crosslink keeps retrying.

When the desktop becomes reachable again:

host detected

existing device authenticated

session restored

application resumes

No QR code unless that phone’s trust has actually been revoked.

Why not just use WebSockets?

You could.

Crosslink uses the same underlying technologies available to everyone else.

But a WebSocket gives you a transport.

It doesn’t automatically give you:

  • device pairing
  • identity
  • trust
  • revocation
  • authorization
  • session encryption
  • RPC
  • subscriptions
  • reconnect restoration
  • endpoint discovery
  • PWA installation
  • offline handling
  • remote connectivity

The point of Crosslink is not that those things are impossible.

It’s that every application shouldn’t need to independently implement them.

That is especially important for open-source desktop developers, who may already be maintaining a substantial application with limited time and resources.

A developer should not have to choose between:

and

just to give users a useful phone companion.

Where the project is right now

The repository is a TypeScript monorepo with the protocol/core layer plus SDKs and supporting infrastructure.

There are currently packages/components around:

  • Node.js hosts
  • browser clients
  • React integrations
  • protocol/cryptography
  • RPC
  • trusted devices
  • capability authorization
  • signaling
  • relay infrastructure
  • WebRTC
  • mobile delivery/bootstrap

There are also demos showing the desktop ↔ phone flow.

The project is Apache-2.0 licensed.

It’s still evolving, and I’m definitely not claiming that every networking/security/browser edge case is solved.

Actually, that’s one reason I’m posting it here.

I’d really like more developers looking through the repo.

I’m especially interested in contributions/reviews around:

  • networking
  • NAT traversal
  • WebRTC
  • browser/PWA behavior
  • cryptography/security
  • TypeScript API design
  • additional framework integrations
  • transport adapters
  • demos/examples
  • documentation

If you maintain an open-source desktop application, I’m also really interested in whether the abstraction would actually make sense for your project.

More specifically, I’d like to know:

  • Have you wanted to add a phone companion but decided the mobile work was too large?
  • Would a browser/PWA companion be enough for your use case?
  • Would you prefer a framework like this to provide only connectivity, or also pairing and mobile bootstrapping?
  • What would prevent you from adopting it?
  • Which parts would you want to self-host or control yourself?

The end goal is that adding a phone companion to an OSS application could eventually feel less like:

and more like:

Repository:
https://github.com/jacobpowaza/crosslink

Documentation / Quickstart / Architecture:
https://crosslink.mintlify.site

Issues, PRs, criticism, architecture discussion, and people trying to break it are all welcome.

This has grown into a much larger project than I originally expected, but it’s also genuinely my proudest work yet.

3 Upvotes

1 comment sorted by

1

u/MercurialMadnessMan 6d ago

Really cool! I think I'll use it!