openvibers/openvibe.link Commits

Use Link audience and URLs for Connect shares (#137)

OP OpenVibers committed 5 days ago · cbfaf23d01 · 10 files, +48 −18

* Use the Link audience for Connect shares and show Link URLs

* Merge remote-tracking branch 'origin/main' into cx/link-shares-node-1108

* v0.47.0: Connect shares on openvibe.link

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex ef50fcc..9865d51 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -5,8 +5,8 @@
"wave": "T15",
"plan": "Plan track T15 (Bot complete) and ADR-043 (Bot: devices, pairing, control and safety); the computer plugin and the job worker serve OpenVibe.Actor and the Codes job runner (tracks T14/T17)",
"kind": "client",
-- "version": "0.45.0",
-- "release": "v0.45.0: sell a local model on the Exchange (`openvibe-node exchange serve`, #120); AI plugin 0.4.0, machine pools over llama.cpp RPC behind a private link, isolated with bubblewrap where Ubuntu restricts user namespaces (#121); Memory M5, `memory export` and `memory import` of the portable memory file (#122); `skills sync`, the skills and rules chosen in the Codes hub written into each coding tool's own folders (#123).",
++ "version": "0.47.0",
++ "release": "v0.47.0: `openvibe-node share` asks for the openvibe.link audience and prints share addresses on openvibe.link (`<label>--<user>.openvibe.link`; the openvibe.website address keeps working, #137); Link answers HO12 camera and location requests on the phone (#138).",
"runtime": "go 1.24 (one static binary per platform); plugins in Python 3; an ESP32 Arduino library",
"database": "none",
"deployed": false,
ddiff --git a/cmd/openvibe-node/share.go b/cmd/openvibe-node/share.go
iindex 3cbca24..08c2994 100644
---- a/cmd/openvibe-node/share.go
++++ b/cmd/openvibe-node/share.go
@@@ -36,12 +36,12 @@ const shareUsage = `usage:
openvibe-link share list the shares the service keeps up
openvibe-link share remove <PORT|KEY|ID|ADDRESS> stop keeping one up
--Make a share at https://connect.openvibe.website. On its page, pick this computer under "Or serve it from one of your
++Make a share at https://connect.openvibe.website; its address is https://<label>--<you>.openvibe.link. On its page, pick this computer under "Or serve it from one of your
computers" to serve it with --share and no key (the computer must be added on openvibe.bot/machines). Without --port,
the Node serves the port the share's page names now, and keeps that one. The Node only ever connects to localhost on
the share's port; every visitor signs in with OpenVibe first.`
--// connectNodeToken buys this machine's node token for OpenVibe Connect (audience openvibe.website). A machine
++// connectNodeToken buys this machine's node token for OpenVibe Connect (audience openvibe.link). A machine
// removed on OpenVibe stops its shares (connect.ErrStopped) instead of trying forever.
func connectNodeToken(creds *credentials.Credentials) func(context.Context) (string, error) {
ts := link.NewTokenSourceFor(creds, nil, "openvibe-node/"+version, link.ConnectAudience)
ddiff --git a/cmd/openvibe-node/share_test.go b/cmd/openvibe-node/share_test.go
iindex eb36de9..72352e0 100644
---- a/cmd/openvibe-node/share_test.go
++++ b/cmd/openvibe-node/share_test.go
@@@ -83,10 +83,10 @@ func TestSharesText(t *testing.T) {
t.Fatalf("no shares: %q", got)
}
got := sharesText([]connect.Status{
-- {ID: "shr_01K9ZZZZZZZZZZZZZZZZZZZZZZ", Port: 8790, Host: "bot--alex.openvibe.website", URL: "https://bot--alex.openvibe.website", Connected: true, Requests: 3},
++ {ID: "shr_01K9ZZZZZZZZZZZZZZZZZZZZZZ", Port: 8790, Host: "bot--alex.openvibe.link", URL: "https://bot--alex.openvibe.link", Connected: true, Requests: 3},
{Port: 9001, LastError: "dial: connection refused"},
})
-- for _, want := range []string{"Shares: 2 kept up", "localhost:8790", "https://bot--alex.openvibe.website", "connected, 3 requests", "localhost:9001", "not connected", "dial: connection refused"} {
++ for _, want := range []string{"Shares: 2 kept up", "localhost:8790", "https://bot--alex.openvibe.link", "connected, 3 requests", "localhost:9001", "not connected", "dial: connection refused"} {
if !strings.Contains(got, want) {
t.Fatalf("status is missing %q:\n%s", want, got)
}
ddiff --git a/internal/connect/connect.go b/internal/connect/connect.go
iindex c8de40b..163b2db 100644
---- a/internal/connect/connect.go
++++ b/internal/connect/connect.go
@@@ -1,5 +1,5 @@
// Package connect keeps OpenVibe Connect shares up from the Node: each share is one port on this machine served at
--// https://<label>--<user>.openvibe.website behind OpenVibe sign-in (OpenVibe.Connect, ADR-057, Website Connect v0).
++// https://<label>--<user>.openvibe.link behind OpenVibe sign-in (OpenVibe.Connect, ADR-057, Website Connect v0).
//
// The Node opens one outgoing WebSocket per share to wss://connect.openvibe.website/tunnel and speaks ovconnect-ws/1,
// the same protocol as the one-file client (OpenVibe.Connect public/ov-connect.js):
@@@ -80,7 +80,7 @@ type Options struct {
MinWait time.Duration // reconnect backoff floor (default 1 s)
MaxWait time.Duration // reconnect backoff ceiling (default 30 s)
-- // NodeToken buys this computer's node token for openvibe.website, for shares it serves by id (node.go). An error
++ // NodeToken buys this computer's node token for openvibe.link, for shares it serves by id (node.go). An error
// wrapping ErrStopped (the machine was removed on OpenVibe) stops the share; any other is tried again.
NodeToken func(ctx context.Context) (string, error)
HTTPClient *http.Client // Connect's API, for tunnel tokens (default: 20 s timeout)
ddiff --git a/internal/connect/connect_test.go b/internal/connect/connect_test.go
iindex 6a4b322..02afefb 100644
---- a/internal/connect/connect_test.go
++++ b/internal/connect/connect_test.go
@@@ -67,7 +67,7 @@ func newFakeServer(t *testing.T, refuse int) *fakeServer {
_ = ws.Close()
return
}
-- _ = ws.WriteMessage(websocket.TextMessage, []byte(`{"t":"ready","v":1,"share":{"id":"shr_1","host":"agents--alex.openvibe.website","url":"https://agents--alex.openvibe.website","mode":"owner"}}`))
++ _ = ws.WriteMessage(websocket.TextMessage, []byte(`{"t":"ready","v":1,"share":{"id":"shr_1","host":"agents--alex.openvibe.link","url":"https://agents--alex.openvibe.link","mode":"owner"}}`))
c := &serverConn{ws: ws, frames: map[uint32]chan frame{}}
f.conns <- c
for {
@@@ -237,7 +237,7 @@ func TestHelloAndHTTP(t *testing.T) {
if hello["t"] != "hello" || hello["v"].(float64) != 1 || hello["port"].(float64) != float64(port) || !strings.HasPrefix(hello["key"].(string), "ovc_") {
t.Fatalf("hello %v", hello)
}
-- if r := <-ready; r.Host != "agents--alex.openvibe.website" {
++ if r := <-ready; r.Host != "agents--alex.openvibe.link" {
t.Fatalf("ready %v", r)
}
ddiff --git a/internal/connect/live_test.go b/internal/connect/live_test.go
iindex a65f8ef..ec79046 100644
---- a/internal/connect/live_test.go
++++ b/internal/connect/live_test.go
@@@ -16,12 +16,12 @@ func TestLiveSharesForStatus(t *testing.T) {
if got := LiveShares(); len(got) != 1 || got[0].Port != 8790 || got[0].Connected {
t.Fatalf("marked: %+v", got)
}
-- liveReady(s, Ready{Host: "agents--alex.openvibe.website", URL: "https://agents--alex.openvibe.website"})
++ liveReady(s, Ready{Host: "agents--alex.openvibe.link", URL: "https://agents--alex.openvibe.link"})
liveRequest(s)
liveRequest(s)
got := LiveShares()
-- if len(got) != 1 || !got[0].Connected || got[0].Host != "agents--alex.openvibe.website" ||
-- got[0].URL != "https://agents--alex.openvibe.website" || got[0].Requests != 2 || got[0].LastError != "" {
++ if len(got) != 1 || !got[0].Connected || got[0].Host != "agents--alex.openvibe.link" ||
++ got[0].URL != "https://agents--alex.openvibe.link" || got[0].Requests != 2 || got[0].LastError != "" {
t.Fatalf("ready: %+v", got)
}
if got[0].ID != s.ID {
ddiff --git a/internal/connect/node.go b/internal/connect/node.go
iindex e501014..19cf6fe 100644
---- a/internal/connect/node.go
++++ b/internal/connect/node.go
@@@ -2,7 +2,7 @@ package connect
// A share served by this computer itself, as OpenVibe Node (CON-4, ADR-057 decision 4). The share's owner picks the
// computer on the share's page; there is no connect key. Before every connection the Node buys its node token (subject
--// node:nod_…, audience openvibe.website) and asks Connect for a tunnel token for that one share:
++// node:nod_…, audience openvibe.link) and asks Connect for a tunnel token for that one share:
//
// POST https://connect.openvibe.website/api/v1/node/shares/<shr_…>/tunnel-token Authorization: Bearer <node token>
// → {"tunnel_token":"ovt_…","expires_in":60,"share":{"id","host","url","local_port","mode"}}
ddiff --git a/internal/connect/node_test.go b/internal/connect/node_test.go
iindex 683188b..3c5c2bc 100644
---- a/internal/connect/node_test.go
++++ b/internal/connect/node_test.go
@@@ -39,7 +39,7 @@ func newNodeServer(t *testing.T) *nodeServer {
n.mu.Lock()
n.bearers = append(n.bearers, r.Header.Get("Authorization"))
n.mu.Unlock()
-- _ = json.NewEncoder(w).Encode(map[string]any{"shares": []map[string]any{{"id": testShareID, "host": "portal--alex.openvibe.website", "url": "https://portal--alex.openvibe.website", "local_port": n.pagePort, "mode": "owner"}}})
++ _ = json.NewEncoder(w).Encode(map[string]any{"shares": []map[string]any{{"id": testShareID, "host": "portal--alex.openvibe.link", "url": "https://portal--alex.openvibe.link", "local_port": n.pagePort, "mode": "owner"}}})
})
mux.HandleFunc("/api/v1/node/shares/"+testShareID+"/tunnel-token", func(w http.ResponseWriter, r *http.Request) {
n.mu.Lock()
@@@ -71,7 +71,7 @@ func newNodeServer(t *testing.T) *nodeServer {
var hello map[string]any
_ = json.Unmarshal(data, &hello)
n.hellos <- hello
-- _ = ws.WriteMessage(websocket.TextMessage, []byte(`{"t":"ready","v":1,"node":true,"share":{"id":"`+testShareID+`","host":"portal--alex.openvibe.website","url":"https://portal--alex.openvibe.website","mode":"owner","local_port":8790}}`))
++ _ = ws.WriteMessage(websocket.TextMessage, []byte(`{"t":"ready","v":1,"node":true,"share":{"id":"`+testShareID+`","host":"portal--alex.openvibe.link","url":"https://portal--alex.openvibe.link","mode":"owner","local_port":8790}}`))
for {
_, data, err := ws.ReadMessage()
if err != nil {
@@@ -123,7 +123,7 @@ func TestAShareServedByThisComputer(t *testing.T) {
if _, has := hello["key"]; has {
t.Fatal("no key in a computer's hello")
}
-- if r := <-ready; r.Host != "portal--alex.openvibe.website" {
++ if r := <-ready; r.Host != "portal--alex.openvibe.link" {
t.Fatalf("ready %v", r)
}
// It keeps the tunnel with fresh node tokens.
ddiff --git a/internal/link/connect_audience_test.go b/internal/link/connect_audience_test.go
nnew file mode 100644
iindex 0000000..4fea018
---- /dev/null
++++ b/internal/link/connect_audience_test.go
@@@ -0,0 +1,30 @@
++package link
++
++import (
++ "context"
++ "net/http"
++ "net/http/httptest"
++ "testing"
++)
++
++func TestConnectTokenRequestsLinkAudience(t *testing.T) {
++ _, _, creds := networkPair(t)
++ var audience string
++ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
++ if err := r.ParseForm(); err != nil {
++ t.Error(err)
++ }
++ audience = r.Form.Get("audience")
++ w.Header().Set("Content-Type", "application/json")
++ _, _ = w.Write([]byte(`{"access_token":"test-node-token","token_type":"Bearer","expires_in":300}`))
++ }))
++ defer server.Close()
++ creds.Network = server.URL
++ source := NewTokenSourceFor(creds, server.Client(), "test", ConnectAudience)
++ if _, err := source.Token(context.Background()); err != nil {
++ t.Fatal(err)
++ }
++ if audience != "openvibe.link" {
++ t.Fatalf("requested audience = %q, want openvibe.link", audience)
++ }
++}
ddiff --git a/internal/link/network.go b/internal/link/network.go
iindex 13db388..026f756 100644
---- a/internal/link/network.go
++++ b/internal/link/network.go
@@@ -156,7 +156,7 @@ func NewTokenSource(c *credentials.Credentials, client *http.Client, userAgent s
// ConnectAudience is OpenVibe Connect's: any person's machine may buy a node token for it, whichever service paired
// the machine (Network, CON-4), to serve the shares bound to it.
--const ConnectAudience = "openvibe.website"
++const ConnectAudience = "openvibe.link"
// NewTokenSourceFor returns a token source for another audience the machine may speak to (ConnectAudience).
func NewTokenSourceFor(c *credentials.Credentials, client *http.Client, userAgent, audience string) *TokenSource {