first pass
parent
d1de401ce4
commit
e651446cc2
|
|
@ -1,7 +1,7 @@
|
||||||
import {resolve, sep} from 'path';
|
import {resolve, sep} from 'path';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'*.{js,ts,vue}': 'eslint --cache --fix',
|
'*.{js,ts,tsx}': 'eslint --cache --fix',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run typechecking if any type-sensitive files was staged
|
* Run typechecking if any type-sensitive files was staged
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
25
package.json
25
package.json
|
|
@ -13,22 +13,25 @@
|
||||||
},
|
},
|
||||||
"main": "packages/main/dist/index.cjs",
|
"main": "packages/main/dist/index.cjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:main && npm run build:preload && npm run build:renderer",
|
"build": "npm run build:main && npm run build:preload && npm run build:renderer && npm run build:server",
|
||||||
"build:main": "cd ./packages/main && vite build",
|
"build:main": "cd ./packages/main && vite build",
|
||||||
"build:preload": "cd ./packages/preload && vite build",
|
"build:preload": "cd ./packages/preload && vite build",
|
||||||
"build:renderer": "cd ./packages/renderer && vite build",
|
"build:renderer": "cd ./packages/renderer && vite build",
|
||||||
|
"build:server": "cd ./packages/server && vite build",
|
||||||
"compile": "cross-env MODE=production npm run build && electron-builder build --config .electron-builder.config.js --dir --config.asar=false",
|
"compile": "cross-env MODE=production npm run build && electron-builder build --config .electron-builder.config.js --dir --config.asar=false",
|
||||||
"test": "npm run test:main && npm run test:preload && npm run test:renderer && npm run test:e2e",
|
"test": "npm run test:main && npm run test:preload && npm run test:renderer && npm run test:e2e && npm run test:server",
|
||||||
"test:e2e": "vitest run",
|
"test:e2e": "vitest run",
|
||||||
"test:main": "vitest run -r packages/main --passWithNoTests",
|
"test:main": "vitest run -r packages/main --passWithNoTests",
|
||||||
"test:preload": "vitest run -r packages/preload --passWithNoTests",
|
"test:preload": "vitest run -r packages/preload --passWithNoTests",
|
||||||
"test:renderer": "vitest run -r packages/renderer --passWithNoTests",
|
"test:renderer": "vitest run -r packages/renderer --passWithNoTests",
|
||||||
|
"test:server": "vitest run -r packages/server --passWithNoTests",
|
||||||
"watch": "node scripts/watch.js",
|
"watch": "node scripts/watch.js",
|
||||||
"lint": "eslint . --ext js,ts,vue",
|
"lint": "eslint . --ext js,ts,vue",
|
||||||
"typecheck:main": "tsc --noEmit -p packages/main/tsconfig.json",
|
"typecheck:main": "tsc --noEmit -p packages/main/tsconfig.json",
|
||||||
"typecheck:preload": "tsc --noEmit -p packages/preload/tsconfig.json",
|
"typecheck:preload": "tsc --noEmit -p packages/preload/tsconfig.json",
|
||||||
"typecheck:renderer": "vue-tsc --noEmit -p packages/renderer/tsconfig.json",
|
"typecheck:renderer": "vue-tsc --noEmit -p packages/renderer/tsconfig.json",
|
||||||
"typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer",
|
"typecheck:server": "tsc --noEmit -p packages/server/tsconfig.json",
|
||||||
|
"typecheck": "npm run typecheck:main && npm run typecheck:preload && npm run typecheck:renderer && npm run typecheck:server",
|
||||||
"postinstall": "cross-env ELECTRON_RUN_AS_NODE=1 npx --no-install electron ./scripts/update-electron-vendors.js "
|
"postinstall": "cross-env ELECTRON_RUN_AS_NODE=1 npx --no-install electron ./scripts/update-electron-vendors.js "
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -52,7 +55,21 @@
|
||||||
"vue-tsc": "0.38.8"
|
"vue-tsc": "0.38.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/react": "^18.0.15",
|
||||||
|
"@types/react-dom": "^18.0.6",
|
||||||
|
"@types/react-timeago": "^4.1.3",
|
||||||
|
"@types/uuid": "^8.3.4",
|
||||||
|
"@types/ws": "^8.5.3",
|
||||||
|
"@vitejs/plugin-react": "^2.0.0",
|
||||||
"electron-updater": "5.0.5",
|
"electron-updater": "5.0.5",
|
||||||
"vue": "3.2.37"
|
"eslint-plugin-react": "^7.30.1",
|
||||||
|
"express": "^4.18.1",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-time-ago": "^7.2.1",
|
||||||
|
"react-timeago": "^7.1.0",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"vue": "3.2.37",
|
||||||
|
"ws": "^8.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,13 @@ app.whenReady()
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
app.whenReady()
|
app.whenReady()
|
||||||
.then(() => import('electron-devtools-installer'))
|
.then(() => import('electron-devtools-installer'))
|
||||||
.then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, {
|
.catch(e => console.error('Failed importing electron-devtools-installer:', e));
|
||||||
loadExtensionOptions: {
|
// .then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, {
|
||||||
allowFileAccess: true,
|
// loadExtensionOptions: {
|
||||||
},
|
// allowFileAccess: true,
|
||||||
}))
|
// },
|
||||||
.catch(e => console.error('Failed install extension:', e));
|
// }))
|
||||||
|
// .catch(e => console.error('Failed install extension:', e));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ async function createWindow() {
|
||||||
const browserWindow = new BrowserWindow({
|
const browserWindow = new BrowserWindow({
|
||||||
show: false, // Use 'ready-to-show' event to show window
|
show: false, // Use 'ready-to-show' event to show window
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
sandbox: false,
|
||||||
webviewTag: false, // The webview tag is not recommended. Consider alternatives like iframe or Electron's BrowserView. https://www.electronjs.org/docs/latest/api/webview-tag#warning
|
webviewTag: false, // The webview tag is not recommended. Consider alternatives like iframe or Electron's BrowserView. https://www.electronjs.org/docs/latest/api/webview-tag#warning
|
||||||
preload: join(__dirname, '../../preload/dist/index.cjs'),
|
preload: join(__dirname, '../../preload/dist/index.cjs'),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
/** @see https://eslint.vuejs.org/rules/ */
|
/** @see https://eslint.vuejs.org/rules/ */
|
||||||
"plugin:vue/vue3-recommended"
|
"eslint:recommended"
|
||||||
|
// "plugin:react/recommended"
|
||||||
],
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' blob:">
|
<!-- <meta http-equiv="Content-Security-Policy" content="script-src 'self' blob:"> -->
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<title>Vite App</title>
|
<title>Vite App</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body style="margin: 0px; overflow: hidden; color: #f8f8f2; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif">
|
||||||
<div id="app"></div>
|
<div id="app" style="width: 100vw; height: 100vh"></div>
|
||||||
<script src="./src/index.ts" type="module"></script>
|
<script src="./src/index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import ReactiveCounter from '/@/components/ReactiveCounter.vue';
|
|
||||||
import ReactiveHash from '/@/components/ReactiveHash.vue';
|
|
||||||
import ElectronVersions from '/@/components/ElectronVersions.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<img
|
|
||||||
alt="Vue logo"
|
|
||||||
src="../assets/logo.svg"
|
|
||||||
width="150"
|
|
||||||
>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
For a guide and recipes on how to configure / customize this project,<br>
|
|
||||||
check out the
|
|
||||||
<a
|
|
||||||
href="https://github.com/cawa-93/vite-electron-builder"
|
|
||||||
target="_blank"
|
|
||||||
>vite-electron-builder documentation</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Test Vue Reactivity</legend>
|
|
||||||
<reactive-counter />
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Test Node.js API</legend>
|
|
||||||
<reactive-hash />
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Environment</legend>
|
|
||||||
<electron-versions />
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Edit
|
|
||||||
<code>packages/renderer/src/App.vue</code> to test hot module replacement.
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#app {
|
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
text-align: center;
|
|
||||||
color: #2c3e50;
|
|
||||||
margin: 60px auto;
|
|
||||||
max-width: 700px;
|
|
||||||
}
|
|
||||||
fieldset {
|
|
||||||
margin: 2rem;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import {versions} from '#preload';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ul id="process-versions">
|
|
||||||
<li
|
|
||||||
v-for="(version, lib) in versions"
|
|
||||||
:key="lib"
|
|
||||||
>
|
|
||||||
<strong>{{ lib }}</strong>: v{{ version }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<code>packages/renderer/src/components/ElectronVersions.vue</code>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import {ref} from 'vue';
|
|
||||||
|
|
||||||
const count = ref(0);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button @click="count++">
|
|
||||||
count is: {{ count }}
|
|
||||||
</button>
|
|
||||||
<br><br>
|
|
||||||
<code>packages/renderer/src/components/ReactiveCounter.vue</code>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import {computed, ref} from 'vue';
|
|
||||||
import {sha256sum} from '#preload';
|
|
||||||
|
|
||||||
const rawString = ref('Hello World');
|
|
||||||
/**
|
|
||||||
* window.nodeCrypto was exposed from {@link module:preload}
|
|
||||||
*/
|
|
||||||
const hashedString = computed(() => sha256sum(rawString.value));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<label>
|
|
||||||
Raw value
|
|
||||||
<input
|
|
||||||
v-model="rawString"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
<br>
|
|
||||||
<label>
|
|
||||||
Hashed by node:crypto
|
|
||||||
<input
|
|
||||||
v-model="hashedString"
|
|
||||||
type="text"
|
|
||||||
readonly
|
|
||||||
>
|
|
||||||
</label>
|
|
||||||
<br><br>
|
|
||||||
<code>packages/renderer/src/components/ReactiveHash.vue</code>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import {createApp} from 'vue';
|
|
||||||
import App from '/@/App.vue';
|
|
||||||
|
|
||||||
createApp(App).mount('#app');
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import Chat from './pages/Chat';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
(
|
||||||
|
<>
|
||||||
|
<Chat
|
||||||
|
|
||||||
|
></Chat>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
document.getElementById('app'),
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let socket: WebSocket | null = null;
|
||||||
|
let connectionAttempts = 0;
|
||||||
|
const url = 'wss://dev.valnet.xyz';
|
||||||
|
|
||||||
|
let routers: any[] = [];
|
||||||
|
|
||||||
|
const connect = async () => {
|
||||||
|
try {
|
||||||
|
connectionAttempts ++;
|
||||||
|
console.log('attempting api connection...');
|
||||||
|
socket = new WebSocket(url);
|
||||||
|
} catch (e) {
|
||||||
|
if(connectionAttempts === 1)
|
||||||
|
connect();
|
||||||
|
else {
|
||||||
|
const seconds = 2 ** connectionAttempts;
|
||||||
|
console.log(`waiting ${seconds} seconds before reconnecting`);
|
||||||
|
setTimeout(connect, 1000 * seconds);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.addEventListener('open', () => {
|
||||||
|
if(socket === null) return;
|
||||||
|
connectionAttempts = 0;
|
||||||
|
// socket.send('Hello Server!');
|
||||||
|
console.log('API Connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('message', (event) => {
|
||||||
|
console.log('API Broadcasted', event.data);
|
||||||
|
const {action, data} = JSON.parse(event.data);
|
||||||
|
for(const router of routers) {
|
||||||
|
// debugger;
|
||||||
|
router(action, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('close', () => {
|
||||||
|
socket = null;
|
||||||
|
console.log('API Closed');
|
||||||
|
connect();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
connect();
|
||||||
|
|
||||||
|
export function send(action: string, data?: any) {
|
||||||
|
if(socket === null) return;
|
||||||
|
const message = JSON.stringify({ action, data });
|
||||||
|
socket.send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function router(routes: any) {
|
||||||
|
for(const routeName in routes) {
|
||||||
|
const route = routes[routeName];
|
||||||
|
if(typeof route === 'object') {
|
||||||
|
for(const suffix in route) {
|
||||||
|
const combinedRouteName = routeName + ':' + suffix;
|
||||||
|
routes[combinedRouteName] = route[suffix];
|
||||||
|
}
|
||||||
|
delete routes[routeName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return function(route: string, data: any) {
|
||||||
|
if(route in routes) {
|
||||||
|
routes[route](data);
|
||||||
|
} else {
|
||||||
|
console.warn(`route <${route}> not found`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerRouter(router: any) {
|
||||||
|
routers.push(router);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregisterRouter(router: any) {
|
||||||
|
routers = routers.filter(r => r !== router);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import TimeAgo from 'react-timeago';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { registerRouter, router, send, unregisterRouter } from '../lib/api';
|
||||||
|
|
||||||
|
const firstLineIndent = '10px';
|
||||||
|
const multiLineIndent = '16px';
|
||||||
|
const rightMessagePagging = '16px';
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
text: string;
|
||||||
|
from: string;
|
||||||
|
timestamp: number;
|
||||||
|
// nid: number;
|
||||||
|
uid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMessage(from: string, text: string, t = 0): Message {
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
from,
|
||||||
|
timestamp: Date.now() - t * 1000,
|
||||||
|
uid: v4(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockMessages: Message[] = [
|
||||||
|
// createMessage('Bob', 'Hey', 55),
|
||||||
|
// createMessage('Alice', 'Hello', 50),
|
||||||
|
// createMessage('Bob', 'What up', 45),
|
||||||
|
// createMessage('Alice', 'nm UUU', 40),
|
||||||
|
// createMessage('Bob', 'Hey', 35),
|
||||||
|
// createMessage('Alice', 'Hello', 30),
|
||||||
|
// createMessage('Bob', 'What up', 25),
|
||||||
|
// createMessage('Alice', 'nm UUU', 20),
|
||||||
|
// createMessage('Bob', 'Hey', 15),
|
||||||
|
// createMessage('Alice', 'Hello', 10),
|
||||||
|
// createMessage('Bob', 'What up', 5),
|
||||||
|
// createMessage('Alice', 'This is what a really long message could possibly look like, if a person decided to write a really long essay. This is what a really long message could possibly look like, if a person decided to write a really long essay. This is what a really long message could possibly look like, if a person decided to write a really long essay. This is what a really long message could possibly look like, if a person decided to write a really long essay.'),
|
||||||
|
];
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const [messages, setMessages] = useState<Message[]>(mockMessages);
|
||||||
|
|
||||||
|
const textBoxRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const actions = router({
|
||||||
|
message(data: Message) {
|
||||||
|
setMessages([...messages, data]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
registerRouter(actions);
|
||||||
|
return () => {
|
||||||
|
unregisterRouter(actions);
|
||||||
|
};
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const sendMessage = useCallback(() => {
|
||||||
|
if(textBoxRef.current === null) return;
|
||||||
|
send('message', createMessage('Anonymous', textBoxRef.current.innerText));
|
||||||
|
textBoxRef.current.innerText = '';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const keyDown = useCallback((evt: any) => {
|
||||||
|
console.log(evt);
|
||||||
|
if(evt.key === 'Enter') {
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
|
}, [sendMessage]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: '#282a36',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '1fr 64px',
|
||||||
|
gridTemplateRows: '1fr 64px',
|
||||||
|
gridTemplateAreas: '"content content" "message send"',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
// borderBottom: '1px solid #bd93f9',
|
||||||
|
gridArea: 'content',
|
||||||
|
position: 'relative',
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '0px',
|
||||||
|
width: '100%',
|
||||||
|
}}>
|
||||||
|
{messages.map(message => (
|
||||||
|
<div style={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: '128px 1fr',
|
||||||
|
width: '100%',
|
||||||
|
padding: '1px 0px',
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
fontStyle: 'italic',
|
||||||
|
color: '#596793',
|
||||||
|
textAlign: 'right',
|
||||||
|
userSelect: 'none',
|
||||||
|
marginRight: '16px',
|
||||||
|
}}>
|
||||||
|
<TimeAgo
|
||||||
|
date={message.timestamp}
|
||||||
|
formatter={(t, u) => u === 'second' ? 'Just Now' : ('' + t + u[0])}
|
||||||
|
></TimeAgo>
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
float: 'left',
|
||||||
|
paddingRight: firstLineIndent,
|
||||||
|
// marginRight: '16px',
|
||||||
|
// height: '100%'
|
||||||
|
// borderBottom: '1px solid white'
|
||||||
|
}}>
|
||||||
|
{message.from}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
marginRight: rightMessagePagging,
|
||||||
|
paddingLeft: multiLineIndent,
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
position: 'relative',
|
||||||
|
}}>
|
||||||
|
{message.text}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div onClick={() => {
|
||||||
|
textBoxRef.current?.focus();
|
||||||
|
}}style={{
|
||||||
|
margin: '8px',
|
||||||
|
marginRight: '3px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
background: '#343746',
|
||||||
|
gridArea: 'message',
|
||||||
|
display: 'grid',
|
||||||
|
placeItems: 'center center',
|
||||||
|
padding: '0px 16px',
|
||||||
|
cursor: 'text',
|
||||||
|
overflow: 'auto',
|
||||||
|
}}>
|
||||||
|
<div
|
||||||
|
ref={textBoxRef}
|
||||||
|
onKeyDown={keyDown}
|
||||||
|
className="input"
|
||||||
|
role="textbox"
|
||||||
|
contentEditable
|
||||||
|
style={{
|
||||||
|
background: 'inherit',
|
||||||
|
outline: 'none',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
borderRadius: '8px',
|
||||||
|
width: '100%',
|
||||||
|
resize: 'none',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
padding: '8px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}}>
|
||||||
|
<div onClick={sendMessage} style={{
|
||||||
|
background: '#bd93f9',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
// borderRadius: '50%',
|
||||||
|
borderRadius: '8px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'grid',
|
||||||
|
placeItems: 'center center',
|
||||||
|
fontSize: '32px',
|
||||||
|
}}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import {mount} from '@vue/test-utils';
|
|
||||||
import {expect, test, vi} from 'vitest';
|
|
||||||
import ElectronVersions from '../src/components/ElectronVersions.vue';
|
|
||||||
|
|
||||||
vi.mock('#preload', () => {
|
|
||||||
return {
|
|
||||||
versions: {lib1: 1, lib2: 2},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('ElectronVersions component', async () => {
|
|
||||||
expect(ElectronVersions).toBeTruthy();
|
|
||||||
const wrapper = mount(ElectronVersions);
|
|
||||||
|
|
||||||
const lis = wrapper.findAll<HTMLElement>('li');
|
|
||||||
expect(lis.length).toBe(2);
|
|
||||||
expect(lis[0].text()).toBe('lib1: v1');
|
|
||||||
expect(lis[1].text()).toBe('lib2: v2');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import {mount} from '@vue/test-utils';
|
|
||||||
import {expect, test} from 'vitest';
|
|
||||||
import ReactiveCounter from '../src/components/ReactiveCounter.vue';
|
|
||||||
|
|
||||||
test('ReactiveHash component', async () => {
|
|
||||||
expect(ReactiveCounter).toBeTruthy();
|
|
||||||
const wrapper = mount(ReactiveCounter);
|
|
||||||
|
|
||||||
const button = wrapper.get('button');
|
|
||||||
|
|
||||||
expect(button.text()).toBe('count is: 0');
|
|
||||||
await button.trigger('click');
|
|
||||||
expect(button.text()).toBe('count is: 1');
|
|
||||||
});
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
import {mount} from '@vue/test-utils';
|
|
||||||
import {expect, test, vi} from 'vitest';
|
|
||||||
import ReactiveHash from '../src/components/ReactiveHash.vue';
|
|
||||||
|
|
||||||
|
|
||||||
vi.mock('#preload', () => {
|
|
||||||
return {
|
|
||||||
sha256sum: vi.fn((s: string) => `${s}:HASHED`),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
test('ReactiveHash component', async () => {
|
|
||||||
expect(ReactiveHash).toBeTruthy();
|
|
||||||
const wrapper = mount(ReactiveHash);
|
|
||||||
|
|
||||||
const dataInput = wrapper.get<HTMLInputElement>('input:not([readonly])');
|
|
||||||
const hashInput = wrapper.get<HTMLInputElement>('input[readonly]');
|
|
||||||
|
|
||||||
const dataToHashed = Math.random().toString(36).slice(2, 7);
|
|
||||||
await dataInput.setValue(dataToHashed);
|
|
||||||
expect(hashInput.element.value).toBe(`${dataToHashed}:HASHED`);
|
|
||||||
});
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"impl"
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
|
|
@ -7,7 +8,6 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
|
||||||
|
|
||||||
"types" : ["node"],
|
"types" : ["node"],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
|
@ -19,11 +19,12 @@
|
||||||
"./src/*"
|
"./src/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lib": ["ESNext", "dom", "dom.iterable"]
|
"lib": ["ESNext", "dom", "dom.iterable"],
|
||||||
|
"jsx": "preserve",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
|
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.vue",
|
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
"types/**/*.d.ts",
|
"types/**/*.d.ts",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
declare module '*.vue' {
|
|
||||||
import type { DefineComponent } from 'vue';
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
|
|
||||||
const component: DefineComponent<{}, {}, any>;
|
|
||||||
export default component;
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import {chrome} from '../../.electron-vendors.cache.json';
|
import {chrome} from '../../.electron-vendors.cache.json';
|
||||||
import {join} from 'path';
|
import {join} from 'path';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import react from '@vitejs/plugin-react';
|
||||||
import {renderer} from 'unplugin-auto-expose';
|
import {renderer} from 'unplugin-auto-expose';
|
||||||
|
|
||||||
const PACKAGE_ROOT = __dirname;
|
const PACKAGE_ROOT = __dirname;
|
||||||
|
|
@ -40,7 +40,7 @@ const config = {
|
||||||
environment: 'happy-dom',
|
environment: 'happy-dom',
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
react(),
|
||||||
renderer.vite({
|
renderer.vite({
|
||||||
preloadEntry: join(PACKAGE_ROOT, '../preload/src/index.ts'),
|
preloadEntry: join(PACKAGE_ROOT, '../preload/src/index.ts'),
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { WebSocketServer } from 'ws';
|
||||||
|
import router from './routers/root';
|
||||||
|
|
||||||
|
const wss = new WebSocketServer({
|
||||||
|
port: 3000,
|
||||||
|
}, () => {
|
||||||
|
console.log('ws chat server started on dev.valnet.xyz');
|
||||||
|
});
|
||||||
|
|
||||||
|
wss.on('connection', (ws) => {
|
||||||
|
ws.on('message', (str) => {
|
||||||
|
try {
|
||||||
|
const message = JSON.parse(str.toString());
|
||||||
|
if(typeof message.action !== 'string') {
|
||||||
|
console.warn('invalid JSON message');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {action, data} = message;
|
||||||
|
try {
|
||||||
|
router(action, data);
|
||||||
|
} catch(e) {
|
||||||
|
console.warn(`error in action ${action}`);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('JSON parse failed on message');
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export function send(client: any, action: string, data?: any) {
|
||||||
|
client.send(JSON.stringify({action, data}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function broadcast(action: string, data?: any) {
|
||||||
|
for(const client of wss.clients) {
|
||||||
|
send(client, action, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default wss;
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
export default function router(routes: any) {
|
||||||
|
for(const routeName in routes) {
|
||||||
|
const route = routes[routeName];
|
||||||
|
if(typeof route === 'object') {
|
||||||
|
for(const suffix in route) {
|
||||||
|
const combinedRouteName = routeName + ':' + suffix;
|
||||||
|
routes[combinedRouteName] = route[suffix];
|
||||||
|
}
|
||||||
|
delete routes[routeName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return function(route: any, data: any) {
|
||||||
|
if(route in routes) {
|
||||||
|
routes[route](data);
|
||||||
|
} else {
|
||||||
|
console.warn(`route <${route}> not found`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import router from '../router';
|
||||||
|
import { broadcast } from '../index';
|
||||||
|
|
||||||
|
export default router({
|
||||||
|
up() {
|
||||||
|
console.log(Date.now());
|
||||||
|
},
|
||||||
|
message(data: string) {
|
||||||
|
broadcast('message', data);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"sourceMap": false,
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
|
||||||
|
"types" : ["node"],
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"/@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"../../types/**/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.test.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import {node} from '../../.electron-vendors.cache.json';
|
||||||
|
import {join} from 'path';
|
||||||
|
|
||||||
|
const PACKAGE_ROOT = __dirname;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('vite').UserConfig}
|
||||||
|
* @see https://vitejs.dev/config/
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
mode: process.env.MODE,
|
||||||
|
root: PACKAGE_ROOT,
|
||||||
|
envDir: process.cwd(),
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'/@/': join(PACKAGE_ROOT, 'src') + '/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
ssr: true,
|
||||||
|
sourcemap: 'inline',
|
||||||
|
target: `node${node}`,
|
||||||
|
outDir: 'dist',
|
||||||
|
assetsDir: '.',
|
||||||
|
minify: process.env.MODE !== 'development',
|
||||||
|
lib: {
|
||||||
|
entry: 'src/index.ts',
|
||||||
|
formats: ['cjs'],
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: '[name].cjs',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emptyOutDir: true,
|
||||||
|
brotliSize: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
const {createServer, build, createLogger} = require('vite');
|
const {createServer, build, createLogger} = require('vite');
|
||||||
const electronPath = require('electron');
|
const electronPath = require('electron');
|
||||||
const {spawn} = require('child_process');
|
const {spawn} = require('child_process');
|
||||||
|
const node = 'node' + (process.platform === 'win32' ? '.exe' : '');
|
||||||
|
|
||||||
/** @type 'production' | 'development'' */
|
/** @type 'production' | 'development'' */
|
||||||
const mode = process.env.MODE = process.env.MODE || 'development';
|
const mode = process.env.MODE = process.env.MODE || 'development';
|
||||||
|
|
@ -23,6 +23,58 @@ const stderrFilterPatterns = [
|
||||||
/ExtensionLoadWarning/,
|
/ExtensionLoadWarning/,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const setupServerPackageWatcher = () => {
|
||||||
|
const logger = createLogger(logLevel, {
|
||||||
|
prefix: '[srvr]',
|
||||||
|
});
|
||||||
|
|
||||||
|
let spawnProcess = null;
|
||||||
|
|
||||||
|
const processDied = () => {
|
||||||
|
logger.error('Server has died.', {timestamp: true});
|
||||||
|
spawnProcess = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return build({
|
||||||
|
mode,
|
||||||
|
logLevel,
|
||||||
|
build: {
|
||||||
|
watch: {},
|
||||||
|
},
|
||||||
|
configFile: 'packages/server/vite.config.js',
|
||||||
|
plugins: [{
|
||||||
|
name: 'reload-server-on-server-package-change',
|
||||||
|
writeBundle() {
|
||||||
|
/** Kill electron ff process already exist */
|
||||||
|
if (spawnProcess !== null) {
|
||||||
|
spawnProcess.off('exit', processDied);
|
||||||
|
spawnProcess.kill('SIGINT');
|
||||||
|
spawnProcess = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Spawn new electron process */
|
||||||
|
spawnProcess = spawn(node, ['./index.cjs'], {
|
||||||
|
cwd: './packages/server/dist',
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Proxy all logs */
|
||||||
|
spawnProcess.stdout.on('data', d => d.toString().trim() && logger.warn(d.toString(), {timestamp: true}));
|
||||||
|
|
||||||
|
/** Proxy error logs but stripe some noisy messages. See {@link stderrFilterPatterns} */
|
||||||
|
spawnProcess.stderr.on('data', d => {
|
||||||
|
const data = d.toString().trim();
|
||||||
|
if (!data) return;
|
||||||
|
const mayIgnore = stderrFilterPatterns.some((r) => r.test(data));
|
||||||
|
if (mayIgnore) return;
|
||||||
|
logger.error(data, {timestamp: true});
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Stops the watch script when the application has been quit */
|
||||||
|
spawnProcess.on('exit', processDied);
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup watcher for `main` package
|
* Setup watcher for `main` package
|
||||||
|
|
@ -30,6 +82,7 @@ const stderrFilterPatterns = [
|
||||||
* @param {import('vite').ViteDevServer} watchServer Renderer watch server instance.
|
* @param {import('vite').ViteDevServer} watchServer Renderer watch server instance.
|
||||||
* Needs to set up `VITE_DEV_SERVER_URL` environment variable from {@link import('vite').ViteDevServer.resolvedUrls}
|
* Needs to set up `VITE_DEV_SERVER_URL` environment variable from {@link import('vite').ViteDevServer.resolvedUrls}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const setupMainPackageWatcher = ({resolvedUrls}) => {
|
const setupMainPackageWatcher = ({resolvedUrls}) => {
|
||||||
process.env.VITE_DEV_SERVER_URL = resolvedUrls.local[0];
|
process.env.VITE_DEV_SERVER_URL = resolvedUrls.local[0];
|
||||||
|
|
||||||
|
|
@ -142,6 +195,7 @@ const setupPreloadPackageWatcher = ({ws}) =>
|
||||||
* See {@link setupMainPackageWatcher} JSDoc
|
* See {@link setupMainPackageWatcher} JSDoc
|
||||||
*/
|
*/
|
||||||
await setupMainPackageWatcher(rendererWatchServer);
|
await setupMainPackageWatcher(rendererWatchServer);
|
||||||
|
await setupServerPackageWatcher();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
||||||
Reference in New Issue