Everyone signs in at the one central Quine identity server and is called by username. The caller picks which Jitsi Meet server each call happens on (meet.jit.si by default, or any other deployment), and the invite sends the callee to the same one. Jitsi servers need no credentials from Quple. - server: POST /call takes an optional jitsiUrl (http(s) only, no credentials/query/fragment; default DEFAULT_JITSI) and returns it with the meeting ID; invites carry it. Adds GET /me and per-person contacts with presence (persisted in DATA_DIR), replacing GET /users. - desktop: "On server" field for calls, remembered as the last-used server; incoming calls show which server they're on; open_meeting refuses non-http(s) URLs. meet.jit.si sign-in is now an inline prompt when calling there rather than a gate in front of the whole app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.1 KiB
Go
67 lines
2.1 KiB
Go
// Command quple-server is the central rendezvous service for Quple
|
|
// (meet.quineglobal.com).
|
|
//
|
|
// Everyone signs in with Quine (identity.quineglobal.com), the one identity
|
|
// server, and is known by their Quine username. Signed-in clients announce
|
|
// themselves, keep a contact list, place calls, and poll for calls placed to
|
|
// them. A call names the Jitsi Meet server it happens on - any one, picked by
|
|
// the caller - and this server just tells the callee where to go. The meeting
|
|
// itself (media, chat, etc.) happens entirely inside Jitsi Meet, which this
|
|
// server never talks to.
|
|
//
|
|
// Configuration (environment):
|
|
//
|
|
// ADDR listen address (default :8080)
|
|
// ISSUER Quine identity server whose access tokens are accepted
|
|
// (default https://identity.quineglobal.com)
|
|
// CLIENT_ID app the tokens must be issued to (default quple)
|
|
// DEFAULT_JITSI Jitsi Meet server for calls that don't name one (default https://meet.jit.si)
|
|
// DATA_DIR contacts (default ./data)
|
|
package main
|
|
|
|
import (
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
func main() {
|
|
addr := envOr("ADDR", ":8080")
|
|
issuer := envOr("ISSUER", "https://identity.quineglobal.com")
|
|
dataDir := envOr("DATA_DIR", "data")
|
|
defaultJitsi, err := normalizeJitsiURL(envOr("DEFAULT_JITSI", "https://meet.jit.si"))
|
|
if err != nil {
|
|
log.Fatalf("DEFAULT_JITSI: %v", err)
|
|
}
|
|
|
|
if err := os.MkdirAll(dataDir, 0o700); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
contacts, err := LoadContacts(filepath.Join(dataDir, "contacts.json"))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
app := &App{
|
|
verifier: NewVerifier(issuer, envOr("CLIENT_ID", "quple")),
|
|
store: NewStore(),
|
|
contacts: contacts,
|
|
defaultJitsiURL: defaultJitsi,
|
|
}
|
|
mux := http.NewServeMux()
|
|
app.Routes(mux)
|
|
mux.HandleFunc("/healthz", withCORS(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("ok"))
|
|
}))
|
|
|
|
log.Printf("quple-server listening on %s, accepting tokens from %s, default Jitsi %s", addr, issuer, defaultJitsi)
|
|
log.Fatal(http.ListenAndServe(addr, mux))
|
|
}
|
|
|
|
func envOr(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|