mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2026-08-31 19:03:45 +02:00
215 lines
6.3 KiB
TypeScript
215 lines
6.3 KiB
TypeScript
/*
|
|
* server component for the TimeLimit App
|
|
* Copyright (C) 2019 Jonas Lochmann
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { EventEmitter } from 'events'
|
|
import { Database } from '../database'
|
|
|
|
export class ConnectedDevicesManager {
|
|
private deviceConnectionCounters = new Map<string, number>()
|
|
// event name = key, value = boolean/ isConnected
|
|
deviceConnectionChangeEmitter = new EventEmitter()
|
|
|
|
static buildKey = ({ familyId, deviceId }: {familyId: string, deviceId: string}) => `${familyId}_${deviceId}`
|
|
|
|
reportDeviceConnected = ({ key }: {key: string}) => {
|
|
const oldValue = this.deviceConnectionCounters.get(key) || 0
|
|
const newValue = oldValue + 1
|
|
|
|
this.deviceConnectionCounters.set(key, newValue)
|
|
|
|
if (oldValue === 0) {
|
|
this.deviceConnectionChangeEmitter.emit(key, true)
|
|
}
|
|
}
|
|
|
|
reportDeviceDisconnected = ({ key }: {key: string}) => {
|
|
const oldValue = this.deviceConnectionCounters.get(key) || 0
|
|
const newValue = Math.max(0, oldValue - 1)
|
|
|
|
if (newValue === 0) {
|
|
this.deviceConnectionCounters.delete(key)
|
|
} else {
|
|
this.deviceConnectionCounters.set(key, newValue)
|
|
}
|
|
|
|
if (oldValue !== 0 && newValue === 0) {
|
|
this.deviceConnectionChangeEmitter.emit(key, false)
|
|
}
|
|
}
|
|
|
|
isDeviceConnected = ({ key }: {key: string}) => (
|
|
this.deviceConnectionCounters.has(key) &&
|
|
(this.deviceConnectionCounters.get(key) !== 0)
|
|
)
|
|
}
|
|
|
|
export class VisibleConnectedDevicesManager {
|
|
connectedDevicesManager = new ConnectedDevicesManager()
|
|
private database: Database
|
|
|
|
constructor ({ database }: {
|
|
database: Database
|
|
}) {
|
|
this.database = database
|
|
}
|
|
|
|
private familyDeviceShareConnectedChangeEmitter = new EventEmitter()
|
|
|
|
notifyShareConnectedChanged = ({ familyId, deviceId, showDeviceConnected }: { familyId: string, deviceId: string, showDeviceConnected: boolean }) => {
|
|
this.familyDeviceShareConnectedChangeEmitter.emit(familyId, { deviceId, showDeviceConnected })
|
|
}
|
|
|
|
observeConnectedDevicesOfFamily = ({ familyId, listener }: {
|
|
familyId: string
|
|
listener: (deviceIds: Array<string>) => void
|
|
}): {
|
|
shutdown: () => void
|
|
} => {
|
|
let observesDevices = new Set<string>()
|
|
let devicesWithSharingEnabled = new Set<string>()
|
|
let connectedDevices = new Set<string>()
|
|
let sentConnectedDevices = new Set<string>()
|
|
let hasShutDown = false
|
|
let shutdownHooks: Array<() => void> = []
|
|
|
|
const shutdown = () => {
|
|
hasShutDown = true
|
|
|
|
shutdownHooks.forEach((hook) => hook())
|
|
}
|
|
|
|
const sendStatus = () => {
|
|
if (hasShutDown) {
|
|
return
|
|
}
|
|
|
|
let result: Array<string> = []
|
|
|
|
sentConnectedDevices.forEach((deviceId) => result.push(deviceId))
|
|
|
|
listener(result)
|
|
}
|
|
|
|
const addDevice = ({ deviceId, showDeviceConnected }: {
|
|
deviceId: string
|
|
showDeviceConnected: boolean
|
|
}) => {
|
|
const key = ConnectedDevicesManager.buildKey({ familyId, deviceId })
|
|
const isConnected = this.connectedDevicesManager.isDeviceConnected({ key })
|
|
|
|
if (!observesDevices.has(deviceId)) {
|
|
observesDevices.add(deviceId)
|
|
|
|
if (isConnected) {
|
|
connectedDevices.add(deviceId)
|
|
}
|
|
|
|
if (!hasShutDown) {
|
|
const listener = (nowConnected: boolean) => {
|
|
const oldConnected = connectedDevices.has(deviceId)
|
|
|
|
if (oldConnected !== nowConnected) {
|
|
if (nowConnected) {
|
|
connectedDevices.add(deviceId)
|
|
|
|
if (devicesWithSharingEnabled.has(deviceId)) {
|
|
sentConnectedDevices.add(deviceId)
|
|
sendStatus()
|
|
}
|
|
} else {
|
|
connectedDevices.delete(deviceId)
|
|
|
|
if (devicesWithSharingEnabled.has(deviceId)) {
|
|
sentConnectedDevices.delete(deviceId)
|
|
sendStatus()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.connectedDevicesManager.deviceConnectionChangeEmitter.addListener(key, listener)
|
|
|
|
shutdownHooks.push(() => {
|
|
this.connectedDevicesManager.deviceConnectionChangeEmitter.removeListener(key, listener)
|
|
})
|
|
}
|
|
}
|
|
|
|
if (showDeviceConnected) {
|
|
if (!devicesWithSharingEnabled.has(deviceId)) {
|
|
devicesWithSharingEnabled.add(deviceId)
|
|
}
|
|
|
|
if (isConnected) {
|
|
if (!sentConnectedDevices.has(deviceId)) {
|
|
sentConnectedDevices.add(deviceId)
|
|
sendStatus()
|
|
}
|
|
}
|
|
} else {
|
|
if (devicesWithSharingEnabled.has(deviceId)) {
|
|
devicesWithSharingEnabled.delete(deviceId)
|
|
|
|
if (sentConnectedDevices.has(deviceId)) {
|
|
sentConnectedDevices.delete(deviceId)
|
|
sendStatus()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add all current devices
|
|
;(async () => {
|
|
const devicesUnsafe = await this.database.device.findAll({
|
|
where: {
|
|
familyId
|
|
},
|
|
attributes: [
|
|
'deviceId',
|
|
'showDeviceConnected'
|
|
]
|
|
})
|
|
|
|
const devices = devicesUnsafe.map(({ deviceId, showDeviceConnected }) => ({
|
|
deviceId, showDeviceConnected
|
|
}))
|
|
|
|
devices.forEach(({ deviceId, showDeviceConnected }) => {
|
|
addDevice({ deviceId, showDeviceConnected })
|
|
})
|
|
})().catch((ex) => { /* ignore */ })
|
|
|
|
{
|
|
// add all new devices + apply changes of sharing
|
|
const listener = ({ deviceId, showDeviceConnected }: {
|
|
deviceId: string
|
|
showDeviceConnected: boolean
|
|
}) => {
|
|
addDevice({
|
|
deviceId,
|
|
showDeviceConnected
|
|
})
|
|
}
|
|
|
|
this.familyDeviceShareConnectedChangeEmitter.addListener(familyId, listener)
|
|
shutdownHooks.push(() => this.familyDeviceShareConnectedChangeEmitter.removeListener(familyId, listener))
|
|
}
|
|
|
|
return { shutdown }
|
|
}
|
|
}
|