Add yaw2 invite interoperability

Protocol:
- invite.go: include full 64-char `net` hash in waste: invite blob
  (matches yaw2's `net` field — any client parsing the base64 JSON can
  join without knowing the plaintext name). Expose NetHash() helper.
- netmgr: add JoinByHash() — join via full 64-char hex hash alone,
  storing the short ID as display name. Enables joining yaw2 networks
  from a URL that only carries the hash.
- anchor: expose RunByHash() so netmgr can pass a pre-computed hash
  directly without a name→hash roundtrip.
- ipc/proto: add network_hash field to join_network — routes to
  JoinByHash when present and network_name is absent.

Web UI:
- Parse ?net=<64hex> (yaw2 URL param) and ?a=<anchor> in addition to
  existing ?n= / ?invite= params. Hash-only joins send network_hash.
- Sidebar shows yaw: contact card (yaw:<masterID>?n=<alias>) using the
  master identity — compatible with yaw2 contact card format. Click to
  copy to clipboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-22 23:29:45 +02:00
parent 7fe02e9463
commit add7c5fea8
8 changed files with 193 additions and 33 deletions

View File

@@ -1,10 +1,17 @@
// Package invite encodes and decodes waste invite strings.
// An invite carries the anchor URL and network name needed to join a network.
//
// Format: "waste:<url-safe-base64(json)>"
//
// The JSON payload is compatible with yaw2: the `net` field carries the full
// 64-char hex SHA-256("yaw2-net:"+name) hash that yaw2 clients pass directly
// to the signaling server. A yaw2 client that can parse the base64 JSON can join
// the same network without knowing the plaintext name.
package invite
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
@@ -14,8 +21,9 @@ const prefix = "waste:"
// Invite holds the information needed to join a network.
type Invite struct {
Anchor string `json:"anchor"` // WebSocket anchor URL
Network string `json:"network"` // plaintext network name
Anchor string `json:"anchor"` // WebSocket anchor URL
Network string `json:"network"` // plaintext network name
Net string `json:"net,omitempty"` // 64-char hex SHA-256("yaw2-net:"+name) — yaw2 `net` field
}
// Encode returns a waste: invite string for the given anchor URL and network name.
@@ -26,7 +34,12 @@ func Encode(anchor, network string) (string, error) {
if network == "" {
return "", fmt.Errorf("network name is required")
}
b, err := json.Marshal(Invite{Anchor: anchor, Network: network})
h := sha256.Sum256([]byte("yaw2-net:" + network))
b, err := json.Marshal(Invite{
Anchor: anchor,
Network: network,
Net: hex.EncodeToString(h[:]),
})
if err != nil {
return "", err
}
@@ -52,3 +65,10 @@ func Decode(s string) (Invite, error) {
}
return inv, nil
}
// NetHash returns the full 64-char hex network hash for the given name
// (SHA-256("yaw2-net:" + name)). This is the `net` field sent to the anchor.
func NetHash(name string) string {
h := sha256.Sum256([]byte("yaw2-net:" + name))
return hex.EncodeToString(h[:])
}