Просмотр исходного кода

webapi: update status badge when changing away status

Mike 1 неделя назад
Родитель
Сommit
de410e7d3a
2 измененных файлов с 95 добавлено и 0 удалено
  1. 57 0
      server/webapi/handlers/presence.go
  2. 38 0
      server/webapi/handlers/presence_test.go

+ 57 - 0
server/webapi/handlers/presence.go

@@ -347,6 +347,13 @@ func (h *PresenceHandler) SetState(w http.ResponseWriter, r *http.Request, sessi
 	// Queue presence event for other WebAPI sessions watching this user
 	h.broadcastPresenceEvent(session.ScreenName.IdentScreenName(), stateParam, awayMsg, "")
 
+	// Notify the user's own client so its status indicator re-renders. The AIM
+	// client updates its self-presence badge only from "myInfo" events; the
+	// "presence" broadcast above drives buddy dots, not the user's own state.
+	// Without this, changing to Busy/Away leaves the user still showing as
+	// available in their own UI.
+	h.pushMyInfo(session, stateParam, awayMsg, "")
+
 	h.Logger.InfoContext(ctx, "presence state updated",
 		"screenName", session.ScreenName.String(),
 		"state", stateParam,
@@ -392,6 +399,10 @@ func (h *PresenceHandler) SetStatus(w http.ResponseWriter, r *http.Request, sess
 	// Queue status event for other WebAPI sessions
 	h.broadcastPresenceEvent(session.ScreenName.IdentScreenName(), "", "", statusMsg)
 
+	// Notify the user's own client so its status message re-renders. Preserve the
+	// current presence state so a status-only change does not flip the self badge.
+	h.pushMyInfo(session, currentWebState(oscarSession), oscarSession.Session().AwayMessage(), statusMsg)
+
 	h.Logger.InfoContext(ctx, "status message updated",
 		"screenName", session.ScreenName.String(),
 		"statusMsg", statusMsg,
@@ -541,6 +552,52 @@ func (h *PresenceHandler) Icon(w http.ResponseWriter, r *http.Request) {
 	http.Redirect(w, r, iconURL, http.StatusFound)
 }
 
+// currentWebState maps an OSCAR session's presence flags to the web state string
+// the AIM client expects ("online", "away", "idle", "invisible").
+func currentWebState(instance *state.SessionInstance) string {
+	sess := instance.Session()
+	switch {
+	case sess.Invisible():
+		return "invisible"
+	case sess.Away():
+		return "away"
+	case instance.Idle():
+		return "idle"
+	default:
+		return "online"
+	}
+}
+
+// pushMyInfo queues a "myInfo" event on the user's own session so the AIM client
+// re-renders its self-presence badge. The client binds its identity-badge render
+// to "myInfo" events only, so state changes made via setState/setStatus are
+// invisible in the user's own UI unless a myInfo event is delivered.
+func (h *PresenceHandler) pushMyInfo(session *state.WebAPISession, webState, awayMsg, statusMsg string) {
+	if session.EventQueue == nil {
+		return
+	}
+	if !session.IsSubscribedTo("myInfo") && !session.IsSubscribedTo("presence") {
+		return
+	}
+
+	screenName := session.ScreenName.String()
+	myInfo := map[string]interface{}{
+		"aimId":     screenName,
+		"displayId": screenName,
+		"friendly":  screenName,
+		"state":     webState,
+		"userType":  "aim",
+	}
+	if awayMsg != "" {
+		myInfo["awayMsg"] = awayMsg
+	}
+	if statusMsg != "" {
+		myInfo["statusMsg"] = statusMsg
+	}
+
+	session.EventQueue.Push(types.EventType("myInfo"), myInfo)
+}
+
 // broadcastPresenceEvent sends presence updates to all WebAPI sessions watching this user
 func (h *PresenceHandler) broadcastPresenceEvent(screenName state.IdentScreenName, stateStr, awayMsg, statusMsg string) {
 	// Get all sessions that have this user in their buddy list

+ 38 - 0
server/webapi/handlers/presence_test.go

@@ -374,6 +374,44 @@ func TestPresenceHandler_SetState_InvalidState(t *testing.T) {
 	assert.Contains(t, rr.Body.String(), "invalid state parameter")
 }
 
+func TestPresenceHandler_SetState_EmitsMyInfoEvent(t *testing.T) {
+	// The AIM client re-renders its own status badge only from "myInfo" events,
+	// so setState must queue one on the user's own session for the change to be
+	// visible in their UI.
+	oscarInstance := state.NewSession().AddInstance()
+	sessionMgr, aimsid := createTestSessionManagerWithOSCAR("testuser", oscarInstance)
+
+	broadcaster := &MockBuddyBroadcaster{}
+	broadcaster.On("BroadcastBuddyArrived", mock.Anything, mock.Anything, mock.Anything).Return(nil)
+
+	handler := &PresenceHandler{
+		SessionManager:   sessionMgr,
+		BuddyBroadcaster: broadcaster,
+		Logger:           slog.Default(),
+	}
+
+	req, err := http.NewRequest("GET", "/presence/setState?aimsid="+aimsid+"&state=away&awayMsg=brb", nil)
+	assert.NoError(t, err)
+
+	rr := httptest.NewRecorder()
+	requireSession(handler.SessionManager, handler.SetState).ServeHTTP(rr, req)
+	assert.Equal(t, http.StatusOK, rr.Code)
+
+	session, err := sessionMgr.GetSession(context.Background(), aimsid)
+	assert.NoError(t, err)
+
+	var myInfo map[string]interface{}
+	for _, event := range session.EventQueue.GetAllEvents() {
+		if event.Type == "myInfo" {
+			myInfo, _ = event.Data.(map[string]interface{})
+		}
+	}
+	assert.NotNil(t, myInfo, "expected a myInfo event to be queued")
+	assert.Equal(t, "away", myInfo["state"])
+	assert.Equal(t, "brb", myInfo["awayMsg"])
+	assert.Equal(t, "testuser", myInfo["aimId"])
+}
+
 func TestPresenceHandler_SetState_NoOSCARSession_Rejected(t *testing.T) {
 	// Anonymous (web-only, no OSCAR) sessions are rejected by the session
 	// middleware before the handler runs.