bridgev2: add custom event for requesting state change (#428)
Some checks are pending
Go / Lint (latest) (push) Waiting to run
Go / Build (old, libolm) (push) Waiting to run
Go / Build (latest, libolm) (push) Waiting to run
Go / Build (old, goolm) (push) Waiting to run
Go / Build (latest, goolm) (push) Waiting to run

This commit is contained in:
Tulir Asokan 2025-11-19 12:15:38 +01:00 committed by GitHub
commit 57657d54ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 60 additions and 4 deletions

View file

@ -145,6 +145,7 @@ func (br *Connector) Init(bridge *bridgev2.Bridge) {
br.EventProcessor.On(event.StateMember, br.handleRoomEvent)
br.EventProcessor.On(event.StatePowerLevels, br.handleRoomEvent)
br.EventProcessor.On(event.StateRoomName, br.handleRoomEvent)
br.EventProcessor.On(event.BeeperSendState, br.handleRoomEvent)
br.EventProcessor.On(event.StateRoomAvatar, br.handleRoomEvent)
br.EventProcessor.On(event.StateTopic, br.handleRoomEvent)
br.EventProcessor.On(event.StateTombstone, br.handleRoomEvent)

View file

@ -20,6 +20,7 @@ import (
type MessageStatusEventInfo struct {
RoomID id.RoomID
TransactionID string
SourceEventID id.EventID
NewEventID id.EventID
EventType event.Type
@ -41,6 +42,7 @@ func StatusEventInfoFromEvent(evt *event.Event) *MessageStatusEventInfo {
return &MessageStatusEventInfo{
RoomID: evt.RoomID,
TransactionID: evt.Unsigned.TransactionID,
SourceEventID: evt.ID,
EventType: evt.Type,
MessageType: evt.Content.AsMessage().MsgType,
@ -182,9 +184,10 @@ func (ms *MessageStatus) ToMSSEvent(evt *MessageStatusEventInfo) *event.BeeperMe
Type: event.RelReference,
EventID: evt.SourceEventID,
},
Status: ms.Status,
Reason: ms.ErrorReason,
Message: ms.Message,
TargetTxnID: evt.TransactionID,
Status: ms.Status,
Reason: ms.ErrorReason,
Message: ms.Message,
}
if ms.InternalError != nil {
content.InternalError = ms.InternalError.Error()

View file

@ -512,6 +512,13 @@ func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCal
}()
switch evt := rawEvt.(type) {
case *portalMatrixEvent:
isStateRequest := evt.evt.Type == event.BeeperSendState
if isStateRequest {
if err := portal.unwrapBeeperSendState(ctx, evt.evt); err != nil {
portal.sendErrorStatus(ctx, evt.evt, err)
return
}
}
res = portal.handleMatrixEvent(ctx, evt.sender, evt.evt)
if res.SendMSS {
if res.Error != nil {
@ -520,9 +527,21 @@ func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCal
portal.sendSuccessStatus(ctx, evt.evt, 0, "")
}
}
if res.Error != nil && evt.evt.StateKey != nil {
if !isStateRequest && res.Error != nil && evt.evt.StateKey != nil {
portal.revertRoomMeta(ctx, evt.evt)
}
if isStateRequest && res.Success {
portal.sendRoomMeta(
ctx,
evt.sender.DoublePuppet(ctx),
time.UnixMilli(evt.evt.Timestamp),
evt.evt.Type,
evt.evt.GetStateKey(),
evt.evt.Content.Parsed,
false,
evt.evt.Content.Raw,
)
}
case *portalRemoteEvent:
res = portal.handleRemoteEvent(ctx, evt.source, evt.evtType, evt.evt)
case *portalCreateEvent:
@ -534,6 +553,29 @@ func (portal *Portal) handleSingleEvent(ctx context.Context, rawEvt any, doneCal
}
}
func (portal *Portal) unwrapBeeperSendState(ctx context.Context, evt *event.Event) error {
content, ok := evt.Content.Parsed.(*event.BeeperSendStateEventContent)
if !ok {
return fmt.Errorf("%w: %T", ErrUnexpectedParsedContentType, evt.Content.Parsed)
}
evt.Content = content.Content
evt.StateKey = &content.StateKey
evt.Type = event.Type{Type: content.Type, Class: event.StateEventType}
_ = evt.Content.ParseRaw(evt.Type)
mx, ok := portal.Bridge.Matrix.(MatrixConnectorWithArbitraryRoomState)
if !ok {
return fmt.Errorf("matrix connector doesn't support fetching state")
}
prevEvt, err := mx.GetStateEvent(ctx, portal.MXID, evt.Type, evt.GetStateKey())
if err != nil {
return fmt.Errorf("failed to get prev event: %w", err)
} else if prevEvt != nil {
evt.Unsigned.PrevContent = &prevEvt.Content
evt.Unsigned.PrevSender = prevEvt.Sender
}
return nil
}
func (portal *Portal) FindPreferredLogin(ctx context.Context, user *User, allowRelay bool) (*UserLogin, *database.UserPortal, error) {
if portal.Receiver != "" {
login, err := portal.Bridge.GetExistingUserLoginByID(ctx, portal.Receiver)

View file

@ -53,6 +53,8 @@ type BeeperMessageStatusEventContent struct {
LastRetry id.EventID `json:"last_retry,omitempty"`
TargetTxnID string `json:"relates_to_txn_id,omitempty"`
MutateEventKey string `json:"mutate_event_key,omitempty"`
// Indicates the set of users to whom the event was delivered. If nil, then
@ -90,6 +92,12 @@ type BeeperChatDeleteEventContent struct {
DeleteForEveryone bool `json:"delete_for_everyone,omitempty"`
}
type BeeperSendStateEventContent struct {
Type string `json:"type"`
StateKey string `json:"state_key"`
Content Content `json:"content"`
}
type IntOrString int
func (ios *IntOrString) UnmarshalJSON(data []byte) error {

View file

@ -64,6 +64,7 @@ var TypeMap = map[Type]reflect.Type{
BeeperMessageStatus: reflect.TypeOf(BeeperMessageStatusEventContent{}),
BeeperTranscription: reflect.TypeOf(BeeperTranscriptionEventContent{}),
BeeperDeleteChat: reflect.TypeOf(BeeperChatDeleteEventContent{}),
BeeperSendState: reflect.TypeOf(BeeperSendStateEventContent{}),
AccountDataRoomTags: reflect.TypeOf(TagEventContent{}),
AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}),

View file

@ -237,6 +237,7 @@ var (
BeeperMessageStatus = Type{"com.beeper.message_send_status", MessageEventType}
BeeperTranscription = Type{"com.beeper.transcription", MessageEventType}
BeeperDeleteChat = Type{"com.beeper.delete_chat", MessageEventType}
BeeperSendState = Type{"com.beeper.send_state", MessageEventType}
EventUnstablePollStart = Type{Type: "org.matrix.msc3381.poll.start", Class: MessageEventType}
EventUnstablePollResponse = Type{Type: "org.matrix.msc3381.poll.response", Class: MessageEventType}