diff --git a/backend/package.json b/backend/package.json index a075221..9a3ffea 100644 --- a/backend/package.json +++ b/backend/package.json @@ -51,6 +51,7 @@ "hi-base32": "^0.5.1", "i18n-iso-countries": "^7.14.0", "ioredis": "^5.6.1", + "livekit-server-sdk": "1.2.7", "otpauth": "^9.4.0", "prisma": "^6.10.1", "qrcode": "^1.5.4", diff --git a/backend/src/core/config/livekit.config.ts b/backend/src/core/config/livekit.config.ts new file mode 100644 index 0000000..94dd652 --- /dev/null +++ b/backend/src/core/config/livekit.config.ts @@ -0,0 +1,10 @@ +import { TypeLiveKitOptions } from '@/src/module/libs/livekit/type/livekit.type' +import { ConfigService } from '@nestjs/config' + +export function getLiveKitConfig(configService: ConfigService): TypeLiveKitOptions { + return { + apiSecret: configService.getOrThrow('LIVEKIT_API_SECRET'), + apiKey: configService.getOrThrow('LIVEKIT_API_KEY'), + apiUrl: configService.getOrThrow('LIVEKIT_URL'), + } +} diff --git a/backend/src/core/core.module.ts b/backend/src/core/core.module.ts index 7dee345..82c81ea 100644 --- a/backend/src/core/core.module.ts +++ b/backend/src/core/core.module.ts @@ -1,4 +1,5 @@ import { getGraphQLConfig } from '@/src/core/config/graphql.config' +import { getLiveKitConfig } from '@/src/core/config/livekit.config'; import { AccountModule } from '@/src/module/auth/account/account.module' import { DeactivateModule } from '@/src/module/auth/deactivate/deactivate.module' import { PasswordRecoveryModule } from '@/src/module/auth/password-recovery/password-recovery.module' @@ -7,8 +8,10 @@ import { SessionModule } from '@/src/module/auth/session/session.module' import { TotpModule } from '@/src/module/auth/totp/totp.module' import { VerificationModule } from '@/src/module/auth/verification/verification.module' import { CronModule } from '@/src/module/cron/cron.module' +import { LiveKitModule } from '@/src/module/libs/livekit/livekit.module' import { MailModule } from '@/src/module/libs/mail/mail.module' import { StorageModule } from '@/src/module/libs/storage/storage.module' +import { IngressModule } from '@/src/module/stream/ingress/ingress.module'; import { StreamModule } from '@/src/module/stream/stream.module' import { IS_DEV } from '@/src/shared/util/is-dev.util' import { ApolloDriver } from '@nestjs/apollo' @@ -35,6 +38,11 @@ import { RedisModule } from './redis/redis.module' MailModule, CronModule, StorageModule, + LiveKitModule.registerAsync({ + imports: [ConfigModule], + useFactory: getLiveKitConfig, + inject: [ConfigService], + }), AccountModule, SessionModule, VerificationModule, @@ -43,6 +51,7 @@ import { RedisModule } from './redis/redis.module' DeactivateModule, ProfileModule, StreamModule, + IngressModule, ], }) export class CoreModule {} diff --git a/backend/src/core/graphql/schema.gql b/backend/src/core/graphql/schema.gql index 0513969..d10e6cb 100644 --- a/backend/src/core/graphql/schema.gql +++ b/backend/src/core/graphql/schema.gql @@ -82,6 +82,7 @@ type Mutation { changeStreamInfo(data: ChangeStreamInfoInput!): StreamModel! changeStreamThumbnail(thumbnail: Upload!): Boolean! clearSessionCookie: Boolean! + createIngress(ingressType: Float!): Boolean! createSocialLink(data: SocialLinkInput!): SocialLinkModel! createUser(data: CreateUserInput!): UserModel! deactivateAccount(data: DeactivateAccountInput!): AuthModel! diff --git a/backend/src/module/libs/livekit/livekit.module.ts b/backend/src/module/libs/livekit/livekit.module.ts new file mode 100644 index 0000000..d8ee024 --- /dev/null +++ b/backend/src/module/libs/livekit/livekit.module.ts @@ -0,0 +1,42 @@ +import { LiveKitService } from '@/src/module/libs/livekit/livekit.service' +import { + LiveKitOptionSymbol, + TypeLiveKitAsyncOptions, + TypeLiveKitOptions, +} from '@/src/module/libs/livekit/type/livekit.type' +import { DynamicModule, Module } from '@nestjs/common' + +@Module({}) +export class LiveKitModule { + public static register(options?: TypeLiveKitOptions): DynamicModule { + return { + module: LiveKitModule, + providers: [ + { + provide: LiveKitOptionSymbol, + useValue: options, + }, + LiveKitService, + ], + exports: [LiveKitService], + global: true, + } + } + + public static registerAsync(options: TypeLiveKitAsyncOptions): DynamicModule { + return { + module: LiveKitModule, + imports: options.imports || [], + providers: [ + { + provide: LiveKitOptionSymbol, + useFactory: options.useFactory, + inject: options.inject || [], + }, + LiveKitService, + ], + exports: [LiveKitService], + global: true, + } + } +} diff --git a/backend/src/module/libs/livekit/livekit.service.ts b/backend/src/module/libs/livekit/livekit.service.ts new file mode 100644 index 0000000..818e0a7 --- /dev/null +++ b/backend/src/module/libs/livekit/livekit.service.ts @@ -0,0 +1,44 @@ +import { LiveKitOptionSymbol, TypeLiveKitOptions } from '@/src/module/libs/livekit/type/livekit.type' +import { Inject, Injectable } from '@nestjs/common' +import { IngressClient, RoomServiceClient, WebhookReceiver } from 'livekit-server-sdk' + +@Injectable() +export class LiveKitService { + private roomService: RoomServiceClient + private ingressClient: IngressClient + private webhookReceiver: WebhookReceiver + + constructor( + @Inject(LiveKitOptionSymbol) private readonly options: TypeLiveKitOptions, + ) { + this.roomService = new RoomServiceClient(options.apiUrl, options.apiKey, options.apiSecret) + this.ingressClient = new IngressClient(options.apiUrl) + this.webhookReceiver = new WebhookReceiver(options.apiKey, options.apiSecret) + } + + public get ingress(): IngressClient { + return this.createProxy(this.ingressClient) + } + + public get room(): RoomServiceClient { + return this.createProxy(this.roomService) + } + + public get webhook(): WebhookReceiver { + return this.createProxy(this.webhookReceiver) + } + + private createProxy(target: Target) { + return new Proxy(target, { + get: (obj, prop) => { + const value = obj[prop as Prop] + + if (typeof value === 'function') { + return value.bind(obj) + } + + return value + }, + }) + } +} diff --git a/backend/src/module/libs/livekit/type/livekit.type.ts b/backend/src/module/libs/livekit/type/livekit.type.ts new file mode 100644 index 0000000..81b9b0a --- /dev/null +++ b/backend/src/module/libs/livekit/type/livekit.type.ts @@ -0,0 +1,12 @@ +import { FactoryProvider, ModuleMetadata } from '@nestjs/common' + +export const LiveKitOptionSymbol = Symbol('LivekitOptionSymbol') + +export type TypeLiveKitOptions = { + apiUrl: string + apiKey: string + apiSecret: string +} + +export type TypeLiveKitAsyncOptions = Pick + & Pick, 'useFactory' | 'inject'> diff --git a/backend/src/module/stream/ingress/ingress.module.ts b/backend/src/module/stream/ingress/ingress.module.ts new file mode 100644 index 0000000..f171ff1 --- /dev/null +++ b/backend/src/module/stream/ingress/ingress.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { IngressService } from './ingress.service'; +import { IngressResolver } from './ingress.resolver'; + +@Module({ + providers: [IngressResolver, IngressService], +}) +export class IngressModule {} diff --git a/backend/src/module/stream/ingress/ingress.resolver.ts b/backend/src/module/stream/ingress/ingress.resolver.ts new file mode 100644 index 0000000..8a2e8bd --- /dev/null +++ b/backend/src/module/stream/ingress/ingress.resolver.ts @@ -0,0 +1,20 @@ +import { Authorization } from '@/src/shared/decorators/auth.decorator' +import { Authorized } from '@/src/shared/decorators/authorized.decorator' +import { Args, Mutation, Resolver } from '@nestjs/graphql' +import { User } from '@prisma/generated' +import { IngressInput } from 'livekit-server-sdk' +import { IngressService } from './ingress.service' + +@Resolver('Ingress') +export class IngressResolver { + constructor(private readonly ingressService: IngressService) {} + + @Authorization() + @Mutation(() => Boolean, { name: 'createIngress' }) + public async create( + @Authorized() user: User, + @Args('ingressType') ingressType: IngressInput, + ) { + return this.ingressService.create(user, ingressType) + } +} diff --git a/backend/src/module/stream/ingress/ingress.service.ts b/backend/src/module/stream/ingress/ingress.service.ts new file mode 100644 index 0000000..fd0c39a --- /dev/null +++ b/backend/src/module/stream/ingress/ingress.service.ts @@ -0,0 +1,79 @@ +import { PrismaService } from '@/src/core/prisma/prisma.service' +import { LiveKitService } from '@/src/module/libs/livekit/livekit.service' +import { BadRequestException, Injectable } from '@nestjs/common' +import { User } from '@prisma/generated' +import { + CreateIngressOptions, + IngressAudioEncodingPreset, + IngressInput, + IngressVideoEncodingPreset, +} from 'livekit-server-sdk' + +@Injectable() +export class IngressService { + constructor( + private readonly prismaService: PrismaService, + private readonly liveKitService: LiveKitService, + ) { + } + + public async create(user: User, ingressType: IngressInput) { + await this.resetIngresses(user) + + const options: CreateIngressOptions = { + name: user.name, + roomName: user.id, + participantName: user.name, + participantIdentity: user.id, + } + + if (ingressType === IngressInput.WHIP_INPUT) { + options.bypassTranscoding = true + } + else { + options.video = { + source: 1, + preset: IngressVideoEncodingPreset.H264_1080P_30FPS_3_LAYERS, + } + options.audio = { + source: 2, + preset: IngressAudioEncodingPreset.OPUS_STEREO_96KBPS, + } + } + + const ingress = await this.liveKitService.ingress.createIngress(ingressType, options) + + if (!ingress || !ingress.url || !ingress.streamKey) { + throw new BadRequestException('Не удалось создать входной поток') + } + + await this.prismaService.stream.update({ + where: { userId: user.id }, + data: { + ingressId: ingress.ingressId, + serverUrl: ingress.url, + key: ingress.streamKey, + }, + }) + + return true + } + + private async resetIngresses(user: User) { + const ingresses = await this.liveKitService.ingress.listIngress({ + roomName: user.id, + }) + + const rooms = await this.liveKitService.room.listRooms([user.id]) + + for (const room of rooms) { + await this.liveKitService.room.deleteRoom(room.name) + } + + for (const ingress of ingresses) { + if (ingress.ingressId) { + await this.liveKitService.ingress.deleteIngress(ingress.ingressId) + } + } + } +} diff --git a/backend/src/module/stream/stream.module.ts b/backend/src/module/stream/stream.module.ts index 77f0a9d..978942f 100644 --- a/backend/src/module/stream/stream.module.ts +++ b/backend/src/module/stream/stream.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common' import { StreamService } from './stream.service' import { StreamResolver } from './stream.resolver' +import { IngressModule } from './ingress/ingress.module' @Module({ providers: [StreamResolver, StreamService], + imports: [IngressModule], }) export class StreamModule {} diff --git a/backend/yarn.lock b/backend/yarn.lock index 550b6ec..1ed73af 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -3337,6 +3337,13 @@ dependencies: undici-types "~7.8.0" +"@types/node@>=13.7.0": + version "24.0.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.0.12.tgz#3cf8719572145cfecf4cf9d180d8e7f74a61af00" + integrity sha512-LtOrbvDf5ndC9Xi+4QZjVL0woFymF/xSTKZKPgrrl7H7XoeDvnD+E2IclKVDyaK9UM756W/3BXqSU+JEHopA9g== + dependencies: + undici-types "~7.8.0" + "@types/node@^22.10.7": version "22.15.32" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.32.tgz#c301cc2275b535a5e54bb81d516b1d2e9afe06e5" @@ -4007,6 +4014,15 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +axios@^1.3.6: + version "1.10.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.10.0.tgz#af320aee8632eaf2a400b6a1979fa75856f38d54" + integrity sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + b4a@^1.6.4: version "1.6.7" resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" @@ -4227,6 +4243,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -4306,12 +4327,22 @@ camel-case@^3.0.0: no-case "^2.2.0" upper-case "^1.1.1" +camelcase-keys@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252" + integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg== + dependencies: + camelcase "^6.3.0" + map-obj "^4.1.0" + quick-lru "^5.1.1" + type-fest "^1.2.1" + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: +camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -5036,6 +5067,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + editorconfig@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-1.0.4.tgz#040c9a8e9a6c5288388b87c2db07028aa89f53a3" @@ -5682,6 +5720,11 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + foreground-child@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" @@ -6941,6 +6984,22 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonwebtoken@^9.0.0: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + jstransformer@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3" @@ -6960,6 +7019,23 @@ juice@^10.0.0: slick "^1.12.2" web-resource-inliner "^6.0.1" +jwa@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + keyv@^4.5.3, keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -7044,6 +7120,17 @@ liquidjs@^10.11.1: dependencies: commander "^10.0.0" +livekit-server-sdk@1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/livekit-server-sdk/-/livekit-server-sdk-1.2.7.tgz#54fde18710bea6896741bccbf182ebe17c07632d" + integrity sha512-tOhRb0vz1wBzMpTkP4ixptlC9MFME24PvG8Z/R7vBbQ1VGd6EdNr56voBSr+RCalYxaQqx0E9Gg4l+57m/Nlmw== + dependencies: + axios "^1.3.6" + camelcase-keys "^7.0.0" + jsonwebtoken "^9.0.0" + long "^5.0.0" + protobufjs "^7.2.4" + load-esm@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/load-esm/-/load-esm-1.0.2.tgz#35dbac8a1a3abdb802cf236008048fcc8a9289a6" @@ -7073,11 +7160,41 @@ lodash.defaults@^4.2.0: resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + lodash.isarguments@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -7093,6 +7210,11 @@ lodash.omit@4.5.0: resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -7121,6 +7243,11 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" @@ -7209,6 +7336,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +map-obj@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" + integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== + marked@7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/marked/-/marked-7.0.4.tgz#e2558ee2d535b9df6a27c6e282dc603a18388a6d" @@ -7720,7 +7852,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.3, ms@^2.1.3: +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -8260,6 +8392,24 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== +protobufjs@^7.2.4: + version "7.5.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.3.tgz#13f95a9e3c84669995ec3652db2ac2fb00b89363" + integrity sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@^2.0.7, proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -8268,6 +8418,11 @@ proxy-addr@^2.0.7, proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pug-attrs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-3.0.0.tgz#b10451e0348165e31fad1cc23ebddd9dc7347c41" @@ -9418,6 +9573,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + type-fest@^4.41.0: version "4.41.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.41.0.tgz#6ae1c8e5731273c2bf1f58ad39cbae2c91a46c58"