mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 15:11:29 -07:00
update presence on drop
This commit is contained in:
@@ -673,6 +673,10 @@ const app = new Hono<App>()
|
|||||||
// The instance they were in lost a player — recompute its fullness so a
|
// The instance they were in lost a player — recompute its fullness so a
|
||||||
// full room frees up. No-op for the synthetic dorm/orientation instances.
|
// full room frees up. No-op for the synthetic dorm/orientation instances.
|
||||||
if (instanceId != null) await refreshInstanceFullness(c.env.DB, instanceId)
|
if (instanceId != null) await refreshInstanceFullness(c.env.DB, instanceId)
|
||||||
|
// Their presence changed — tell online friends they went offline. Presence
|
||||||
|
// is already cleared, so notifyFriendsPresence reads null and sends the
|
||||||
|
// offline snapshot (roomInstance null, isOnline false).
|
||||||
|
await notifyFriendsPresence(c, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.body(null, 200)
|
return c.body(null, 200)
|
||||||
|
|||||||
@@ -1084,7 +1084,7 @@ describe('auth-gated endpoints', () => {
|
|||||||
// fan-out is a single batch call carrying the friend ids.
|
// fan-out is a single batch call carrying the friend ids.
|
||||||
type Batch = {
|
type Batch = {
|
||||||
playerIds: number[]
|
playerIds: number[]
|
||||||
notificationType: number
|
notificationType: number | string
|
||||||
data: {
|
data: {
|
||||||
playerId: number
|
playerId: number
|
||||||
statusVisibility: number
|
statusVisibility: number
|
||||||
@@ -1110,7 +1110,7 @@ describe('auth-gated endpoints', () => {
|
|||||||
// Delivered to the two friends (in both graph directions), not the pending-request
|
// Delivered to the two friends (in both graph directions), not the pending-request
|
||||||
// player (9703).
|
// player (9703).
|
||||||
expect(batch.playerIds.slice().sort((a, b) => a - b)).toEqual([9701, 9702])
|
expect(batch.playerIds.slice().sort((a, b) => a - b)).toEqual([9701, 9702])
|
||||||
expect(batch.notificationType).toBe(12) // NotificationType.SubscriptionUpdatePresence
|
expect(batch.notificationType).toBe('PresenceUpdate') // NotificationType.SubscriptionUpdatePresence
|
||||||
expect(batch.data).toMatchObject({
|
expect(batch.data).toMatchObject({
|
||||||
playerId: 9700,
|
playerId: 9700,
|
||||||
statusVisibility: 0, // Everyone — not hidden from friends
|
statusVisibility: 0, // Everyone — not hidden from friends
|
||||||
@@ -1140,6 +1140,57 @@ describe('auth-gated endpoints', () => {
|
|||||||
expect(await (await hub().fetch('http://do/all')).json()).toEqual([])
|
expect(await (await hub().fetch('http://do/all')).json()).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('logout fires SubscriptionUpdatePresence (offline) to friends', async () => {
|
||||||
|
type Batch = {
|
||||||
|
playerIds: number[]
|
||||||
|
notificationType: number | string
|
||||||
|
data: { playerId: number; isOnline: boolean; roomInstance: Record<string, unknown> | null }
|
||||||
|
}
|
||||||
|
const hub = () => env.RECFLARE_NOTIFICATIONS_HUB.getByName('global')
|
||||||
|
const sent = async (): Promise<Batch[]> =>
|
||||||
|
(await (await hub().fetch('http://do/all')).json()) as Batch[]
|
||||||
|
|
||||||
|
// 9900 is friends with 9901.
|
||||||
|
await env.DB.prepare(
|
||||||
|
'INSERT INTO relationship (requester_id, target_id, relationship_type) VALUES (?1, ?2, ?3)'
|
||||||
|
)
|
||||||
|
.bind(9900, 9901, 3)
|
||||||
|
.run()
|
||||||
|
|
||||||
|
// 9900 enters a room (presence created), then reset the hub so we isolate the
|
||||||
|
// logout push from the entry push.
|
||||||
|
await exports.default.fetch(`${ORIGIN}/matchmake/room/2`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: await bearer('9900'),
|
||||||
|
})
|
||||||
|
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||||
|
|
||||||
|
const res = await exports.default.fetch(`${ORIGIN}/player/logout`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: await bearer('9900'),
|
||||||
|
})
|
||||||
|
expect(res.status).toBe(200)
|
||||||
|
|
||||||
|
// The friend gets an offline presence snapshot: no room, isOnline false.
|
||||||
|
const batch = await sent()
|
||||||
|
expect(batch).toHaveLength(1)
|
||||||
|
expect(batch[0].playerIds).toEqual([9901])
|
||||||
|
expect(batch[0].notificationType).toBe('PresenceUpdate') // SubscriptionUpdatePresence
|
||||||
|
expect(batch[0].data).toMatchObject({ playerId: 9900, isOnline: false, roomInstance: null })
|
||||||
|
|
||||||
|
// Presence is actually cleared — a second logout (no presence) fires nothing.
|
||||||
|
await hub().fetch('http://do/all', { method: 'DELETE' })
|
||||||
|
await exports.default.fetch(`${ORIGIN}/player/logout`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: await bearer('9900'),
|
||||||
|
})
|
||||||
|
expect(await sent()).toEqual([])
|
||||||
|
|
||||||
|
// An unauthenticated logout is a no-op too.
|
||||||
|
await exports.default.fetch(`${ORIGIN}/player/logout`, { method: 'POST' })
|
||||||
|
expect(await sent()).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
test('POST /matchmake/player/:id follows a friend into their room, friends only', async () => {
|
test('POST /matchmake/player/:id follows a friend into their room, friends only', async () => {
|
||||||
// 9800 is friends with 9801 (in a room) and 9803 (not in any room); 9802 is not a
|
// 9800 is friends with 9801 (in a room) and 9803 (not in any room); 9802 is not a
|
||||||
// friend.
|
// friend.
|
||||||
|
|||||||
Reference in New Issue
Block a user