mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(stats): record web audiobook sessions as listening
The web audiobook player shared the reading session composable, which never sent a sessionType, so the server stored every listening session as a read session and counted audiobook time as reading in activity stats. Let callers pass an optional sessionType and have the audiobook view send listen. Other readers keep omitting the field, so their payloads are unchanged. Existing rows are not backfilled.
This commit is contained in:
@@ -161,7 +161,7 @@ const audioBookmarks = useAudioBookmarks(props.bookId)
|
||||
|
||||
// ── Reading session ───────────────────────────────────────────────────────────
|
||||
|
||||
const session = useReadingSession(props.fileId, () => ({ percentage: progressPct.value }), { trackingEnabled })
|
||||
const session = useReadingSession(props.fileId, () => ({ percentage: progressPct.value }), { trackingEnabled, sessionType: 'listen' })
|
||||
|
||||
// ── Ticker (updates position every 500ms while playing) ──────────────────────
|
||||
|
||||
|
||||
@@ -108,3 +108,39 @@ describe('useReadingSession - elapsedMinutes', () => {
|
||||
expect(apiMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('useReadingSession - sessionType', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.clearAllMocks()
|
||||
apiMock.mockResolvedValue({ ok: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
async function savedBody(options?: Parameters<typeof useReadingSession>[2]): Promise<Record<string, unknown>> {
|
||||
const { onActivity, endSession } = useReadingSession(7, () => ({ percentage: 10 }), options)
|
||||
onActivity()
|
||||
await vi.advanceTimersByTimeAsync(20 * 1000)
|
||||
endSession()
|
||||
expect(apiMock).toHaveBeenCalledTimes(1)
|
||||
const [url, init] = apiMock.mock.calls[0] as [string, { body: string }]
|
||||
expect(url).toBe('/api/v1/books/files/7/sessions')
|
||||
return JSON.parse(init.body) as Record<string, unknown>
|
||||
}
|
||||
|
||||
it('omits sessionType by default so the server records a read session', async () => {
|
||||
const body = await savedBody()
|
||||
expect(body).not.toHaveProperty('sessionType')
|
||||
expect(body).not.toHaveProperty('source')
|
||||
})
|
||||
|
||||
it('sends the configured sessionType', async () => {
|
||||
const body = await savedBody({ sessionType: 'listen' })
|
||||
expect(body.sessionType).toBe('listen')
|
||||
expect(body).not.toHaveProperty('source')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,8 +11,11 @@ const IDLE_TIMEOUT_MS = 5 * 60 * 1000
|
||||
const MIN_SESSION_MS = 10 * 1000
|
||||
const ELAPSED_UPDATE_INTERVAL_MS = 30 * 1000
|
||||
|
||||
export type ReadingSessionType = 'read' | 'tts' | 'listen'
|
||||
|
||||
export interface ReadingSessionOptions {
|
||||
trackingEnabled?: MaybeRef<boolean>
|
||||
sessionType?: ReadingSessionType
|
||||
}
|
||||
|
||||
function generateSessionId(): string {
|
||||
@@ -150,6 +153,7 @@ export function useReadingSession(bookFileId: number, getProgress: () => Progres
|
||||
durationSeconds,
|
||||
progressDelta,
|
||||
endProgress,
|
||||
...(options.sessionType ? { sessionType: options.sessionType } : {}),
|
||||
})
|
||||
|
||||
const url = `/api/v1/books/files/${bookFileId}/sessions`
|
||||
|
||||
Reference in New Issue
Block a user