closes #1
40
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,40 @@
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
server:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Keyscan
|
||||
run: |
|
||||
ssh-keyscan git.koptilnya.xyz >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
ssh-strict: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build
|
||||
run: docker build -f server/Dockerfile -t chad-server .
|
||||
|
||||
- name: Stop old container
|
||||
run: docker rm -f chad-server || true
|
||||
|
||||
- name: Run
|
||||
run: |
|
||||
docker run -d \
|
||||
--name chad-server \
|
||||
--network traefik \
|
||||
--label "traefik.enable=true" \
|
||||
--label "traefik.http.routers.chad-server.rule=Host(`api.koptilnya.xyz`) && PathPrefix(`/chad`)"
|
||||
--label "traefik.http.routers.chad-server.entrypoints=websecure" \
|
||||
--label "traefik.http.routers.chad-server.tls.certresolver=myresolver" \
|
||||
--label "traefik.http.services.chad-server.loadbalancer.server.port=80" \
|
||||
chad-server:latest
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
12
.idea/chad.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/git_toolbox_blame.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxBlameSettings">
|
||||
<option name="version" value="2" />
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/git_toolbox_prj.xml
generated
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxProjectSettings">
|
||||
<option name="commitMessageIssueKeyValidationOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
<option name="commitMessageValidationEnabledOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
6
.idea/jsLinters/eslint.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EslintConfiguration">
|
||||
<option name="fix-on-save" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/chad.iml" filepath="$PROJECT_DIR$/.idea/chad.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/server" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
24
client/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
dist
|
||||
|
||||
# Node dependencies
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
BIN
client/.yarn/install-state.gz
Normal file
1
client/.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
||||
75
client/README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Nuxt Minimal Starter
|
||||
|
||||
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install dependencies:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
|
||||
# pnpm
|
||||
pnpm preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
13
client/app/app.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<h1>yo</h1>
|
||||
<pre>{{ connected }}</pre>
|
||||
<audio v-for="(s, i) in streams" :key="i" :ref="(el) => onAudioRef(el, s)" autoplay controls />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { connected, streams } = useMediasoup()
|
||||
|
||||
function onAudioRef(ref: Element, stream: MediaStream) {
|
||||
(ref as HTMLAudioElement).srcObject = stream
|
||||
}
|
||||
</script>
|
||||
122
client/app/composables/use-mediasoup.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import type { Socket } from 'socket.io-client'
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
import * as mediasoupClient from 'mediasoup-client'
|
||||
import { io } from 'socket.io-client'
|
||||
|
||||
export const useMediasoup = createGlobalState(() => {
|
||||
const socket: Socket = io('http://localhost:1337', {
|
||||
transports: ['websocket'],
|
||||
})
|
||||
|
||||
const initializing = ref(false)
|
||||
const connected = ref(false)
|
||||
const streams = shallowRef<MediaStream[]>([])
|
||||
let device: mediasoupClient.Device
|
||||
let sendTransport: mediasoupClient.types.Transport
|
||||
let recvTransport: mediasoupClient.types.Transport
|
||||
|
||||
socket.on('newProducer', async ({ producerId }) => {
|
||||
const params = await socket.emitWithAck('consume', {
|
||||
producerId,
|
||||
transportId: recvTransport.id,
|
||||
rtpCapabilities: device.rtpCapabilities,
|
||||
})
|
||||
|
||||
if (params?.error) {
|
||||
console.error('consume error:', params.error)
|
||||
return
|
||||
}
|
||||
|
||||
const consumer = await recvTransport.consume(params)
|
||||
const stream = new MediaStream([consumer.track])
|
||||
|
||||
streams.value.push(stream)
|
||||
triggerRef(streams)
|
||||
})
|
||||
|
||||
async function loadDevice() {
|
||||
device = new mediasoupClient.Device()
|
||||
const rtpCapabilities = await socket.emitWithAck('getRtpCapabilities')
|
||||
await device.load({ routerRtpCapabilities: rtpCapabilities })
|
||||
}
|
||||
|
||||
async function createSendTransport() {
|
||||
const params = await socket.emitWithAck('createTransport')
|
||||
sendTransport = device.createSendTransport(params)
|
||||
|
||||
sendTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||
try {
|
||||
await socket.emitWithAck('connectTransport', {
|
||||
transportId: sendTransport.id,
|
||||
dtlsParameters,
|
||||
})
|
||||
|
||||
callback()
|
||||
}
|
||||
catch (err) {
|
||||
errback(err)
|
||||
}
|
||||
})
|
||||
|
||||
sendTransport.on('produce', async ({ kind, rtpParameters }, callback, errback) => {
|
||||
try {
|
||||
const { id } = await socket.emitWithAck('produce', {
|
||||
transportId: sendTransport.id,
|
||||
kind,
|
||||
rtpParameters,
|
||||
})
|
||||
callback({ id })
|
||||
}
|
||||
catch (err) {
|
||||
errback(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function publishMic() {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
const track = stream.getAudioTracks()[0]
|
||||
|
||||
await sendTransport.produce({ track })
|
||||
}
|
||||
|
||||
async function createRecvTransport() {
|
||||
const params = await socket.emitWithAck('createTransport')
|
||||
recvTransport = device.createRecvTransport(params)
|
||||
|
||||
recvTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||
try {
|
||||
await socket.emitWithAck('connectTransport', {
|
||||
transportId: recvTransport.id,
|
||||
dtlsParameters,
|
||||
})
|
||||
|
||||
callback()
|
||||
}
|
||||
catch (err) {
|
||||
errback(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (initializing.value || connected.value)
|
||||
return
|
||||
|
||||
initializing.value = true
|
||||
connected.value = false
|
||||
|
||||
await loadDevice()
|
||||
await createSendTransport()
|
||||
await createRecvTransport()
|
||||
await publishMic()
|
||||
|
||||
initializing.value = false
|
||||
connected.value = true
|
||||
})()
|
||||
|
||||
return {
|
||||
connected,
|
||||
streams,
|
||||
}
|
||||
})
|
||||
14
client/eslint.config.mjs
Normal file
@ -0,0 +1,14 @@
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default antfu({
|
||||
formatters: {
|
||||
css: true,
|
||||
},
|
||||
overrides: {
|
||||
vue: {
|
||||
'vue/block-order': ['error', {
|
||||
order: ['template', 'script', 'style'],
|
||||
}],
|
||||
},
|
||||
},
|
||||
})
|
||||
16
client/nuxt.config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export default defineNuxtConfig({
|
||||
compatibilityDate: '2025-09-29',
|
||||
devtools: { enabled: true },
|
||||
ssr: false,
|
||||
devServer: {
|
||||
// host: '0',
|
||||
},
|
||||
vite: {
|
||||
clearScreen: false,
|
||||
envPrefix: ['VITE_', 'TAURI_'],
|
||||
server: {
|
||||
strictPort: true,
|
||||
},
|
||||
},
|
||||
ignore: ['**/src-tauri/**'],
|
||||
})
|
||||
28
client/package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "nuxt-app",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"mediasoup-client": "^3.16.7",
|
||||
"nuxt": "^4.1.2",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"packageManager": "yarn@4.10.3",
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@tauri-apps/cli": "^2.8.4",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-format": "^1.0.2"
|
||||
}
|
||||
}
|
||||
BIN
client/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
2
client/public/robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
4
client/src-tauri/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
/gen/schemas
|
||||
5068
client/src-tauri/Cargo.lock
generated
Normal file
25
client/src-tauri/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
rust-version = "1.77.2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
name = "app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.4.1", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.8.5", features = [] }
|
||||
tauri-plugin-log = "2"
|
||||
3
client/src-tauri/build.rs
Normal file
@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
11
client/src-tauri/capabilities/default.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "enables the default permissions",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
]
|
||||
}
|
||||
BIN
client/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
client/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
client/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
client/src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
client/src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
client/src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
client/src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
client/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
client/src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
client/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
client/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
client/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
client/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
client/src-tauri/icons/icon.icns
Normal file
BIN
client/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
client/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
16
client/src-tauri/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
6
client/src-tauri/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
}
|
||||
37
client/src-tauri/tauri.conf.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "chad",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.tauri.dev",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
"devUrl": "http://localhost:3000",
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"beforeBuildCommand": "yarn build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Chad",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"resizable": false,
|
||||
"fullscreen": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
18
client/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
// https://nuxt.com/docs/guide/concepts/typescript
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.server.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.shared.json"
|
||||
},
|
||||
{
|
||||
"path": "./.nuxt/tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||