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)