mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-17 13:13:49 +01:00
Add Electron preload script to allow communication
Adds a channel to communicate between the main process & the renderer process, so that the game can easily ship data back to the main process. It uses the Electron contextBridge & ipcRenderer/ipcMain. Connects those events to various save functions. Adds triggered events on game save, game load, and imported game. Adds way for the Electron app to ask for certain actions or data. Hook handlers to disable automatic restore Allows to temporarily disable restore when the game just did an import or deleted a save game. Prevents looping screens.
This commit is contained in:
parent
26432082e2
commit
855a4e622d
@ -27,6 +27,7 @@ async function createWindow(killall) {
|
|||||||
backgroundColor: "#000000",
|
backgroundColor: "#000000",
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nativeWindowOpen: true,
|
nativeWindowOpen: true,
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
38
electron/preload.js
Normal file
38
electron/preload.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
const { ipcRenderer, contextBridge } = require('electron')
|
||||||
|
const log = require("electron-log");
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld(
|
||||||
|
"electronBridge", {
|
||||||
|
send: (channel, data) => {
|
||||||
|
log.log("Send on channel " + channel)
|
||||||
|
// whitelist channels
|
||||||
|
let validChannels = [
|
||||||
|
"get-save-data-response",
|
||||||
|
"get-save-info-response",
|
||||||
|
"push-game-saved",
|
||||||
|
"push-game-ready",
|
||||||
|
"push-import-result",
|
||||||
|
"push-disable-restore",
|
||||||
|
];
|
||||||
|
if (validChannels.includes(channel)) {
|
||||||
|
ipcRenderer.send(channel, data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
receive: (channel, func) => {
|
||||||
|
log.log("Receive on channel " + channel)
|
||||||
|
let validChannels = [
|
||||||
|
"get-save-data-request",
|
||||||
|
"get-save-info-request",
|
||||||
|
"push-save-request",
|
||||||
|
"trigger-save",
|
||||||
|
"trigger-game-export",
|
||||||
|
"trigger-scripts-export",
|
||||||
|
];
|
||||||
|
if (validChannels.includes(channel)) {
|
||||||
|
// Deliberately strip event as it includes `sender`
|
||||||
|
ipcRenderer.on(channel, (event, ...args) => func(...args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
131
src/Electron.tsx
131
src/Electron.tsx
@ -1,10 +1,18 @@
|
|||||||
import { Player } from "./Player";
|
import { Player } from "./Player";
|
||||||
|
import { Router } from "./ui/GameRoot";
|
||||||
|
import { isScriptFilename } from "./Script/isScriptFilename";
|
||||||
|
import { Script } from "./Script/Script";
|
||||||
import { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
|
import { removeLeadingSlash } from "./Terminal/DirectoryHelpers";
|
||||||
import { Terminal } from "./Terminal";
|
import { Terminal } from "./Terminal";
|
||||||
import { SnackbarEvents } from "./ui/React/Snackbar";
|
import { SnackbarEvents } from "./ui/React/Snackbar";
|
||||||
import { IMap, IReturnStatus } from "./types";
|
import { IMap, IReturnStatus } from "./types";
|
||||||
import { GetServer } from "./Server/AllServers";
|
import { GetServer } from "./Server/AllServers";
|
||||||
import { resolve } from "cypress/types/bluebird";
|
import { resolve } from "cypress/types/bluebird";
|
||||||
|
import { ImportPlayerData, SaveData, saveObject } from "./SaveObject";
|
||||||
|
import { Settings } from "./Settings/Settings";
|
||||||
|
import { exportScripts } from "./Terminal/commands/download";
|
||||||
|
import { CONSTANTS } from "./Constants";
|
||||||
|
import { hash } from "./hash/hash";
|
||||||
|
|
||||||
export function initElectron(): void {
|
export function initElectron(): void {
|
||||||
const userAgent = navigator.userAgent.toLowerCase();
|
const userAgent = navigator.userAgent.toLowerCase();
|
||||||
@ -13,6 +21,8 @@ export function initElectron(): void {
|
|||||||
(document as any).achievements = [];
|
(document as any).achievements = [];
|
||||||
initWebserver();
|
initWebserver();
|
||||||
initAppNotifier();
|
initAppNotifier();
|
||||||
|
initSaveFunctions();
|
||||||
|
initElectronBridge();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +119,123 @@ function initAppNotifier(): void {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Will be consumud by the electron wrapper.
|
// Will be consumud by the electron wrapper.
|
||||||
// @ts-ignore
|
(window as any).appNotifier = funcs;
|
||||||
window.appNotifier = funcs;
|
}
|
||||||
|
|
||||||
|
function initSaveFunctions(): void {
|
||||||
|
const funcs = {
|
||||||
|
triggerSave: (): Promise<void> => saveObject.saveGame(true),
|
||||||
|
triggerGameExport: (): void => {
|
||||||
|
try {
|
||||||
|
saveObject.exportGame();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
SnackbarEvents.emit("Could not export game.", "error", 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
triggerScriptsExport: (): void => exportScripts("*", Player.getHomeComputer()),
|
||||||
|
getSaveData: (): { save: string; fileName: string } => {
|
||||||
|
return {
|
||||||
|
save: saveObject.getSaveString(Settings.ExcludeRunningScriptsFromSave),
|
||||||
|
fileName: saveObject.getSaveFileName(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getSaveInfo: async (base64save: string): Promise<ImportPlayerData | undefined> => {
|
||||||
|
try {
|
||||||
|
const data = await saveObject.getImportDataFromString(base64save);
|
||||||
|
return data.playerData;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pushSaveData: (base64save: string, automatic = false): void => Router.toImportSave(base64save, automatic),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Will be consumud by the electron wrapper.
|
||||||
|
(window as any).appSaveFns = funcs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initElectronBridge(): void {
|
||||||
|
const bridge = (window as any).electronBridge as any;
|
||||||
|
if (!bridge) return;
|
||||||
|
|
||||||
|
bridge.receive("get-save-data-request", () => {
|
||||||
|
const data = (window as any).appSaveFns.getSaveData();
|
||||||
|
bridge.send("get-save-data-response", data);
|
||||||
|
});
|
||||||
|
bridge.receive("get-save-info-request", async (save: string) => {
|
||||||
|
const data = await (window as any).appSaveFns.getSaveInfo(save);
|
||||||
|
bridge.send("get-save-info-response", data);
|
||||||
|
});
|
||||||
|
bridge.receive("push-save-request", ({ save, automatic = false }: { save: string; automatic: boolean }) => {
|
||||||
|
(window as any).appSaveFns.pushSaveData(save, automatic);
|
||||||
|
});
|
||||||
|
bridge.receive("trigger-save", () => {
|
||||||
|
return (window as any).appSaveFns
|
||||||
|
.triggerSave()
|
||||||
|
.then(() => {
|
||||||
|
bridge.send("save-completed");
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
SnackbarEvents.emit("Could not save game.", "error", 2000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
bridge.receive("trigger-game-export", () => {
|
||||||
|
try {
|
||||||
|
(window as any).appSaveFns.triggerGameExport();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
SnackbarEvents.emit("Could not export game.", "error", 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bridge.receive("trigger-scripts-export", () => {
|
||||||
|
try {
|
||||||
|
(window as any).appSaveFns.triggerScriptsExport();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
SnackbarEvents.emit("Could not export scripts.", "error", 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushGameSaved(data: SaveData): void {
|
||||||
|
const bridge = (window as any).electronBridge as any;
|
||||||
|
if (!bridge) return;
|
||||||
|
|
||||||
|
bridge.send("push-game-saved", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushGameReady(): void {
|
||||||
|
const bridge = (window as any).electronBridge as any;
|
||||||
|
if (!bridge) return;
|
||||||
|
|
||||||
|
// Send basic information to the electron wrapper
|
||||||
|
bridge.send("push-game-ready", {
|
||||||
|
player: {
|
||||||
|
identifier: Player.identifier,
|
||||||
|
playtime: Player.totalPlaytime,
|
||||||
|
lastSave: Player.lastSave,
|
||||||
|
},
|
||||||
|
game: {
|
||||||
|
version: CONSTANTS.VersionString,
|
||||||
|
hash: hash(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushImportResult(wasImported: boolean): void {
|
||||||
|
const bridge = (window as any).electronBridge as any;
|
||||||
|
if (!bridge) return;
|
||||||
|
|
||||||
|
bridge.send("push-import-result", { wasImported });
|
||||||
|
pushDisableRestore();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pushDisableRestore(): void {
|
||||||
|
const bridge = (window as any).electronBridge as any;
|
||||||
|
if (!bridge) return;
|
||||||
|
|
||||||
|
bridge.send("push-disable-restore", { duration: 1000 * 60 });
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,19 @@ import { PlayerOwnedAugmentation } from "./Augmentation/PlayerOwnedAugmentation"
|
|||||||
import { LocationName } from "./Locations/data/LocationNames";
|
import { LocationName } from "./Locations/data/LocationNames";
|
||||||
import { SxProps } from "@mui/system";
|
import { SxProps } from "@mui/system";
|
||||||
import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
|
import { PlayerObject } from "./PersonObjects/Player/PlayerObject";
|
||||||
|
import { pushGameSaved } from "./Electron";
|
||||||
|
|
||||||
/* SaveObject.js
|
/* SaveObject.js
|
||||||
* Defines the object used to save/load games
|
* Defines the object used to save/load games
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export interface SaveData {
|
||||||
|
playerIdentifier: string;
|
||||||
|
fileName: string;
|
||||||
|
save: string;
|
||||||
|
savedOn: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ImportData {
|
export interface ImportData {
|
||||||
base64: string;
|
base64: string;
|
||||||
parsed: any;
|
parsed: any;
|
||||||
@ -91,11 +99,20 @@ class BitburnerSaveObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveGame(emitToastEvent = true): Promise<void> {
|
saveGame(emitToastEvent = true): Promise<void> {
|
||||||
Player.lastSave = new Date().getTime();
|
const savedOn = new Date().getTime();
|
||||||
|
Player.lastSave = savedOn;
|
||||||
const saveString = this.getSaveString(Settings.ExcludeRunningScriptsFromSave);
|
const saveString = this.getSaveString(Settings.ExcludeRunningScriptsFromSave);
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
save(saveString)
|
save(saveString)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
const saveData: SaveData = {
|
||||||
|
playerIdentifier: Player.identifier,
|
||||||
|
fileName: this.getSaveFileName(),
|
||||||
|
save: saveString,
|
||||||
|
savedOn,
|
||||||
|
};
|
||||||
|
pushGameSaved(saveData);
|
||||||
|
|
||||||
if (emitToastEvent) {
|
if (emitToastEvent) {
|
||||||
SnackbarEvents.emit("Game Saved!", "info", 2000);
|
SnackbarEvents.emit("Game Saved!", "info", 2000);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import { GameRoot } from "./GameRoot";
|
|||||||
import { CONSTANTS } from "../Constants";
|
import { CONSTANTS } from "../Constants";
|
||||||
import { ActivateRecoveryMode } from "./React/RecoveryRoot";
|
import { ActivateRecoveryMode } from "./React/RecoveryRoot";
|
||||||
import { hash } from "../hash/hash";
|
import { hash } from "../hash/hash";
|
||||||
|
import { pushGameReady } from "../Electron";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@ -56,6 +57,7 @@ export function LoadingScreen(): React.ReactElement {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushGameReady();
|
||||||
setLoaded(true);
|
setLoaded(true);
|
||||||
})
|
})
|
||||||
.catch((reason) => {
|
.catch((reason) => {
|
||||||
|
@ -5,6 +5,7 @@ import Button from "@mui/material/Button";
|
|||||||
import { Tooltip } from '@mui/material';
|
import { Tooltip } from '@mui/material';
|
||||||
|
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import { pushDisableRestore } from '../../Electron';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
color?: "primary" | "warning" | "error";
|
color?: "primary" | "warning" | "error";
|
||||||
@ -21,7 +22,10 @@ export function DeleteGameButton({ color = "primary" }: IProps): React.ReactElem
|
|||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
setModalOpened(false);
|
setModalOpened(false);
|
||||||
deleteGame()
|
deleteGame()
|
||||||
.then(() => setTimeout(() => location.reload(), 1000))
|
.then(() => {
|
||||||
|
pushDisableRestore();
|
||||||
|
setTimeout(() => location.reload(), 1000);
|
||||||
|
})
|
||||||
.catch((r) => console.error(`Could not delete game: ${r}`));
|
.catch((r) => console.error(`Could not delete game: ${r}`));
|
||||||
}}
|
}}
|
||||||
open={modalOpened}
|
open={modalOpened}
|
||||||
|
@ -30,7 +30,7 @@ import { Settings } from "../../Settings/Settings";
|
|||||||
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
import { convertTimeMsToTimeElapsedString } from "../../utils/StringHelperFunctions";
|
||||||
import { numeralWrapper } from "../numeralFormat";
|
import { numeralWrapper } from "../numeralFormat";
|
||||||
import { ConfirmationModal } from "./ConfirmationModal";
|
import { ConfirmationModal } from "./ConfirmationModal";
|
||||||
|
import { pushImportResult } from "../../Electron";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
@ -117,11 +117,13 @@ export function ImportSaveRoot({ importString, automatic, onReturning }: ImportS
|
|||||||
|
|
||||||
function handleGoBack(): void {
|
function handleGoBack(): void {
|
||||||
Settings.AutosaveInterval = initialAutosave;
|
Settings.AutosaveInterval = initialAutosave;
|
||||||
|
pushImportResult(false);
|
||||||
onReturning();
|
onReturning();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleImport(): Promise<void> {
|
async function handleImport(): Promise<void> {
|
||||||
await saveObject.importGame(importString, true);
|
await saveObject.importGame(importString, true);
|
||||||
|
pushImportResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user