Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65583b1564 | |||
| 595354b7f0 | |||
|
|
d08b011596 | ||
| 12ce381abd | |||
| 2d30ac2863 | |||
| 0f218c1519 | |||
| 4b1a563850 | |||
| 169d43f0db | |||
| 47a464f08f | |||
| 4d5db12e1b | |||
| 4f59cbcf65 | |||
| 3b3f6b6e40 | |||
| 461cbc6f83 | |||
| a5cda8828f | |||
| 778f0a5687 | |||
| 2aca9bca08 | |||
| 7ed23df3e9 | |||
| 2ac88f1010 | |||
| c2cffd18de | |||
| bf38267c37 | |||
| 22c5fafb11 | |||
| 37683c42a9 | |||
| 2cbc75d7e3 | |||
| 6721f63d22 | |||
| b47643552f | |||
| 0ab3e15784 | |||
| 28c64edaf8 | |||
| 67a8dc7782 | |||
| 43a8b98a6a | |||
| 0f9a7e39ce | |||
| 8265e2d719 | |||
| 4f91309f7f | |||
| bcd457e2d6 | |||
| 8eef4fc477 |
@@ -1,16 +1,55 @@
|
|||||||
name: Deploy
|
name: Deploy
|
||||||
|
|
||||||
# on:
|
on:
|
||||||
# push:
|
workflow_dispatch:
|
||||||
# tags:
|
push:
|
||||||
# - "v[0-9]+.[0-9]+.[0-9]+"
|
tags:
|
||||||
|
- "v[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
|
||||||
# paths:
|
# paths:
|
||||||
# - ".gitea/workflows/deploy-client.yml"
|
# - ".gitea/workflows/deploy-client.yml"
|
||||||
# - "client/**"
|
# - "client/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-windows:
|
# publish-windows:
|
||||||
|
# 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 \
|
||||||
|
# -t chad-client-windows-builder \
|
||||||
|
# -f ./client/Dockerfile.windows \
|
||||||
|
# ./client \
|
||||||
|
# --build-arg COMMIT_SHA=${{ gitea.sha }} \
|
||||||
|
# --build-arg API_BASE_URL=${{ vars.API_BASE_URL }} \
|
||||||
|
# --build-arg TAURI_SIGNING_PRIVATE_KEY=${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
|
#
|
||||||
|
# docker create --name chad-client-windows-container chad-client-windows-builder
|
||||||
|
# mkdir -p artifacts
|
||||||
|
# docker cp chad-client-windows-container:/artifacts artifacts/
|
||||||
|
# docker rm chad-client-windows-container
|
||||||
|
# ls -la artifacts
|
||||||
|
#
|
||||||
|
# - name: Publish
|
||||||
|
# uses: akkuman/gitea-release-action@v1
|
||||||
|
# with:
|
||||||
|
# files: |
|
||||||
|
# artifacts/**
|
||||||
|
# draft: true
|
||||||
|
|
||||||
|
publish-web:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -26,57 +65,19 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
|
run: docker build -t chad-client -f ./client/Dockerfile.web ./client --build-arg COMMIT_SHA=${{ gitea.sha }} --build-arg API_BASE_URL=${{ vars.API_BASE_URL }}
|
||||||
|
|
||||||
|
- name: Stop old container
|
||||||
|
run: docker rm -f chad-client || true
|
||||||
|
|
||||||
|
- name: Run
|
||||||
run: |
|
run: |
|
||||||
docker build \
|
docker run -d \
|
||||||
-t chad-client-windows-builder \
|
--name chad-client \
|
||||||
-f ./client/Dockerfile.windows \
|
--network traefik \
|
||||||
./client \
|
--label "traefik.enable=true" \
|
||||||
--build-arg COMMIT_SHA=${{ gitea.sha }} \
|
--label "traefik.http.routers.chad-client.rule=Host(\`chad.koptilnya.xyz\`)" \
|
||||||
--build-arg API_BASE_URL=${{ vars.API_BASE_URL }} \
|
--label "traefik.http.routers.chad-client.entrypoints=websecure" \
|
||||||
--build-arg TAURI_SIGNING_PRIVATE_KEY=${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
--label "traefik.http.routers.chad-client.tls.certresolver=myresolver" \
|
||||||
|
--label "traefik.http.services.chad-client.loadbalancer.server.port=80" \
|
||||||
docker create --name chad-client-windows-container chad-client-windows-builder
|
chad-client:latest
|
||||||
mkdir -p artifacts
|
|
||||||
docker cp chad-client-windows-container:/artifacts artifacts/
|
|
||||||
docker rm chad-client-windows-container
|
|
||||||
ls -la artifacts
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
uses: akkuman/gitea-release-action@v1
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
artifacts/**
|
|
||||||
draft: true
|
|
||||||
|
|
||||||
# publish-web:
|
|
||||||
# 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 -t chad-client -f ./client/Dockerfile.web ./client --build-arg COMMIT_SHA=${{ gitea.sha }} --build-arg API_BASE_URL=${{ vars.API_BASE_URL }}
|
|
||||||
|
|
||||||
# - name: Stop old container
|
|
||||||
# run: docker rm -f chad-client || true
|
|
||||||
|
|
||||||
# - name: Run
|
|
||||||
# run: |
|
|
||||||
# docker run -d \
|
|
||||||
# --name chad-client \
|
|
||||||
# --network traefik \
|
|
||||||
# --label "traefik.enable=true" \
|
|
||||||
# --label "traefik.http.routers.chad-client.rule=Host(\`chad.koptilnya.xyz\`)" \
|
|
||||||
# --label "traefik.http.routers.chad-client.entrypoints=websecure" \
|
|
||||||
# --label "traefik.http.routers.chad-client.tls.certresolver=myresolver" \
|
|
||||||
# --label "traefik.http.services.chad-client.loadbalancer.server.port=80" \
|
|
||||||
# chad-client:latest
|
|
||||||
|
|||||||
Binary file not shown.
@@ -2,12 +2,10 @@ FROM node:lts-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN corepack enable
|
|
||||||
RUN yarn set version stable
|
|
||||||
|
|
||||||
COPY package.json yarn.lock .yarnrc.yml ./
|
COPY package.json yarn.lock .yarnrc.yml ./
|
||||||
|
COPY .yarn ./.yarn
|
||||||
|
|
||||||
RUN yarn install
|
RUN yarn install --immutable
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ARG COMMIT_SHA=unknown
|
ARG COMMIT_SHA=unknown
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ FROM node:lts AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# RUN corepack enable yarn && yarn set version stable
|
|
||||||
|
|
||||||
COPY package.json yarn.lock .yarnrc.yml ./
|
COPY package.json yarn.lock .yarnrc.yml ./
|
||||||
COPY .yarn ./.yarn
|
COPY .yarn ./.yarn
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,5 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
console.group('Build Info')
|
const route = useRoute()
|
||||||
console.log(`COMMIT_SHA: ${__COMMIT_SHA__}`)
|
|
||||||
console.groupEnd()
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -6,8 +6,42 @@ body {
|
|||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-image: radial-gradient(var(--p-surface-700), var(--p-surface-800));
|
||||||
|
background-size: 200% 200%;
|
||||||
|
background-position: left -100% top -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#__nuxt {
|
#__nuxt {
|
||||||
|
--p-scrollpanel-bar-size: 5px;
|
||||||
|
--p-scrollpanel-bar-background: var(--p-surface-950);
|
||||||
|
--p-divider-horizontal-margin: 2rem 0 1rem;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-divider {
|
||||||
|
&:first-child {
|
||||||
|
--p-divider-horizontal-margin: 1rem 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-scrollpanel-bar-y {
|
||||||
|
translate: -0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-select-overlay {
|
||||||
|
/* Force dropdown width to match computed min-width from PrimeVue internals. */
|
||||||
|
width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-select-label {
|
||||||
|
width: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-select-option-label {
|
||||||
|
min-width: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
}
|
||||||
10
client/app/components.d.ts
vendored
10
client/app/components.d.ts
vendored
@@ -13,16 +13,20 @@ declare module 'vue' {
|
|||||||
PrimeButton: typeof import('primevue/button')['default']
|
PrimeButton: typeof import('primevue/button')['default']
|
||||||
PrimeButtonGroup: typeof import('primevue/buttongroup')['default']
|
PrimeButtonGroup: typeof import('primevue/buttongroup')['default']
|
||||||
PrimeCard: typeof import('primevue/card')['default']
|
PrimeCard: typeof import('primevue/card')['default']
|
||||||
|
PrimeDivider: typeof import('primevue/divider')['default']
|
||||||
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
PrimeFloatLabel: typeof import('primevue/floatlabel')['default']
|
||||||
PrimeInputText: typeof import('primevue/inputtext')['default']
|
PrimeInputText: typeof import('primevue/inputtext')['default']
|
||||||
PrimeMenu: typeof import('primevue/menu')['default']
|
|
||||||
PrimePanel: typeof import('primevue/panel')['default']
|
|
||||||
PrimePassword: typeof import('primevue/password')['default']
|
PrimePassword: typeof import('primevue/password')['default']
|
||||||
PrimeProgressBar: typeof import('primevue/progressbar')['default']
|
PrimeScrollPanel: typeof import('primevue/scrollpanel')['default']
|
||||||
|
PrimeSelect: typeof import('primevue/select')['default']
|
||||||
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
PrimeSelectButton: typeof import('primevue/selectbutton')['default']
|
||||||
PrimeSlider: typeof import('primevue/slider')['default']
|
PrimeSlider: typeof import('primevue/slider')['default']
|
||||||
PrimeToast: typeof import('primevue/toast')['default']
|
PrimeToast: typeof import('primevue/toast')['default']
|
||||||
|
PrimeToggleSwitch: typeof import('primevue/toggleswitch')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
}
|
}
|
||||||
|
export interface ComponentCustomProperties {
|
||||||
|
Tooltip: typeof import('primevue/tooltip')['default']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="flex items-center justify-between gap-2 border-b-2 border-surface-800 px-3 py-3"
|
|
||||||
:class="{
|
|
||||||
'bg-surface-950': !secondary,
|
|
||||||
'bg-surface-900': secondary,
|
|
||||||
}"
|
|
||||||
style="height: 75px;"
|
|
||||||
>
|
|
||||||
<slot name="left">
|
|
||||||
<h1>
|
|
||||||
{{ title }}
|
|
||||||
</h1>
|
|
||||||
</slot>
|
|
||||||
|
|
||||||
<slot name="right" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
title: string
|
|
||||||
secondary?: boolean
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
@@ -1,106 +1,129 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="py-3">
|
<div
|
||||||
<div class="flex items-center gap-3 ">
|
class="overflow-hidden rounded-xl transition-[background-color]"
|
||||||
<PrimeAvatar
|
:class="{
|
||||||
icon="pi pi-user"
|
'hover:bg-surface-800 cursor-pointer': !isMe,
|
||||||
size="small"
|
'bg-surface-800': expanded,
|
||||||
/>
|
}"
|
||||||
|
>
|
||||||
|
<div class="p-3 flex items-center gap-3" @click="toggleExpand">
|
||||||
|
<PrimeAvatar size="small">
|
||||||
|
<template #icon>
|
||||||
|
<User :size="20" />
|
||||||
|
</template>
|
||||||
|
</PrimeAvatar>
|
||||||
|
|
||||||
<div class="flex-1">
|
<div class="flex-1 overflow-hidden text-sm leading-5 font-medium text-color whitespace-nowrap overflow-ellipsis">
|
||||||
<div class="text-sm leading-5 font-medium text-color whitespace-nowrap overflow-ellipsis">
|
{{ client.displayName || client.username }}
|
||||||
{{ client.displayName }}
|
|
||||||
</div>
|
|
||||||
<div v-if="client.username !== client.displayName" class="mt-1 text-xs leading-5 text-muted-color">
|
|
||||||
{{ client.username }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PrimeBadge v-if="audioConsumerPaused" severity="info" value="Muted" />
|
<div class="flex align-center gap-1">
|
||||||
<PrimeBadge v-if="isMe" severity="secondary" value="You" />
|
<PrimeBadge v-if="!!shareConsumer" v-tooltip.top="'Watch'" severity="success" value="Streaming" size="small" @click.stop="watchStream" />
|
||||||
|
|
||||||
<template v-if="!isMe">
|
<PrimeBadge v-if="premuted" severity="danger" value="Muted" size="small" />
|
||||||
<PrimeButton icon="pi pi-ellipsis-h" text size="small" severity="contrast" @click="menuRef?.toggle" />
|
<PrimeBadge v-else-if="client.outputMuted" severity="info" value="No sound" size="small" />
|
||||||
|
<PrimeBadge v-else-if="inputMuted" severity="info" value="Muted" size="small" />
|
||||||
|
|
||||||
<PrimeMenu ref="menu" popup :model="menuItems" style="translate: calc(-100% + 2rem) 0.5rem">
|
<PrimeBadge v-if="isMe" severity="secondary" value="You" size="small" class="shrink-0" />
|
||||||
<template #start>
|
</div>
|
||||||
<div class="px-4 py-3">
|
|
||||||
<div class="flex justify-between">
|
<Component :is="expanded ? ChevronUp : ChevronDown" v-if="!isMe" :size="20" class="text-muted-color" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CollapseTransition v-if="!isMe">
|
||||||
|
<div v-if="expanded">
|
||||||
|
<div class="px-3 pb-3">
|
||||||
|
<div class="flex justify-between text-sm mb-3">
|
||||||
<span>Volume</span>
|
<span>Volume</span>
|
||||||
<span>{{ volume }}</span>
|
<span>{{ volume }}</span>
|
||||||
</div>
|
</div>
|
||||||
<PrimeSlider v-model="volume" class="mt-4" :min="0" :max="1000" :step="volume < 200 ? 5 : 25" />
|
<PrimeSlider v-model="volume" :min="0" :max="1000" :step="volume < 200 ? 5 : 25" />
|
||||||
|
|
||||||
|
<div class="mt-3 flex gap-1 justify-end">
|
||||||
|
<PrimeButton size="small" variant="text" @click="premuted = !premuted">
|
||||||
|
{{ premuted ? 'Unmute' : 'Mute' }}
|
||||||
|
</PrimeButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</PrimeMenu>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</CollapseTransition>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ChadClient } from '#shared/types'
|
import type { ChadClient } from '#shared/types'
|
||||||
import type { MenuItem } from 'primevue/menuitem'
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
|
import { ChevronDown, ChevronUp, User } from 'lucide-vue-next'
|
||||||
|
import CollapseTransition from '~/components/CollapseTransition.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
client: ChadClient
|
client: ChadClient
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { inputMuted, outputMuted } = useApp()
|
const { outputMuted } = useApp()
|
||||||
const { getClientConsumers } = useMediasoup()
|
const { consumers: allConsumers, micProducer } = useMediasoup()
|
||||||
const { me } = useClients()
|
const { me } = useClients()
|
||||||
|
const { show } = useFullscreenVideo()
|
||||||
|
|
||||||
const menuRef = useTemplateRef<HTMLAudioElement>('menu')
|
const expanded = ref(false)
|
||||||
|
|
||||||
const volume = ref(100)
|
const volume = useLocalStorage<number>(computed(() => `CLIENT_VOLUME_${props.client.userId}`), 100, { writeDefaults: false })
|
||||||
|
const premuted = useLocalStorage<boolean>(computed(() => `CLIENT_PREMUTED_${props.client.userId}`), false, { writeDefaults: false })
|
||||||
const menuItems: MenuItem[] = [
|
|
||||||
{
|
|
||||||
label: 'Mute',
|
|
||||||
icon: 'pi pi-headphones',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'DM',
|
|
||||||
icon: 'pi pi-comment',
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const isMe = computed(() => {
|
const isMe = computed(() => {
|
||||||
return me.value && props.client.userId === me.value.userId
|
return me.value && props.client.userId === me.value.userId
|
||||||
})
|
})
|
||||||
|
|
||||||
const audioConsumer = computed(() => {
|
const consumers = computed(() => {
|
||||||
if (isMe.value)
|
return allConsumers.value.values().filter(consumer => consumer.appData.socketId === props.client.socketId).toArray()
|
||||||
return undefined
|
|
||||||
|
|
||||||
const consumers = getClientConsumers(props.client.socketId)
|
|
||||||
|
|
||||||
return consumers.find(consumer => consumer.track.kind === 'audio')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const audioConsumerPaused = computed(() => {
|
const audioConsumer = computed(() => {
|
||||||
if (isMe.value)
|
return consumers.value.find(consumer => consumer.track.kind === 'audio')
|
||||||
return false
|
})
|
||||||
|
|
||||||
const consumers = getClientConsumers(props.client.socketId)
|
const videoConsumers = computed(() => {
|
||||||
|
return consumers.value.filter(consumer => consumer.track.kind === 'video')
|
||||||
|
})
|
||||||
|
|
||||||
return consumers.find(consumer => consumer.track.kind === 'audio')?.paused
|
const shareConsumer = computed(() => {
|
||||||
|
return videoConsumers.value.find(consumer => consumer.appData.source === 'share')
|
||||||
})
|
})
|
||||||
|
|
||||||
const audioTrack = computed(() => {
|
const audioTrack = computed(() => {
|
||||||
return audioConsumer.value?.track
|
return audioConsumer.value?.track
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const audioConsumerPaused = ref(false)
|
||||||
|
|
||||||
|
const inputMuted = computed(() => {
|
||||||
|
if (isMe.value)
|
||||||
|
return micProducer.value?.paused ?? false
|
||||||
|
|
||||||
|
return premuted.value || audioConsumerPaused.value
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(allConsumers, () => {
|
||||||
|
audioConsumerPaused.value = audioConsumer.value?.paused ?? false
|
||||||
|
})
|
||||||
|
|
||||||
const { setGain } = useAudioContext(audioTrack)
|
const { setGain } = useAudioContext(audioTrack)
|
||||||
|
|
||||||
watch(volume, (volume) => {
|
watchEffect(() => {
|
||||||
if (outputMuted.value)
|
setGain((outputMuted.value || premuted.value) ? 0 : (volume.value * 0.01))
|
||||||
|
})
|
||||||
|
|
||||||
|
function toggleExpand() {
|
||||||
|
if (isMe.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
setGain(volume * 0.01)
|
expanded.value = !expanded.value
|
||||||
})
|
}
|
||||||
|
|
||||||
watch(outputMuted, (outputMuted) => {
|
function watchStream() {
|
||||||
setGain(outputMuted ? 0 : (volume.value * 0.01))
|
if (!shareConsumer.value)
|
||||||
})
|
return
|
||||||
|
|
||||||
|
show(new MediaStream([shareConsumer.value.track]))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
118
client/app/components/CollapseTransition.vue
Normal file
118
client/app/components/CollapseTransition.vue
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<Transition name="collapse-transition" v-on="bindings">
|
||||||
|
<slot />
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { RendererElement } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'CollapseTransition',
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
expanded: []
|
||||||
|
collapsed: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const bindings = {
|
||||||
|
beforeEnter(el: RendererElement) {
|
||||||
|
if (!el.dataset)
|
||||||
|
el.dataset = {}
|
||||||
|
|
||||||
|
el.dataset.oldPaddingTop = el.style.paddingTop
|
||||||
|
el.dataset.oldPaddingBottom = el.style.paddingBottom
|
||||||
|
el.dataset.elExistsHeight = el.style.height ?? undefined
|
||||||
|
el.style.maxHeight = 0
|
||||||
|
el.style.paddingTop = 0
|
||||||
|
el.style.paddingBottom = 0
|
||||||
|
},
|
||||||
|
|
||||||
|
enter(el: RendererElement) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
el.dataset.oldOverflow = el.style.overflow
|
||||||
|
if (el.dataset.elExistsHeight) {
|
||||||
|
el.style.maxHeight = el.dataset.elExistsHeight
|
||||||
|
}
|
||||||
|
else if (el.scrollHeight !== 0) {
|
||||||
|
el.style.maxHeight = `${el.scrollHeight}px`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
el.style.maxHeight = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
el.style.paddingTop = el.dataset.oldPaddingTop
|
||||||
|
el.style.paddingBottom = el.dataset.oldPaddingBottom
|
||||||
|
el.style.overflow = 'hidden'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
afterEnter(el: RendererElement) {
|
||||||
|
el.style.maxHeight = ''
|
||||||
|
el.style.overflow = el.dataset.oldOverflow
|
||||||
|
|
||||||
|
emit('expanded')
|
||||||
|
},
|
||||||
|
|
||||||
|
enterCancelled(el: RendererElement) {
|
||||||
|
reset(el)
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeLeave(el: RendererElement) {
|
||||||
|
if (!el.dataset)
|
||||||
|
el.dataset = {}
|
||||||
|
el.dataset.oldPaddingTop = el.style.paddingTop
|
||||||
|
el.dataset.oldPaddingBottom = el.style.paddingBottom
|
||||||
|
el.dataset.oldOverflow = el.style.overflow
|
||||||
|
|
||||||
|
el.style.maxHeight = `${el.scrollHeight}px`
|
||||||
|
el.style.overflow = 'hidden'
|
||||||
|
},
|
||||||
|
|
||||||
|
leave(el: RendererElement) {
|
||||||
|
if (el.scrollHeight !== 0) {
|
||||||
|
el.style.maxHeight = 0
|
||||||
|
el.style.paddingTop = 0
|
||||||
|
el.style.paddingBottom = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
afterLeave(el: RendererElement) {
|
||||||
|
reset(el)
|
||||||
|
|
||||||
|
emit('collapsed')
|
||||||
|
},
|
||||||
|
|
||||||
|
leaveCancelled(el: RendererElement) {
|
||||||
|
reset(el)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset(el: RendererElement) {
|
||||||
|
el.style.maxHeight = ''
|
||||||
|
el.style.overflow = el.dataset.oldOverflow
|
||||||
|
el.style.paddingTop = el.dataset.oldPaddingTop
|
||||||
|
el.style.paddingBottom = el.dataset.oldPaddingBottom
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.collapse-transition {
|
||||||
|
transition-property: height, padding-top, padding-bottom;
|
||||||
|
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
|
||||||
|
transition-duration: var(--default-transition-duration, 150ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-transition-leave-active,
|
||||||
|
.collapse-transition-enter-active {
|
||||||
|
transition-property: opacity, max-height, padding-top, padding-bottom;
|
||||||
|
transition-timing-function: var(--default-transition-timing-function, cubic-bezier(0.4, 0, 0.2, 1));
|
||||||
|
transition-duration: var(--default-transition-duration, 150ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-transition-leave-to,
|
||||||
|
.collapse-transition-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,76 +6,112 @@ import { useClients } from '~/composables/use-clients'
|
|||||||
export const useApp = createGlobalState(() => {
|
export const useApp = createGlobalState(() => {
|
||||||
const { clients } = useClients()
|
const { clients } = useClients()
|
||||||
const mediasoup = useMediasoup()
|
const mediasoup = useMediasoup()
|
||||||
|
const signaling = useSignaling()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const inputMuted = ref(false)
|
const ready = ref(false)
|
||||||
const outputMuted = ref(false)
|
const isTauri = computed(() => '__TAURI_INTERNALS__' in window)
|
||||||
|
const commitSha = __COMMIT_SHA__
|
||||||
|
const version = computedAsync(() => {
|
||||||
|
if (import.meta.dev) {
|
||||||
|
return 'dev'
|
||||||
|
}
|
||||||
|
else if (isTauri.value) {
|
||||||
|
return getVersion()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'web'
|
||||||
|
}
|
||||||
|
}, '-')
|
||||||
|
|
||||||
|
const inputMuted = computed(() => {
|
||||||
|
return !!mediasoup.micProducer.value?.paused
|
||||||
|
})
|
||||||
const previousInputMuted = ref(inputMuted.value)
|
const previousInputMuted = ref(inputMuted.value)
|
||||||
|
|
||||||
const isTauri = computed(() => '__TAURI_INTERNALS__' in window)
|
const outputMuted = ref(false)
|
||||||
|
|
||||||
const version = computedAsync(() => isTauri.value ? getVersion() : 'web', '-')
|
const sharingEnabled = computed(() => {
|
||||||
|
return !!mediasoup.shareProducer.value
|
||||||
|
})
|
||||||
|
|
||||||
function muteInput() {
|
async function muteInput() {
|
||||||
inputMuted.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function unmuteInput() {
|
|
||||||
inputMuted.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleInput() {
|
|
||||||
if (inputMuted.value)
|
if (inputMuted.value)
|
||||||
unmuteInput()
|
return
|
||||||
else
|
|
||||||
muteInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
function muteOutput() {
|
|
||||||
outputMuted.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function unmuteOutput() {
|
|
||||||
outputMuted.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleOutput() {
|
|
||||||
if (outputMuted.value)
|
|
||||||
unmuteOutput()
|
|
||||||
else
|
|
||||||
muteOutput()
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(inputMuted, async (inputMuted) => {
|
|
||||||
if (inputMuted) {
|
|
||||||
await mediasoup.pauseProducer('microphone')
|
await mediasoup.pauseProducer('microphone')
|
||||||
|
|
||||||
|
toast.add({ severity: 'info', summary: 'Microphone muted', closable: false, life: 1000 })
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
async function unmuteInput() {
|
||||||
|
if (!inputMuted.value)
|
||||||
|
return
|
||||||
|
|
||||||
if (outputMuted.value) {
|
if (outputMuted.value) {
|
||||||
outputMuted.value = false
|
await unmuteOutput()
|
||||||
}
|
|
||||||
await mediasoup.resumeProducer('microphone')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastText = inputMuted ? 'Microphone muted' : 'Microphone activated'
|
await mediasoup.resumeProducer('microphone')
|
||||||
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
|
|
||||||
|
toast.add({ severity: 'info', summary: 'Microphone activated', closable: false, life: 1000 })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleInput() {
|
||||||
|
if (inputMuted.value)
|
||||||
|
await unmuteInput()
|
||||||
|
else
|
||||||
|
await muteInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function muteOutput() {
|
||||||
|
if (outputMuted.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
outputMuted.value = true
|
||||||
|
|
||||||
|
previousInputMuted.value = inputMuted.value
|
||||||
|
|
||||||
|
await muteInput()
|
||||||
|
|
||||||
|
await signaling.socket.value?.emitWithAck('updateClient', {
|
||||||
|
outputMuted: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(outputMuted, (outputMuted) => {
|
toast.add({ severity: 'info', summary: 'Sound muted', closable: false, life: 1000 })
|
||||||
if (outputMuted) {
|
}
|
||||||
previousInputMuted.value = inputMuted.value
|
|
||||||
muteInput()
|
async function unmuteOutput() {
|
||||||
|
outputMuted.value = false
|
||||||
|
|
||||||
|
if (!previousInputMuted.value)
|
||||||
|
await unmuteInput()
|
||||||
|
|
||||||
|
await signaling.socket.value?.emitWithAck('updateClient', {
|
||||||
|
outputMuted: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.add({ severity: 'info', summary: 'Sound resumed', closable: false, life: 1000 })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleOutput() {
|
||||||
|
if (outputMuted.value)
|
||||||
|
await unmuteOutput()
|
||||||
|
else
|
||||||
|
await muteOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleShare() {
|
||||||
|
if (!mediasoup.shareProducer.value) {
|
||||||
|
await mediasoup.enableShare()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
inputMuted.value = previousInputMuted.value
|
await mediasoup.disableProducer('share')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastText = outputMuted ? 'Sound muted' : 'Sound resumed'
|
|
||||||
toast.add({ severity: 'info', summary: toastText, closable: false, life: 1000 })
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
ready,
|
||||||
clients,
|
clients,
|
||||||
inputMuted,
|
inputMuted,
|
||||||
muteInput,
|
muteInput,
|
||||||
@@ -87,5 +123,8 @@ export const useApp = createGlobalState(() => {
|
|||||||
toggleOutput,
|
toggleOutput,
|
||||||
version,
|
version,
|
||||||
isTauri,
|
isTauri,
|
||||||
|
commitSha,
|
||||||
|
toggleShare,
|
||||||
|
sharingEnabled,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
|||||||
const ctx = new (window.AudioContext || window.webkitAudioContext)()
|
const ctx = new (window.AudioContext || window.webkitAudioContext)()
|
||||||
|
|
||||||
const stream = new MediaStream()
|
const stream = new MediaStream()
|
||||||
|
const audioEl = new Audio()
|
||||||
|
|
||||||
const sourceNode = shallowRef<MediaStreamAudioSourceNode>()
|
const sourceNode = shallowRef<MediaStreamAudioSourceNode>()
|
||||||
const gainNode = ctx.createGain()
|
const gainNode = ctx.createGain()
|
||||||
|
|
||||||
let hackExecuted = false
|
watch(audioTrack, async (track, prevTrack) => {
|
||||||
|
|
||||||
watch(audioTrack, (track, prevTrack) => {
|
|
||||||
if (prevTrack)
|
if (prevTrack)
|
||||||
stream.removeTrack(prevTrack)
|
stream.removeTrack(prevTrack)
|
||||||
|
|
||||||
@@ -19,16 +18,14 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
|||||||
|
|
||||||
stream.addTrack(track)
|
stream.addTrack(track)
|
||||||
|
|
||||||
if (!hackExecuted) {
|
if (!audioEl.srcObject) {
|
||||||
const audioEl = new Audio()
|
|
||||||
audioEl.srcObject = stream
|
audioEl.srcObject = stream
|
||||||
audioEl.muted = true
|
audioEl.muted = true
|
||||||
hackExecuted = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceNode.value = ctx.createMediaStreamSource(stream)
|
sourceNode.value = ctx.createMediaStreamSource(stream)
|
||||||
|
|
||||||
connect()
|
await connect()
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
useEventListener(document, 'click', async () => {
|
useEventListener(document, 'click', async () => {
|
||||||
@@ -36,10 +33,16 @@ export default function useAudioContext(audioTrack: Ref<MediaStreamTrack | undef
|
|||||||
await ctx.resume()
|
await ctx.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
connect()
|
await connect()
|
||||||
}, { once: true })
|
}, { once: true })
|
||||||
|
|
||||||
function connect() {
|
onScopeDispose(() => {
|
||||||
|
audioEl.pause()
|
||||||
|
audioEl.srcObject = null
|
||||||
|
ctx.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function connect() {
|
||||||
if (!sourceNode.value || ctx.state === 'suspended')
|
if (!sourceNode.value || ctx.state === 'suspended')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
28
client/app/composables/use-devices.ts
Normal file
28
client/app/composables/use-devices.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { createGlobalState, useDevicesList } from '@vueuse/core'
|
||||||
|
|
||||||
|
export const useDevices = createGlobalState(() => {
|
||||||
|
const {
|
||||||
|
ensurePermissions,
|
||||||
|
permissionGranted,
|
||||||
|
videoInputs,
|
||||||
|
audioInputs,
|
||||||
|
audioOutputs,
|
||||||
|
} = useDevicesList()
|
||||||
|
|
||||||
|
async function getShareStream(fps = 30) {
|
||||||
|
return navigator.mediaDevices.getDisplayMedia({
|
||||||
|
audio: false,
|
||||||
|
video: {
|
||||||
|
displaySurface: 'monitor',
|
||||||
|
frameRate: { max: fps },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
videoInputs: computed<MediaDeviceInfo[]>(() => JSON.parse(JSON.stringify(videoInputs.value))),
|
||||||
|
audioInputs: computed<MediaDeviceInfo[]>(() => JSON.parse(JSON.stringify(audioInputs.value))),
|
||||||
|
audioOutputs: computed<MediaDeviceInfo[]>(() => JSON.parse(JSON.stringify(audioOutputs.value))),
|
||||||
|
getShareStream,
|
||||||
|
}
|
||||||
|
})
|
||||||
65
client/app/composables/use-fullscreen-video.ts
Normal file
65
client/app/composables/use-fullscreen-video.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { createGlobalState, useEventListener } from '@vueuse/core'
|
||||||
|
|
||||||
|
export const useFullscreenVideo = createGlobalState(() => {
|
||||||
|
const videoEl = shallowRef<HTMLVideoElement>()
|
||||||
|
|
||||||
|
const visible = computed(() => !!videoEl.value)
|
||||||
|
|
||||||
|
async function show(stream: MediaStream) {
|
||||||
|
if (videoEl.value) {
|
||||||
|
videoEl.value.srcObject = stream
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const el = document.createElement('video')
|
||||||
|
el.srcObject = stream
|
||||||
|
el.autoplay = true
|
||||||
|
el.playsInline = true
|
||||||
|
el.controls = false
|
||||||
|
el.muted = true
|
||||||
|
// el.style.position = 'fixed'
|
||||||
|
// el.style.top = '0'
|
||||||
|
// el.style.left = '0'
|
||||||
|
// el.style.width = '1px'
|
||||||
|
// el.style.height = '1px'
|
||||||
|
// el.style.opacity = '0'
|
||||||
|
// el.style.pointerEvents = 'none'
|
||||||
|
|
||||||
|
document.body.appendChild(el)
|
||||||
|
|
||||||
|
videoEl.value = el
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.getTracks().forEach(t =>
|
||||||
|
t.addEventListener('ended', hide),
|
||||||
|
)
|
||||||
|
videoEl.value.addEventListener('ended', hide)
|
||||||
|
|
||||||
|
await videoEl.value.requestFullscreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
if (!videoEl.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
(videoEl.value.srcObject as MediaStream).getTracks().forEach(t =>
|
||||||
|
t.removeEventListener('ended', hide),
|
||||||
|
)
|
||||||
|
videoEl.value.removeEventListener('ended', hide)
|
||||||
|
|
||||||
|
videoEl.value?.remove()
|
||||||
|
videoEl.value = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
useEventListener(document, 'fullscreenchange', () => {
|
||||||
|
if (!document.fullscreenElement && videoEl.value) {
|
||||||
|
videoEl.value?.remove()
|
||||||
|
videoEl.value = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
visible,
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { ChadClient } from '#shared/types'
|
import type { MediaKind, ProducerOptions } from 'mediasoup-client/types'
|
||||||
import { createSharedComposable } from '@vueuse/core'
|
import { createSharedComposable } from '@vueuse/core'
|
||||||
import * as mediasoupClient from 'mediasoup-client'
|
import * as mediasoupClient from 'mediasoup-client'
|
||||||
|
import { useDevices } from '~/composables/use-devices'
|
||||||
import { usePreferences } from '~/composables/use-preferences'
|
import { usePreferences } from '~/composables/use-preferences'
|
||||||
import { useSignaling } from '~/composables/use-signaling'
|
import { useSignaling } from '~/composables/use-signaling'
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
const signaling = useSignaling()
|
const signaling = useSignaling()
|
||||||
const { addClient, removeClient } = useClients()
|
const { addClient, removeClient } = useClients()
|
||||||
const preferences = usePreferences()
|
const preferences = usePreferences()
|
||||||
const { me } = useAuth()
|
const { getShareStream } = useDevices()
|
||||||
|
|
||||||
const device = shallowRef<mediasoupClient.Device>()
|
const device = shallowRef<mediasoupClient.Device>()
|
||||||
const rtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
|
const rtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
|
||||||
@@ -158,7 +159,7 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
producerId,
|
producerId,
|
||||||
kind,
|
kind,
|
||||||
rtpParameters,
|
rtpParameters,
|
||||||
streamId: `${socketId}-${appData.share ? 'share' : 'mic-webcam'}`,
|
streamId: `${socketId}-${appData.source === 'share' ? 'share' : 'mic-webcam'}`,
|
||||||
appData: { ...appData, socketId },
|
appData: { ...appData, socketId },
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -213,8 +214,6 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
|
|
||||||
consumer.pause()
|
consumer.pause()
|
||||||
|
|
||||||
console.log(consumerId)
|
|
||||||
|
|
||||||
triggerRef(consumers)
|
triggerRef(consumers)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -230,34 +229,77 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
})
|
})
|
||||||
}, { immediate: true, flush: 'sync' })
|
}, { immediate: true, flush: 'sync' })
|
||||||
|
|
||||||
function getClientConsumers(socketId: ChadClient['socketId']) {
|
async function enableProducer(type: ProducerType, options: ProducerOptions) {
|
||||||
return consumers.value.values().filter(consumer => consumer.appData.socketId === socketId)
|
const producer = getProducerByType(type)
|
||||||
}
|
|
||||||
|
|
||||||
async function enableMic() {
|
if (producer.value)
|
||||||
if (micProducer.value)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if (!device.value || !sendTransport.value)
|
if (!device.value || !sendTransport.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
if (!device.value.canProduce('audio'))
|
if (!options.track)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (!device.value.canProduce(options.track.kind as MediaKind))
|
||||||
|
return
|
||||||
|
|
||||||
|
producer.value = await sendTransport.value.produce(options)
|
||||||
|
|
||||||
|
producers.value.set(producer.value.id, producer.value)
|
||||||
|
triggerRef(producers)
|
||||||
|
triggerRef(producer)
|
||||||
|
|
||||||
|
producer.value.on('transportclose', () => {
|
||||||
|
micProducer.value = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
producer.value.on('trackended', () => {
|
||||||
|
disableProducer(type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableProducer(type: ProducerType) {
|
||||||
|
const producer = getProducerByType(type)
|
||||||
|
|
||||||
|
if (!signaling.socket.value || !producer.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
producers.value.delete(producer.value.id)
|
||||||
|
|
||||||
|
try {
|
||||||
|
producer.value.close()
|
||||||
|
|
||||||
|
await signaling.socket.value.emitWithAck('closeProducer', {
|
||||||
|
producerId: producer.value.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
triggerRef(producers)
|
||||||
|
triggerRef(producer)
|
||||||
|
}
|
||||||
|
|
||||||
|
producer.value = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enableMic() {
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
audio: {
|
audio: {
|
||||||
autoGainControl: false,
|
deviceId: { exact: preferences.inputDeviceId.value },
|
||||||
noiseSuppression: true,
|
autoGainControl: { exact: preferences.autoGainControl.value },
|
||||||
echoCancellation: false,
|
echoCancellation: { exact: preferences.echoCancellation.value },
|
||||||
channelCount: 2,
|
noiseSuppression: { exact: preferences.noiseSuppression.value },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const track = stream.getAudioTracks()[0]
|
const track = stream.getAudioTracks()[0]
|
||||||
|
|
||||||
if (!track)
|
if (!track)
|
||||||
return
|
return
|
||||||
|
|
||||||
micProducer.value = await sendTransport.value.produce({
|
await enableProducer('microphone', {
|
||||||
track,
|
track,
|
||||||
codecOptions: {
|
codecOptions: {
|
||||||
opusStereo: true,
|
opusStereo: true,
|
||||||
@@ -265,38 +307,36 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
opusFec: false, // Фиксит пакет лос
|
opusFec: false, // Фиксит пакет лос
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
producers.value.set(micProducer.value.id, micProducer.value)
|
|
||||||
triggerRef(producers)
|
|
||||||
|
|
||||||
micProducer.value.on('transportclose', () => {
|
|
||||||
micProducer.value = undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
micProducer.value.on('trackended', () => {
|
|
||||||
disableMic()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function disableMic() {
|
async function disableMic() {
|
||||||
if (!signaling.socket.value || !micProducer.value)
|
await disableProducer('microphone')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enableShare() {
|
||||||
|
if (!device.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
producers.value.delete(micProducer.value.id)
|
const stream = await getShareStream(preferences.shareFps.value)
|
||||||
triggerRef(producers)
|
|
||||||
|
|
||||||
try {
|
const track = stream.getVideoTracks()[0]
|
||||||
micProducer.value.close()
|
|
||||||
|
|
||||||
await signaling.socket.value.emitWithAck('closeProducer', {
|
if (!track)
|
||||||
producerId: micProducer.value.id,
|
return
|
||||||
|
|
||||||
|
await enableProducer('share', {
|
||||||
|
track,
|
||||||
|
codec: device.value.rtpCapabilities.codecs?.find(
|
||||||
|
c => c.mimeType.toLowerCase() === 'video/h264',
|
||||||
|
),
|
||||||
|
codecOptions: {
|
||||||
|
videoGoogleStartBitrate: 1000,
|
||||||
|
},
|
||||||
|
appData: {
|
||||||
|
source: 'share',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch {
|
|
||||||
}
|
|
||||||
|
|
||||||
micProducer.value = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pauseProducer(type: ProducerType) {
|
async function pauseProducer(type: ProducerType) {
|
||||||
if (!signaling.socket.value)
|
if (!signaling.socket.value)
|
||||||
@@ -304,21 +344,25 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
|
|
||||||
const producer = getProducerByType(type)
|
const producer = getProducerByType(type)
|
||||||
|
|
||||||
if (!producer)
|
if (!producer.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
if (producer.paused)
|
if (producer.value.paused)
|
||||||
return
|
return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
producer.pause()
|
producer.value.pause()
|
||||||
|
|
||||||
await signaling.socket.value.emitWithAck('pauseProducer', {
|
await signaling.socket.value.emitWithAck('pauseProducer', {
|
||||||
producerId: producer.id,
|
producerId: producer.value.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
producer.resume()
|
producer.value.resume()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
triggerRef(producers)
|
||||||
|
triggerRef(producer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,18 +372,22 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
|
|
||||||
const producer = getProducerByType(type)
|
const producer = getProducerByType(type)
|
||||||
|
|
||||||
if (!producer)
|
if (!producer.value)
|
||||||
return
|
return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
producer.resume()
|
producer.value.resume()
|
||||||
|
|
||||||
await signaling.socket.value.emitWithAck('resumeProducer', {
|
await signaling.socket.value.emitWithAck('resumeProducer', {
|
||||||
producerId: producer.id,
|
producerId: producer.value.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
producer.pause()
|
producer.value.pause()
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
triggerRef(producers)
|
||||||
|
triggerRef(producer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,14 +398,28 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
function getProducerByType(type: ProducerType) {
|
function getProducerByType(type: ProducerType) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'microphone':
|
case 'microphone':
|
||||||
return micProducer.value
|
return micProducer
|
||||||
case 'camera':
|
case 'camera':
|
||||||
return cameraProducer.value
|
return cameraProducer
|
||||||
case 'share':
|
case 'share':
|
||||||
return shareProducer.value
|
return shareProducer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch([
|
||||||
|
preferences.inputDeviceId,
|
||||||
|
preferences.echoCancellation,
|
||||||
|
preferences.autoGainControl,
|
||||||
|
preferences.noiseSuppression,
|
||||||
|
], async ([inputDeviceId]) => {
|
||||||
|
await disableMic()
|
||||||
|
|
||||||
|
if (!inputDeviceId)
|
||||||
|
return
|
||||||
|
|
||||||
|
await enableMic()
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init,
|
init,
|
||||||
consumers,
|
consumers,
|
||||||
@@ -369,8 +431,9 @@ export const useMediasoup = createSharedComposable(() => {
|
|||||||
micProducer,
|
micProducer,
|
||||||
cameraProducer,
|
cameraProducer,
|
||||||
shareProducer,
|
shareProducer,
|
||||||
getClientConsumers,
|
|
||||||
pauseProducer,
|
pauseProducer,
|
||||||
resumeProducer,
|
resumeProducer,
|
||||||
|
enableShare,
|
||||||
|
disableProducer,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,22 +1,68 @@
|
|||||||
import { createGlobalState, useDevicesList } from '@vueuse/core'
|
import chadApi from '#shared/chad-api'
|
||||||
|
import { createGlobalState, useLocalStorage, watchDebounced } from '@vueuse/core'
|
||||||
|
|
||||||
|
export interface SyncedPreferences {
|
||||||
|
toggleInputHotkey: string
|
||||||
|
toggleOutputHotkey: string
|
||||||
|
volumes: Record<Client['id'], number>
|
||||||
|
}
|
||||||
|
|
||||||
export const usePreferences = createGlobalState(() => {
|
export const usePreferences = createGlobalState(() => {
|
||||||
const inputDeviceId = shallowRef<MediaDeviceInfo['deviceId']>()
|
const { videoInputs, audioInputs, audioOutputs } = useDevices()
|
||||||
const outputDeviceId = shallowRef<MediaDeviceInfo['deviceId']>()
|
|
||||||
|
|
||||||
const {
|
const fetched = ref(false)
|
||||||
ensurePermissions,
|
|
||||||
permissionGranted,
|
const inputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('INPUT_DEVICE_ID', 'default')
|
||||||
videoInputs,
|
const outputDeviceId = useLocalStorage<MediaDeviceInfo['deviceId']>('OUTPUT_DEVICE_ID', 'default')
|
||||||
audioInputs,
|
|
||||||
audioOutputs,
|
const autoGainControl = useLocalStorage('AUTO_GAIN_CONTROL', false)
|
||||||
} = useDevicesList()
|
const noiseSuppression = useLocalStorage('NOISE_SUPPRESSION', true)
|
||||||
|
const echoCancellation = useLocalStorage('ECHO_CANCELLATION', true)
|
||||||
|
|
||||||
|
const shareFps = useLocalStorage('SHARE_FPS', 30)
|
||||||
|
|
||||||
|
const toggleInputHotkey = ref<SyncedPreferences['toggleInputHotkey']>('')
|
||||||
|
const toggleOutputHotkey = ref<SyncedPreferences['toggleOutputHotkey']>('')
|
||||||
|
|
||||||
|
const inputDeviceExist = computed(() => {
|
||||||
|
return audioInputs.value.some(device => device.deviceId === inputDeviceId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const outputDeviceExist = computed(() => {
|
||||||
|
return audioOutputs.value.some(device => device.deviceId === outputDeviceId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
watchDebounced(
|
||||||
|
[toggleInputHotkey, toggleOutputHotkey],
|
||||||
|
async ([toggleInputHotkey, toggleOutputHotkey]) => {
|
||||||
|
try {
|
||||||
|
await chadApi(
|
||||||
|
'/preferences',
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
body: {
|
||||||
|
toggleInputHotkey,
|
||||||
|
toggleOutputHotkey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
},
|
||||||
|
{ debounce: 1000 },
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
fetched,
|
||||||
inputDeviceId,
|
inputDeviceId,
|
||||||
outputDeviceId,
|
outputDeviceId,
|
||||||
videoInputs,
|
autoGainControl,
|
||||||
audioInputs,
|
noiseSuppression,
|
||||||
audioOutputs,
|
echoCancellation,
|
||||||
|
shareFps,
|
||||||
|
toggleInputHotkey,
|
||||||
|
toggleOutputHotkey,
|
||||||
|
inputDeviceExist,
|
||||||
|
outputDeviceExist,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -66,11 +66,9 @@ export const useSignaling = createSharedComposable(() => {
|
|||||||
|
|
||||||
const uri = host ? `${protocol}//${host}` : ``
|
const uri = host ? `${protocol}//${host}` : ``
|
||||||
|
|
||||||
socket.value = io(`http://localhost:4000/webrtc`, {
|
socket.value = io(`${uri}/webrtc`, {
|
||||||
path: `/chad/ws`,
|
path: `${pathname}/ws`,
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
// socket.value = io(`${uri}/webrtc`, {
|
|
||||||
// path: `${pathname}/ws`,
|
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
auth: {
|
auth: {
|
||||||
userId: me.value.id,
|
userId: me.value.id,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const useUpdater = createGlobalState(() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
lastUpdate,
|
lastUpdate,
|
||||||
|
checking,
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ const options = computed(() => {
|
|||||||
{
|
{
|
||||||
label: 'Register',
|
label: 'Register',
|
||||||
routeName: 'Register',
|
routeName: 'Register',
|
||||||
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,52 +1,125 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-2 h-screen">
|
<div class="grid grid-cols-[1fr_1.25fr] gap-2 p-2 h-screen grid-rows-[auto_1fr]">
|
||||||
<div class="flex flex-col shadow-xl shadow-surface-950 overflow-y-hidden">
|
<div
|
||||||
<AppHeader title="Шальные сиськи 18+">
|
class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950"
|
||||||
<template #right>
|
>
|
||||||
|
<div class="inline-flex items-center gap-3">
|
||||||
|
<PrimeBadge class="opacity-50" severity="secondary" :value="version" size="small" />
|
||||||
|
<PrimeBadge :severity="connected ? 'success' : 'danger' " />
|
||||||
|
</div>
|
||||||
|
|
||||||
<PrimeButtonGroup class="ml-auto">
|
<PrimeButtonGroup class="ml-auto">
|
||||||
<PrimeButton
|
<PrimeButton :severity="inputMuted ? 'info' : undefined" outlined @click="toggleInput">
|
||||||
icon="pi pi-microphone" size="large" :severity="inputMuted ? 'contrast' : 'secondary'"
|
<template #icon>
|
||||||
:outlined="!inputMuted" @click="toggleInput"
|
<Component :is="inputMuted ? MicOff : Mic" />
|
||||||
/>
|
</template>
|
||||||
<PrimeButton
|
</PrimeButton>
|
||||||
icon="pi pi-headphones" size="large" :severity="outputMuted ? 'contrast' : 'secondary'"
|
<PrimeButton :severity="outputMuted ? 'info' : undefined" outlined @click="toggleOutput">
|
||||||
:outlined="!outputMuted" @click="toggleOutput"
|
<template #icon>
|
||||||
/>
|
<Component :is="outputMuted ? VolumeOff : Volume2" />
|
||||||
|
</template>
|
||||||
|
</PrimeButton>
|
||||||
</PrimeButtonGroup>
|
</PrimeButtonGroup>
|
||||||
|
|
||||||
<PrimeButton icon="pi pi-cog" size="large" :text="!inPreferences" :severity="inPreferences ? 'contrast' : 'secondary'" @click="onClickPreferences" />
|
<PrimeButton :severity="sharingEnabled ? 'success' : undefined" outlined @click="toggleShare">
|
||||||
|
<template #icon>
|
||||||
|
<Component :is="sharingEnabled ? ScreenShareOff : ScreenShare" />
|
||||||
</template>
|
</template>
|
||||||
</AppHeader>
|
</PrimeButton>
|
||||||
|
|
||||||
<div v-auto-animate class="p-3 overflow-y-auto flex-1 bg-surface-900 overflow-hidden divide-y divide-surface-800">
|
|
||||||
<ClientRow v-for="client of clients" :key="client.id" :client="client" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-y-auto">
|
<div
|
||||||
|
class="flex items-center justify-center rounded-xl p-3 bg-surface-950"
|
||||||
|
>
|
||||||
|
<PrimeSelectButton
|
||||||
|
v-model="activeTab"
|
||||||
|
:options="tabs"
|
||||||
|
data-key="id"
|
||||||
|
:allow-empty="false"
|
||||||
|
style="--p-togglebutton-content-padding: 0.25rem 0.5rem"
|
||||||
|
>
|
||||||
|
<template #option="{ option }">
|
||||||
|
<Component :is="option.icon" size="24" />
|
||||||
|
</template>
|
||||||
|
</PrimeSelectButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PrimeScrollPanel class="bg-surface-900 rounded-xl" style="min-height: 0">
|
||||||
|
<div v-auto-animate class="p-3 space-y-1">
|
||||||
|
<ClientRow v-for="client of clients" :key="client.userId" :client="client" />
|
||||||
|
</div>
|
||||||
|
</PrimeScrollPanel>
|
||||||
|
|
||||||
|
<PrimeScrollPanel class="bg-surface-900 rounded-xl" style="min-height: 0">
|
||||||
|
<div class="p-3">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PrimeScrollPanel>
|
||||||
|
|
||||||
<div class="fixed top-3 right-3 inline-flex items-center gap-3">
|
|
||||||
<PrimeBadge v-if="isTauri" class="opacity-50" severity="secondary" :value="version" size="small" />
|
|
||||||
<PrimeBadge :severity="connected ? 'success' : 'danger' " />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { clients, inputMuted, toggleInput, outputMuted, toggleOutput, version, isTauri } = useApp()
|
import {
|
||||||
|
MessageCircle,
|
||||||
|
Mic,
|
||||||
|
MicOff,
|
||||||
|
ScreenShare,
|
||||||
|
ScreenShareOff,
|
||||||
|
Settings,
|
||||||
|
UserPen,
|
||||||
|
Volume2,
|
||||||
|
VolumeOff,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const {
|
||||||
|
version,
|
||||||
|
clients,
|
||||||
|
inputMuted,
|
||||||
|
outputMuted,
|
||||||
|
sharingEnabled,
|
||||||
|
toggleInput,
|
||||||
|
toggleOutput,
|
||||||
|
toggleShare,
|
||||||
|
} = useApp()
|
||||||
const { connect, connected } = useSignaling()
|
const { connect, connected } = useSignaling()
|
||||||
|
|
||||||
|
interface Tab {
|
||||||
|
id: string
|
||||||
|
icon: Component
|
||||||
|
onClick: () => void | Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const inPreferences = computed(() => {
|
const tabs: Tab[] = [
|
||||||
return route.name === 'Preferences'
|
{
|
||||||
})
|
id: 'Index',
|
||||||
|
icon: MessageCircle,
|
||||||
|
onClick: () => {
|
||||||
|
navigateTo({ name: 'Index' })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Profile',
|
||||||
|
icon: UserPen,
|
||||||
|
onClick: () => {
|
||||||
|
navigateTo({ name: 'Profile' })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'Preferences',
|
||||||
|
icon: Settings,
|
||||||
|
onClick: () => {
|
||||||
|
navigateTo({ name: 'Preferences' })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
function onClickPreferences() {
|
const activeTab = ref<Tab>(tabs.find(tab => tab.id === route.name) ?? tabs[0]!)
|
||||||
navigateTo(!inPreferences.value ? { name: 'Preferences' } : '/')
|
|
||||||
}
|
watch(activeTab, (activeTab) => {
|
||||||
|
activeTab.onClick()
|
||||||
|
})
|
||||||
|
|
||||||
connect()
|
connect()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||||
// if (import.meta.dev || import.meta.server)
|
if (import.meta.dev || import.meta.server)
|
||||||
// return
|
return
|
||||||
|
|
||||||
const { isTauri } = useApp()
|
const { isTauri } = useApp()
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import chadApi from '#shared/chad-api'
|
|||||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||||
const { me, setMe } = useAuth()
|
const { me, setMe } = useAuth()
|
||||||
|
|
||||||
if (!me.value && !from?.name) {
|
if (!me.value) {
|
||||||
try {
|
try {
|
||||||
setMe(await chadApi('/me'))
|
setMe(await chadApi('/me', { method: 'GET' }))
|
||||||
|
|
||||||
|
if (to.meta.auth !== false)
|
||||||
|
return navigateTo({ name: 'Index' })
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
if (to.meta.auth !== 'guest') {
|
if (to.meta.auth !== 'guest') {
|
||||||
@@ -15,6 +18,6 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (me.value && to.meta.auth === 'guest') {
|
if (me.value && to.meta.auth === 'guest') {
|
||||||
return navigateTo('/')
|
return navigateTo({ name: 'Index' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
26
client/app/middleware/02.user-preferences.global.ts
Normal file
26
client/app/middleware/02.user-preferences.global.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { SyncedPreferences } from '~/composables/use-preferences'
|
||||||
|
import chadApi from '#shared/chad-api'
|
||||||
|
|
||||||
|
export default defineNuxtRouteMiddleware(async () => {
|
||||||
|
const { me } = useAuth()
|
||||||
|
|
||||||
|
if (!me.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
const { fetched, toggleInputHotkey, toggleOutputHotkey } = usePreferences()
|
||||||
|
|
||||||
|
if (fetched.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const preferences = await chadApi<SyncedPreferences>('/preferences', { method: 'GET' })
|
||||||
|
|
||||||
|
if (!preferences)
|
||||||
|
return
|
||||||
|
|
||||||
|
toggleInputHotkey.value = preferences.toggleInputHotkey ?? toggleInputHotkey.value
|
||||||
|
toggleOutputHotkey.value = preferences.toggleOutputHotkey ?? toggleOutputHotkey.value
|
||||||
|
fetched.value = true
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
})
|
||||||
@@ -1,62 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-3 p-3 pt-12">
|
<div>
|
||||||
<PrimePanel header="Clients" toggleable collapsed :pt="{ content: { class: 'divide-y divide-surface-800 py-4' } }">
|
<div class="flex items-center justify-center">
|
||||||
<dl v-for="client in clients" :key="client.socketId" class="">
|
<PrimeCard>
|
||||||
<div v-for="(value, key) in client" :key="key" class="py-2">
|
<template #content>
|
||||||
<dt class="font-bold">
|
The chat is under development.
|
||||||
{{ key }}
|
</template>
|
||||||
</dt>
|
</PrimeCard>
|
||||||
<dd class="pl-8">
|
|
||||||
{{ value }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
|
||||||
</PrimePanel>
|
|
||||||
|
|
||||||
<PrimePanel header="Producers" toggleable collapsed :pt="{ content: { class: 'divide-y divide-surface-800 py-4' } }">
|
|
||||||
<dl v-for="[_, producer] in Array.from(producers)" :key="producer.id" class="">
|
|
||||||
<div v-for="key in ['id', 'paused']" :key="key" class="py-2">
|
|
||||||
<dt class="font-bold">
|
|
||||||
{{ key }}
|
|
||||||
</dt>
|
|
||||||
<dd class="pl-8">
|
|
||||||
{{ producer[key] }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
|
||||||
</PrimePanel>
|
|
||||||
|
|
||||||
<PrimePanel header="Consumers" toggleable collapsed :pt="{ content: { class: 'divide-y divide-surface-800 py-4' } }">
|
|
||||||
<dl v-for="[_, consumer] in Array.from(consumers)" :key="consumer.id" class="">
|
|
||||||
<div v-for="key in ['id', 'paused']" :key="key" class="py-2">
|
|
||||||
<dt class="font-bold">
|
|
||||||
{{ key }}
|
|
||||||
</dt>
|
|
||||||
<dd class="pl-8">
|
|
||||||
{{ consumer[key] }}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</PrimePanel>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <div class="flex items-center justify-center p-3"> -->
|
|
||||||
<!-- <PrimeCard> -->
|
|
||||||
<!-- <template #subtitle> -->
|
|
||||||
<!-- Important information -->
|
|
||||||
<!-- </template> -->
|
|
||||||
|
|
||||||
<!-- <template #content> -->
|
|
||||||
<!-- The chat is under development. -->
|
|
||||||
<!-- </template> -->
|
|
||||||
<!-- </PrimeCard> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { clients } = useClients()
|
|
||||||
const { producers, consumers } = useMediasoup()
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
name: 'Index',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,95 +1,174 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<AppHeader title="Preferences" secondary />
|
<PrimeDivider align="left">
|
||||||
|
Audio
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
<form class="flex flex-col gap-3 p-3" @submit.prevent="save">
|
|
||||||
<PrimeFloatLabel variant="on">
|
<PrimeFloatLabel variant="on">
|
||||||
<PrimeInputText id="username" v-model="displayName" size="large" fluid autocomplete="off" />
|
<PrimeSelect
|
||||||
<label for="username">Username</label>
|
v-model="inputDeviceId"
|
||||||
|
:options="audioInputs"
|
||||||
|
option-label="label"
|
||||||
|
option-value="deviceId"
|
||||||
|
input-id="inputDevice"
|
||||||
|
fluid
|
||||||
|
:invalid="!inputDeviceExist"
|
||||||
|
/>
|
||||||
|
<label for="inputDevice">Input device</label>
|
||||||
</PrimeFloatLabel>
|
</PrimeFloatLabel>
|
||||||
|
|
||||||
<PrimeButton label="Save" type="submit" :disabled="!valid" />
|
<div class="flex items-center gap-2 mt-3">
|
||||||
</form>
|
<PrimeToggleSwitch v-model="autoGainControl" input-id="autoGainControl" />
|
||||||
|
<label for="autoGainControl">Auto Gain Control</label>
|
||||||
<div v-if="isTauri" class="p-3">
|
|
||||||
<PrimeButton label="Check for Updates" fluid severity="info" @click="onCheckForUpdates" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-3">
|
<div class="flex items-center gap-2 mt-3">
|
||||||
<PrimeButton label="Logout" fluid severity="danger" @click="logout()" />
|
<PrimeToggleSwitch v-model="echoCancellation" input-id="echoCancellation" />
|
||||||
</div>
|
<label for="echoCancellation">Echo Cancellation</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PrimeToast position="bottom-center" group="updater">
|
<div class="flex items-center gap-2 mt-3">
|
||||||
<template #container="slotProps">
|
<PrimeToggleSwitch v-model="noiseSuppression" input-id="noiseSuppression" />
|
||||||
<div class="p-3">
|
<label for="noiseSuppression">Noise Suppression</label>
|
||||||
<div class="font-medium text-lg mb-4">
|
|
||||||
{{ slotProps.message.detail }}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3">
|
|
||||||
<PrimeButton size="small" label="Update now" @click="() => {}" />
|
<!-- <PrimeFloatLabel variant="on"> -->
|
||||||
<PrimeButton size="small" label="Later" severity="secondary" outlined @click="slotProps.closeCallback()" />
|
<!-- <PrimeSelect -->
|
||||||
|
<!-- v-model="outputDeviceId" -->
|
||||||
|
<!-- :options="audioOutputs" -->
|
||||||
|
<!-- option-label="label" -->
|
||||||
|
<!-- option-value="deviceId" -->
|
||||||
|
<!-- fluid -->
|
||||||
|
<!-- class="mt-6" -->
|
||||||
|
<!-- input-id="outputDevice" -->
|
||||||
|
<!-- :invalid="!outputDeviceExist" -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- /> -->
|
||||||
|
<!-- <label for="outputDevice">Output device</label> -->
|
||||||
|
<!-- </PrimeFloatLabel> -->
|
||||||
|
|
||||||
|
<PrimeDivider align="left">
|
||||||
|
Video
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between text-sm mb-3">
|
||||||
|
<span>FPS</span>
|
||||||
|
<span>{{ shareFps }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<PrimeSlider v-model="shareFps" class="mx-[10px]" :min="30" :max="60" :step="5" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template v-if="isTauri">
|
||||||
|
<PrimeDivider align="left">
|
||||||
|
Hotkeys
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
|
<PrimeFloatLabel variant="on">
|
||||||
|
<PrimeInputText id="microphoneToggle" :model-value="toggleInputHotkey" fluid @keydown="setupToggleInputHotkey" />
|
||||||
|
<label for="microphoneToggle">Toggle microphone</label>
|
||||||
|
</PrimeFloatLabel>
|
||||||
|
|
||||||
|
<PrimeFloatLabel variant="on" class="mt-3">
|
||||||
|
<PrimeInputText id="soundToggle" :model-value="toggleOutputHotkey" fluid @keydown="setupToggleOutputHotkey" />
|
||||||
|
<label for="soundToggle">Toggle sound</label>
|
||||||
|
</PrimeFloatLabel>
|
||||||
</template>
|
</template>
|
||||||
</PrimeToast>
|
|
||||||
|
<PrimeDivider align="left">
|
||||||
|
About
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
|
<p v-if="version" class="text-muted-color text-sm">
|
||||||
|
VERSION: {{ version }}
|
||||||
|
</p>
|
||||||
|
<p class="text-muted-color text-sm mt-2">
|
||||||
|
COMMIT_SHA: {{ commitSha }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<template v-if="isTauri">
|
||||||
|
<PrimeButton
|
||||||
|
v-if="lastUpdate"
|
||||||
|
class="mt-3"
|
||||||
|
size="small"
|
||||||
|
label="Install new version"
|
||||||
|
fluid
|
||||||
|
severity="success"
|
||||||
|
@click="navigateTo({ name: 'Updater' })"
|
||||||
|
/>
|
||||||
|
<PrimeButton
|
||||||
|
v-else
|
||||||
|
class="mt-3"
|
||||||
|
size="small"
|
||||||
|
label="Check for Updates"
|
||||||
|
fluid
|
||||||
|
severity="info"
|
||||||
|
:loading="checking"
|
||||||
|
@click="checkForUpdates"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { RemovableRef } from '@vueuse/core'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: 'Preferences',
|
name: 'Preferences',
|
||||||
})
|
})
|
||||||
|
const { isTauri, version, commitSha } = useApp()
|
||||||
|
const { checking, checkForUpdates, lastUpdate } = useUpdater()
|
||||||
|
const { audioInputs, audioOutputs } = useDevices()
|
||||||
|
const {
|
||||||
|
inputDeviceId,
|
||||||
|
outputDeviceId,
|
||||||
|
autoGainControl,
|
||||||
|
noiseSuppression,
|
||||||
|
echoCancellation,
|
||||||
|
toggleInputHotkey,
|
||||||
|
toggleOutputHotkey,
|
||||||
|
inputDeviceExist,
|
||||||
|
outputDeviceExist,
|
||||||
|
shareFps,
|
||||||
|
} = usePreferences()
|
||||||
|
|
||||||
const { isTauri } = useApp()
|
const setupToggleInputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleInputHotkey)
|
||||||
const { checkForUpdates } = useUpdater()
|
const setupToggleOutputHotkey = (event: KeyboardEvent) => setupHotkey(event, toggleOutputHotkey)
|
||||||
const { me, setMe, logout } = useAuth()
|
|
||||||
|
|
||||||
const signaling = useSignaling()
|
function setupHotkey(event: KeyboardEvent, model: RemovableRef<string>) {
|
||||||
const toast = useToast()
|
if (event.key === 'Tab' || event.key === 'Enter') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const displayName = ref(me.value?.displayName || '')
|
event.preventDefault()
|
||||||
|
|
||||||
const valid = computed(() => {
|
const hotkey = []
|
||||||
if (!displayName.value || !me.value)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (displayName.value === me.value.displayName)
|
if (event.ctrlKey || event.metaKey)
|
||||||
return false
|
hotkey.push('CommandOrControl')
|
||||||
|
if (event.altKey)
|
||||||
|
hotkey.push('Alt')
|
||||||
|
if (event.shiftKey)
|
||||||
|
hotkey.push('Shift')
|
||||||
|
|
||||||
return true
|
const modifierApplied = hotkey.length > 0
|
||||||
})
|
|
||||||
|
|
||||||
async function onCheckForUpdates() {
|
if (!modifierApplied && ['Escape', 'Backspace', 'Delete'].includes(event.key)) {
|
||||||
const update = await checkForUpdates()
|
model.value = ''
|
||||||
|
|
||||||
toast.removeGroup('updater')
|
|
||||||
|
|
||||||
if (!update) {
|
|
||||||
toast.add({ severity: 'success', summary: 'You are up to date', closable: false, life: 1000 })
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.add({
|
if (!['Control', 'Shift', 'Alt'].includes(event.key)) {
|
||||||
group: 'updater',
|
hotkey.push(event.key.toUpperCase())
|
||||||
severity: 'info',
|
}
|
||||||
detail: `Version ${update?.version ?? '1.0.1'} is available!`,
|
|
||||||
closable: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
// asdasd
|
if (modifierApplied && hotkey.length === 1) {
|
||||||
}
|
model.value = ''
|
||||||
|
|
||||||
async function save() {
|
|
||||||
if (!valid.value)
|
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const updatedMe = await signaling.socket.value?.emitWithAck('updateClient', {
|
model.value = hotkey.join('+')
|
||||||
displayName: displayName.value,
|
|
||||||
})
|
|
||||||
|
|
||||||
setMe({ ...me.value, displayName: updatedMe.displayName })
|
|
||||||
|
|
||||||
toast.add({ severity: 'success', summary: 'Saved', life: 1000, closable: false })
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
68
client/app/pages/profile.vue
Normal file
68
client/app/pages/profile.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="save()">
|
||||||
|
<PrimeDivider align="left">
|
||||||
|
General
|
||||||
|
</PrimeDivider>
|
||||||
|
|
||||||
|
<PrimeFloatLabel variant="on">
|
||||||
|
<PrimeInputText id="displayName" v-model="displayName" fluid autocomplete="off" />
|
||||||
|
<label for="displayName">Display name</label>
|
||||||
|
</PrimeFloatLabel>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3 mt-6">
|
||||||
|
<PrimeButton label="Save" :disabled="!valid" :loading="saving" fluid type="submit" />
|
||||||
|
<PrimeButton severity="danger" class="shrink-0" @click="logout()">
|
||||||
|
<template #icon>
|
||||||
|
<LogOut />
|
||||||
|
</template>
|
||||||
|
</PrimeButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import chadApi from '#shared/chad-api'
|
||||||
|
import { LogOut } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
name: 'Profile',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { me, setMe, logout } = useAuth()
|
||||||
|
const signaling = useSignaling()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const displayName = ref(me.value?.displayName || '')
|
||||||
|
|
||||||
|
const saving = ref(false)
|
||||||
|
|
||||||
|
const valid = computed(() => {
|
||||||
|
if (!me.value)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if (displayName.value === me.value.displayName)
|
||||||
|
return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
if (!valid.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
saving.value = true
|
||||||
|
|
||||||
|
const updatedMe = await chadApi('/profile', {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: {
|
||||||
|
displayName: displayName.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
setMe({ ...me.value!, displayName: (updatedMe.displayName as string) })
|
||||||
|
|
||||||
|
toast.add({ severity: 'success', summary: 'Saved', life: 1000, closable: false })
|
||||||
|
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
7
client/app/plugins/00.build-info.ts
Normal file
7
client/app/plugins/00.build-info.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default defineNuxtPlugin({
|
||||||
|
setup() {
|
||||||
|
console.group('Build Info')
|
||||||
|
console.log(`COMMIT_SHA: ${__COMMIT_SHA__}`)
|
||||||
|
console.groupEnd()
|
||||||
|
},
|
||||||
|
})
|
||||||
45
client/app/plugins/01.register-hotkeys.ts
Normal file
45
client/app/plugins/01.register-hotkeys.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { isRegistered, register, unregister, unregisterAll } from '@tauri-apps/plugin-global-shortcut'
|
||||||
|
|
||||||
|
export default defineNuxtPlugin({
|
||||||
|
async setup() {
|
||||||
|
const { isTauri, toggleInput, toggleOutput } = useApp()
|
||||||
|
const preferences = usePreferences()
|
||||||
|
|
||||||
|
if (!isTauri.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
await unregisterAll()
|
||||||
|
|
||||||
|
watch(preferences.toggleInputHotkey, async (shortcut, prevShortcut) => {
|
||||||
|
await registerHotkey(shortcut, prevShortcut, toggleInput)
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(preferences.toggleOutputHotkey, async (shortcut, prevShortcut) => {
|
||||||
|
await registerHotkey(shortcut, prevShortcut, toggleOutput)
|
||||||
|
}, {
|
||||||
|
immediate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
async function registerHotkey(shortcut: string, prevShortcut: string | undefined, cb: () => void) {
|
||||||
|
if (!!prevShortcut && await isRegistered(prevShortcut)) {
|
||||||
|
await unregister(prevShortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shortcut)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (await isRegistered(shortcut)) {
|
||||||
|
await unregister(shortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
await register(shortcut, ({ state }) => {
|
||||||
|
if (state !== 'Released')
|
||||||
|
return
|
||||||
|
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -97,7 +97,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
__API_BASE_URL__: JSON.stringify(import.meta.env.API_BASE_URL || '/api'),
|
__API_BASE_URL__: JSON.stringify(import.meta.env.API_BASE_URL || 'http://localhost:4000/chad'),
|
||||||
__COMMIT_SHA__: JSON.stringify(import.meta.env.COMMIT_SHA || 'local'),
|
__COMMIT_SHA__: JSON.stringify(import.meta.env.COMMIT_SHA || 'local'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,9 +14,12 @@
|
|||||||
"@nuxt/fonts": "^0.11.4",
|
"@nuxt/fonts": "^0.11.4",
|
||||||
"@primeuix/themes": "^1.2.5",
|
"@primeuix/themes": "^1.2.5",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
|
"@tauri-apps/plugin-global-shortcut": "~2",
|
||||||
"@tauri-apps/plugin-process": "~2",
|
"@tauri-apps/plugin-process": "~2",
|
||||||
"@tauri-apps/plugin-updater": "~2",
|
"@tauri-apps/plugin-updater": "~2",
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
|
"hotkeys-js": "^4.0.0",
|
||||||
|
"lucide-vue-next": "^0.562.0",
|
||||||
"mediasoup-client": "^3.16.7",
|
"mediasoup-client": "^3.16.7",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ const instance = $fetch.create({
|
|||||||
baseURL: __API_BASE_URL__,
|
baseURL: __API_BASE_URL__,
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
retry: false,
|
retry: false,
|
||||||
onResponseError({ response }) {
|
onResponseError({ request, response }) {
|
||||||
if (!import.meta.client)
|
if (!import.meta.client)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (typeof request === 'string' && request.endsWith('/me'))
|
||||||
|
return
|
||||||
|
|
||||||
const message = response._data.error || 'Something went wrong'
|
const message = response._data.error || 'Something went wrong'
|
||||||
|
|
||||||
ToastEventBus.emit('add', { severity: 'error', summary: 'Error', detail: message, closable: false, life: 3000 })
|
ToastEventBus.emit('add', { severity: 'error', summary: 'Error', detail: message, closable: false, life: 3000 })
|
||||||
|
|||||||
87
client/src-tauri/Cargo.lock
generated
87
client/src-tauri/Cargo.lock
generated
@@ -93,6 +93,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
"tauri-plugin-global-shortcut",
|
||||||
"tauri-plugin-log",
|
"tauri-plugin-log",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
@@ -292,7 +293,7 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
"object",
|
"object",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -603,7 +604,7 @@ dependencies = [
|
|||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1375,6 +1376,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gethostname"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-link 0.2.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@@ -1504,6 +1515,24 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "global-hotkey"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"keyboard-types",
|
||||||
|
"objc2 0.6.2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"once_cell",
|
||||||
|
"serde",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
"x11rb",
|
||||||
|
"xkeysym",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gobject-sys"
|
name = "gobject-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@@ -4180,6 +4209,21 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-global-shortcut"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "424af23c7e88d05e4a1a6fc2c7be077912f8c76bd7900fd50aa2b7cbf5a2c405"
|
||||||
|
dependencies = [
|
||||||
|
"global-hotkey",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-log"
|
name = "tauri-plugin-log"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
@@ -5227,7 +5271,7 @@ checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement",
|
||||||
"windows-interface",
|
"windows-interface",
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
"windows-result 0.4.0",
|
"windows-result 0.4.0",
|
||||||
"windows-strings 0.5.0",
|
"windows-strings 0.5.0",
|
||||||
]
|
]
|
||||||
@@ -5273,9 +5317,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-numerics"
|
name = "windows-numerics"
|
||||||
@@ -5302,7 +5346,7 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5320,7 +5364,7 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5365,7 +5409,7 @@ version = "0.61.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5405,7 +5449,7 @@ version = "0.53.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
|
checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
"windows_aarch64_gnullvm 0.53.0",
|
"windows_aarch64_gnullvm 0.53.0",
|
||||||
"windows_aarch64_msvc 0.53.0",
|
"windows_aarch64_msvc 0.53.0",
|
||||||
"windows_i686_gnu 0.53.0",
|
"windows_i686_gnu 0.53.0",
|
||||||
@@ -5431,7 +5475,7 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "700dad7c058606087f6fdc1f88da5841e06da40334413c6cd4367b25ef26d24e"
|
checksum = "700dad7c058606087f6fdc1f88da5841e06da40334413c6cd4367b25ef26d24e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5687,6 +5731,23 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||||
|
dependencies = [
|
||||||
|
"gethostname",
|
||||||
|
"rustix",
|
||||||
|
"x11rb-protocol",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11rb-protocol"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xattr"
|
name = "xattr"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
@@ -5697,6 +5758,12 @@ dependencies = [
|
|||||||
"rustix",
|
"rustix",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xkeysym"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ tauri-plugin-log = "2"
|
|||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
|
tauri-plugin-global-shortcut = "2"
|
||||||
tauri-plugin-single-instance = "2"
|
tauri-plugin-single-instance = "2"
|
||||||
tauri-plugin-updater = "2"
|
tauri-plugin-updater = "2"
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
"main"
|
"main"
|
||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"updater:default"
|
"updater:default",
|
||||||
|
"global-shortcut:allow-is-registered",
|
||||||
|
"global-shortcut:allow-register",
|
||||||
|
"global-shortcut:allow-unregister",
|
||||||
|
"global-shortcut:allow-unregister-all"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_single_instance::init(|_, _, _| {
|
.plugin(tauri_plugin_single_instance::init(|_, _, _| {}))
|
||||||
}))
|
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
app.handle().plugin(
|
app.handle().plugin(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||||
"productName": "chad",
|
"productName": "chad",
|
||||||
"version": "0.2.8",
|
"version": "0.2.19",
|
||||||
"identifier": "xyz.koptilnya.chad",
|
"identifier": "xyz.koptilnya.chad",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../.output/public",
|
"frontendDist": "../.output/public",
|
||||||
|
|||||||
@@ -2831,6 +2831,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tauri-apps/api@npm:^2.8.0":
|
||||||
|
version: 2.9.1
|
||||||
|
resolution: "@tauri-apps/api@npm:2.9.1"
|
||||||
|
checksum: 10c0/18c76ec58b579860bfde5cd5b5ea6c3b13019d356c17d436bf395cafdf15dd1f277364cacd24cc94e5d4aa3816f39698f231773d2a18625e98702295ab0c2c8f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-arm64@npm:2.8.4":
|
"@tauri-apps/cli-darwin-arm64@npm:2.8.4":
|
||||||
version: 2.8.4
|
version: 2.8.4
|
||||||
resolution: "@tauri-apps/cli-darwin-arm64@npm:2.8.4"
|
resolution: "@tauri-apps/cli-darwin-arm64@npm:2.8.4"
|
||||||
@@ -2952,6 +2959,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@tauri-apps/plugin-global-shortcut@npm:~2":
|
||||||
|
version: 2.3.1
|
||||||
|
resolution: "@tauri-apps/plugin-global-shortcut@npm:2.3.1"
|
||||||
|
dependencies:
|
||||||
|
"@tauri-apps/api": "npm:^2.8.0"
|
||||||
|
checksum: 10c0/e3833a1d0bab370aecb64396ea7e701f5342e303c6fdd3870583619b50c9e2784854b85ca24c279e9871bdcd6796de878392acd1ea4cd563fda83068939e27cd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tauri-apps/plugin-process@npm:~2":
|
"@tauri-apps/plugin-process@npm:~2":
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
resolution: "@tauri-apps/plugin-process@npm:2.3.0"
|
resolution: "@tauri-apps/plugin-process@npm:2.3.0"
|
||||||
@@ -4036,11 +4052,14 @@ __metadata:
|
|||||||
"@primevue/nuxt-module": "npm:^4.4.0"
|
"@primevue/nuxt-module": "npm:^4.4.0"
|
||||||
"@tailwindcss/vite": "npm:^4.1.14"
|
"@tailwindcss/vite": "npm:^4.1.14"
|
||||||
"@tauri-apps/cli": "npm:^2.8.4"
|
"@tauri-apps/cli": "npm:^2.8.4"
|
||||||
|
"@tauri-apps/plugin-global-shortcut": "npm:~2"
|
||||||
"@tauri-apps/plugin-process": "npm:~2"
|
"@tauri-apps/plugin-process": "npm:~2"
|
||||||
"@tauri-apps/plugin-updater": "npm:~2"
|
"@tauri-apps/plugin-updater": "npm:~2"
|
||||||
"@vueuse/core": "npm:^13.9.0"
|
"@vueuse/core": "npm:^13.9.0"
|
||||||
eslint: "npm:^9.36.0"
|
eslint: "npm:^9.36.0"
|
||||||
eslint-plugin-format: "npm:^1.0.2"
|
eslint-plugin-format: "npm:^1.0.2"
|
||||||
|
hotkeys-js: "npm:^4.0.0"
|
||||||
|
lucide-vue-next: "npm:^0.562.0"
|
||||||
mediasoup-client: "npm:^3.16.7"
|
mediasoup-client: "npm:^3.16.7"
|
||||||
nuxt: "npm:^4.2.2"
|
nuxt: "npm:^4.2.2"
|
||||||
postcss: "npm:^8.5.6"
|
postcss: "npm:^8.5.6"
|
||||||
@@ -6206,6 +6225,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"hotkeys-js@npm:^4.0.0":
|
||||||
|
version: 4.0.0
|
||||||
|
resolution: "hotkeys-js@npm:4.0.0"
|
||||||
|
checksum: 10c0/7cf84a0a8c20ff36e3d90de83977480a035a69335afffc41597d66930be49f69c5ad0a430ef38e953ab2ec73804b2a1635f34d096c7146fef2a2f7a45b3417c8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"http-cache-semantics@npm:^4.1.1":
|
"http-cache-semantics@npm:^4.1.1":
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
resolution: "http-cache-semantics@npm:4.2.0"
|
resolution: "http-cache-semantics@npm:4.2.0"
|
||||||
@@ -7024,6 +7050,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"lucide-vue-next@npm:^0.562.0":
|
||||||
|
version: 0.562.0
|
||||||
|
resolution: "lucide-vue-next@npm:0.562.0"
|
||||||
|
peerDependencies:
|
||||||
|
vue: ">=3.0.1"
|
||||||
|
checksum: 10c0/5ba792ea5e48d01fc99a3c5ae4a59d9767e5d4c7826901800831cf051cf85eb4a680b3564d2910ed4d17dc1d35223c37d006bdbcdb291d90a9491b9c6a20ae14
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"magic-regexp@npm:^0.10.0":
|
"magic-regexp@npm:^0.10.0":
|
||||||
version: 0.10.0
|
version: 0.10.0
|
||||||
resolution: "magic-regexp@npm:0.10.0"
|
resolution: "magic-regexp@npm:0.10.0"
|
||||||
|
|||||||
14
server/dto/channel.dto.ts
Normal file
14
server/dto/channel.dto.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// dto/channel.dto.ts
|
||||||
|
import type { Prisma } from '../prisma/generated/client'
|
||||||
|
|
||||||
|
export const channelPublicSelect = {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
owner_id: true,
|
||||||
|
persistent: true,
|
||||||
|
maxClients: true,
|
||||||
|
} satisfies Prisma.ChannelSelect
|
||||||
|
|
||||||
|
export type ChannelPublicDTO = Prisma.ChannelGetPayload<{
|
||||||
|
select: typeof channelPublicSelect
|
||||||
|
}>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "server",
|
"name": "server",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node --transpile-only server.ts",
|
"start": "ts-node --transpile-only server.ts",
|
||||||
"db:deploy": "npx prisma migrate deploy && npx prisma generate"
|
"db:deploy": "npx prisma migrate deploy && npx prisma db seed && npx prisma generate"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "yarn@4.10.3",
|
"packageManager": "yarn@4.10.3",
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
"@fastify/cookie": "^11.0.2",
|
"@fastify/cookie": "^11.0.2",
|
||||||
"@fastify/cors": "^11.1.0",
|
"@fastify/cors": "^11.1.0",
|
||||||
"@lucia-auth/adapter-prisma": "^4.0.1",
|
"@lucia-auth/adapter-prisma": "^4.0.1",
|
||||||
"@prisma/client": "^6.17.0",
|
"@prisma/adapter-better-sqlite3": "^7.2.0",
|
||||||
|
"@prisma/client": "7",
|
||||||
"bcrypt": "^6.0.0",
|
"bcrypt": "^6.0.0",
|
||||||
"consola": "^3.4.2",
|
"consola": "^3.4.2",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
"fastify-plugin": "^5.1.0",
|
"fastify-plugin": "^5.1.0",
|
||||||
"lucia": "^3.2.2",
|
"lucia": "^3.2.2",
|
||||||
"mediasoup": "^3.19.3",
|
"mediasoup": "^3.19.3",
|
||||||
"prisma": "^6.17.0",
|
"prisma": "7",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"ws": "^8.18.3",
|
"ws": "^8.18.3",
|
||||||
"zod": "^4.1.12"
|
"zod": "^4.1.12"
|
||||||
|
|||||||
@@ -25,5 +25,50 @@ export const autoConfig: mediasoup.types.RouterOptions = {
|
|||||||
channels: 2,
|
channels: 2,
|
||||||
parameters: { useinbandfec: 1, stereo: 1 },
|
parameters: { useinbandfec: 1, stereo: 1 },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
kind: 'video',
|
||||||
|
mimeType: 'video/VP8',
|
||||||
|
clockRate: 90000,
|
||||||
|
parameters: {
|
||||||
|
'x-google-start-bitrate': 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'video',
|
||||||
|
mimeType: 'video/VP9',
|
||||||
|
clockRate: 90000,
|
||||||
|
parameters: {
|
||||||
|
'profile-id': 2,
|
||||||
|
'x-google-start-bitrate': 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'video',
|
||||||
|
mimeType: 'video/h264',
|
||||||
|
clockRate: 90000,
|
||||||
|
parameters: {
|
||||||
|
'packetization-mode': 1,
|
||||||
|
'profile-level-id': '4d0032',
|
||||||
|
'level-asymmetry-allowed': 1,
|
||||||
|
'x-google-start-bitrate': 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'video',
|
||||||
|
mimeType: 'video/h264',
|
||||||
|
clockRate: 90000,
|
||||||
|
parameters: {
|
||||||
|
'packetization-mode': 1,
|
||||||
|
'profile-level-id': '42e01f',
|
||||||
|
'level-asymmetry-allowed': 1,
|
||||||
|
'x-google-start-bitrate': 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 'video',
|
||||||
|
mimeType: 'video/AV1',
|
||||||
|
clockRate: 90000,
|
||||||
|
parameters: {},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export default fp<Partial<ServerOptions>>(
|
|||||||
|
|
||||||
fastify.ready(() => {
|
fastify.ready(() => {
|
||||||
registerWebrtcSocket(fastify.io, fastify.mediasoupRouter)
|
registerWebrtcSocket(fastify.io, fastify.mediasoupRouter)
|
||||||
|
registerChannelSocket(fastify.io, fastify.mediasoupRouter)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] },
|
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] },
|
||||||
|
|||||||
13
server/prisma.config.ts
Normal file
13
server/prisma.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig, env } from 'prisma/config'
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: './prisma/schema.prisma',
|
||||||
|
migrations: {
|
||||||
|
path: './prisma/migrations',
|
||||||
|
seed: 'ts-node ./prisma/seed.ts',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
import { PrismaClient } from '@prisma/client'
|
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
|
||||||
|
import { env } from 'prisma/config'
|
||||||
|
import { PrismaClient } from './generated/client/index.js'
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
const client = new PrismaClient({
|
const client = new PrismaClient({
|
||||||
|
adapter: new PrismaBetterSqlite3({
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
}, {
|
||||||
|
timestampFormat: 'unixepoch-ms',
|
||||||
|
}),
|
||||||
log: ['query', 'error', 'warn'],
|
log: ['query', 'error', 'warn'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
39
server/prisma/generated/client/browser.ts
Normal file
39
server/prisma/generated/client/browser.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||||
|
* Use it to get access to models, enums, and input types.
|
||||||
|
*
|
||||||
|
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
||||||
|
* See `client.ts` for the standard, server-side entry point.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Prisma from './internal/prismaNamespaceBrowser.ts'
|
||||||
|
export { Prisma }
|
||||||
|
export * as $Enums from './enums.ts'
|
||||||
|
export * from './enums.ts';
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Session = Prisma.SessionModel
|
||||||
|
/**
|
||||||
|
* Model UserPreferences
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type UserPreferences = Prisma.UserPreferencesModel
|
||||||
|
/**
|
||||||
|
* Model Channel
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Channel = Prisma.ChannelModel
|
||||||
1
server/prisma/generated/client/client.d.ts
vendored
Normal file
1
server/prisma/generated/client/client.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
5
server/prisma/generated/client/client.js
Normal file
5
server/prisma/generated/client/client.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('.') }
|
||||||
61
server/prisma/generated/client/client.ts
Normal file
61
server/prisma/generated/client/client.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
||||||
|
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as process from 'node:process'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.ts"
|
||||||
|
import * as $Class from "./internal/class.ts"
|
||||||
|
import * as Prisma from "./internal/prismaNamespace.ts"
|
||||||
|
|
||||||
|
export * as $Enums from './enums.ts'
|
||||||
|
export * from "./enums.ts"
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
export const PrismaClient = $Class.getPrismaClientClass()
|
||||||
|
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||||
|
export { Prisma }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Session = Prisma.SessionModel
|
||||||
|
/**
|
||||||
|
* Model UserPreferences
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type UserPreferences = Prisma.UserPreferencesModel
|
||||||
|
/**
|
||||||
|
* Model Channel
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Channel = Prisma.ChannelModel
|
||||||
298
server/prisma/generated/client/commonInputTypes.ts
Normal file
298
server/prisma/generated/client/commonInputTypes.ts
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.ts"
|
||||||
|
import type * as Prisma from "./internal/prismaNamespace.ts"
|
||||||
|
|
||||||
|
|
||||||
|
export type StringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SortOrderInput = {
|
||||||
|
sort: Prisma.SortOrder
|
||||||
|
nulls?: Prisma.NullsOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BoolFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[]
|
||||||
|
notIn?: number[]
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedBoolFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
1
server/prisma/generated/client/default.d.ts
vendored
Normal file
1
server/prisma/generated/client/default.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./index"
|
||||||
5
server/prisma/generated/client/default.js
Normal file
5
server/prisma/generated/client/default.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
module.exports = { ...require('#main-entry-point') }
|
||||||
1
server/prisma/generated/client/edge.d.ts
vendored
Normal file
1
server/prisma/generated/client/edge.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./default"
|
||||||
165
server/prisma/generated/client/edge.js
Normal file
165
server/prisma/generated/client/edge.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
PrismaClientKnownRequestError,
|
||||||
|
PrismaClientUnknownRequestError,
|
||||||
|
PrismaClientRustPanicError,
|
||||||
|
PrismaClientInitializationError,
|
||||||
|
PrismaClientValidationError,
|
||||||
|
getPrismaClient,
|
||||||
|
sqltag,
|
||||||
|
empty,
|
||||||
|
join,
|
||||||
|
raw,
|
||||||
|
skip,
|
||||||
|
Decimal,
|
||||||
|
Debug,
|
||||||
|
DbNull,
|
||||||
|
JsonNull,
|
||||||
|
AnyNull,
|
||||||
|
NullTypes,
|
||||||
|
makeStrictEnum,
|
||||||
|
Extensions,
|
||||||
|
warnOnce,
|
||||||
|
defineDmmfProperty,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
createParam,
|
||||||
|
} = require('./runtime/wasm-compiler-edge.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 7.2.0
|
||||||
|
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "7.2.0",
|
||||||
|
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||||
|
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
|
||||||
|
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
|
||||||
|
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
|
||||||
|
Prisma.PrismaClientValidationError = PrismaClientValidationError
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = sqltag
|
||||||
|
Prisma.empty = empty
|
||||||
|
Prisma.join = join
|
||||||
|
Prisma.raw = raw
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = Extensions.getExtensionContext
|
||||||
|
Prisma.defineExtension = Extensions.defineExtension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = DbNull
|
||||||
|
Prisma.JsonNull = JsonNull
|
||||||
|
Prisma.AnyNull = AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = NullTypes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Create the Client
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
"previewFeatures": [],
|
||||||
|
"clientVersion": "7.2.0",
|
||||||
|
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||||
|
"activeProvider": "sqlite",
|
||||||
|
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
|
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
||||||
|
config.compilerWasm = {
|
||||||
|
getRuntime: async () => require('./query_compiler_bg.js'),
|
||||||
|
getQueryCompilerWasmModule: async () => {
|
||||||
|
const loader = (await import('#wasm-compiler-loader')).default
|
||||||
|
const compiler = (await loader).default
|
||||||
|
return compiler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof globalThis !== 'undefined' && globalThis['DEBUG'] || (typeof process !== 'undefined' && process.env && process.env.DEBUG) || undefined) {
|
||||||
|
Debug.enable(typeof globalThis !== 'undefined' && globalThis['DEBUG'] || (typeof process !== 'undefined' && process.env && process.env.DEBUG) || undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrismaClient = getPrismaClient(config)
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
15
server/prisma/generated/client/enums.ts
Normal file
15
server/prisma/generated/client/enums.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports all enum related types from the schema.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This file is empty because there are no enums in the schema.
|
||||||
|
export {}
|
||||||
196
server/prisma/generated/client/index-browser.js
Normal file
196
server/prisma/generated/client/index-browser.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
Decimal,
|
||||||
|
DbNull,
|
||||||
|
JsonNull,
|
||||||
|
AnyNull,
|
||||||
|
NullTypes,
|
||||||
|
makeStrictEnum,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
skip
|
||||||
|
} = require('./runtime/index-browser.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 7.2.0
|
||||||
|
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "7.2.0",
|
||||||
|
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientKnownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)};
|
||||||
|
Prisma.PrismaClientUnknownRequestError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientUnknownRequestError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientRustPanicError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientRustPanicError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientInitializationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientInitializationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.PrismaClientValidationError = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`PrismaClientValidationError is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`sqltag is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.empty = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`empty is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.join = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`join is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.raw = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`raw is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.getExtensionContext is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
Prisma.defineExtension = () => {
|
||||||
|
const runtimeName = getRuntime().prettyName;
|
||||||
|
throw new Error(`Extensions.defineExtension is unable to run in this browser environment, or has been bundled for the browser (running in ${runtimeName}).
|
||||||
|
In case this error is unexpected for you, please report it in https://pris.ly/prisma-prisma-bug-report`,
|
||||||
|
)}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = DbNull
|
||||||
|
Prisma.JsonNull = JsonNull
|
||||||
|
Prisma.AnyNull = AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = NullTypes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a stub Prisma Client that will error at runtime if called.
|
||||||
|
*/
|
||||||
|
class PrismaClient {
|
||||||
|
constructor() {
|
||||||
|
return new Proxy(this, {
|
||||||
|
get(target, prop) {
|
||||||
|
let message
|
||||||
|
const runtime = getRuntime()
|
||||||
|
if (runtime.isEdge) {
|
||||||
|
message = `PrismaClient is not configured to run in ${runtime.prettyName}. In order to run Prisma Client on edge runtime, either:
|
||||||
|
- Use Prisma Accelerate: https://pris.ly/d/accelerate
|
||||||
|
- Use Driver Adapters: https://pris.ly/d/driver-adapters
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
message = 'PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in `' + runtime.prettyName + '`).'
|
||||||
|
}
|
||||||
|
|
||||||
|
message += `
|
||||||
|
If this is unexpected, please open an issue: https://pris.ly/prisma-prisma-bug-report`
|
||||||
|
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
6981
server/prisma/generated/client/index.d.ts
vendored
Normal file
6981
server/prisma/generated/client/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
165
server/prisma/generated/client/index.js
Normal file
165
server/prisma/generated/client/index.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
|
||||||
|
const {
|
||||||
|
PrismaClientKnownRequestError,
|
||||||
|
PrismaClientUnknownRequestError,
|
||||||
|
PrismaClientRustPanicError,
|
||||||
|
PrismaClientInitializationError,
|
||||||
|
PrismaClientValidationError,
|
||||||
|
getPrismaClient,
|
||||||
|
sqltag,
|
||||||
|
empty,
|
||||||
|
join,
|
||||||
|
raw,
|
||||||
|
skip,
|
||||||
|
Decimal,
|
||||||
|
Debug,
|
||||||
|
DbNull,
|
||||||
|
JsonNull,
|
||||||
|
AnyNull,
|
||||||
|
NullTypes,
|
||||||
|
makeStrictEnum,
|
||||||
|
Extensions,
|
||||||
|
warnOnce,
|
||||||
|
defineDmmfProperty,
|
||||||
|
Public,
|
||||||
|
getRuntime,
|
||||||
|
createParam,
|
||||||
|
} = require('./runtime/client.js')
|
||||||
|
|
||||||
|
|
||||||
|
const Prisma = {}
|
||||||
|
|
||||||
|
exports.Prisma = Prisma
|
||||||
|
exports.$Enums = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prisma Client JS version: 7.2.0
|
||||||
|
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
|
||||||
|
*/
|
||||||
|
Prisma.prismaVersion = {
|
||||||
|
client: "7.2.0",
|
||||||
|
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
|
||||||
|
}
|
||||||
|
|
||||||
|
Prisma.PrismaClientKnownRequestError = PrismaClientKnownRequestError;
|
||||||
|
Prisma.PrismaClientUnknownRequestError = PrismaClientUnknownRequestError
|
||||||
|
Prisma.PrismaClientRustPanicError = PrismaClientRustPanicError
|
||||||
|
Prisma.PrismaClientInitializationError = PrismaClientInitializationError
|
||||||
|
Prisma.PrismaClientValidationError = PrismaClientValidationError
|
||||||
|
Prisma.Decimal = Decimal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of sql-template-tag
|
||||||
|
*/
|
||||||
|
Prisma.sql = sqltag
|
||||||
|
Prisma.empty = empty
|
||||||
|
Prisma.join = join
|
||||||
|
Prisma.raw = raw
|
||||||
|
Prisma.validator = Public.validator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extensions
|
||||||
|
*/
|
||||||
|
Prisma.getExtensionContext = Extensions.getExtensionContext
|
||||||
|
Prisma.defineExtension = Extensions.defineExtension
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand utilities for JSON filtering
|
||||||
|
*/
|
||||||
|
Prisma.DbNull = DbNull
|
||||||
|
Prisma.JsonNull = JsonNull
|
||||||
|
Prisma.AnyNull = AnyNull
|
||||||
|
|
||||||
|
Prisma.NullTypes = NullTypes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
exports.Prisma.TransactionIsolationLevel = makeStrictEnum({
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.Prisma.UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.Prisma.NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.Prisma.ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Create the Client
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
"previewFeatures": [],
|
||||||
|
"clientVersion": "7.2.0",
|
||||||
|
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||||
|
"activeProvider": "sqlite",
|
||||||
|
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
|
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
||||||
|
config.compilerWasm = {
|
||||||
|
getRuntime: async () => require('./query_compiler_bg.js'),
|
||||||
|
getQueryCompilerWasmModule: async () => {
|
||||||
|
const { Buffer } = require('node:buffer')
|
||||||
|
const { wasm } = require('./query_compiler_bg.wasm-base64.js')
|
||||||
|
const queryCompilerWasmFileBytes = Buffer.from(wasm, 'base64')
|
||||||
|
|
||||||
|
return new WebAssembly.Module(queryCompilerWasmFileBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrismaClient = getPrismaClient(config)
|
||||||
|
exports.PrismaClient = PrismaClient
|
||||||
|
Object.assign(exports, Prisma)
|
||||||
220
server/prisma/generated/client/internal/class.ts
Normal file
220
server/prisma/generated/client/internal/class.ts
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* WARNING: This is an internal file that is subject to change!
|
||||||
|
*
|
||||||
|
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||||
|
*
|
||||||
|
* Please import the `PrismaClient` class from the `client.ts` file instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import type * as Prisma from "./prismaNamespace.ts"
|
||||||
|
|
||||||
|
|
||||||
|
const config: runtime.GetPrismaClientConfig = {
|
||||||
|
"previewFeatures": [],
|
||||||
|
"clientVersion": "7.2.0",
|
||||||
|
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
|
||||||
|
"activeProvider": "sqlite",
|
||||||
|
"inlineSchema": "datasource db {\n provider = \"sqlite\"\n // url = env(\"DATABASE_URL\")\n}\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"./generated/client\"\n}\n\nmodel User {\n id String @id @default(cuid())\n username String @unique\n password String\n displayName String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n Session Session[]\n UserPreferences UserPreferences?\n channels Channel[]\n}\n\nmodel Session {\n id String @id\n userId String\n expiresAt DateTime\n user User @relation(references: [id], fields: [userId], onDelete: Cascade)\n\n @@index([userId])\n}\n\nmodel UserPreferences {\n userId String @id\n toggleInputHotkey String? @default(\"\")\n toggleOutputHotkey String? @default(\"\")\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n\nmodel Channel {\n id String @id @default(cuid())\n name String\n maxClients Int?\n persistent Boolean @default(false)\n owner_id String?\n owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)\n}\n",
|
||||||
|
"runtimeDataModel": {
|
||||||
|
"models": {},
|
||||||
|
"enums": {},
|
||||||
|
"types": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"displayName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"Session\",\"kind\":\"object\",\"type\":\"Session\",\"relationName\":\"SessionToUser\"},{\"name\":\"UserPreferences\",\"kind\":\"object\",\"type\":\"UserPreferences\",\"relationName\":\"UserToUserPreferences\"},{\"name\":\"channels\",\"kind\":\"object\",\"type\":\"Channel\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null},\"Session\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiresAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"SessionToUser\"}],\"dbName\":null},\"UserPreferences\":{\"fields\":[{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleInputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"toggleOutputHotkey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"UserToUserPreferences\"}],\"dbName\":null},\"Channel\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"maxClients\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"persistent\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"owner_id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChannelToUser\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
|
|
||||||
|
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
|
||||||
|
const { Buffer } = await import('node:buffer')
|
||||||
|
const wasmArray = Buffer.from(wasmBase64, 'base64')
|
||||||
|
return new WebAssembly.Module(wasmArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.compilerWasm = {
|
||||||
|
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_bg.sqlite.mjs"),
|
||||||
|
|
||||||
|
getQueryCompilerWasmModule: async () => {
|
||||||
|
const { wasm } = await import("@prisma/client/runtime/query_compiler_bg.sqlite.wasm-base64.mjs")
|
||||||
|
return await decodeBase64AsWasm(wasm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type LogOptions<ClientOptions extends Prisma.PrismaClientOptions> =
|
||||||
|
'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition> ? Prisma.GetEvents<ClientOptions['log']> : never : never
|
||||||
|
|
||||||
|
export interface PrismaClientConstructor {
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
|
||||||
|
new <
|
||||||
|
Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
|
||||||
|
LogOpts extends LogOptions<Options> = LogOptions<Options>,
|
||||||
|
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'],
|
||||||
|
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||||
|
>(options: Prisma.Subset<Options, Prisma.PrismaClientOptions> ): PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface PrismaClient<
|
||||||
|
in LogOpts extends Prisma.LogLevel = never,
|
||||||
|
in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined,
|
||||||
|
in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||||
|
> {
|
||||||
|
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] }
|
||||||
|
|
||||||
|
$on<V extends LogOpts>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect with the database
|
||||||
|
*/
|
||||||
|
$connect(): runtime.Types.Utils.JsPromise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the database
|
||||||
|
*/
|
||||||
|
$disconnect(): runtime.Types.Utils.JsPromise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a prepared raw query and returns the number of affected rows.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$executeRaw`UPDATE User SET cool = ${true} WHERE email = ${'user@email.com'};`
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a raw query and returns the number of affected rows.
|
||||||
|
* Susceptible to SQL injections, see documentation.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$executeRawUnsafe('UPDATE User SET cool = $1 WHERE email = $2 ;', true, 'user@email.com')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a prepared raw query and returns the `SELECT` data.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${1} OR email = ${'user@email.com'};`
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a raw query and returns the `SELECT` data.
|
||||||
|
* Susceptible to SQL injections, see documentation.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$queryRawUnsafe('SELECT * FROM User WHERE id = $1 OR email = $2;', 1, 'user@email.com')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const [george, bob, alice] = await prisma.$transaction([
|
||||||
|
* prisma.user.create({ data: { name: 'George' } }),
|
||||||
|
* prisma.user.create({ data: { name: 'Bob' } }),
|
||||||
|
* prisma.user.create({ data: { name: 'Alice' } }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions).
|
||||||
|
*/
|
||||||
|
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
|
||||||
|
|
||||||
|
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>
|
||||||
|
|
||||||
|
$extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb<OmitOpts>, ExtArgs, runtime.Types.Utils.Call<Prisma.TypeMapCb<OmitOpts>, {
|
||||||
|
extArgs: ExtArgs
|
||||||
|
}>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.user`: Exposes CRUD operations for the **User** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get user(): Prisma.UserDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.session`: Exposes CRUD operations for the **Session** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Sessions
|
||||||
|
* const sessions = await prisma.session.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get session(): Prisma.SessionDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.userPreferences`: Exposes CRUD operations for the **UserPreferences** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more UserPreferences
|
||||||
|
* const userPreferences = await prisma.userPreferences.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get userPreferences(): Prisma.UserPreferencesDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.channel`: Exposes CRUD operations for the **Channel** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Channels
|
||||||
|
* const channels = await prisma.channel.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get channel(): Prisma.ChannelDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPrismaClientClass(): PrismaClientConstructor {
|
||||||
|
return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor
|
||||||
|
}
|
||||||
1000
server/prisma/generated/client/internal/prismaNamespace.ts
Normal file
1000
server/prisma/generated/client/internal/prismaNamespace.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* WARNING: This is an internal file that is subject to change!
|
||||||
|
*
|
||||||
|
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||||
|
*
|
||||||
|
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
|
||||||
|
* While this enables partial backward compatibility, it is not part of the stable public API.
|
||||||
|
*
|
||||||
|
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
|
||||||
|
* model files in the `model` directory!
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/index-browser"
|
||||||
|
|
||||||
|
export type * from '../models.ts'
|
||||||
|
export type * from './prismaNamespace.ts'
|
||||||
|
|
||||||
|
export const Decimal = runtime.Decimal
|
||||||
|
|
||||||
|
|
||||||
|
export const NullTypes = {
|
||||||
|
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
|
||||||
|
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
|
||||||
|
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const DbNull = runtime.DbNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const JsonNull = runtime.JsonNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const AnyNull = runtime.AnyNull
|
||||||
|
|
||||||
|
|
||||||
|
export const ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Session: 'Session',
|
||||||
|
UserPreferences: 'UserPreferences',
|
||||||
|
Channel: 'Channel'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const TransactionIsolationLevel = {
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
username: 'username',
|
||||||
|
password: 'password',
|
||||||
|
displayName: 'displayName',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
userId: 'userId',
|
||||||
|
expiresAt: 'expiresAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SessionScalarFieldEnum = (typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserPreferencesScalarFieldEnum = {
|
||||||
|
userId: 'userId',
|
||||||
|
toggleInputHotkey: 'toggleInputHotkey',
|
||||||
|
toggleOutputHotkey: 'toggleOutputHotkey'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserPreferencesScalarFieldEnum = (typeof UserPreferencesScalarFieldEnum)[keyof typeof UserPreferencesScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const ChannelScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
maxClients: 'maxClients',
|
||||||
|
persistent: 'persistent',
|
||||||
|
owner_id: 'owner_id'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ChannelScalarFieldEnum = (typeof ChannelScalarFieldEnum)[keyof typeof ChannelScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
|
||||||
|
|
||||||
|
|
||||||
|
export const NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
|
||||||
|
|
||||||
15
server/prisma/generated/client/models.ts
Normal file
15
server/prisma/generated/client/models.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This is a barrel export file for all models and their related types.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
export type * from './models/User.ts'
|
||||||
|
export type * from './models/Session.ts'
|
||||||
|
export type * from './models/UserPreferences.ts'
|
||||||
|
export type * from './models/Channel.ts'
|
||||||
|
export type * from './commonInputTypes.ts'
|
||||||
1412
server/prisma/generated/client/models/Channel.ts
Normal file
1412
server/prisma/generated/client/models/Channel.ts
Normal file
File diff suppressed because it is too large
Load Diff
1267
server/prisma/generated/client/models/Session.ts
Normal file
1267
server/prisma/generated/client/models/Session.ts
Normal file
File diff suppressed because it is too large
Load Diff
1597
server/prisma/generated/client/models/User.ts
Normal file
1597
server/prisma/generated/client/models/User.ts
Normal file
File diff suppressed because it is too large
Load Diff
1228
server/prisma/generated/client/models/UserPreferences.ts
Normal file
1228
server/prisma/generated/client/models/UserPreferences.ts
Normal file
File diff suppressed because it is too large
Load Diff
144
server/prisma/generated/client/package.json
Normal file
144
server/prisma/generated/client/package.json
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"name": "prisma-client-bc59e606b3f744a8b15c18a26f89a0eb5328cd0b7e5ee2c8265028eef68b0317",
|
||||||
|
"main": "index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
|
"browser": "default.js",
|
||||||
|
"exports": {
|
||||||
|
"./client": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./extension": {
|
||||||
|
"types": "./extension.d.ts",
|
||||||
|
"require": "./extension.js",
|
||||||
|
"import": "./extension.js",
|
||||||
|
"default": "./extension.js"
|
||||||
|
},
|
||||||
|
"./index-browser": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index-browser.js",
|
||||||
|
"import": "./index-browser.js",
|
||||||
|
"default": "./index-browser.js"
|
||||||
|
},
|
||||||
|
"./index": {
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"require": "./index.js",
|
||||||
|
"import": "./index.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./edge": {
|
||||||
|
"types": "./edge.d.ts",
|
||||||
|
"require": "./edge.js",
|
||||||
|
"import": "./edge.js",
|
||||||
|
"default": "./edge.js"
|
||||||
|
},
|
||||||
|
"./runtime/client": {
|
||||||
|
"types": "./runtime/client.d.ts",
|
||||||
|
"node": {
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"default": "./runtime/client.js"
|
||||||
|
},
|
||||||
|
"require": "./runtime/client.js",
|
||||||
|
"import": "./runtime/client.mjs",
|
||||||
|
"default": "./runtime/client.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-compiler-edge": {
|
||||||
|
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-compiler-edge.js",
|
||||||
|
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/index-browser": {
|
||||||
|
"types": "./runtime/index-browser.d.ts",
|
||||||
|
"require": "./runtime/index-browser.js",
|
||||||
|
"import": "./runtime/index-browser.mjs",
|
||||||
|
"default": "./runtime/index-browser.mjs"
|
||||||
|
},
|
||||||
|
"./generator-build": {
|
||||||
|
"require": "./generator-build/index.js",
|
||||||
|
"import": "./generator-build/index.js",
|
||||||
|
"default": "./generator-build/index.js"
|
||||||
|
},
|
||||||
|
"./sql": {
|
||||||
|
"require": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.js",
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"types": "./sql.d.ts",
|
||||||
|
"node": "./sql.mjs",
|
||||||
|
"default": "./sql.mjs"
|
||||||
|
},
|
||||||
|
"default": "./sql.js"
|
||||||
|
},
|
||||||
|
"./*": "./*"
|
||||||
|
},
|
||||||
|
"version": "7.2.0",
|
||||||
|
"sideEffects": false,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/client-runtime-utils": "7.2.0"
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"#wasm-compiler-loader": {
|
||||||
|
"edge-light": "./wasm-edge-light-loader.mjs",
|
||||||
|
"workerd": "./wasm-worker-loader.mjs",
|
||||||
|
"worker": "./wasm-worker-loader.mjs",
|
||||||
|
"default": "./wasm-worker-loader.mjs"
|
||||||
|
},
|
||||||
|
"#main-entry-point": {
|
||||||
|
"require": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"node": "./index.js",
|
||||||
|
"edge-light": "./edge.js",
|
||||||
|
"workerd": "./edge.js",
|
||||||
|
"worker": "./edge.js",
|
||||||
|
"browser": "./index-browser.js",
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"default": "./index.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
server/prisma/generated/client/query_compiler_bg.js
Normal file
2
server/prisma/generated/client/query_compiler_bg.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";var h=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var M=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var D=(e,t)=>{for(var n in t)h(e,n,{get:t[n],enumerable:!0})},O=(e,t,n,_)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of M(t))!j.call(e,r)&&r!==n&&h(e,r,{get:()=>t[r],enumerable:!(_=T(t,r))||_.enumerable});return e};var B=e=>O(h({},"__esModule",{value:!0}),e);var xe={};D(xe,{QueryCompiler:()=>F,__wbg_Error_e83987f665cf5504:()=>q,__wbg_Number_bb48ca12f395cd08:()=>C,__wbg_String_8f0eb39a4a4c2f66:()=>k,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68:()=>W,__wbg___wbindgen_debug_string_df47ffb5e35e6763:()=>V,__wbg___wbindgen_in_bb933bd9e1b3bc0f:()=>z,__wbg___wbindgen_is_object_c818261d21f283a4:()=>L,__wbg___wbindgen_is_string_fbb76cb2940daafd:()=>P,__wbg___wbindgen_is_undefined_2d472862bd29a478:()=>Q,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147:()=>Y,__wbg___wbindgen_number_get_a20bf9b85341449d:()=>G,__wbg___wbindgen_string_get_e4f06c90489ad01b:()=>J,__wbg___wbindgen_throw_b855445ff6a94295:()=>X,__wbg_entries_e171b586f8f6bdbf:()=>H,__wbg_getTime_14776bfb48a1bff9:()=>K,__wbg_get_7bed016f185add81:()=>Z,__wbg_get_with_ref_key_1dc361bd10053bfe:()=>v,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38:()=>ee,__wbg_instanceof_Uint8Array_20c8e73002f7af98:()=>te,__wbg_isSafeInteger_d216eda7911dde36:()=>ne,__wbg_length_69bca3cb64fc8748:()=>re,__wbg_length_cdd215e10d9dd507:()=>_e,__wbg_new_0_f9740686d739025c:()=>oe,__wbg_new_1acc0b6eea89d040:()=>ce,__wbg_new_5a79be3ab53b8aa5:()=>ie,__wbg_new_68651c719dcda04e:()=>se,__wbg_new_e17d9f43105b08be:()=>ue,__wbg_prototypesetcall_2a6620b6922694b2:()=>fe,__wbg_set_3f1d0b984ed272ed:()=>be,__wbg_set_907fb406c34a251d:()=>de,__wbg_set_c213c871859d6500:()=>ae,__wbg_set_message_82ae475bb413aa5c:()=>ge,__wbg_set_wasm:()=>N,__wbindgen_cast_2241b6af4c4b2941:()=>le,__wbindgen_cast_4625c577ab2ec9ee:()=>we,__wbindgen_cast_9ae0607507abb057:()=>pe,__wbindgen_cast_d6cd19b81560fd6e:()=>ye,__wbindgen_init_externref_table:()=>me});module.exports=B(xe);var A=()=>{};A.prototype=A;let o;function N(e){o=e}let p=null;function a(){return(p===null||p.byteLength===0)&&(p=new Uint8Array(o.memory.buffer)),p}let y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0});y.decode();const U=2146435072;let S=0;function R(e,t){return S+=t,S>=U&&(y=new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}),y.decode(),S=t),y.decode(a().subarray(e,e+t))}function m(e,t){return e=e>>>0,R(e,t)}let f=0;const g=new TextEncoder;"encodeInto"in g||(g.encodeInto=function(e,t){const n=g.encode(e);return t.set(n),{read:e.length,written:n.length}});function l(e,t,n){if(n===void 0){const i=g.encode(e),d=t(i.length,1)>>>0;return a().subarray(d,d+i.length).set(i),f=i.length,d}let _=e.length,r=t(_,1)>>>0;const s=a();let c=0;for(;c<_;c++){const i=e.charCodeAt(c);if(i>127)break;s[r+c]=i}if(c!==_){c!==0&&(e=e.slice(c)),r=n(r,_,_=c+e.length*3,1)>>>0;const i=a().subarray(r+c,r+_),d=g.encodeInto(e,i);c+=d.written,r=n(r,_,c,1)>>>0}return f=c,r}let b=null;function u(){return(b===null||b.buffer.detached===!0||b.buffer.detached===void 0&&b.buffer!==o.memory.buffer)&&(b=new DataView(o.memory.buffer)),b}function x(e){return e==null}function I(e){const t=typeof e;if(t=="number"||t=="boolean"||e==null)return`${e}`;if(t=="string")return`"${e}"`;if(t=="symbol"){const r=e.description;return r==null?"Symbol":`Symbol(${r})`}if(t=="function"){const r=e.name;return typeof r=="string"&&r.length>0?`Function(${r})`:"Function"}if(Array.isArray(e)){const r=e.length;let s="[";r>0&&(s+=I(e[0]));for(let c=1;c<r;c++)s+=", "+I(e[c]);return s+="]",s}const n=/\[object ([^\]]+)\]/.exec(toString.call(e));let _;if(n&&n.length>1)_=n[1];else return toString.call(e);if(_=="Object")try{return"Object("+JSON.stringify(e)+")"}catch{return"Object"}return e instanceof Error?`${e.name}: ${e.message}
|
||||||
|
${e.stack}`:_}function $(e,t){return e=e>>>0,a().subarray(e/1,e/1+t)}function w(e){const t=o.__wbindgen_externrefs.get(e);return o.__externref_table_dealloc(e),t}const E=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry(e=>o.__wbg_querycompiler_free(e>>>0,1));class F{__destroy_into_raw(){const t=this.__wbg_ptr;return this.__wbg_ptr=0,E.unregister(this),t}free(){const t=this.__destroy_into_raw();o.__wbg_querycompiler_free(t,0)}compileBatch(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compileBatch(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}constructor(t){const n=o.querycompiler_new(t);if(n[2])throw w(n[1]);return this.__wbg_ptr=n[0]>>>0,E.register(this,this.__wbg_ptr,this),this}compile(t){const n=l(t,o.__wbindgen_malloc,o.__wbindgen_realloc),_=f,r=o.querycompiler_compile(this.__wbg_ptr,n,_);if(r[2])throw w(r[1]);return w(r[0])}}Symbol.dispose&&(F.prototype[Symbol.dispose]=F.prototype.free);function q(e,t){return Error(m(e,t))}function C(e){return Number(e)}function k(e,t){const n=String(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function W(e){const t=e,n=typeof t=="boolean"?t:void 0;return x(n)?16777215:n?1:0}function V(e,t){const n=I(t),_=l(n,o.__wbindgen_malloc,o.__wbindgen_realloc),r=f;u().setInt32(e+4*1,r,!0),u().setInt32(e+4*0,_,!0)}function z(e,t){return e in t}function L(e){const t=e;return typeof t=="object"&&t!==null}function P(e){return typeof e=="string"}function Q(e){return e===void 0}function Y(e,t){return e==t}function G(e,t){const n=t,_=typeof n=="number"?n:void 0;u().setFloat64(e+8*1,x(_)?0:_,!0),u().setInt32(e+4*0,!x(_),!0)}function J(e,t){const n=t,_=typeof n=="string"?n:void 0;var r=x(_)?0:l(_,o.__wbindgen_malloc,o.__wbindgen_realloc),s=f;u().setInt32(e+4*1,s,!0),u().setInt32(e+4*0,r,!0)}function X(e,t){throw new Error(m(e,t))}function H(e){return Object.entries(e)}function K(e){return e.getTime()}function Z(e,t){return e[t>>>0]}function v(e,t){return e[t]}function ee(e){let t;try{t=e instanceof ArrayBuffer}catch{t=!1}return t}function te(e){let t;try{t=e instanceof Uint8Array}catch{t=!1}return t}function ne(e){return Number.isSafeInteger(e)}function re(e){return e.length}function _e(e){return e.length}function oe(){return new Date}function ce(){return new Object}function ie(e){return new Uint8Array(e)}function se(){return new Map}function ue(){return new Array}function fe(e,t,n){Uint8Array.prototype.set.call($(e,t),n)}function be(e,t,n){e[t]=n}function de(e,t,n){return e.set(t,n)}function ae(e,t,n){e[t>>>0]=n}function ge(e,t){global.PRISMA_WASM_PANIC_REGISTRY.set_message(m(e,t))}function le(e,t){return m(e,t)}function we(e){return BigInt.asUintN(64,e)}function pe(e){return e}function ye(e){return e}function me(){const e=o.__wbindgen_externrefs,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)}0&&(module.exports={QueryCompiler,__wbg_Error_e83987f665cf5504,__wbg_Number_bb48ca12f395cd08,__wbg_String_8f0eb39a4a4c2f66,__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68,__wbg___wbindgen_debug_string_df47ffb5e35e6763,__wbg___wbindgen_in_bb933bd9e1b3bc0f,__wbg___wbindgen_is_object_c818261d21f283a4,__wbg___wbindgen_is_string_fbb76cb2940daafd,__wbg___wbindgen_is_undefined_2d472862bd29a478,__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147,__wbg___wbindgen_number_get_a20bf9b85341449d,__wbg___wbindgen_string_get_e4f06c90489ad01b,__wbg___wbindgen_throw_b855445ff6a94295,__wbg_entries_e171b586f8f6bdbf,__wbg_getTime_14776bfb48a1bff9,__wbg_get_7bed016f185add81,__wbg_get_with_ref_key_1dc361bd10053bfe,__wbg_instanceof_ArrayBuffer_70beb1189ca63b38,__wbg_instanceof_Uint8Array_20c8e73002f7af98,__wbg_isSafeInteger_d216eda7911dde36,__wbg_length_69bca3cb64fc8748,__wbg_length_cdd215e10d9dd507,__wbg_new_0_f9740686d739025c,__wbg_new_1acc0b6eea89d040,__wbg_new_5a79be3ab53b8aa5,__wbg_new_68651c719dcda04e,__wbg_new_e17d9f43105b08be,__wbg_prototypesetcall_2a6620b6922694b2,__wbg_set_3f1d0b984ed272ed,__wbg_set_907fb406c34a251d,__wbg_set_c213c871859d6500,__wbg_set_message_82ae475bb413aa5c,__wbg_set_wasm,__wbindgen_cast_2241b6af4c4b2941,__wbindgen_cast_4625c577ab2ec9ee,__wbindgen_cast_9ae0607507abb057,__wbindgen_cast_d6cd19b81560fd6e,__wbindgen_init_externref_table});
|
||||||
BIN
server/prisma/generated/client/query_compiler_bg.wasm
Normal file
BIN
server/prisma/generated/client/query_compiler_bg.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
3317
server/prisma/generated/client/runtime/client.d.ts
vendored
Normal file
3317
server/prisma/generated/client/runtime/client.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
86
server/prisma/generated/client/runtime/client.js
Normal file
86
server/prisma/generated/client/runtime/client.js
Normal file
File diff suppressed because one or more lines are too long
87
server/prisma/generated/client/runtime/index-browser.d.ts
vendored
Normal file
87
server/prisma/generated/client/runtime/index-browser.d.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { AnyNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { DbNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { Decimal } from '@prisma/client-runtime-utils';
|
||||||
|
import { isAnyNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { isDbNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { isJsonNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { JsonNull } from '@prisma/client-runtime-utils';
|
||||||
|
import { NullTypes } from '@prisma/client-runtime-utils';
|
||||||
|
|
||||||
|
export { AnyNull }
|
||||||
|
|
||||||
|
declare type Args<T, F extends Operation> = T extends {
|
||||||
|
[K: symbol]: {
|
||||||
|
types: {
|
||||||
|
operations: {
|
||||||
|
[K in F]: {
|
||||||
|
args: any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} ? T[symbol]['types']['operations'][F]['args'] : any;
|
||||||
|
|
||||||
|
export { DbNull }
|
||||||
|
|
||||||
|
export { Decimal }
|
||||||
|
|
||||||
|
declare type Exact<A, W> = (A extends unknown ? (W extends A ? {
|
||||||
|
[K in keyof A]: Exact<A[K], W[K]>;
|
||||||
|
} : W) : never) | (A extends Narrowable ? A : never);
|
||||||
|
|
||||||
|
export declare function getRuntime(): GetRuntimeOutput;
|
||||||
|
|
||||||
|
declare type GetRuntimeOutput = {
|
||||||
|
id: RuntimeName;
|
||||||
|
prettyName: string;
|
||||||
|
isEdge: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { isAnyNull }
|
||||||
|
|
||||||
|
export { isDbNull }
|
||||||
|
|
||||||
|
export { isJsonNull }
|
||||||
|
|
||||||
|
export { JsonNull }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates more strict variant of an enum which, unlike regular enum,
|
||||||
|
* throws on non-existing property access. This can be useful in following situations:
|
||||||
|
* - we have an API, that accepts both `undefined` and `SomeEnumType` as an input
|
||||||
|
* - enum values are generated dynamically from DMMF.
|
||||||
|
*
|
||||||
|
* In that case, if using normal enums and no compile-time typechecking, using non-existing property
|
||||||
|
* will result in `undefined` value being used, which will be accepted. Using strict enum
|
||||||
|
* in this case will help to have a runtime exception, telling you that you are probably doing something wrong.
|
||||||
|
*
|
||||||
|
* Note: if you need to check for existence of a value in the enum you can still use either
|
||||||
|
* `in` operator or `hasOwnProperty` function.
|
||||||
|
*
|
||||||
|
* @param definition
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export declare function makeStrictEnum<T extends Record<PropertyKey, string | number>>(definition: T): T;
|
||||||
|
|
||||||
|
declare type Narrowable = string | number | bigint | boolean | [];
|
||||||
|
|
||||||
|
export { NullTypes }
|
||||||
|
|
||||||
|
declare type Operation = 'findFirst' | 'findFirstOrThrow' | 'findUnique' | 'findUniqueOrThrow' | 'findMany' | 'create' | 'createMany' | 'createManyAndReturn' | 'update' | 'updateMany' | 'updateManyAndReturn' | 'upsert' | 'delete' | 'deleteMany' | 'aggregate' | 'count' | 'groupBy' | '$queryRaw' | '$executeRaw' | '$queryRawUnsafe' | '$executeRawUnsafe' | 'findRaw' | 'aggregateRaw' | '$runCommandRaw';
|
||||||
|
|
||||||
|
declare namespace Public {
|
||||||
|
export {
|
||||||
|
validator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { Public }
|
||||||
|
|
||||||
|
declare type RuntimeName = 'workerd' | 'deno' | 'netlify' | 'node' | 'bun' | 'edge-light' | '';
|
||||||
|
|
||||||
|
declare function validator<V>(): <S>(select: Exact<S, V>) => S;
|
||||||
|
|
||||||
|
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation>(client: C, model: M, operation: O): <S>(select: Exact<S, Args<C[M], O>>) => S;
|
||||||
|
|
||||||
|
declare function validator<C, M extends Exclude<keyof C, `$${string}`>, O extends keyof C[M] & Operation, P extends keyof Args<C[M], O>>(client: C, model: M, operation: O, prop: P): <S>(select: Exact<S, Args<C[M], O>[P]>) => S;
|
||||||
|
|
||||||
|
export { }
|
||||||
6
server/prisma/generated/client/runtime/index-browser.js
Normal file
6
server/prisma/generated/client/runtime/index-browser.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
"use strict";var s=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var a=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of p(t))!f.call(e,i)&&i!==n&&s(e,i,{get:()=>t[i],enumerable:!(r=g(t,i))||r.enumerable});return e};var x=e=>y(s({},"__esModule",{value:!0}),e);var O={};a(O,{AnyNull:()=>o.AnyNull,DbNull:()=>o.DbNull,Decimal:()=>m.Decimal,JsonNull:()=>o.JsonNull,NullTypes:()=>o.NullTypes,Public:()=>l,getRuntime:()=>c,isAnyNull:()=>o.isAnyNull,isDbNull:()=>o.isDbNull,isJsonNull:()=>o.isJsonNull,makeStrictEnum:()=>u});module.exports=x(O);var l={};a(l,{validator:()=>d});function d(...e){return t=>t}var b=new Set(["toJSON","$$typeof","asymmetricMatch",Symbol.iterator,Symbol.toStringTag,Symbol.isConcatSpreadable,Symbol.toPrimitive]);function u(e){return new Proxy(e,{get(t,n){if(n in t)return t[n];if(!b.has(n))throw new TypeError("Invalid enum value: ".concat(String(n)))}})}var N=()=>{var e,t;return((t=(e=globalThis.process)==null?void 0:e.release)==null?void 0:t.name)==="node"},S=()=>{var e,t;return!!globalThis.Bun||!!((t=(e=globalThis.process)==null?void 0:e.versions)!=null&&t.bun)},E=()=>!!globalThis.Deno,R=()=>typeof globalThis.Netlify=="object",h=()=>typeof globalThis.EdgeRuntime=="object",C=()=>{var e;return((e=globalThis.navigator)==null?void 0:e.userAgent)==="Cloudflare-Workers"};function k(){var n;return(n=[[R,"netlify"],[h,"edge-light"],[C,"workerd"],[E,"deno"],[S,"bun"],[N,"node"]].flatMap(r=>r[0]()?[r[1]]:[]).at(0))!=null?n:""}var M={node:"Node.js",workerd:"Cloudflare Workers",deno:"Deno and Deno Deploy",netlify:"Netlify Edge Functions","edge-light":"Edge Runtime (Vercel Edge Functions, Vercel Edge Middleware, Next.js (Pages Router) Edge API Routes, Next.js (App Router) Edge Route Handlers or Next.js Middleware)"};function c(){let e=k();return{id:e,prettyName:M[e]||e,isEdge:["workerd","deno","netlify","edge-light"].includes(e)}}var o=require("@prisma/client-runtime-utils"),m=require("@prisma/client-runtime-utils");0&&(module.exports={AnyNull,DbNull,Decimal,JsonNull,NullTypes,Public,getRuntime,isAnyNull,isDbNull,isJsonNull,makeStrictEnum});
|
||||||
|
//# sourceMappingURL=index-browser.js.map
|
||||||
76
server/prisma/generated/client/runtime/wasm-compiler-edge.js
Normal file
76
server/prisma/generated/client/runtime/wasm-compiler-edge.js
Normal file
File diff suppressed because one or more lines are too long
45
server/prisma/generated/client/schema.prisma
Normal file
45
server/prisma/generated/client/schema.prisma
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
output = "./generated/client"
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
username String @unique
|
||||||
|
password String
|
||||||
|
displayName String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
Session Session[]
|
||||||
|
UserPreferences UserPreferences?
|
||||||
|
channels Channel[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id
|
||||||
|
userId String
|
||||||
|
expiresAt DateTime
|
||||||
|
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model UserPreferences {
|
||||||
|
userId String @id
|
||||||
|
toggleInputHotkey String? @default("")
|
||||||
|
toggleOutputHotkey String? @default("")
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model Channel {
|
||||||
|
id String @id
|
||||||
|
name String
|
||||||
|
maxClients Int?
|
||||||
|
persistent Boolean @default(false)
|
||||||
|
owner_id String?
|
||||||
|
owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_compiler_bg.wasm?module')
|
||||||
5
server/prisma/generated/client/wasm-worker-loader.mjs
Normal file
5
server/prisma/generated/client/wasm-worker-loader.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!!
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
export default import('./query_compiler_bg.wasm')
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserPreferences" (
|
||||||
|
"userId" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"toggleInputHotkey" TEXT NOT NULL,
|
||||||
|
"toggleOutputHotkey" TEXT NOT NULL,
|
||||||
|
CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `volumes` to the `UserPreferences` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_UserPreferences" (
|
||||||
|
"userId" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"toggleInputHotkey" TEXT NOT NULL,
|
||||||
|
"toggleOutputHotkey" TEXT NOT NULL,
|
||||||
|
"volumes" JSONB NOT NULL,
|
||||||
|
CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_UserPreferences" ("toggleInputHotkey", "toggleOutputHotkey", "userId") SELECT "toggleInputHotkey", "toggleOutputHotkey", "userId" FROM "UserPreferences";
|
||||||
|
DROP TABLE "UserPreferences";
|
||||||
|
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_UserPreferences" (
|
||||||
|
"userId" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"toggleInputHotkey" TEXT,
|
||||||
|
"toggleOutputHotkey" TEXT,
|
||||||
|
"volumes" JSONB,
|
||||||
|
CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_UserPreferences" ("toggleInputHotkey", "toggleOutputHotkey", "userId", "volumes") SELECT "toggleInputHotkey", "toggleOutputHotkey", "userId", "volumes" FROM "UserPreferences";
|
||||||
|
DROP TABLE "UserPreferences";
|
||||||
|
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `volumes` on the `UserPreferences` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Channel" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"maxClients" INTEGER,
|
||||||
|
"persistent" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"owner_id" TEXT,
|
||||||
|
CONSTRAINT "Channel_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_UserPreferences" (
|
||||||
|
"userId" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"toggleInputHotkey" TEXT DEFAULT '',
|
||||||
|
"toggleOutputHotkey" TEXT DEFAULT '',
|
||||||
|
CONSTRAINT "UserPreferences_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_UserPreferences" ("toggleInputHotkey", "toggleOutputHotkey", "userId") SELECT "toggleInputHotkey", "toggleOutputHotkey", "userId" FROM "UserPreferences";
|
||||||
|
DROP TABLE "UserPreferences";
|
||||||
|
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "sqlite"
|
||||||
url = env("DATABASE_URL")
|
// url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client"
|
||||||
// output = "./generated/client"
|
output = "./generated/client"
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
@@ -15,16 +15,32 @@ model User {
|
|||||||
displayName String
|
displayName String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
Session Session[]
|
Session Session[]
|
||||||
|
UserPreferences UserPreferences?
|
||||||
|
channels Channel[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id String @id
|
id String @id
|
||||||
userId String
|
userId String
|
||||||
expiresAt DateTime
|
expiresAt DateTime
|
||||||
|
|
||||||
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model UserPreferences {
|
||||||
|
userId String @id
|
||||||
|
toggleInputHotkey String? @default("")
|
||||||
|
toggleOutputHotkey String? @default("")
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model Channel {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
maxClients Int?
|
||||||
|
persistent Boolean @default(false)
|
||||||
|
owner_id String?
|
||||||
|
owner User? @relation(fields: [owner_id], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
23
server/prisma/seed.ts
Normal file
23
server/prisma/seed.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import prisma from '../prisma/client.ts'
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await prisma.channel.upsert({
|
||||||
|
where: { id: 'default' },
|
||||||
|
update: {},
|
||||||
|
create: {
|
||||||
|
id: 'default',
|
||||||
|
name: 'Default',
|
||||||
|
persistent: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.then(async () => {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
})
|
||||||
|
.catch(async (e) => {
|
||||||
|
console.error(e)
|
||||||
|
await prisma.$disconnect()
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
97
server/routes/user.ts
Normal file
97
server/routes/user.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import type { FastifyInstance } from 'fastify'
|
||||||
|
import type { Namespace } from '../types/socket.ts'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import prisma from '../prisma/client.ts'
|
||||||
|
import { socketToClient } from '../utils/socket-to-client.ts'
|
||||||
|
|
||||||
|
export default function (fastify: FastifyInstance) {
|
||||||
|
fastify.get('/preferences', async (req, reply) => {
|
||||||
|
if (req.user) {
|
||||||
|
return prisma.userPreferences.findFirst({ where: { userId: req.user.id } })
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.code(401).send(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.patch('/preferences', async (req, reply) => {
|
||||||
|
if (!req.user) {
|
||||||
|
reply.code(401).send(false)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const schema = z.object({
|
||||||
|
toggleInputHotkey: z.string().optional(),
|
||||||
|
toggleOutputHotkey: z.string().optional(),
|
||||||
|
volumes: z.record(z.string(), z.number()).optional(),
|
||||||
|
})
|
||||||
|
const input = schema.parse(req.body)
|
||||||
|
|
||||||
|
return prisma.userPreferences.upsert({
|
||||||
|
where: { userId: req.user.id },
|
||||||
|
create: {
|
||||||
|
userId: req.user.id,
|
||||||
|
...input,
|
||||||
|
},
|
||||||
|
update: input,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fastify.log.error(err)
|
||||||
|
reply.code(400)
|
||||||
|
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
reply.send({ error: z.prettifyError(err) })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reply.send({ error: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
fastify.patch('/profile', async (req, reply) => {
|
||||||
|
if (!req.user) {
|
||||||
|
reply.code(401).send(false)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const schema = z.object({
|
||||||
|
displayName: z.string().optional(),
|
||||||
|
})
|
||||||
|
const input = schema.parse(req.body)
|
||||||
|
|
||||||
|
const updatedUser = prisma.user.update({
|
||||||
|
where: { id: req.user.id },
|
||||||
|
data: {
|
||||||
|
displayName: input.displayName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const namespace: Namespace = fastify.io.of('/webrtc')
|
||||||
|
const sockets = await namespace.fetchSockets()
|
||||||
|
|
||||||
|
const found = sockets.find(socket => socket.data.joined && socket.data.userId === req.user!.id)
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
found.data.displayName = input.displayName
|
||||||
|
namespace.emit('clientChanged', found.id, socketToClient(found))
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedUser
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
fastify.log.error(err)
|
||||||
|
reply.code(400)
|
||||||
|
|
||||||
|
if (err instanceof z.ZodError) {
|
||||||
|
reply.send({ error: z.prettifyError(err) })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reply.send({ error: err.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -17,7 +17,10 @@ fastify.register(FastifyCors, {
|
|||||||
origin: [
|
origin: [
|
||||||
'http://localhost:3000',
|
'http://localhost:3000',
|
||||||
'http://tauri.localhost',
|
'http://tauri.localhost',
|
||||||
|
'https://koptilnya.xyz',
|
||||||
|
'https://chad.koptilnya.xyz',
|
||||||
],
|
],
|
||||||
|
methods: ['PATCH', 'PUT', 'OPTIONS', 'GET', 'POST', 'DELETE'],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
449
server/socket/channel.ts
Normal file
449
server/socket/channel.ts
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
import type { types } from 'mediasoup'
|
||||||
|
import type { Server as SocketServer } from 'socket.io'
|
||||||
|
import type {
|
||||||
|
Namespace,
|
||||||
|
SomeSocket,
|
||||||
|
} from '../types/socket.ts'
|
||||||
|
import { consola } from 'consola'
|
||||||
|
import prisma from '../prisma/client.ts'
|
||||||
|
import { socketToClient } from '../utils/socket-to-client.ts'
|
||||||
|
|
||||||
|
export default function (io: SocketServer<Namespace>) {
|
||||||
|
io.on('connection', async (socket) => {
|
||||||
|
consola.info('[WebRtc]', 'Client connected', socket.id)
|
||||||
|
|
||||||
|
socket.data.joined = false
|
||||||
|
|
||||||
|
socket.data.inputMuted = false
|
||||||
|
socket.data.outputMuted = false
|
||||||
|
|
||||||
|
socket.data.transports = new Map()
|
||||||
|
socket.data.producers = new Map()
|
||||||
|
socket.data.consumers = new Map()
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: socket.handshake.auth.userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
displayName: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
socket.disconnect()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, username, displayName } = user
|
||||||
|
|
||||||
|
socket.data.userId = id
|
||||||
|
socket.data.username = username
|
||||||
|
socket.data.displayName = displayName
|
||||||
|
|
||||||
|
socket.emit('authenticated', { channels })
|
||||||
|
|
||||||
|
socket.on('join', async ({ rtpCapabilities }, cb) => {
|
||||||
|
if (socket.data.joined) {
|
||||||
|
consola.error('[WebRtc]', 'Already joined')
|
||||||
|
cb({ error: 'Already joined' })
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.data.joined = true
|
||||||
|
socket.data.rtpCapabilities = rtpCapabilities
|
||||||
|
|
||||||
|
const joinedSockets = await getJoinedSockets()
|
||||||
|
|
||||||
|
cb(joinedSockets.map(socketToClient))
|
||||||
|
|
||||||
|
for (const joinedSocket of joinedSockets.filter(joinedSocket => joinedSocket.id !== socket.id)) {
|
||||||
|
for (const producer of joinedSocket.data.producers.values()) {
|
||||||
|
createConsumer(
|
||||||
|
socket,
|
||||||
|
joinedSocket,
|
||||||
|
producer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.broadcast.emit('newPeer', socketToClient(socket))
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('getRtpCapabilities', (cb) => {
|
||||||
|
cb(router.rtpCapabilities)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('createTransport', async ({ producing, consuming }, cb) => {
|
||||||
|
try {
|
||||||
|
const transport = await router.createWebRtcTransport({
|
||||||
|
listenInfos: [
|
||||||
|
{
|
||||||
|
protocol: 'udp',
|
||||||
|
ip: '0.0.0.0',
|
||||||
|
announcedAddress: process.env.ANNOUNCED_ADDRESS || '127.0.0.1',
|
||||||
|
portRange: {
|
||||||
|
min: 40000,
|
||||||
|
max: 40100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
enableUdp: true,
|
||||||
|
preferUdp: true,
|
||||||
|
appData: {
|
||||||
|
producing,
|
||||||
|
consuming,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.data.transports.set(transport.id, transport)
|
||||||
|
|
||||||
|
cb({
|
||||||
|
id: transport.id,
|
||||||
|
iceParameters: transport.iceParameters,
|
||||||
|
iceCandidates: transport.iceCandidates,
|
||||||
|
dtlsParameters: transport.dtlsParameters,
|
||||||
|
})
|
||||||
|
|
||||||
|
transport.on('icestatechange', (iceState: types.IceState) => {
|
||||||
|
if (iceState === 'disconnected' || iceState === 'closed') {
|
||||||
|
consola.info('[WebRtc]', '[WebRtcTransport]', `"icestatechange" event [iceState:${iceState}], closing peer`, transport.id)
|
||||||
|
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
transport.on('dtlsstatechange', (dtlsState: types.DtlsState) => {
|
||||||
|
if (dtlsState === 'failed' || dtlsState === 'closed') {
|
||||||
|
consola.warn('WebRtcTransport "dtlsstatechange" event [dtlsState:%s], closing peer', dtlsState)
|
||||||
|
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
consola.error('[WebRtc]', '[createTransport]', error.message)
|
||||||
|
cb({ error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('connectTransport', async ({ transportId, dtlsParameters }, cb) => {
|
||||||
|
const transport = socket.data.transports.get(transportId)
|
||||||
|
|
||||||
|
if (!transport) {
|
||||||
|
consola.error('[WebRtc]', '[connectTransport]', `Transport with id ${transportId} not found`)
|
||||||
|
cb({ error: 'Transport not found' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await transport.connect({ dtlsParameters })
|
||||||
|
|
||||||
|
cb({ ok: true })
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
consola.error('[WebRtc]', '[connectTransport]', error.message)
|
||||||
|
cb({ error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('produce', async ({ transportId, kind, rtpParameters, appData }, cb) => {
|
||||||
|
if (!socket.data.joined) {
|
||||||
|
consola.error('Peer not joined yet')
|
||||||
|
cb({ error: 'Peer not joined yet' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const transport = socket.data.transports.get(transportId)
|
||||||
|
|
||||||
|
if (!transport) {
|
||||||
|
consola.error('[WebRtc]', '[produce]', `Transport with id ${transportId} not found`)
|
||||||
|
cb({ error: 'Transport not found' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const producer = await transport.produce({ kind, rtpParameters, appData: { ...appData, socketId: socket.id } })
|
||||||
|
|
||||||
|
socket.data.producers.set(producer.id, producer)
|
||||||
|
|
||||||
|
cb({ id: producer.id })
|
||||||
|
|
||||||
|
const otherSockets = await getJoinedSockets(socket.id)
|
||||||
|
|
||||||
|
for (const otherSocket of otherSockets) {
|
||||||
|
createConsumer(
|
||||||
|
otherSocket,
|
||||||
|
socket,
|
||||||
|
producer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Add into the AudioLevelObserver and ActiveSpeakerObserver.
|
||||||
|
// https://github.com/versatica/mediasoup-demo/blob/v3/server/lib/Room.js#L1276
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
consola.error('[WebRtc]', '[produce]', error.message)
|
||||||
|
cb({ error: error.message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('closeProducer', async ({ producerId }, cb) => {
|
||||||
|
if (!socket.data.joined) {
|
||||||
|
consola.error('Peer not joined yet')
|
||||||
|
cb({ error: 'Peer not joined yet' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const producer = socket.data.producers.get(producerId)
|
||||||
|
|
||||||
|
if (!producer) {
|
||||||
|
consola.error(`producer with id "${producerId}" not found`)
|
||||||
|
cb({ error: `producer with id "${producerId}" not found` })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
producer.close()
|
||||||
|
|
||||||
|
socket.data.producers.delete(producerId)
|
||||||
|
|
||||||
|
cb({ ok: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('pauseProducer', async ({ producerId }, cb) => {
|
||||||
|
if (!socket.data.joined) {
|
||||||
|
consola.error('Peer not joined yet')
|
||||||
|
cb({ error: 'Peer not joined yet' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const producer = socket.data.producers.get(producerId)
|
||||||
|
|
||||||
|
if (!producer) {
|
||||||
|
consola.error(`producer with id "${producerId}" not found`)
|
||||||
|
cb({ error: `producer with id "${producerId}" not found` })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (producer.paused)
|
||||||
|
return
|
||||||
|
|
||||||
|
await producer.pause()
|
||||||
|
|
||||||
|
cb({ ok: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('resumeProducer', async ({ producerId }, cb) => {
|
||||||
|
if (!socket.data.joined) {
|
||||||
|
consola.error('Peer not joined yet')
|
||||||
|
cb({ error: 'Peer not joined yet' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const producer = socket.data.producers.get(producerId)
|
||||||
|
|
||||||
|
if (!producer) {
|
||||||
|
consola.error(`producer with id "${producerId}" not found`)
|
||||||
|
cb({ error: `producer with id "${producerId}" not found` })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await producer.resume()
|
||||||
|
|
||||||
|
cb({ ok: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('pauseConsumer', async ({ consumerId }, cb) => {
|
||||||
|
if (!socket.data.joined) {
|
||||||
|
consola.error('Peer not joined yet')
|
||||||
|
cb({ error: 'Peer not joined yet' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const consumer = socket.data.consumers.get(consumerId)
|
||||||
|
|
||||||
|
if (!consumer) {
|
||||||
|
consola.error(`consumer with id "${consumerId}" not found`)
|
||||||
|
cb({ error: `consumer with id "${consumerId}" not found` })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await consumer.pause()
|
||||||
|
|
||||||
|
cb({ ok: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('resumeConsumer', async ({ consumerId }, cb) => {
|
||||||
|
if (!socket.data.joined) {
|
||||||
|
consola.error('Peer not joined yet')
|
||||||
|
cb({ error: 'Peer not joined yet' })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const consumer = socket.data.consumers.get(consumerId)
|
||||||
|
|
||||||
|
if (!consumer) {
|
||||||
|
consola.error(`consumer with id "${consumerId}" not found`)
|
||||||
|
cb({ error: `consumer with id "${consumerId}" not found` })
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await consumer.resume()
|
||||||
|
|
||||||
|
cb({ ok: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('updateClient', async (updatedClient, cb) => {
|
||||||
|
if (typeof updatedClient.inputMuted === 'boolean') {
|
||||||
|
socket.data.inputMuted = updatedClient.inputMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof updatedClient.outputMuted === 'boolean') {
|
||||||
|
socket.data.outputMuted = updatedClient.outputMuted
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(socketToClient(socket))
|
||||||
|
|
||||||
|
namespace.emit('clientChanged', socket.id, socketToClient(socket))
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
consola.info('Client disconnected:', socket.id)
|
||||||
|
|
||||||
|
if (socket.data.joined) {
|
||||||
|
socket.broadcast.emit('peerClosed', socket.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const transport of socket.data.transports.values()) {
|
||||||
|
transport.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function getJoinedSockets(excludeId?: string) {
|
||||||
|
const sockets = await namespace.fetchSockets()
|
||||||
|
|
||||||
|
return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createConsumer(
|
||||||
|
consumerSocket: SomeSocket,
|
||||||
|
producerSocket: SomeSocket,
|
||||||
|
producer: types.Producer,
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!consumerSocket.data.rtpCapabilities
|
||||||
|
|| !router.canConsume(
|
||||||
|
{
|
||||||
|
producerId: producer.id,
|
||||||
|
rtpCapabilities: consumerSocket.data.rtpCapabilities,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const transport = Array.from(consumerSocket.data.transports.values())
|
||||||
|
.find(t => t.appData.consuming)
|
||||||
|
|
||||||
|
if (!transport) {
|
||||||
|
consola.error('createConsumer() | Transport for consuming not found')
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let consumer: types.Consumer
|
||||||
|
|
||||||
|
try {
|
||||||
|
consumer = await transport.consume(
|
||||||
|
{
|
||||||
|
producerId: producer.id,
|
||||||
|
rtpCapabilities: consumerSocket.data.rtpCapabilities,
|
||||||
|
// Enable NACK for OPUS.
|
||||||
|
enableRtx: true,
|
||||||
|
paused: true,
|
||||||
|
ignoreDtx: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
consola.error('_createConsumer() | transport.consume():%o', error)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
consumerSocket.data.consumers.set(consumer.id, consumer)
|
||||||
|
|
||||||
|
consumer.on('transportclose', () => {
|
||||||
|
consumerSocket.data.consumers.delete(consumer.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
consumer.on('producerclose', () => {
|
||||||
|
consumerSocket.data.consumers.delete(consumer.id)
|
||||||
|
|
||||||
|
consumerSocket.emit('consumerClosed', { consumerId: consumer.id })
|
||||||
|
})
|
||||||
|
|
||||||
|
consumer.on('producerpause', () => {
|
||||||
|
consumerSocket.emit('consumerPaused', { consumerId: consumer.id })
|
||||||
|
})
|
||||||
|
|
||||||
|
consumer.on('producerresume', () => {
|
||||||
|
consumerSocket.emit('consumerResumed', { consumerId: consumer.id })
|
||||||
|
})
|
||||||
|
|
||||||
|
consumer.on('score', (score: types.ConsumerScore) => {
|
||||||
|
consumerSocket.emit('consumerScore', { consumerId: consumer.id, score })
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await consumerSocket.emitWithAck(
|
||||||
|
'newConsumer',
|
||||||
|
{
|
||||||
|
socketId: producerSocket.id,
|
||||||
|
producerId: producer.id,
|
||||||
|
id: consumer.id,
|
||||||
|
kind: consumer.kind,
|
||||||
|
rtpParameters: consumer.rtpParameters,
|
||||||
|
type: consumer.type,
|
||||||
|
appData: producer.appData,
|
||||||
|
producerPaused: consumer.producerPaused,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await consumer.resume()
|
||||||
|
|
||||||
|
consumerSocket.emit(
|
||||||
|
'consumerScore',
|
||||||
|
{
|
||||||
|
consumerId: consumer.id,
|
||||||
|
score: consumer.score,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
consola.error('_createConsumer() | failed:%o', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,147 +1,14 @@
|
|||||||
import type { User } from '@prisma/client'
|
|
||||||
import type { types } from 'mediasoup'
|
import type { types } from 'mediasoup'
|
||||||
import type { Namespace, RemoteSocket, Socket, Server as SocketServer } from 'socket.io'
|
import type { Server as SocketServer } from 'socket.io'
|
||||||
|
import type {
|
||||||
|
SomeSocket,
|
||||||
|
} from '../types/socket.ts'
|
||||||
import { consola } from 'consola'
|
import { consola } from 'consola'
|
||||||
import prisma from '../prisma/client.ts'
|
import prisma from '../prisma/client.ts'
|
||||||
|
import { socketToClient } from '../utils/socket-to-client.ts'
|
||||||
interface ChadClient {
|
|
||||||
socketId: string
|
|
||||||
userId: User['id']
|
|
||||||
username: User['username']
|
|
||||||
displayName: User['displayName']
|
|
||||||
inputMuted: boolean
|
|
||||||
outputMuted: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProducerShort {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
kind: types.MediaKind
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorCallbackResult {
|
|
||||||
error: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SuccessCallbackResult {
|
|
||||||
ok: true
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventCallback<T = SuccessCallbackResult> = (result: T | ErrorCallbackResult) => void
|
|
||||||
|
|
||||||
interface ClientToServerEvents {
|
|
||||||
join: (
|
|
||||||
options: {
|
|
||||||
rtpCapabilities: types.RtpCapabilities
|
|
||||||
},
|
|
||||||
cb: EventCallback<ChadClient[]>
|
|
||||||
) => void
|
|
||||||
getRtpCapabilities: (
|
|
||||||
cb: EventCallback<types.RtpCapabilities>
|
|
||||||
) => void
|
|
||||||
createTransport: (
|
|
||||||
options: {
|
|
||||||
producing: boolean
|
|
||||||
consuming: boolean
|
|
||||||
},
|
|
||||||
cb: EventCallback<Pick<types.WebRtcTransport, 'id' | 'iceParameters' | 'iceCandidates' | 'dtlsParameters'>>
|
|
||||||
) => void
|
|
||||||
connectTransport: (
|
|
||||||
options: {
|
|
||||||
transportId: types.WebRtcTransport['id']
|
|
||||||
dtlsParameters: types.WebRtcTransport['dtlsParameters']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
produce: (
|
|
||||||
options: {
|
|
||||||
transportId: types.WebRtcTransport['id']
|
|
||||||
kind: types.MediaKind
|
|
||||||
rtpParameters: types.RtpParameters
|
|
||||||
},
|
|
||||||
cb: EventCallback<{ id: types.Producer['id'] }>
|
|
||||||
) => void
|
|
||||||
closeProducer: (
|
|
||||||
options: {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
pauseProducer: (
|
|
||||||
options: {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
resumeProducer: (
|
|
||||||
options: {
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
pauseConsumer: (
|
|
||||||
options: {
|
|
||||||
consumerId: types.Consumer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
resumeConsumer: (
|
|
||||||
options: {
|
|
||||||
consumerId: types.Consumer['id']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
updateClient: (
|
|
||||||
options: Partial<Omit<ChadClient, 'socketId' | 'userId'>>,
|
|
||||||
cb: EventCallback<ChadClient>
|
|
||||||
) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServerToClientEvents {
|
|
||||||
authenticated: () => void
|
|
||||||
newPeer: (arg: ChadClient) => void
|
|
||||||
producers: (arg: ProducerShort[]) => void
|
|
||||||
newConsumer: (
|
|
||||||
arg: {
|
|
||||||
socketId: string
|
|
||||||
producerId: types.Producer['id']
|
|
||||||
id: types.Consumer['id']
|
|
||||||
kind: types.MediaKind
|
|
||||||
rtpParameters: types.RtpParameters
|
|
||||||
type: types.ConsumerType
|
|
||||||
appData: types.Producer['appData']
|
|
||||||
producerPaused: types.Consumer['producerPaused']
|
|
||||||
},
|
|
||||||
cb: EventCallback
|
|
||||||
) => void
|
|
||||||
peerClosed: (arg: string) => void
|
|
||||||
consumerClosed: (arg: { consumerId: string }) => void
|
|
||||||
consumerPaused: (arg: { consumerId: string }) => void
|
|
||||||
consumerResumed: (arg: { consumerId: string }) => void
|
|
||||||
consumerScore: (arg: { consumerId: string, score: types.ConsumerScore }) => void
|
|
||||||
clientChanged: (clientId: ChadClient['socketId'], client: ChadClient) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InterServerEvent {}
|
|
||||||
|
|
||||||
interface SocketData {
|
|
||||||
joined: boolean
|
|
||||||
userId: User['id']
|
|
||||||
username: User['username']
|
|
||||||
displayName: User['displayName']
|
|
||||||
inputMuted: boolean
|
|
||||||
outputMuted: boolean
|
|
||||||
rtpCapabilities: types.RtpCapabilities
|
|
||||||
transports: Map<types.WebRtcTransport['id'], types.WebRtcTransport>
|
|
||||||
producers: Map<types.Producer['id'], types.Producer>
|
|
||||||
consumers: Map<types.Consumer['id'], types.Consumer>
|
|
||||||
}
|
|
||||||
|
|
||||||
type SomeSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> | RemoteSocket<ServerToClientEvents, SocketData>
|
|
||||||
|
|
||||||
export default function (io: SocketServer, router: types.Router) {
|
export default function (io: SocketServer, router: types.Router) {
|
||||||
const namespace: Namespace<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> = io.of('/webrtc')
|
io.on('connection', async (socket) => {
|
||||||
|
|
||||||
namespace.on('connection', async (socket) => {
|
|
||||||
consola.info('[WebRtc]', 'Client connected', socket.id)
|
consola.info('[WebRtc]', 'Client connected', socket.id)
|
||||||
|
|
||||||
socket.data.joined = false
|
socket.data.joined = false
|
||||||
@@ -153,7 +20,7 @@ export default function (io: SocketServer, router: types.Router) {
|
|||||||
socket.data.producers = new Map()
|
socket.data.producers = new Map()
|
||||||
socket.data.consumers = new Map()
|
socket.data.consumers = new Map()
|
||||||
|
|
||||||
const { id, username, displayName } = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: socket.handshake.auth.userId,
|
id: socket.handshake.auth.userId,
|
||||||
},
|
},
|
||||||
@@ -164,11 +31,19 @@ export default function (io: SocketServer, router: types.Router) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
socket.disconnect()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, username, displayName } = user
|
||||||
|
|
||||||
socket.data.userId = id
|
socket.data.userId = id
|
||||||
socket.data.username = username
|
socket.data.username = username
|
||||||
socket.data.displayName = displayName
|
socket.data.displayName = displayName
|
||||||
|
|
||||||
socket.emit('authenticated')
|
socket.emit('authenticated', { channels })
|
||||||
|
|
||||||
socket.on('join', async ({ rtpCapabilities }, cb) => {
|
socket.on('join', async ({ rtpCapabilities }, cb) => {
|
||||||
if (socket.data.joined) {
|
if (socket.data.joined) {
|
||||||
@@ -278,7 +153,7 @@ export default function (io: SocketServer, router: types.Router) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('produce', async ({ transportId, kind, rtpParameters }, cb) => {
|
socket.on('produce', async ({ transportId, kind, rtpParameters, appData }, cb) => {
|
||||||
if (!socket.data.joined) {
|
if (!socket.data.joined) {
|
||||||
consola.error('Peer not joined yet')
|
consola.error('Peer not joined yet')
|
||||||
cb({ error: 'Peer not joined yet' })
|
cb({ error: 'Peer not joined yet' })
|
||||||
@@ -296,7 +171,7 @@ export default function (io: SocketServer, router: types.Router) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const producer = await transport.produce({ kind, rtpParameters, appData: { socketId: socket.id } })
|
const producer = await transport.produce({ kind, rtpParameters, appData: { ...appData, socketId: socket.id } })
|
||||||
|
|
||||||
socket.data.producers.set(producer.id, producer)
|
socket.data.producers.set(producer.id, producer)
|
||||||
|
|
||||||
@@ -439,30 +314,17 @@ export default function (io: SocketServer, router: types.Router) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
socket.on('updateClient', async (updatedClient, cb) => {
|
socket.on('updateClient', async (updatedClient, cb) => {
|
||||||
if (updatedClient.displayName) {
|
if (typeof updatedClient.inputMuted === 'boolean') {
|
||||||
await prisma.user.update({
|
|
||||||
where: {
|
|
||||||
id: socket.data.userId,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
displayName: updatedClient.displayName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.data.displayName = updatedClient.displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updatedClient.inputMuted) {
|
|
||||||
socket.data.inputMuted = updatedClient.inputMuted
|
socket.data.inputMuted = updatedClient.inputMuted
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedClient.outputMuted) {
|
if (typeof updatedClient.outputMuted === 'boolean') {
|
||||||
socket.data.outputMuted = updatedClient.outputMuted
|
socket.data.outputMuted = updatedClient.outputMuted
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(socketToClient(socket))
|
cb(socketToClient(socket))
|
||||||
|
|
||||||
namespace.emit('clientChanged', socket.id, socketToClient(socket))
|
io.emit('clientChanged', socket.id, socketToClient(socket))
|
||||||
})
|
})
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
@@ -479,7 +341,7 @@ export default function (io: SocketServer, router: types.Router) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function getJoinedSockets(excludeId?: string) {
|
async function getJoinedSockets(excludeId?: string) {
|
||||||
const sockets = await namespace.fetchSockets()
|
const sockets = await io.fetchSockets()
|
||||||
|
|
||||||
return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true))
|
return sockets.filter(socket => socket.data.joined && (excludeId ? excludeId !== socket.id : true))
|
||||||
}
|
}
|
||||||
@@ -583,15 +445,4 @@ export default function (io: SocketServer, router: types.Router) {
|
|||||||
consola.error('_createConsumer() | failed:%o', error)
|
consola.error('_createConsumer() | failed:%o', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function socketToClient(socket: SomeSocket): ChadClient {
|
|
||||||
return {
|
|
||||||
socketId: socket.id,
|
|
||||||
userId: socket.data.userId,
|
|
||||||
username: socket.data.username,
|
|
||||||
displayName: socket.data.displayName,
|
|
||||||
inputMuted: socket.data.inputMuted,
|
|
||||||
outputMuted: socket.data.outputMuted,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2016",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "nodenext",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"allowImportingTsExtensions": true
|
"allowImportingTsExtensions": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "ES2023",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
147
server/types/socket.ts
Normal file
147
server/types/socket.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import type { types } from 'mediasoup'
|
||||||
|
import type { RemoteSocket, Server, Socket } from 'socket.io'
|
||||||
|
import type { ChannelModel, UserModel } from '../prisma/generated/client/models.ts'
|
||||||
|
|
||||||
|
export interface ServerInfo {
|
||||||
|
owner_id: UserModel['id']
|
||||||
|
channels: ChannelModel[]
|
||||||
|
rtpCapabilities: types.RtpCapabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChadClient {
|
||||||
|
socketId: string
|
||||||
|
userId: UserModel['id']
|
||||||
|
username: UserModel['username']
|
||||||
|
displayName: UserModel['displayName']
|
||||||
|
inputMuted: boolean
|
||||||
|
outputMuted: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProducerShort {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
kind: types.MediaKind
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorCallbackResult {
|
||||||
|
error: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuccessCallbackResult {
|
||||||
|
ok: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventCallback<T = SuccessCallbackResult> = (result: T | ErrorCallbackResult) => void
|
||||||
|
|
||||||
|
export interface ClientToServerEvents {
|
||||||
|
join: (
|
||||||
|
options: {
|
||||||
|
rtpCapabilities: types.RtpCapabilities
|
||||||
|
},
|
||||||
|
cb: EventCallback<ChadClient[]>
|
||||||
|
) => void
|
||||||
|
getRtpCapabilities: (
|
||||||
|
cb: EventCallback<types.RtpCapabilities>
|
||||||
|
) => void
|
||||||
|
createTransport: (
|
||||||
|
options: {
|
||||||
|
producing: boolean
|
||||||
|
consuming: boolean
|
||||||
|
},
|
||||||
|
cb: EventCallback<Pick<types.WebRtcTransport, 'id' | 'iceParameters' | 'iceCandidates' | 'dtlsParameters'>>
|
||||||
|
) => void
|
||||||
|
connectTransport: (
|
||||||
|
options: {
|
||||||
|
transportId: types.WebRtcTransport['id']
|
||||||
|
dtlsParameters: types.WebRtcTransport['dtlsParameters']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
produce: (
|
||||||
|
options: {
|
||||||
|
transportId: types.WebRtcTransport['id']
|
||||||
|
kind: types.MediaKind
|
||||||
|
rtpParameters: types.RtpParameters
|
||||||
|
appData: { source: 'share' | string }
|
||||||
|
},
|
||||||
|
cb: EventCallback<{ id: types.Producer['id'] }>
|
||||||
|
) => void
|
||||||
|
closeProducer: (
|
||||||
|
options: {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
pauseProducer: (
|
||||||
|
options: {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
resumeProducer: (
|
||||||
|
options: {
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
pauseConsumer: (
|
||||||
|
options: {
|
||||||
|
consumerId: types.Consumer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
resumeConsumer: (
|
||||||
|
options: {
|
||||||
|
consumerId: types.Consumer['id']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
updateClient: (
|
||||||
|
options: Partial<Pick<ChadClient, 'inputMuted' | 'outputMuted'>>,
|
||||||
|
cb: EventCallback<ChadClient>
|
||||||
|
) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerToClientEvents {
|
||||||
|
authenticated: (arg: ServerInfo) => void
|
||||||
|
newPeer: (arg: ChadClient) => void
|
||||||
|
producers: (arg: ProducerShort[]) => void
|
||||||
|
newConsumer: (
|
||||||
|
arg: {
|
||||||
|
socketId: string
|
||||||
|
producerId: types.Producer['id']
|
||||||
|
id: types.Consumer['id']
|
||||||
|
kind: types.MediaKind
|
||||||
|
rtpParameters: types.RtpParameters
|
||||||
|
type: types.ConsumerType
|
||||||
|
appData: types.Producer['appData']
|
||||||
|
producerPaused: types.Consumer['producerPaused']
|
||||||
|
},
|
||||||
|
cb: EventCallback
|
||||||
|
) => void
|
||||||
|
peerClosed: (arg: string) => void
|
||||||
|
consumerClosed: (arg: { consumerId: string }) => void
|
||||||
|
consumerPaused: (arg: { consumerId: string }) => void
|
||||||
|
consumerResumed: (arg: { consumerId: string }) => void
|
||||||
|
consumerScore: (arg: { consumerId: string, score: types.ConsumerScore }) => void
|
||||||
|
clientChanged: (clientId: ChadClient['socketId'], client: ChadClient) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InterServerEvent {}
|
||||||
|
|
||||||
|
export interface SocketData {
|
||||||
|
joined: boolean
|
||||||
|
userId: UserModel['id']
|
||||||
|
username: UserModel['username']
|
||||||
|
displayName: UserModel['displayName']
|
||||||
|
inputMuted: boolean
|
||||||
|
outputMuted: boolean
|
||||||
|
rtpCapabilities: types.RtpCapabilities
|
||||||
|
transports: Map<types.WebRtcTransport['id'], types.WebRtcTransport>
|
||||||
|
producers: Map<types.Producer['id'], types.Producer>
|
||||||
|
consumers: Map<types.Consumer['id'], types.Consumer>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SomeSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
||||||
|
| RemoteSocket<ServerToClientEvents, SocketData>
|
||||||
|
|
||||||
|
export type SocketServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
||||||
12
server/utils/socket-to-client.ts
Normal file
12
server/utils/socket-to-client.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { ChadClient, SomeSocket } from '../types/socket.ts'
|
||||||
|
|
||||||
|
export function socketToClient(socket: SomeSocket): ChadClient {
|
||||||
|
return {
|
||||||
|
socketId: socket.id,
|
||||||
|
userId: socket.data.userId,
|
||||||
|
username: socket.data.username,
|
||||||
|
displayName: socket.data.displayName,
|
||||||
|
inputMuted: socket.data.inputMuted,
|
||||||
|
outputMuted: socket.data.outputMuted,
|
||||||
|
}
|
||||||
|
}
|
||||||
825
server/yarn.lock
825
server/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user