Chore+Refactor: Replace `nedb` package with `nedb-promises`

The 'nedb' package is unmaintained (last update was 5 years ago) and
has a couple of high severity vulnerabilities.

In addition, the use of callbacks is somewhat cumbersome for
the project's current workflow.

Therefore, I've decided to replace it with the 'nedb-promises' package,
which, internally, makes use of a maintained fork of 'nedb' and
wraps its API with Promises.
This commit is contained in:
Svallinn 2021-06-17 04:16:52 +01:00
parent 73c198a30c
commit be11e3d8cb
No known key found for this signature in database
GPG Key ID: 09FB527F34037CCA
8 changed files with 329 additions and 399 deletions

55
package-lock.json generated
View File

@ -3577,6 +3577,21 @@
"fastq": "^1.6.0"
}
},
"@seald-io/binary-search-tree": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz",
"integrity": "sha512-+pYGvPFAk7wUR+ONMOlc6A+LUN4kOCFwyPLjyaeS7wVibADPHWYJNYsNtyIAwjF1AXQkuaXElnIc4XjKt55QZA=="
},
"@seald-io/nedb": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@seald-io/nedb/-/nedb-2.0.3.tgz",
"integrity": "sha512-NLnaDav050gnAMM8mnKQE9oH3F9RzGdKornukxLJ7BCf2YXEWndTf3Bmd+1mOqFs5DZnTY0SRigIwVXd8L39Xg==",
"requires": {
"@seald-io/binary-search-tree": "^1.0.2",
"async": "0.2.10",
"localforage": "^1.9.0"
}
},
"@silvermine/videojs-quality-selector": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@silvermine/videojs-quality-selector/-/videojs-quality-selector-1.2.5.tgz",
@ -5449,21 +5464,6 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"binary-search-tree": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz",
"integrity": "sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q=",
"requires": {
"underscore": "~1.4.4"
},
"dependencies": {
"underscore": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
}
}
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@ -12841,7 +12841,8 @@
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"minipass": {
"version": "3.1.3",
@ -12887,6 +12888,7 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
@ -12988,23 +12990,12 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
"nedb": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/nedb/-/nedb-1.8.0.tgz",
"integrity": "sha1-DjUCzYLABNU1WkPJ5VV3vXvZHYg=",
"nedb-promises": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nedb-promises/-/nedb-promises-5.0.0.tgz",
"integrity": "sha512-PZ+V+1RScHr3mZHflAMMkx35naGHKVVGtqLwdK2aado7uN2HcIcubiaVlE/X8BTa34RnIHmusVM+VeTj/pnfew==",
"requires": {
"async": "0.2.10",
"binary-search-tree": "0.2.5",
"localforage": "^1.3.0",
"mkdirp": "~0.5.1",
"underscore": "~1.4.4"
},
"dependencies": {
"underscore": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
}
"@seald-io/nedb": "^2.0.3"
}
},
"needle": {

View File

@ -26,7 +26,7 @@
"lodash.uniqwith": "^4.5.0",
"marked": "^2.1.1",
"material-design-icons": "^3.0.1",
"nedb": "^1.8.0",
"nedb-promises": "^5.0.0",
"node-forge": "^0.10.0",
"opml-to-json": "^1.0.1",
"rss-parser": "^3.12.0",

View File

@ -2,7 +2,7 @@ import {
app, BrowserWindow, dialog, Menu, ipcMain,
powerSaveBlocker, screen, session, shell
} from 'electron'
import Datastore from 'nedb'
import Datastore from 'nedb-promises'
import path from 'path'
import cp from 'child_process'
@ -23,7 +23,7 @@ function runApp() {
const localDataStorage = app.getPath('userData') // Grabs the userdata directory based on the user's OS
const settingsDb = new Datastore({
const settingsDb = Datastore.create({
filename: localDataStorage + '/settings.db',
autoload: true
})
@ -83,8 +83,10 @@ function runApp() {
})
}
app.on('ready', (_, __) => {
settingsDb.find({
app.on('ready', async (_, __) => {
let docArray
try {
docArray = await settingsDb.find({
$or: [
{ _id: 'disableSmoothScrolling' },
{ _id: 'useProxy' },
@ -92,8 +94,9 @@ function runApp() {
{ _id: 'proxyHostname' },
{ _id: 'proxyPort' }
]
}, function (err, doc) {
if (err) {
})
} catch (err) {
console.error(err)
app.exit()
return
}
@ -104,23 +107,23 @@ function runApp() {
let proxyHostname = '127.0.0.1'
let proxyPort = '9050'
if (typeof doc === 'object' && doc.length > 0) {
doc.forEach((dbItem) => {
switch (dbItem._id) {
if (docArray?.length > 0) {
docArray.forEach((doc) => {
switch (doc._id) {
case 'disableSmoothScrolling':
disableSmoothScrolling = dbItem.value
disableSmoothScrolling = doc.value
break
case 'useProxy':
useProxy = dbItem.value
useProxy = doc.value
break
case 'proxyProtocol':
proxyProtocol = dbItem.value
proxyProtocol = doc.value
break
case 'proxyHostname':
proxyHostname = dbItem.value
proxyHostname = doc.value
break
case 'proxyPort':
proxyPort = dbItem.value
proxyPort = doc.value
break
}
})
@ -153,7 +156,7 @@ function runApp() {
})
})
createWindow()
await createWindow()
if (isDev) {
installDevTools()
@ -163,7 +166,6 @@ function runApp() {
mainWindow.webContents.openDevTools()
}
})
})
async function installDevTools() {
try {
@ -175,7 +177,7 @@ function runApp() {
}
}
function createWindow(replaceMainWindow = true) {
async function createWindow(replaceMainWindow = true) {
/**
* Initial window options
*/
@ -206,18 +208,9 @@ function runApp() {
height: 800
})
settingsDb.findOne({
_id: 'bounds'
}, function (err, doc) {
if (doc === null || err) {
return
}
if (typeof doc !== 'object' || typeof doc.value !== 'object') {
return
}
const { maximized, ...bounds } = doc.value
const boundsDoc = await settingsDb.findOne({ _id: 'bounds' })
if (typeof boundsDoc?.value === 'object') {
const { maximized, ...bounds } = boundsDoc.value
const allDisplaysSummaryWidth = screen
.getAllDisplays()
.reduce((accumulator, { size: { width } }) => accumulator + width, 0)
@ -233,7 +226,7 @@ function runApp() {
if (maximized) {
newWindow.maximize()
}
})
}
// If called multiple times
// Duplicate menu items will be added
@ -274,18 +267,19 @@ function runApp() {
}
// Save closing window bounds & maximized status
ipcMain.on('setBounds', (event) => {
ipcMain.on('setBounds', async (event) => {
const value = {
...mainWindow.getNormalBounds(),
maximized: mainWindow.isMaximized()
}
settingsDb.update(
await settingsDb.update(
{ _id: 'bounds' },
{ _id: 'bounds', value },
{ upsert: true },
() => { event.returnValue = 0 }
{ upsert: true }
)
event.returnValue = 0
})
ipcMain.on('appReady', () => {

View File

@ -1,4 +1,4 @@
import Datastore from 'nedb'
import Datastore from 'nedb-promises'
// Initialize all datastores and export their references
// Current dbs:
@ -7,36 +7,34 @@ import Datastore from 'nedb'
// `playlists.db`
// `history.db`
let buildFileName = null
// Check if using Electron
let userDataPath
const usingElectron = window?.process?.type === 'renderer'
if (usingElectron) {
const { ipcRenderer } = require('electron')
userDataPath = ipcRenderer.sendSync('getUserDataPathSync')
const userDataPath = ipcRenderer.sendSync('getUserDataPathSync')
buildFileName = (dbName) => userDataPath + '/' + dbName + '.db'
} else {
buildFileName = (dbName) => dbName + '.db'
}
const buildFileName = (dbName) => {
return usingElectron
? userDataPath + '/' + dbName + '.db'
: dbName + '.db'
}
const settingsDb = new Datastore({
const settingsDb = Datastore.create({
filename: buildFileName('settings'),
autoload: true
})
const playlistsDb = new Datastore({
const playlistsDb = Datastore.create({
filename: buildFileName('playlists'),
autoload: true
})
const profilesDb = new Datastore({
const profilesDb = Datastore.create({
filename: buildFileName('profiles'),
autoload: true
})
const historyDb = new Datastore({
const historyDb = Datastore.create({
filename: buildFileName('history'),
autoload: true
})

View File

@ -11,57 +11,46 @@ const getters = {
}
const actions = {
grabHistory ({ commit }) {
historyDb.find({}).sort({
timeWatched: -1
}).exec((err, results) => {
if (err) {
console.log(err)
return
}
async grabHistory({ commit }) {
const results = await historyDb.find({}).sort({ timeWatched: -1 })
commit('setHistoryCache', results)
})
},
updateHistory ({ dispatch }, videoData) {
historyDb.update({ videoId: videoData.videoId }, videoData, { upsert: true }, (err, numReplaced) => {
if (!err) {
async updateHistory({ dispatch }, videoData) {
await historyDb.update(
{ videoId: videoData.videoId },
videoData,
{ upsert: true }
)
dispatch('grabHistory')
}
})
},
removeFromHistory ({ dispatch }, videoId) {
historyDb.remove({ videoId: videoId }, (err, numReplaced) => {
if (!err) {
async removeFromHistory({ dispatch }, videoId) {
await historyDb.remove({ videoId: videoId })
dispatch('grabHistory')
}
})
},
removeAllHistory ({ dispatch }) {
historyDb.remove({}, { multi: true }, (err, numReplaced) => {
if (!err) {
async removeAllHistory({ dispatch }) {
await historyDb.remove({}, { multi: true })
dispatch('grabHistory')
}
})
},
updateWatchProgress ({ dispatch }, videoData) {
historyDb.update({ videoId: videoData.videoId }, { $set: { watchProgress: videoData.watchProgress } }, { upsert: true }, (err, numReplaced) => {
if (!err) {
async updateWatchProgress({ dispatch }, videoData) {
await historyDb.update(
{ videoId: videoData.videoId },
{ $set: { watchProgress: videoData.watchProgress } },
{ upsert: true }
)
dispatch('grabHistory')
}
})
},
compactHistory (_) {
compactHistory(_) {
historyDb.persistence.compactDatafile()
}
}
const mutations = {
setHistoryCache (state, historyCache) {
setHistoryCache(state, historyCache) {
state.historyCache = historyCache
}
}

View File

@ -24,156 +24,146 @@ const getters = {
}
const actions = {
addPlaylist ({ commit }, payload) {
playlistsDb.insert(payload, (err, payload) => {
if (err) {
console.error(err)
} else {
async addPlaylist({ commit }, payload) {
await playlistsDb.insert(payload)
commit('addPlaylist', payload)
}
})
},
addPlaylists ({ commit }, payload) {
playlistsDb.insert(payload, (err, payload) => {
if (err) {
console.error(err)
} else {
async addPlaylists({ commit }, payload) {
await playlistsDb.insert(payload)
commit('addPlaylists', payload)
}
})
},
addVideo ({ commit }, payload) {
playlistsDb.update({ playlistName: payload.playlistName }, { $push: { videos: payload.videoData } }, { upsert: true }, err => {
if (err) {
console.error(err)
} else {
async addVideo({ commit }, payload) {
await playlistsDb.update(
{ playlistName: payload.playlistName },
{ $push: { videos: payload.videoData } },
{ upsert: true }
)
commit('addVideo', payload)
}
})
},
addVideos ({ commit }, payload) {
playlistsDb.update({ _id: payload.playlistId }, { $push: { videos: { $each: payload.videosIds } } }, { upsert: true }, err => {
if (err) {
console.error(err)
} else {
async addVideos({ commit }, payload) {
await playlistsDb.update(
{ _id: payload.playlistId },
{ $push: { videos: { $each: payload.videosIds } } },
{ upsert: true }
)
commit('addVideos', payload)
}
})
},
grabAllPlaylists({ commit, dispatch }) {
playlistsDb.find({}, (err, payload) => {
if (err) {
console.error(err)
} else {
async grabAllPlaylists({ commit, dispatch }) {
const payload = await playlistsDb.find({})
if (payload.length === 0) {
commit('setAllPlaylists', state.playlists)
dispatch('addPlaylists', payload)
} else {
commit('setAllPlaylists', payload)
}
}
})
},
removeAllPlaylists ({ commit }) {
playlistsDb.remove({ protected: { $ne: true } }, err => {
if (err) {
console.error(err)
} else {
async removeAllPlaylists({ commit }) {
await playlistsDb.remove({ protected: { $ne: true } })
commit('removeAllPlaylists')
}
})
},
removeAllVideos ({ commit }, playlistName) {
playlistsDb.update({ playlistName: playlistName }, { $set: { videos: [] } }, { upsert: true }, err => {
if (err) {
console.error(err)
} else {
async removeAllVideos({ commit }, playlistName) {
await playlistsDb.update(
{ playlistName: playlistName },
{ $set: { videos: [] } },
{ upsert: true }
)
commit('removeAllVideos', playlistName)
}
})
},
removePlaylist ({ commit }, playlistId) {
playlistsDb.remove({ _id: playlistId, protected: { $ne: true } }, (err, playlistId) => {
if (err) {
console.error(err)
} else {
async removePlaylist({ commit }, playlistId) {
await playlistsDb.remove({
_id: playlistId,
protected: { $ne: true }
})
commit('removePlaylist', playlistId)
}
})
},
removePlaylists ({ commit }, playlistIds) {
playlistsDb.remove({ _id: { $in: playlistIds }, protected: { $ne: true } }, (err, playlistIds) => {
if (err) {
console.error(err)
} else {
async removePlaylists({ commit }, playlistIds) {
await playlistsDb.remove({
_id: { $in: playlistIds },
protected: { $ne: true }
})
commit('removePlaylists', playlistIds)
}
})
},
removeVideo ({ commit }, payload) {
playlistsDb.update({ playlistName: payload.playlistName }, { $pull: { videos: { videoId: payload.videoId } } }, { upsert: true }, (err, numRemoved) => {
if (err) {
console.error(err)
} else {
async removeVideo({ commit }, payload) {
await playlistsDb.update(
{ playlistName: payload.playlistName },
{ $pull: { videos: { videoId: payload.videoId } } },
{ upsert: true }
)
commit('removeVideo', payload)
}
})
},
removeVideos ({ commit }, payload) {
playlistsDb.update({ _id: payload.playlistName }, { $pull: { videos: { $in: payload.videoId } } }, { upsert: true }, err => {
if (err) {
console.error(err)
} else {
async removeVideos({ commit }, payload) {
await playlistsDb.update(
{ _id: payload.playlistName },
{ $pull: { videos: { $in: payload.videoId } } },
{ upsert: true }
)
commit('removeVideos', payload)
}
})
}
}
const mutations = {
addPlaylist (state, payload) {
addPlaylist(state, payload) {
state.playlists.push(payload)
},
addPlaylists (state, payload) {
addPlaylists(state, payload) {
state.playlists = state.playlists.concat(payload)
},
addVideo (state, payload) {
addVideo(state, payload) {
const playlist = state.playlists.find(playlist => playlist.playlistName === payload.playlistName)
if (playlist) {
playlist.videos.push(payload.videoData)
}
},
addVideos (state, payload) {
addVideos(state, payload) {
const playlist = state.playlists.find(playlist => playlist._id === payload.playlistId)
if (playlist) {
playlist.videos = playlist.videos.concat(payload.playlistIds)
}
},
removeAllPlaylists (state) {
removeAllPlaylists(state) {
state.playlists = state.playlists.filter(playlist => playlist.protected !== true)
},
removeAllVideos (state, playlistName) {
removeAllVideos(state, playlistName) {
const playlist = state.playlists.find(playlist => playlist.playlistName === playlistName)
if (playlist) {
playlist.videos = []
}
},
removeVideo (state, payload) {
removeVideo(state, payload) {
const playlist = state.playlists.findIndex(playlist => playlist.playlistName === payload.playlistName)
if (playlist !== -1) {
state.playlists[playlist].videos = state.playlists[playlist].videos.filter(video => video.videoId !== payload.videoId)
}
},
removeVideos (state, payload) {
removeVideos(state, payload) {
const playlist = state.playlists.findIndex(playlist => playlist._id === payload.playlistId)
if (playlist !== -1) {
playlist.videos = playlist.videos.filter(video => payload.videoId.indexOf(video) === -1)
}
},
removePlaylist (state, playlistId) {
removePlaylist(state, playlistId) {
state.playlists = state.playlists.filter(playlist => playlist._id !== playlistId || playlist.protected)
},
setAllPlaylists (state, payload) {
setAllPlaylists(state, payload) {
state.playlists = payload
}
}

View File

@ -22,16 +22,15 @@ const getters = {
}
const actions = {
grabAllProfiles ({ rootState, dispatch, commit }, defaultName = null) {
return new Promise((resolve, reject) => {
profilesDb.find({}, (err, results) => {
if (!err) {
if (results.length === 0) {
async grabAllProfiles({ rootState, dispatch, commit }, defaultName = null) {
let profiles = await profilesDb.find({})
if (profiles.length === 0) {
dispatch('createDefaultProfile', defaultName)
} else {
return
}
// We want the primary profile to always be first
// So sort with that then sort alphabetically by profile name
const profiles = results.sort((a, b) => {
profiles = profiles.sort((a, b) => {
if (a._id === 'allChannels') {
return -1
}
@ -54,28 +53,14 @@ const actions = {
}
commit('setProfileList', profiles)
}
resolve()
} else {
reject(err)
}
})
})
},
grabProfileInfo (_, profileId) {
return new Promise((resolve, reject) => {
async grabProfileInfo(_, profileId) {
console.log(profileId)
profilesDb.findOne({ _id: profileId }, (err, results) => {
if (!err) {
resolve(results)
}
})
})
return await profilesDb.findOne({ _id: profileId })
},
async createDefaultProfile ({ dispatch }, defaultName) {
async createDefaultProfile({ dispatch }, defaultName) {
const randomColor = await dispatch('getRandomColor')
const textColor = await dispatch('calculateColorLuminance', randomColor)
const defaultProfile = {
@ -86,51 +71,47 @@ const actions = {
subscriptions: []
}
profilesDb.update({ _id: 'allChannels' }, defaultProfile, { upsert: true }, (err, numReplaced) => {
if (!err) {
await profilesDb.update(
{ _id: 'allChannels' },
defaultProfile,
{ upsert: true }
)
dispatch('grabAllProfiles')
}
})
},
updateProfile ({ dispatch }, profile) {
profilesDb.update({ _id: profile._id }, profile, { upsert: true }, (err, numReplaced) => {
if (!err) {
async updateProfile({ dispatch }, profile) {
await profilesDb.update(
{ _id: profile._id },
profile,
{ upsert: true }
)
dispatch('grabAllProfiles')
}
})
},
insertProfile ({ dispatch }, profile) {
profilesDb.insert(profile, (err, newDocs) => {
if (!err) {
async insertProfile({ dispatch }, profile) {
await profilesDb.insert(profile)
dispatch('grabAllProfiles')
}
})
},
removeProfile ({ dispatch }, profileId) {
profilesDb.remove({ _id: profileId }, (err, numReplaced) => {
if (!err) {
async removeProfile({ dispatch }, profileId) {
await profilesDb.remove({ _id: profileId })
dispatch('grabAllProfiles')
}
})
},
compactProfiles (_) {
compactProfiles(_) {
profilesDb.persistence.compactDatafile()
},
updateActiveProfile ({ commit }, index) {
updateActiveProfile({ commit }, index) {
commit('setActiveProfile', index)
}
}
const mutations = {
setProfileList (state, profileList) {
setProfileList(state, profileList) {
state.profileList = profileList
},
setActiveProfile (state, activeProfile) {
setActiveProfile(state, activeProfile) {
state.activeProfile = activeProfile
}
}

View File

@ -264,15 +264,10 @@ Object.assign(customGetters, {
/**********/
const customActions = {
grabUserSettings: ({ commit, dispatch, getters }) => {
return new Promise((resolve, reject) => {
settingsDb.find(
{ _id: { $ne: 'bounds' } },
(err, userSettings) => {
if (err) {
reject(err)
return
}
grabUserSettings: async ({ commit, dispatch, getters }) => {
const userSettings = await settingsDb.find({
_id: { $ne: 'bounds' }
})
for (const setting of userSettings) {
const { _id, value } = setting
@ -282,11 +277,6 @@ const customActions = {
commit(defaultMutationId(_id), value)
}
resolve()
}
)
})
},
setUpListenerToSyncSettings: ({ commit, dispatch, getters }) => {
@ -347,13 +337,12 @@ for (const settingId of Object.keys(state)) {
actions[triggerId] = stateWithSideEffects[settingId].sideEffectsHandler
}
actions[updaterId] = ({ commit, dispatch, getters }, value) => {
settingsDb.update(
actions[updaterId] = async ({ commit, dispatch, getters }, value) => {
await settingsDb.update(
{ _id: settingId },
{ _id: settingId, value: value },
{ upsert: true },
(err, _) => {
if (err) return
{ upsert: true }
)
const {
getUsingElectron: usingElectron,
@ -374,8 +363,6 @@ for (const settingId of Object.keys(state)) {
})
}
}
)
}
}
// Add all custom data/logic to their respective objects