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:
1 parent
455dae1013
commit
d8332b21a8
12 files changed
+1492
-12
No files matched your search
+56
-1
@@ -176,6 +176,10 @@ type Client struct {
|
||||
OnChat func(chat string, avatars []Avatar)
|
||||
// OnLibrary, if set, is called when connected and whenever the person's library changes.
|
||||
OnLibrary func()
|
||||
// OnConnect, if set, is called each time the connection to the hub is made (to send state again).
|
||||
OnConnect func()
|
||||
// OnRoom, if set, gets the hub's messages about hosted rooms ("room.*"), as sent.
|
||||
OnRoom func(data []byte)
|
||||
|
||||
mu sync.Mutex
|
||||
saved *Saved
|
||||
@@ -183,11 +187,13 @@ type Client struct {
|
||||
outfit []Item
|
||||
chats map[string]string // chat ID -> room instance ("" for other chats)
|
||||
wake chan struct{}
|
||||
outbox chan []byte // messages sent as they come (Send), not state
|
||||
}
|
||||
|
||||
// New makes a client for the sign-in saved at path.
|
||||
func New(path string, saved *Saved, logger *log.Logger) *Client {
|
||||
return &Client{path: path, saved: saved, log: logger, chats: map[string]string{}, wake: make(chan struct{}, 1)}
|
||||
return &Client{path: path, saved: saved, log: logger, chats: map[string]string{}, wake: make(chan struct{}, 1),
|
||||
outbox: make(chan []byte, 256)}
|
||||
}
|
||||
|
||||
// Join reports the client is in an IMVU chat (room: the room instance, for a room's chat).
|
||||
@@ -241,6 +247,22 @@ func (c *Client) Outfit(imvuID int64, items []Item) {
|
||||
c.poke()
|
||||
}
|
||||
|
||||
// Send sends a message to the hub once (hosted rooms' messages); false if it can't be queued. Messages queued while
|
||||
// disconnected are dropped on connecting (OnConnect sends what's still true).
|
||||
func (c *Client) Send(v any) bool {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case c.outbox <- b:
|
||||
return true
|
||||
default:
|
||||
c.log.Printf("hub: too many messages waiting; dropped one")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) poke() {
|
||||
select {
|
||||
case c.wake <- struct{}{}:
|
||||
@@ -289,6 +311,13 @@ func (c *Client) connect(ctx context.Context) error {
|
||||
if c.OnLibrary != nil {
|
||||
go c.OnLibrary()
|
||||
}
|
||||
for drained := false; !drained; {
|
||||
select {
|
||||
case <-c.outbox:
|
||||
default:
|
||||
drained = true
|
||||
}
|
||||
}
|
||||
|
||||
errs := make(chan error, 1)
|
||||
go func() {
|
||||
@@ -302,6 +331,17 @@ func (c *Client) connect(ctx context.Context) error {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
var kind struct{ Type string }
|
||||
if json.Unmarshal(data, &kind) != nil {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(kind.Type, "room.") {
|
||||
// Hosted rooms' messages, whose fields differ (chat IDs are numbers).
|
||||
if c.OnRoom != nil {
|
||||
c.OnRoom(data)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var m struct {
|
||||
Type, Message, Chat string
|
||||
Avatars []Avatar
|
||||
@@ -349,6 +389,7 @@ func (c *Client) connect(ctx context.Context) error {
|
||||
}()
|
||||
sentID, sentOutfit := int64(-1), ""
|
||||
sentChats := map[string]string{}
|
||||
connected := false
|
||||
for {
|
||||
c.mu.Lock()
|
||||
id, outfit := c.imvuID, append([]Item{}, c.outfit...) // a list, never null
|
||||
@@ -396,12 +437,26 @@ func (c *Client) connect(ctx context.Context) error {
|
||||
delete(sentChats, chat)
|
||||
}
|
||||
}
|
||||
if !connected {
|
||||
// After the session (so the hub knows the IMVU account), say what's still true.
|
||||
connected = true
|
||||
if c.OnConnect != nil {
|
||||
go c.OnConnect()
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
conn.Close(websocket.StatusNormalClosure, "")
|
||||
return ctx.Err()
|
||||
case err := <-errs:
|
||||
return err
|
||||
case b := <-c.outbox:
|
||||
wctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
err := conn.Write(wctx, websocket.MessageText, b)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-c.wake:
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user