Serve T5DE-hosted rooms; a second client instance's own data
CI / build (push) Successful in 36s

Hosted rooms (internal/hosted): room calls for them answered here, their IMQ queues served (a new
proxy.IMQRouter hook takes them out of subscriptions to IMVU), chat relayed through the hub's
WebSocket (hub.Client gains Send, OnRoom and OnConnect), the room list extended, room products
checked against the real owner, and anything else naming a hosted room kept from IMVU.

T5DE_INSTANCE gives a second client's proxy its own data folder and log.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-30 03:36:17 -07:00
1 parent 455dae1013
commit d8332b21a8
12 files changed
+1492 -12

No files matched your search

+60 -2
View File
@@ -17,6 +17,7 @@ import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
@@ -29,12 +30,15 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"git.t5de.dev/t5de/t5de-proxy/internal/buildinfo"
"git.t5de.dev/t5de/t5de-proxy/internal/control"
"git.t5de.dev/t5de/t5de-proxy/internal/fetch"
"git.t5de.dev/t5de/t5de-proxy/internal/hosted"
"git.t5de.dev/t5de/t5de-proxy/internal/hub"
"git.t5de.dev/t5de/t5de-proxy/internal/imq"
"git.t5de.dev/t5de/t5de-proxy/internal/parent"
"git.t5de.dev/t5de/t5de-proxy/internal/products"
"git.t5de.dev/t5de/t5de-proxy/internal/proxy"
@@ -55,7 +59,7 @@ func main() {
trace := flag.Bool("trace", false, "log full URLs and every IMQ frame")
packs := flag.String("packs", "", "community product packs: directories of packs, separated by "+string(filepath.ListSeparator)+
" (default: %APPDATA%/T5DE/packs and $T5DE_PACKS)")
data := flag.String("data", "", "where the proxy keeps its state (default: %APPDATA%/T5DE/proxy)")
data := flag.String("data", "", "where the proxy keeps its state (default: %APPDATA%/T5DE/proxy, or T5DE-<n> for $T5DE_INSTANCE)")
version := flag.Bool("version", false, "print the version and exit")
flag.Parse()
if *version {
@@ -65,6 +69,10 @@ func main() {
var out io.Writer = os.Stderr
if *logFile != "" {
if instance := os.Getenv("T5DE_INSTANCE"); instance != "" {
// Another client beside the usual one (its T5DE_INSTANCE): its own log.
*logFile = strings.TrimSuffix(*logFile, ".log") + "-" + instance + ".log"
}
f, err := os.Create(*logFile)
if err != nil {
os.Exit(1)
@@ -96,6 +104,9 @@ func dataDir(flagValue string) string {
return flagValue
}
if config, err := os.UserConfigDir(); err == nil {
if instance := os.Getenv("T5DE_INSTANCE"); instance != "" {
return filepath.Join(config, "T5DE-"+instance, "proxy") // the client's own data (its T5DE_INSTANCE)
}
return filepath.Join(config, "T5DE", "proxy")
}
return "."
@@ -138,10 +149,25 @@ func run(logger *log.Logger, listen, ready string, parentPID int, trace bool, pa
handler.Interceptor, handler.IMQFilter = prods, prods
logger.Printf("community products from %s", strings.Join(packs, ", "))
}
// T5DE-hosted rooms (internal/hosted), served with the hub the proxy is signed in to.
hubNow := &hubRef{}
rooms := &hosted.Rooms{Hub: hubNow, Logf: logger.Printf}
if prods != nil {
prods.Hosted = rooms
rooms.User, rooms.LocalProduct, rooms.GlobalProduct = prods.User, prods.LocalID, prods.ProductID
prods.HostedOwner = rooms.Owner
rooms.Deliver = func(c *proxy.IMQConn, m imq.Delivered) {
m.Message = prods.Incoming(c, m)
c.Deliver(m)
}
}
// The hub connection, which the client's T5DE settings can sign in and out (internal/control).
manager := &control.Manager{Path: filepath.Join(data, "hub.json"), DefaultHub: envOr("T5DE_HUB", defaultHub),
Log: logger, OpenBrowser: openBrowser}
manager.Attach = func(client *hub.Client) {
hubNow.set(client)
client.OnRoom, client.OnConnect = rooms.FromHub, rooms.Reconnected
rooms.OnJoin, rooms.OnLeave = client.Join, client.Leave
fetcher := fetch.New(client, hubProducts, logger.Printf)
client.OnLibrary = func() { fetcher.SyncLibrary(context.Background()) }
if prods == nil {
@@ -168,7 +194,7 @@ func run(logger *log.Logger, listen, ready string, parentPID int, trace bool, pa
}
}
manager.Start()
chain := proxy.Interceptors{manager}
chain := proxy.Interceptors{manager, rooms} // hosted rooms' calls before the products'
if prods != nil {
chain = append(chain, prods)
}
@@ -186,6 +212,38 @@ func run(logger *log.Logger, listen, ready string, parentPID int, trace bool, pa
return server.Serve(ln)
}
// hubRef is the hub client the proxy is signed in with, for the hosted rooms (none while signed out).
type hubRef struct {
mu sync.Mutex
c *hub.Client
}
func (h *hubRef) set(c *hub.Client) {
h.mu.Lock()
h.c = c
h.mu.Unlock()
}
func (h *hubRef) get() *hub.Client {
h.mu.Lock()
defer h.mu.Unlock()
return h.c
}
func (h *hubRef) Send(v any) bool {
if c := h.get(); c != nil {
return c.Send(v)
}
return false
}
func (h *hubRef) Call(ctx context.Context, method, path string, body, out any) error {
if c := h.get(); c != nil {
return c.Call(ctx, method, path, body, out)
}
return errors.New("not signed in to T5DE's hub")
}
func writeReady(path, address, token string) error {
data, err := json.Marshal(map[string]string{"address": address, "token": token})
if err != nil {