diff --git a/package.json b/package.json index 6b9ab7a6e5..3c8701c55a 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,6 @@ "escape-html": "^1.0.3", "file-saver": "^2.0.5", "filesize": "10.0.6", - "flux": "4.0.3", "focus-visible": "^5.2.0", "gfm.css": "^1.1.2", "glob-to-regexp": "^0.4.1", @@ -153,7 +152,6 @@ "@types/diff-match-patch": "^1.0.32", "@types/escape-html": "^1.0.1", "@types/file-saver": "^2.0.3", - "@types/flux": "^3.1.9", "@types/fs-extra": "^11.0.0", "@types/geojson": "^7946.0.8", "@types/glob-to-regexp": "^0.4.1", diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index bf41002afb..6e471dde42 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -18,7 +18,6 @@ limitations under the License. import { Room } from "matrix-js-sdk/src/models/room"; import classNames from "classnames"; -import { Dispatcher } from "flux"; import { Enable, Resizable } from "re-resizable"; import { Direction } from "re-resizable/lib/resizer"; import * as React from "react"; @@ -28,7 +27,7 @@ import { polyfillTouchEvent } from "../../../@types/polyfill"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { RovingAccessibleButton, RovingTabIndexWrapper } from "../../../accessibility/RovingTabIndex"; import { Action } from "../../../dispatcher/actions"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; +import defaultDispatcher, { MatrixDispatcher } from "../../../dispatcher/dispatcher"; import { ActionPayload } from "../../../dispatcher/payloads"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { getKeyBindingsManager } from "../../../KeyBindingsManager"; @@ -68,7 +67,7 @@ polyfillTouchEvent(); export interface IAuxButtonProps { tabIndex: number; - dispatcher?: Dispatcher; + dispatcher?: MatrixDispatcher; } interface IProps { diff --git a/src/dispatcher/dispatcher.ts b/src/dispatcher/dispatcher.ts index 9d3eb2ef00..9a1d586256 100644 --- a/src/dispatcher/dispatcher.ts +++ b/src/dispatcher/dispatcher.ts @@ -16,15 +16,133 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Dispatcher } from "flux"; - import { Action } from "./actions"; import { ActionPayload, AsyncActionPayload } from "./payloads"; +type DispatchToken = string; + +function invariant(cond: any, error: string): void { + if (!cond) throw new Error(error); +} + /** * A dispatcher for ActionPayloads (the default within the SDK). + * Based on the old Flux dispatcher https://github.com/facebook/flux/blob/main/src/Dispatcher.js */ -export class MatrixDispatcher extends Dispatcher { +export class MatrixDispatcher { + private readonly callbacks = new Map void>(); + private readonly isHandled = new Map(); + private readonly isPending = new Map(); + private pendingPayload?: ActionPayload; + private lastId = 1; + + /** + * Registers a callback to be invoked with every dispatched payload. Returns + * a token that can be used with `waitFor()`. + */ + public register(callback: (payload: ActionPayload) => void): DispatchToken { + const id = "ID_" + this.lastId++; + this.callbacks.set(id, callback); + if (this.isDispatching()) { + // If there is a dispatch happening right now then the newly registered callback should be skipped + this.isPending.set(id, true); + this.isHandled.set(id, true); + } + return id; + } + + /** + * Removes a callback based on its token. + */ + public unregister(id: DispatchToken): void { + invariant(this.callbacks.has(id), `Dispatcher.unregister(...): '${id}' does not map to a registered callback.`); + this.callbacks.delete(id); + } + + /** + * Waits for the callbacks specified to be invoked before continuing execution + * of the current callback. This method should only be used by a callback in + * response to a dispatched payload. + */ + public waitFor(ids: DispatchToken[]): void { + invariant(this.isDispatching(), "Dispatcher.waitFor(...): Must be invoked while dispatching."); + for (const id of ids) { + if (this.isPending.get(id)) { + invariant( + this.isHandled.get(id), + `Dispatcher.waitFor(...): Circular dependency detected while waiting for '${id}'.`, + ); + continue; + } + invariant( + this.callbacks.get(id), + `Dispatcher.waitFor(...): '${id}' does not map to a registered callback.`, + ); + this.invokeCallback(id); + } + } + + /** + * Dispatches a payload to all registered callbacks. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + private _dispatch = (payload: ActionPayload): void => { + invariant(!this.isDispatching(), "Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch."); + this.startDispatching(payload); + try { + for (const [id] of this.callbacks) { + if (this.isPending.get(id)) { + continue; + } + this.invokeCallback(id); + } + } finally { + this.stopDispatching(); + } + }; + + /** + * Is this Dispatcher currently dispatching. + */ + public isDispatching(): boolean { + return !!this.pendingPayload; + } + + /** + * Call the callback stored with the given id. Also do some internal + * bookkeeping. + * + * Must only be called with an id which has a callback and pendingPayload set + * @internal + */ + private invokeCallback(id: DispatchToken): void { + this.isPending.set(id, true); + this.callbacks.get(id)!(this.pendingPayload!); + this.isHandled.set(id, true); + } + + /** + * Set up bookkeeping needed when dispatching. + * + * @internal + */ + private startDispatching(payload: ActionPayload): void { + for (const [id] of this.callbacks) { + this.isPending.set(id, false); + this.isHandled.set(id, false); + } + this.pendingPayload = payload; + } + + /** + * Clear bookkeeping used for dispatching. + * + * @internal + */ + private stopDispatching(): void { + this.pendingPayload = undefined; + } + /** * Dispatches an event on the dispatcher's event bus. * @param {ActionPayload} payload Required. The payload to dispatch. @@ -42,14 +160,14 @@ export class MatrixDispatcher extends Dispatcher { } if (sync) { - super.dispatch(payload); + this._dispatch(payload); } else { // Unless the caller explicitly asked for us to dispatch synchronously, // we always set a timeout to do this: The flux dispatcher complains // if you dispatch from within a dispatch, so rather than action // handlers having to worry about not calling anything that might // then dispatch, we just do dispatches asynchronously. - window.setTimeout(super.dispatch.bind(this, payload), 0); + window.setTimeout(this._dispatch, 0, payload); } } diff --git a/src/hooks/useDispatcher.ts b/src/hooks/useDispatcher.ts index 1eb0ba2461..3c069fc1bc 100644 --- a/src/hooks/useDispatcher.ts +++ b/src/hooks/useDispatcher.ts @@ -15,15 +15,12 @@ limitations under the License. */ import { useEffect, useRef } from "react"; -import { Dispatcher } from "flux"; import { ActionPayload } from "../dispatcher/payloads"; +import { MatrixDispatcher } from "../dispatcher/dispatcher"; -// Hook to simplify listening to flux dispatches -export const useDispatcher = ( - dispatcher: Dispatcher, - handler: (payload: ActionPayload) => void, -): void => { +// Hook to simplify listening to event dispatches +export const useDispatcher = (dispatcher: MatrixDispatcher, handler: (payload: ActionPayload) => void): void => { // Create a ref that stores handler const savedHandler = useRef((payload: ActionPayload) => {}); diff --git a/src/stores/AsyncStore.ts b/src/stores/AsyncStore.ts index dd38351591..d8f5bbdd1e 100644 --- a/src/stores/AsyncStore.ts +++ b/src/stores/AsyncStore.ts @@ -16,9 +16,9 @@ limitations under the License. import { EventEmitter } from "events"; import AwaitLock from "await-lock"; -import { Dispatcher } from "flux"; import { ActionPayload } from "../dispatcher/payloads"; +import { MatrixDispatcher } from "../dispatcher/dispatcher"; /** * The event/channel to listen for in an AsyncStore. @@ -52,7 +52,7 @@ export abstract class AsyncStore extends EventEmitter { * @param {Dispatcher} dispatcher The dispatcher to rely upon. * @param {T} initialState The initial state for the store. */ - protected constructor(private dispatcher: Dispatcher, initialState: T = {}) { + protected constructor(private dispatcher: MatrixDispatcher, initialState: T = {}) { super(); this.dispatcherRef = dispatcher.register(this.onDispatch.bind(this)); diff --git a/src/stores/AsyncStoreWithClient.ts b/src/stores/AsyncStoreWithClient.ts index 35e5249a0d..84ff7da317 100644 --- a/src/stores/AsyncStoreWithClient.ts +++ b/src/stores/AsyncStoreWithClient.ts @@ -15,16 +15,16 @@ limitations under the License. */ import { MatrixClient } from "matrix-js-sdk/src/client"; -import { Dispatcher } from "flux"; import { AsyncStore } from "./AsyncStore"; import { ActionPayload } from "../dispatcher/payloads"; import { ReadyWatchingStore } from "./ReadyWatchingStore"; +import { MatrixDispatcher } from "../dispatcher/dispatcher"; export abstract class AsyncStoreWithClient extends AsyncStore { protected readyStore: ReadyWatchingStore; - protected constructor(dispatcher: Dispatcher, initialState: T = {}) { + protected constructor(dispatcher: MatrixDispatcher, initialState: T = {}) { super(dispatcher, initialState); // Create an anonymous class to avoid code duplication diff --git a/src/stores/LifecycleStore.ts b/src/stores/LifecycleStore.ts index 26a1da54a4..e4da964553 100644 --- a/src/stores/LifecycleStore.ts +++ b/src/stores/LifecycleStore.ts @@ -14,12 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { Store } from "flux/utils"; - import { Action } from "../dispatcher/actions"; import dis from "../dispatcher/dispatcher"; import { ActionPayload } from "../dispatcher/payloads"; import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload"; +import { AsyncStore } from "./AsyncStore"; interface IState { deferredAction: ActionPayload | null; @@ -30,32 +29,24 @@ const INITIAL_STATE: IState = { }; /** - * A class for storing application state to do with authentication. This is a simple flux + * A class for storing application state to do with authentication. This is a simple * store that listens for actions and updates its state accordingly, informing any * listeners (views) of state changes. */ -class LifecycleStore extends Store { - private state: IState = INITIAL_STATE; - +class LifecycleStore extends AsyncStore { public constructor() { - super(dis); + super(dis, INITIAL_STATE); } - private setState(newState: Partial): void { - this.state = Object.assign(this.state, newState); - this.__emitChange(); - } - - // eslint-disable-next-line @typescript-eslint/naming-convention - protected __onDispatch(payload: ActionPayload | DoAfterSyncPreparedPayload): void { + protected onDispatch(payload: ActionPayload | DoAfterSyncPreparedPayload): void { switch (payload.action) { case Action.DoAfterSyncPrepared: - this.setState({ + this.updateState({ deferredAction: payload.deferred_action, }); break; case "cancel_after_sync_prepared": - this.setState({ + this.updateState({ deferredAction: null, }); break; @@ -65,7 +56,7 @@ class LifecycleStore extends Store { } if (!this.state.deferredAction) break; const deferredAction = Object.assign({}, this.state.deferredAction); - this.setState({ + this.updateState({ deferredAction: null, }); dis.dispatch(deferredAction); @@ -77,10 +68,6 @@ class LifecycleStore extends Store { break; } } - - private reset(): void { - this.state = Object.assign({}, INITIAL_STATE); - } } let singletonLifecycleStore: LifecycleStore | null = null; diff --git a/src/stores/ReadyWatchingStore.ts b/src/stores/ReadyWatchingStore.ts index 24117bab37..cfb3ad2c19 100644 --- a/src/stores/ReadyWatchingStore.ts +++ b/src/stores/ReadyWatchingStore.ts @@ -16,19 +16,19 @@ import { MatrixClient } from "matrix-js-sdk/src/client"; import { SyncState } from "matrix-js-sdk/src/sync"; -import { Dispatcher } from "flux"; import { EventEmitter } from "events"; import { MatrixClientPeg } from "../MatrixClientPeg"; import { ActionPayload } from "../dispatcher/payloads"; import { IDestroyable } from "../utils/IDestroyable"; import { Action } from "../dispatcher/actions"; +import { MatrixDispatcher } from "../dispatcher/dispatcher"; export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable { protected matrixClient: MatrixClient | null = null; private dispatcherRef: string | null = null; - public constructor(protected readonly dispatcher: Dispatcher) { + public constructor(protected readonly dispatcher: MatrixDispatcher) { super(); } diff --git a/test/dispatcher/dispatcher-test.ts b/test/dispatcher/dispatcher-test.ts new file mode 100644 index 0000000000..db27e828c9 --- /dev/null +++ b/test/dispatcher/dispatcher-test.ts @@ -0,0 +1,88 @@ +/* +Copyright 2023 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { defer } from "matrix-js-sdk/src/utils"; + +import defaultDispatcher from "../../src/dispatcher/dispatcher"; +import { Action } from "../../src/dispatcher/actions"; +import { AsyncActionPayload } from "../../src/dispatcher/payloads"; + +describe("MatrixDispatcher", () => { + it("should throw error if unregistering unknown token", () => { + expect(() => defaultDispatcher.unregister("not-a-real-token")).toThrow( + "Dispatcher.unregister(...): 'not-a-real-token' does not map to a registered callback.", + ); + }); + + it("should execute callbacks in registered order", async () => { + const deferred1 = defer(); + const deferred2 = defer(); + + const fn1 = jest.fn(() => deferred1.resolve(1)); + const fn2 = jest.fn(() => deferred2.resolve(2)); + + defaultDispatcher.register(fn1); + defaultDispatcher.register(fn2); + + defaultDispatcher.dispatch({ action: Action.OnLoggedIn }); + const res = await Promise.race([deferred1.promise, deferred2.promise]); + + expect(res).toBe(1); + }); + + it("should skip the queue for the given callback", async () => { + const deferred1 = defer(); + const deferred2 = defer(); + + const fn1 = jest.fn(() => deferred1.resolve(1)); + const fn2 = jest.fn(() => deferred2.resolve(2)); + + defaultDispatcher.register(() => { + defaultDispatcher.waitFor([id2]); + }); + defaultDispatcher.register(fn1); + const id2 = defaultDispatcher.register(fn2); + + defaultDispatcher.dispatch({ action: Action.OnLoggedIn }); + const res = await Promise.race([deferred1.promise, deferred2.promise]); + + expect(res).toBe(2); + }); + + it("should not fire callback which was added during a dispatch", () => { + const fn2 = jest.fn(); + + defaultDispatcher.register(() => { + defaultDispatcher.register(fn2); + }); + + defaultDispatcher.dispatch({ action: Action.OnLoggedIn }, true); + + expect(fn2).not.toHaveBeenCalled(); + }); + + it("should handle AsyncActionPayload", () => { + const fn = jest.fn(); + defaultDispatcher.register(fn); + + const readyFn = jest.fn((dispatch) => { + dispatch({ action: "test" }); + }); + defaultDispatcher.dispatch(new AsyncActionPayload(readyFn), true); + + expect(fn).toHaveBeenLastCalledWith(expect.objectContaining({ action: "test" })); + }); +}); diff --git a/yarn.lock b/yarn.lock index 9f738a287e..b69d4b29d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2150,24 +2150,11 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/fbemitter@*": - version "2.0.32" - resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c" - integrity sha512-Hwq28bBlbmfCgLnNJvjl5ssTrbZCTSblI4vqPpqZrbbEL8vn5l2UivxhlMYfUY7a4SR8UB6RKoLjOZfljqAa6g== - "@types/file-saver@^2.0.3": version "2.0.5" resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.5.tgz#9ee342a5d1314bb0928375424a2f162f97c310c7" integrity sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ== -"@types/flux@^3.1.9": - version "3.1.11" - resolved "https://registry.yarnpkg.com/@types/flux/-/flux-3.1.11.tgz#e030d61e6c7fd6187dfa0e7e3dfcc8036c511581" - integrity sha512-Aq4UB1ZqAKcPbhB0GpgMw2sntvOh71he9tjz53TLKrI7rw3Y3LxCW5pTYY9IV455hQapm4pmxFjpqlWOs308Yg== - dependencies: - "@types/fbemitter" "*" - "@types/react" "*" - "@types/fs-extra@^11.0.0": version "11.0.1" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.1.tgz#f542ec47810532a8a252127e6e105f487e0a6ea5" @@ -3506,13 +3493,6 @@ crc-32@^0.3.0: resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-0.3.0.tgz#6a3d3687f5baec41f7e9b99fe1953a2e5d19775e" integrity sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA== -cross-fetch@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" - integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== - dependencies: - node-fetch "2.6.7" - cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -4573,31 +4553,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fbemitter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" - integrity sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw== - dependencies: - fbjs "^3.0.0" - -fbjs-css-vars@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" - integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== - -fbjs@^3.0.0, fbjs@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" - integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== - dependencies: - cross-fetch "^3.1.5" - fbjs-css-vars "^1.0.0" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.30" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -4709,14 +4664,6 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -flux@4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/flux/-/flux-4.0.3.tgz#573b504a24982c4768fdfb59d8d2ea5637d72ee7" - integrity sha512-yKAbrp7JhZhj6uiT1FTuVMlIAT1J4jqEyBpFApi1kxpGZCvacMVc/t1pMQyotqHhAgvoE3bNvAykhCo2CLjnYw== - dependencies: - fbemitter "^3.0.0" - fbjs "^3.0.1" - focus-lock@^0.11.6: version "0.11.6" resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.11.6.tgz#e8821e21d218f03e100f7dc27b733f9c4f61e683" @@ -6799,7 +6746,7 @@ nwsapi@^2.2.2: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== -object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -7266,13 +7213,6 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -promise@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -8494,11 +8434,6 @@ typescript@4.9.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -ua-parser-js@^0.7.30: - version "0.7.32" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.32.tgz#cd8c639cdca949e30fa68c44b7813ef13e36d211" - integrity sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw== - ua-parser-js@^1.0.2: version "1.0.33" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.33.tgz#f21f01233e90e7ed0f059ceab46eb190ff17f8f4"