Refactor: Erase `@electron/remote` references and other dangerous calls

The `remote` module is deprecated and `@electron/remote` is unnecessary,
since the `ipcMain` and `ipcRenderer` can replace their functionality,
providing better performance and better security.

All other dangerous calls (mainly pulling main process constructs into
the renderer process) have also been removed.
This commit is contained in:
Svallinn 2021-05-22 00:49:48 +01:00
parent c9fd6385f4
commit af0353ea32
No known key found for this signature in database
GPG Key ID: 09FB527F34037CCA
20 changed files with 505 additions and 459 deletions

View File

@ -1,4 +1,7 @@
import { app, BrowserWindow, Menu, ipcMain, screen } from 'electron'
import {
app, BrowserWindow, dialog, Menu,
ipcMain, powerSaveBlocker, screen, shell
} from 'electron'
import Datastore from 'nedb'
if (process.argv.includes('--version')) {
@ -9,8 +12,6 @@ if (process.argv.includes('--version')) {
}
function runApp() {
require('@electron/remote/main').initialize()
require('electron-context-menu')({
showSearchWithGoogle: false,
showSaveImageAs: true,
@ -416,6 +417,38 @@ function runApp() {
mainWindow.webContents.session.setProxy({})
})
ipcMain.on('openExternalLink', (_, url) => {
if (typeof url === 'string') shell.openExternal(url)
})
ipcMain.handle('getLocale', () => {
return app.getLocale()
})
ipcMain.handle('getUserDataPath', () => {
return app.getPath('userData')
})
ipcMain.on('getUserDataPathSync', (event) => {
event.returnValue = app.getPath('userData')
})
ipcMain.handle('showOpenDialog', async (_, options) => {
return await dialog.showOpenDialog(options)
})
ipcMain.handle('showSaveDialog', async (_, options) => {
return await dialog.showSaveDialog(options)
})
ipcMain.on('stopPowerSaveBlocker', (_, id) => {
powerSaveBlocker.stop(id)
})
ipcMain.handle('startPowerSaveBlocker', (_, type) => {
return powerSaveBlocker.start(type)
})
ipcMain.on('createNewWindow', () => {
createWindow(false, '', false)
})

View File

@ -10,23 +10,17 @@ import FtButton from './components/ft-button/ft-button.vue'
import FtToast from './components/ft-toast/ft-toast.vue'
import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
import $ from 'jquery'
import { app } from '@electron/remote'
import { markdown } from 'markdown'
import Parser from 'rss-parser'
let useElectron
let shell
let electron
let useElectron = false
let ipcRenderer = null
Vue.directive('observe-visibility', ObserveVisibility)
if (window && window.process && window.process.type === 'renderer') {
/* eslint-disable-next-line */
electron = require('electron')
shell = electron.shell
useElectron = true
} else {
useElectron = false
ipcRenderer = require('electron').ipcRenderer
}
export default Vue.extend({
@ -90,12 +84,12 @@ export default Vue.extend({
mounted: function () {
const v = this
this.$store.dispatch('grabUserSettings').then(() => {
this.$store.dispatch('grabAllProfiles', this.$t('Profile.All Channels')).then(() => {
this.$store.dispatch('grabAllProfiles', this.$t('Profile.All Channels')).then(async () => {
this.$store.dispatch('grabHistory')
this.$store.dispatch('grabAllPlaylists')
this.$store.commit('setUsingElectron', useElectron)
this.checkThemeSettings()
this.checkLocale()
await this.checkLocale()
v.dataReady = true
@ -115,14 +109,15 @@ export default Vue.extend({
})
},
methods: {
checkLocale: function () {
checkLocale: async function () {
const locale = localStorage.getItem('locale')
if (locale === null || locale === 'system') {
const systemLocale = app.getLocale().replace(/-|_/, '_')
const systemLocale = await this.getLocale()
const findLocale = Object.keys(this.$i18n.messages).find((locale) => {
const localeName = locale.replace(/-|_/, '_')
return localeName.includes(systemLocale)
const localeName = locale.replace('-', '_')
return localeName.includes(systemLocale.replace('-', '_'))
})
if (typeof findLocale !== 'undefined') {
@ -248,7 +243,7 @@ export default Vue.extend({
handleNewBlogBannerClick: function (response) {
if (response) {
shell.openExternal(this.latestBlogUrl)
this.openExternalLink(this.latestBlogUrl)
}
this.showBlogBanner = false
@ -256,7 +251,7 @@ export default Vue.extend({
openDownloadsPage: function () {
const url = 'https://freetubeapp.io#download'
shell.openExternal(url)
this.openExternalLink(url)
this.showReleaseNotes = false
this.showUpdatesBanner = false
},
@ -301,9 +296,7 @@ export default Vue.extend({
this.handleYoutubeLink(el.href)
} else {
// Open links externally by default
if (typeof (shell) !== 'undefined') {
shell.openExternal(el.href)
}
this.openExternalLink(el.href)
}
})
},
@ -385,23 +378,24 @@ export default Vue.extend({
enableOpenUrl: function () {
const v = this
electron.ipcRenderer.on('openUrl', function (event, url) {
ipcRenderer.on('openUrl', function (event, url) {
if (url) {
v.handleYoutubeLink(url)
}
})
electron.ipcRenderer.send('appReady')
ipcRenderer.send('appReady')
},
setBoundsOnClose: function () {
window.onbeforeunload = (e) => {
electron.ipcRenderer.send('setBounds')
ipcRenderer.send('setBounds')
}
},
...mapActions([
'showToast'
'showToast',
'openExternalLink'
])
}
})

View File

@ -10,9 +10,7 @@ import fs from 'fs'
import { opmlToJSON } from 'opml-to-json'
import ytch from 'yt-channel-info'
const remote = require('@electron/remote')
const app = remote.app
const dialog = remote.dialog
// FIXME: Missing web logic branching
export default Vue.extend({
name: 'DataSettings',
@ -210,7 +208,7 @@ export default Vue.extend({
})
},
importFreeTubeSubscriptions: function () {
importFreeTubeSubscriptions: async function () {
const options = {
properties: ['openFile'],
filters: [
@ -221,14 +219,13 @@ export default Vue.extend({
]
}
dialog.showOpenDialog(options).then((response) => {
if (response.canceled || response.filePaths.length === 0) {
return
}
const response = await this.showOpenDialog(options)
if (response.canceled || response.filePaths.length === 0) {
return
}
const filePath = response.filePaths[0]
this.handleFreetubeImportFile(filePath)
})
const filePath = response.filePaths[0]
this.handleFreetubeImportFile(filePath)
},
handleYoutubeImportFile: function (filePath) {
@ -313,7 +310,7 @@ export default Vue.extend({
})
},
importYouTubeSubscriptions: function () {
importYouTubeSubscriptions: async function () {
const options = {
properties: ['openFile'],
filters: [
@ -324,17 +321,16 @@ export default Vue.extend({
]
}
dialog.showOpenDialog(options).then(async (response) => {
if (response.canceled || response.filePaths.length === 0) {
return
}
const response = await this.showOpenDialog(options)
if (response.canceled || response.filePaths.length === 0) {
return
}
const filePath = response.filePaths[0]
this.handleYoutubeImportFile(filePath)
})
const filePath = response.filePaths[0]
this.handleYoutubeImportFile(filePath)
},
importOpmlYouTubeSubscriptions: function () {
importOpmlYouTubeSubscriptions: async function () {
const options = {
properties: ['openFile'],
filters: [
@ -345,148 +341,38 @@ export default Vue.extend({
]
}
dialog.showOpenDialog(options).then(async (response) => {
if (response.canceled || response.filePaths.length === 0) {
return
}
const filePath = response.filePaths[0]
fs.readFile(filePath, async (err, data) => {
if (err) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${err}`
})
return
}
opmlToJSON(data).then((json) => {
let feedData = json.children[0].children
if (typeof feedData === 'undefined') {
if (json.title.includes('gPodder')) {
feedData = json.children
} else {
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
this.showToast({
message: message
})
return
}
}
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = []
this.showToast({
message: this.$t('Settings.Data Settings.This might take a while, please wait')
})
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
let count = 0
feedData.forEach(async (channel, index) => {
const channelId = channel.xmlurl.replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '')
let channelInfo
if (this.backendPreference === 'invidious') {
channelInfo = await this.getChannelInfoInvidious(channelId)
} else {
channelInfo = await this.getChannelInfoLocal(channelId)
}
if (typeof channelInfo.author !== 'undefined') {
const subscription = {
id: channelId,
name: channelInfo.author,
thumbnail: channelInfo.authorThumbnails[1].url
}
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
return sub.id === subscription.id || sub.name === subscription.name
})
if (subExists === -1) {
subscriptions.push(subscription)
}
}
count++
const progressPercentage = (count / feedData.length) * 100
this.setProgressBarPercentage(progressPercentage)
if (count === feedData.length) {
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
this.updateProfile(primaryProfile)
if (subscriptions.length < count) {
this.showToast({
message: this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')
})
} else {
this.showToast({
message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported')
})
}
this.updateShowProgressBar(false)
}
})
}).catch((err) => {
console.log(err)
console.log('error reading')
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
this.showToast({
message: `${message}: ${err}`
})
})
})
})
},
importNewPipeSubscriptions: function () {
const options = {
properties: ['openFile'],
filters: [
{
name: 'Database File',
extensions: ['json']
}
]
const response = await this.showOpenDialog(options)
if (response.canceled || response.filePaths.length === 0) {
return
}
dialog.showOpenDialog(options).then(async (response) => {
if (response.canceled || response.filePaths.length === 0) {
const filePath = response.filePaths[0]
fs.readFile(filePath, async (err, data) => {
if (err) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${err}`
})
return
}
const filePath = response.filePaths[0]
opmlToJSON(data).then((json) => {
let feedData = json.children[0].children
fs.readFile(filePath, async (err, data) => {
if (err) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${err}`
})
return
if (typeof feedData === 'undefined') {
if (json.title.includes('gPodder')) {
feedData = json.children
} else {
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
this.showToast({
message: message
})
return
}
}
const newPipeData = JSON.parse(data)
if (typeof newPipeData.subscriptions === 'undefined') {
this.showToast({
message: this.$t('Settings.Data Settings.Invalid subscriptions file')
})
return
}
const newPipeSubscriptions = newPipeData.subscriptions
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = []
@ -499,8 +385,8 @@ export default Vue.extend({
let count = 0
newPipeSubscriptions.forEach(async (channel, index) => {
const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '')
feedData.forEach(async (channel, index) => {
const channelId = channel.xmlurl.replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '')
let channelInfo
if (this.backendPreference === 'invidious') {
channelInfo = await this.getChannelInfoInvidious(channelId)
@ -526,10 +412,10 @@ export default Vue.extend({
count++
const progressPercentage = (count / newPipeSubscriptions.length) * 100
const progressPercentage = (count / feedData.length) * 100
this.setProgressBarPercentage(progressPercentage)
if (count === newPipeSubscriptions.length) {
if (count === feedData.length) {
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
this.updateProfile(primaryProfile)
@ -546,6 +432,114 @@ export default Vue.extend({
this.updateShowProgressBar(false)
}
})
}).catch((err) => {
console.log(err)
console.log('error reading')
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
this.showToast({
message: `${message}: ${err}`
})
})
})
},
importNewPipeSubscriptions: async function () {
const options = {
properties: ['openFile'],
filters: [
{
name: 'Database File',
extensions: ['json']
}
]
}
const response = await this.showOpenDialog(options)
if (response.canceled || response.filePaths.length === 0) {
return
}
const filePath = response.filePaths[0]
fs.readFile(filePath, async (err, data) => {
if (err) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${err}`
})
return
}
const newPipeData = JSON.parse(data)
if (typeof newPipeData.subscriptions === 'undefined') {
this.showToast({
message: this.$t('Settings.Data Settings.Invalid subscriptions file')
})
return
}
const newPipeSubscriptions = newPipeData.subscriptions
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
const subscriptions = []
this.showToast({
message: this.$t('Settings.Data Settings.This might take a while, please wait')
})
this.updateShowProgressBar(true)
this.setProgressBarPercentage(0)
let count = 0
newPipeSubscriptions.forEach(async (channel, index) => {
const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '')
let channelInfo
if (this.backendPreference === 'invidious') {
channelInfo = await this.getChannelInfoInvidious(channelId)
} else {
channelInfo = await this.getChannelInfoLocal(channelId)
}
if (typeof channelInfo.author !== 'undefined') {
const subscription = {
id: channelId,
name: channelInfo.author,
thumbnail: channelInfo.authorThumbnails[1].url
}
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
return sub.id === subscription.id || sub.name === subscription.name
})
if (subExists === -1) {
subscriptions.push(subscription)
}
}
count++
const progressPercentage = (count / newPipeSubscriptions.length) * 100
this.setProgressBarPercentage(progressPercentage)
if (count === newPipeSubscriptions.length) {
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
this.updateProfile(primaryProfile)
if (subscriptions.length < count) {
this.showToast({
message: this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')
})
} else {
this.showToast({
message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported')
})
}
this.updateShowProgressBar(false)
}
})
})
},
@ -575,7 +569,7 @@ export default Vue.extend({
exportFreeTubeSubscriptions: async function () {
await this.compactProfiles()
const userData = app.getPath('userData')
const userData = await this.getUserDataPath()
const subscriptionsDb = `${userData}/profiles.db`
const date = new Date()
let dateMonth = date.getMonth() + 1
@ -603,41 +597,40 @@ export default Vue.extend({
]
}
dialog.showSaveDialog(options).then((response) => {
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
const response = await this.showSaveDialog(options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
const filePath = response.filePath
fs.readFile(subscriptionsDb, (readErr, data) => {
if (readErr) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${readErr}`
})
return
}
const filePath = response.filePath
fs.readFile(subscriptionsDb, (readErr, data) => {
if (readErr) {
const message = this.$t('Settings.Data Settings.Unable to read file')
fs.writeFile(filePath, data, (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${readErr}`
message: `${message}: ${writeErr}`
})
return
}
fs.writeFile(filePath, data, (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
})
})
},
exportYouTubeSubscriptions: function () {
exportYouTubeSubscriptions: async function () {
const date = new Date()
let dateMonth = date.getMonth() + 1
@ -700,26 +693,25 @@ export default Vue.extend({
return object
})
dialog.showSaveDialog(options).then((response) => {
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
const response = await this.showSaveDialog(options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
const filePath = response.filePath
fs.writeFile(filePath, JSON.stringify(subscriptionsObject), (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
const filePath = response.filePath
fs.writeFile(filePath, JSON.stringify(subscriptionsObject), (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
})
},
@ -766,31 +758,30 @@ export default Vue.extend({
}
})
dialog.showSaveDialog(options).then((response) => {
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
const response = await this.showSaveDialog(options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
const filePath = response.filePath
fs.writeFile(filePath, opmlData, (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
const filePath = response.filePath
fs.writeFile(filePath, opmlData, (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
})
},
exportNewPipeSubscriptions: function () {
exportNewPipeSubscriptions: async function () {
const date = new Date()
let dateMonth = date.getMonth() + 1
@ -834,32 +825,31 @@ export default Vue.extend({
newPipeObject.subscriptions.push(subscription)
})
dialog.showSaveDialog(options).then((response) => {
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
const response = await this.showSaveDialog(options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
const filePath = response.filePath
fs.writeFile(filePath, JSON.stringify(newPipeObject), (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
const filePath = response.filePath
fs.writeFile(filePath, JSON.stringify(newPipeObject), (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
this.showToast({
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
})
})
},
checkForLegacySubscriptions: function () {
let dbLocation = app.getPath('userData')
checkForLegacySubscriptions: async function () {
let dbLocation = await this.getUserDataPath()
dbLocation = dbLocation + '/subscriptions.db'
this.handleFreetubeImportFile(dbLocation)
fs.unlink(dbLocation, (err) => {
@ -869,7 +859,7 @@ export default Vue.extend({
})
},
importHistory: function () {
importHistory: async function () {
const options = {
properties: ['openFile'],
filters: [
@ -880,79 +870,78 @@ export default Vue.extend({
]
}
dialog.showOpenDialog(options).then((response) => {
if (response.canceled || response.filePaths.length === 0) {
const response = await this.showOpenDialog(options)
if (response.canceled || response.filePaths.length === 0) {
return
}
const filePath = response.filePaths[0]
fs.readFile(filePath, async (err, data) => {
if (err) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${err}`
})
return
}
const filePath = response.filePaths[0]
let textDecode = new TextDecoder('utf-8').decode(data)
textDecode = textDecode.split('\n')
textDecode.pop()
fs.readFile(filePath, async (err, data) => {
if (err) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${err}`
})
return
}
textDecode.forEach((history) => {
const historyData = JSON.parse(history)
// We would technically already be done by the time the data is parsed,
// however we want to limit the possibility of malicious data being sent
// to the app, so we'll only grab the data we need here.
const requiredKeys = [
'_id',
'author',
'authorId',
'description',
'isLive',
'lengthSeconds',
'paid',
'published',
'timeWatched',
'title',
'type',
'videoId',
'viewCount',
'watchProgress'
]
let textDecode = new TextDecoder('utf-8').decode(data)
textDecode = textDecode.split('\n')
textDecode.pop()
const historyObject = {}
textDecode.forEach((history) => {
const historyData = JSON.parse(history)
// We would technically already be done by the time the data is parsed,
// however we want to limit the possibility of malicious data being sent
// to the app, so we'll only grab the data we need here.
const requiredKeys = [
'_id',
'author',
'authorId',
'description',
'isLive',
'lengthSeconds',
'paid',
'published',
'timeWatched',
'title',
'type',
'videoId',
'viewCount',
'watchProgress'
]
const historyObject = {}
Object.keys(historyData).forEach((key) => {
if (!requiredKeys.includes(key)) {
this.showToast({
message: `Unknown data key: ${key}`
})
} else {
historyObject[key] = historyData[key]
}
})
if (Object.keys(historyObject).length < (requiredKeys.length - 2)) {
Object.keys(historyData).forEach((key) => {
if (!requiredKeys.includes(key)) {
this.showToast({
message: this.$t('Settings.Data Settings.History object has insufficient data, skipping item')
message: `Unknown data key: ${key}`
})
} else {
this.updateHistory(historyObject)
historyObject[key] = historyData[key]
}
})
this.showToast({
message: this.$t('Settings.Data Settings.All watched history has been successfully imported')
})
if (Object.keys(historyObject).length < (requiredKeys.length - 2)) {
this.showToast({
message: this.$t('Settings.Data Settings.History object has insufficient data, skipping item')
})
} else {
this.updateHistory(historyObject)
}
})
this.showToast({
message: this.$t('Settings.Data Settings.All watched history has been successfully imported')
})
})
},
exportHistory: async function () {
await this.compactHistory()
const userData = app.getPath('userData')
const userData = await this.getUserDataPath()
const historyDb = `${userData}/history.db`
const date = new Date()
let dateMonth = date.getMonth() + 1
@ -980,35 +969,34 @@ export default Vue.extend({
]
}
dialog.showSaveDialog(options).then((response) => {
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
const response = await this.showSaveDialog(options)
if (response.canceled || response.filePath === '') {
// User canceled the save dialog
return
}
const filePath = response.filePath
fs.readFile(historyDb, (readErr, data) => {
if (readErr) {
const message = this.$t('Settings.Data Settings.Unable to read file')
this.showToast({
message: `${message}: ${readErr}`
})
return
}
const filePath = response.filePath
fs.readFile(historyDb, (readErr, data) => {
if (readErr) {
const message = this.$t('Settings.Data Settings.Unable to read file')
fs.writeFile(filePath, data, (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${readErr}`
message: `${message}: ${writeErr}`
})
return
}
fs.writeFile(filePath, data, (writeErr) => {
if (writeErr) {
const message = this.$t('Settings.Data Settings.Unable to write file')
this.showToast({
message: `${message}: ${writeErr}`
})
return
}
this.showToast({
message: this.$t('Settings.Data Settings.All watched history has been successfully exported')
})
this.showToast({
message: this.$t('Settings.Data Settings.All watched history has been successfully exported')
})
})
})
@ -1114,7 +1102,10 @@ export default Vue.extend({
'compactHistory',
'showToast',
'getRandomColor',
'calculateColorLuminance'
'calculateColorLuminance',
'showOpenDialog',
'showSaveDialog',
'getUserDataPath'
]),
...mapMutations([

View File

@ -60,10 +60,6 @@ export default Vue.extend({
}
},
computed: {
usingElectron: function () {
return this.$store.getters.getUsingElectron
},
historyCache: function () {
return this.$store.getters.getHistoryCache
},
@ -216,10 +212,7 @@ export default Vue.extend({
})
break
case 'openYoutube':
if (this.usingElectron) {
const shell = require('electron').shell
shell.openExternal(this.youtubeUrl)
}
this.openExternalLink(this.youtubeUrl)
break
case 'copyYoutubeEmbed':
navigator.clipboard.writeText(this.youtubeEmbedUrl)
@ -228,10 +221,7 @@ export default Vue.extend({
})
break
case 'openYoutubeEmbed':
if (this.usingElectron) {
const shell = require('electron').shell
shell.openExternal(this.youtubeEmbedUrl)
}
this.openExternalLink(this.youtubeEmbedUrl)
break
case 'copyInvidious':
navigator.clipboard.writeText(this.invidiousUrl)
@ -240,11 +230,7 @@ export default Vue.extend({
})
break
case 'openInvidious':
if (this.usingElectron) {
console.log('using electron')
const shell = require('electron').shell
shell.openExternal(this.invidiousUrl)
}
this.openExternalLink(this.invidiousUrl)
break
case 'copyYoutubeChannel':
navigator.clipboard.writeText(this.youtubeChannelUrl)
@ -253,10 +239,7 @@ export default Vue.extend({
})
break
case 'openYoutubeChannel':
if (this.usingElectron) {
const shell = require('electron').shell
shell.openExternal(this.youtubeChannelUrl)
}
this.openExternalLink(this.youtubeChannelUrl)
break
case 'copyInvidiousChannel':
navigator.clipboard.writeText(this.invidiousChannelUrl)
@ -265,10 +248,7 @@ export default Vue.extend({
})
break
case 'openInvidiousChannel':
if (this.usingElectron) {
const shell = require('electron').shell
shell.openExternal(this.invidiousChannelUrl)
}
this.openExternalLink(this.invidiousChannelUrl)
break
}
},
@ -460,7 +440,8 @@ export default Vue.extend({
'updateHistory',
'removeFromHistory',
'addVideo',
'removeVideo'
'removeVideo',
'openExternalLink'
])
}
})

View File

@ -34,10 +34,6 @@ export default Vue.extend({
return this.$store.getters.getInvidiousInstance
},
usingElectron: function () {
return this.$store.getters.getUsingElectron
},
invidiousURL() {
return `${this.invidiousInstance}/watch?v=${this.id}`
},
@ -63,15 +59,8 @@ export default Vue.extend({
navigator.clipboard.writeText(text)
},
open(url) {
if (this.usingElectron) {
const shell = require('electron').shell
shell.openExternal(url)
}
},
openInvidious() {
this.open(this.getFinalUrl(this.invidiousURL))
this.openExternalLink(this.getFinalUrl(this.invidiousURL))
this.$refs.iconButton.focusOut()
},
@ -84,7 +73,7 @@ export default Vue.extend({
},
openYoutube() {
this.open(this.getFinalUrl(this.youtubeURL))
this.openExternalLink(this.getFinalUrl(this.youtubeURL))
this.$refs.iconButton.focusOut()
},
@ -97,7 +86,7 @@ export default Vue.extend({
},
openYoutubeEmbed() {
this.open(this.getFinalUrl(this.youtubeEmbedURL))
this.openExternalLink(this.getFinalUrl(this.youtubeEmbedURL))
this.$refs.iconButton.focusOut()
},
@ -110,7 +99,7 @@ export default Vue.extend({
},
openInvidiousEmbed() {
this.open(this.getFinalUrl(this.invidiousEmbedURL))
this.openExternalLink(this.getFinalUrl(this.invidiousEmbedURL))
this.$refs.iconButton.focusOut()
},
@ -134,7 +123,8 @@ export default Vue.extend({
},
...mapActions([
'showToast'
'showToast',
'openExternalLink'
])
}
})

View File

@ -30,8 +30,8 @@ export default Vue.extend({
}
if (this.usingElectron && this.powerSaveBlocker !== null) {
const { powerSaveBlocker } = require('electron')
powerSaveBlocker.stop(this.powerSaveBlocker)
const { ipcRenderer } = require('electron')
ipcRenderer.send('stopPowerSaveBlocker', this.powerSaveBlocker)
}
},
props: {
@ -189,8 +189,8 @@ export default Vue.extend({
}
if (this.usingElectron && this.powerSaveBlocker !== null) {
const { powerSaveBlocker } = require('electron')
powerSaveBlocker.stop(this.powerSaveBlocker)
const { ipcRenderer } = require('electron')
ipcRenderer.send('stopPowerSaveBlocker', this.powerSaveBlocker)
}
},
methods: {
@ -274,18 +274,18 @@ export default Vue.extend({
v.$emit('error', error.target.player.error_)
})
this.player.on('play', function () {
this.player.on('play', async function () {
if (this.usingElectron) {
const { powerSaveBlocker } = require('electron')
this.powerSaveBlocker = powerSaveBlocker.start('prevent-display-sleep')
const { ipcRenderer } = require('electron')
this.powerSaveBlocker =
await ipcRenderer.invoke('startPowerSaveBlocker', 'prevent-display-sleep')
}
})
this.player.on('pause', function () {
if (this.usingElectron && this.powerSaveBlocker !== null) {
const { powerSaveBlocker } = require('electron')
powerSaveBlocker.stop(this.powerSaveBlocker)
const { ipcRenderer } = require('electron')
ipcRenderer.send('stopPowerSaveBlocker', this.powerSaveBlocker)
this.powerSaveBlocker = null
}
})

View File

@ -1,7 +1,6 @@
import Vue from 'vue'
import $ from 'jquery'
import { mapActions } from 'vuex'
import { app } from '@electron/remote'
import FtCard from '../ft-card/ft-card.vue'
import FtSelect from '../ft-select/ft-select.vue'
import FtInput from '../ft-input/ft-input.vue'
@ -59,6 +58,11 @@ export default Vue.extend({
isDev: function () {
return process.env.NODE_ENV === 'development'
},
usingElectron: function () {
return this.$store.getters.getUsingElectron
},
invidiousInstance: function () {
return this.$store.getters.getInvidiousInstance
},
@ -192,12 +196,13 @@ export default Vue.extend({
}
},
updateLocale: function (locale) {
updateLocale: async function (locale) {
if (locale === 'system') {
const systemLocale = app.getLocale().replace(/-|_/, '_')
const systemLocale = await this.getLocale()
const findLocale = Object.keys(this.$i18n.messages).find((locale) => {
const localeName = locale.replace(/-|_/, '_')
return localeName.includes(systemLocale)
const localeName = locale.replace('-', '_')
return localeName.includes(systemLocale.replace('-', '_'))
})
if (typeof findLocale !== 'undefined') {
@ -240,7 +245,8 @@ export default Vue.extend({
'updateThumbnailPreference',
'updateInvidiousInstance',
'updateForceLocalBackendForLegacy',
'getRegionData'
'getRegionData',
'getLocale'
])
}
})

View File

@ -1,6 +1,6 @@
import Vue from 'vue'
import { mapActions } from 'vuex'
import FtListDropdown from '../ft-list-dropdown/ft-list-dropdown.vue'
import { shell } from 'electron'
export default Vue.extend({
name: 'FtElementList',
@ -101,15 +101,19 @@ export default Vue.extend({
navigator.clipboard.writeText(youtubeUrl)
break
case 'openYoutube':
shell.openExternal(youtubeUrl)
this.openExternalLink(youtubeUrl)
break
case 'copyInvidious':
navigator.clipboard.writeText(invidiousUrl)
break
case 'openInvidious':
shell.openExternal(invidiousUrl)
this.openExternalLink(invidiousUrl)
break
}
}
},
...mapActions([
'openExternalLink'
])
}
})

View File

@ -9,7 +9,9 @@ import FtInput from '../ft-input/ft-input.vue'
import FtLoader from '../ft-loader/ft-loader.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import electron from 'electron'
// FIXME: Missing web logic branching
import { ipcRenderer } from 'electron'
import debounce from 'lodash.debounce'
export default Vue.extend({
@ -109,11 +111,11 @@ export default Vue.extend({
},
enableProxy: function () {
electron.ipcRenderer.send('enableProxy', this.proxyUrl)
ipcRenderer.send('enableProxy', this.proxyUrl)
},
disableProxy: function () {
electron.ipcRenderer.send('disableProxy')
ipcRenderer.send('disableProxy')
},
testProxy: function () {

View File

@ -146,6 +146,7 @@ export default Vue.extend({
},
handleUiScale: function (value) {
// FIXME: No electron safeguard
const { webFrame } = require('electron')
const zoomFactor = value / 100
webFrame.setZoomFactor(zoomFactor)
@ -167,12 +168,13 @@ export default Vue.extend({
this.updateDisableSmoothScrolling(this.disableSmoothScrollingToggleValue)
const electron = require('electron')
// FIXME: No electron safeguard
const { ipcRenderer } = require('electron')
if (this.disableSmoothScrollingToggleValue) {
electron.ipcRenderer.send('disableSmoothScrolling')
ipcRenderer.send('disableSmoothScrolling')
} else {
electron.ipcRenderer.send('enableSmoothScrolling')
ipcRenderer.send('enableSmoothScrolling')
}
},

View File

@ -6,7 +6,6 @@ import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue'
import $ from 'jquery'
import debounce from 'lodash.debounce'
import ytSuggest from 'youtube-suggest'
const { ipcRenderer } = require('electron')
export default Vue.extend({
name: 'TopNav',
@ -24,6 +23,10 @@ export default Vue.extend({
}
},
computed: {
usingElectron: function () {
return this.$store.getters.getUsingElectron
},
enableSearchSuggestions: function () {
return this.$store.getters.getEnableSearchSuggestions
},
@ -255,7 +258,12 @@ export default Vue.extend({
},
createNewWindow: function () {
ipcRenderer.send('createNewWindow')
if (this.usingElectron) {
const { ipcRenderer } = require('electron')
ipcRenderer.send('createNewWindow')
} else {
// Web placeholder
}
},
...mapActions([

View File

@ -6,7 +6,6 @@ import FtListDropdown from '../ft-list-dropdown/ft-list-dropdown.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import FtShareButton from '../ft-share-button/ft-share-button.vue'
// import { shell } from 'electron'
export default Vue.extend({
name: 'WatchVideoInfo',
@ -111,10 +110,6 @@ export default Vue.extend({
return this.$store.getters.getInvidiousInstance
},
usingElectron: function () {
return this.$store.getters.getUsingElectron
},
profileList: function () {
return this.$store.getters.getProfileList
},
@ -346,11 +341,6 @@ export default Vue.extend({
}
},
handleDownloadLink: function (url) {
const shell = require('electron').shell
shell.openExternal(url)
},
addToPlaylist: function () {
const videoData = {
videoId: this.id,
@ -396,7 +386,8 @@ export default Vue.extend({
'showToast',
'updateProfile',
'addVideo',
'removeVideo'
'removeVideo',
'openExternalLink'
])
}
})

View File

@ -86,7 +86,7 @@
icon="download"
:dropdown-names="downloadLinkNames"
:dropdown-values="downloadLinkValues"
@click="handleDownloadLink"
@click="openExternalLink"
/>
<ft-icon-button
v-if="!isUpcoming"

View File

@ -11,8 +11,8 @@ if (window && window.process && window.process.type === 'renderer') {
dbLocation = electron.remote.app.getPath('userData')
} */
const remote = require('@electron/remote')
dbLocation = remote.app.getPath('userData')
const { ipcRenderer } = require('electron')
dbLocation = ipcRenderer.sendSync('getUserDataPathSync')
dbLocation = dbLocation + '/history.db'
} else {
dbLocation = 'history.db'

View File

@ -13,9 +13,8 @@ if (window && window.process && window.process.type === 'renderer') {
//
// dbLocation += '/playlists.db'
const remote = require('@electron/remote')
dbLocation = remote.app.getPath('userData')
const { ipcRenderer } = require('electron')
dbLocation = ipcRenderer.sendSync('getUserDataPathSync')
dbLocation = dbLocation + '/playlists.db'
} else {
dbLocation = 'playlists.db'

View File

@ -11,9 +11,8 @@ if (window && window.process && window.process.type === 'renderer') {
dbLocation = electron.remote.app.getPath('userData')
} */
const remote = require('@electron/remote')
dbLocation = remote.app.getPath('userData')
const { ipcRenderer } = require('electron')
dbLocation = ipcRenderer.sendSync('getUserDataPathSync')
dbLocation = dbLocation + '/profiles.db'
} else {
dbLocation = 'profiles.db'

View File

@ -1,7 +1,6 @@
import Datastore from 'nedb'
let dbLocation
let electron = null
let webframe = null
if (window && window.process && window.process.type === 'renderer') {
@ -13,12 +12,11 @@ if (window && window.process && window.process.type === 'renderer') {
dbLocation = electron.remote.app.getPath('userData')
} */
electron = require('electron')
webframe = electron.webFrame
const remote = require('@electron/remote')
dbLocation = remote.app.getPath('userData')
const electron = require('electron')
const ipcRenderer = electron.ipcRenderer
dbLocation = ipcRenderer.sendSync('getUserDataPathSync')
dbLocation = dbLocation + '/settings.db'
webframe = electron.webframe
} else {
dbLocation = 'settings.db'
}
@ -332,7 +330,9 @@ const actions = {
commit('setBarColor', result.value)
break
case 'uiScale':
webframe.setZoomFactor(parseInt(result.value) / 100)
if (webframe) {
webframe.setZoomFactor(parseInt(result.value) / 100)
}
commit('setUiScale', result.value)
break
case 'disableSmoothScrolling':

View File

@ -105,7 +105,67 @@ const getters = {
}
}
/**
* Wrapper function that calls `ipcRenderer.invoke(IRCtype, payload)` if the user is
* using Electron or a provided custom callback otherwise.
* @param {Object} context Object
* @param {String} IRCtype String
* @param {Function} webCbk Function
* @param {Object} payload any (default: null)
*/
async function invokeIRC(context, IRCtype, webCbk, payload = null) {
let response = null
const usingElectron = context.rootState.settings.usingElectron
if (usingElectron) {
const { ipcRenderer } = require('electron')
response = await ipcRenderer.invoke(IRCtype, payload)
} else if (webCbk) {
response = await webCbk()
}
return response
}
const actions = {
openExternalLink ({ rootState }, url) {
const usingElectron = rootState.settings.usingElectron
if (usingElectron) {
const ipcRenderer = require('electron').ipcRenderer
ipcRenderer.send('openExternalLink', url)
} else {
// Web placeholder
}
},
async getLocale (context) {
const webCbk = () => {
if (navigator && navigator.language) {
return navigator.language
}
}
return await invokeIRC(context, 'getLocale', webCbk) || 'en-US'
},
async showOpenDialog (context, options) {
// TODO: implement showOpenDialog web compatible callback
const webCbk = () => null
return await invokeIRC(context, 'showOpenDialog', webCbk, options)
},
async showSaveDialog (context, options) {
// TODO: implement showSaveDialog web compatible callback
const webCbk = () => null
return await invokeIRC(context, 'showSaveDialog', webCbk, options)
},
async getUserDataPath (context) {
// TODO: implement getUserDataPath web compatible callback
const webCbk = () => null
return await invokeIRC(context, 'getUserDataPath', webCbk)
},
updateShowProgressBar ({ commit }, value) {
commit('setShowProgressBar', value)
},

View File

@ -88,18 +88,5 @@ export default Vue.extend({
}
]
}
},
computed: {
usingElectron: function () {
return this.$store.getters.getUsingElectron
}
},
methods: {
openUrl: function (url) {
if (this.usingElectron) {
const shell = require('electron').shell
shell.openExternal(url)
}
}
}
})

View File

@ -14,8 +14,6 @@ import WatchVideoLiveChat from '../../components/watch-video-live-chat/watch-vid
import WatchVideoPlaylist from '../../components/watch-video-playlist/watch-video-playlist.vue'
import WatchVideoRecommendations from '../../components/watch-video-recommendations/watch-video-recommendations.vue'
const remote = require('@electron/remote')
export default Vue.extend({
name: 'Watch',
components: {
@ -931,7 +929,7 @@ export default Vue.extend({
const countDownIntervalId = setInterval(showCountDownMessage, 1000)
},
handleRouteChange: function () {
handleRouteChange: async function () {
clearTimeout(this.playNextTimeout)
this.handleWatchProgress()
@ -961,7 +959,7 @@ export default Vue.extend({
}
if (this.removeVideoMetaFiles) {
const userData = remote.app.getPath('userData')
const userData = await this.getUserDataPath()
if (this.isDev) {
const dashFileLocation = `dashFiles/${this.videoId}.xml`
const vttFileLocation = `storyboards/${this.videoId}.vtt`
@ -1004,9 +1002,9 @@ export default Vue.extend({
}
},
createLocalDashManifest: function (formats) {
createLocalDashManifest: async function (formats) {
const xmlData = ytDashGen.generate_dash_file_from_formats(formats, this.videoLengthSeconds)
const userData = remote.app.getPath('userData')
const userData = await this.getUserDataPath()
let fileLocation
let uriSchema
if (this.isDev) {
@ -1076,8 +1074,8 @@ export default Vue.extend({
})
})
// TODO: MAKE A VARIABLE WHICH CAN CHOOSE BETWEEN STROYBOARD ARRAY ELEMENTS
this.buildVTTFileLocally(storyboardArray[1]).then((results) => {
const userData = remote.app.getPath('userData')
this.buildVTTFileLocally(storyboardArray[1]).then(async (results) => {
const userData = await this.getUserDataPath()
let fileLocation
let uriSchema
@ -1195,7 +1193,8 @@ export default Vue.extend({
'showToast',
'buildVTTFileLocally',
'updateHistory',
'updateWatchProgress'
'updateWatchProgress',
'getUserDataPath'
])
}
})