Files
tajniak81andClaude Opus 5.5 b6bf3ce741
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
Desktop client (warp-desktop) with Wails v3
apps/desktop is the web client in a native window. It embeds the same
build warp-web does and serves it through the same Go package, so every
screen is shared. Unlike warp-web it proxies nothing: the client calls the
homeserver directly, and the CSP allows the homeserver plus any https:
and wss: origin for the call backend.

- First run asks for the homeserver: a server name or address, with
  .well-known discovery and a /versions check; File > Change homeserver
  asks again. Signed-in sessions keep their own base URL, so a change
  never sends one server's token to another.
- Links to other sites open in the system browser (/_desktop/links.js,
  added to index.html); only http, https and mailto are opened.
- One instance at a time, since sessions and keys live in the webview's
  storage.
- Dev builds open the DevTools Protocol on Windows when
  WARP_DESKTOP_REMOTE_DEBUGGING_PORT is set, for scripted testing.
- Packaging: icon, version info and manifest in the Windows exe, a macOS
  .app with camera and microphone descriptions, Linux packages via nfpm.
  Make targets: desktop, desktop-run, desktop-test, desktop-resources,
  desktop-macos, desktop-linux.

Web client: host and client move out of internal/ so the desktop app can
import them, and host gains AnyCallOrigin. The Dockerfile, .dockerignore
and the vite output path follow the move.

Pinned to Wails v3.0.0-beta.27.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 21:43:54 +02:00

129 lines
3.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package shell
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// Discover turns what someone typed on the first-run page into a homeserver
// base URL, the way Matrix clients do (spec: client-server API, server
// discovery). The input is a server name ("example.org") or a URL; a
// /.well-known/matrix/client on that host may delegate to another base URL;
// and whatever comes out has to answer /_matrix/client/versions.
//
// The errors are written for the person who typed the input.
func Discover(ctx context.Context, c *http.Client, input string) (string, error) {
input = strings.TrimSpace(input)
if input == "" {
return "", errors.New("Enter a server name, like example.org, or its address.")
}
raw := input
if !strings.Contains(raw, "://") {
raw = "https://" + raw
}
u, err := parseBase(raw)
if err != nil {
return "", fmt.Errorf("“%s” isn’t a server name or address.", input)
}
delegated, err := wellKnown(ctx, c, u)
if err != nil {
return "", err
}
if delegated == nil || *delegated == *u {
if err := checkVersions(ctx, c, u); err != nil {
return "", err
}
return u.String(), nil
}
err = checkVersions(ctx, c, delegated)
if err == nil {
return delegated.String(), nil
}
// A server whose well-known names an address that doesn't work (a
// development server advertising its public name, say) is still
// usable at the address that was typed.
if checkVersions(ctx, c, u) == nil {
return u.String(), nil
}
return "", fmt.Errorf("%s points to %s. %w", u.Host, delegated, err)
}
// parseBase accepts an absolute http(s) URL with no query or fragment and
// returns it without a trailing slash.
func parseBase(raw string) (*url.URL, error) {
u, err := url.Parse(raw)
if err != nil {
return nil, err
}
if (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" || u.User != nil ||
u.RawQuery != "" || u.Fragment != "" {
return nil, errors.New("not an http(s) base URL")
}
u.Path = strings.TrimRight(u.Path, "/")
u.RawPath = ""
return u, nil
}
// wellKnown returns the base URL the host delegates to, or nil when it
// doesn't say (no document, or one without m.homeserver). A host that can't
// be reached at all is not an error here: the versions check reports it.
func wellKnown(ctx context.Context, c *http.Client, u *url.URL) (*url.URL, error) {
wk := url.URL{Scheme: u.Scheme, Host: u.Host, Path: "/.well-known/matrix/client"}
body, status, err := get(ctx, c, wk.String())
if err != nil || status != http.StatusOK {
return nil, nil
}
var doc struct {
Homeserver *struct {
BaseURL string `json:"base_url"`
} `json:"m.homeserver"`
}
if json.Unmarshal(body, &doc) != nil || doc.Homeserver == nil {
return nil, nil
}
base, err := parseBase(doc.Homeserver.BaseURL)
if err != nil {
return nil, fmt.Errorf("%s points to “%s”, which isn’t a valid homeserver address.", u.Host, doc.Homeserver.BaseURL)
}
return base, nil
}
func checkVersions(ctx context.Context, c *http.Client, base *url.URL) error {
body, status, err := get(ctx, c, base.String()+"/_matrix/client/versions")
if err != nil {
if ctx.Err() != nil {
return fmt.Errorf("%s took too long to answer.", base.Host)
}
return fmt.Errorf("Can’t reach %s.", base.Host)
}
var v struct {
Versions []string `json:"versions"`
}
if status != http.StatusOK || json.Unmarshal(body, &v) != nil || len(v.Versions) == 0 {
return fmt.Errorf("%s doesn’t look like a Matrix homeserver.", base.Host)
}
return nil
}
func get(ctx context.Context, c *http.Client, u string) ([]byte, int, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return nil, 0, err
}
req.Header.Set("Accept", "application/json")
res, err := c.Do(req)
if err != nil {
return nil, 0, err
}
defer res.Body.Close()
body, err := io.ReadAll(io.LimitReader(res.Body, 64<<10))
return body, res.StatusCode, err
}