diff --git a/scripthaus.md b/scripthaus.md index 10a5ad1ee..da296095b 100644 --- a/scripthaus.md +++ b/scripthaus.md @@ -60,7 +60,7 @@ node_modules/.bin/webpack --config webpack.electron.prod.js (cd ../mshell; GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o ../sh2/bin/mshell/mshell-v0.2-darwin.arm64 main-mshell.go) (cd ../mshell; GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o ../sh2/bin/mshell/mshell-v0.2-linux.amd64 main-mshell.go) (cd ../mshell; GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o ../sh2/bin/mshell/mshell-v0.2-linux.arm64 main-mshell.go) -(cd ../sh2-server; GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ../sh2/bin/scripthaus-local-server cmd/main-server.go) +(cd ../sh2-server; GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o ../sh2/bin/prompt-local-server cmd/main-server.go) node_modules/.bin/electron-forge make ``` diff --git a/src/emain.ts b/src/emain.ts index 81648c9fc..7cab14abe 100644 --- a/src/emain.ts +++ b/src/emain.ts @@ -9,12 +9,15 @@ import {handleJsonFetchResponse} from "./util"; import * as winston from "winston"; import * as util from "util"; import {sprintf} from "sprintf-js"; +import {v4 as uuidv4} from "uuid"; const PromptAppPathVarName = "PROMPT_APP_PATH"; +const AuthKeyFile = "prompt.authkey"; let isDev = (process.env.PROMPT_DEV != null); let scHome = getPromptHomeDir(); ensureDir(scHome); let DistDir = (isDev ? "dist-dev" : "dist"); +let GlobalAuthKey = ""; // these are either "darwin/amd64" or "darwin/arm64" // normalize darwin/x64 to darwin/amd64 for GOARCH compatibility @@ -94,6 +97,22 @@ function ensureDir(dir) { fs.mkdirSync(dir, {recursive: true, mode: 0o700}); } +function readAuthKey() { + let homeDir = getPromptHomeDir(); + let authKeyFileName = path.join(homeDir, AuthKeyFile); + if (!fs.existsSync(authKeyFileName)) { + let authKeyStr = String(uuidv4()); + fs.writeFileSync(authKeyFileName, authKeyStr, 0o600); + return authKeyStr; + } + let authKeyData = fs.readFileSync(authKeyFileName); + let authKeyStr = String(authKeyData); + if (authKeyStr == null || authKeyStr == "") { + throw new Error("cannot read authkey"); + } + return authKeyStr.trim(); +} + let app = electron.app; app.setName("Prompt"); @@ -248,7 +267,8 @@ function mainResizeHandler(e) { console.log("resize/move", win.getBounds()); let winSize = {width: bounds.width, height: bounds.height, top: bounds.y, left: bounds.x}; let url = "http://localhost:8080/api/set-winsize"; - fetch(url, {method: "post", body: JSON.stringify(winSize)}).then((resp) => handleJsonFetchResponse(url, resp)).catch((err) => { + let fetchHeaders = getFetchHeaders(); + fetch(url, {method: "post", body: JSON.stringify(winSize), headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).catch((err) => { console.log("error setting winsize", err) }); } @@ -302,6 +322,11 @@ electron.ipcMain.on("get-id", (event) => { return; }); +electron.ipcMain.on("get-authkey", (event) => { + event.returnValue = GlobalAuthKey; + return; +}); + electron.ipcMain.on("local-server-status", (event) => { event.returnValue = (localServerProc != null); return; @@ -327,9 +352,16 @@ function getContextMenu() : any { return menu; } +function getFetchHeaders() { + return { + "x-authkey": GlobalAuthKey, + }; +} + function getClientData() { let url = "http://localhost:8080/api/get-client-data"; - return fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { + let fetchHeaders = getFetchHeaders(); + return fetch(url, {headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { if (data == null) { return null; } @@ -422,6 +454,7 @@ async function sleep(ms) { // ====== MAIN ====== // (async () => { + GlobalAuthKey = readAuthKey(); try { await runLocalServer(); } diff --git a/src/main.tsx b/src/main.tsx index 76040d254..cac5fbf30 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -298,7 +298,8 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width this.rtnStateDiffFetched = true; let usp = new URLSearchParams({sessionid: line.sessionid, cmdid: line.cmdid}); let url = "http://localhost:8080/api/rtnstate?" + usp.toString(); - fetch(url).then((resp) => { + let fetchHeaders = GlobalModel.getFetchHeaders(); + fetch(url, {headers: fetchHeaders}).then((resp) => { if (!resp.ok) { throw new Error(sprintf("Bad fetch response for /api/rtnstate: %d %s", resp.status, resp.statusText)); } diff --git a/src/model.ts b/src/model.ts index 97742c9b3..c97613fd6 100644 --- a/src/model.ts +++ b/src/model.ts @@ -92,6 +92,7 @@ type KeyModsType = { type ElectronApi = { getId : () => string, + getAuthKey : () => string, getLocalServerStatus : () => boolean, restartLocalServer : () => boolean, onTCmd : (callback : (mods : KeyModsType) => void) => void, @@ -1543,10 +1544,12 @@ class Model { debugCmds : number = 0; debugSW : OV = mobx.observable.box(false); localServerRunning : OV; + authKey : string; constructor() { this.clientId = getApi().getId(); - this.ws = new WSControl(this.clientId, (message : any) => this.runUpdate(message, false)); + this.authKey = getApi().getAuthKey(); + this.ws = new WSControl(this.clientId, this.authKey, (message : any) => this.runUpdate(message, false)); this.ws.reconnect(); this.inputModel = new InputModel(); let isLocalServerRunning = getApi().getLocalServerStatus(); @@ -1565,6 +1568,12 @@ class Model { document.addEventListener("keydown", this.docKeyDownHandler.bind(this)); } + getFetchHeaders() : Record { + return { + "x-authkey": this.authKey, + }; + } + docKeyDownHandler(e : any) { if (isModKeyPress(e)) { return; @@ -1979,7 +1988,8 @@ class Model { } } let url = sprintf("http://localhost:8080/api/run-command"); - fetch(url, {method: "post", body: JSON.stringify(cmdPk)}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { + let fetchHeaders = this.getFetchHeaders(); + fetch(url, {method: "post", body: JSON.stringify(cmdPk), headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { mobx.action(() => { let update = data.data; if (update != null) { @@ -2062,7 +2072,8 @@ class Model { this.windows.set(newWin.sessionId + "/" + newWin.windowId, newWin); let usp = new URLSearchParams({sessionid: newWin.sessionId, windowid: newWin.windowId}); let url = new URL("http://localhost:8080/api/get-window?" + usp.toString()); - fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { + let fetchHeaders = GlobalModel.getFetchHeaders(); + fetch(url, {headers: fetchHeaders}).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => { if (data.data == null) { console.log("null window returned from get-window"); return; diff --git a/src/preload.js b/src/preload.js index bdb57e05d..acb16812b 100644 --- a/src/preload.js +++ b/src/preload.js @@ -2,6 +2,7 @@ let {contextBridge, ipcRenderer} = require("electron"); contextBridge.exposeInMainWorld("api", { getId: () => ipcRenderer.sendSync("get-id"), + getAuthKey: () => ipcRenderer.sendSync("get-authkey"), getLocalServerStatus: () => ipcRenderer.sendSync("local-server-status"), restartLocalServer: () => ipcRenderer.sendSync("restart-server"), onTCmd: (callback) => ipcRenderer.on("t-cmd", callback), diff --git a/src/sh2.ts b/src/sh2.ts index 37e57d775..e6cef1471 100644 --- a/src/sh2.ts +++ b/src/sh2.ts @@ -4,7 +4,6 @@ import {createRoot} from 'react-dom/client'; import {sprintf} from "sprintf-js"; import {Terminal} from 'xterm'; import {Main} from "./main"; -import {WSControl} from "./ws"; import {GlobalModel} from "./model"; import {v4 as uuidv4} from "uuid"; @@ -43,4 +42,4 @@ document.addEventListener("DOMContentLoaded", () => { (window as any).mobx = mobx; (window as any).sprintf = sprintf; -console.log("SCRIPTHAUS", VERSION) +console.log("PROMPT", VERSION) diff --git a/src/term.ts b/src/term.ts index 250a32a78..ee71b0b2b 100644 --- a/src/term.ts +++ b/src/term.ts @@ -245,7 +245,8 @@ class TermWrap { this.terminal.reset(); let url = this._getReloadUrl(); let ptyOffset = 0; - fetch(url).then((resp) => { + let fetchHeaders = GlobalModel.getFetchHeaders(); + fetch(url, {headers: fetchHeaders}).then((resp) => { if (!resp.ok) { mobx.action(() => { this.loadError.set(true); })(); this.dataUpdates = []; diff --git a/src/types.ts b/src/types.ts index d759e3bd6..b88f026b8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -203,6 +203,7 @@ type WatchScreenPacketType = { sessionid : string, screenid : string, connect : boolean, + authkey : string, }; type TermWinSize = { diff --git a/src/ws.ts b/src/ws.ts index d7953fea4..a2b248caa 100644 --- a/src/ws.ts +++ b/src/ws.ts @@ -15,10 +15,12 @@ class WSControl { watchSessionId : string = null; watchScreenId : string = null; wsLog : mobx.IObservableArray = mobx.observable.array([], {name: "wsLog"}) + authKey : string; - constructor(clientId : string, messageCallback : (any) => void) { + constructor(clientId : string, authKey : string, messageCallback : (any) => void) { this.messageCallback = messageCallback; this.clientId = clientId; + this.authKey = authKey; this.open = mobx.observable.box(false, {name: "WSOpen"}); setInterval(this.sendPing, 5000); } @@ -174,7 +176,7 @@ class WSControl { } sendWatchScreenPacket(connect : boolean) { - let pk : WatchScreenPacketType = {"type": "watchscreen", connect: connect, sessionid: null, screenid: null}; + let pk : WatchScreenPacketType = {"type": "watchscreen", connect: connect, sessionid: null, screenid: null, authkey: this.authKey}; if (this.watchSessionId != null) { pk.sessionid = this.watchSessionId; }