mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Open media item share sessions shown on listening sessions page, create device info for share sessions
This commit is contained in:
parent
d7ace4d1dc
commit
8e286a6070
@ -80,8 +80,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/3">
|
||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
|
||||
<p class="mb-1 text-xs">{{ _session.userId }}</p>
|
||||
<p v-if="!isMediaItemShareSession" class="font-semibold uppercase text-xs text-gray-400 tracking-wide mb-2 mt-6 md:mt-0">{{ $strings.LabelUser }}</p>
|
||||
<p v-if="!isMediaItemShareSession" class="mb-1 text-xs">{{ _session.userId }}</p>
|
||||
|
||||
<p class="font-semibold uppercase text-xs text-gray-400 tracking-wide mt-6 mb-2">{{ $strings.LabelMediaPlayer }}</p>
|
||||
<p class="mb-1">{{ playMethodName }}</p>
|
||||
@ -99,8 +99,8 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<ui-btn v-if="!isOpenSession" small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
|
||||
<ui-btn v-else small color="error" @click.stop="closeSessionClick">Close Open Session</ui-btn>
|
||||
<ui-btn v-if="!isOpenSession && !isMediaItemShareSession" small color="error" @click.stop="deleteSessionClick">{{ $strings.ButtonDelete }}</ui-btn>
|
||||
<ui-btn v-else-if="!isMediaItemShareSession" small color="error" @click.stop="closeSessionClick">Close Open Session</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</modals-modal>
|
||||
@ -166,6 +166,9 @@ export default {
|
||||
},
|
||||
isOpenSession() {
|
||||
return !!this._session.open
|
||||
},
|
||||
isMediaItemShareSession() {
|
||||
return this._session.mediaPlayer === 'web-share'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -100,7 +100,7 @@
|
||||
</div>
|
||||
<p v-else class="text-white text-opacity-50">{{ $strings.MessageNoListeningSessions }}</p>
|
||||
|
||||
<div class="w-full my-8 h-px bg-white/10" />
|
||||
<div v-if="openListeningSessions.length" class="w-full my-8 h-px bg-white/10" />
|
||||
|
||||
<!-- open listening sessions table -->
|
||||
<p v-if="openListeningSessions.length" class="text-lg my-4">Open Listening Sessions</p>
|
||||
@ -144,6 +144,45 @@
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="openShareListeningSessions.length" class="w-full my-8 h-px bg-white/10" />
|
||||
|
||||
<!-- open share listening sessions table -->
|
||||
<p v-if="openShareListeningSessions.length" class="text-lg my-4">Open Share Listening Sessions</p>
|
||||
<div v-if="openShareListeningSessions.length" class="block max-w-full">
|
||||
<table class="userSessionsTable">
|
||||
<tr class="bg-primary bg-opacity-40">
|
||||
<th class="w-48 min-w-48 text-left">{{ $strings.LabelItem }}</th>
|
||||
<th class="w-20 min-w-20 text-left hidden md:table-cell">{{ $strings.LabelUser }}</th>
|
||||
<th class="w-32 min-w-32 text-left hidden md:table-cell">{{ $strings.LabelPlayMethod }}</th>
|
||||
<th class="w-32 min-w-32 text-left hidden sm:table-cell">{{ $strings.LabelDeviceInfo }}</th>
|
||||
<th class="w-16 min-w-16">{{ $strings.LabelLastTime }}</th>
|
||||
<th class="flex-grow hidden sm:table-cell">{{ $strings.LabelLastUpdate }}</th>
|
||||
</tr>
|
||||
|
||||
<tr v-for="session in openShareListeningSessions" :key="`open-${session.id}`" class="cursor-pointer" @click="showSession(session)">
|
||||
<td class="py-1 max-w-48">
|
||||
<p class="text-xs text-gray-200 truncate">{{ session.displayTitle }}</p>
|
||||
<p class="text-xs text-gray-400 truncate">{{ session.displayAuthor }}</p>
|
||||
</td>
|
||||
<td class="hidden md:table-cell"></td>
|
||||
<td class="hidden md:table-cell">
|
||||
<p class="text-xs">{{ getPlayMethodName(session.playMethod) }}</p>
|
||||
</td>
|
||||
<td class="hidden sm:table-cell max-w-32 min-w-32">
|
||||
<p class="text-xs truncate" v-html="getDeviceInfoString(session.deviceInfo)" />
|
||||
</td>
|
||||
<td class="text-center hover:underline" @click.stop="clickCurrentTime(session)">
|
||||
<p class="text-xs font-mono">{{ $secondsToTimestamp(session.currentTime) }}</p>
|
||||
</td>
|
||||
<td class="text-center hidden sm:table-cell">
|
||||
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
|
||||
<p class="text-xs text-gray-200">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
|
||||
</ui-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</app-settings-content>
|
||||
|
||||
<modals-listening-session-modal v-model="showSessionModal" :session="selectedSession" @removedSession="removedSession" @closedSession="closedSession" />
|
||||
@ -180,6 +219,7 @@ export default {
|
||||
selectedSession: null,
|
||||
listeningSessions: [],
|
||||
openListeningSessions: [],
|
||||
openShareListeningSessions: [],
|
||||
numPages: 0,
|
||||
total: 0,
|
||||
currentPage: 0,
|
||||
@ -455,6 +495,7 @@ export default {
|
||||
s.open = true
|
||||
return s
|
||||
})
|
||||
this.openShareListeningSessions = data.shareSessions || []
|
||||
},
|
||||
init() {
|
||||
this.loadSessions(0)
|
||||
|
@ -26,7 +26,7 @@ export default {
|
||||
if (query.t && !isNaN(query.t)) {
|
||||
endpoint += `?t=${query.t}`
|
||||
}
|
||||
const mediaItemShare = await app.$axios.$get(endpoint).catch((error) => {
|
||||
const mediaItemShare = await app.$axios.$get(endpoint, { timeout: 10000 }).catch((error) => {
|
||||
console.error('Failed', error)
|
||||
return null
|
||||
})
|
||||
|
@ -81,7 +81,7 @@ class Server {
|
||||
// Routers
|
||||
this.apiRouter = new ApiRouter(this)
|
||||
this.hlsRouter = new HlsRouter(this.auth, this.playbackSessionManager)
|
||||
this.publicRouter = new PublicRouter()
|
||||
this.publicRouter = new PublicRouter(this.playbackSessionManager)
|
||||
|
||||
Logger.logManager = new LogManager()
|
||||
|
||||
|
@ -2,8 +2,10 @@ const Logger = require('../Logger')
|
||||
const Database = require('../Database')
|
||||
const { toNumber, isUUID } = require('../utils/index')
|
||||
|
||||
const ShareManager = require('../managers/ShareManager')
|
||||
|
||||
class SessionController {
|
||||
constructor() { }
|
||||
constructor() {}
|
||||
|
||||
async findOne(req, res) {
|
||||
return res.json(req.playbackSession)
|
||||
@ -68,15 +70,13 @@ class SessionController {
|
||||
const { rows, count } = await Database.playbackSessionModel.findAndCountAll({
|
||||
where,
|
||||
include,
|
||||
order: [
|
||||
[orderKey, orderDesc]
|
||||
],
|
||||
order: [[orderKey, orderDesc]],
|
||||
limit: itemsPerPage,
|
||||
offset: itemsPerPage * page
|
||||
})
|
||||
|
||||
// Map playback sessions to old playback sessions
|
||||
const sessions = rows.map(session => {
|
||||
const sessions = rows.map((session) => {
|
||||
const oldPlaybackSession = Database.playbackSessionModel.getOldPlaybackSession(session)
|
||||
if (session.user) {
|
||||
return {
|
||||
@ -112,15 +112,18 @@ class SessionController {
|
||||
}
|
||||
|
||||
const minifiedUserObjects = await Database.userModel.getMinifiedUserObjects()
|
||||
const openSessions = this.playbackSessionManager.sessions.map(se => {
|
||||
const openSessions = this.playbackSessionManager.sessions.map((se) => {
|
||||
return {
|
||||
...se.toJSON(),
|
||||
user: minifiedUserObjects.find(u => u.id === se.userId) || null
|
||||
user: minifiedUserObjects.find((u) => u.id === se.userId) || null
|
||||
}
|
||||
})
|
||||
|
||||
const shareSessions = ShareManager.openSharePlaybackSessions.map((se) => se.toJSON())
|
||||
|
||||
res.json({
|
||||
sessions: openSessions
|
||||
sessions: openSessions,
|
||||
shareSessions
|
||||
})
|
||||
}
|
||||
|
||||
@ -170,7 +173,7 @@ class SessionController {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
// Validate session ids
|
||||
if (!req.body.sessions?.length || !Array.isArray(req.body.sessions) || req.body.sessions.some(s => !isUUID(s))) {
|
||||
if (!req.body.sessions?.length || !Array.isArray(req.body.sessions) || req.body.sessions.some((s) => !isUUID(s))) {
|
||||
Logger.error(`[SessionController] Invalid request body. "sessions" array is required`, req.body)
|
||||
return res.status(400).send('Invalid request body. "sessions" array of session id strings is required.')
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
const uuidv4 = require('uuid').v4
|
||||
const uuid = require('uuid')
|
||||
const Path = require('path')
|
||||
const { Op } = require('sequelize')
|
||||
const Logger = require('../Logger')
|
||||
@ -18,6 +18,8 @@ class ShareController {
|
||||
* GET: /api/share/:slug
|
||||
* Get media item share by slug
|
||||
*
|
||||
* @this {import('../routers/PublicRouter')}
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
*/
|
||||
@ -28,7 +30,8 @@ class ShareController {
|
||||
|
||||
const mediaItemShare = ShareManager.findBySlug(slug)
|
||||
if (!mediaItemShare) {
|
||||
return res.status(404)
|
||||
Logger.warn(`[ShareController] Media item share not found with slug ${slug}`)
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
if (mediaItemShare.expiresAt && mediaItemShare.expiresAt.valueOf() < Date.now()) {
|
||||
ShareManager.removeMediaItemShare(mediaItemShare.id)
|
||||
@ -43,7 +46,10 @@ class ShareController {
|
||||
return res.json(mediaItemShare)
|
||||
} else {
|
||||
Logger.info(`[ShareController] Share playback session not found with id ${req.cookies.share_session_id}`)
|
||||
res.clearCookie('share_session_id')
|
||||
if (!uuid.validate(req.cookies.share_session_id) || uuid.version(req.cookies.share_session_id) !== 4) {
|
||||
Logger.warn(`[ShareController] Invalid share session id ${req.cookies.share_session_id}`)
|
||||
res.clearCookie('share_session_id')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,11 +81,18 @@ class ShareController {
|
||||
startTime = 0
|
||||
}
|
||||
|
||||
const shareSessionId = req.cookies.share_session_id || uuid.v4()
|
||||
const clientDeviceInfo = {
|
||||
clientName: 'Abs Web Share',
|
||||
deviceId: shareSessionId
|
||||
}
|
||||
const deviceInfo = await this.playbackSessionManager.getDeviceInfo(req, clientDeviceInfo)
|
||||
|
||||
const newPlaybackSession = new PlaybackSession()
|
||||
newPlaybackSession.setData(oldLibraryItem, null, 'web-public', null, startTime)
|
||||
newPlaybackSession.setData(oldLibraryItem, null, 'web-share', deviceInfo, startTime)
|
||||
newPlaybackSession.audioTracks = publicTracks
|
||||
newPlaybackSession.playMethod = PlayMethod.DIRECTPLAY
|
||||
newPlaybackSession.shareSessionId = uuidv4() // New share session id
|
||||
newPlaybackSession.shareSessionId = shareSessionId
|
||||
newPlaybackSession.mediaItemShareId = mediaItemShare.id
|
||||
newPlaybackSession.coverAspectRatio = oldLibraryItem.librarySettings.coverAspectRatio
|
||||
|
||||
@ -119,7 +132,6 @@ class ShareController {
|
||||
|
||||
const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id)
|
||||
if (!playbackSession || playbackSession.mediaItemShareId !== mediaItemShare.id) {
|
||||
res.clearCookie('share_session_id')
|
||||
return res.status(404).send('Share session not found')
|
||||
}
|
||||
|
||||
@ -160,7 +172,6 @@ class ShareController {
|
||||
|
||||
const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id)
|
||||
if (!playbackSession || playbackSession.mediaItemShareId !== mediaItemShare.id) {
|
||||
res.clearCookie('share_session_id')
|
||||
return res.status(404).send('Share session not found')
|
||||
}
|
||||
|
||||
@ -211,7 +222,6 @@ class ShareController {
|
||||
|
||||
const playbackSession = ShareManager.findPlaybackSessionBySessionId(req.cookies.share_session_id)
|
||||
if (!playbackSession || playbackSession.mediaItemShareId !== mediaItemShare.id) {
|
||||
res.clearCookie('share_session_id')
|
||||
return res.status(404).send('Share session not found')
|
||||
}
|
||||
|
||||
|
@ -35,14 +35,18 @@ class PlaybackSessionManager {
|
||||
return session?.stream || null
|
||||
}
|
||||
|
||||
async getDeviceInfo(req) {
|
||||
/**
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {Object} [clientDeviceInfo]
|
||||
* @returns {Promise<DeviceInfo>}
|
||||
*/
|
||||
async getDeviceInfo(req, clientDeviceInfo = null) {
|
||||
const ua = uaParserJs(req.headers['user-agent'])
|
||||
const ip = requestIp.getClientIp(req)
|
||||
|
||||
const clientDeviceInfo = req.body?.deviceInfo || null
|
||||
|
||||
const deviceInfo = new DeviceInfo()
|
||||
deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion, req.user.id)
|
||||
deviceInfo.setData(ip, ua, clientDeviceInfo, serverVersion, req.user?.id)
|
||||
|
||||
if (clientDeviceInfo?.deviceId) {
|
||||
const existingDevice = await Database.getDeviceByDeviceId(clientDeviceInfo.deviceId)
|
||||
@ -66,7 +70,7 @@ class PlaybackSessionManager {
|
||||
* @param {string} [episodeId]
|
||||
*/
|
||||
async startSessionRequest(req, res, episodeId) {
|
||||
const deviceInfo = await this.getDeviceInfo(req)
|
||||
const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
|
||||
Logger.debug(`[PlaybackSessionManager] startSessionRequest for device ${deviceInfo.deviceDescription}`)
|
||||
const { user, libraryItem, body: options } = req
|
||||
const session = await this.startSession(user, deviceInfo, libraryItem, episodeId, options)
|
||||
@ -82,7 +86,7 @@ class PlaybackSessionManager {
|
||||
}
|
||||
|
||||
async syncLocalSessionsRequest(req, res) {
|
||||
const deviceInfo = await this.getDeviceInfo(req)
|
||||
const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
|
||||
const user = req.user
|
||||
const sessions = req.body.sessions || []
|
||||
|
||||
@ -199,7 +203,7 @@ class PlaybackSessionManager {
|
||||
}
|
||||
|
||||
async syncLocalSessionRequest(req, res) {
|
||||
const deviceInfo = await this.getDeviceInfo(req)
|
||||
const deviceInfo = await this.getDeviceInfo(req, req.body?.deviceInfo)
|
||||
const user = req.user
|
||||
const sessionJson = req.body
|
||||
const result = await this.syncLocalSession(user, sessionJson, deviceInfo)
|
||||
|
@ -2,7 +2,10 @@ const express = require('express')
|
||||
const ShareController = require('../controllers/ShareController')
|
||||
|
||||
class PublicRouter {
|
||||
constructor() {
|
||||
constructor(playbackSessionManager) {
|
||||
/** @type {import('../managers/PlaybackSessionManager')} */
|
||||
this.playbackSessionManager = playbackSessionManager
|
||||
|
||||
this.router = express()
|
||||
this.router.disable('x-powered-by')
|
||||
this.init()
|
||||
|
Loading…
Reference in New Issue
Block a user