mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-31 23:11:28 +01:00
Set up electron-builder for new app (#113)
Adds electron-builder, which we will use to package and distribute our application, same as in the existing app. Replaces explicit port assignments with dynamic ones, which are then stored into environment variables. Adds a ~/.w2-dev folder for use when running a dev build. The build-helper pipeline from the old repo is included here too, but it is not updated yet so it will fail. Also removes some redundant utility functions and cleans up some let vs. const usage. The packaging can be run using the `package:prod` and `package:dev` tasks. --------- Co-authored-by: sawka <mike.sawka@gmail.com>
This commit is contained in:
parent
e4204b96d8
commit
8971e2feba
75
.github/workflows/build-helper.yml
vendored
Normal file
75
.github/workflows/build-helper.yml
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
name: "Build Helper"
|
||||
on: workflow_dispatch
|
||||
env:
|
||||
GO_VERSION: "1.22.0"
|
||||
NODE_VERSION: "21.5.0"
|
||||
jobs:
|
||||
runbuild:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: "darwin"
|
||||
arch: "universal"
|
||||
runner: "macos-latest-xlarge"
|
||||
task: "build-package"
|
||||
- platform: "linux"
|
||||
arch: "amd64"
|
||||
runner: "ubuntu-latest"
|
||||
scripthaus: "build-package-linux"
|
||||
- platform: "linux"
|
||||
arch: "arm64"
|
||||
runner: "ubuntu-latest"
|
||||
scripthaus: "build-package-linux"
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: scripthaus-dev/scripthaus
|
||||
path: scripthaus
|
||||
- name: Install Linux Build Dependencies (Linux only)
|
||||
if: matrix.platform == 'linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y libarchive-tools libopenjp2-tools rpm
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{env.GO_VERSION}}
|
||||
cache-dependency-path: |
|
||||
wavesrv/go.sum
|
||||
waveshell/go.sum
|
||||
scripthaus/go.sum
|
||||
- name: Install Scripthaus
|
||||
run: |
|
||||
go work use ./scripthaus;
|
||||
cd scripthaus;
|
||||
go get ./...;
|
||||
CGO_ENABLED=1 go build -o scripthaus cmd/main.go
|
||||
echo $PWD >> $GITHUB_PATH
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{env.NODE_VERSION}}
|
||||
- name: Install yarn
|
||||
run: |
|
||||
corepack enable
|
||||
yarn install
|
||||
- name: Set Version
|
||||
id: set-version
|
||||
run: |
|
||||
VERSION=$(node -e 'console.log(require("./version.js"))')
|
||||
echo "WAVETERM_VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
- name: Build ${{ matrix.platform }}/${{ matrix.arch }}
|
||||
run: scripthaus run ${{ matrix.scripthaus }}
|
||||
env:
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
CSC_LINK: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE}}
|
||||
CSC_KEY_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_CERTIFICATE_PWD }}
|
||||
APPLE_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_PWD }}
|
||||
APPLE_TEAM_ID: ${{ matrix.platform == 'darwin' && secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
|
||||
- name: Upload to S3 staging
|
||||
run: aws s3 cp make/ s3://waveterm-github-artifacts/staging/${{ steps.set-version.outputs.WAVETERM_VERSION }}/ --recursive --exclude "*/*" --exclude "builder-*.yml"
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: "${{ secrets.S3_USERID }}"
|
||||
AWS_SECRET_ACCESS_KEY: "${{ secrets.S3_SECRETKEY }}"
|
||||
AWS_DEFAULT_REGION: us-west-2
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,6 +13,7 @@ bin/
|
||||
.DS_Store
|
||||
*~
|
||||
out/
|
||||
make/
|
||||
|
||||
# Yarn Modern
|
||||
.pnp.*
|
||||
|
108
Taskfile.yml
108
Taskfile.yml
@ -10,6 +10,39 @@ vars:
|
||||
sh: node version.cjs
|
||||
|
||||
tasks:
|
||||
|
||||
package:dev:
|
||||
cmds:
|
||||
- yarn build:dev && yarn electron-builder -c electron-builder.config.cjs -p never
|
||||
deps:
|
||||
- generate
|
||||
- build:server
|
||||
- build:wsh
|
||||
|
||||
package:prod:
|
||||
cmds:
|
||||
- yarn build:prod && yarn electron-builder -c electron-builder.config.cjs -p never
|
||||
deps:
|
||||
- generate
|
||||
- build:server
|
||||
- build:wsh
|
||||
|
||||
electron:dev:
|
||||
cmds:
|
||||
- WAVETERM_DEV=1 yarn dev
|
||||
deps:
|
||||
- generate
|
||||
- build:server
|
||||
- build:wsh
|
||||
|
||||
electron:start:
|
||||
cmds:
|
||||
- WAVETERM_DEV=1 yarn start
|
||||
deps:
|
||||
- generate
|
||||
- build:server
|
||||
- build:wsh
|
||||
|
||||
generate:
|
||||
cmds:
|
||||
- go run cmd/generate/main-generate.go
|
||||
@ -20,41 +53,78 @@ tasks:
|
||||
- "pkg/wstore/*.go"
|
||||
- "pkg/wshrpc/**/*.go"
|
||||
|
||||
electron:dev:
|
||||
cmds:
|
||||
- WAVETERM_DEV=1 yarn dev
|
||||
deps:
|
||||
- build:server
|
||||
- build:wsh
|
||||
|
||||
electron:start:
|
||||
cmds:
|
||||
- WAVETERM_DEV=1 yarn start
|
||||
deps:
|
||||
- build:server
|
||||
- build:wsh
|
||||
|
||||
build:server:
|
||||
deps:
|
||||
- task: build:server:internal
|
||||
vars:
|
||||
GOARCH: arm64
|
||||
- task: build:server:internal
|
||||
vars:
|
||||
GOARCH: amd64
|
||||
|
||||
build:server:internal:
|
||||
requires:
|
||||
vars:
|
||||
- GOARCH
|
||||
cmds:
|
||||
- go build -o dist/bin/wavesrv{{exeExt}} cmd/server/main-server.go
|
||||
- CGO_ENABLED=1 GOARCH={{.GOARCH}} go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflags "-X main.BuildTime=$(date +'%Y%m%d%H%M') -X main.WaveVersion={{.VERSION}}" -o dist/bin/wavesrv.{{.GOARCH}}{{exeExt}} cmd/server/main-server.go
|
||||
sources:
|
||||
- "cmd/server/*.go"
|
||||
- "pkg/**/*.go"
|
||||
generates:
|
||||
- dist/bin/wavesrv{{exeExt}}
|
||||
- dist/bin/wavesrv.{{.GOARCH}}{{exeExt}}
|
||||
deps:
|
||||
- go:mod:tidy
|
||||
internal: true
|
||||
|
||||
build:wsh:
|
||||
cmds:
|
||||
- go build -o dist/bin/wsh{{exeExt}} cmd/wsh/main-wsh.go
|
||||
deps:
|
||||
- task: build:wsh:internal
|
||||
vars:
|
||||
GOOS: darwin
|
||||
GOARCH: arm64
|
||||
- task: build:wsh:internal
|
||||
vars:
|
||||
GOOS: darwin
|
||||
GOARCH: amd64
|
||||
- task: build:wsh:internal
|
||||
vars:
|
||||
GOOS: linux
|
||||
GOARCH: arm64
|
||||
- task: build:wsh:internal
|
||||
vars:
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
- task: build:wsh:internal
|
||||
vars:
|
||||
GOOS: windows
|
||||
GOARCH: amd64
|
||||
- task: build:wsh:internal
|
||||
vars:
|
||||
GOOS: windows
|
||||
GOARCH: arm64
|
||||
|
||||
build:wsh:internal:
|
||||
vars:
|
||||
GO_LDFLAGS:
|
||||
sh: echo "-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')"
|
||||
EXT:
|
||||
sh: echo {{if eq .GOOS "windows"}}.exe{{end}}
|
||||
requires:
|
||||
vars:
|
||||
- GOOS
|
||||
- GOARCH
|
||||
- VERSION
|
||||
sources:
|
||||
- "cmd/wsh/**/*.go"
|
||||
- "pkg/**/*.go"
|
||||
generates:
|
||||
- dist/bin/wsh{{exeExt}}
|
||||
- dist/bin/wsh-{{.VERSION}}-{{.GOOS}}.{{.GOARCH}}{{.EXT}}
|
||||
cmds:
|
||||
- (CGO_ENABLED=0 GOOS={{.GOOS}} GOARCH={{.GOARCH}} go build -ldflags="{{.GO_LDFLAGS}}" -o dist/bin/wsh-{{.VERSION}}-{{.GOOS}}.{{.GOARCH}}{{.EXT}} cmd/wsh/main-wsh.go)
|
||||
deps:
|
||||
- go:mod:tidy
|
||||
internal: true
|
||||
|
||||
go:mod:tidy:
|
||||
summary: Runs `go mod tidy`
|
||||
|
@ -124,12 +124,17 @@ func main() {
|
||||
|
||||
go stdinReadWatch()
|
||||
configWatcher()
|
||||
go web.RunWebSocketServer()
|
||||
webListener, err := web.MakeTCPListener()
|
||||
webListener, err := web.MakeTCPListener("web")
|
||||
if err != nil {
|
||||
log.Printf("error creating web listener: %v\n", err)
|
||||
return
|
||||
}
|
||||
wsListener, err := web.MakeTCPListener("websocket")
|
||||
if err != nil {
|
||||
log.Printf("error creating websocket listener: %v\n", err)
|
||||
return
|
||||
}
|
||||
go web.RunWebSocketServer(wsListener)
|
||||
unixListener, err := web.MakeUnixListener()
|
||||
if err != nil {
|
||||
log.Printf("error creating unix listener: %v\n", err)
|
||||
@ -141,7 +146,7 @@ func main() {
|
||||
_, err := strconv.Atoi(pidStr)
|
||||
if err == nil {
|
||||
// use fmt instead of log here to make sure it goes directly to stderr
|
||||
fmt.Fprintf(os.Stderr, "WAVESRV-ESTART\n")
|
||||
fmt.Fprintf(os.Stderr, "WAVESRV-ESTART ws:%s web:%s\n", wsListener.Addr(), webListener.Addr())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
79
electron-builder.config.cjs
Normal file
79
electron-builder.config.cjs
Normal file
@ -0,0 +1,79 @@
|
||||
const pkg = require("./package.json");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* @type {import('electron-builder').Configuration}
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
*/
|
||||
const config = {
|
||||
appId: pkg.build.appId,
|
||||
productName: pkg.productName,
|
||||
artifactName: "${productName}-${platform}-${arch}-${version}.${ext}",
|
||||
npmRebuild: false,
|
||||
nodeGypRebuild: false,
|
||||
electronCompile: false,
|
||||
files: [
|
||||
{
|
||||
from: "./dist",
|
||||
to: "./dist",
|
||||
filter: ["**/*"],
|
||||
},
|
||||
{
|
||||
from: ".",
|
||||
to: ".",
|
||||
filter: ["package.json"],
|
||||
},
|
||||
],
|
||||
directories: {
|
||||
output: "make",
|
||||
},
|
||||
asarUnpack: ["dist/bin/**/*"],
|
||||
mac: {
|
||||
target: [
|
||||
{
|
||||
target: "zip",
|
||||
arch: "universal",
|
||||
},
|
||||
{
|
||||
target: "dmg",
|
||||
arch: "universal",
|
||||
},
|
||||
],
|
||||
icon: "build/icons.icns",
|
||||
category: "public.app-category.developer-tools",
|
||||
minimumSystemVersion: "10.15.0",
|
||||
notarize: process.env.APPLE_TEAM_ID
|
||||
? {
|
||||
teamId: process.env.APPLE_TEAM_ID,
|
||||
}
|
||||
: false,
|
||||
binaries: fs
|
||||
.readdirSync("dist/bin", { recursive: true, withFileTypes: true })
|
||||
.filter((f) => f.isFile() && (f.name.startsWith("wavesrv") || f.name.includes("darwin")))
|
||||
.map((f) => path.resolve(f.path, f.name)),
|
||||
},
|
||||
linux: {
|
||||
executableName: pkg.productName,
|
||||
category: "TerminalEmulator",
|
||||
icon: "build/icons.icns",
|
||||
target: ["zip", "deb", "rpm", "AppImage", "pacman"],
|
||||
synopsis: pkg.description,
|
||||
description: null,
|
||||
desktop: {
|
||||
Name: pkg.productName,
|
||||
Comment: pkg.description,
|
||||
Keywords: "developer;terminal;emulator;",
|
||||
category: "Development;Utility;",
|
||||
},
|
||||
},
|
||||
appImage: {
|
||||
license: "LICENSE",
|
||||
},
|
||||
publish: {
|
||||
provider: "generic",
|
||||
url: "https://dl.waveterm.dev/releases",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
144
emain/emain.ts
144
emain/emain.ts
@ -8,32 +8,34 @@ import os from "os";
|
||||
import * as path from "path";
|
||||
import * as readline from "readline";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { getBackendHostPort } from "../frontend/app/store/global";
|
||||
import * as util from "util";
|
||||
import winston from "winston";
|
||||
import * as services from "../frontend/app/store/services";
|
||||
import * as keyutil from "../frontend/util/keyutil";
|
||||
import { fireAndForget } from "../frontend/util/util";
|
||||
|
||||
import { getServerWebEndpoint, WebServerEndpointVarName, WSServerEndpointVarName } from "@/util/endpoints";
|
||||
import { WaveDevVarName, WaveDevViteVarName } from "@/util/isdev";
|
||||
import { sprintf } from "sprintf-js";
|
||||
const electronApp = electron.app;
|
||||
const isDev = process.env.WAVETERM_DEV;
|
||||
const isDevServer = !electronApp.isPackaged && process.env.ELECTRON_RENDERER_URL;
|
||||
|
||||
const WaveAppPathVarName = "WAVETERM_APP_PATH";
|
||||
const WaveDevVarName = "WAVETERM_DEV";
|
||||
const WaveSrvReadySignalPidVarName = "WAVETERM_READY_SIGNAL_PID";
|
||||
const AuthKeyFile = "waveterm.authkey";
|
||||
const DevServerEndpoint = "http://127.0.0.1:8190";
|
||||
const ProdServerEndpoint = "http://127.0.0.1:1719";
|
||||
electron.nativeTheme.themeSource = "dark";
|
||||
|
||||
type WaveBrowserWindow = Electron.BrowserWindow & { waveWindowId: string; readyPromise: Promise<void> };
|
||||
|
||||
let waveSrvReadyResolve = (value: boolean) => {};
|
||||
let waveSrvReady: Promise<boolean> = new Promise((resolve, _) => {
|
||||
const waveSrvReady: Promise<boolean> = new Promise((resolve, _) => {
|
||||
waveSrvReadyResolve = resolve;
|
||||
});
|
||||
let globalIsQuitting = false;
|
||||
let globalIsStarting = true;
|
||||
|
||||
const isDev = !electron.app.isPackaged;
|
||||
const isDevVite = isDev && process.env.ELECTRON_RENDERER_URL;
|
||||
|
||||
let waveSrvProc: child_process.ChildProcessWithoutNullStreams | null = null;
|
||||
electronApp.setName(isDev ? "NextWave (Dev)" : "NextWave");
|
||||
const unamePlatform = process.platform;
|
||||
@ -43,16 +45,50 @@ if (unameArch == "x64") {
|
||||
}
|
||||
keyutil.setKeyUtilPlatform(unamePlatform);
|
||||
|
||||
function getBaseHostPort(): string {
|
||||
if (isDev) {
|
||||
return DevServerEndpoint;
|
||||
}
|
||||
return ProdServerEndpoint;
|
||||
}
|
||||
|
||||
// must match golang
|
||||
function getWaveHomeDir() {
|
||||
return path.join(os.homedir(), ".w2");
|
||||
return path.join(os.homedir(), isDev ? ".w2-dev" : ".w2");
|
||||
}
|
||||
|
||||
const waveHome = getWaveHomeDir();
|
||||
|
||||
const oldConsoleLog = console.log;
|
||||
|
||||
const loggerTransports: winston.transport[] = [
|
||||
new winston.transports.File({ filename: path.join(waveHome, "waveterm-app.log"), level: "info" }),
|
||||
];
|
||||
if (isDev) {
|
||||
loggerTransports.push(new winston.transports.Console());
|
||||
}
|
||||
const loggerConfig = {
|
||||
level: "info",
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||
winston.format.printf((info) => `${info.timestamp} ${info.message}`)
|
||||
),
|
||||
transports: loggerTransports,
|
||||
};
|
||||
const logger = winston.createLogger(loggerConfig);
|
||||
function log(...msg: any[]) {
|
||||
try {
|
||||
logger.info(util.format(...msg));
|
||||
} catch (e) {
|
||||
oldConsoleLog(...msg);
|
||||
}
|
||||
}
|
||||
console.log = log;
|
||||
console.log(
|
||||
sprintf(
|
||||
"waveterm-app starting, WAVETERM_HOME=%s, electronpath=%s gopath=%s arch=%s/%s",
|
||||
waveHome,
|
||||
getElectronAppBasePath(),
|
||||
getGoAppBasePath(),
|
||||
unamePlatform,
|
||||
unameArch
|
||||
)
|
||||
);
|
||||
if (isDev) {
|
||||
console.log("waveterm-app WAVETERM_DEV set");
|
||||
}
|
||||
|
||||
function getElectronAppBasePath(): string {
|
||||
@ -60,20 +96,18 @@ function getElectronAppBasePath(): string {
|
||||
}
|
||||
|
||||
function getGoAppBasePath(): string {
|
||||
const appDir = getElectronAppBasePath();
|
||||
if (appDir.endsWith(".asar")) {
|
||||
return `${appDir}.unpacked`;
|
||||
} else {
|
||||
return appDir;
|
||||
}
|
||||
return getElectronAppBasePath().replace("app.asar", "app.asar.unpacked");
|
||||
}
|
||||
|
||||
const wavesrvBinName = `wavesrv.${unameArch}`;
|
||||
|
||||
function getWaveSrvPath(): string {
|
||||
return path.join(getGoAppBasePath(), "bin", "wavesrv");
|
||||
return path.join(getGoAppBasePath(), "bin", wavesrvBinName);
|
||||
}
|
||||
|
||||
function getWaveSrvPathWin(): string {
|
||||
const appPath = path.join(getGoAppBasePath(), "bin", "wavesrv.exe");
|
||||
const winBinName = `${wavesrvBinName}.exe`;
|
||||
const appPath = path.join(getGoAppBasePath(), "bin", winBinName);
|
||||
return `& "${appPath}"`;
|
||||
}
|
||||
|
||||
@ -93,11 +127,16 @@ function runWaveSrv(): Promise<boolean> {
|
||||
pResolve = argResolve;
|
||||
pReject = argReject;
|
||||
});
|
||||
if (isDev) {
|
||||
process.env[WaveDevVarName] = "1";
|
||||
}
|
||||
|
||||
if (isDevVite) {
|
||||
process.env[WaveDevViteVarName] = "1";
|
||||
}
|
||||
|
||||
const envCopy = { ...process.env };
|
||||
envCopy[WaveAppPathVarName] = getGoAppBasePath();
|
||||
if (isDev) {
|
||||
envCopy[WaveDevVarName] = "1";
|
||||
}
|
||||
envCopy[WaveSrvReadySignalPidVarName] = process.pid.toString();
|
||||
let waveSrvCmd: string;
|
||||
if (process.platform === "win32") {
|
||||
@ -139,6 +178,14 @@ function runWaveSrv(): Promise<boolean> {
|
||||
});
|
||||
rlStderr.on("line", (line) => {
|
||||
if (line.includes("WAVESRV-ESTART")) {
|
||||
const addrs = /ws:([a-z0-9.:]+) web:([a-z0-9.:]+)/gm.exec(line);
|
||||
if (addrs == null) {
|
||||
console.log("error parsing WAVESRV-ESTART line", line);
|
||||
electron.app.quit();
|
||||
return;
|
||||
}
|
||||
process.env[WSServerEndpointVarName] = addrs[1];
|
||||
process.env[WebServerEndpointVarName] = addrs[2];
|
||||
waveSrvReadyResolve(true);
|
||||
return;
|
||||
}
|
||||
@ -159,12 +206,12 @@ function runWaveSrv(): Promise<boolean> {
|
||||
|
||||
async function handleWSEvent(evtMsg: WSEventType) {
|
||||
if (evtMsg.eventtype == "electron:newwindow") {
|
||||
let windowId: string = evtMsg.data;
|
||||
let windowData: WaveWindow = (await services.ObjectService.GetObject("window:" + windowId)) as WaveWindow;
|
||||
const windowId: string = evtMsg.data;
|
||||
const windowData: WaveWindow = (await services.ObjectService.GetObject("window:" + windowId)) as WaveWindow;
|
||||
if (windowData == null) {
|
||||
return;
|
||||
}
|
||||
let clientData = await services.ClientService.GetClientData();
|
||||
const clientData = await services.ClientService.GetClientData();
|
||||
const newWin = createBrowserWindow(clientData.oid, windowData);
|
||||
await newWin.readyPromise;
|
||||
newWin.show();
|
||||
@ -221,7 +268,7 @@ function shFrameNavHandler(event: Electron.Event<Electron.WebContentsWillFrameNa
|
||||
}
|
||||
if (
|
||||
event.frame.name == "pdfview" &&
|
||||
(url.startsWith("blob:file:///") || url.startsWith(getBaseHostPort() + "/wave/stream-file?"))
|
||||
(url.startsWith("blob:file:///") || url.startsWith(getServerWebEndpoint() + "/wave/stream-file?"))
|
||||
) {
|
||||
// allowed
|
||||
return;
|
||||
@ -266,12 +313,11 @@ function createBrowserWindow(clientId: string, waveWindow: WaveWindow): WaveBrow
|
||||
readyResolve = resolve;
|
||||
});
|
||||
const win: WaveBrowserWindow = bwin as WaveBrowserWindow;
|
||||
// const indexHtml = isDev ? "index-dev.html" : "index.html";
|
||||
let usp = new URLSearchParams();
|
||||
const usp = new URLSearchParams();
|
||||
usp.set("clientid", clientId);
|
||||
usp.set("windowid", waveWindow.oid);
|
||||
const indexHtml = "index.html";
|
||||
if (isDevServer) {
|
||||
if (isDevVite) {
|
||||
console.log("running as dev server");
|
||||
win.loadURL(`${process.env.ELECTRON_RENDERER_URL}/index.html?${usp.toString()}`);
|
||||
} else {
|
||||
@ -343,7 +389,7 @@ function isWindowFullyVisible(bounds: electron.Rectangle): boolean {
|
||||
|
||||
// Helper function to check if a point is inside any display
|
||||
function isPointInDisplay(x, y) {
|
||||
for (let display of displays) {
|
||||
for (const display of displays) {
|
||||
const { x: dx, y: dy, width, height } = display.bounds;
|
||||
if (x >= dx && x < dx + width && y >= dy && y < dy + height) {
|
||||
return true;
|
||||
@ -418,18 +464,9 @@ function ensureBoundsAreVisible(bounds: electron.Rectangle): electron.Rectangle
|
||||
return bounds;
|
||||
}
|
||||
|
||||
electron.ipcMain.on("isDev", (event) => {
|
||||
event.returnValue = isDev;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("isDevServer", (event) => {
|
||||
event.returnValue = isDevServer;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("getPlatform", (event, url) => {
|
||||
event.returnValue = unamePlatform;
|
||||
});
|
||||
|
||||
// Listen for the open-external event from the renderer process
|
||||
electron.ipcMain.on("open-external", (event, url) => {
|
||||
if (url && typeof url === "string") {
|
||||
@ -443,8 +480,7 @@ electron.ipcMain.on("open-external", (event, url) => {
|
||||
|
||||
electron.ipcMain.on("download", (event, payload) => {
|
||||
const window = electron.BrowserWindow.fromWebContents(event.sender);
|
||||
const baseName = payload.filePath.split(/[\\/]/).pop();
|
||||
const streamingUrl = getBackendHostPort() + "/wave/stream-file?path=" + encodeURIComponent(payload.filePath);
|
||||
const streamingUrl = getServerWebEndpoint() + "/wave/stream-file?path=" + encodeURIComponent(payload.filePath);
|
||||
window.webContents.downloadURL(streamingUrl);
|
||||
});
|
||||
|
||||
@ -459,8 +495,12 @@ electron.ipcMain.on("getCursorPoint", (event) => {
|
||||
event.returnValue = retVal;
|
||||
});
|
||||
|
||||
electron.ipcMain.on("getEnv", (event, varName) => {
|
||||
event.returnValue = process.env[varName] ?? null;
|
||||
});
|
||||
|
||||
async function createNewWaveWindow() {
|
||||
let clientData = await services.ClientService.GetClientData();
|
||||
const clientData = await services.ClientService.GetClientData();
|
||||
const newWindow = await services.ClientService.MakeWindow();
|
||||
const newBrowserWindow = createBrowserWindow(clientData.oid, newWindow);
|
||||
newBrowserWindow.show();
|
||||
@ -498,7 +538,7 @@ function convertMenuDefArrToMenu(menuDefArr: ElectronContextMenuItem[]): electro
|
||||
}
|
||||
|
||||
function makeAppMenu() {
|
||||
let fileMenu: Electron.MenuItemConstructorOptions[] = [];
|
||||
const fileMenu: Electron.MenuItemConstructorOptions[] = [];
|
||||
fileMenu.push({
|
||||
label: "New Window",
|
||||
accelerator: "CommandOrControl+N",
|
||||
@ -557,12 +597,12 @@ async function appMain() {
|
||||
const ready = await waveSrvReady;
|
||||
console.log("wavesrv ready signal received", ready, Date.now() - startTs, "ms");
|
||||
console.log("get client data");
|
||||
let clientData = await services.ClientService.GetClientData();
|
||||
const clientData = await services.ClientService.GetClientData();
|
||||
console.log("client data ready");
|
||||
await electronApp.whenReady();
|
||||
let wins: WaveBrowserWindow[] = [];
|
||||
for (let windowId of clientData.windowids.slice().reverse()) {
|
||||
let windowData: WaveWindow = (await services.ObjectService.GetObject("window:" + windowId)) as WaveWindow;
|
||||
const wins: WaveBrowserWindow[] = [];
|
||||
for (const windowId of clientData.windowids.slice().reverse()) {
|
||||
const windowData: WaveWindow = (await services.ObjectService.GetObject("window:" + windowId)) as WaveWindow;
|
||||
if (windowData == null) {
|
||||
services.WindowService.CloseWindow(windowId).catch((e) => {
|
||||
/* ignore */
|
||||
@ -572,7 +612,7 @@ async function appMain() {
|
||||
const win = createBrowserWindow(clientData.oid, windowData);
|
||||
wins.push(win);
|
||||
}
|
||||
for (let win of wins) {
|
||||
for (const win of wins) {
|
||||
await win.readyPromise;
|
||||
console.log("show", win.waveWindowId);
|
||||
win.show();
|
||||
|
@ -4,8 +4,6 @@
|
||||
let { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("api", {
|
||||
isDev: () => ipcRenderer.sendSync("isDev"),
|
||||
isDevServer: () => ipcRenderer.sendSync("isDevServer"),
|
||||
getPlatform: () => ipcRenderer.sendSync("getPlatform"),
|
||||
getCursorPoint: () => ipcRenderer.sendSync("getCursorPoint"),
|
||||
openNewWindow: () => ipcRenderer.send("openNewWindow"),
|
||||
@ -19,6 +17,7 @@ contextBridge.exposeInMainWorld("api", {
|
||||
console.error("Invalid URL passed to openExternal:", url);
|
||||
}
|
||||
},
|
||||
getEnv: (varName) => ipcRenderer.sendSync("getEnv", varName),
|
||||
});
|
||||
|
||||
// Custom event for "new-window"
|
||||
|
@ -6,6 +6,7 @@ import { getLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom";
|
||||
import { layoutTreeStateReducer } from "@/faraday/lib/layoutState";
|
||||
|
||||
import { handleIncomingRpcMessage } from "@/app/store/wshrpc";
|
||||
import { getServerWebEndpoint, getServerWSEndpoint } from "@/util/endpoints";
|
||||
import * as layoututil from "@/util/layoututil";
|
||||
import { produce } from "immer";
|
||||
import * as jotai from "jotai";
|
||||
@ -193,15 +194,6 @@ function useBlockAtom<T>(blockId: string, name: string, makeFn: () => jotai.Atom
|
||||
return atom as jotai.Atom<T>;
|
||||
}
|
||||
|
||||
function getBackendHostPort(): string {
|
||||
// TODO deal with dev/production
|
||||
return "http://127.0.0.1:8190";
|
||||
}
|
||||
|
||||
function getBackendWSHostPort(): string {
|
||||
return "ws://127.0.0.1:8191";
|
||||
}
|
||||
|
||||
let globalWS: WSControl = null;
|
||||
|
||||
function handleWSEventMessage(msg: WSEventType) {
|
||||
@ -278,7 +270,7 @@ function handleWSMessage(msg: any) {
|
||||
}
|
||||
|
||||
function initWS() {
|
||||
globalWS = new WSControl(getBackendWSHostPort(), globalStore, globalWindowId, "", (msg) => {
|
||||
globalWS = new WSControl(getServerWSEndpoint(), globalStore, globalWindowId, "", (msg) => {
|
||||
handleWSMessage(msg);
|
||||
});
|
||||
globalWS.connectNow("initWS");
|
||||
@ -332,7 +324,7 @@ async function fetchWaveFile(
|
||||
if (offset != null) {
|
||||
usp.set("offset", offset.toString());
|
||||
}
|
||||
const resp = await fetch(getBackendHostPort() + "/wave/file?" + usp.toString());
|
||||
const resp = await fetch(getServerWebEndpoint() + "/wave/file?" + usp.toString());
|
||||
if (!resp.ok) {
|
||||
if (resp.status === 404) {
|
||||
return { data: null, fileInfo: null };
|
||||
@ -375,13 +367,10 @@ function getObjectId(obj: any): number {
|
||||
}
|
||||
|
||||
export {
|
||||
PLATFORM,
|
||||
WOS,
|
||||
atoms,
|
||||
createBlock,
|
||||
fetchWaveFile,
|
||||
getApi,
|
||||
getBackendHostPort,
|
||||
getEventORefSubject,
|
||||
getEventSubject,
|
||||
getFileSubject,
|
||||
@ -389,10 +378,12 @@ export {
|
||||
globalStore,
|
||||
globalWS,
|
||||
initWS,
|
||||
PLATFORM,
|
||||
sendWSCommand,
|
||||
setBlockFocus,
|
||||
setPlatform,
|
||||
useBlockAtom,
|
||||
useBlockCache,
|
||||
useSettingsAtom,
|
||||
WOS,
|
||||
};
|
||||
|
@ -4,10 +4,11 @@
|
||||
// WaveObjectStore
|
||||
|
||||
import { sendRpcCommand } from "@/app/store/wshrpc";
|
||||
import { getServerWebEndpoint } from "@/util/endpoints";
|
||||
import * as jotai from "jotai";
|
||||
import * as React from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { atoms, getBackendHostPort, globalStore } from "./global";
|
||||
import { atoms, globalStore } from "./global";
|
||||
import * as services from "./services";
|
||||
|
||||
const IsElectron = true;
|
||||
@ -67,22 +68,23 @@ function callBackendService(service: string, method: string, args: any[], noUICo
|
||||
if (!noUIContext) {
|
||||
uiContext = globalStore.get(atoms.uiContext);
|
||||
}
|
||||
let waveCall: WebCallType = {
|
||||
const waveCall: WebCallType = {
|
||||
service: service,
|
||||
method: method,
|
||||
args: args,
|
||||
uicontext: uiContext,
|
||||
};
|
||||
// usp is just for debugging (easier to filter URLs)
|
||||
let methodName = service + "." + method;
|
||||
let usp = new URLSearchParams();
|
||||
const methodName = `${service}.${method}`;
|
||||
const usp = new URLSearchParams();
|
||||
usp.set("service", service);
|
||||
usp.set("method", method);
|
||||
let fetchPromise = fetch(getBackendHostPort() + "/wave/service?" + usp.toString(), {
|
||||
const url = getServerWebEndpoint() + "/wave/service?" + usp.toString();
|
||||
const fetchPromise = fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(waveCall),
|
||||
});
|
||||
let prtn = fetchPromise
|
||||
const prtn = fetchPromise
|
||||
.then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error(`call ${methodName} failed: ${resp.status} ${resp.statusText}`);
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
import { WindowDrag } from "@/element/windowdrag";
|
||||
import { deleteLayoutStateAtomForTab } from "@/faraday/lib/layoutAtom";
|
||||
import { debounce } from "@/faraday/lib/utils";
|
||||
import { atoms } from "@/store/global";
|
||||
import * as services from "@/store/services";
|
||||
import { useAtomValue } from "jotai";
|
||||
@ -12,6 +11,7 @@ import React, { createRef, useCallback, useEffect, useRef, useState } from "reac
|
||||
|
||||
import { Tab } from "./tab";
|
||||
|
||||
import { debounce } from "throttle-debounce";
|
||||
import "./tabbar.less";
|
||||
|
||||
const TAB_DEFAULT_WIDTH = 130;
|
||||
@ -111,7 +111,7 @@ const TabBar = React.memo(({ workspace }: TabBarProps) => {
|
||||
|
||||
// const debouncedSetTabWidth = debounce((width) => setTabWidth(width), 100);
|
||||
// const debouncedSetScrollable = debounce((scrollable) => setScrollable(scrollable), 100);
|
||||
const debouncedUpdateTabPositions = debounce(() => updateTabPositions(), 100);
|
||||
const debouncedUpdateTabPositions = debounce(100, () => updateTabPositions());
|
||||
|
||||
const handleResizeTabs = useCallback(() => {
|
||||
const tabBar = tabBarRef.current;
|
||||
|
@ -3,9 +3,10 @@
|
||||
|
||||
import { ContextMenuModel } from "@/app/store/contextmenu";
|
||||
import { Markdown } from "@/element/markdown";
|
||||
import { getBackendHostPort, globalStore, useBlockAtom } from "@/store/global";
|
||||
import { globalStore, useBlockAtom } from "@/store/global";
|
||||
import * as services from "@/store/services";
|
||||
import * as WOS from "@/store/wos";
|
||||
import { getServerWebEndpoint } from "@/util/endpoints";
|
||||
import * as util from "@/util/util";
|
||||
import clsx from "clsx";
|
||||
import * as jotai from "jotai";
|
||||
@ -274,7 +275,7 @@ function MarkdownPreview({ contentAtom }: { contentAtom: jotai.Atom<Promise<stri
|
||||
|
||||
function StreamingPreview({ fileInfo }: { fileInfo: FileInfo }) {
|
||||
const filePath = fileInfo.path;
|
||||
const streamingUrl = getBackendHostPort() + "/wave/stream-file?path=" + encodeURIComponent(filePath);
|
||||
const streamingUrl = getServerWebEndpoint() + "/wave/stream-file?path=" + encodeURIComponent(filePath);
|
||||
if (fileInfo.mimetype == "application/pdf") {
|
||||
return (
|
||||
<div className="view-preview view-preview-pdf">
|
||||
|
@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { WshServer } from "@/app/store/wshserver";
|
||||
import { createBlock, getBackendHostPort } from "@/store/global";
|
||||
import { createBlock } from "@/store/global";
|
||||
import { getServerWebEndpoint } from "@/util/endpoints";
|
||||
import clsx from "clsx";
|
||||
import * as jotai from "jotai";
|
||||
import * as React from "react";
|
||||
@ -116,7 +117,7 @@ function TermSticker({ sticker, config }: { sticker: StickerType; config: Sticke
|
||||
if (sticker.imgsrc == null) {
|
||||
return null;
|
||||
}
|
||||
const streamingUrl = getBackendHostPort() + "/wave/stream-file?path=" + encodeURIComponent(sticker.imgsrc);
|
||||
const streamingUrl = getServerWebEndpoint() + "/wave/stream-file?path=" + encodeURIComponent(sticker.imgsrc);
|
||||
return (
|
||||
<div className="term-sticker term-sticker-image" style={style} onClick={clickHandler}>
|
||||
<img src={streamingUrl} />
|
||||
|
@ -1,8 +1,9 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { getCrypto } from "@/util/util";
|
||||
import { DefaultNodeSize, LayoutNode } from "./model";
|
||||
import { FlexDirection, getCrypto, reverseFlexDirection } from "./utils";
|
||||
import { FlexDirection, reverseFlexDirection } from "./utils";
|
||||
|
||||
const crypto = getCrypto();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright 2024, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { lazy } from "@/util/util";
|
||||
import {
|
||||
addChildAt,
|
||||
addIntermediateNode,
|
||||
@ -25,7 +26,7 @@ import {
|
||||
LayoutTreeSwapNodeAction,
|
||||
MoveOperation,
|
||||
} from "./model";
|
||||
import { DropDirection, FlexDirection, lazy } from "./utils";
|
||||
import { DropDirection, FlexDirection } from "./utils";
|
||||
|
||||
/**
|
||||
* Initializes a layout tree state.
|
||||
|
@ -97,43 +97,3 @@ export function setTransform({ top, left, width, height }: Dimensions, setSize:
|
||||
position: "absolute",
|
||||
};
|
||||
}
|
||||
|
||||
export const debounce = <T extends (...args: any[]) => any>(callback: T, waitFor: number) => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return (...args: Parameters<T>): ReturnType<T> => {
|
||||
let result: any;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
result = callback(...args);
|
||||
}, waitFor);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple wrapper function that lazily evaluates the provided function and caches its result for future calls.
|
||||
* @param callback The function to lazily run.
|
||||
* @returns The result of the function.
|
||||
*/
|
||||
export const lazy = <T extends (...args: any[]) => any>(callback: T) => {
|
||||
let res: ReturnType<T>;
|
||||
let processed = false;
|
||||
return (...args: Parameters<T>): ReturnType<T> => {
|
||||
if (processed) return res;
|
||||
res = callback(...args);
|
||||
processed = true;
|
||||
return res;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Workaround for NodeJS compatibility. Will attempt to resolve the Crypto API from the browser and fallback to NodeJS if it isn't present.
|
||||
* @returns The Crypto API.
|
||||
*/
|
||||
export function getCrypto() {
|
||||
try {
|
||||
return window.crypto;
|
||||
} catch {
|
||||
return crypto;
|
||||
}
|
||||
}
|
||||
|
15
frontend/types/custom.d.ts
vendored
15
frontend/types/custom.d.ts
vendored
@ -17,23 +17,10 @@ declare global {
|
||||
};
|
||||
|
||||
type ElectronApi = {
|
||||
/**
|
||||
* Determines whether the current app instance is a development build.
|
||||
* @returns True if the current app instance is a development build.
|
||||
*/
|
||||
isDev: () => boolean;
|
||||
/**
|
||||
* Determines whether the current app instance is hosted in a Vite dev server.
|
||||
* @returns True if the current app instance is hosted in a Vite dev server.
|
||||
*/
|
||||
isDevServer: () => boolean;
|
||||
/**
|
||||
* Get a point value representing the cursor's position relative to the calling BrowserWindow
|
||||
* @returns A point value.
|
||||
*/
|
||||
getCursorPoint: () => Electron.Point;
|
||||
|
||||
getPlatform: () => NodeJS.Platform;
|
||||
getEnv: (varName: string) => string;
|
||||
|
||||
showContextMenu: (menu: ElectronContextMenuItem[], position: { x: number; y: number }) => void;
|
||||
onContextMenuClick: (callback: (id: string) => void) => void;
|
||||
|
72
frontend/types/gotypes.d.ts
vendored
72
frontend/types/gotypes.d.ts
vendored
@ -15,24 +15,6 @@ declare global {
|
||||
meta: MetaType;
|
||||
};
|
||||
|
||||
// wshutil.BlockAppendFileCommand
|
||||
type BlockAppendFileCommand = {
|
||||
command: "blockfile:append";
|
||||
filename: string;
|
||||
data: number[];
|
||||
};
|
||||
|
||||
// wshutil.BlockAppendIJsonCommand
|
||||
type BlockAppendIJsonCommand = {
|
||||
command: "blockfile:appendijson";
|
||||
filename: string;
|
||||
data: MetaType;
|
||||
};
|
||||
|
||||
type BlockCommand = {
|
||||
command: string;
|
||||
} & ( BlockAppendFileCommand | BlockAppendIJsonCommand | BlockInputCommand | BlockRestartCommand | CreateBlockCommand | BlockGetMetaCommand | BlockMessageCommand | ResolveIdsCommand | BlockSetMetaCommand | BlockSetViewCommand );
|
||||
|
||||
// blockcontroller.BlockControllerRuntimeStatus
|
||||
type BlockControllerRuntimeStatus = {
|
||||
blockid: string;
|
||||
@ -48,26 +30,11 @@ declare global {
|
||||
meta?: MetaType;
|
||||
};
|
||||
|
||||
// wshutil.BlockGetMetaCommand
|
||||
type BlockGetMetaCommand = {
|
||||
command: "getmeta";
|
||||
oref: string;
|
||||
};
|
||||
|
||||
// wconfig.BlockHeaderOpts
|
||||
type BlockHeaderOpts = {
|
||||
showblockids: boolean;
|
||||
};
|
||||
|
||||
// wshutil.BlockInputCommand
|
||||
type BlockInputCommand = {
|
||||
blockid: string;
|
||||
command: "controller:input";
|
||||
inputdata64?: string;
|
||||
signame?: string;
|
||||
termsize?: TermSize;
|
||||
};
|
||||
|
||||
// webcmd.BlockInputWSCommand
|
||||
type BlockInputWSCommand = {
|
||||
wscommand: "blockinput";
|
||||
@ -75,31 +42,6 @@ declare global {
|
||||
inputdata64: string;
|
||||
};
|
||||
|
||||
// wshutil.BlockMessageCommand
|
||||
type BlockMessageCommand = {
|
||||
command: "message";
|
||||
message: string;
|
||||
};
|
||||
|
||||
// wshutil.BlockRestartCommand
|
||||
type BlockRestartCommand = {
|
||||
command: "controller:restart";
|
||||
blockid: string;
|
||||
};
|
||||
|
||||
// wshutil.BlockSetMetaCommand
|
||||
type BlockSetMetaCommand = {
|
||||
command: "setmeta";
|
||||
oref?: string;
|
||||
meta: MetaType;
|
||||
};
|
||||
|
||||
// wshutil.BlockSetViewCommand
|
||||
type BlockSetViewCommand = {
|
||||
command: "setview";
|
||||
view: string;
|
||||
};
|
||||
|
||||
// wstore.Client
|
||||
type Client = WaveObj & {
|
||||
mainwindowid: string;
|
||||
@ -174,14 +116,6 @@ declare global {
|
||||
meta: MetaType;
|
||||
};
|
||||
|
||||
// wshutil.CreateBlockCommand
|
||||
type CreateBlockCommand = {
|
||||
command: "createblock";
|
||||
tabid: string;
|
||||
blockdef: BlockDef;
|
||||
rtopts?: RuntimeOpts;
|
||||
};
|
||||
|
||||
// wstore.FileDef
|
||||
type FileDef = {
|
||||
filetype?: string;
|
||||
@ -248,12 +182,6 @@ declare global {
|
||||
y: number;
|
||||
};
|
||||
|
||||
// wshutil.ResolveIdsCommand
|
||||
type ResolveIdsCommand = {
|
||||
command: "resolveids";
|
||||
ids: string[];
|
||||
};
|
||||
|
||||
// wshutil.RpcMessage
|
||||
type RpcMessage = {
|
||||
command?: string;
|
||||
|
9
frontend/util/endpoints.ts
Normal file
9
frontend/util/endpoints.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { getEnv } from "./getenv";
|
||||
import { lazy } from "./util";
|
||||
|
||||
export const WebServerEndpointVarName = "WAVE_SERVER_WEB_ENDPOINT";
|
||||
export const WSServerEndpointVarName = "WAVE_SERVER_WS_ENDPOINT";
|
||||
|
||||
export const getServerWebEndpoint = lazy(() => `http://${getEnv(WebServerEndpointVarName)}`);
|
||||
|
||||
export const getServerWSEndpoint = lazy(() => `ws://${getEnv(WSServerEndpointVarName)}`);
|
26
frontend/util/getenv.ts
Normal file
26
frontend/util/getenv.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { getApi } from "@/app/store/global";
|
||||
|
||||
function getWindow(): Window {
|
||||
return globalThis.window;
|
||||
}
|
||||
|
||||
function getProcess(): NodeJS.Process {
|
||||
return globalThis.process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an environment variable from the host process, either directly or via IPC if called from the browser.
|
||||
* @param paramName The name of the environment variable to attempt to retrieve.
|
||||
* @returns The value of the environment variable or null if not present.
|
||||
*/
|
||||
export function getEnv(paramName: string): string {
|
||||
const win = getWindow();
|
||||
if (win != null) {
|
||||
return getApi().getEnv(paramName);
|
||||
}
|
||||
const proc = getProcess();
|
||||
if (proc != null) {
|
||||
return proc.env[paramName];
|
||||
}
|
||||
return null;
|
||||
}
|
17
frontend/util/isdev.ts
Normal file
17
frontend/util/isdev.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { getEnv } from "./getenv";
|
||||
import { lazy } from "./util";
|
||||
|
||||
export const WaveDevVarName = "WAVETERM_DEV";
|
||||
export const WaveDevViteVarName = "WAVETERM_DEV_VITE";
|
||||
|
||||
/**
|
||||
* Determines whether the current app instance is a development build.
|
||||
* @returns True if the current app instance is a development build.
|
||||
*/
|
||||
export const isDev = lazy(() => !!getEnv(WaveDevVarName));
|
||||
|
||||
/**
|
||||
* Determines whether the current app instance is running via the Vite dev server.
|
||||
* @returns True if the app is running via the Vite dev server.
|
||||
*/
|
||||
export const isDevVite = lazy(() => !!getEnv(WaveDevViteVarName));
|
@ -162,15 +162,45 @@ function useAtomValueSafe<T>(atom: jotai.Atom<T>): T {
|
||||
return jotai.useAtomValue(atom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple wrapper function that lazily evaluates the provided function and caches its result for future calls.
|
||||
* @param callback The function to lazily run.
|
||||
* @returns The result of the function.
|
||||
*/
|
||||
const lazy = <T extends (...args: any[]) => any>(callback: T) => {
|
||||
let res: ReturnType<T>;
|
||||
let processed = false;
|
||||
return (...args: Parameters<T>): ReturnType<T> => {
|
||||
if (processed) return res;
|
||||
res = callback(...args);
|
||||
processed = true;
|
||||
return res;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Workaround for NodeJS compatibility. Will attempt to resolve the Crypto API from the browser and fallback to NodeJS if it isn't present.
|
||||
* @returns The Crypto API.
|
||||
*/
|
||||
function getCrypto() {
|
||||
try {
|
||||
return window.crypto;
|
||||
} catch {
|
||||
return crypto;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
base64ToArray,
|
||||
base64ToString,
|
||||
fireAndForget,
|
||||
getCrypto,
|
||||
getPromiseState,
|
||||
getPromiseValue,
|
||||
isBlank,
|
||||
jotaiLoadableValue,
|
||||
jsonDeepEqual,
|
||||
lazy,
|
||||
makeIconClass,
|
||||
stringToBase64,
|
||||
useAtomValueSafe,
|
||||
|
@ -12,13 +12,13 @@ import { App } from "./app/app";
|
||||
import { loadFonts } from "./util/fontutil";
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
let windowId = urlParams.get("windowid");
|
||||
let clientId = urlParams.get("clientid");
|
||||
const windowId = urlParams.get("windowid");
|
||||
const clientId = urlParams.get("clientid");
|
||||
|
||||
console.log("Wave Starting");
|
||||
console.log("clientid", clientId, "windowid", windowId);
|
||||
|
||||
let platform = getApi().getPlatform();
|
||||
const platform = getApi().getPlatform();
|
||||
setPlatform(platform);
|
||||
keyutil.setKeyUtilPlatform(platform);
|
||||
|
||||
|
24
package.json
24
package.json
@ -1,7 +1,18 @@
|
||||
{
|
||||
"name": "thenextwave",
|
||||
"private": true,
|
||||
"author": {
|
||||
"name": "Command Line Inc",
|
||||
"email": "info@commandline.dev"
|
||||
},
|
||||
"productName": "TheNextWave",
|
||||
"description": "An Open-Source, AI-Native, Terminal Built for Seamless Workflows",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.0.0",
|
||||
"homepage": "https://waveterm.dev",
|
||||
"build": {
|
||||
"appId": "dev.commandline.thenextwave"
|
||||
},
|
||||
"private": true,
|
||||
"main": "./dist/main/index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@ -12,7 +23,8 @@
|
||||
"storybook": "storybook dev -p 6006 --no-open",
|
||||
"build-storybook": "storybook build",
|
||||
"coverage": "vitest run --coverage",
|
||||
"test": "vitest"
|
||||
"test": "vitest",
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^1.5.0",
|
||||
@ -27,11 +39,14 @@
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/papaparse": "^5",
|
||||
"@types/react": "^18.3.2",
|
||||
"@types/sprintf-js": "^1",
|
||||
"@types/throttle-debounce": "^5",
|
||||
"@types/tinycolor2": "^1",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@vitejs/plugin-react": "^4.3.0",
|
||||
"@vitest/coverage-istanbul": "^1.6.0",
|
||||
"electron": "^31.1.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-vite": "^2.2.0",
|
||||
"eslint": "^9.2.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
@ -65,7 +80,6 @@
|
||||
"base64-js": "^1.5.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.11",
|
||||
"electron": "^31.1.0",
|
||||
"html-to-image": "^1.11.11",
|
||||
"immer": "^10.1.1",
|
||||
"jotai": "^2.8.0",
|
||||
@ -83,10 +97,12 @@
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"sprintf-js": "^1.1.3",
|
||||
"throttle-debounce": "^5.0.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"use-device-pixel-ratio": "^1.1.2",
|
||||
"uuid": "^9.0.1"
|
||||
"uuid": "^9.0.1",
|
||||
"winston": "^3.13.1"
|
||||
},
|
||||
"packageManager": "yarn@4.3.1"
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
|
||||
const WaveVersion = "v0.1.0"
|
||||
const DefaultWaveHome = "~/.w2"
|
||||
const DevWaveHome = "~/.w2-dev"
|
||||
const WaveHomeVarName = "WAVETERM_HOME"
|
||||
const WaveDevVarName = "WAVETERM_DEV"
|
||||
const WaveLockFile = "waveterm.lock"
|
||||
@ -74,6 +75,9 @@ func GetWaveHomeDir() string {
|
||||
if homeVar != "" {
|
||||
return ExpandHomeDir(homeVar)
|
||||
}
|
||||
if IsDevMode() {
|
||||
return ExpandHomeDir(DevWaveHome)
|
||||
}
|
||||
return ExpandHomeDir(DefaultWaveHome)
|
||||
}
|
||||
|
||||
|
@ -47,10 +47,6 @@ const HttpWriteTimeout = 21 * time.Second
|
||||
const HttpMaxHeaderBytes = 60000
|
||||
const HttpTimeoutDuration = 21 * time.Second
|
||||
|
||||
const MainServerAddr = "127.0.0.1:1719" // wavesrv, P=16+1, S=19, PS=1719
|
||||
const WebSocketServerAddr = "127.0.0.1:1723" // wavesrv:websocket, P=16+1, W=23, PW=1723
|
||||
const MainServerDevAddr = "127.0.0.1:8190"
|
||||
const WebSocketServerDevAddr = "127.0.0.1:8191"
|
||||
const WSStateReconnectTime = 30 * time.Second
|
||||
const WSStatePacketChSize = 20
|
||||
|
||||
@ -213,16 +209,13 @@ func WebFnWrap(opts WebFnOpts, fn WebFnType) WebFnType {
|
||||
}
|
||||
}
|
||||
|
||||
func MakeTCPListener() (net.Listener, error) {
|
||||
serverAddr := MainServerAddr
|
||||
if wavebase.IsDevMode() {
|
||||
serverAddr = MainServerDevAddr
|
||||
}
|
||||
func MakeTCPListener(serviceName string) (net.Listener, error) {
|
||||
serverAddr := "127.0.0.1:"
|
||||
rtn, err := net.Listen("tcp", serverAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating listener at %v: %v", serverAddr, err)
|
||||
}
|
||||
log.Printf("Server listening on %s\n", serverAddr)
|
||||
log.Printf("Server [%s] listening on %s\n", serviceName, rtn.Addr())
|
||||
return rtn, nil
|
||||
}
|
||||
|
||||
@ -234,7 +227,7 @@ func MakeUnixListener() (net.Listener, error) {
|
||||
return nil, fmt.Errorf("error creating listener at %v: %v", serverAddr, err)
|
||||
}
|
||||
os.Chmod(serverAddr, 0700)
|
||||
log.Printf("Server listening on %s\n", serverAddr)
|
||||
log.Printf("Server [unix-domain] listening on %s\n", serverAddr)
|
||||
return rtn, nil
|
||||
}
|
||||
|
||||
@ -244,15 +237,15 @@ func RunWebServer(listener net.Listener) {
|
||||
gr.HandleFunc("/wave/stream-file", WebFnWrap(WebFnOpts{AllowCaching: true}, handleStreamFile))
|
||||
gr.HandleFunc("/wave/file", WebFnWrap(WebFnOpts{AllowCaching: false}, handleWaveFile))
|
||||
gr.HandleFunc("/wave/service", WebFnWrap(WebFnOpts{JsonErrors: true}, handleService))
|
||||
var allowedOrigins handlers.CORSOption
|
||||
handler := http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout")
|
||||
if wavebase.IsDevMode() {
|
||||
allowedOrigins = handlers.AllowedOrigins([]string{"*"})
|
||||
handler = handlers.CORS(handlers.AllowedOrigins([]string{"*"}))(handler)
|
||||
}
|
||||
server := &http.Server{
|
||||
ReadTimeout: HttpReadTimeout,
|
||||
WriteTimeout: HttpWriteTimeout,
|
||||
MaxHeaderBytes: HttpMaxHeaderBytes,
|
||||
Handler: handlers.CORS(allowedOrigins)(http.TimeoutHandler(gr, HttpTimeoutDuration, "Timeout")),
|
||||
Handler: handler,
|
||||
}
|
||||
err := server.Serve(listener)
|
||||
if err != nil {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
@ -31,20 +32,18 @@ const wsInitialPingTime = 1 * time.Second
|
||||
|
||||
const DefaultCommandTimeout = 2 * time.Second
|
||||
|
||||
func RunWebSocketServer() {
|
||||
func RunWebSocketServer(listener net.Listener) {
|
||||
gr := mux.NewRouter()
|
||||
gr.HandleFunc("/ws", HandleWs)
|
||||
serverAddr := WebSocketServerDevAddr
|
||||
server := &http.Server{
|
||||
Addr: serverAddr,
|
||||
ReadTimeout: HttpReadTimeout,
|
||||
WriteTimeout: HttpWriteTimeout,
|
||||
MaxHeaderBytes: HttpMaxHeaderBytes,
|
||||
Handler: gr,
|
||||
}
|
||||
server.SetKeepAlivesEnabled(false)
|
||||
log.Printf("Running websocket server on %s\n", serverAddr)
|
||||
err := server.ListenAndServe()
|
||||
log.Printf("Running websocket server on %s\n", listener.Addr())
|
||||
err := server.Serve(listener)
|
||||
if err != nil {
|
||||
log.Printf("[error] trying to run websocket server: %v\n", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user