first pass
parent
d1de401ce4
commit
e651446cc2
|
|
@ -1,7 +1,7 @@
|
|||
import {resolve, sep} from 'path';
|
||||
|
||||
export default {
|
||||
'*.{js,ts,vue}': 'eslint --cache --fix',
|
||||
'*.{js,ts,tsx}': 'eslint --cache --fix',
|
||||
|
||||
/**
|
||||
* 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",
|
||||
"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:preload": "cd ./packages/preload && 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",
|
||||
"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:main": "vitest run -r packages/main --passWithNoTests",
|
||||
"test:preload": "vitest run -r packages/preload --passWithNoTests",
|
||||
"test:renderer": "vitest run -r packages/renderer --passWithNoTests",
|
||||
"test:server": "vitest run -r packages/server --passWithNoTests",
|
||||
"watch": "node scripts/watch.js",
|
||||
"lint": "eslint . --ext js,ts,vue",
|
||||
"typecheck:main": "tsc --noEmit -p packages/main/tsconfig.json",
|
||||
"typecheck:preload": "tsc --noEmit -p packages/preload/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 "
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -52,7 +55,21 @@
|
|||
"vue-tsc": "0.38.8"
|
||||
},
|
||||
"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",
|
||||
"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) {
|
||||
app.whenReady()
|
||||
.then(() => import('electron-devtools-installer'))
|
||||
.then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, {
|
||||
loadExtensionOptions: {
|
||||
allowFileAccess: true,
|
||||
},
|
||||
}))
|
||||
.catch(e => console.error('Failed install extension:', e));
|
||||
.catch(e => console.error('Failed importing electron-devtools-installer:', e));
|
||||
// .then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, {
|
||||
// loadExtensionOptions: {
|
||||
// allowFileAccess: true,
|
||||
// },
|
||||
// }))
|
||||
// .catch(e => console.error('Failed install extension:', e));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ async function createWindow() {
|
|||
const browserWindow = new BrowserWindow({
|
||||
show: false, // Use 'ready-to-show' event to show window
|
||||
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
|
||||
preload: join(__dirname, '../../preload/dist/index.cjs'),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
},
|
||||
"extends": [
|
||||
/** @see https://eslint.vuejs.org/rules/ */
|
||||
"plugin:vue/vue3-recommended"
|
||||
"eslint:recommended"
|
||||
// "plugin:react/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' blob:">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./src/index.ts" type="module"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- <meta http-equiv="Content-Security-Policy" content="script-src 'self' blob:"> -->
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<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" style="width: 100vw; height: 100vh"></div>
|
||||
<script src="./src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</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": {
|
||||
"impl"
|
||||
"module": "esnext",
|
||||
"target": "esnext",
|
||||
"sourceMap": false,
|
||||
|
|
@ -7,7 +8,6 @@
|
|||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
"types" : ["node"],
|
||||
"baseUrl": ".",
|
||||
|
|
@ -19,11 +19,12 @@
|
|||
"./src/*"
|
||||
]
|
||||
},
|
||||
"lib": ["ESNext", "dom", "dom.iterable"]
|
||||
"lib": ["ESNext", "dom", "dom.iterable"],
|
||||
"jsx": "preserve",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
|
||||
"include": [
|
||||
"src/**/*.vue",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"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 {join} from 'path';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import {renderer} from 'unplugin-auto-expose';
|
||||
|
||||
const PACKAGE_ROOT = __dirname;
|
||||
|
|
@ -40,7 +40,7 @@ const config = {
|
|||
environment: 'happy-dom',
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
react(),
|
||||
renderer.vite({
|
||||
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 electronPath = require('electron');
|
||||
const {spawn} = require('child_process');
|
||||
|
||||
const node = 'node' + (process.platform === 'win32' ? '.exe' : '');
|
||||
|
||||
/** @type 'production' | 'development'' */
|
||||
const mode = process.env.MODE = process.env.MODE || 'development';
|
||||
|
|
@ -23,6 +23,58 @@ const stderrFilterPatterns = [
|
|||
/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
|
||||
|
|
@ -30,6 +82,7 @@ const stderrFilterPatterns = [
|
|||
* @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}
|
||||
*/
|
||||
|
||||
const setupMainPackageWatcher = ({resolvedUrls}) => {
|
||||
process.env.VITE_DEV_SERVER_URL = resolvedUrls.local[0];
|
||||
|
||||
|
|
@ -142,6 +195,7 @@ const setupPreloadPackageWatcher = ({ws}) =>
|
|||
* See {@link setupMainPackageWatcher} JSDoc
|
||||
*/
|
||||
await setupMainPackageWatcher(rendererWatchServer);
|
||||
await setupServerPackageWatcher();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
|
|
|
|||
Reference in New Issue