From 5eee4ac2952a454f32933317ccd7493a43239a00 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 25 Oct 2026 03:07:43 +0000 Subject: [PATCH] Send a JSON body on face-room writes Enter chat POSTs /v1/face-rooms/:id/start with no body. The deployed Bot Social answers 500 "internal error" for that request before the handler runs, and both homes show it in the waiting dialog. An empty JSON object reaches the handler. Docs-not-needed: the face-room request body is not an architecture change. Co-authored-by: Danyang Chen --- .../1085-face-enter-internal-error.fixed.md | 1 + plugins/social/src/face-rooms.js | 10 ++++++-- plugins/social/test/face-rooms.test.js | 24 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 changelog.d/1085-face-enter-internal-error.fixed.md diff --git a/changelog.d/1085-face-enter-internal-error.fixed.md b/changelog.d/1085-face-enter-internal-error.fixed.md new file mode 100644 index 00000000..acb6da52 --- /dev/null +++ b/changelog.d/1085-face-enter-internal-error.fixed.md @@ -0,0 +1 @@ +Enter chat in a face-to-face room no longer shows "internal error" after the other person has joined. The start request sends an empty JSON body, which Bot Social accepts ([#1085](https://github.com/ThinkFlowLab/FlyBot/issues/1085)). diff --git a/plugins/social/src/face-rooms.js b/plugins/social/src/face-rooms.js index a8642f67..24727f4e 100644 --- a/plugins/social/src/face-rooms.js +++ b/plugins/social/src/face-rooms.js @@ -706,15 +706,21 @@ export function createFaceRoomApi({ server, token, fetch: fetchImpl = socialFetc let response let data const sentAt = Date.now() + // A POST or PUT with no body never reaches the handler on the deployed Bot Social + // (social.botawaken.com, 2026-10-25): it answers 500 "internal error" before auth. + // The same call with `{}` is an empty JSON body and the handler runs. Enter chat + // is POST /face-rooms/:id/start, which otherwise sends nothing (#1085). + const verb = String(method || 'GET').toUpperCase() + const payload = body === undefined && (verb === 'POST' || verb === 'PUT' || verb === 'PATCH') ? {} : body try { response = await fetchImpl(url, { method, headers: { authorization: `Bearer ${token}`, accept: 'application/json', - ...(body !== undefined ? { 'content-type': 'application/json' } : {}), + ...(payload !== undefined ? { 'content-type': 'application/json' } : {}), }, - body: body !== undefined ? JSON.stringify(body) : undefined, + body: payload !== undefined ? JSON.stringify(payload) : undefined, signal, }) const offset = serverClockOffset(response.headers?.get?.('date'), sentAt, Date.now()) diff --git a/plugins/social/test/face-rooms.test.js b/plugins/social/test/face-rooms.test.js index 2ed0324b..b22973e5 100644 --- a/plugins/social/test/face-rooms.test.js +++ b/plugins/social/test/face-rooms.test.js @@ -439,6 +439,30 @@ test('REST join, poll, start, leave, and pending_approval send', async () => { assert.equal(left.status, 'closed') }) +test('#1085: Enter chat sends a JSON body, so a bodyless POST is not an internal error', async () => { + // social.botawaken.com (2026-10-25): POST /v1/face-rooms/:id/start with no body + // answers 500 { code: "internal_error", message: "internal error" } before auth. + // The same POST with `{}` reaches the handler. Enter chat is that POST. + const fetch = async (url, init) => { + assert.equal(String(url), 'https://s/v1/face-rooms/frm_1/start') + assert.equal(init.method, 'POST') + const raw = init.body + if (raw === undefined || raw === '') { + return new Response(JSON.stringify({ error: { code: 'internal_error', message: 'internal error' } }), { status: 500 }) + } + assert.equal(init.headers['content-type'], 'application/json') + assert.equal(raw, '{}') + return new Response(JSON.stringify({ + room_id: 'frm_1', status: 'open', members: [], events: [], event_cursor: 1, + started_at: '2026-10-25T00:00:00.000Z', + }), { status: 200 }) + } + const api = createFaceRoomApi({ server: 'https://s', token: 'otk_test', fetch }) + const started = await api.start('frm_1') + assert.equal(started.status, 'open') + assert.equal(started.roomId, 'frm_1') +}) + test('failed join never includes members or events', () => { const err = mapFaceError({ error: { code: 'room_full', members: [{ handle: '@x' }], events: [1], room_id: 'frm_leak' } }, 409) assert.equal(err.message.includes('frm_leak'), false)