Initial commit

This commit is contained in:
Jonas L
2019-02-25 00:00:00 +00:00
commit 22c372e246
200 changed files with 27781 additions and 0 deletions
+214
View File
@@ -0,0 +1,214 @@
/*
* 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 }
}
}