1
0
Fork 0
forked from Mirrors/gomuks

web/devtools: add send message event button

This commit is contained in:
Tulir Asokan 2025-03-02 21:00:52 +02:00
parent e6242a9c37
commit d093ea2f90
7 changed files with 112 additions and 20 deletions

View file

@ -101,7 +101,7 @@ func main() {
resp, err := cli.Send(ctx, id.RoomID(fields[1]), event.EventMessage, &event.MessageEventContent{ resp, err := cli.Send(ctx, id.RoomID(fields[1]), event.EventMessage, &event.MessageEventContent{
Body: strings.Join(fields[2:], " "), Body: strings.Join(fields[2:], " "),
MsgType: event.MsgText, MsgType: event.MsgText,
}) }, false)
_, _ = fmt.Fprintln(rl, err) _, _ = fmt.Fprintln(rl, err)
_, _ = fmt.Fprintf(rl, "%+v\n", resp) _, _ = fmt.Fprintf(rl, "%+v\n", resp)
} }

View file

@ -47,7 +47,7 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any
}) })
case "send_event": case "send_event":
return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) { return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) {
return h.Send(ctx, params.RoomID, params.EventType, params.Content) return h.Send(ctx, params.RoomID, params.EventType, params.Content, params.DisableEncryption)
}) })
case "resend_event": case "resend_event":
return unmarshalAndCall(req.Data, func(params *resendEventParams) (*database.Event, error) { return unmarshalAndCall(req.Data, func(params *resendEventParams) (*database.Event, error) {
@ -267,9 +267,10 @@ type sendMessageParams struct {
} }
type sendEventParams struct { type sendEventParams struct {
RoomID id.RoomID `json:"room_id"` RoomID id.RoomID `json:"room_id"`
EventType event.Type `json:"type"` EventType event.Type `json:"type"`
Content json.RawMessage `json:"content"` Content json.RawMessage `json:"content"`
DisableEncryption bool `json:"disable_encryption"`
} }
type resendEventParams struct { type resendEventParams struct {

View file

@ -245,8 +245,9 @@ func (h *HiClient) Send(
roomID id.RoomID, roomID id.RoomID,
evtType event.Type, evtType event.Type,
content any, content any,
disableEncryption bool,
) (*database.Event, error) { ) (*database.Event, error) {
return h.send(ctx, roomID, evtType, content, "", false) return h.send(ctx, roomID, evtType, content, "", disableEncryption)
} }
func (h *HiClient) Resend(ctx context.Context, txnID string) (*database.Event, error) { func (h *HiClient) Resend(ctx context.Context, txnID string) (*database.Event, error) {

View file

@ -292,12 +292,14 @@ export default class Client {
} }
} }
async sendEvent(roomID: RoomID, type: EventType, content: unknown): Promise<void> { async sendEvent(
roomID: RoomID, type: EventType, content: unknown, disableEncryption: boolean = false,
): Promise<void> {
const room = this.store.rooms.get(roomID) const room = this.store.rooms.get(roomID)
if (!room) { if (!room) {
throw new Error("Room not found") throw new Error("Room not found")
} }
const dbEvent = await this.rpc.sendEvent(roomID, type, content) const dbEvent = await this.rpc.sendEvent(roomID, type, content, disableEncryption)
this.#handleOutgoingEvent(dbEvent, room) this.#handleOutgoingEvent(dbEvent, room)
} }

View file

@ -148,8 +148,10 @@ export default abstract class RPCClient {
return this.request("send_message", params) return this.request("send_message", params)
} }
sendEvent(room_id: RoomID, type: EventType, content: unknown): Promise<RawDBEvent> { sendEvent(
return this.request("send_event", { room_id, type, content }) room_id: RoomID, type: EventType, content: unknown, disable_encryption: boolean = false,
): Promise<RawDBEvent> {
return this.request("send_event", { room_id, type, content, disable_encryption })
} }
resendEvent(transaction_id: string): Promise<RawDBEvent> { resendEvent(transaction_id: string): Promise<RawDBEvent> {

View file

@ -8,6 +8,10 @@ div.state-explorer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
h3 {
margin: 0 0 1rem 0;
}
div.state-button-list { div.state-button-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -26,7 +30,15 @@ div.state-explorer {
flex-wrap: wrap; flex-wrap: wrap;
gap: .5rem; gap: .5rem;
margin-top: .5rem; margin-top: .5rem;
justify-content: space-between;
> div.spacer {
flex: 1;
}
> label {
display: flex;
align-items: center;
}
> button { > button {
padding: .5rem 1rem; padding: .5rem 1rem;

View file

@ -31,6 +31,11 @@ interface StateEventViewProps {
onDone?: (type: string, stateKey: string) => void onDone?: (type: string, stateKey: string) => void
} }
interface NewMessageEventViewProps {
room: RoomStateStore
onBack: () => void
}
interface StateKeyListProps { interface StateKeyListProps {
room: RoomStateStore room: RoomStateStore
type: string type: string
@ -84,6 +89,7 @@ const StateEventView = ({ room, type, stateKey, onBack, onDone }: StateEventView
<h3>New state event</h3> <h3>New state event</h3>
<div className="new-event-type"> <div className="new-event-type">
<input <input
autoFocus
type="text" type="text"
value={newType} value={newType}
onChange={evt => setNewType(evt.target.value)} onChange={evt => setNewType(evt.target.value)}
@ -100,7 +106,7 @@ const StateEventView = ({ room, type, stateKey, onBack, onDone }: StateEventView
: <h3><code>{type}</code> ({stateKey ? <code>{stateKey}</code> : "no state key"})</h3> : <h3><code>{type}</code> ({stateKey ? <code>{stateKey}</code> : "no state key"})</h3>
} }
</div> </div>
<div className={`state-event-content`}> <div className="state-event-content">
{editingContent !== null {editingContent !== null
? <textarea rows={10} value={editingContent} onChange={evt => setEditingContent(evt.target.value)}/> ? <textarea rows={10} value={editingContent} onChange={evt => setEditingContent(evt.target.value)}/>
: <JSONView data={event}/> : <JSONView data={event}/>
@ -119,6 +125,65 @@ const StateEventView = ({ room, type, stateKey, onBack, onDone }: StateEventView
) )
} }
const NewMessageEventView = ({ room, onBack }: NewMessageEventViewProps) => {
const [content, setContent] = useState<string>("{\n\n}")
const [type, setType] = useState<string>("")
const [disableEncryption, setDisableEncryption] = useState<boolean>(false)
const client = use(ClientContext)!
const sendEvent = () => {
let parsedContent
try {
parsedContent = JSON.parse(content || "{}")
} catch (err) {
window.alert(`Failed to parse JSON: ${err}`)
return
}
client.sendEvent(room.roomID, type, parsedContent, disableEncryption).then(
() => {
console.log("Successfully sent message event", room.roomID, type)
onBack()
},
err => {
console.error("Failed to send message event", err)
window.alert(`Failed to send message event: ${err}`)
},
)
}
return (
<div className="state-explorer state-event-view">
<div className="state-header">
<h3>New message event</h3>
<div className="new-event-type">
<input
autoFocus
type="text"
value={type}
onChange={evt => setType(evt.target.value)}
placeholder="Event type"
/>
</div>
</div>
<div className="state-event-content">
<textarea rows={10} value={content} onChange={evt => setContent(evt.target.value)}/>
</div>
<div className="nav-buttons">
<button onClick={onBack}>Back</button>
<button onClick={sendEvent}>Send</button>
{room.meta.current.encryption_event ? <label>
<input
type="checkbox"
checked={disableEncryption}
onChange={evt => setDisableEncryption(evt.target.checked)}
/>
Disable encryption
</label> : null}
</div>
</div>
)
}
const StateKeyList = ({ room, type, onSelectStateKey, onBack }: StateKeyListProps) => { const StateKeyList = ({ room, type, onSelectStateKey, onBack }: StateKeyListProps) => {
const stateMap = room.state.get(type) const stateMap = room.state.get(type)
return ( return (
@ -141,7 +206,7 @@ const StateKeyList = ({ room, type, onSelectStateKey, onBack }: StateKeyListProp
} }
export const StateExplorer = ({ room }: StateExplorerProps) => { export const StateExplorer = ({ room }: StateExplorerProps) => {
const [creatingNew, setCreatingNew] = useState(false) const [creatingNew, setCreatingNew] = useState<"message" | "state" | null>(null)
const [selectedType, setSelectedType] = useState<string | null>(null) const [selectedType, setSelectedType] = useState<string | null>(null)
const [selectedStateKey, setSelectedStateKey] = useState<string | null>(null) const [selectedStateKey, setSelectedStateKey] = useState<string | null>(null)
const [loadingState, setLoadingState] = useState(false) const [loadingState, setLoadingState] = useState(false)
@ -167,7 +232,7 @@ export const StateExplorer = ({ room }: StateExplorerProps) => {
const handleBack = useCallback(() => { const handleBack = useCallback(() => {
if (creatingNew) { if (creatingNew) {
setCreatingNew(false) setCreatingNew(null)
} else if (selectedStateKey !== null && selectedType !== null) { } else if (selectedStateKey !== null && selectedType !== null) {
setSelectedStateKey(null) setSelectedStateKey(null)
const stateKeysMap = room.state.get(selectedType) const stateKeysMap = room.state.get(selectedType)
@ -178,18 +243,25 @@ export const StateExplorer = ({ room }: StateExplorerProps) => {
setSelectedType(null) setSelectedType(null)
} }
}, [selectedType, selectedStateKey, creatingNew, room]) }, [selectedType, selectedStateKey, creatingNew, room])
const handleNewEventDone = useCallback((type: string, stateKey: string) => { const handleNewEventDone = useCallback((type: string, stateKey?: string) => {
setCreatingNew(false) setCreatingNew(null)
setSelectedType(type) if (stateKey !== undefined) {
setSelectedStateKey(stateKey) setSelectedType(type)
setSelectedStateKey(stateKey)
}
}, []) }, [])
if (creatingNew) { if (creatingNew === "state") {
return <StateEventView return <StateEventView
room={room} room={room}
onBack={handleBack} onBack={handleBack}
onDone={handleNewEventDone} onDone={handleNewEventDone}
/> />
} else if (creatingNew === "message") {
return <NewMessageEventView
room={room}
onBack={handleBack}
/>
} else if (selectedType !== null && selectedStateKey !== null) { } else if (selectedType !== null && selectedStateKey !== null) {
return <StateEventView return <StateEventView
room={room} room={room}
@ -237,7 +309,9 @@ export const StateExplorer = ({ room }: StateExplorerProps) => {
: "Load room members" : "Load room members"
: "Load room state and members"} : "Load room state and members"}
</button> </button>
<button onClick={() => setCreatingNew(true)}>Send new state event</button> <div className="spacer"/>
<button onClick={() => setCreatingNew("message")}>Send new message event</button>
<button onClick={() => setCreatingNew("state")}>Send new state event</button>
</div> </div>
</div> </div>
} }