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:
@@ -147,6 +147,88 @@ func (mgr *Manager) Join(name, shareDir string) (string, error) {
|
||||
return netID, nil
|
||||
}
|
||||
|
||||
// JoinByHash joins a network using its pre-computed full 64-char hex hash
|
||||
// (yaw2 `net` field) instead of the plaintext name. The network is stored
|
||||
// with an empty name; the network_id (first 8 bytes) is used for display.
|
||||
// This enables joining networks whose names are unknown — e.g. from a yaw2
|
||||
// invite URL that only contains the hash.
|
||||
func (mgr *Manager) JoinByHash(netHash64, shareDir string) (string, error) {
|
||||
if len(netHash64) != 64 {
|
||||
return "", fmt.Errorf("netHash must be 64 hex chars, got %d", len(netHash64))
|
||||
}
|
||||
netID := netHash64[:16] // first 8 bytes = 16 hex chars
|
||||
|
||||
mgr.mu.Lock()
|
||||
if _, exists := mgr.networks[netID]; exists {
|
||||
mgr.mu.Unlock()
|
||||
return netID, nil
|
||||
}
|
||||
mgr.mu.Unlock()
|
||||
|
||||
derived, err := crypto.DeriveForNetwork(mgr.cfg.MasterIdentity, netHash64)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("derive identity for net %s: %w", netID, err)
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(mgr.cfg.StoreDir, "messages-"+netID+".db")
|
||||
st, err := store.Open(dbPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("open store for net %s: %w", netID, err)
|
||||
}
|
||||
|
||||
m := mesh.New(derived, st)
|
||||
if shareDir != "" {
|
||||
m.ShareDir = shareDir
|
||||
} else if mgr.cfg.ShareDir != "" {
|
||||
m.ShareDir = mgr.cfg.ShareDir
|
||||
}
|
||||
m.DownloadDir = filepath.Join(mgr.cfg.StoreDir, "downloads-"+netID)
|
||||
|
||||
meshEvents := m.Subscribe()
|
||||
go func() {
|
||||
for evt := range meshEvents {
|
||||
evt.NetworkID = netID
|
||||
mgr.emit(evt)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
net := &Network{
|
||||
ID: netID,
|
||||
Name: netID, // display as short hash when name is unknown
|
||||
Hash: netHash64,
|
||||
Identity: derived,
|
||||
Mesh: m,
|
||||
Store: st,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
mgr.mu.Lock()
|
||||
mgr.networks[netID] = net
|
||||
mgr.order = append(mgr.order, netID)
|
||||
mgr.mu.Unlock()
|
||||
|
||||
log.Printf("netmgr: joining network by hash id=%s peer=%s", netID, derived.PeerID().Short())
|
||||
|
||||
if mgr.cfg.AnchorURL != "" {
|
||||
go func() {
|
||||
anchor.RunByHash(ctx, mgr.cfg.AnchorURL, netHash64, derived, m)
|
||||
log.Printf("netmgr: left network %s", netID)
|
||||
}()
|
||||
}
|
||||
|
||||
mgr.emit(proto.IpcMessage{
|
||||
Type: proto.EvtNetworkJoined,
|
||||
NetworkID: netID,
|
||||
NetworkName: netID,
|
||||
LocalPeer: peerPtr(derived.PeerInfo()),
|
||||
ShareDir: m.ShareDir,
|
||||
})
|
||||
|
||||
return netID, nil
|
||||
}
|
||||
|
||||
// Leave cancels a network context by ID. Closes its store.
|
||||
func (mgr *Manager) Leave(netID string) {
|
||||
mgr.mu.Lock()
|
||||
|
||||
Reference in New Issue
Block a user