diff --git a/electron.vite.config.js b/electron.vite.config.js index 8c1fd70..9fad50e 100644 --- a/electron.vite.config.js +++ b/electron.vite.config.js @@ -24,6 +24,9 @@ export default defineConfig({ // }, }, renderer: { + server: { + port: 1040, + }, resolve: { alias: { "config": resolve("src/renderer/config"), diff --git a/src/main/auth.js b/src/main/auth.js index 5cd4294..7107cde 100644 --- a/src/main/auth.js +++ b/src/main/auth.js @@ -1,5 +1,34 @@ -class AuthService { - authorizeFromUrl(url) { - console.log("authorizeFromUrl", url) +import { safeStorage } from "electron" +import sendToRender from "./utils/sendToRender" + +export default class AuthService { + authorize(pkg_id, token) { + console.log("Authorizing", pkg_id, token) + global.SettingsStore.set(`auth:${pkg_id}`, safeStorage.encryptString(token)) + + sendToRender(`new:notification`, { + message: "Authorized", + description: "Now you can start this package", + }) + + return true + } + + unauthorize(pkg_id) { + global.SettingsStore.delete(`auth:${pkg_id}`) + + return true + } + + getAuth(pkg_id) { + const value = global.SettingsStore.get(`auth:${pkg_id}`) + + if (!value) { + return null + } + + console.log("getAuth", value) + + return safeStorage.decryptString(Buffer.from(value.data)) } } \ No newline at end of file diff --git a/src/main/index.js b/src/main/index.js index c1db6a2..fa6dba1 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -21,6 +21,8 @@ import { readManifest } from "./utils/readManifest" import GoogleDriveAPI from "./lib/google_drive" +import AuthService from "./auth" + const { autoUpdater } = require("electron-differential-updater") const ProtocolRegistry = require("protocol-registry") @@ -32,7 +34,7 @@ class ElectronApp { this.win = null } - authService = new AuthService() + authService = global.authService = new AuthService() handlers = { "pkg:list": async () => { @@ -69,6 +71,9 @@ class ElectronApp { "pkg:cancel_install": async (event, manifest_id) => { return await this.pkgManager.uninstall(manifest_id) }, + "pkg:delete_auth": async (event, manifest_id) => { + return this.authService.unauthorize(manifest_id) + }, "pkg:uninstall": async (event, ...args) => { return await this.pkgManager.uninstall(...args) }, @@ -99,7 +104,16 @@ class ElectronApp { return global.SettingsStore.has(key) }, "app:init": async (event, data) => { - await setup() + try { + await setup() + } catch (err) { + console.error(err) + + sendToRender("new:notification", { + message: "Setup failed", + description: err.message + }) + } // check if can decode google drive token const googleDrive_enabled = !!(await GoogleDriveAPI.readCredentials()) @@ -158,20 +172,21 @@ class ElectronApp { handleURLProtocol(url) { const urlStarter = `${protocolRegistryNamespace}://` + console.log(url) + if (url.startsWith(urlStarter)) { const urlValue = url.split(urlStarter)[1] - const explicitAction = urlValue.split("%3E") + const explicitAction = urlValue.split("#") - if (explicitAction[1]) { - const [action, value] = explicitAction + console.log(explicitAction) - switch (action) { - case "install": { - return sendToRender("installation:invoked", value) - } + if (explicitAction[0]) { + switch (explicitAction[0]) { case "authorize": { - return authService.authorizeFromUrl(url) + const [pkgid, token] = explicitAction[1].split("%23") + + return this.authService.authorize(pkgid, token) } default: { return sendToRender("new:message", { diff --git a/src/main/lib/auth/index.js b/src/main/lib/auth/index.js new file mode 100644 index 0000000..5601955 --- /dev/null +++ b/src/main/lib/auth/index.js @@ -0,0 +1,59 @@ +import open from "open" +import axios from "axios" +import sendToRender from "../../utils/sendToRender" + +export default class Auth { + constructor(manifest) { + this.manifest = manifest + + console.log(this.manifest) + } + + async get() { + const authData = global.authService.getAuth(this.manifest.id) + + console.log(authData) + + if (authData && this.manifest.auth && this.manifest.auth.getter) { + const result = await axios({ + method: "POST", + url: this.manifest.auth.getter, + headers: { + "Content-Type": "application/json", + }, + data: { + auth_data: authData, + } + }).catch((err) => { + sendToRender(`new:notification`, { + type: "error", + message: "Failed to authorize", + description: err.response.data.message ?? err.response.data.error ?? err.message, + duration: 10 + }) + + return err + }) + + if (result instanceof Error) { + throw result + } + + console.log(result.data) + + return result.data + } + + return authData + } + + request() { + if (!this.manifest.auth) { + return false + } + + const authURL = this.manifest.auth.fetcher + + open(authURL) + } +} \ No newline at end of file diff --git a/src/main/lib/public_bind.js b/src/main/lib/public_bind.js index 61c0674..4030c4f 100644 --- a/src/main/lib/public_bind.js +++ b/src/main/lib/public_bind.js @@ -1,11 +1,13 @@ -import MCL from "./mcl" -import RendererIPC from "./renderer_ipc" +import mcl from "./mcl" +import ipc from "./renderer_ipc" import rfs from "./rfs" import exec from "./execa/public_lib" +import auth from "./auth" export default { - mcl: MCL, - ipc: RendererIPC, + mcl: mcl, + ipc: ipc, rfs: rfs, exec: exec, + auth: auth, } \ No newline at end of file diff --git a/src/main/setup.js b/src/main/setup.js index 98e5047..63d6f87 100644 --- a/src/main/setup.js +++ b/src/main/setup.js @@ -35,9 +35,10 @@ async function main() { let sevenzip_exec = Vars.sevenzip_path let git_exec = Vars.git_path let rclone_exec = Vars.rclone_path + let java_exec = Vars.java_path if (!fs.existsSync(sevenzip_exec)) { - global.win.webContents.send("setup:step", "Downloading 7z binaries...") + global.win.webContents.send("setup_step", "Downloading 7z binaries...") console.log(`Downloading 7z binaries...`) fs.mkdirSync(path.resolve(binariesPath, "7z-bin"), { recursive: true }) @@ -54,16 +55,12 @@ async function main() { } } - if (!fs.existsSync(git_exec)) { - if (process.platform !== "win32") { - return git_exec = null - } - + if (!fs.existsSync(git_exec) && process.platform === "win32") { const tempPath = path.resolve(binariesPath, "git-bundle.zip") const binPath = path.resolve(binariesPath, "git-bin") if (!fs.existsSync(tempPath)) { - global.win.webContents.send("setup:step", "Downloading GIT binaries...") + global.win.webContents.send("setup_step", "Downloading GIT binaries...") console.log(`Downloading git binaries...`) let url = resolveDestBin(`https://storage.ragestudio.net/rstudio/binaries/git`, "git-bundle-2.4.0.zip") @@ -74,7 +71,7 @@ async function main() { ) } - global.win.webContents.send("setup:step", "Extracting GIT binaries...") + global.win.webContents.send("setup_step", "Extracting GIT binaries...") console.log(`Extracting GIT...`) await new Promise((resolve, reject) => { @@ -84,9 +81,9 @@ async function main() { fs.unlinkSync(tempPath) } - if (!fs.existsSync(Vars.rclone_path)) { + if (!fs.existsSync(Vars.rclone_path) && process.platform === "win32") { console.log(`Downloading rclone binaries...`) - global.win.webContents.send("setup:step", "Downloading rclone binaries...") + global.win.webContents.send("setup_step", "Downloading rclone binaries...") const tempPath = path.resolve(binariesPath, "rclone-bin.zip") @@ -97,7 +94,7 @@ async function main() { fs.createWriteStream(tempPath) ) - global.win.webContents.send("setup:step", "Extracting rclone binaries...") + global.win.webContents.send("setup_step", "Extracting rclone binaries...") await new Promise((resolve, reject) => { fs.createReadStream(tempPath).pipe(unzipper.Extract({ path: path.resolve(binariesPath, "rclone-bin") })).on("close", resolve).on("error", reject) @@ -112,7 +109,7 @@ async function main() { if (!fs.existsSync(Vars.java_path)) { console.log(`Downloading java binaries...`) - global.win.webContents.send("setup:step", "Downloading Java JDK...") + global.win.webContents.send("setup_step", "Downloading Java JDK...") const tempPath = path.resolve(binariesPath, "java-jdk.zip") @@ -123,14 +120,14 @@ async function main() { fs.createWriteStream(tempPath) ) - global.win.webContents.send("setup:step", "Extracting JAVA...") + global.win.webContents.send("setup_step", "Extracting JAVA...") await new Promise((resolve, reject) => { fs.createReadStream(tempPath).pipe(unzipper.Extract({ path: path.resolve(binariesPath, "java-jdk") })).on("close", resolve).on("error", reject) }) if (os.platform() !== "win32") { - ChildProcess.execSync("chmod +x " + Vars.rclone_path) + ChildProcess.execSync("chmod +x " + path.resolve(binariesPath, "java-jdk")) } fs.unlinkSync(tempPath) @@ -141,7 +138,7 @@ async function main() { console.log(`rclone binaries: ${rclone_exec}`) console.log(`JAVA jdk: ${Vars.java_path}`) - global.win.webContents.send("setup:step", undefined) + global.win.webContents.send("setup_step", undefined) global.win.webContents.send("setup:done") } diff --git a/src/main/utils/initManifest.js b/src/main/utils/initManifest.js index 9ac2841..3139d4b 100644 --- a/src/main/utils/initManifest.js +++ b/src/main/utils/initManifest.js @@ -44,6 +44,9 @@ export default async (manifest = {}) => { id: manifest.id, version: manifest.version, install_path: install_path, + auth: manifest.auth, + configs: manifest.configs, + os_string: os_string, }) console.log(`[${manifest.id}] initManifest() | Using libraries: ${manifest.import_libs.join(", ")}`) diff --git a/src/renderer/src/App.jsx b/src/renderer/src/App.jsx index fe31cd6..dda0d7a 100644 --- a/src/renderer/src/App.jsx +++ b/src/renderer/src/App.jsx @@ -21,6 +21,7 @@ class App extends React.Component { pkg: null, initializing: false, + setup_step: null, updateAvailable: false, updateText: null, @@ -99,7 +100,9 @@ class App extends React.Component { message.success("Google Drive API unauthorized") }, - "setup:step": (event, data) => { + "setup_step": (event, data) => { + console.log(`setup:step`, data) + this.setState({ setup_step: data, }) diff --git a/src/renderer/src/pages/pkg/[pkg_id].jsx b/src/renderer/src/pages/pkg/[pkg_id].jsx index 36c3f50..df90cd0 100644 --- a/src/renderer/src/pages/pkg/[pkg_id].jsx +++ b/src/renderer/src/pages/pkg/[pkg_id].jsx @@ -115,6 +115,24 @@ const PackageOptions = (props) => { }) } + function handleDeleteAuth() { + antd.Modal.confirm({ + title: "Clear auth data", + content: "Are you sure you want to delete auth data? May you need to reauthorize.", + onOk() { + const closeModal = props.onClose || props.close + + if (closeModal) { + closeModal() + } else { + app.location.push("/") + } + + ipc.exec("pkg:delete_auth", manifest.id) + }, + }) + } + function canApplyChanges() { return Object.keys(changes).length > 0 } @@ -223,6 +241,17 @@ const PackageOptions = (props) => {