diff --git a/.electron-vue/build.js b/.electron-vue/build.js index 84449b9afd..6e0e0cb4e3 100644 --- a/.electron-vue/build.js +++ b/.electron-vue/build.js @@ -11,6 +11,7 @@ const Multispinner = require('multispinner'); const mainConfig = require('./webpack.main.config'); const rendererConfig = require('./webpack.renderer.config'); +const webConfig = require('./webpack.web.config'); const doneLog = chalk.bgGreen.white(' DONE ') + ' '; const errorLog = chalk.bgRed.white(' ERROR ') + ' '; @@ -60,17 +61,29 @@ function build() { process.exit(1); }); - pack(rendererConfig) - .then(result => { - results += result + '\n\n'; - m.success('renderer'); - }) - .catch(err => { - m.error('renderer'); - console.log(`\n ${errorLog}failed to build renderer process`); - console.error(`\n${err}\n`); - process.exit(1); - }); + pack(rendererConfig) + .then(result => { + results += result + '\n\n'; + m.success('renderer'); + }) + .catch(err => { + m.error('renderer'); + console.log(`\n ${errorLog}failed to build renderer process`); + console.error(`\n${err}\n`); + process.exit(1); + }); + + pack(webConfig) + .then(result => { + results += result + '\n\n'; + m.success('renderer'); + }) + .catch(err => { + m.error('renderer'); + console.log(`\n ${errorLog}failed to build renderer process`); + console.error(`\n${err}\n`); + process.exit(1); + }); } function pack(config) { diff --git a/.electron-vue/dev-runner.js b/.electron-vue/dev-runner.js index 1e82e4a61e..ef6534c7d9 100644 --- a/.electron-vue/dev-runner.js +++ b/.electron-vue/dev-runner.js @@ -12,6 +12,7 @@ const webpackHotMiddleware = require('webpack-hot-middleware'); const mainConfig = require('./webpack.main.config'); const rendererConfig = require('./webpack.renderer.config'); +const webConfig = require('./webpack.web.config'); let electronProcess = null; let manualRestart = false; @@ -83,6 +84,49 @@ function startRenderer() { }); } + + +function startWeb() { + return new Promise((resolve, reject) => { + webConfig.entry.index = [path.join(__dirname, 'dev-client')].concat( + webConfig.entry.index, + ); + webConfig.mode = 'development'; + const compiler = webpack(webConfig); + hotMiddleware = webpackHotMiddleware(compiler, { + log: false, + heartbeat: 2500, + }); + + compiler.hooks.compilation.tap('compilation', compilation => { + compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync( + 'html-webpack-plugin-after-emit', + (data, cb) => { + hotMiddleware.publish({ action: 'reload' }); + cb(); + }, + ); + }); + + compiler.hooks.done.tap('done', stats => { + logStats('Renderer', stats); + }); + + const server = new WebpackDevServer(compiler, { + contentBase: path.join(__dirname, '../'), + quiet: true, + before(app, ctx) { + app.use(hotMiddleware); + ctx.middleware.waitUntilValid(() => { + resolve(); + }); + }, + }); + + server.listen(9081); + }); +} + function startMain() { return new Promise((resolve, reject) => { mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat( @@ -189,7 +233,7 @@ function greeting() { function init() { greeting(); - Promise.all([startRenderer(), startMain()]) + Promise.all([startRenderer(), startMain(), startWeb()]) .then(() => { startElectron(); }) diff --git a/.electron-vue/webpack.renderer.config.js b/.electron-vue/webpack.renderer.config.js index 8be5ef84e4..1d96cc7d7b 100644 --- a/.electron-vue/webpack.renderer.config.js +++ b/.electron-vue/webpack.renderer.config.js @@ -59,7 +59,6 @@ let rendererConfig = { devtool: '#module-eval-source-map', entry: { preference: path.join(__dirname, '../src/renderer/preference.js'), - login: path.join(__dirname, '../src/renderer/login.ts'), about: path.join(__dirname, '../src/renderer/about.js'), labor: path.join(__dirname, '../src/renderer/labor.ts'), index: path.join(__dirname, '../src/renderer/main.ts'), @@ -201,7 +200,6 @@ let rendererConfig = { new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('index')), new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('labor')), new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('about')), - new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('login')), new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('preference')), new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('browsing')), new webpack.HotModuleReplacementPlugin(), diff --git a/.electron-vue/webpack.web.config.js b/.electron-vue/webpack.web.config.js new file mode 100644 index 0000000000..17255fbd54 --- /dev/null +++ b/.electron-vue/webpack.web.config.js @@ -0,0 +1,306 @@ +'use strict'; + +process.env.BABEL_ENV = 'renderer'; + +const path = require('path'); +const childProcess = require('child_process'); +const webpack = require('webpack'); +const { VueLoaderPlugin } = require('vue-loader'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const SentryWebpackPlugin = require('@sentry/webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const { dependencies, optionalDependencies } = require('../package.json'); + +let release = ''; +try { + const result = childProcess.spawnSync('git', [ + 'describe', + '--tag', + '--exact-match', + '--abbrev=0', + ]); + if (result.status === 0) { + const tag = result.stdout.toString('utf8').replace(/^\s+|\s+$/g, ''); + if (tag) release = `SPlayer${tag}`; + } +} catch (ex) { + console.error(ex); +} + +function generateHtmlWebpackPluginConfig(name) { + return { + chunks: [name], + filename: `${name}.html`, + template: path.resolve(__dirname, `../src/login.ejs`), + minify: { + collapseWhitespace: true, + removeAttributeQuotes: true, + removeComments: true, + }, + nodeModules: + process.env.NODE_ENV !== 'production' ? path.resolve(__dirname, '../node_modules') : false, + }; +} + +/** + * List of node_modules to include in webpack bundle + * + * Required for specific packages like Vue UI libraries + * that provide pure *.vue files that need compiling + * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals + */ +let whiteListedModules = ['vue', 'vuex', 'vue-router', 'vue-i18n', 'vue-axios', 'axios']; + +let rendererConfig = { + mode: 'development', + devtool: '#module-eval-source-map', + entry: { + index: path.join(__dirname, '../src/renderer/login.ts'), + login: path.join(__dirname, '../src/renderer/login.ts'), + }, + externals: [ + ...Object.keys(Object.assign({}, dependencies, optionalDependencies)).filter( + d => !whiteListedModules.includes(d), + ), + ], + module: { + rules: [ + { + test: /\.(js)$/, + enforce: 'pre', + exclude: /node_modules/, + use: { + loader: 'eslint-loader', + options: { + formatter: require('eslint-friendly-formatter'), + }, + }, + }, + { + test: /\.css$/, + use: ExtractTextPlugin.extract({ + fallback: 'style-loader', + use: 'css-loader', + }), + }, + { + test: /\.html$/, + use: 'vue-html-loader', + }, + { + test: /\.tsx?$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + appendTsSuffixTo: [/\.vue$/], + }, + }, + ], + }, + { + test: /\.js$/, + use: 'babel-loader', + exclude: /node_modules/, + }, + { + test: /\.vue$/, + use: { + loader: 'vue-loader', + options: { + extractCSS: process.env.NODE_ENV === 'production', + loaders: { + i18n: 'vue-i18n-loader', + }, + }, + }, + }, + { + test: /\.sass$/, + use: [ + 'vue-style-loader', + 'css-loader', + { loader: 'sass-loader', options: { indentedSyntax: 1 } }, + { + loader: 'sass-resources-loader', + options: { resources: path.join(__dirname, '../src/renderer/css/global.scss') }, + }, + ], + }, + { + test: /\.scss$/, + use: [ + 'vue-style-loader', + 'css-loader', + 'sass-loader', + { + loader: 'sass-resources-loader', + options: { resources: path.join(__dirname, '../src/renderer/css/global.scss') }, + }, + ], + }, + { + test: /\.svg$/, + include: [path.resolve(__dirname, '../src/renderer/assets/icon')], + use: { + loader: 'svg-sprite-loader', + options: { + symbolId: '[name]', + }, + }, + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + exclude: [path.resolve(__dirname, '../src/renderer/assets/icon')], + use: { + loader: 'url-loader', + query: { + limit: 10000, + name: 'imgs/[name]--[folder].[ext]', + }, + }, + }, + { + test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, + use: { + loader: 'url-loader', + options: { + limit: 10000, + name: 'media/[name]--[folder].[ext]', + }, + }, + }, + { + test: /\.(woff2?|eot|ttf|ttc|otf)(\?.*)?$/, + use: { + loader: 'url-loader', + query: { + limit: 10000, + name: 'fonts/[name]--[folder].[ext]', + }, + }, + }, + ], + }, + node: { + __dirname: process.env.NODE_ENV !== 'production', + __filename: process.env.NODE_ENV !== 'production', + }, + plugins: [ + new VueLoaderPlugin(), + new ExtractTextPlugin('styles.css'), + new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('index')), + new HtmlWebpackPlugin(generateHtmlWebpackPluginConfig('login')), + new webpack.HotModuleReplacementPlugin(), + ], + output: { + filename: '[name].js', + libraryTarget: 'umd', + path: path.join(__dirname, '../dist/electron'), + globalObject: 'this', + }, + resolve: { + alias: { + '@': path.join(__dirname, '../src/renderer'), + vue$: 'vue/dist/vue.esm.js', + electron: '@chiflix/electron', + grpc: '@grpc/grpc-js', + }, + extensions: ['.ts', '.tsx', '.js', '.json', '.node'], + }, + target: 'web', +}; + +const sharedDefinedVariables = { + 'process.platform': `"${process.platform}"`, +}; + +if (process.env.ENVIRONMENT_NAME === 'APPX') { + // quick fix for process.windowsStore undefined on Windows Store build + sharedDefinedVariables['process.windowsStore'] = 'true'; +} +/** + * Adjust rendererConfig for development settings + */ +if (process.env.NODE_ENV !== 'production') { + rendererConfig.plugins.push( + new ForkTsCheckerWebpackPlugin({ eslint: true, vue: true }), + new webpack.DefinePlugin( + Object.assign(sharedDefinedVariables, { + 'process.env.SAGI_API': `"${process.env.SAGI_API || 'apis.stage.sagittarius.ai:8443'}"`, + 'process.env.ACCOUNT_API': `"${process.env.ACCOUNT_API || 'https://account.stage.splayer.org'}"`, + __static: `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`, + }), + ), + ); +} + +/** + * Adjust rendererConfig for production settings + */ +if (process.env.NODE_ENV === 'production') { + rendererConfig.mode = 'production'; + rendererConfig.devtool = '#source-map'; + + rendererConfig.plugins.push( + new CopyWebpackPlugin([ + { + from: path.join(__dirname, '../static'), + to: path.join(__dirname, '../dist/electron/static'), + ignore: ['.*'], + }, + ]), + new webpack.DefinePlugin( + Object.assign(sharedDefinedVariables, { + 'process.env.SAGI_API': `"${process.env.SAGI_API || 'apis.sagittarius.ai:8443'}"`, + 'process.env.ACCOUNT_API': `"${process.env.ACCOUNT_API || 'https://account.splayer.org'}"`, + 'process.env.SENTRY_RELEASE': `"${release}"`, + 'process.env.NODE_ENV': '"production"', + }), + ), + new webpack.LoaderOptionsPlugin({ + minimize: true, + }), + ); + + rendererConfig.optimization = { + minimizer: [ + new TerserPlugin({ + terserOptions: { + keep_classnames: true, + }, + }), + ], + }; + + if (process.platform === 'darwin') { + // only check on mac, to speed up Windows build + rendererConfig.plugins.push(new ForkTsCheckerWebpackPlugin({ eslint: true, vue: true })); + } + + if (release && process.env.SENTRY_AUTH_TOKEN) { + rendererConfig.plugins.push( + new SentryWebpackPlugin({ + release, + include: './dist', + urlPrefix: 'app:///dist/', + ext: ['js', 'map'], + ignore: ['node_modules'], + }), + new SentryWebpackPlugin({ + release, + include: './src', + urlPrefix: 'webpack:///./src/', + ext: ['js', 'ts', 'vue'], + ignore: ['node_modules'], + }), + ); + } +} + +module.exports = rendererConfig; diff --git a/package.json b/package.json index 8995bef8b0..1e39030645 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "pack": "npm run pack:main && npm run pack:renderer", "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", + "pack:web": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.web.config.js", "test": "npm run unit", "unit": "karma start test/unit/karma.conf.js", "postinstall": "node ./scripts/post-install.js" @@ -47,7 +48,6 @@ "electron-updater": "^4.0.0", "franc": "^4.0.0", "fs-extra": "^7.0.1", - "geoip-lite": "^1.3.8", "get-video-id": "^3.1.4", "google-protobuf": "^3.6.0", "iconv-lite": "^0.4.24", diff --git a/src/login.ejs b/src/login.ejs new file mode 100644 index 0000000000..63c692d98d --- /dev/null +++ b/src/login.ejs @@ -0,0 +1,11 @@ + + + + + SPlayer + + +
+ + + diff --git a/src/main/helpers/AudioGrabService.ts b/src/main/helpers/AudioGrabService.ts index 618e43d286..f429f1e650 100644 --- a/src/main/helpers/AudioGrabService.ts +++ b/src/main/helpers/AudioGrabService.ts @@ -2,7 +2,7 @@ * @Author: tanghaixiang@xindong.com * @Date: 2019-07-22 17:18:34 * @Last Modified by: tanghaixiang@xindong.com - * @Last Modified time: 2019-09-27 10:11:29 + * @Last Modified time: 2019-10-11 14:22:07 */ import { EventEmitter } from 'events'; @@ -204,7 +204,6 @@ export default class AudioGrabService extends EventEmitter { getIP().then((ip) => { metadata.set('clientip', ip); }).finally(() => { - console.log(metadata); // eslint-disable-line cb(null, metadata); }); }; diff --git a/src/main/index.js b/src/main/index.js index 0d0bf156d1..b5460872fc 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -8,13 +8,16 @@ import path, { basename, dirname, extname, join, resolve, } from 'path'; import fs from 'fs'; +import http from 'http'; import rimraf from 'rimraf'; -// import { audioHandler } from './helpers/audioHandler'; import { audioGrabService } from './helpers/AudioGrabService'; import './helpers/electronPrototypes'; import writeLog from './helpers/writeLog'; import { - getValidVideoRegex, getValidSubtitleRegex, getToken, saveToken, + getValidVideoRegex, getValidSubtitleRegex, + getToken, saveToken, + getIP, + getRightPort, } from '../shared/utils'; import { mouse } from './helpers/mouse'; import MenuService from './menu/MenuService'; @@ -88,6 +91,9 @@ let needBlockCloseLaborWindow = true; // 标记是否阻塞nsfw窗口关闭 let inited = false; let hideBrowsingWindow = false; let finalVideoToOpen = []; +let signInEndPoint = ''; +let localHostPort = 0; +let fileDirServerOnLine = false; const locale = new Locale(); const tmpVideoToOpen = []; const tmpSubsToOpen = []; @@ -108,8 +114,8 @@ const aboutURL = process.env.NODE_ENV === 'development' const preferenceURL = process.env.NODE_ENV === 'development' ? 'http://localhost:9080/preference.html' : `file://${__dirname}/preference.html`; -const loginURL = process.env.NODE_ENV === 'development' - ? 'http://localhost:9080/login.html' +let loginURL = process.env.NODE_ENV === 'development' + ? 'http://localhost:9081/login.html' : `file://${__dirname}/login.html`; const browsingURL = process.env.NODE_ENV === 'development' ? 'http://localhost:9080/browsing.html' @@ -302,23 +308,73 @@ function createPreferenceWindow(e, route) { preferenceWindow.once('ready-to-show', () => { preferenceWindow.show(); }); + preferenceWindow.on('focus', () => { + menuService.enableMenu(false); + }); +} + +/** + * @description sign in window need aliyun nc valication with // protocal + * @author tanghaixiang + */ +function createFileDirServer() { + http.createServer((request, response) => { + console.log('request ', request.url); + let filePath = `${request.url}`; + if (filePath === '/') { + filePath = '/login.html'; + } + const extname = String(path.extname(filePath)).toLowerCase(); + const mimeTypes = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.wav': 'audio/wav', + '.mp4': 'video/mp4', + '.woff': 'application/font-woff', + '.ttf': 'application/font-ttf', + '.eot': 'application/vnd.ms-fontobject', + '.otf': 'application/font-otf', + '.wasm': 'application/wasm', + }; + const contentType = mimeTypes[extname] || 'application/octet-stream'; + fs.readFile(`${__dirname}${filePath}`, (error, content) => { + if (error) { + return; + } + response.writeHead(200, { 'Content-Type': contentType }); + response.end(content, 'utf-8'); + }); + }).listen(localHostPort); } function createLoginWindow(e, route) { + // in production use http protocal + // aliyun captcha use // protocal + if (process.env.NODE_ENV === 'production' && !fileDirServerOnLine) { + createFileDirServer(); + fileDirServerOnLine = true; + loginURL = `http://localhost:${localHostPort}/login.html`; + } const loginWindowOptions = { useContentSize: true, frame: false, titleBarStyle: 'none', width: 412, height: 284, - transparent: true, - resizable: false, - show: false, webPreferences: { - webSecurity: false, - nodeIntegration: true, experimentalFeatures: true, + webSecurity: false, + preload: `${require('path').resolve(__static, 'login/preload.js')}`, }, + transparent: true, + resizable: false, + show: false, acceptFirstMouse: true, fullscreenable: false, maximizable: false, @@ -350,6 +406,9 @@ function createLoginWindow(e, route) { loginWindow.once('ready-to-show', () => { loginWindow.show(); }); + loginWindow.on('focus', () => { + menuService.enableMenu(false); + }); } function createAboutWindow() { @@ -1093,6 +1152,9 @@ function registerMainWindowEvent(mainWindow) { if (mainWindow && !mainWindow.webContents.isDestroyed()) { mainWindow.webContents.send('mainDispatch', 'setPreference', args); } + if (loginWindow && !loginWindow.webContents.isDestroyed()) { + loginWindow.webContents.send('setPreference', args); + } }); ipcMain.on('main-to-preference', (e, args) => { if (preferenceWindow && !preferenceWindow.webContents.isDestroyed()) { @@ -1124,6 +1186,12 @@ function registerMainWindowEvent(mainWindow) { ipcMain.on('add-login', createLoginWindow); + ipcMain.on('login-captcha', () => { + if (loginWindow && !loginWindow.webContents.isDestroyed()) { + loginWindow.setSize(412, 336, true); + } + }); + ipcMain.on('account-enabled', () => { // get storage token getToken().then((account) => { @@ -1140,6 +1208,10 @@ function registerMainWindowEvent(mainWindow) { } }).catch(console.error); }); + + ipcMain.on('sign-in-end-point', (events, data) => { + signInEndPoint = data; + }); } function createMainWindow(openDialog, playlistId) { @@ -1217,6 +1289,9 @@ function createMainWindow(openDialog, playlistId) { if (mainWindow) mainWindow.openDevTools({ mode: 'detach' }); }, 1000); } + mainWindow.on('focus', () => { + menuService.enableMenu(true); + }); } ['left-drag', 'left-up'].forEach((channel) => { @@ -1472,6 +1547,12 @@ app.on('sign-in', (account) => { } }); +app.on('sign-out-confirm', () => { + if (mainWindow && !mainWindow.webContents.isDestroyed()) { + mainWindow.webContents.send('sign-out-confirm', undefined); + } +}); + app.on('sign-out', () => { global['account'] = undefined; menuService.updateAccount(undefined); @@ -1481,3 +1562,18 @@ app.on('sign-out', () => { mainWindow.webContents.send('sign-in', undefined); } }); + +app.getDisplayLanguage = () => { + locale.getDisplayLanguage(); + return locale.displayLanguage; +}; + +// export getIp to static login preload.js +app.getIP = getIP; + +// export getSignInEndPoint to static login preload.js +app.getSignInEndPoint = () => signInEndPoint; + +getRightPort().then((port) => { + localHostPort = port; +}).catch(console.error); diff --git a/src/main/menu/Menu.ts b/src/main/menu/Menu.ts index 261439ab54..79861b47ca 100644 --- a/src/main/menu/Menu.ts +++ b/src/main/menu/Menu.ts @@ -357,7 +357,7 @@ export default class Menubar { }, undefined, false); accountMenu.append(idMenu); const logout = this.createMenuItem('msg.account.logout', () => { - app.emit('sign-out'); + app.emit('sign-out-confirm'); }, undefined, true); accountMenu.append(logout); Menu.setApplicationMenu(this.menubar); @@ -865,7 +865,7 @@ export default class Menubar { }, undefined, false); accountMenu.append(idMenu); const logout = this.createMenuItem('msg.account.logout', () => { - app.emit('sign-out'); + app.emit('sign-out-confirm'); }, undefined, true); accountMenu.append(logout); } else { diff --git a/src/renderer/App.vue b/src/renderer/App.vue index 2bf86a3fd5..e64a2abfd6 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -37,17 +37,21 @@