From 64ac8b69af557ee3e01df28681a1fc25dc8bd364 Mon Sep 17 00:00:00 2001 From: Bryce Tham Date: Wed, 10 Jan 2024 15:25:36 -0500 Subject: [PATCH] feat: update effects APIs (#68) * feat: update effects APIs * fix: allow one effect per kind and replace effect if same kind is added --------- Co-authored-by: Bryce Tham --- package.json | 2 +- src/errors.ts | 1 + src/media/local-stream.spec.ts | 155 +++++++++++++++++++-------------- src/media/local-stream.ts | 133 +++++++++++++++++++++------- yarn.lock | 79 ++++++++--------- 5 files changed, 230 insertions(+), 140 deletions(-) diff --git a/package.json b/package.json index 220b20c..5c50f8c 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "dependencies": { "@webex/ts-events": "^1.1.0", "@webex/web-capabilities": "^1.1.0", - "@webex/web-media-effects": "^2.7.0", + "@webex/web-media-effects": "^2.15.6", "events": "^3.3.0", "js-logger": "^1.6.1", "typed-emitter": "^2.1.0", diff --git a/src/errors.ts b/src/errors.ts index 47c9ad7..e912870 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,6 +1,7 @@ export enum WebrtcCoreErrorType { DEVICE_PERMISSION_DENIED = 'DEVICE_PERMISSION_DENIED', CREATE_STREAM_FAILED = 'CREATE_STREAM_FAILED', + ADD_EFFECT_FAILED = 'ADD_EFFECT_FAILED', } /** diff --git a/src/media/local-stream.spec.ts b/src/media/local-stream.spec.ts index f3f6418..1d6bb05 100644 --- a/src/media/local-stream.spec.ts +++ b/src/media/local-stream.spec.ts @@ -1,10 +1,6 @@ -import { BaseEffect } from '@webex/web-media-effects'; -import { createBrowserMock } from '../mocks/create-browser-mock'; -import MediaStreamStub from '../mocks/media-stream-stub'; -import MediaStreamTrackStub from '../mocks/media-stream-track-stub'; -import { mocked } from '../mocks/mock'; +import { WebrtcCoreError } from '../errors'; import { createMockedStream } from '../util/test-utils'; -import { LocalStream } from './local-stream'; +import { LocalStream, LocalStreamEventNames, TrackEffect } from './local-stream'; /** * A dummy LocalStream implementation so we can instantiate it for testing. @@ -50,72 +46,105 @@ describe('LocalStream', () => { expect(spy).toHaveBeenCalledWith(); }); }); -}); -describe('LocalTrack addEffect', () => { - createBrowserMock(MediaStreamStub, 'MediaStream'); - - // eslint-disable-next-line jsdoc/require-jsdoc - const createMockedTrackEffect = () => { - const effectTrack = mocked(new MediaStreamTrackStub()); - const effect = { - dispose: jest.fn().mockResolvedValue(undefined), - load: jest.fn().mockResolvedValue(effectTrack), - on: jest.fn(), - }; - - return { effectTrack, effect }; - }; - - // TODO: addTrack and removeTrack do not work the current implementation of createMockedStream, so - // we have to use the stubs here directly for now - const mockTrack = mocked(new MediaStreamTrackStub()) as unknown as MediaStreamTrack; - const mockStream = mocked(new MediaStreamStub([mockTrack])) as unknown as MediaStream; - let localStream: LocalStream; - beforeEach(() => { - localStream = new TestLocalStream(mockStream); - }); + describe('addEffect', () => { + let effect: TrackEffect; + let loadSpy: jest.SpyInstance; + let emitSpy: jest.SpyInstance; + + beforeEach(() => { + effect = { + id: 'test-id', + kind: 'test-kind', + isEnabled: false, + dispose: jest.fn().mockResolvedValue(undefined), + load: jest.fn().mockResolvedValue(undefined), + on: jest.fn(), + } as unknown as TrackEffect; + + loadSpy = jest.spyOn(effect, 'load'); + emitSpy = jest.spyOn(localStream[LocalStreamEventNames.EffectAdded], 'emit'); + }); - it('loads and uses the effect when there is no loading effect', async () => { - expect.hasAssertions(); + it('should load and add an effect', async () => { + expect.hasAssertions(); - const { effectTrack, effect } = createMockedTrackEffect(); + const addEffectPromise = localStream.addEffect(effect); - const addEffectPromise = localStream.addEffect('test-effect', effect as unknown as BaseEffect); + await expect(addEffectPromise).resolves.toBeUndefined(); + expect(loadSpy).toHaveBeenCalledWith(mockStream.getTracks()[0]); + expect(localStream.getEffects()).toStrictEqual([effect]); + expect(emitSpy).toHaveBeenCalledWith(effect); + }); - await expect(addEffectPromise).resolves.toBeUndefined(); - expect(localStream.outputStream.getTracks()[0]).toBe(effectTrack); - }); + it('should load and add multiple effects with different IDs and kinds', async () => { + expect.hasAssertions(); + + const firstEffect = effect; + const secondEffect = { + ...effect, + id: 'another-id', + kind: 'another-kind', + } as unknown as TrackEffect; + await localStream.addEffect(firstEffect); + await localStream.addEffect(secondEffect); + + expect(loadSpy).toHaveBeenCalledTimes(2); + expect(localStream.getEffects()).toStrictEqual([firstEffect, secondEffect]); + expect(emitSpy).toHaveBeenCalledTimes(2); + }); - it('does not use the effect when the loading effect is cleared during load', async () => { - expect.hasAssertions(); + it('should not load an effect with the same ID twice', async () => { + expect.hasAssertions(); - const { effect } = createMockedTrackEffect(); + await localStream.addEffect(effect); + const secondAddEffectPromise = localStream.addEffect(effect); - // Add effect and immediately dispose all effects to clear loading effects - const addEffectPromise = localStream.addEffect('test-effect', effect as unknown as BaseEffect); - await localStream.disposeEffects(); + await expect(secondAddEffectPromise).resolves.toBeUndefined(); // no-op + expect(loadSpy).toHaveBeenCalledTimes(1); + expect(localStream.getEffects()).toStrictEqual([effect]); + expect(emitSpy).toHaveBeenCalledTimes(1); + }); - await expect(addEffectPromise).rejects.toThrow('not required after loading'); - expect(localStream.outputStream).toBe(mockStream); - }); + it('should throw an error if an effect of the same kind is added while loading', async () => { + expect.hasAssertions(); - it('loads and uses the latest effect when the loading effect changes during load', async () => { - expect.hasAssertions(); - const { effect: firstEffect } = createMockedTrackEffect(); - const { effectTrack, effect: secondEffect } = createMockedTrackEffect(); - - const firstAddEffectPromise = localStream.addEffect( - 'test-effect', - firstEffect as unknown as BaseEffect - ); - const secondAddEffectPromise = localStream.addEffect( - 'test-effect', - secondEffect as unknown as BaseEffect - ); - await expect(firstAddEffectPromise).rejects.toThrow('not required after loading'); - await expect(secondAddEffectPromise).resolves.toBeUndefined(); - - expect(localStream.outputStream.getTracks()[0]).toBe(effectTrack); + const firstEffect = effect; + const secondEffect = { ...effect, id: 'another-id' } as unknown as TrackEffect; // same kind + const firstAddEffectPromise = localStream.addEffect(firstEffect); + const secondAddEffectPromise = localStream.addEffect(secondEffect); + + await expect(firstAddEffectPromise).rejects.toBeInstanceOf(WebrtcCoreError); + await expect(secondAddEffectPromise).resolves.toBeUndefined(); + expect(loadSpy).toHaveBeenCalledTimes(2); + expect(localStream.getEffects()).toStrictEqual([secondEffect]); + expect(emitSpy).toHaveBeenCalledTimes(1); + }); + + it('should replace the effect if an effect of the same kind is added after loading', async () => { + expect.hasAssertions(); + + const firstEffect = effect; + const secondEffect = { ...effect, id: 'another-id' } as unknown as TrackEffect; // same kind + await localStream.addEffect(firstEffect); + const secondAddEffectPromise = localStream.addEffect(secondEffect); + + await expect(secondAddEffectPromise).resolves.toBeUndefined(); + expect(loadSpy).toHaveBeenCalledTimes(2); + expect(localStream.getEffects()).toStrictEqual([secondEffect]); + expect(emitSpy).toHaveBeenCalledTimes(2); + }); + + it('should throw an error if effects are cleared while loading', async () => { + expect.hasAssertions(); + + const addEffectPromise = localStream.addEffect(effect); + await localStream.disposeEffects(); + + await expect(addEffectPromise).rejects.toBeInstanceOf(WebrtcCoreError); + expect(loadSpy).toHaveBeenCalledTimes(1); + expect(localStream.getEffects()).toStrictEqual([]); + expect(emitSpy).toHaveBeenCalledTimes(0); + }); }); }); diff --git a/src/media/local-stream.ts b/src/media/local-stream.ts index de0c844..190053e 100644 --- a/src/media/local-stream.ts +++ b/src/media/local-stream.ts @@ -1,21 +1,23 @@ import { AddEvents, TypedEvent, WithEventsDummyType } from '@webex/ts-events'; import { BaseEffect, EffectEvent } from '@webex/web-media-effects'; +import { WebrtcCoreError, WebrtcCoreErrorType } from '../errors'; +import { logger } from '../util/logger'; import { Stream, StreamEventNames } from './stream'; +export type TrackEffect = BaseEffect; + export enum LocalStreamEventNames { ConstraintsChange = 'constraints-change', OutputTrackChange = 'output-track-change', + EffectAdded = 'effect-added', } interface LocalStreamEvents { [LocalStreamEventNames.ConstraintsChange]: TypedEvent<() => void>; [LocalStreamEventNames.OutputTrackChange]: TypedEvent<(track: MediaStreamTrack) => void>; + [LocalStreamEventNames.EffectAdded]: TypedEvent<(effect: TrackEffect) => void>; } -export type TrackEffect = BaseEffect; - -type EffectItem = { name: string; effect: TrackEffect }; - /** * A stream which originates on the local device. */ @@ -24,7 +26,9 @@ abstract class _LocalStream extends Stream { [LocalStreamEventNames.OutputTrackChange] = new TypedEvent<(track: MediaStreamTrack) => void>(); - private effects: EffectItem[] = []; + [LocalStreamEventNames.EffectAdded] = new TypedEvent<(effect: TrackEffect) => void>(); + + private effects: TrackEffect[] = []; private loadingEffects: Map = new Map(); @@ -141,55 +145,118 @@ abstract class _LocalStream extends Stream { /** * Adds an effect to a local stream. * - * @param name - The name of the effect. * @param effect - The effect to add. */ - async addEffect(name: string, effect: TrackEffect): Promise { - // Load the effect - this.loadingEffects.set(name, effect); - const outputTrack = await effect.load(this.outputTrack); + async addEffect(effect: TrackEffect): Promise { + // Check if the effect has already been added. + if (this.effects.some((e) => e.id === effect.id)) { + return; + } + + // Load the effect. Because loading is asynchronous, keep track of the loading effects. + this.loadingEffects.set(effect.kind, effect); + await effect.load(this.outputTrack); - // Check that the loaded effect is the latest one and dispose if not - if (effect !== this.loadingEffects.get(name)) { + // After loading, check whether or not we still want to use this effect. If another effect of + // the same kind was added while this effect was loading, we only want to use the latest effect, + // so dispose this one. If the effects list was cleared while this effect was loading, also + // dispose it. + if (effect !== this.loadingEffects.get(effect.kind)) { await effect.dispose(); - throw new Error(`Effect "${name}" not required after loading`); + throw new WebrtcCoreError( + WebrtcCoreErrorType.ADD_EFFECT_FAILED, + `Another effect with kind ${effect.kind} was added while effect ${effect.id} was loading, or the effects list was cleared.` + ); } + this.loadingEffects.delete(effect.kind); - // Use the effect - this.loadingEffects.delete(name); - this.effects.push({ name, effect }); - this.changeOutputTrack(outputTrack); - - // When the effect's track is updated, update the next effect or output stream. - // TODO: using EffectEvent.TrackUpdated will cause the entire web-media-effects lib to be built - // and makes the size of the webrtc-core build much larger, so we use type assertion here as a - // temporary workaround. - effect.on('track-updated' as EffectEvent, (track: MediaStreamTrack) => { - const effectIndex = this.effects.findIndex((e) => e.name === name); + /** + * Handle when the effect's output track has been changed. This will update the input of the + * next effect in the effects list of the output of the stream. + * + * @param track - The new output track of the effect. + */ + const handleEffectTrackUpdated = (track: MediaStreamTrack) => { + const effectIndex = this.effects.findIndex((e) => e.id === effect.id); if (effectIndex === this.effects.length - 1) { this.changeOutputTrack(track); + } else if (effectIndex >= 0) { + this.effects[effectIndex + 1]?.replaceInputTrack(track); } else { - this.effects[effectIndex + 1]?.effect.replaceInputTrack(track); + logger.error(`Effect with ID ${effect.id} not found in effects list.`); + } + }; + + /** + * Handle when the effect has been disposed. This will remove all event listeners from the + * effect. + */ + const handleEffectDisposed = () => { + effect.off('track-updated' as EffectEvent, handleEffectTrackUpdated); + effect.off('disposed' as EffectEvent, handleEffectDisposed); + }; + + // TODO: using EffectEvent.TrackUpdated or EffectEvent.Disposed will cause the entire + // web-media-effects lib to be rebuilt and inflates the size of the webrtc-core build, so + // we use type assertion here as a temporary workaround. + effect.on('track-updated' as EffectEvent, handleEffectTrackUpdated); + effect.on('disposed' as EffectEvent, handleEffectDisposed); + + // Add the effect to the effects list. If an effect of the same kind has already been added, + // dispose the existing effect and replace it with the new effect. If the existing effect was + // enabled, also enable the new effect. + const existingEffectIndex = this.effects.findIndex((e) => e.kind === effect.kind); + if (existingEffectIndex >= 0) { + const [existingEffect] = this.effects.splice(existingEffectIndex, 1, effect); + if (existingEffect.isEnabled) { + // If the existing effect is not the first effect in the effects list, then the input of the + // new effect should be the output of the previous effect in the effects list. We know the + // output track of the previous effect must exist because it must have been loaded (and all + // loaded effects have an output track). + const inputTrack = + existingEffectIndex === 0 + ? this.inputTrack + : (this.effects[existingEffectIndex - 1].getOutputTrack() as MediaStreamTrack); + await effect.replaceInputTrack(inputTrack); + // Enabling the new effect will trigger the track-updated event, which will handle the new + // effect's updated output track. + await effect.enable(); } - }); + await existingEffect.dispose(); + } else { + this.effects.push(effect); + } + + // Emit an event with the effect so others can listen to the effect events. + this[LocalStreamEventNames.EffectAdded].emit(effect); + } + + /** + * Get an effect from the effects list by ID. + * + * @param id - The id of the effect you want to get. + * @returns The effect or undefined. + */ + getEffectById(id: string): TrackEffect | undefined { + return this.effects.find((effect) => effect.id === id); } /** - * Get an effect from the effects list. + * Get an effect from the effects list by kind. * - * @param name - The name of the effect you want to get. + * @param kind - The kind of the effect you want to get. * @returns The effect or undefined. */ - getEffect(name: string): TrackEffect | undefined { - return this.effects.find((e) => e.name === name)?.effect; + getEffectByKind(kind: string): TrackEffect | undefined { + return this.effects.find((effect) => effect.kind === kind); } /** * Get all the effects from the effects list. * - * @returns A list of effect items, each containing the name and the effect itself. + * @returns A list of effects. */ - getAllEffects(): EffectItem[] { + getEffects(): TrackEffect[] { return this.effects; } @@ -202,7 +269,7 @@ abstract class _LocalStream extends Stream { // Dispose of any effects currently in use if (this.effects.length > 0) { this.changeOutputTrack(this.inputTrack); - await Promise.all(this.effects.map((item: EffectItem) => item.effect.dispose())); + await Promise.all(this.effects.map((effect) => effect.dispose())); this.effects = []; } } diff --git a/yarn.lock b/yarn.lock index 6e94f87..0ab7de1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2417,11 +2417,6 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.186.tgz#862e5514dd7bd66ada6c70ee5fce844b06c8ee97" integrity sha512-eHcVlLXP0c2FlMPm56ITode2AgLMSa6aJ05JTTbYbI+7EMkCEE5qk2E41d5g2lCVTqRe0GnnRFurmlCsDODrPw== -"@types/long@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" - integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== - "@types/mime@*": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" @@ -2670,12 +2665,12 @@ dependencies: "@wdio/logger" "6.10.10" -"@webex/ladon-ts@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@webex/ladon-ts/-/ladon-ts-4.0.0.tgz#c1d96a70ea79802a7a94b3c4f306d9cb6e9f5d35" - integrity sha512-gmFJJt+qSeJBgQqebCDpyF0TVsyZn9riCJj+ruUpkCNlkaRJ6m2LREAIA/lYYv/exrYJO/51bHMScY0WtPT+Kg== +"@webex/ladon-ts@^4.2.4": + version "4.2.4" + resolved "https://registry.yarnpkg.com/@webex/ladon-ts/-/ladon-ts-4.2.4.tgz#27be22fc4149356720341bc3cdea0681711a5f2d" + integrity sha512-cv+AHUvUdr23PlulKsS2zO2MbYj+PgnQQ352rY1zFnuYyfHmIxETvios9ksuk7JunCX/x44mzEN9HB6q2Ce8gQ== dependencies: - onnxruntime-web "^1.13.1" + onnxruntime-web "^1.15.1" "@webex/ts-events@^1.1.0": version "1.1.0" @@ -2692,15 +2687,16 @@ dependencies: bowser "^2.11.0" -"@webex/web-media-effects@^2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@webex/web-media-effects/-/web-media-effects-2.7.0.tgz#f203f9512fb95d0639c74c10f39eda2e58663739" - integrity sha512-CfmygG0Q2Zm8plHZG0VIUX/1SFwmNiUx+IZCbutIh4uAKnE/RRx2cmYetBZlMbNqDSJcGJnHVVUOvu1MGo4eCQ== +"@webex/web-media-effects@^2.15.6": + version "2.15.6" + resolved "https://registry.yarnpkg.com/@webex/web-media-effects/-/web-media-effects-2.15.6.tgz#9506963447a6c96435bd1b01acab0f87a6b7d7df" + integrity sha512-uK8sg14FtCwGvpRKi3guJuA6/I/qSI0v8lGblWvpYBqnfgNAki0pzHfJn6H1oiVK03zWqYQ3uzVDvS07hMAghg== dependencies: - "@webex/ladon-ts" "^4.0.0" + "@webex/ladon-ts" "^4.2.4" events "^3.3.0" js-logger "^1.6.1" typed-emitter "^1.4.0" + uuid "^9.0.1" "@yarn-tool/resolve-package@^1.0.40": version "1.0.47" @@ -7866,10 +7862,10 @@ loglevel@^1.6.0: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.0.tgz#e7ec73a57e1e7b419cb6c6ac06bf050b67356114" integrity sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA== -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.0.0, long@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== loupe@^2.3.1: version "2.3.4" @@ -8747,29 +8743,22 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -onnx-proto@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/onnx-proto/-/onnx-proto-4.0.4.tgz#2431a25bee25148e915906dda0687aafe3b9e044" - integrity sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA== - dependencies: - protobufjs "^6.8.8" - -onnxruntime-common@~1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz#2bb5dac5261269779aa5fb6536ca379657de8bf6" - integrity sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew== +onnxruntime-common@~1.16.3: + version "1.16.3" + resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.16.3.tgz#216bd1318d171496f1e92906a801c95bd2fb1aaa" + integrity sha512-ZZfFzEqBf6YIGwB9PtBLESHI53jMXA+/hn+ACVUbEfPuK2xI5vMGpLPn+idpwCmHsKJNRzRwqV12K+6TQj6tug== -onnxruntime-web@^1.13.1: - version "1.14.0" - resolved "https://registry.yarnpkg.com/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz#c8cee538781b1d4c1c6b043934f4a3e6ddf1466e" - integrity sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw== +onnxruntime-web@^1.15.1: + version "1.16.3" + resolved "https://registry.yarnpkg.com/onnxruntime-web/-/onnxruntime-web-1.16.3.tgz#cbd0fccc5348c4e318f80e009c4ea56b634ffc33" + integrity sha512-8O1xCG/RcNQNYYWvdiQJSNpncVg78OVOFeV6MYs/jx++/b12oje8gYUzKqz9wR/sXiX/8TCvdyHgEjj5gQGKUg== dependencies: flatbuffers "^1.12.0" guid-typescript "^1.0.9" - long "^4.0.0" - onnx-proto "^4.0.4" - onnxruntime-common "~1.14.0" + long "^5.2.3" + onnxruntime-common "~1.16.3" platform "^1.3.6" + protobufjs "^7.2.4" opener@^1.5.2: version "1.5.2" @@ -9340,10 +9329,10 @@ 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@^6.8.8: - version "6.11.3" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" - integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== +protobufjs@^7.2.4: + version "7.2.5" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" + integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -9355,9 +9344,8 @@ protobufjs@^6.8.8: "@protobufjs/path" "^1.1.2" "@protobufjs/pool" "^1.1.0" "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" "@types/node" ">=13.7.0" - long "^4.0.0" + long "^5.0.0" proxy-from-env@1.1.0, proxy-from-env@^1.0.0: version "1.1.0" @@ -11250,6 +11238,11 @@ uuid@^8.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"