mirror of https://github.com/FreeTubeApp/FreeTube
Merge branch 'development' of https://github.com/dkshxd/FreeTube into development
This commit is contained in:
commit
c4e32bce9c
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Injects the paths that the renderer process is allowed to read into the main.js file,
|
||||
* by replacing __FREETUBE_ALLOWED_PATHS__ with an array of strings with the paths.
|
||||
*
|
||||
* This allows the main process to validate the paths which the renderer process accesses,
|
||||
* to ensure that it cannot access other files on the disk, without the users permission (e.g. file picker).
|
||||
*/
|
||||
import { closeSync, ftruncateSync, openSync, readFileSync, readdirSync, writeSync } from 'fs'
|
||||
import { dirname, join, relative, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const distDirectory = resolve(__dirname, '..', 'dist')
|
||||
const webDirectory = join(distDirectory, 'web')
|
||||
|
||||
const paths = readdirSync(distDirectory, {
|
||||
recursive: true,
|
||||
withFileTypes: true
|
||||
})
|
||||
.filter(dirent => {
|
||||
// only include files not directories
|
||||
return dirent.isFile() &&
|
||||
// disallow the renderer process/browser windows to read the main.js file
|
||||
dirent.name !== 'main.js' &&
|
||||
dirent.name !== 'main.js.LICENSE.txt' &&
|
||||
// filter out any web build files, in case the dist directory contains a web build
|
||||
!dirent.path.startsWith(webDirectory);
|
||||
})
|
||||
.map(dirent => {
|
||||
const joined = join(dirent.path, dirent.name)
|
||||
return '/' + relative(distDirectory, joined).replaceAll('\\', '/')
|
||||
})
|
||||
|
||||
|
||||
let fileHandle
|
||||
try {
|
||||
fileHandle = openSync(join(distDirectory, 'main.js'), 'r+')
|
||||
|
||||
let contents = readFileSync(fileHandle, 'utf-8')
|
||||
|
||||
contents = contents.replace('__FREETUBE_ALLOWED_PATHS__', JSON.stringify(paths))
|
||||
|
||||
ftruncateSync(fileHandle)
|
||||
writeSync(fileHandle, contents, 0, 'utf-8')
|
||||
} finally {
|
||||
if (typeof fileHandle !== 'undefined') {
|
||||
closeSync(fileHandle)
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@
|
|||
"lint-style": "stylelint \"**/*.{css,scss}\"",
|
||||
"lint-style-fix": "stylelint --fix \"**/*.{css,scss}\"",
|
||||
"lint-yml": "eslint --ext .yml,.yaml ./",
|
||||
"pack": "run-p pack:main pack:renderer",
|
||||
"pack": "run-p pack:main pack:renderer && node _scripts/injectAllowedPaths.mjs",
|
||||
"pack:main": "webpack --mode=production --node-env=production --config _scripts/webpack.main.config.js",
|
||||
"pack:renderer": "webpack --mode=production --node-env=production --config _scripts/webpack.renderer.config.js",
|
||||
"pack:web": "webpack --mode=production --node-env=production --config _scripts/webpack.web.config.js",
|
||||
|
|
|
@ -11,9 +11,13 @@ import * as baseHandlers from '../datastores/handlers/base'
|
|||
import { extractExpiryTimestamp, ImageCache } from './ImageCache'
|
||||
import { existsSync } from 'fs'
|
||||
import asyncFs from 'fs/promises'
|
||||
import { promisify } from 'util'
|
||||
import { brotliDecompress } from 'zlib'
|
||||
|
||||
import packageDetails from '../../package.json'
|
||||
|
||||
const brotliDecompressAsync = promisify(brotliDecompress)
|
||||
|
||||
if (process.argv.includes('--version')) {
|
||||
app.exit()
|
||||
} else {
|
||||
|
@ -21,6 +25,24 @@ if (process.argv.includes('--version')) {
|
|||
}
|
||||
|
||||
function runApp() {
|
||||
/** @type {Set<string>} */
|
||||
let ALLOWED_RENDERER_FILES
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// __FREETUBE_ALLOWED_PATHS__ is replaced by the injectAllowedPaths.mjs script
|
||||
// eslint-disable-next-line no-undef
|
||||
ALLOWED_RENDERER_FILES = new Set(__FREETUBE_ALLOWED_PATHS__)
|
||||
|
||||
protocol.registerSchemesAsPrivileged([{
|
||||
scheme: 'app',
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
supportFetchAPI: true
|
||||
}
|
||||
}])
|
||||
}
|
||||
|
||||
require('electron-context-menu')({
|
||||
showSearchWithGoogle: false,
|
||||
showSaveImageAs: true,
|
||||
|
@ -222,6 +244,48 @@ function runApp() {
|
|||
}
|
||||
|
||||
app.on('ready', async (_, __) => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
protocol.handle('app', async (request) => {
|
||||
if (request.method !== 'GET') {
|
||||
return new Response(null, {
|
||||
status: 405,
|
||||
headers: {
|
||||
Allow: 'GET'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const { host, pathname } = new URL(request.url)
|
||||
|
||||
if (host !== 'bundle' || !ALLOWED_RENDERER_FILES.has(pathname)) {
|
||||
return new Response(null, {
|
||||
status: 400
|
||||
})
|
||||
}
|
||||
|
||||
const contents = await asyncFs.readFile(path.join(__dirname, pathname))
|
||||
|
||||
if (pathname.endsWith('.json.br')) {
|
||||
const decompressed = await brotliDecompressAsync(contents)
|
||||
|
||||
return new Response(decompressed.buffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Encoding': 'br'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return new Response(contents.buffer, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': contentTypeFromFileExtension(pathname.split('.').at(-1))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let docArray
|
||||
try {
|
||||
docArray = await baseHandlers.settings._findAppReadyRelatedSettings()
|
||||
|
@ -455,6 +519,34 @@ function runApp() {
|
|||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {string} extension
|
||||
*/
|
||||
function contentTypeFromFileExtension(extension) {
|
||||
switch (extension) {
|
||||
case 'html':
|
||||
return 'text/html'
|
||||
case 'css':
|
||||
return 'text/css'
|
||||
case 'js':
|
||||
return 'text/javascript'
|
||||
case 'ttf':
|
||||
return 'font/ttf'
|
||||
case 'woff':
|
||||
return 'font/woff'
|
||||
case 'svg':
|
||||
return 'image/svg+xml'
|
||||
case 'png':
|
||||
return 'image/png'
|
||||
case 'json':
|
||||
return 'application/json'
|
||||
case 'txt':
|
||||
return 'text/plain'
|
||||
default:
|
||||
return 'application/octet-stream'
|
||||
}
|
||||
}
|
||||
|
||||
async function installDevTools() {
|
||||
try {
|
||||
/* eslint-disable */
|
||||
|
@ -605,8 +697,7 @@ function runApp() {
|
|||
if (windowStartupUrl != null) {
|
||||
newWindow.loadURL(windowStartupUrl)
|
||||
} else {
|
||||
/* eslint-disable-next-line n/no-path-concat */
|
||||
newWindow.loadFile(`${__dirname}/index.html`)
|
||||
newWindow.loadURL('app://bundle/index.html')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,29 +34,20 @@ export async function loadLocale(locale) {
|
|||
return
|
||||
}
|
||||
|
||||
let path
|
||||
|
||||
// locales are only compressed in our production Electron builds
|
||||
if (process.env.IS_ELECTRON && process.env.NODE_ENV !== 'development') {
|
||||
const { promisify } = require('util')
|
||||
const { brotliDecompress } = require('zlib')
|
||||
const brotliDecompressAsync = promisify(brotliDecompress)
|
||||
try {
|
||||
// decompress brotli compressed json file and then load it
|
||||
const url = createWebURL(`/static/locales/${locale}.json.br`)
|
||||
const compressed = await (await fetch(url)).arrayBuffer()
|
||||
|
||||
const decompressed = await brotliDecompressAsync(compressed)
|
||||
const data = JSON.parse(decompressed.toString())
|
||||
i18n.setLocaleMessage(locale, data)
|
||||
} catch (err) {
|
||||
console.error(locale, err)
|
||||
}
|
||||
path = `/static/locales/${locale}.json.br`
|
||||
} else {
|
||||
const url = createWebURL(`/static/locales/${locale}.json`)
|
||||
|
||||
const response = await fetch(url)
|
||||
const data = await response.json()
|
||||
i18n.setLocaleMessage(locale, data)
|
||||
path = `/static/locales/${locale}.json`
|
||||
}
|
||||
|
||||
const url = createWebURL(path)
|
||||
|
||||
const response = await fetch(url)
|
||||
const data = await response.json()
|
||||
i18n.setLocaleMessage(locale, data)
|
||||
}
|
||||
|
||||
export default i18n
|
||||
|
|
Loading…
Reference in New Issue