2026-06-21 18:56:24 +02:00
|
|
|
// Package invite encodes and decodes waste invite strings.
|
2026-06-22 23:29:45 +02:00
|
|
|
//
|
2026-06-21 18:56:24 +02:00
|
|
|
// Format: "waste:<url-safe-base64(json)>"
|
2026-06-22 23:29:45 +02:00
|
|
|
//
|
|
|
|
|
// 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.
|
2026-06-21 18:56:24 +02:00
|
|
|
package invite
|
|
|
|
|
|
|
|
|
|
import (
|
2026-06-22 23:29:45 +02:00
|
|
|
"crypto/sha256"
|
2026-06-21 18:56:24 +02:00
|
|
|
"encoding/base64"
|
2026-06-22 23:29:45 +02:00
|
|
|
"encoding/hex"
|
2026-06-21 18:56:24 +02:00
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const prefix = "waste:"
|
|
|
|
|
|
|
|
|
|
// Invite holds the information needed to join a network.
|
|
|
|
|
type Invite struct {
|
2026-06-22 23:29:45 +02:00
|
|
|
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
|
2026-06-21 18:56:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Encode returns a waste: invite string for the given anchor URL and network name.
|
|
|
|
|
func Encode(anchor, network string) (string, error) {
|
|
|
|
|
if anchor == "" {
|
|
|
|
|
return "", fmt.Errorf("anchor URL is required")
|
|
|
|
|
}
|
|
|
|
|
if network == "" {
|
|
|
|
|
return "", fmt.Errorf("network name is required")
|
|
|
|
|
}
|
2026-06-22 23:29:45 +02:00
|
|
|
h := sha256.Sum256([]byte("yaw2-net:" + network))
|
|
|
|
|
b, err := json.Marshal(Invite{
|
|
|
|
|
Anchor: anchor,
|
|
|
|
|
Network: network,
|
|
|
|
|
Net: hex.EncodeToString(h[:]),
|
|
|
|
|
})
|
2026-06-21 18:56:24 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return prefix + base64.URLEncoding.EncodeToString(b), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decode parses a waste: invite string and returns the Invite.
|
|
|
|
|
func Decode(s string) (Invite, error) {
|
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
|
if !strings.HasPrefix(s, prefix) {
|
|
|
|
|
return Invite{}, fmt.Errorf("not a waste invite (expected 'waste:' prefix)")
|
|
|
|
|
}
|
|
|
|
|
b, err := base64.URLEncoding.DecodeString(strings.TrimPrefix(s, prefix))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Invite{}, fmt.Errorf("invalid invite: %w", err)
|
|
|
|
|
}
|
|
|
|
|
var inv Invite
|
|
|
|
|
if err := json.Unmarshal(b, &inv); err != nil {
|
|
|
|
|
return Invite{}, fmt.Errorf("invalid invite payload: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if inv.Anchor == "" || inv.Network == "" {
|
|
|
|
|
return Invite{}, fmt.Errorf("invite is missing anchor or network name")
|
|
|
|
|
}
|
|
|
|
|
return inv, nil
|
|
|
|
|
}
|
2026-06-22 23:29:45 +02:00
|
|
|
|
|
|
|
|
// 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[:])
|
|
|
|
|
}
|