From 895ff0a96663adeff28cef2aeeef6d99488ce1f2 Mon Sep 17 00:00:00 2001 From: Manoj Vivek Date: Sat, 25 May 2024 18:46:51 +0530 Subject: [PATCH] Revamped the about window and added info about app update status --- desktop-app/package.json | 3 +- desktop-app/src/common/constants.ts | 1 + desktop-app/src/main/app-updater.ts | 71 ++++++++ desktop-app/src/main/main.ts | 23 +-- desktop-app/src/main/menu/help.ts | 159 +++++++--------- desktop-app/src/main/menu/index.ts | 10 +- .../src/main/native-functions/index.ts | 6 +- desktop-app/src/main/util.ts | 11 +- desktop-app/src/renderer/AppContent.tsx | 2 + .../renderer/components/AboutDialog/index.tsx | 170 ++++++++++++++++++ desktop-app/yarn.lock | 46 +++-- 11 files changed, 369 insertions(+), 133 deletions(-) create mode 100644 desktop-app/src/main/app-updater.ts create mode 100644 desktop-app/src/renderer/components/AboutDialog/index.tsx diff --git a/desktop-app/package.json b/desktop-app/package.json index 77793106b..e9282c47b 100644 --- a/desktop-app/package.json +++ b/desktop-app/package.json @@ -118,8 +118,9 @@ "electron-debug": "^3.2.0", "electron-log": "^4.4.8", "electron-store": "^8.0.2", - "electron-updater": "^6.1.1", + "electron-updater": "^6.1.8", "emitter": "^0.0.5", + "javascript-time-ago": "^2.5.10", "mousetrap": "^1.6.5", "os": "^0.1.2", "postcss": "^8.4.31", diff --git a/desktop-app/src/common/constants.ts b/desktop-app/src/common/constants.ts index 9f2eaf3b3..d06dbbca7 100644 --- a/desktop-app/src/common/constants.ts +++ b/desktop-app/src/common/constants.ts @@ -29,6 +29,7 @@ export const IPC_MAIN_CHANNELS = { OPEN_URL: 'open-url', START_WATCHING_FILE: 'start-watching-file', STOP_WATCHER: 'stop-watcher', + OPEN_ABOUT_DIALOG: 'open-about-dialog', } as const; export type Channels = typeof IPC_MAIN_CHANNELS[keyof typeof IPC_MAIN_CHANNELS]; diff --git a/desktop-app/src/main/app-updater.ts b/desktop-app/src/main/app-updater.ts new file mode 100644 index 000000000..1d94b0d07 --- /dev/null +++ b/desktop-app/src/main/app-updater.ts @@ -0,0 +1,71 @@ +import { autoUpdater } from 'electron-updater'; + +export interface AppUpdaterStatus { + status: string; + version?: string; + lastChecked?: number; + progress?: number; + size?: number; + error?: Error; +} + +export class AppUpdater { + status: string = 'IDLE'; + + version?: string; + + lastChecked?: number; + + progress?: number; + + size?: number; + + error?: Error; + + constructor() { + autoUpdater.logger = console; + autoUpdater.checkForUpdatesAndNotify(); + autoUpdater.on('checking-for-update', () => { + this.status = 'CHECKING'; + this.lastChecked = Date.now(); + }); + autoUpdater.on('update-available', (info) => { + this.status = 'AVAILABLE'; + this.version = info.version; + this.lastChecked = Date.now(); + }); + autoUpdater.on('update-not-available', (info) => { + this.status = 'UP_TO_DATE'; + this.lastChecked = Date.now(); + }); + autoUpdater.on('error', (err) => { + this.status = 'ERROR'; + this.error = err; + this.lastChecked = Date.now(); + }); + autoUpdater.on('download-progress', (progressObj) => { + const logMessage = `Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}% (${progressObj.transferred}/${progressObj.total})`; + // eslint-disable-next-line no-console + console.log(logMessage); + this.status = `DOWNLOADING - ${progressObj.percent}%`; + this.progress = progressObj.percent; + this.size = progressObj.total; + this.lastChecked = Date.now(); + }); + autoUpdater.on('update-downloaded', (info) => { + this.status = 'DOWNLOADED (Restart to apply update)'; + this.lastChecked = Date.now(); + }); + } + + getStatus(): AppUpdaterStatus { + return { + status: this.status, + version: this.version, + lastChecked: this.lastChecked, + progress: this.progress, + size: this.size, + error: this.error, + }; + } +} diff --git a/desktop-app/src/main/main.ts b/desktop-app/src/main/main.ts index 69574d3e8..b3d371a27 100644 --- a/desktop-app/src/main/main.ts +++ b/desktop-app/src/main/main.ts @@ -10,12 +10,7 @@ */ import path from 'path'; import { app, BrowserWindow, shell, screen, ipcMain } from 'electron'; -import { autoUpdater } from 'electron-updater'; -import log from 'electron-log'; -import { - setupTitlebar, - attachTitlebarToWindow, -} from 'custom-electron-titlebar/main'; +import { setupTitlebar } from 'custom-electron-titlebar/main'; import cli from './cli'; import { PROTOCOL } from '../common/constants'; import MenuBuilder from './menu'; @@ -36,6 +31,7 @@ import { WebPermissionHandlers } from './web-permissions'; import { initHttpBasicAuthHandlers } from './http-basic-auth'; import { initAppMetaHandlers } from './app-meta'; import { openUrl } from './protocol-handler'; +import { AppUpdater } from './app-updater'; let windowShownOnOpen = false; @@ -53,14 +49,6 @@ let urlToOpen: string | undefined = cli.input[0]?.includes('electronmon') ? undefined : cli.input[0]; -export default class AppUpdater { - constructor() { - log.transports.file.level = 'info'; - autoUpdater.logger = log; - autoUpdater.checkForUpdatesAndNotify(); - } -} - let mainWindow: BrowserWindow | null = null; initAppMetaHandlers(); @@ -217,7 +205,9 @@ const createWindow = async () => { mainWindow = null; }); - const menuBuilder = new MenuBuilder(mainWindow); + const appUpdater = new AppUpdater(); + + const menuBuilder = new MenuBuilder(mainWindow, appUpdater); menuBuilder.buildMenu(); // Open urls in the user's browser @@ -239,9 +229,6 @@ const createWindow = async () => { ipcMain.on('stop-watcher', async () => { await stopWatchFiles(); }); - // Remove this if your app does not use auto updates - // eslint-disable-next-line - new AppUpdater(); }; app.on('open-url', async (event, url) => { diff --git a/desktop-app/src/main/menu/help.ts b/desktop-app/src/main/menu/help.ts index 9d4d8bd5f..cbb05155a 100644 --- a/desktop-app/src/main/menu/help.ts +++ b/desktop-app/src/main/menu/help.ts @@ -1,106 +1,83 @@ import { BrowserWindow, - clipboard, - dialog, MenuItemConstructorOptions, + ipcMain, shell, } from 'electron'; -import path from 'path'; -import { getEnvironmentInfo, getPackageJson } from '../util'; +import { EnvironmentInfo, getEnvironmentInfo } from '../util'; +import { IPC_MAIN_CHANNELS } from '../../common/constants'; +import { AppUpdater, AppUpdaterStatus } from '../app-updater'; -const aboutOnClick = () => { - const iconPath = path.join(__dirname, '../resources/icons/64x64.png'); - const title = 'Responsively'; - const { description } = getPackageJson(); - const { - appVersion, - electronVersion, - chromeVersion, - nodeVersion, - v8Version, - osInfo, - } = getEnvironmentInfo(); +export interface AboutDialogArgs { + environmentInfo: EnvironmentInfo; + updaterStatus: AppUpdaterStatus; +} - const usefulInfo = `Version: ${appVersion}\nElectron: ${electronVersion}\nChrome: ${chromeVersion}\nNode.js: ${nodeVersion}\nV8: ${v8Version}\nOS: ${osInfo}`; - const detail = description ? `${description}\n\n${usefulInfo}` : usefulInfo; - let buttons = ['OK', 'Copy']; - let cancelId = 0; - let defaultId = 1; - if (process.platform === 'linux') { - buttons = ['Copy', 'OK']; - cancelId = 1; - defaultId = 0; - } - dialog - .showMessageBox(BrowserWindow.getAllWindows()[0], { - type: 'none', - buttons, - title, - message: title, - detail, - noLink: true, - icon: iconPath, - cancelId, - defaultId, - }) - .then(({ response }) => { - if (response === defaultId) { - clipboard.writeText(usefulInfo, 'clipboard'); - } - return null; - }) - .catch((err) => { - console.error('Error opening about', err); - }); -}; +export const subMenuHelp = ( + mainWindow: BrowserWindow, + appUpdater: AppUpdater +): MenuItemConstructorOptions => { + const environmentInfo = getEnvironmentInfo(); + ipcMain.handle('get-about-info', async (_): Promise => { + return { + environmentInfo, + updaterStatus: appUpdater.getStatus(), + }; + }); -export const subMenuHelp: MenuItemConstructorOptions = { - label: 'Help', - submenu: [ - { - label: 'Learn More', - click() { - shell.openExternal('https://responsively.app'); + return { + label: 'Help', + submenu: [ + { + label: 'Learn More', + click() { + shell.openExternal('https://responsively.app'); + }, + }, + { + label: 'Open Source', + click() { + shell.openExternal( + 'https://github.com/responsively-org/responsively-app' + ); + }, + }, + { + label: 'Join Discord', + click() { + shell.openExternal('https://responsively.app/join-discord/'); + }, }, - }, - { - label: 'Open Source', - click() { - shell.openExternal( - 'https://github.com/responsively-org/responsively-app' - ); + { + label: 'Search Issues', + click() { + shell.openExternal( + 'https://github.com/responsively-org/responsively-app/issues' + ); + }, }, - }, - { - label: 'Join Discord', - click() { - shell.openExternal('https://responsively.app/join-discord/'); + { + label: 'Sponsor Responsively', + click() { + shell.openExternal( + 'https://responsively.app/sponsor?utm_source=app&utm_medium=menu&utm_campaign=sponsor' + ); + }, }, - }, - { - label: 'Search Issues', - click() { - shell.openExternal( - 'https://github.com/responsively-org/responsively-app/issues' - ); + { + type: 'separator', }, - }, - { - label: 'Sponsor Responsively', - click() { - shell.openExternal( - 'https://responsively.app/sponsor?utm_source=app&utm_medium=menu&utm_campaign=sponsor' - ); + { + label: 'About', + accelerator: 'F1', + click: () => { + mainWindow.webContents.send(IPC_MAIN_CHANNELS.OPEN_ABOUT_DIALOG, { + environmentInfo, + updaterStatus: appUpdater.getStatus(), + }); + }, }, - }, - { - type: 'separator', - }, - { - label: 'About', - accelerator: 'F1', - click: aboutOnClick, - }, - ], + ], + }; }; diff --git a/desktop-app/src/main/menu/index.ts b/desktop-app/src/main/menu/index.ts index e108ffe66..93651633e 100644 --- a/desktop-app/src/main/menu/index.ts +++ b/desktop-app/src/main/menu/index.ts @@ -1,6 +1,7 @@ import { app, Menu, BrowserWindow, MenuItemConstructorOptions } from 'electron'; import { subMenuHelp } from './help'; import { getViewMenu } from './view'; +import { AppUpdater } from '../app-updater'; interface DarwinMenuItemConstructorOptions extends MenuItemConstructorOptions { selector?: string; @@ -14,8 +15,11 @@ export interface ReloadArgs { export default class MenuBuilder { mainWindow: BrowserWindow; - constructor(mainWindow: BrowserWindow) { + appUpdater: AppUpdater; + + constructor(mainWindow: BrowserWindow, appUpdater: AppUpdater) { this.mainWindow = mainWindow; + this.appUpdater = appUpdater; } buildMenu(): Menu { @@ -118,7 +122,7 @@ export default class MenuBuilder { subMenuEdit, getViewMenu(this.mainWindow), subMenuWindow, - subMenuHelp, + subMenuHelp(this.mainWindow, this.appUpdater), ]; } @@ -141,7 +145,7 @@ export default class MenuBuilder { ], }, getViewMenu(this.mainWindow), - subMenuHelp, + subMenuHelp(this.mainWindow, this.appUpdater), ]; } } diff --git a/desktop-app/src/main/native-functions/index.ts b/desktop-app/src/main/native-functions/index.ts index b7d0e4d2a..135dfe7d4 100644 --- a/desktop-app/src/main/native-functions/index.ts +++ b/desktop-app/src/main/native-functions/index.ts @@ -1,4 +1,4 @@ -import { ipcMain, nativeTheme, webContents } from 'electron'; +import { clipboard, ipcMain, nativeTheme, webContents } from 'electron'; export interface DisableDefaultWindowOpenHandlerArgs { webContentsId: number; @@ -38,4 +38,8 @@ export const initNativeFunctionHandlers = () => { return { done: true }; } ); + + ipcMain.handle('copy-to-clipboard', async (_, arg: string): Promise => { + clipboard.writeText(arg); + }); }; diff --git a/desktop-app/src/main/util.ts b/desktop-app/src/main/util.ts index a5a924c6d..0cb01d03c 100644 --- a/desktop-app/src/main/util.ts +++ b/desktop-app/src/main/util.ts @@ -59,7 +59,16 @@ export const getPackageJson = () => { return {}; }; -export const getEnvironmentInfo = () => { +export interface EnvironmentInfo { + appVersion: string; + electronVersion: string; + chromeVersion: string; + nodeVersion: string; + v8Version: string; + osInfo: string; +} + +export const getEnvironmentInfo = (): EnvironmentInfo => { const pkg = getPackageJson(); const appVersion = pkg.version || 'Unknown'; const electronVersion = process.versions.electron || 'Unknown'; diff --git a/desktop-app/src/renderer/AppContent.tsx b/desktop-app/src/renderer/AppContent.tsx index 13b3abcc7..88a63adbb 100644 --- a/desktop-app/src/renderer/AppContent.tsx +++ b/desktop-app/src/renderer/AppContent.tsx @@ -12,6 +12,7 @@ import DeviceManager from './components/DeviceManager'; import KeyboardShortcutsManager from './components/KeyboardShortcutsManager'; import { ReleaseNotes } from './components/ReleaseNotes'; import { Sponsorship } from './components/Sponsorship'; +import { AboutDialog } from './components/AboutDialog'; if ((navigator as any).userAgentData.platform === 'Windows') { import('./titlebar-styles.css'); @@ -51,6 +52,7 @@ const AppContent = () => { + ); diff --git a/desktop-app/src/renderer/components/AboutDialog/index.tsx b/desktop-app/src/renderer/components/AboutDialog/index.tsx new file mode 100644 index 000000000..f994be5d5 --- /dev/null +++ b/desktop-app/src/renderer/components/AboutDialog/index.tsx @@ -0,0 +1,170 @@ +import { useEffect, useRef, useState } from 'react'; +import { IPC_MAIN_CHANNELS } from 'common/constants'; +import { AboutDialogArgs } from 'main/menu/help'; +import TimeAgo from 'javascript-time-ago'; +import en from 'javascript-time-ago/locale/en'; +import Icon from '../../assets/img/logo.png'; +import Modal from '../Modal'; +import Button from '../Button'; + +TimeAgo.addLocale(en); + +const timeAgo = new TimeAgo('en-US'); + +export const AboutDialog = () => { + const [show, setShow] = useState(false); + const [args, setArgs] = useState(null); + const intervalRef = useRef(null); + const clearIntervalIfAvailable = () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + }; + + useEffect(() => { + const onOpenAboutDialog = (arg: AboutDialogArgs) => { + setShow(true); + setArgs(arg); + }; + window.electron.ipcRenderer.on( + IPC_MAIN_CHANNELS.OPEN_ABOUT_DIALOG, + onOpenAboutDialog + ); + return () => { + window.electron.ipcRenderer.removeListener( + IPC_MAIN_CHANNELS.OPEN_ABOUT_DIALOG, + onOpenAboutDialog + ); + }; + }, []); + + useEffect(() => { + if (show) { + intervalRef.current = setInterval(() => { + window.electron.ipcRenderer + .invoke('get-about-info') + .then((arg: AboutDialogArgs) => { + setArgs(arg); + + return arg; + }) + .catch((err) => { + // eslint-disable-next-line no-console + console.error('Error while refreshing about info', err); + }); + }, 1000); + } else { + clearIntervalIfAvailable(); + } + }, [show]); + + return ( + setShow(false)} + title={ +
+ Logo +
Responsively App
+
+ A dev-tool that aids faster and precise responsive web development. +
+
+ } + > +
+
+
Versions
+
+
+ App + + v{args?.environmentInfo.appVersion} + +
+
+ Electron + + v{args?.environmentInfo.electronVersion} + +
+
+ Chrome + + v{args?.environmentInfo.chromeVersion} + +
+
+ Node.js + + v{args?.environmentInfo.nodeVersion} + +
+
+ V8 + + v{args?.environmentInfo.v8Version} + +
+
+ OS + {args?.environmentInfo.osInfo} +
+
+
+ +
+
+
+
Update Status
+
+
+ Status + + {args?.updaterStatus.status.toLocaleLowerCase()} + +
+ {args?.updaterStatus.version != null ? ( +
+ New Version + {args?.updaterStatus.version} +
+ ) : null} + {args?.updaterStatus.error != null ? ( +
+ Error + + {args?.updaterStatus.error.message} + +
+ ) : null} +
+ Last Checked + + {args?.updaterStatus.lastChecked != null + ? timeAgo.format(args?.updaterStatus.lastChecked) + : 'NA'} + +
+
+
+ +
+
+ ); +}; diff --git a/desktop-app/yarn.lock b/desktop-app/yarn.lock index 9f1d7ed07..47d0e9834 100644 --- a/desktop-app/yarn.lock +++ b/desktop-app/yarn.lock @@ -4157,10 +4157,10 @@ builder-util-runtime@9.1.1: debug "^4.3.4" sax "^1.2.4" -builder-util-runtime@9.2.1: - version "9.2.1" - resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz#3184dcdf7ed6c47afb8df733813224ced4f624fd" - integrity sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA== +builder-util-runtime@9.2.3: + version "9.2.3" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz#0a82c7aca8eadef46d67b353c638f052c206b83c" + integrity sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw== dependencies: debug "^4.3.4" sax "^1.2.4" @@ -5783,19 +5783,19 @@ electron-to-chromium@^1.4.601: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz#5790a70aaa96de232501b56e14b64d17aff93988" integrity sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw== -electron-updater@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.1.1.tgz#4ede9b560936957b2b87181736f98b1621fa26fd" - integrity sha512-IBT3zJ4yO5UZMF2gOTC9HrlmG4OYSRtOiHKzNAShJvfuicdx6UaXoa6AvhcTxdx6zf/rJyFMRBISS9jhVwTfow== +electron-updater@^6.1.8: + version "6.1.8" + resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.1.8.tgz#17637bca165322f4e526b13c99165f43e6f697d8" + integrity sha512-hhOTfaFAd6wRHAfUaBhnAOYc+ymSGCWJLtFkw4xJqOvtpHmIdNHnXDV9m1MHC+A6q08Abx4Ykgyz/R5DGKNAMQ== dependencies: - builder-util-runtime "9.2.1" + builder-util-runtime "9.2.3" fs-extra "^10.1.0" js-yaml "^4.1.0" lazy-val "^1.0.5" lodash.escaperegexp "^4.1.2" lodash.isequal "^4.5.0" semver "^7.3.8" - typed-emitter "^2.1.0" + tiny-typed-emitter "^2.1.0" electron@^27.0.4: version "27.0.4" @@ -8498,6 +8498,13 @@ jake@^10.8.5: filelist "^1.0.1" minimatch "^3.0.4" +javascript-time-ago@^2.5.10: + version "2.5.10" + resolved "https://registry.yarnpkg.com/javascript-time-ago/-/javascript-time-ago-2.5.10.tgz#f1c188496203a1aa37318276703a2ebd2941c1b1" + integrity sha512-EUxp4BP74QH8xiYHyeSHopx1XhMMJ9qEX4rcBdFtpVWmKRdzpxbNzz2GSbuekZr5wt0rmLehuyp0PE34EAJT9g== + dependencies: + relative-time-format "^1.1.6" + jest-changed-files@^28.1.3: version "28.1.3" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.1.3.tgz#d9aeee6792be3686c47cb988a8eaf82ff4238831" @@ -11846,6 +11853,11 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== +relative-time-format@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/relative-time-format/-/relative-time-format-1.1.6.tgz#724a5fbc3794b8e0471b6b61419af2ce699eb9f1" + integrity sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ== + remark-parse@^10.0.0: version "10.0.1" resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-10.0.1.tgz#6f60ae53edbf0cf38ea223fe643db64d112e0775" @@ -12102,7 +12114,7 @@ rx@4.1.0: resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" integrity sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug== -rxjs@^7.5.2, rxjs@^7.8.0, rxjs@^7.8.1: +rxjs@^7.8.0, rxjs@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== @@ -13241,6 +13253,11 @@ tiny-glob@^0.2.9: globalyzer "0.1.0" globrex "^0.1.2" +tiny-typed-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5" + integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA== + tmp-promise@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" @@ -13510,13 +13527,6 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typed-emitter@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/typed-emitter/-/typed-emitter-2.1.0.tgz#ca78e3d8ef1476f228f548d62e04e3d4d3fd77fb" - integrity sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA== - optionalDependencies: - rxjs "^7.5.2" - typescript@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"