Skip to content

kacy/chatbubbles

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

chatbubbles

chatbubbles is a small https api for iMessage over tailscale.

it runs on a Mac, talks to imsg, and gives paired clients a clean way to read chats, stream events, send messages, and manage webhooks without exposing the machine to the public internet.

what it does

today, chatbubbles supports:

  • tls on first boot with a pinned self-signed fingerprint
  • token-based auth with direct pairing and delegated web sessions
  • read endpoints for server info, chats, and message history
  • a websocket event stream backed by imsg watch
  • local admin over a unix socket with chatbubbles-cli
  • webhook registration and delivery
  • api-based message and attachment sending

still landing:

  • a more polished install story for running it as a background service
  • a homebrew tap, with notes in docs/homebrew.md

what you need

  • macOS 14 or newer
  • Messages signed in and working
  • full disk access for the terminal or daemon
  • imsg: brew install imsg
  • tailscale installed and connected

for local bridge work, the daemon accepts CHATBUBBLES_IMSG_BIN=/path/to/imsg or -imsg-bin /path/to/imsg if you need to point at a patched checkout before upstream catches up.

quick start

make fmt
make test
make build
make build-cli
make run

the server listens on :8443 by default.

on first boot it will:

  • create a self-signed cert in the data dir
  • log the tls fingerprint used for pairing
  • create a short-lived bootstrap pairing code if no clients exist yet

after that, local admin happens over the unix socket at ~/.local/share/chatbubbles/chatbubbles.sock.

how to use it

there are really two client modes today:

  • direct clients: cli tools, native apps, and anything that can live with the bridge's self-signed tls plus the pairing fingerprint
  • browser clients: the web app shell, which needs a browser-trusted https host and should go through tailscale serve

direct clients

direct clients can talk to the bridge itself on :8443.

the normal path is:

chatbubbles-cli pair

that command mints a pairing code, prints the server fingerprint, and renders a terminal qr that direct clients can scan.

common local commands:

chatbubbles-cli status
chatbubbles-cli pair
chatbubbles-cli clients
chatbubbles-cli revoke c_01example

for local bridge work, you can also point the daemon at a patched imsg checkout:

CHATBUBBLES_IMSG_BIN=/path/to/imsg make run

or:

./bin/chatbubbles -imsg-bin /path/to/imsg

browser clients

the browser should not connect to the bridge's direct :8443 listener.

that listener uses a self-signed cert, which is fine for pinned native flows but not for a normal browser.

for browsers, use:

  • cloudflare pages for the static shell
  • tailscale serve for the bridge traffic
  • the *.ts.net hostname as the saved browser host

1. run the bridge locally

make run

or your built binary:

./bin/chatbubbles

2. put tailscale serve in front of it

if your bridge is listening on 127.0.0.1:8443 or :8443 with its current self-signed cert, this is the simplest setup:

tailscale serve --https=443 https+insecure://127.0.0.1:8443
tailscale serve status

that gives you a browser-trusted tailscale https endpoint, usually something like:

https://bridge-name.your-tailnet.ts.net

3. deploy the web shell

the web app is just a static frontend:

cd web
npm install
npm run build

then deploy web/dist to cloudflare pages.

cloudflare is only hosting the shell. it is not proxying message traffic.

4. pair from the browser

open the cloudflare-hosted web app and use either:

  • pair with qr
  • approval code

for the browser host, use the plain *.ts.net host:

bridge-name.your-tailnet.ts.net

not:

bridge-name.your-tailnet.ts.net:8443
100.x.y.z:8443
https://100.x.y.z:8443

the web app stores:

  • https://bridge-name.your-tailnet.ts.net for api requests
  • wss://bridge-name.your-tailnet.ts.net for live events

delegated browser login

web clients can also use the delegated session flow instead of qr pairing:

curl -sk https://127.0.0.1:8443/v1/sessions \
  -H 'Content-Type: application/json' \
  -d '{"client_name":"Chrome","client_type":"web"}'

the browser polls the returned session_id, and an already-paired client approves it.

pairing a client

the short version:

  • use chatbubbles-cli pair for direct/native clients
  • use the cloudflare-hosted web shell plus your *.ts.net host for browser clients
  • do not use :8443 as the browser host

api shape

main endpoints:

  • GET /healthz
  • GET /v1/server
  • GET /v1/chats
  • GET /v1/chats/{id}/messages
  • POST /v1/pair
  • POST /v1/sessions
  • GET /v1/sessions/{id}
  • POST /v1/sessions/{id}/approve
  • GET /v1/events
  • POST /v1/messages
  • POST /v1/attachments
  • GET /v1/attachments/{id}
  • GET /v1/webhooks
  • POST /v1/webhooks
  • DELETE /v1/webhooks/{id}

example send request:

curl -sk https://127.0.0.1:8443/v1/messages \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"to":"+15551234567","text":"hi from chatbubbles","service":"auto"}'

example webhook list request:

curl -sk https://127.0.0.1:8443/v1/webhooks \
  -H "Authorization: Bearer $TOKEN"

example attachment send request:

curl -sk https://127.0.0.1:8443/v1/attachments \
  -H "Authorization: Bearer $TOKEN" \
  -F to=+15551234567 \
  -F text='photo attached' \
  -F file=@./photo.jpg

webhook targets must use https:// and cannot resolve to loopback, private, link-local, or metadata addresses.

browser troubleshooting

if desktop works but mobile chrome fails, check these first:

  • the saved browser host must be your *.ts.net hostname with no :8443
  • tailscale serve status should show port 443 serving the bridge
  • if you just deployed the web shell, do one hard refresh so the browser picks up the latest service worker and bundle

if the bridge log shows:

tls: unknown certificate

or:

tls handshake error ... EOF

the browser is almost always talking to the bridge's direct self-signed tls listener instead of the tailscale serve host.

project notes

  • the daemon keeps its state in ~/.local/share/chatbubbles/
  • the api is meant for tailscale clients, not direct public exposure
  • websocket auth uses ?token= because browser websocket clients cannot set custom auth headers
  • for now, the browser story assumes tailscale serve and a *.ts.net hostname. direct self-signed tls is for native/direct clients.

if you want the deeper architecture and rollout plan, that lives in the repo notes rather than this README.

About

authenticated iMessage API server for Tailscale with pairing, web sessions, events, webhooks, and local CLI admin

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors