работаем бля работаем
This commit is contained in:
54
server/.zed/settings.json
Normal file
54
server/.zed/settings.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
// Use ESLint's --fix:
|
||||
"code_actions_on_format": {
|
||||
"source.fixAll.eslint": true,
|
||||
},
|
||||
"formatter": [],
|
||||
// Enable eslint for all supported languages
|
||||
// Defaults only include https://github.com/search?q=repo%3Azed-industries%2Fzed%20eslint_languages&type=code
|
||||
"languages": {
|
||||
"HTML": {
|
||||
"language_servers": ["...", "eslint"],
|
||||
},
|
||||
"Markdown": {
|
||||
"language_servers": ["...", "eslint"],
|
||||
},
|
||||
"Markdown-Inline": {
|
||||
"language_servers": ["...", "eslint"],
|
||||
},
|
||||
"JSON": {
|
||||
"language_servers": ["...", "eslint"],
|
||||
},
|
||||
"JSONC": {
|
||||
"language_servers": ["...", "eslint"],
|
||||
},
|
||||
"YAML": {
|
||||
"language_servers": ["...", "eslint"],
|
||||
},
|
||||
"CSS": {
|
||||
"language_servers": ["...", "eslint"],
|
||||
},
|
||||
// Add other languages as needed
|
||||
},
|
||||
"lsp": {
|
||||
"eslint": {
|
||||
"settings": {
|
||||
"workingDirectories": ["./"],
|
||||
|
||||
// Silent the stylistic rules in your IDE, but still auto fix them
|
||||
"rulesCustomizations": [
|
||||
{ "rule": "style/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "format/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-indent", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spacing", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spaces", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-order", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-dangle", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-newline", "severity": "off", "fixable": true },
|
||||
{ "rule": "*quotes", "severity": "off", "fixable": true },
|
||||
{ "rule": "*semi", "severity": "off", "fixable": true },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
382
server/Api.ts
382
server/Api.ts
@@ -10,6 +10,167 @@
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Attachment
|
||||
* Attachment
|
||||
*/
|
||||
export interface Attachment {
|
||||
id: string;
|
||||
name: string;
|
||||
mimetype: string;
|
||||
/** @min 0 */
|
||||
size: number;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel
|
||||
* Channel
|
||||
*/
|
||||
export interface Channel {
|
||||
id: string;
|
||||
ownerId: string | null;
|
||||
name: string;
|
||||
persistent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ChatMessage
|
||||
* ChatMessage
|
||||
*/
|
||||
export interface ChatMessage {
|
||||
/** @format uuid */
|
||||
id: string;
|
||||
/** @format uuid */
|
||||
senderId: string;
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
/** @format date-time */
|
||||
updatedAt: string;
|
||||
attachments: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateChannelPayload
|
||||
* CreateChannelPayload
|
||||
*/
|
||||
export interface CreateChannelPayload {
|
||||
name: string;
|
||||
persistent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateUser
|
||||
* CreateUser
|
||||
*/
|
||||
export interface CreateUser {
|
||||
/** @minLength 1 */
|
||||
username: string;
|
||||
/** @minLength 6 */
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAttachmentParams
|
||||
* GetAttachmentParams
|
||||
*/
|
||||
export interface GetAttachmentParams {
|
||||
/** @format uuid */
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetUserQuery
|
||||
* GetUserQuery
|
||||
*/
|
||||
export interface GetUserQuery {
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
* Login
|
||||
*/
|
||||
export interface Login {
|
||||
/** @minLength 1 */
|
||||
username: string;
|
||||
/** @minLength 1 */
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* NewChatMessagePayload
|
||||
* NewChatMessagePayload
|
||||
*/
|
||||
export interface NewChatMessagePayload {
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
attachments?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply
|
||||
* Reply
|
||||
*/
|
||||
export interface Reply {
|
||||
/** @format uuid */
|
||||
messageId: string;
|
||||
/** @format uuid */
|
||||
senderId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ResponseError
|
||||
* ResponseError
|
||||
*/
|
||||
export interface ResponseError {
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateUserPayload
|
||||
* UpdateUserPayload
|
||||
*/
|
||||
export interface UpdateUserPayload {
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateUserPreferencesPayload
|
||||
* UpdateUserPreferencesPayload
|
||||
*/
|
||||
export interface UpdateUserPreferencesPayload {
|
||||
toggleInputHotkey?: string;
|
||||
toggleOutputHotkey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UserPreferences
|
||||
* UserPreferences
|
||||
*/
|
||||
export interface UserPreferences {
|
||||
toggleInputHotkey: string;
|
||||
toggleOutputHotkey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User
|
||||
* User
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export type QueryParamsType = Record<string | number, any>;
|
||||
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
||||
|
||||
@@ -282,14 +443,7 @@ export class Api<
|
||||
* @request POST:/chad/attachment/upload
|
||||
*/
|
||||
attachmentUpload: (params: RequestParams = {}) =>
|
||||
this.request<
|
||||
string,
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
this.request<string, ResponseError>({
|
||||
path: `/chad/attachment/upload`,
|
||||
method: "POST",
|
||||
format: "json",
|
||||
@@ -305,14 +459,7 @@ export class Api<
|
||||
* @request GET:/chad/attachment/{id}
|
||||
*/
|
||||
attachmentGet: (id: string, params: RequestParams = {}) =>
|
||||
this.request<
|
||||
any,
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/attachment/${id}`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
@@ -327,29 +474,8 @@ export class Api<
|
||||
* @summary Register
|
||||
* @request POST:/chad/auth/register
|
||||
*/
|
||||
authRegister: (
|
||||
data: {
|
||||
/** @minLength 1 */
|
||||
username: string;
|
||||
/** @minLength 6 */
|
||||
password: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
{
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
authRegister: (data: CreateUser, params: RequestParams = {}) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/auth/register`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
@@ -366,29 +492,8 @@ export class Api<
|
||||
* @summary Login
|
||||
* @request POST:/chad/auth/login
|
||||
*/
|
||||
authLogin: (
|
||||
data: {
|
||||
/** @minLength 1 */
|
||||
username: string;
|
||||
/** @minLength 1 */
|
||||
password: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
{
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
authLogin: (data: Login, params: RequestParams = {}) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/auth/login`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
@@ -406,20 +511,7 @@ export class Api<
|
||||
* @request GET:/chad/auth/me
|
||||
*/
|
||||
authMe: (params: RequestParams = {}) =>
|
||||
this.request<
|
||||
{
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/auth/me`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
@@ -435,19 +527,61 @@ export class Api<
|
||||
* @request POST:/chad/auth/logout
|
||||
*/
|
||||
authLogout: (params: RequestParams = {}) =>
|
||||
this.request<
|
||||
any,
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/auth/logout`,
|
||||
method: "POST",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Channel
|
||||
* @name ChannelList
|
||||
* @summary Get channel list
|
||||
* @request GET:/chad/channels
|
||||
*/
|
||||
channelList: (params: RequestParams = {}) =>
|
||||
this.request<Channel[], ResponseError>({
|
||||
path: `/chad/channels`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Channel
|
||||
* @name ChannelCreate
|
||||
* @summary Create channel
|
||||
* @request POST:/chad/channels
|
||||
*/
|
||||
channelCreate: (data: CreateChannelPayload, params: RequestParams = {}) =>
|
||||
this.request<Channel, ResponseError>({
|
||||
path: `/chad/channels`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Channel
|
||||
* @name ChannelDelete
|
||||
* @summary Delete channel
|
||||
* @request DELETE:/chad/channels/{id}
|
||||
*/
|
||||
channelDelete: (id: string, params: RequestParams = {}) =>
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/channels/${id}`,
|
||||
method: "DELETE",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
@@ -460,6 +594,7 @@ export class Api<
|
||||
data: {
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
attachments?: string[];
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
@@ -475,12 +610,9 @@ export class Api<
|
||||
createdAt: string;
|
||||
/** @format date-time */
|
||||
updatedAt: string;
|
||||
attachments: string[];
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
ResponseError
|
||||
>({
|
||||
path: `/chad/chat/send`,
|
||||
method: "POST",
|
||||
@@ -508,7 +640,7 @@ export class Api<
|
||||
/**
|
||||
* @min 1
|
||||
* @max 100
|
||||
* @default 2
|
||||
* @default 10
|
||||
*/
|
||||
limit: number;
|
||||
},
|
||||
@@ -527,6 +659,7 @@ export class Api<
|
||||
createdAt: string;
|
||||
/** @format date-time */
|
||||
updatedAt: string;
|
||||
attachments: string[];
|
||||
}[];
|
||||
/**
|
||||
* Cursor to last message
|
||||
@@ -534,11 +667,7 @@ export class Api<
|
||||
*/
|
||||
nextCursor?: string;
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
ResponseError
|
||||
>({
|
||||
path: `/chad/chat`,
|
||||
method: "GET",
|
||||
@@ -561,20 +690,7 @@ export class Api<
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
{
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/user`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
@@ -591,17 +707,7 @@ export class Api<
|
||||
* @request GET:/chad/user/preferences
|
||||
*/
|
||||
userGetPreferences: (params: RequestParams = {}) =>
|
||||
this.request<
|
||||
{
|
||||
toggleInputHotkey: string;
|
||||
toggleOutputHotkey: string;
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
this.request<UserPreferences, ResponseError>({
|
||||
path: `/chad/user/preferences`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
@@ -617,20 +723,10 @@ export class Api<
|
||||
* @request PATCH:/chad/user/preferences
|
||||
*/
|
||||
userUpdatePreferences: (
|
||||
data: {
|
||||
toggleInputHotkey?: string;
|
||||
toggleOutputHotkey?: string;
|
||||
},
|
||||
data: UpdateUserPreferencesPayload,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
any,
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/user/preferences`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
@@ -646,26 +742,8 @@ export class Api<
|
||||
* @summary Update profile
|
||||
* @request PATCH:/chad/profile
|
||||
*/
|
||||
userUpdateProfile: (
|
||||
data: {
|
||||
displayName: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
{
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
},
|
||||
{
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
>({
|
||||
userUpdateProfile: (data: UpdateUserPayload, params: RequestParams = {}) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/profile`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
"watch": ["."],
|
||||
"ext": ".ts,.js",
|
||||
"ignore": ["node_modules", ".idea", "dist"],
|
||||
"exec": "ts-node --transpile-only server.ts"
|
||||
"exec": ""
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"name": "server",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@4.10.3",
|
||||
"engines": {
|
||||
"node": ">=22.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"start": "ts-node --transpile-only server.ts",
|
||||
"db:deploy": "npx prisma migrate deploy && npx prisma generate"
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "yarn@4.10.3",
|
||||
"dependencies": {
|
||||
"@fastify/autoload": "^6.3.1",
|
||||
"@fastify/cookie": "^11.0.2",
|
||||
@@ -25,7 +28,7 @@
|
||||
"fastify": "^5.6.1",
|
||||
"fastify-plugin": "^5.1.0",
|
||||
"lucia": "^3.2.2",
|
||||
"mediasoup": "^3.19.3",
|
||||
"mediasoup": "^3.19.21",
|
||||
"prisma": "7",
|
||||
"socket.io": "^4.8.1",
|
||||
"typebox": "^1.1.27",
|
||||
@@ -42,8 +45,5 @@
|
||||
"nodemon": "^3.1.14",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import type { Type } from 'typebox'
|
||||
import type { UserSchema } from '../schemas/auth.ts'
|
||||
import type { ChatMessageSchema } from '../schemas/chat.ts'
|
||||
import type { Channel } from '../prisma/generated-client/client.ts'
|
||||
import type { UserSchema } from './schemas/auth.ts'
|
||||
import type { ChatMessageSchema } from './schemas/chat.ts'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import fp from 'fastify-plugin'
|
||||
|
||||
@@ -14,6 +15,8 @@ declare module 'fastify' {
|
||||
interface EventMap {
|
||||
'chat:new-message': [Type.Static<typeof ChatMessageSchema>]
|
||||
'user:profile-updated': [Type.Static<typeof UserSchema>]
|
||||
'channel:created': [Channel]
|
||||
'channel:removed': [Channel]
|
||||
}
|
||||
|
||||
const plugin: FastifyPluginAsync = fp(async (fastify) => {
|
||||
|
||||
@@ -27,20 +27,9 @@ export const autoConfig: mediasoup.types.RouterOptions = {
|
||||
},
|
||||
{
|
||||
kind: 'video',
|
||||
mimeType: 'video/VP8',
|
||||
mimeType: 'video/AV1',
|
||||
clockRate: 90000,
|
||||
parameters: {
|
||||
'x-google-start-bitrate': 1000,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'video',
|
||||
mimeType: 'video/VP9',
|
||||
clockRate: 90000,
|
||||
parameters: {
|
||||
'profile-id': 2,
|
||||
'x-google-start-bitrate': 1000,
|
||||
},
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
kind: 'video',
|
||||
@@ -66,9 +55,20 @@ export const autoConfig: mediasoup.types.RouterOptions = {
|
||||
},
|
||||
{
|
||||
kind: 'video',
|
||||
mimeType: 'video/AV1',
|
||||
mimeType: 'video/VP8',
|
||||
clockRate: 90000,
|
||||
parameters: {},
|
||||
parameters: {
|
||||
'x-google-start-bitrate': 1000,
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'video',
|
||||
mimeType: 'video/VP9',
|
||||
clockRate: 90000,
|
||||
parameters: {
|
||||
'profile-id': 2,
|
||||
'x-google-start-bitrate': 1000,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ declare module 'fastify' {
|
||||
export default fp(
|
||||
async (fastify) => {
|
||||
const worker = await mediasoup.createWorker()
|
||||
|
||||
worker.on('died', () => {
|
||||
consola.error('[Mediasoup]', 'Worker died, exiting...')
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ declare module 'fastify' {
|
||||
|
||||
const plugin: FastifyPluginAsync = fp(async (fastify) => {
|
||||
const prisma = new PrismaClient({
|
||||
log: ['query', 'error', 'warn'],
|
||||
log: ['error'],
|
||||
adapter: new PrismaBetterSqlite3({
|
||||
url: process.env.DATABASE_URL!,
|
||||
}),
|
||||
|
||||
11
server/plugins/schemas.ts
Normal file
11
server/plugins/schemas.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import fp from 'fastify-plugin'
|
||||
import * as schemas from './schemas/index.ts'
|
||||
|
||||
const plugin: FastifyPluginAsync = fp(async (fastify) => {
|
||||
for (const schema of Object.values(schemas)) {
|
||||
fastify.addSchema(schema)
|
||||
}
|
||||
})
|
||||
|
||||
export default plugin
|
||||
@@ -6,6 +6,8 @@ export const AttachmentSchema = Type.Object({
|
||||
mimetype: Type.String(),
|
||||
size: Type.Number({ minimum: 0 }),
|
||||
createdAt: Type.String({ format: 'date-time' }),
|
||||
}, { $id: 'Attachment' })
|
||||
|
||||
// message: Type.MessageAttachment(),
|
||||
}, { title: 'Attachment', description: 'Attachment' })
|
||||
export const GetAttachmentParamsSchema = Type.Object({
|
||||
id: Type.String({ format: 'uuid' }),
|
||||
}, { $id: 'GetAttachmentParams' })
|
||||
@@ -5,9 +5,14 @@ export const UserSchema = Type.Object({
|
||||
username: Type.String(),
|
||||
displayName: Type.String(),
|
||||
createdAt: Type.String({ format: 'date-time' }),
|
||||
}, { title: 'User', description: 'User' })
|
||||
}, { $id: 'User' })
|
||||
|
||||
export const CreateUserSchema = Type.Object({
|
||||
export const CreateUserPayloadSchema = Type.Object({
|
||||
username: Type.String({ minLength: 1 }),
|
||||
password: Type.String({ minLength: 6 }),
|
||||
}, { title: 'CreateUser' })
|
||||
}, { $id: 'CreateUser' })
|
||||
|
||||
export const LoginPayloadSchema = Type.Object({
|
||||
username: Type.String({ minLength: 1 }),
|
||||
password: Type.String({ minLength: 1 }),
|
||||
}, { $id: 'Login' })
|
||||
13
server/plugins/schemas/channel.ts
Normal file
13
server/plugins/schemas/channel.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Type } from 'typebox'
|
||||
|
||||
export const ChannelSchema = Type.Object({
|
||||
id: Type.String(),
|
||||
ownerId: Type.Union([Type.String(), Type.Null()]),
|
||||
name: Type.String(),
|
||||
persistent: Type.Boolean(),
|
||||
}, { $id: 'Channel' })
|
||||
|
||||
export const CreateChannelPayloadSchema = Type.Object({
|
||||
name: Type.String(),
|
||||
persistent: Type.Boolean(),
|
||||
}, { $id: 'CreateChannelPayload' })
|
||||
@@ -4,7 +4,7 @@ export const ReplySchema = Type.Object({
|
||||
messageId: Type.String({ format: 'uuid' }),
|
||||
senderId: Type.String({ format: 'uuid' }),
|
||||
text: Type.String(),
|
||||
}, { title: 'Reply', description: 'Reply' })
|
||||
}, { $id: 'Reply' })
|
||||
|
||||
export const ChatMessageSchema = Type.Object({
|
||||
id: Type.String({ format: 'uuid' }),
|
||||
@@ -14,13 +14,12 @@ export const ChatMessageSchema = Type.Object({
|
||||
updatedAt: Type.String({ format: 'date-time' }),
|
||||
|
||||
attachments: Type.Array(Type.String({ format: 'uuid' })),
|
||||
// replyTo: ReplySchema,
|
||||
}, { title: 'ChatMessage', description: 'ChatMessage' })
|
||||
}, { $id: 'ChatMessage' })
|
||||
|
||||
export const NewChatMessageSchema = Type.Object({
|
||||
export const NewChatMessagePayloadSchema = Type.Object({
|
||||
text: Type.String({ minLength: 1 }),
|
||||
attachments: Type.Optional(Type.Array(Type.String({ format: 'uuid' }))),
|
||||
// replyTo: Type.Object({
|
||||
// messageId: Type.String({ format: 'uuid' }),
|
||||
// }),
|
||||
}, { title: 'NewChatMessage', description: 'NewChatMessage' })
|
||||
}, { $id: 'NewChatMessagePayload' })
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from 'typebox'
|
||||
|
||||
export const ErrorReplySchema = Type.Object({
|
||||
export const ResponseErrorSchema = Type.Object({
|
||||
statusCode: Type.Number(),
|
||||
error: Type.String(),
|
||||
message: Type.String(),
|
||||
}, { title: 'ResponseError', description: 'Response Error' })
|
||||
}, { $id: 'ResponseError' })
|
||||
6
server/plugins/schemas/index.ts
Normal file
6
server/plugins/schemas/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './attachment.ts'
|
||||
export * from './auth.ts'
|
||||
export * from './channel.ts'
|
||||
export * from './chat.ts'
|
||||
export * from './common.ts'
|
||||
export * from './user.ts'
|
||||
19
server/plugins/schemas/user.ts
Normal file
19
server/plugins/schemas/user.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Type } from 'typebox'
|
||||
|
||||
export const GetUserQuerySchema = Type.Partial(Type.Object({
|
||||
username: Type.String(),
|
||||
}), { $id: 'GetUserQuery' })
|
||||
|
||||
export const UserPreferencesSchema = Type.Object({
|
||||
toggleInputHotkey: Type.String(),
|
||||
toggleOutputHotkey: Type.String(),
|
||||
}, { $id: 'UserPreferences' })
|
||||
|
||||
export const UpdateUserPreferencesPayloadSchema = Type.Partial(
|
||||
UserPreferencesSchema,
|
||||
{ $id: 'UpdateUserPreferencesPayload' },
|
||||
)
|
||||
|
||||
export const UpdateUserPayloadSchema = Type.Object({
|
||||
displayName: Type.String(),
|
||||
}, { $id: 'UpdateUserPayload' })
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import type { ServerOptions } from 'socket.io'
|
||||
import type { MessageSelect } from '../prisma/generated-client/models/Message.ts'
|
||||
import fp from 'fastify-plugin'
|
||||
import { Server } from 'socket.io'
|
||||
import registerChatSocket from '../socket/chat.ts'
|
||||
import registerWebrtcSocket from '../socket/webrtc.ts'
|
||||
import registerChatSocket from './socket/chat/index.ts'
|
||||
import registerWebrtcSocket from './socket/webrtc/index.ts'
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
@@ -24,12 +23,26 @@ export default fp<Partial<ServerOptions>>(
|
||||
await fastify.io.close()
|
||||
})
|
||||
|
||||
await registerWebrtcSocket(fastify.io, fastify.mediasoupRouter, fastify.prisma)
|
||||
await registerChatSocket(fastify.io)
|
||||
fastify.io.use(async (socket, next) => {
|
||||
const sessionId = fastify.lucia.readSessionCookie(socket.handshake.headers.cookie ?? '')
|
||||
|
||||
fastify.bus.on('chat:new-message', async (message: MessageSelect) => {
|
||||
fastify.io.emit('chat:new-message', message)
|
||||
if (!sessionId) {
|
||||
return next(fastify.httpErrors.unauthorized())
|
||||
}
|
||||
|
||||
const { user } = await fastify.lucia.validateSession(sessionId)
|
||||
|
||||
if (!user) {
|
||||
return next(fastify.httpErrors.unauthorized())
|
||||
}
|
||||
|
||||
socket.data.user = user
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
await registerWebrtcSocket(fastify)
|
||||
await registerChatSocket(fastify)
|
||||
},
|
||||
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router', 'prisma', 'event-bus'] },
|
||||
)
|
||||
|
||||
10
server/plugins/socket/chat/index.ts
Normal file
10
server/plugins/socket/chat/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import type { MessageSelect } from '../../../prisma/generated-client/models.ts'
|
||||
|
||||
export default async function (fastify: FastifyInstance) {
|
||||
const { io, bus } = fastify
|
||||
|
||||
bus.on('chat:new-message', async (message: MessageSelect) => {
|
||||
io.emit('chat:new-message', message)
|
||||
})
|
||||
}
|
||||
136
server/plugins/socket/types.ts
Normal file
136
server/plugins/socket/types.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { types } from 'mediasoup'
|
||||
import type { Server, Socket } from 'socket.io'
|
||||
import type { Channel, User } from '../../prisma/generated-client/client.ts'
|
||||
|
||||
export interface SerializedClient {
|
||||
socketId: string
|
||||
userId: User['id']
|
||||
channelId: Channel['id']
|
||||
inputMuted: boolean
|
||||
outputMuted: boolean
|
||||
streaming: boolean
|
||||
}
|
||||
|
||||
export interface ProducerAppData extends types.AppData {
|
||||
source: 'mic-video' | 'share'
|
||||
}
|
||||
|
||||
export interface ErrorCallbackResult {
|
||||
error: string
|
||||
}
|
||||
|
||||
export interface SuccessCallbackResult {
|
||||
ok: true
|
||||
}
|
||||
|
||||
export type EventCallback<T = SuccessCallbackResult> = (result: T | ErrorCallbackResult) => void
|
||||
|
||||
export interface ClientToServerEvents {
|
||||
'join-channel': (
|
||||
options: { channelId: string },
|
||||
cb?: EventCallback
|
||||
) => void
|
||||
'create-transport': (
|
||||
options: {
|
||||
producing: boolean
|
||||
consuming: boolean
|
||||
},
|
||||
cb: EventCallback<Pick<types.WebRtcTransport, 'id' | 'iceParameters' | 'iceCandidates' | 'dtlsParameters'>>
|
||||
) => void
|
||||
'connect-transport': (
|
||||
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
|
||||
'close-producer': (
|
||||
options: {
|
||||
producerId: types.Producer['id']
|
||||
},
|
||||
cb: EventCallback
|
||||
) => void
|
||||
'pause-producer': (
|
||||
options: {
|
||||
producerId: types.Producer['id']
|
||||
},
|
||||
cb: EventCallback
|
||||
) => void
|
||||
'resume-producer': (
|
||||
options: {
|
||||
producerId: types.Producer['id']
|
||||
},
|
||||
cb: EventCallback
|
||||
) => void
|
||||
'pause-consumer': (
|
||||
options: {
|
||||
consumerId: types.Consumer['id']
|
||||
},
|
||||
cb: EventCallback
|
||||
) => void
|
||||
'resume-consumer': (
|
||||
options: {
|
||||
consumerId: types.Consumer['id']
|
||||
},
|
||||
cb: EventCallback
|
||||
) => void
|
||||
'update-client': (
|
||||
options: Partial<Pick<SerializedClient, 'inputMuted' | 'outputMuted'>>,
|
||||
cb: EventCallback<SerializedClient>
|
||||
) => void
|
||||
}
|
||||
|
||||
export interface ServerToClientEvents {
|
||||
'initialized': (arg: {
|
||||
rtpCapabilities: types.RtpCapabilities
|
||||
channelId: string
|
||||
clients: SerializedClient[]
|
||||
}) => void
|
||||
'new-client': (arg: SerializedClient) => void
|
||||
'client-updated': (arg: SerializedClient) => void
|
||||
'client-switched-channel': (arg: SerializedClient) => void
|
||||
'client-disconnected': (arg: string) => void
|
||||
'producers': (arg: {
|
||||
producerId: types.Producer['id']
|
||||
kind: types.MediaKind
|
||||
}[]) => void
|
||||
'new-consumer': (
|
||||
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
|
||||
'consumer-closed': (arg: { consumerId: string }) => void
|
||||
'consumer-paused': (arg: { consumerId: string }) => void
|
||||
'consumer-resumed': (arg: { consumerId: string }) => void
|
||||
'speaking-clients': (arg: { clientId: SerializedClient['socketId'], volume: types.AudioLevelObserverVolume['volume'] }[]) => void
|
||||
'active-speaker': (arg?: SerializedClient['socketId']) => void
|
||||
'channel-created': (arg: Channel) => void
|
||||
'channel-removed': (arg: Channel['id']) => void
|
||||
'channel-updated': (arg: Channel) => void
|
||||
}
|
||||
|
||||
export interface InterServerEvent {}
|
||||
|
||||
export interface SocketData {
|
||||
user: User
|
||||
}
|
||||
|
||||
export type ChadSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
||||
export type ChadSocketServer = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
||||
119
server/plugins/socket/webrtc/Channel.ts
Normal file
119
server/plugins/socket/webrtc/Channel.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import type { types } from 'mediasoup'
|
||||
import type { ActiveSpeakerObserverDominantSpeaker } from 'mediasoup/types'
|
||||
import type { Client } from './Client.ts'
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
interface ChannelEvents {
|
||||
'speaking-peers': [{
|
||||
socketId: string
|
||||
volume: number
|
||||
}[]]
|
||||
'silence': []
|
||||
'active-speaker': [socketId: string]
|
||||
'empty': []
|
||||
}
|
||||
|
||||
export class Channel extends EventEmitter<ChannelEvents> {
|
||||
readonly id: string
|
||||
readonly persistent: boolean
|
||||
readonly #audioLevelObserver: types.AudioLevelObserver
|
||||
readonly #activeSpeakerObserver: types.ActiveSpeakerObserver
|
||||
readonly #clients = new Map<string, Client>()
|
||||
|
||||
private constructor(
|
||||
id: string,
|
||||
persistent: boolean,
|
||||
audioLevelObserver: types.AudioLevelObserver,
|
||||
activeSpeakerObserver: types.ActiveSpeakerObserver,
|
||||
) {
|
||||
super()
|
||||
|
||||
this.id = id
|
||||
this.persistent = persistent
|
||||
this.#audioLevelObserver = audioLevelObserver
|
||||
this.#activeSpeakerObserver = activeSpeakerObserver
|
||||
|
||||
this.#audioLevelObserver.on('volumes', (volumes: types.AudioLevelObserverVolume[]) => {
|
||||
this.emit('speaking-peers', volumes.map(({ producer, volume }) => {
|
||||
const { socketId } = producer.appData as { socketId: string }
|
||||
return { socketId, volume }
|
||||
}))
|
||||
})
|
||||
|
||||
this.#audioLevelObserver.on('silence', () => {
|
||||
this.emit('silence')
|
||||
})
|
||||
|
||||
this.#activeSpeakerObserver.on('dominantspeaker', ({ producer }: ActiveSpeakerObserverDominantSpeaker) => {
|
||||
const { socketId } = producer.appData as { socketId: string }
|
||||
this.emit('active-speaker', socketId)
|
||||
})
|
||||
}
|
||||
|
||||
static async create(id: string, persistent: boolean, router: types.Router): Promise<Channel> {
|
||||
const audioLevelObserver = await router.createAudioLevelObserver({
|
||||
maxEntries: 10,
|
||||
threshold: -80,
|
||||
interval: 800,
|
||||
})
|
||||
|
||||
const activeSpeakerObserver = await router.createActiveSpeakerObserver()
|
||||
|
||||
return new Channel(id, persistent, audioLevelObserver, activeSpeakerObserver)
|
||||
}
|
||||
|
||||
get clients(): Client[] {
|
||||
return Array.from(this.#clients.values())
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#clients.size
|
||||
}
|
||||
|
||||
getClient(socketId: string): Client | undefined {
|
||||
return this.#clients.get(socketId)
|
||||
}
|
||||
|
||||
addClient(client: Client): void {
|
||||
client.channelId = this.id
|
||||
this.#clients.set(client.socketId, client)
|
||||
}
|
||||
|
||||
kickClient(client: Client): void {
|
||||
this.#clients.delete(client.socketId)
|
||||
|
||||
if (this.#clients.size === 0)
|
||||
this.emit('empty')
|
||||
}
|
||||
|
||||
async addAudioProducer(producer: types.Producer): Promise<void> {
|
||||
if (producer.kind !== 'audio')
|
||||
return
|
||||
|
||||
await this.#audioLevelObserver.addProducer({ producerId: producer.id })
|
||||
await this.#activeSpeakerObserver.addProducer({ producerId: producer.id })
|
||||
}
|
||||
|
||||
async wireClient(client: Client): Promise<void> {
|
||||
for (const otherClient of this.#clients.values()) {
|
||||
if (otherClient.socketId === client.socketId)
|
||||
continue
|
||||
|
||||
for (const producer of otherClient.producers.values()) {
|
||||
await client.createConsumerFor(producer, otherClient.socketId)
|
||||
}
|
||||
|
||||
for (const producer of client.producers.values()) {
|
||||
await otherClient.createConsumerFor(producer, client.socketId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unwireClient(client: Client): void {
|
||||
for (const otherClient of this.#clients.values()) {
|
||||
for (const producerId of client.producers.keys()) {
|
||||
otherClient.removeConsumersOf(producerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
server/plugins/socket/webrtc/ChannelManager.ts
Normal file
33
server/plugins/socket/webrtc/ChannelManager.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { Router } from 'mediasoup/types'
|
||||
import type { Channel as DbChannel } from '../../../prisma/generated-client/client.ts'
|
||||
import { Channel } from './Channel.ts'
|
||||
|
||||
export class ChannelManager {
|
||||
private channels = new Map<string, Channel>()
|
||||
private mediasoupRouter: Router
|
||||
|
||||
constructor(mediasoupRouter: Router) {
|
||||
this.mediasoupRouter = mediasoupRouter
|
||||
}
|
||||
|
||||
async create(newChannel: Channel | DbChannel) {
|
||||
if (newChannel instanceof Channel) {
|
||||
this.channels.set(newChannel.id, newChannel)
|
||||
}
|
||||
else {
|
||||
this.channels.set(newChannel.id, await Channel.create(newChannel.id, newChannel.persistent, this.mediasoupRouter))
|
||||
}
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
return this.channels.get(id)
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
this.channels.delete(id)
|
||||
}
|
||||
|
||||
get all() {
|
||||
return Array.from(this.channels.values())
|
||||
}
|
||||
}
|
||||
288
server/plugins/socket/webrtc/Client.ts
Normal file
288
server/plugins/socket/webrtc/Client.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
import type { types } from 'mediasoup'
|
||||
import type { SerializedClient } from '../types.ts'
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { consola } from 'consola'
|
||||
|
||||
export interface NewConsumerSignal {
|
||||
socketId: string
|
||||
producerId: string
|
||||
id: string
|
||||
kind: types.MediaKind
|
||||
rtpParameters: types.RtpParameters
|
||||
type: types.ConsumerType
|
||||
appData: types.Producer['appData']
|
||||
producerPaused: boolean
|
||||
}
|
||||
|
||||
interface ClientEvents {
|
||||
'signal:new-consumer': [data: NewConsumerSignal, onAcked: () => Promise<void>]
|
||||
'consumer:closed': [consumerId: string]
|
||||
'consumer:paused': [consumerId: string]
|
||||
'consumer:resumed': [consumerId: string]
|
||||
'transport:closed': []
|
||||
'closed': []
|
||||
'updated': []
|
||||
}
|
||||
|
||||
export class Client extends EventEmitter<ClientEvents> {
|
||||
readonly socketId: string
|
||||
readonly userId: string
|
||||
|
||||
channelId: string = ''
|
||||
#inputMuted = false
|
||||
#outputMuted = false
|
||||
|
||||
readonly #router: types.Router
|
||||
|
||||
readonly #transports = new Map<string, types.WebRtcTransport>()
|
||||
readonly #producers = new Map<string, types.Producer>()
|
||||
readonly #consumers = new Map<string, types.Consumer>()
|
||||
|
||||
constructor(socketId: string, userId: string, router: types.Router) {
|
||||
super()
|
||||
|
||||
this.socketId = socketId
|
||||
this.userId = userId
|
||||
this.#router = router
|
||||
}
|
||||
|
||||
get producers(): ReadonlyMap<string, types.Producer> { return this.#producers }
|
||||
get consumers(): ReadonlyMap<string, types.Consumer> { return this.#consumers }
|
||||
get transports(): ReadonlyMap<string, types.WebRtcTransport> { return this.#transports }
|
||||
get inputMuted(): boolean { return this.#inputMuted }
|
||||
get outputMuted(): boolean { return this.#outputMuted }
|
||||
get streaming(): boolean {
|
||||
return Array.from(this.#producers.values()).some(
|
||||
producer => producer.kind === 'video' && producer.appData.source === 'share',
|
||||
)
|
||||
}
|
||||
|
||||
async createTransport(options: { producing: boolean, consuming: boolean }) {
|
||||
const transport = await this.#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: options,
|
||||
})
|
||||
|
||||
this.#transports.set(transport.id, transport)
|
||||
|
||||
transport.on('icestatechange', (iceState: types.IceState) => {
|
||||
if (iceState === 'disconnected' || iceState === 'closed') {
|
||||
consola.info('[Client]', `[${this.socketId}]`, `iceState=${iceState}`)
|
||||
this.emit('transport:closed')
|
||||
}
|
||||
})
|
||||
|
||||
transport.on('dtlsstatechange', (dtlsState: types.DtlsState) => {
|
||||
if (dtlsState === 'failed' || dtlsState === 'closed') {
|
||||
consola.warn('[Client]', `[${this.socketId}]`, `dtlsState=${dtlsState}`)
|
||||
this.emit('transport:closed')
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
id: transport.id,
|
||||
iceParameters: transport.iceParameters,
|
||||
iceCandidates: transport.iceCandidates,
|
||||
dtlsParameters: transport.dtlsParameters,
|
||||
}
|
||||
}
|
||||
|
||||
async connectTransport(transportId: string, dtlsParameters: types.DtlsParameters): Promise<void> {
|
||||
const transport = this.#transports.get(transportId)
|
||||
|
||||
if (!transport)
|
||||
throw new Error(`Transport not found: ${transportId}`)
|
||||
|
||||
await transport.connect({ dtlsParameters })
|
||||
}
|
||||
|
||||
async produce(
|
||||
transportId: string,
|
||||
kind: types.MediaKind,
|
||||
rtpParameters: types.RtpParameters,
|
||||
appData: object,
|
||||
): Promise<types.Producer> {
|
||||
const transport = this.#transports.get(transportId)
|
||||
if (!transport)
|
||||
throw new Error(`Transport not found: ${transportId}`)
|
||||
|
||||
const streamingBefore = this.streaming
|
||||
|
||||
const producer = await transport.produce({
|
||||
kind,
|
||||
rtpParameters,
|
||||
appData: { ...appData, socketId: this.socketId },
|
||||
})
|
||||
|
||||
this.#producers.set(producer.id, producer)
|
||||
|
||||
if (this.streaming !== streamingBefore)
|
||||
this.emit('updated')
|
||||
|
||||
return producer
|
||||
}
|
||||
|
||||
closeProducer(producerId: string): void {
|
||||
const producer = this.#producers.get(producerId)
|
||||
|
||||
if (!producer)
|
||||
throw new Error(`Producer not found: ${producerId}`)
|
||||
|
||||
const streamingBefore = this.streaming
|
||||
producer.close()
|
||||
this.#producers.delete(producerId)
|
||||
|
||||
if (this.streaming !== streamingBefore)
|
||||
this.emit('updated')
|
||||
}
|
||||
|
||||
async pauseProducer(producerId: string): Promise<void> {
|
||||
const producer = this.#producers.get(producerId)
|
||||
|
||||
if (!producer)
|
||||
throw new Error(`Producer not found: ${producerId}`)
|
||||
|
||||
if (!producer.paused)
|
||||
await producer.pause()
|
||||
}
|
||||
|
||||
async resumeProducer(producerId: string): Promise<void> {
|
||||
const producer = this.#producers.get(producerId)
|
||||
|
||||
if (!producer)
|
||||
throw new Error(`Producer not found: ${producerId}`)
|
||||
|
||||
await producer.resume()
|
||||
}
|
||||
|
||||
async createConsumerFor(producer: types.Producer, producerSocketId: string): Promise<types.Consumer | null> {
|
||||
const transport = Array.from(this.#transports.values()).find(t => t.appData.consuming)
|
||||
|
||||
if (!transport) {
|
||||
consola.warn('[Client]', `[${this.socketId}]`, 'No consuming transport, skipping consumer creation')
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const consumer = await transport.consume({
|
||||
producerId: producer.id,
|
||||
rtpCapabilities: this.#router.rtpCapabilities,
|
||||
enableRtx: true,
|
||||
paused: true,
|
||||
ignoreDtx: true,
|
||||
})
|
||||
|
||||
this.#consumers.set(consumer.id, consumer)
|
||||
|
||||
consumer.observer.on('close', () => {
|
||||
this.#consumers.delete(consumer.id)
|
||||
this.emit('consumer:closed', consumer.id)
|
||||
})
|
||||
|
||||
consumer.on('transportclose', () => {
|
||||
consumer.close()
|
||||
})
|
||||
|
||||
consumer.on('producerclose', () => {
|
||||
consumer.close()
|
||||
})
|
||||
|
||||
consumer.on('producerpause', () => {
|
||||
this.emit('consumer:paused', consumer.id)
|
||||
})
|
||||
|
||||
consumer.on('producerresume', () => {
|
||||
this.emit('consumer:resumed', consumer.id)
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
this.emit('signal:new-consumer', {
|
||||
socketId: producerSocketId,
|
||||
producerId: producer.id,
|
||||
id: consumer.id,
|
||||
kind: consumer.kind,
|
||||
rtpParameters: consumer.rtpParameters,
|
||||
type: consumer.type,
|
||||
appData: producer.appData,
|
||||
producerPaused: consumer.producerPaused,
|
||||
}, async () => { resolve() })
|
||||
})
|
||||
|
||||
await consumer.resume()
|
||||
|
||||
return consumer
|
||||
}
|
||||
catch (error) {
|
||||
consola.error('[Client]', `[${this.socketId}]`, 'createConsumerFor() failed:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
removeConsumersOf(producerId: string): void {
|
||||
for (const consumer of this.#consumers.values()) {
|
||||
if (consumer.producerId === producerId)
|
||||
consumer.close()
|
||||
}
|
||||
}
|
||||
|
||||
clearConsumers(): void {
|
||||
for (const consumer of this.#consumers.values()) {
|
||||
consumer.close()
|
||||
}
|
||||
|
||||
this.#consumers.clear()
|
||||
}
|
||||
|
||||
async pauseConsumer(consumerId: string): Promise<void> {
|
||||
const consumer = this.#consumers.get(consumerId)
|
||||
|
||||
if (!consumer)
|
||||
throw new Error(`Consumer not found: ${consumerId}`)
|
||||
|
||||
await consumer.pause()
|
||||
}
|
||||
|
||||
async resumeConsumer(consumerId: string): Promise<void> {
|
||||
const consumer = this.#consumers.get(consumerId)
|
||||
|
||||
if (!consumer)
|
||||
throw new Error(`Consumer not found: ${consumerId}`)
|
||||
|
||||
await consumer.resume()
|
||||
}
|
||||
|
||||
update(patch: { inputMuted?: boolean, outputMuted?: boolean }): void {
|
||||
if (typeof patch.inputMuted === 'boolean')
|
||||
this.#inputMuted = patch.inputMuted
|
||||
if (typeof patch.outputMuted === 'boolean')
|
||||
this.#outputMuted = patch.outputMuted
|
||||
|
||||
this.emit('updated')
|
||||
}
|
||||
|
||||
close(): void {
|
||||
for (const transport of this.#transports.values()) {
|
||||
transport.close()
|
||||
}
|
||||
|
||||
this.emit('closed')
|
||||
}
|
||||
|
||||
serialize(): SerializedClient {
|
||||
return {
|
||||
socketId: this.socketId,
|
||||
userId: this.userId,
|
||||
channelId: this.channelId,
|
||||
inputMuted: this.#inputMuted,
|
||||
outputMuted: this.#outputMuted,
|
||||
streaming: this.streaming,
|
||||
}
|
||||
}
|
||||
}
|
||||
254
server/plugins/socket/webrtc/Gateway.ts
Normal file
254
server/plugins/socket/webrtc/Gateway.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import type { types } from 'mediasoup'
|
||||
import type { ChadSocket, ChadSocketServer } from '../types.ts'
|
||||
import type { Channel } from './Channel.ts'
|
||||
import type { ChannelManager } from './ChannelManager.ts'
|
||||
import type { Client } from './Client.ts'
|
||||
import { consola } from 'consola'
|
||||
|
||||
export class WebRtcGateway {
|
||||
readonly #io: ChadSocketServer
|
||||
readonly #socket: ChadSocket
|
||||
readonly #client: Client
|
||||
readonly #channels: ChannelManager
|
||||
|
||||
constructor(
|
||||
io: ChadSocketServer,
|
||||
socket: ChadSocket,
|
||||
client: Client,
|
||||
channels: ChannelManager,
|
||||
) {
|
||||
this.#io = io
|
||||
this.#socket = socket
|
||||
this.#client = client
|
||||
this.#channels = channels
|
||||
|
||||
this.register()
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.#client.on('signal:new-consumer', async (data, onAcked) => {
|
||||
await this.#socket.emitWithAck('new-consumer', data)
|
||||
await onAcked()
|
||||
})
|
||||
this.#client.on('consumer:closed', consumerId => this.#socket.emit('consumer-closed', { consumerId }))
|
||||
this.#client.on('consumer:paused', consumerId => this.#socket.emit('consumer-paused', { consumerId }))
|
||||
this.#client.on('consumer:resumed', consumerId => this.#socket.emit('consumer-resumed', { consumerId }))
|
||||
this.#client.on('transport:closed', () => this.#socket.disconnect())
|
||||
this.#client.on('updated', () => this.#io.emit('client-updated', this.#client.serialize()))
|
||||
|
||||
this.#socket.on('join-channel', this.#onJoinChannel.bind(this))
|
||||
this.#socket.on('create-transport', this.#onCreateTransport.bind(this))
|
||||
this.#socket.on('connect-transport', this.#onConnectTransport.bind(this))
|
||||
this.#socket.on('produce', this.#onProduce.bind(this))
|
||||
this.#socket.on('close-producer', this.#onCloseProducer.bind(this))
|
||||
this.#socket.on('pause-producer', this.#onPauseProducer.bind(this))
|
||||
this.#socket.on('resume-producer', this.#onResumeProducer.bind(this))
|
||||
this.#socket.on('pause-consumer', this.#onPauseConsumer.bind(this))
|
||||
this.#socket.on('resume-consumer', this.#onResumeConsumer.bind(this))
|
||||
this.#socket.on('update-client', this.#onUpdateClient.bind(this))
|
||||
this.#socket.on('disconnect', this.#onDisconnect.bind(this))
|
||||
}
|
||||
|
||||
async #onJoinChannel({ channelId }: { channelId: string }): Promise<void> {
|
||||
if (this.#client.channelId === channelId)
|
||||
return
|
||||
|
||||
const newChannel = this.#channels.get(channelId)
|
||||
|
||||
if (!newChannel) {
|
||||
consola.error('[Gateway]', `Channel not found: ${channelId}`)
|
||||
return
|
||||
}
|
||||
|
||||
const oldChannel = this.#channels.get(this.#client.channelId)
|
||||
|
||||
if (oldChannel)
|
||||
this.#leaveChannel(oldChannel)
|
||||
|
||||
this.#client.clearConsumers()
|
||||
|
||||
this.#socket.join(newChannel.id)
|
||||
newChannel.addClient(this.#client)
|
||||
await newChannel.wireClient(this.#client)
|
||||
|
||||
this.#io.emit('client-switched-channel', this.#client.serialize())
|
||||
}
|
||||
|
||||
async #onCreateTransport(
|
||||
{ producing, consuming }: { producing: boolean, consuming: boolean },
|
||||
cb: (result: any) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const transportData = await this.#client.createTransport({ producing, consuming })
|
||||
cb(transportData)
|
||||
|
||||
if (consuming) {
|
||||
const channel = this.#channels.get(this.#client.channelId)
|
||||
|
||||
if (channel)
|
||||
await channel.wireClient(this.#client)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[createTransport]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #onConnectTransport(
|
||||
{ transportId, dtlsParameters }: { transportId: string, dtlsParameters: types.DtlsParameters },
|
||||
cb: (result: any) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.#client.connectTransport(transportId, dtlsParameters)
|
||||
cb({ ok: true })
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[connectTransport]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #onProduce(
|
||||
{ transportId, kind, rtpParameters, appData }: {
|
||||
transportId: string
|
||||
kind: types.MediaKind
|
||||
rtpParameters: types.RtpParameters
|
||||
appData: { source: string }
|
||||
},
|
||||
cb: (result: any) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const producer = await this.#client.produce(transportId, kind, rtpParameters, appData)
|
||||
cb({ id: producer.id })
|
||||
|
||||
const channel = this.#channels.get(this.#client.channelId)
|
||||
if (channel) {
|
||||
for (const peer of channel.clients) {
|
||||
if (peer.socketId !== this.#client.socketId)
|
||||
await peer.createConsumerFor(producer, this.#client.socketId)
|
||||
}
|
||||
|
||||
if (kind === 'audio')
|
||||
await channel.addAudioProducer(producer)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[produce]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onCloseProducer(
|
||||
{ producerId }: { producerId: string },
|
||||
cb: (result: any) => void,
|
||||
): void {
|
||||
try {
|
||||
this.#client.closeProducer(producerId)
|
||||
cb({ ok: true })
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[closeProducer]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #onPauseProducer(
|
||||
{ producerId }: { producerId: string },
|
||||
cb: (result: any) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.#client.pauseProducer(producerId)
|
||||
cb({ ok: true })
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[pauseProducer]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #onResumeProducer(
|
||||
{ producerId }: { producerId: string },
|
||||
cb: (result: any) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.#client.resumeProducer(producerId)
|
||||
cb({ ok: true })
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[resumeProducer]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #onPauseConsumer(
|
||||
{ consumerId }: { consumerId: string },
|
||||
cb: (result: any) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.#client.pauseConsumer(consumerId)
|
||||
cb({ ok: true })
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[pauseConsumer]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #onResumeConsumer(
|
||||
{ consumerId }: { consumerId: string },
|
||||
cb: (result: any) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.#client.resumeConsumer(consumerId)
|
||||
cb({ ok: true })
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('[Gateway]', '[resumeConsumer]', error.message)
|
||||
cb({ error: error.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onUpdateClient(
|
||||
patch: { inputMuted?: boolean, outputMuted?: boolean },
|
||||
cb: (result: any) => void,
|
||||
): void {
|
||||
this.#client.update(patch)
|
||||
cb(this.#client.serialize())
|
||||
}
|
||||
|
||||
#leaveChannel(channel: Channel): void {
|
||||
channel.unwireClient(this.#client)
|
||||
channel.kickClient(this.#client)
|
||||
this.#socket.leave(channel.id)
|
||||
}
|
||||
|
||||
#onDisconnect(): void {
|
||||
consola.info('[Gateway]', 'Client disconnected:', this.#client.socketId)
|
||||
|
||||
this.#socket.broadcast.emit('client-disconnected', this.#client.socketId)
|
||||
|
||||
const channel = this.#channels.get(this.#client.channelId)
|
||||
|
||||
if (channel)
|
||||
this.#leaveChannel(channel)
|
||||
|
||||
this.#client.close()
|
||||
}
|
||||
}
|
||||
14
server/plugins/socket/webrtc/MessagingService.ts
Normal file
14
server/plugins/socket/webrtc/MessagingService.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { Channel } from '../../../prisma/generated-client/browser.ts'
|
||||
import type { ChannelManager } from './ChannelManager.ts'
|
||||
import type { Client } from './Client.ts'
|
||||
|
||||
export class MessagingService {
|
||||
private channels: ChannelManager
|
||||
|
||||
constructor(channels: ChannelManager) {
|
||||
this.channels = channels
|
||||
}
|
||||
|
||||
joinChannel(client: Client, channel: Channel) {
|
||||
}
|
||||
}
|
||||
96
server/plugins/socket/webrtc/index.ts
Normal file
96
server/plugins/socket/webrtc/index.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import { consola } from 'consola'
|
||||
import { Channel } from './Channel.ts'
|
||||
import { ChannelManager } from './ChannelManager.ts'
|
||||
import { Client } from './Client.ts'
|
||||
import { WebRtcGateway } from './Gateway.ts'
|
||||
|
||||
export default async function (fastify: FastifyInstance) {
|
||||
const { io, bus, mediasoupRouter, prisma } = fastify
|
||||
|
||||
const channels = new ChannelManager(mediasoupRouter)
|
||||
|
||||
const dbChannels = await prisma.channel.findMany()
|
||||
|
||||
for (const dbChannel of dbChannels) {
|
||||
const channel = await Channel.create(dbChannel.id, dbChannel.persistent, mediasoupRouter)
|
||||
channels.create(channel)
|
||||
setupChannelEvents(channel)
|
||||
}
|
||||
|
||||
const defaultChannel = channels.get('default')!
|
||||
|
||||
io.on('connection', async (socket) => {
|
||||
consola.info('[WebRtc]', 'Client connected', socket.id)
|
||||
|
||||
const client = new Client(socket.id, socket.data.user.id, mediasoupRouter)
|
||||
|
||||
defaultChannel.addClient(client)
|
||||
socket.join(defaultChannel.id)
|
||||
|
||||
const _gateway = new WebRtcGateway(io, socket, client, channels)
|
||||
|
||||
socket.emit('initialized', {
|
||||
rtpCapabilities: mediasoupRouter.rtpCapabilities,
|
||||
channelId: client.channelId,
|
||||
clients: channels.all.flatMap(c => c.clients).map(c => c.serialize()),
|
||||
})
|
||||
|
||||
socket.broadcast.emit('new-client', client.serialize())
|
||||
})
|
||||
|
||||
bus.on('channel:created', async (dbChannel) => {
|
||||
io.emit('channel-created', dbChannel)
|
||||
|
||||
const channel = await Channel.create(dbChannel.id, dbChannel.persistent, mediasoupRouter)
|
||||
|
||||
channels.create(channel)
|
||||
setupChannelEvents(channel)
|
||||
})
|
||||
|
||||
bus.on('channel:removed', async (dbChannel) => {
|
||||
io.emit('channel-removed', dbChannel.id)
|
||||
|
||||
const channel = channels.get(dbChannel.id)
|
||||
|
||||
if (!channel)
|
||||
return
|
||||
|
||||
for (const client of channel.clients) {
|
||||
channel.unwireClient(client)
|
||||
client.clearConsumers()
|
||||
|
||||
const socket = io.sockets.sockets.get(client.socketId)
|
||||
if (socket) {
|
||||
socket.leave(dbChannel.id)
|
||||
defaultChannel.addClient(client)
|
||||
socket.join(defaultChannel.id)
|
||||
await defaultChannel.wireClient(client)
|
||||
io.emit('client-switched-channel', client.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
channels.delete(dbChannel.id)
|
||||
})
|
||||
|
||||
function setupChannelEvents(channel: Channel): void {
|
||||
channel.on('speaking-peers', peers => io.to(channel.id).emit('speaking-clients', peers))
|
||||
|
||||
channel.on('silence', () => {
|
||||
io.to(channel.id).emit('speaking-clients', [])
|
||||
io.to(channel.id).emit('active-speaker', undefined)
|
||||
})
|
||||
|
||||
channel.on('active-speaker', socketId => io.to(channel.id).emit('active-speaker', socketId))
|
||||
|
||||
channel.on('empty', async () => {
|
||||
if (channel.persistent)
|
||||
return
|
||||
|
||||
channels.delete(channel.id)
|
||||
await prisma.channel.delete({ where: { id: channel.id } })
|
||||
consola.info('[WebRtc]', `Non-persistent channel "${channel.id}" deleted`)
|
||||
io.emit('channel-removed', channel.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
@@ -8,7 +7,7 @@
|
||||
* 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.
|
||||
* See `Client.ts` for the standard, server-side entry point.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +1,3 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
@@ -8,7 +7,7 @@
|
||||
*
|
||||
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||
*
|
||||
* All exports from this file are wrapped under a `Prisma` namespace object in the client.ts file.
|
||||
* All exports from this file are wrapped under a `Prisma` namespace object in the Client.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
|
||||
@@ -1026,6 +1025,7 @@ export type MessageAttachmentScalarFieldEnum = (typeof MessageAttachmentScalarFi
|
||||
|
||||
export const ChannelScalarFieldEnum = {
|
||||
id: 'id',
|
||||
ownerId: 'ownerId',
|
||||
name: 'name',
|
||||
persistent: 'persistent'
|
||||
} as const
|
||||
|
||||
@@ -135,6 +135,7 @@ export type MessageAttachmentScalarFieldEnum = (typeof MessageAttachmentScalarFi
|
||||
|
||||
export const ChannelScalarFieldEnum = {
|
||||
id: 'id',
|
||||
ownerId: 'ownerId',
|
||||
name: 'name',
|
||||
persistent: 'persistent'
|
||||
} as const
|
||||
|
||||
@@ -26,18 +26,21 @@ export type AggregateChannel = {
|
||||
|
||||
export type ChannelMinAggregateOutputType = {
|
||||
id: string | null
|
||||
ownerId: string | null
|
||||
name: string | null
|
||||
persistent: boolean | null
|
||||
}
|
||||
|
||||
export type ChannelMaxAggregateOutputType = {
|
||||
id: string | null
|
||||
ownerId: string | null
|
||||
name: string | null
|
||||
persistent: boolean | null
|
||||
}
|
||||
|
||||
export type ChannelCountAggregateOutputType = {
|
||||
id: number
|
||||
ownerId: number
|
||||
name: number
|
||||
persistent: number
|
||||
_all: number
|
||||
@@ -46,18 +49,21 @@ export type ChannelCountAggregateOutputType = {
|
||||
|
||||
export type ChannelMinAggregateInputType = {
|
||||
id?: true
|
||||
ownerId?: true
|
||||
name?: true
|
||||
persistent?: true
|
||||
}
|
||||
|
||||
export type ChannelMaxAggregateInputType = {
|
||||
id?: true
|
||||
ownerId?: true
|
||||
name?: true
|
||||
persistent?: true
|
||||
}
|
||||
|
||||
export type ChannelCountAggregateInputType = {
|
||||
id?: true
|
||||
ownerId?: true
|
||||
name?: true
|
||||
persistent?: true
|
||||
_all?: true
|
||||
@@ -137,6 +143,7 @@ export type ChannelGroupByArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
|
||||
export type ChannelGroupByOutputType = {
|
||||
id: string
|
||||
ownerId: string | null
|
||||
name: string
|
||||
persistent: boolean
|
||||
_count: ChannelCountAggregateOutputType | null
|
||||
@@ -164,14 +171,18 @@ export type ChannelWhereInput = {
|
||||
OR?: Prisma.ChannelWhereInput[]
|
||||
NOT?: Prisma.ChannelWhereInput | Prisma.ChannelWhereInput[]
|
||||
id?: Prisma.StringFilter<"Channel"> | string
|
||||
ownerId?: Prisma.StringNullableFilter<"Channel"> | string | null
|
||||
name?: Prisma.StringFilter<"Channel"> | string
|
||||
persistent?: Prisma.BoolFilter<"Channel"> | boolean
|
||||
owner?: Prisma.XOR<Prisma.UserNullableScalarRelationFilter, Prisma.UserWhereInput> | null
|
||||
}
|
||||
|
||||
export type ChannelOrderByWithRelationInput = {
|
||||
id?: Prisma.SortOrder
|
||||
ownerId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
persistent?: Prisma.SortOrder
|
||||
owner?: Prisma.UserOrderByWithRelationInput
|
||||
}
|
||||
|
||||
export type ChannelWhereUniqueInput = Prisma.AtLeast<{
|
||||
@@ -179,12 +190,15 @@ export type ChannelWhereUniqueInput = Prisma.AtLeast<{
|
||||
AND?: Prisma.ChannelWhereInput | Prisma.ChannelWhereInput[]
|
||||
OR?: Prisma.ChannelWhereInput[]
|
||||
NOT?: Prisma.ChannelWhereInput | Prisma.ChannelWhereInput[]
|
||||
ownerId?: Prisma.StringNullableFilter<"Channel"> | string | null
|
||||
name?: Prisma.StringFilter<"Channel"> | string
|
||||
persistent?: Prisma.BoolFilter<"Channel"> | boolean
|
||||
owner?: Prisma.XOR<Prisma.UserNullableScalarRelationFilter, Prisma.UserWhereInput> | null
|
||||
}, "id">
|
||||
|
||||
export type ChannelOrderByWithAggregationInput = {
|
||||
id?: Prisma.SortOrder
|
||||
ownerId?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
persistent?: Prisma.SortOrder
|
||||
_count?: Prisma.ChannelCountOrderByAggregateInput
|
||||
@@ -197,6 +211,7 @@ export type ChannelScalarWhereWithAggregatesInput = {
|
||||
OR?: Prisma.ChannelScalarWhereWithAggregatesInput[]
|
||||
NOT?: Prisma.ChannelScalarWhereWithAggregatesInput | Prisma.ChannelScalarWhereWithAggregatesInput[]
|
||||
id?: Prisma.StringWithAggregatesFilter<"Channel"> | string
|
||||
ownerId?: Prisma.StringNullableWithAggregatesFilter<"Channel"> | string | null
|
||||
name?: Prisma.StringWithAggregatesFilter<"Channel"> | string
|
||||
persistent?: Prisma.BoolWithAggregatesFilter<"Channel"> | boolean
|
||||
}
|
||||
@@ -205,10 +220,12 @@ export type ChannelCreateInput = {
|
||||
id?: string
|
||||
name: string
|
||||
persistent: boolean
|
||||
owner?: Prisma.UserCreateNestedOneWithoutChannelsInput
|
||||
}
|
||||
|
||||
export type ChannelUncheckedCreateInput = {
|
||||
id?: string
|
||||
ownerId?: string | null
|
||||
name: string
|
||||
persistent: boolean
|
||||
}
|
||||
@@ -217,16 +234,19 @@ export type ChannelUpdateInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
persistent?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
owner?: Prisma.UserUpdateOneWithoutChannelsNestedInput
|
||||
}
|
||||
|
||||
export type ChannelUncheckedUpdateInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
ownerId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
persistent?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
}
|
||||
|
||||
export type ChannelCreateManyInput = {
|
||||
id?: string
|
||||
ownerId?: string | null
|
||||
name: string
|
||||
persistent: boolean
|
||||
}
|
||||
@@ -239,65 +259,211 @@ export type ChannelUpdateManyMutationInput = {
|
||||
|
||||
export type ChannelUncheckedUpdateManyInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
ownerId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
persistent?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
}
|
||||
|
||||
export type ChannelListRelationFilter = {
|
||||
every?: Prisma.ChannelWhereInput
|
||||
some?: Prisma.ChannelWhereInput
|
||||
none?: Prisma.ChannelWhereInput
|
||||
}
|
||||
|
||||
export type ChannelOrderByRelationAggregateInput = {
|
||||
_count?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type ChannelCountOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
ownerId?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
persistent?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type ChannelMaxOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
ownerId?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
persistent?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type ChannelMinOrderByAggregateInput = {
|
||||
id?: Prisma.SortOrder
|
||||
ownerId?: Prisma.SortOrder
|
||||
name?: Prisma.SortOrder
|
||||
persistent?: Prisma.SortOrder
|
||||
}
|
||||
|
||||
export type ChannelCreateNestedManyWithoutOwnerInput = {
|
||||
create?: Prisma.XOR<Prisma.ChannelCreateWithoutOwnerInput, Prisma.ChannelUncheckedCreateWithoutOwnerInput> | Prisma.ChannelCreateWithoutOwnerInput[] | Prisma.ChannelUncheckedCreateWithoutOwnerInput[]
|
||||
connectOrCreate?: Prisma.ChannelCreateOrConnectWithoutOwnerInput | Prisma.ChannelCreateOrConnectWithoutOwnerInput[]
|
||||
createMany?: Prisma.ChannelCreateManyOwnerInputEnvelope
|
||||
connect?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type ChannelUncheckedCreateNestedManyWithoutOwnerInput = {
|
||||
create?: Prisma.XOR<Prisma.ChannelCreateWithoutOwnerInput, Prisma.ChannelUncheckedCreateWithoutOwnerInput> | Prisma.ChannelCreateWithoutOwnerInput[] | Prisma.ChannelUncheckedCreateWithoutOwnerInput[]
|
||||
connectOrCreate?: Prisma.ChannelCreateOrConnectWithoutOwnerInput | Prisma.ChannelCreateOrConnectWithoutOwnerInput[]
|
||||
createMany?: Prisma.ChannelCreateManyOwnerInputEnvelope
|
||||
connect?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
}
|
||||
|
||||
export type ChannelUpdateManyWithoutOwnerNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.ChannelCreateWithoutOwnerInput, Prisma.ChannelUncheckedCreateWithoutOwnerInput> | Prisma.ChannelCreateWithoutOwnerInput[] | Prisma.ChannelUncheckedCreateWithoutOwnerInput[]
|
||||
connectOrCreate?: Prisma.ChannelCreateOrConnectWithoutOwnerInput | Prisma.ChannelCreateOrConnectWithoutOwnerInput[]
|
||||
upsert?: Prisma.ChannelUpsertWithWhereUniqueWithoutOwnerInput | Prisma.ChannelUpsertWithWhereUniqueWithoutOwnerInput[]
|
||||
createMany?: Prisma.ChannelCreateManyOwnerInputEnvelope
|
||||
set?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
disconnect?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
delete?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
connect?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
update?: Prisma.ChannelUpdateWithWhereUniqueWithoutOwnerInput | Prisma.ChannelUpdateWithWhereUniqueWithoutOwnerInput[]
|
||||
updateMany?: Prisma.ChannelUpdateManyWithWhereWithoutOwnerInput | Prisma.ChannelUpdateManyWithWhereWithoutOwnerInput[]
|
||||
deleteMany?: Prisma.ChannelScalarWhereInput | Prisma.ChannelScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type ChannelUncheckedUpdateManyWithoutOwnerNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.ChannelCreateWithoutOwnerInput, Prisma.ChannelUncheckedCreateWithoutOwnerInput> | Prisma.ChannelCreateWithoutOwnerInput[] | Prisma.ChannelUncheckedCreateWithoutOwnerInput[]
|
||||
connectOrCreate?: Prisma.ChannelCreateOrConnectWithoutOwnerInput | Prisma.ChannelCreateOrConnectWithoutOwnerInput[]
|
||||
upsert?: Prisma.ChannelUpsertWithWhereUniqueWithoutOwnerInput | Prisma.ChannelUpsertWithWhereUniqueWithoutOwnerInput[]
|
||||
createMany?: Prisma.ChannelCreateManyOwnerInputEnvelope
|
||||
set?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
disconnect?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
delete?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
connect?: Prisma.ChannelWhereUniqueInput | Prisma.ChannelWhereUniqueInput[]
|
||||
update?: Prisma.ChannelUpdateWithWhereUniqueWithoutOwnerInput | Prisma.ChannelUpdateWithWhereUniqueWithoutOwnerInput[]
|
||||
updateMany?: Prisma.ChannelUpdateManyWithWhereWithoutOwnerInput | Prisma.ChannelUpdateManyWithWhereWithoutOwnerInput[]
|
||||
deleteMany?: Prisma.ChannelScalarWhereInput | Prisma.ChannelScalarWhereInput[]
|
||||
}
|
||||
|
||||
export type BoolFieldUpdateOperationsInput = {
|
||||
set?: boolean
|
||||
}
|
||||
|
||||
export type ChannelCreateWithoutOwnerInput = {
|
||||
id?: string
|
||||
name: string
|
||||
persistent: boolean
|
||||
}
|
||||
|
||||
export type ChannelUncheckedCreateWithoutOwnerInput = {
|
||||
id?: string
|
||||
name: string
|
||||
persistent: boolean
|
||||
}
|
||||
|
||||
export type ChannelCreateOrConnectWithoutOwnerInput = {
|
||||
where: Prisma.ChannelWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.ChannelCreateWithoutOwnerInput, Prisma.ChannelUncheckedCreateWithoutOwnerInput>
|
||||
}
|
||||
|
||||
export type ChannelCreateManyOwnerInputEnvelope = {
|
||||
data: Prisma.ChannelCreateManyOwnerInput | Prisma.ChannelCreateManyOwnerInput[]
|
||||
}
|
||||
|
||||
export type ChannelUpsertWithWhereUniqueWithoutOwnerInput = {
|
||||
where: Prisma.ChannelWhereUniqueInput
|
||||
update: Prisma.XOR<Prisma.ChannelUpdateWithoutOwnerInput, Prisma.ChannelUncheckedUpdateWithoutOwnerInput>
|
||||
create: Prisma.XOR<Prisma.ChannelCreateWithoutOwnerInput, Prisma.ChannelUncheckedCreateWithoutOwnerInput>
|
||||
}
|
||||
|
||||
export type ChannelUpdateWithWhereUniqueWithoutOwnerInput = {
|
||||
where: Prisma.ChannelWhereUniqueInput
|
||||
data: Prisma.XOR<Prisma.ChannelUpdateWithoutOwnerInput, Prisma.ChannelUncheckedUpdateWithoutOwnerInput>
|
||||
}
|
||||
|
||||
export type ChannelUpdateManyWithWhereWithoutOwnerInput = {
|
||||
where: Prisma.ChannelScalarWhereInput
|
||||
data: Prisma.XOR<Prisma.ChannelUpdateManyMutationInput, Prisma.ChannelUncheckedUpdateManyWithoutOwnerInput>
|
||||
}
|
||||
|
||||
export type ChannelScalarWhereInput = {
|
||||
AND?: Prisma.ChannelScalarWhereInput | Prisma.ChannelScalarWhereInput[]
|
||||
OR?: Prisma.ChannelScalarWhereInput[]
|
||||
NOT?: Prisma.ChannelScalarWhereInput | Prisma.ChannelScalarWhereInput[]
|
||||
id?: Prisma.StringFilter<"Channel"> | string
|
||||
ownerId?: Prisma.StringNullableFilter<"Channel"> | string | null
|
||||
name?: Prisma.StringFilter<"Channel"> | string
|
||||
persistent?: Prisma.BoolFilter<"Channel"> | boolean
|
||||
}
|
||||
|
||||
export type ChannelCreateManyOwnerInput = {
|
||||
id?: string
|
||||
name: string
|
||||
persistent: boolean
|
||||
}
|
||||
|
||||
export type ChannelUpdateWithoutOwnerInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
persistent?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
}
|
||||
|
||||
export type ChannelUncheckedUpdateWithoutOwnerInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
persistent?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
}
|
||||
|
||||
export type ChannelUncheckedUpdateManyWithoutOwnerInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
persistent?: Prisma.BoolFieldUpdateOperationsInput | boolean
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ChannelSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
ownerId?: boolean
|
||||
name?: boolean
|
||||
persistent?: boolean
|
||||
owner?: boolean | Prisma.Channel$ownerArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["channel"]>
|
||||
|
||||
export type ChannelSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
ownerId?: boolean
|
||||
name?: boolean
|
||||
persistent?: boolean
|
||||
owner?: boolean | Prisma.Channel$ownerArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["channel"]>
|
||||
|
||||
export type ChannelSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
ownerId?: boolean
|
||||
name?: boolean
|
||||
persistent?: boolean
|
||||
owner?: boolean | Prisma.Channel$ownerArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["channel"]>
|
||||
|
||||
export type ChannelSelectScalar = {
|
||||
id?: boolean
|
||||
ownerId?: boolean
|
||||
name?: boolean
|
||||
persistent?: boolean
|
||||
}
|
||||
|
||||
export type ChannelOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "name" | "persistent", ExtArgs["result"]["channel"]>
|
||||
export type ChannelOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "ownerId" | "name" | "persistent", ExtArgs["result"]["channel"]>
|
||||
export type ChannelInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
owner?: boolean | Prisma.Channel$ownerArgs<ExtArgs>
|
||||
}
|
||||
export type ChannelIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
owner?: boolean | Prisma.Channel$ownerArgs<ExtArgs>
|
||||
}
|
||||
export type ChannelIncludeUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
owner?: boolean | Prisma.Channel$ownerArgs<ExtArgs>
|
||||
}
|
||||
|
||||
export type $ChannelPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
name: "Channel"
|
||||
objects: {}
|
||||
objects: {
|
||||
owner: Prisma.$UserPayload<ExtArgs> | null
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
ownerId: string | null
|
||||
name: string
|
||||
persistent: boolean
|
||||
}, ExtArgs["result"]["channel"]>
|
||||
@@ -694,6 +860,7 @@ readonly fields: ChannelFieldRefs;
|
||||
*/
|
||||
export interface Prisma__ChannelClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||
owner<T extends Prisma.Channel$ownerArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Channel$ownerArgs<ExtArgs>>): Prisma.Prisma__UserClient<runtime.Types.Result.GetResult<Prisma.$UserPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
@@ -724,6 +891,7 @@ export interface Prisma__ChannelClient<T, Null = never, ExtArgs extends runtime.
|
||||
*/
|
||||
export interface ChannelFieldRefs {
|
||||
readonly id: Prisma.FieldRef<"Channel", 'String'>
|
||||
readonly ownerId: Prisma.FieldRef<"Channel", 'String'>
|
||||
readonly name: Prisma.FieldRef<"Channel", 'String'>
|
||||
readonly persistent: Prisma.FieldRef<"Channel", 'Boolean'>
|
||||
}
|
||||
@@ -742,6 +910,10 @@ export type ChannelFindUniqueArgs<ExtArgs extends runtime.Types.Extensions.Inter
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* Filter, which Channel to fetch.
|
||||
*/
|
||||
@@ -760,6 +932,10 @@ export type ChannelFindUniqueOrThrowArgs<ExtArgs extends runtime.Types.Extension
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* Filter, which Channel to fetch.
|
||||
*/
|
||||
@@ -778,6 +954,10 @@ export type ChannelFindFirstArgs<ExtArgs extends runtime.Types.Extensions.Intern
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* Filter, which Channel to fetch.
|
||||
*/
|
||||
@@ -826,6 +1006,10 @@ export type ChannelFindFirstOrThrowArgs<ExtArgs extends runtime.Types.Extensions
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* Filter, which Channel to fetch.
|
||||
*/
|
||||
@@ -874,6 +1058,10 @@ export type ChannelFindManyArgs<ExtArgs extends runtime.Types.Extensions.Interna
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* Filter, which Channels to fetch.
|
||||
*/
|
||||
@@ -922,6 +1110,10 @@ export type ChannelCreateArgs<ExtArgs extends runtime.Types.Extensions.InternalA
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* The data needed to create a Channel.
|
||||
*/
|
||||
@@ -954,6 +1146,10 @@ export type ChannelCreateManyAndReturnArgs<ExtArgs extends runtime.Types.Extensi
|
||||
* The data used to create many Channels.
|
||||
*/
|
||||
data: Prisma.ChannelCreateManyInput | Prisma.ChannelCreateManyInput[]
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelIncludeCreateManyAndReturn<ExtArgs> | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -968,6 +1164,10 @@ export type ChannelUpdateArgs<ExtArgs extends runtime.Types.Extensions.InternalA
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* The data needed to update a Channel.
|
||||
*/
|
||||
@@ -1020,6 +1220,10 @@ export type ChannelUpdateManyAndReturnArgs<ExtArgs extends runtime.Types.Extensi
|
||||
* Limit how many Channels to update.
|
||||
*/
|
||||
limit?: number
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelIncludeUpdateManyAndReturn<ExtArgs> | null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1034,6 +1238,10 @@ export type ChannelUpsertArgs<ExtArgs extends runtime.Types.Extensions.InternalA
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* The filter to search for the Channel to update in case it exists.
|
||||
*/
|
||||
@@ -1060,6 +1268,10 @@ export type ChannelDeleteArgs<ExtArgs extends runtime.Types.Extensions.InternalA
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
/**
|
||||
* Filter which Channel to delete.
|
||||
*/
|
||||
@@ -1080,6 +1292,25 @@ export type ChannelDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.Inter
|
||||
limit?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel.owner
|
||||
*/
|
||||
export type Channel$ownerArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the User
|
||||
*/
|
||||
select?: Prisma.UserSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the User
|
||||
*/
|
||||
omit?: Prisma.UserOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.UserInclude<ExtArgs> | null
|
||||
where?: Prisma.UserWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel without action
|
||||
*/
|
||||
@@ -1092,4 +1323,8 @@ export type ChannelDefaultArgs<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
}
|
||||
|
||||
@@ -193,6 +193,7 @@ export type UserWhereInput = {
|
||||
Session?: Prisma.SessionListRelationFilter
|
||||
UserPreferences?: Prisma.XOR<Prisma.UserPreferencesNullableScalarRelationFilter, Prisma.UserPreferencesWhereInput> | null
|
||||
Messages?: Prisma.MessageListRelationFilter
|
||||
Channels?: Prisma.ChannelListRelationFilter
|
||||
}
|
||||
|
||||
export type UserOrderByWithRelationInput = {
|
||||
@@ -205,6 +206,7 @@ export type UserOrderByWithRelationInput = {
|
||||
Session?: Prisma.SessionOrderByRelationAggregateInput
|
||||
UserPreferences?: Prisma.UserPreferencesOrderByWithRelationInput
|
||||
Messages?: Prisma.MessageOrderByRelationAggregateInput
|
||||
Channels?: Prisma.ChannelOrderByRelationAggregateInput
|
||||
}
|
||||
|
||||
export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||
@@ -220,6 +222,7 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{
|
||||
Session?: Prisma.SessionListRelationFilter
|
||||
UserPreferences?: Prisma.XOR<Prisma.UserPreferencesNullableScalarRelationFilter, Prisma.UserPreferencesWhereInput> | null
|
||||
Messages?: Prisma.MessageListRelationFilter
|
||||
Channels?: Prisma.ChannelListRelationFilter
|
||||
}, "id" | "username">
|
||||
|
||||
export type UserOrderByWithAggregationInput = {
|
||||
@@ -256,6 +259,7 @@ export type UserCreateInput = {
|
||||
Session?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
UserPreferences?: Prisma.UserPreferencesCreateNestedOneWithoutUserInput
|
||||
Messages?: Prisma.MessageCreateNestedManyWithoutSenderInput
|
||||
Channels?: Prisma.ChannelCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateInput = {
|
||||
@@ -268,6 +272,7 @@ export type UserUncheckedCreateInput = {
|
||||
Session?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedCreateNestedOneWithoutUserInput
|
||||
Messages?: Prisma.MessageUncheckedCreateNestedManyWithoutSenderInput
|
||||
Channels?: Prisma.ChannelUncheckedCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserUpdateInput = {
|
||||
@@ -280,6 +285,7 @@ export type UserUpdateInput = {
|
||||
Session?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
UserPreferences?: Prisma.UserPreferencesUpdateOneWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUpdateManyWithoutSenderNestedInput
|
||||
Channels?: Prisma.ChannelUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateInput = {
|
||||
@@ -292,6 +298,7 @@ export type UserUncheckedUpdateInput = {
|
||||
Session?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedUpdateOneWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUncheckedUpdateManyWithoutSenderNestedInput
|
||||
Channels?: Prisma.ChannelUncheckedUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateManyInput = {
|
||||
@@ -410,6 +417,22 @@ export type UserUpdateOneWithoutMessagesNestedInput = {
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutMessagesInput, Prisma.UserUpdateWithoutMessagesInput>, Prisma.UserUncheckedUpdateWithoutMessagesInput>
|
||||
}
|
||||
|
||||
export type UserCreateNestedOneWithoutChannelsInput = {
|
||||
create?: Prisma.XOR<Prisma.UserCreateWithoutChannelsInput, Prisma.UserUncheckedCreateWithoutChannelsInput>
|
||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutChannelsInput
|
||||
connect?: Prisma.UserWhereUniqueInput
|
||||
}
|
||||
|
||||
export type UserUpdateOneWithoutChannelsNestedInput = {
|
||||
create?: Prisma.XOR<Prisma.UserCreateWithoutChannelsInput, Prisma.UserUncheckedCreateWithoutChannelsInput>
|
||||
connectOrCreate?: Prisma.UserCreateOrConnectWithoutChannelsInput
|
||||
upsert?: Prisma.UserUpsertWithoutChannelsInput
|
||||
disconnect?: Prisma.UserWhereInput | boolean
|
||||
delete?: Prisma.UserWhereInput | boolean
|
||||
connect?: Prisma.UserWhereUniqueInput
|
||||
update?: Prisma.XOR<Prisma.XOR<Prisma.UserUpdateToOneWithWhereWithoutChannelsInput, Prisma.UserUpdateWithoutChannelsInput>, Prisma.UserUncheckedUpdateWithoutChannelsInput>
|
||||
}
|
||||
|
||||
export type UserCreateWithoutSessionInput = {
|
||||
id?: string
|
||||
username: string
|
||||
@@ -419,6 +442,7 @@ export type UserCreateWithoutSessionInput = {
|
||||
updatedAt?: Date | string
|
||||
UserPreferences?: Prisma.UserPreferencesCreateNestedOneWithoutUserInput
|
||||
Messages?: Prisma.MessageCreateNestedManyWithoutSenderInput
|
||||
Channels?: Prisma.ChannelCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutSessionInput = {
|
||||
@@ -430,6 +454,7 @@ export type UserUncheckedCreateWithoutSessionInput = {
|
||||
updatedAt?: Date | string
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedCreateNestedOneWithoutUserInput
|
||||
Messages?: Prisma.MessageUncheckedCreateNestedManyWithoutSenderInput
|
||||
Channels?: Prisma.ChannelUncheckedCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutSessionInput = {
|
||||
@@ -457,6 +482,7 @@ export type UserUpdateWithoutSessionInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
UserPreferences?: Prisma.UserPreferencesUpdateOneWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUpdateManyWithoutSenderNestedInput
|
||||
Channels?: Prisma.ChannelUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutSessionInput = {
|
||||
@@ -468,6 +494,7 @@ export type UserUncheckedUpdateWithoutSessionInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedUpdateOneWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUncheckedUpdateManyWithoutSenderNestedInput
|
||||
Channels?: Prisma.ChannelUncheckedUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateWithoutUserPreferencesInput = {
|
||||
@@ -479,6 +506,7 @@ export type UserCreateWithoutUserPreferencesInput = {
|
||||
updatedAt?: Date | string
|
||||
Session?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
Messages?: Prisma.MessageCreateNestedManyWithoutSenderInput
|
||||
Channels?: Prisma.ChannelCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutUserPreferencesInput = {
|
||||
@@ -490,6 +518,7 @@ export type UserUncheckedCreateWithoutUserPreferencesInput = {
|
||||
updatedAt?: Date | string
|
||||
Session?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
Messages?: Prisma.MessageUncheckedCreateNestedManyWithoutSenderInput
|
||||
Channels?: Prisma.ChannelUncheckedCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutUserPreferencesInput = {
|
||||
@@ -517,6 +546,7 @@ export type UserUpdateWithoutUserPreferencesInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
Session?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUpdateManyWithoutSenderNestedInput
|
||||
Channels?: Prisma.ChannelUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutUserPreferencesInput = {
|
||||
@@ -528,6 +558,7 @@ export type UserUncheckedUpdateWithoutUserPreferencesInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
Session?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUncheckedUpdateManyWithoutSenderNestedInput
|
||||
Channels?: Prisma.ChannelUncheckedUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateWithoutMessagesInput = {
|
||||
@@ -539,6 +570,7 @@ export type UserCreateWithoutMessagesInput = {
|
||||
updatedAt?: Date | string
|
||||
Session?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
UserPreferences?: Prisma.UserPreferencesCreateNestedOneWithoutUserInput
|
||||
Channels?: Prisma.ChannelCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutMessagesInput = {
|
||||
@@ -550,6 +582,7 @@ export type UserUncheckedCreateWithoutMessagesInput = {
|
||||
updatedAt?: Date | string
|
||||
Session?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedCreateNestedOneWithoutUserInput
|
||||
Channels?: Prisma.ChannelUncheckedCreateNestedManyWithoutOwnerInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutMessagesInput = {
|
||||
@@ -577,6 +610,7 @@ export type UserUpdateWithoutMessagesInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
Session?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
UserPreferences?: Prisma.UserPreferencesUpdateOneWithoutUserNestedInput
|
||||
Channels?: Prisma.ChannelUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutMessagesInput = {
|
||||
@@ -588,6 +622,71 @@ export type UserUncheckedUpdateWithoutMessagesInput = {
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
Session?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedUpdateOneWithoutUserNestedInput
|
||||
Channels?: Prisma.ChannelUncheckedUpdateManyWithoutOwnerNestedInput
|
||||
}
|
||||
|
||||
export type UserCreateWithoutChannelsInput = {
|
||||
id?: string
|
||||
username: string
|
||||
password: string
|
||||
displayName: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
Session?: Prisma.SessionCreateNestedManyWithoutUserInput
|
||||
UserPreferences?: Prisma.UserPreferencesCreateNestedOneWithoutUserInput
|
||||
Messages?: Prisma.MessageCreateNestedManyWithoutSenderInput
|
||||
}
|
||||
|
||||
export type UserUncheckedCreateWithoutChannelsInput = {
|
||||
id?: string
|
||||
username: string
|
||||
password: string
|
||||
displayName: string
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
Session?: Prisma.SessionUncheckedCreateNestedManyWithoutUserInput
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedCreateNestedOneWithoutUserInput
|
||||
Messages?: Prisma.MessageUncheckedCreateNestedManyWithoutSenderInput
|
||||
}
|
||||
|
||||
export type UserCreateOrConnectWithoutChannelsInput = {
|
||||
where: Prisma.UserWhereUniqueInput
|
||||
create: Prisma.XOR<Prisma.UserCreateWithoutChannelsInput, Prisma.UserUncheckedCreateWithoutChannelsInput>
|
||||
}
|
||||
|
||||
export type UserUpsertWithoutChannelsInput = {
|
||||
update: Prisma.XOR<Prisma.UserUpdateWithoutChannelsInput, Prisma.UserUncheckedUpdateWithoutChannelsInput>
|
||||
create: Prisma.XOR<Prisma.UserCreateWithoutChannelsInput, Prisma.UserUncheckedCreateWithoutChannelsInput>
|
||||
where?: Prisma.UserWhereInput
|
||||
}
|
||||
|
||||
export type UserUpdateToOneWithWhereWithoutChannelsInput = {
|
||||
where?: Prisma.UserWhereInput
|
||||
data: Prisma.XOR<Prisma.UserUpdateWithoutChannelsInput, Prisma.UserUncheckedUpdateWithoutChannelsInput>
|
||||
}
|
||||
|
||||
export type UserUpdateWithoutChannelsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
username?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
displayName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
Session?: Prisma.SessionUpdateManyWithoutUserNestedInput
|
||||
UserPreferences?: Prisma.UserPreferencesUpdateOneWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUpdateManyWithoutSenderNestedInput
|
||||
}
|
||||
|
||||
export type UserUncheckedUpdateWithoutChannelsInput = {
|
||||
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
username?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
password?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
displayName?: Prisma.StringFieldUpdateOperationsInput | string
|
||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
Session?: Prisma.SessionUncheckedUpdateManyWithoutUserNestedInput
|
||||
UserPreferences?: Prisma.UserPreferencesUncheckedUpdateOneWithoutUserNestedInput
|
||||
Messages?: Prisma.MessageUncheckedUpdateManyWithoutSenderNestedInput
|
||||
}
|
||||
|
||||
|
||||
@@ -598,11 +697,13 @@ export type UserUncheckedUpdateWithoutMessagesInput = {
|
||||
export type UserCountOutputType = {
|
||||
Session: number
|
||||
Messages: number
|
||||
Channels: number
|
||||
}
|
||||
|
||||
export type UserCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
Session?: boolean | UserCountOutputTypeCountSessionArgs
|
||||
Messages?: boolean | UserCountOutputTypeCountMessagesArgs
|
||||
Channels?: boolean | UserCountOutputTypeCountChannelsArgs
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -629,6 +730,13 @@ export type UserCountOutputTypeCountMessagesArgs<ExtArgs extends runtime.Types.E
|
||||
where?: Prisma.MessageWhereInput
|
||||
}
|
||||
|
||||
/**
|
||||
* UserCountOutputType without action
|
||||
*/
|
||||
export type UserCountOutputTypeCountChannelsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
where?: Prisma.ChannelWhereInput
|
||||
}
|
||||
|
||||
|
||||
export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||
id?: boolean
|
||||
@@ -640,6 +748,7 @@ export type UserSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
||||
Session?: boolean | Prisma.User$SessionArgs<ExtArgs>
|
||||
UserPreferences?: boolean | Prisma.User$UserPreferencesArgs<ExtArgs>
|
||||
Messages?: boolean | Prisma.User$MessagesArgs<ExtArgs>
|
||||
Channels?: boolean | Prisma.User$ChannelsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}, ExtArgs["result"]["user"]>
|
||||
|
||||
@@ -675,6 +784,7 @@ export type UserInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
Session?: boolean | Prisma.User$SessionArgs<ExtArgs>
|
||||
UserPreferences?: boolean | Prisma.User$UserPreferencesArgs<ExtArgs>
|
||||
Messages?: boolean | Prisma.User$MessagesArgs<ExtArgs>
|
||||
Channels?: boolean | Prisma.User$ChannelsArgs<ExtArgs>
|
||||
_count?: boolean | Prisma.UserCountOutputTypeDefaultArgs<ExtArgs>
|
||||
}
|
||||
export type UserIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||
@@ -686,6 +796,7 @@ export type $UserPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||
Session: Prisma.$SessionPayload<ExtArgs>[]
|
||||
UserPreferences: Prisma.$UserPreferencesPayload<ExtArgs> | null
|
||||
Messages: Prisma.$MessagePayload<ExtArgs>[]
|
||||
Channels: Prisma.$ChannelPayload<ExtArgs>[]
|
||||
}
|
||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||
id: string
|
||||
@@ -1091,6 +1202,7 @@ export interface Prisma__UserClient<T, Null = never, ExtArgs extends runtime.Typ
|
||||
Session<T extends Prisma.User$SessionArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$SessionArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SessionPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
UserPreferences<T extends Prisma.User$UserPreferencesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$UserPreferencesArgs<ExtArgs>>): Prisma.Prisma__UserPreferencesClient<runtime.Types.Result.GetResult<Prisma.$UserPreferencesPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||
Messages<T extends Prisma.User$MessagesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$MessagesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$MessagePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
Channels<T extends Prisma.User$ChannelsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.User$ChannelsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$ChannelPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||
/**
|
||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||
@@ -1583,6 +1695,30 @@ export type User$MessagesArgs<ExtArgs extends runtime.Types.Extensions.InternalA
|
||||
distinct?: Prisma.MessageScalarFieldEnum | Prisma.MessageScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* User.Channels
|
||||
*/
|
||||
export type User$ChannelsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
/**
|
||||
* Select specific fields to fetch from the Channel
|
||||
*/
|
||||
select?: Prisma.ChannelSelect<ExtArgs> | null
|
||||
/**
|
||||
* Omit specific fields from the Channel
|
||||
*/
|
||||
omit?: Prisma.ChannelOmit<ExtArgs> | null
|
||||
/**
|
||||
* Choose, which related nodes to fetch as well
|
||||
*/
|
||||
include?: Prisma.ChannelInclude<ExtArgs> | null
|
||||
where?: Prisma.ChannelWhereInput
|
||||
orderBy?: Prisma.ChannelOrderByWithRelationInput | Prisma.ChannelOrderByWithRelationInput[]
|
||||
cursor?: Prisma.ChannelWhereUniqueInput
|
||||
take?: number
|
||||
skip?: number
|
||||
distinct?: Prisma.ChannelScalarFieldEnum | Prisma.ChannelScalarFieldEnum[]
|
||||
}
|
||||
|
||||
/**
|
||||
* User without action
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `ownerId` to the `Channel` 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_Channel" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"ownerId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"persistent" BOOLEAN NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Channel" ("id", "name", "persistent") SELECT "id", "name", "persistent" FROM "Channel";
|
||||
DROP TABLE "Channel";
|
||||
ALTER TABLE "new_Channel" RENAME TO "Channel";
|
||||
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_Channel" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"ownerId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"persistent" BOOLEAN NOT NULL,
|
||||
CONSTRAINT "Channel_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Channel" ("id", "name", "ownerId", "persistent") SELECT "id", "name", "ownerId", "persistent" FROM "Channel";
|
||||
DROP TABLE "Channel";
|
||||
ALTER TABLE "new_Channel" RENAME TO "Channel";
|
||||
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_Channel" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"ownerId" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"persistent" BOOLEAN NOT NULL,
|
||||
CONSTRAINT "Channel_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Channel" ("id", "name", "ownerId", "persistent") SELECT "id", "name", "ownerId", "persistent" FROM "Channel";
|
||||
DROP TABLE "Channel";
|
||||
ALTER TABLE "new_Channel" RENAME TO "Channel";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
@@ -18,6 +18,7 @@ model User {
|
||||
Session Session[]
|
||||
UserPreferences UserPreferences?
|
||||
Messages Message[]
|
||||
Channels Channel[]
|
||||
}
|
||||
|
||||
model Session {
|
||||
@@ -70,7 +71,10 @@ model MessageAttachment {
|
||||
}
|
||||
|
||||
model Channel {
|
||||
id String @id @default(uuid())
|
||||
id String @id @default(uuid())
|
||||
ownerId String?
|
||||
name String
|
||||
persistent Boolean
|
||||
|
||||
owner User? @relation(references: [id], fields: [ownerId], onDelete: Cascade)
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
|
||||
import * as fs from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
import { Type } from 'typebox'
|
||||
import { GetAttachmentParamsSchema } from '../plugins/schemas/attachment.ts'
|
||||
import { TypeboxRef } from '../utils/typebox-ref.ts'
|
||||
|
||||
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
const uploadDir = path.join(process.cwd(), 'uploads')
|
||||
@@ -18,6 +20,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
tags: ['Attachment'],
|
||||
operationId: 'attachment.upload',
|
||||
description: 'Pass file to multipart/form-data',
|
||||
consumes: ['multipart/form-data'],
|
||||
response: {
|
||||
200: Type.String({ format: 'uuid', description: 'Attachment UUID' }),
|
||||
},
|
||||
@@ -62,9 +65,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
summary: 'Get attachment',
|
||||
tags: ['Attachment'],
|
||||
operationId: 'attachment.get',
|
||||
params: Type.Object({
|
||||
id: Type.String({ format: 'uuid' }),
|
||||
}),
|
||||
params: TypeboxRef(GetAttachmentParamsSchema),
|
||||
response: {
|
||||
200: Type.Any({ description: 'Attachment content' }),
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { Type } from 'typebox'
|
||||
import { CreateUserSchema, UserSchema } from '../schemas/auth.ts'
|
||||
import { CreateUserPayloadSchema, LoginPayloadSchema, UserSchema } from '../plugins/schemas/auth.ts'
|
||||
import { TypeboxRef } from '../utils/typebox-ref.ts'
|
||||
|
||||
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
fastify.post(
|
||||
@@ -11,9 +11,9 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
summary: 'Register',
|
||||
tags: ['Auth'],
|
||||
operationId: 'auth.register',
|
||||
body: CreateUserSchema,
|
||||
body: TypeboxRef(CreateUserPayloadSchema),
|
||||
response: {
|
||||
200: UserSchema,
|
||||
200: TypeboxRef(UserSchema),
|
||||
},
|
||||
},
|
||||
config: {
|
||||
@@ -54,12 +54,9 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
summary: 'Login',
|
||||
tags: ['Auth'],
|
||||
operationId: 'auth.login',
|
||||
body: Type.Object({
|
||||
username: Type.String({ minLength: 1 }),
|
||||
password: Type.String({ minLength: 1 }),
|
||||
}),
|
||||
body: TypeboxRef(LoginPayloadSchema),
|
||||
response: {
|
||||
200: UserSchema,
|
||||
200: TypeboxRef(UserSchema),
|
||||
},
|
||||
},
|
||||
config: {
|
||||
@@ -107,7 +104,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
tags: ['Auth'],
|
||||
operationId: 'auth.me',
|
||||
response: {
|
||||
200: UserSchema,
|
||||
200: TypeboxRef(UserSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
90
server/routes/channel.ts
Normal file
90
server/routes/channel.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
|
||||
import { Type } from 'typebox'
|
||||
import { ChannelSchema, CreateChannelPayloadSchema } from '../plugins/schemas/channel.ts'
|
||||
import { PrismaClientKnownRequestError } from '../prisma/generated-client/internal/prismaNamespace.ts'
|
||||
import { TypeboxRef } from '../utils/typebox-ref.ts'
|
||||
|
||||
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
fastify.get(
|
||||
'/channels',
|
||||
{
|
||||
schema: {
|
||||
summary: 'Get channel list',
|
||||
tags: ['Channel'],
|
||||
operationId: 'channel.list',
|
||||
response: {
|
||||
200: Type.Array(TypeboxRef(ChannelSchema)),
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
return await fastify.prisma.channel.findMany()
|
||||
},
|
||||
)
|
||||
|
||||
fastify.post(
|
||||
'/channels',
|
||||
{
|
||||
schema: {
|
||||
summary: 'Create channel',
|
||||
tags: ['Channel'],
|
||||
operationId: 'channel.create',
|
||||
body: TypeboxRef(CreateChannelPayloadSchema),
|
||||
response: {
|
||||
200: TypeboxRef(ChannelSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (req, reply) => {
|
||||
const user = req.user!
|
||||
|
||||
const channel = await fastify.prisma.channel.create({
|
||||
data: {
|
||||
name: req.body.name,
|
||||
ownerId: user.id,
|
||||
persistent: req.body.persistent,
|
||||
},
|
||||
})
|
||||
|
||||
if (!channel) {
|
||||
return reply.unprocessableEntity()
|
||||
}
|
||||
|
||||
fastify.bus.emit('channel:created', channel)
|
||||
|
||||
return channel
|
||||
},
|
||||
)
|
||||
|
||||
fastify.delete(
|
||||
'/channels/:id',
|
||||
{
|
||||
schema: {
|
||||
summary: 'Delete channel',
|
||||
tags: ['Channel'],
|
||||
operationId: 'channel.delete',
|
||||
params: Type.Object({
|
||||
id: Type.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
async (req, reply) => {
|
||||
const user = req.user!
|
||||
|
||||
try {
|
||||
const channel = await fastify.prisma.channel.delete({
|
||||
where: { id: req.params.id, ownerId: user.id },
|
||||
})
|
||||
|
||||
fastify.bus.emit('channel:removed', channel)
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError && e.code === 'P2025') {
|
||||
return reply.notFound()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export default plugin
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
|
||||
import { Type } from 'typebox'
|
||||
import { ChatMessageSchema, NewChatMessageSchema } from '../schemas/chat.ts'
|
||||
import { ChatMessageSchema, NewChatMessagePayloadSchema } from '../plugins/schemas/chat.ts'
|
||||
|
||||
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
fastify.post(
|
||||
@@ -10,7 +10,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
summary: 'Send message',
|
||||
tags: ['Chat'],
|
||||
operationId: 'chat.send',
|
||||
body: NewChatMessageSchema,
|
||||
body: NewChatMessagePayloadSchema,
|
||||
response: {
|
||||
200: ChatMessageSchema,
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox'
|
||||
import { Type } from 'typebox'
|
||||
import { UserSchema } from '../schemas/auth.ts'
|
||||
import { UpdateUserPreferencesSchema, UserPreferencesSchema } from '../schemas/user.ts'
|
||||
import { UserSchema } from '../plugins/schemas/auth.ts'
|
||||
import { GetUserQuerySchema, UpdateUserPayloadSchema, UpdateUserPreferencesPayloadSchema, UserPreferencesSchema } from '../plugins/schemas/user.ts'
|
||||
import { TypeboxRef } from '../utils/typebox-ref.ts'
|
||||
|
||||
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
fastify.get(
|
||||
@@ -11,11 +11,9 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
summary: 'Get user',
|
||||
tags: ['User'],
|
||||
operationId: 'user.get',
|
||||
querystring: Type.Partial(Type.Object({
|
||||
username: Type.String(),
|
||||
})),
|
||||
querystring: TypeboxRef(GetUserQuerySchema),
|
||||
response: {
|
||||
200: UserSchema,
|
||||
200: TypeboxRef(UserSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -49,11 +47,11 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
tags: ['User'],
|
||||
operationId: 'user.getPreferences',
|
||||
response: {
|
||||
200: UserPreferencesSchema,
|
||||
200: TypeboxRef(UserPreferencesSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (req, reply) => {
|
||||
async (req) => {
|
||||
const user = req.user!
|
||||
|
||||
const preferences = await fastify.prisma.userPreferences.upsert({
|
||||
@@ -62,10 +60,6 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
update: {},
|
||||
})
|
||||
|
||||
if (!preferences) {
|
||||
return reply.notFound('User preferences not found')
|
||||
}
|
||||
|
||||
return {
|
||||
toggleInputHotkey: preferences.toggleInputHotkey || '',
|
||||
toggleOutputHotkey: preferences.toggleOutputHotkey || '',
|
||||
@@ -80,7 +74,7 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
summary: 'Update preferences',
|
||||
tags: ['User'],
|
||||
operationId: 'user.updatePreferences',
|
||||
body: UpdateUserPreferencesSchema,
|
||||
body: TypeboxRef(UpdateUserPreferencesPayloadSchema),
|
||||
},
|
||||
},
|
||||
async (req) => {
|
||||
@@ -104,11 +98,9 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
||||
summary: 'Update profile',
|
||||
tags: ['User'],
|
||||
operationId: 'user.updateProfile',
|
||||
body: Type.Object({
|
||||
displayName: Type.String(),
|
||||
}),
|
||||
body: TypeboxRef(UpdateUserPayloadSchema),
|
||||
response: {
|
||||
200: UserSchema,
|
||||
200: TypeboxRef(UserSchema),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Type } from 'typebox'
|
||||
|
||||
export const UserPreferencesSchema = Type.Object({
|
||||
toggleInputHotkey: Type.String(),
|
||||
toggleOutputHotkey: Type.String(),
|
||||
}, { title: 'UserPreferences', description: 'UserPreferences' })
|
||||
|
||||
export const UpdateUserPreferencesSchema = Type.Partial(
|
||||
UserPreferencesSchema,
|
||||
{ title: 'UpdateUserPreferences', description: 'UpdateUserPreferences' },
|
||||
)
|
||||
@@ -10,7 +10,6 @@ import FastifySwagger from '@fastify/swagger'
|
||||
import FastifyApiReference from '@scalar/fastify-api-reference'
|
||||
import Fastify from 'fastify'
|
||||
import { Prisma } from './prisma/generated-client/client.ts'
|
||||
import { ErrorReplySchema } from './schemas/common.ts'
|
||||
import 'dotenv/config'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
@@ -52,13 +51,30 @@ fastify.register(FastifySwagger, {
|
||||
const transformedSchema: typeof schema = schema ?? {}
|
||||
const responseSchema: any = transformedSchema.response ?? {}
|
||||
|
||||
responseSchema['4xx'] ??= ErrorReplySchema
|
||||
responseSchema['5xx'] ??= ErrorReplySchema
|
||||
responseSchema['4xx'] ??= { $ref: 'ResponseError' }
|
||||
responseSchema['5xx'] ??= { $ref: 'ResponseError' }
|
||||
|
||||
transformedSchema.response = responseSchema
|
||||
|
||||
return { schema: transformedSchema, url }
|
||||
},
|
||||
refResolver: {
|
||||
buildLocalReference(json, _baseUri, _fragment, i) {
|
||||
if (!json.title && json.$id) {
|
||||
json.title = json.$id
|
||||
}
|
||||
|
||||
if (!json.description) {
|
||||
json.description = json.title
|
||||
}
|
||||
|
||||
if (!json.$id) {
|
||||
return `def-${i}`
|
||||
}
|
||||
|
||||
return json.$id.toString()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
fastify.register(FastifyApiReference, {
|
||||
@@ -68,10 +84,10 @@ fastify.register(FastifyApiReference, {
|
||||
showDeveloperTools: 'never',
|
||||
pageTitle: 'Chad API',
|
||||
customCss: `
|
||||
.scalar-mcp-layer,
|
||||
.agent-button-container,
|
||||
.t-doc__sidebar > div > button:last-child {
|
||||
display: none !important;
|
||||
.scalar-mcp-layer,
|
||||
.agent-button-container,
|
||||
.t-doc__sidebar > div > button:last-child {
|
||||
display: none !important;
|
||||
}
|
||||
`,
|
||||
},
|
||||
@@ -93,6 +109,7 @@ fastify.register(FastifyMultipart)
|
||||
|
||||
fastify.register(FastifyAutoLoad, {
|
||||
dir: join(__dirname, 'plugins'),
|
||||
maxDepth: 1,
|
||||
})
|
||||
|
||||
fastify.register(FastifyAutoLoad, {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { Server as SocketServer } from 'socket.io'
|
||||
|
||||
export default async function (io: SocketServer) {
|
||||
io.on('connection', async (socket) => {
|
||||
|
||||
})
|
||||
}
|
||||
@@ -1,471 +0,0 @@
|
||||
import type { types } from 'mediasoup'
|
||||
import type { Server as SocketServer } from 'socket.io'
|
||||
import type { PrismaClient } from '../prisma/generated-client/client.ts'
|
||||
import type {
|
||||
ChadClient,
|
||||
SomeSocket,
|
||||
} from '../types/webrtc.ts'
|
||||
import { consola } from 'consola'
|
||||
import { socketToClient } from '../utils/socket-to-client.ts'
|
||||
|
||||
export default async function (io: SocketServer, router: types.Router, prisma: PrismaClient) {
|
||||
const audioLevelObserver = await router.createAudioLevelObserver({
|
||||
maxEntries: 10,
|
||||
threshold: -80,
|
||||
interval: 800,
|
||||
})
|
||||
|
||||
const activeSpeakerObserver = await router.createActiveSpeakerObserver()
|
||||
|
||||
audioLevelObserver.on('volumes', async (volumes: types.AudioLevelObserverVolume[]) => {
|
||||
io.emit('speakingPeers', volumes.map(({ producer, volume }) => {
|
||||
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
|
||||
|
||||
return {
|
||||
clientId: socketId,
|
||||
volume,
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
audioLevelObserver.on('silence', () => {
|
||||
io.emit('speakingPeers', [])
|
||||
io.emit('activeSpeaker', undefined)
|
||||
})
|
||||
|
||||
activeSpeakerObserver.on('dominantspeaker', ({ producer }) => {
|
||||
const { socketId } = producer.appData as { socketId: ChadClient['socketId'] }
|
||||
|
||||
io.emit('activeSpeaker', socketId)
|
||||
})
|
||||
|
||||
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 { id, username, displayName } = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: socket.handshake.auth.userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
displayName: true,
|
||||
},
|
||||
})
|
||||
|
||||
socket.data.userId = id
|
||||
socket.data.username = username
|
||||
socket.data.displayName = displayName
|
||||
|
||||
socket.emit('authenticated')
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
await audioLevelObserver.addProducer({ producerId: producer.id })
|
||||
await activeSpeakerObserver.addProducer({ producerId: producer.id })
|
||||
}
|
||||
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))
|
||||
|
||||
io.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 io.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,12 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "ESNext",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"allowImportingTsExtensions": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowImportingTsExtensions": true
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
export interface ChatClientMessage {
|
||||
text: string
|
||||
replyTo?: {
|
||||
messageId: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string
|
||||
sender: string
|
||||
text: string
|
||||
createdAt: string
|
||||
replyTo?: {
|
||||
messageId: string
|
||||
sender: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import type { types } from 'mediasoup'
|
||||
import type { RemoteSocket, Socket, Namespace as SocketNamespace } from 'socket.io'
|
||||
import type { User } from '../prisma/client'
|
||||
|
||||
export interface ChadClient {
|
||||
socketId: string
|
||||
userId: User['id']
|
||||
username: User['username']
|
||||
displayName: User['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: () => 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
|
||||
speakingPeers: (arg: { clientId: ChadClient['socketId'], volume: types.AudioLevelObserverVolume['volume'] }[]) => void
|
||||
activeSpeaker: (clientId?: ChadClient['socketId']) => void
|
||||
}
|
||||
|
||||
export interface InterServerEvent {}
|
||||
|
||||
export 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>
|
||||
}
|
||||
|
||||
export type SomeSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> | RemoteSocket<ServerToClientEvents, SocketData>
|
||||
|
||||
export type Namespace = SocketNamespace<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { ChadClient, SomeSocket } from '../types/webrtc.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,
|
||||
}
|
||||
}
|
||||
12
server/utils/typebox-ref.ts
Normal file
12
server/utils/typebox-ref.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Static, TSchema, TSchemaOptions, TUnsafe } from 'typebox'
|
||||
import { Unsafe } from 'typebox'
|
||||
|
||||
export function TypeboxRef<T extends TSchema>(t: T, options: TSchemaOptions = {}): TUnsafe<Static<T>> {
|
||||
const id = (t as unknown as Record<string, string | undefined>).$id
|
||||
|
||||
if (!id) {
|
||||
throw new Error('missing ID on schema')
|
||||
}
|
||||
|
||||
return Unsafe<Static<T>>({ ...t, $ref: id, $id: undefined, ...options })
|
||||
}
|
||||
@@ -1172,13 +1172,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ini@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "@types/ini@npm:4.1.1"
|
||||
checksum: 10c0/a060753a39f8bd73b615186018f7aded0eeb5698c0cb00e2f92ae495aa44b6351260e27f938891eeb304e28c2d42036bac5793a4e2031eff1df1a47de8cc8a97
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/json-schema@npm:^7.0.15":
|
||||
version: 7.0.15
|
||||
resolution: "@types/json-schema@npm:7.0.15"
|
||||
@@ -3042,7 +3035,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"flatbuffers@npm:^25.2.10":
|
||||
"flatbuffers@npm:^25.9.23":
|
||||
version: 25.9.23
|
||||
resolution: "flatbuffers@npm:25.9.23"
|
||||
checksum: 10c0/957c4ae2a02be1703c98b36b4dc8ceb81613cf8e2333026afc95a7b68b088bed5458056dc29d0ab7ce8bc403b8c003732b0968d24aba46f5e7c8f71789a6bd9e
|
||||
@@ -3269,12 +3262,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"h264-profile-level-id@npm:^2.2.3":
|
||||
version: 2.3.1
|
||||
resolution: "h264-profile-level-id@npm:2.3.1"
|
||||
"h264-profile-level-id@npm:^2.3.2":
|
||||
version: 2.3.2
|
||||
resolution: "h264-profile-level-id@npm:2.3.2"
|
||||
dependencies:
|
||||
debug: "npm:^4.4.3"
|
||||
checksum: 10c0/c3459549bb28e456db62428c79885cffd4958ce282099c4181b09576f8e5ad90b42395a77209fff4f20a7cb920aaeb660f73902f08343daead0f5527faeb4015
|
||||
checksum: 10c0/75bd12ff36707ffacf379c31c403d4508f3116ef2065e375deadcfafd4f7d163521cf0c70ae5385ebac970fa0acc07f9dd497c4248cfc1ee5623b4533707731d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3423,13 +3416,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ini@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "ini@npm:5.0.0"
|
||||
checksum: 10c0/657491ce766cbb4b335ab221ee8f72b9654d9f0e35c32fe5ff2eb7ab8c5ce72237ff6456555b50cde88e6507a719a70e28e327b450782b4fc20c90326ec8c1a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ini@npm:~1.3.0":
|
||||
version: 1.3.8
|
||||
resolution: "ini@npm:1.3.8"
|
||||
@@ -3967,19 +3953,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mediasoup@npm:^3.19.3":
|
||||
version: 3.19.3
|
||||
resolution: "mediasoup@npm:3.19.3"
|
||||
"mediasoup@npm:^3.19.21":
|
||||
version: 3.19.21
|
||||
resolution: "mediasoup@npm:3.19.21"
|
||||
dependencies:
|
||||
"@types/ini": "npm:^4.1.1"
|
||||
debug: "npm:^4.4.3"
|
||||
flatbuffers: "npm:^25.2.10"
|
||||
h264-profile-level-id: "npm:^2.2.3"
|
||||
ini: "npm:^5.0.0"
|
||||
flatbuffers: "npm:^25.9.23"
|
||||
h264-profile-level-id: "npm:^2.3.2"
|
||||
node-fetch: "npm:^3.3.2"
|
||||
supports-color: "npm:^10.2.2"
|
||||
tar: "npm:^7.4.4"
|
||||
checksum: 10c0/957ab3de4e7419eff3e76767511505f007fcd348b431acc2aa5ffcc2d9917cb83aa22f21f1f0004cb10ad65b46219b58fcf009b3b850719c09d5578aa6545f78
|
||||
tar: "npm:^7.5.13"
|
||||
checksum: 10c0/da37e478540002bae8350dda138eb9c54bb4b67f04c45386fd13205401e1bcd054043189664f35b03b466f8a4dc7df2d9b9a9fa022d0377d5602da4be6db4091
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5426,7 +5410,7 @@ __metadata:
|
||||
fastify: "npm:^5.6.1"
|
||||
fastify-plugin: "npm:^5.1.0"
|
||||
lucia: "npm:^3.2.2"
|
||||
mediasoup: "npm:^3.19.3"
|
||||
mediasoup: "npm:^3.19.21"
|
||||
nodemon: "npm:^3.1.14"
|
||||
prisma: "npm:7"
|
||||
socket.io: "npm:^4.8.1"
|
||||
@@ -5800,7 +5784,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:^7.4.3, tar@npm:^7.4.4":
|
||||
"tar@npm:^7.4.3":
|
||||
version: 7.5.1
|
||||
resolution: "tar@npm:7.5.1"
|
||||
dependencies:
|
||||
@@ -5813,6 +5797,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tar@npm:^7.5.13":
|
||||
version: 7.5.13
|
||||
resolution: "tar@npm:7.5.13"
|
||||
dependencies:
|
||||
"@isaacs/fs-minipass": "npm:^4.0.0"
|
||||
chownr: "npm:^3.0.0"
|
||||
minipass: "npm:^7.1.2"
|
||||
minizlib: "npm:^3.1.0"
|
||||
yallist: "npm:^5.0.0"
|
||||
checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"thread-stream@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "thread-stream@npm:3.1.0"
|
||||
|
||||
Reference in New Issue
Block a user