diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 00000000..bc4bf977 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,31 @@ +env: + browser: true + es2021: true + node: true +extends: + - 'eslint:recommended' + - 'plugin:react/recommended' + - 'plugin:@typescript-eslint/recommended' +parser: '@typescript-eslint/parser' +parserOptions: + ecmaFeatures: + jsx: true + ecmaVersion: 12 + sourceType: module +plugins: + - react + - '@typescript-eslint' +rules: + indent: + - error + - tab + linebreak-style: + - error + - unix + quotes: + - error + - single + semi: + - error + - always + '@typescript-eslint/ban-ts-comment': off diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..7948cecf --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +patreon: ottomated +custom: https://www.paypal.me/ottomated diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..155cf322 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,43 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[BUG REPORT]' +labels: 'bug' +assignees: '' +--- +# You MUST use this template or your issue will be deleted. + +# Pre-Flight Checklist +Please use this checklist to avoid spamming: + +1. [ ] I am not asking a question => use the [Discord](https://discord.gg/9mwuVNA) if you are +2. [ ] I am using the Steam version of Among Us +3. [ ] I have tried to use an [alternative voice server](https://status.crewl.ink/) +4. [ ] I have checked everyone in my lobby is on the same CrewLink server +5. [ ] I have used `Ctrl+R` on the CrewLink app when I can't hear some people (this is a known issue) +6. [ ] I have checked that the CrewLink server I'm using is up to date +7. [ ] I have a screenshot of any errors +8. [ ] I have checked that someone else has not reported this using the [search bar](https://github.com/ottomated/CrewLink/issues?q=is%3Aissue) + +**Describe the bug** + + +**To Reproduce** +Steps to reproduce the behaviour: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behaviour** + + +**Screenshots** + + +**Desktop (please complete the following information):** + - OS: [e.g. Windows 10] + - Version: [e.g. 1.1.5] + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..a0440d3b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: YOU MUST USE ONE OF THE ABOVE TEMPLATES OR YOUR ISSUE WILL BE DELETED! + url: https://github.com/ottomated/CrewLink/issues/new/choose + about: This is to prevent duplicate issues and spam. + - name: I need help + url: https://discord.gg/9mwuVNA + about: Please ask and answer questions here. + - name: Security issue + url: https://discord.gg/9mwuVNA + about: Please report security vulnerabilities directly to Ottomated#9999. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..fef99b67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,30 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '[FEATURE]' +labels: 'enhancement' +assignees: '' +--- + +# Pre-Flight Checklist +Please use this checklist to avoid spamming: + +1. [ ] I am not asking a question => use the [Discord](https://discord.gg/9mwuVNA) if you are +2. [ ] I have checked that someone else has not suggested this using the [search bar](https://github.com/ottomated/CrewLink/issues?q=is%3Aissue) +3. [ ] My feature request is not one of the **commonly suggested features**: +- Adjustable voice radius +- Mobile support +- Linux / MacOS support +- Itch.io support + +**Is your feature request related to a problem? Please describe.** + + +**Describe the solution you'd like** + + +**Describe alternatives you've considered** + + +**Additional context** + diff --git a/README.md b/README.md index a8f55c3c..c302baa0 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,13 @@ ยท INSTALLATION INSTRUCTIONS

+

+ DONATE TO THE PROJECT + (all donations will be used for server costs or paying for college) +

- ## Table of Contents diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..040c986c --- /dev/null +++ b/TODO.md @@ -0,0 +1,40 @@ +# TODO before CrewLink 2.0.0 + +## Server + +- [ ] Migrate from socket.io to a raw websocket connection. Ensure it auto-reconnects. +- [ ] Request offsets over the websocket connection, to keep the number of open sockets down. +- [ ] Move the default server to a better host. +- [ ] Rewrite all error messages to be even more human-readable. +- [ ] Integrate an official server list into the client. +- [ ] Detect the reason *why* the server can't provide offsets: i.e. Among Us just updated, it's an old version of Among Us, the server hasn't updated, etc. + +### Stretch + +- [ ] Distribute the server load, with a centralized matchmaking database. +- [ ] Re-write the server in Rust. + +## Voice / WebRTC + +- [ ] Add a microphone boost slider. +- [ ] Add a speaker adjustment slider. +- [ ] Add individual adjustment sliders to each of the players. +- [ ] Handle all RTC errors to make it unnecessary to ever re-open an RTC connection. +- [ ] Detect reason for RTC failure: NAT type, etc? +- [ ] Re-enable all `navigator.getUserMedia` functions that can be re-enabled with autoGainControl kicking in. +- [ ] Move all player-to-player communication logic to RTC data channels, versus sending them over the websocket. + +### Stretch + +- [ ] Implement an optional TURN server. + +## Game Reader + +- [ ] Fix unicode characters in player names +- [ ] Indicate to the user when it can't read memory properly. Example: screen displays `MENU` while in lobby due to some misplaced offset. +- [ ] Don't use the Unity Analytics file to read the game version. Use either a hash of the GameAssembly dll, or DMA it from the process. + +### Stretch + +- [ ] Move away from DMA and towards a different method. Probably network packet sniffing? Maybe DLL injection? +- [ ] Add itch.io and Linux/Mac support. This will be easiest with packet sniffing. diff --git a/package.json b/package.json index 99b1197e..0a1b8026 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "crewlink", - "version": "1.1.2", + "version": "1.1.6", "license": "GPL-3.0-or-later", "description": "Free, open, Among Us proximity voice chat", "repository": { @@ -18,22 +18,23 @@ }, "scripts": { "postinstall": "electron-builder install-app-deps", + "lint": "eslint src", "dev": "electron-webpack dev", "compile": "electron-webpack", "dist": "yarn compile && electron-builder --win --x64", "publish": "yarn compile && electron-builder --win --x64 --publish always" }, "dependencies": { + "ajv": "^6.12.6", "analyser-frequency-average": "^1.0.0", - "audio-activity": "^1.0.0", "axios": "^0.21.0", - "color": "^3.1.3", "cross-spawn": "^7.0.3", "electron-store": "^6.0.1", "electron-updater": "^4.3.5", + "electron-window-state": "^5.0.3", "iohook": "git://github.com/ykhwong/iohook", "jsondiffpatch": "^0.4.1", - "memoryjs": "https://github.com/Rob--/memoryjs", + "memoryjs": "git://github.com/Rob--/memoryjs", "react": "^17.0.1", "react-dom": "^17.0.1", "react-spinners-kit": "^1.9.1", @@ -56,10 +57,14 @@ "@types/simple-peer": "^9.6.1", "@types/socket.io-client": "^1.4.34", "@types/webpack-env": "^1.15.3", + "@typescript-eslint/eslint-plugin": "^4.9.1", + "@typescript-eslint/parser": "^4.9.1", "electron": "9.3.3", "electron-builder": "^22.9.1", "electron-webpack": "^2.8.2", "electron-webpack-ts": "^4.0.1", + "eslint": "^7.15.0", + "eslint-plugin-react": "^7.21.5", "ts-interface-builder": "^0.2.2", "typescript": "^4.0.3", "webpack": "~4.42.1" diff --git a/src/common/AmongUsState.ts b/src/common/AmongUsState.ts new file mode 100644 index 00000000..17d7301d --- /dev/null +++ b/src/common/AmongUsState.ts @@ -0,0 +1,29 @@ + +export interface AmongUsState { + gameState: GameState; + oldGameState: GameState; + lobbyCode: string; + players: Player[]; +} +export interface Player { + ptr: number; + id: number; + name: string; + colorId: number; + hatId: number; + petId: number; + skinId: number; + disconnected: boolean; + isImpostor: boolean; + isDead: boolean; + taskPtr: number; + objectPtr: number; + isLocal: boolean; + + x: number; + y: number; + inVent: boolean; +} +export enum GameState { + LOBBY, TASKS, DISCUSSION, MENU, UNKNOWN +} diff --git a/src/common/ISettings.d.ts b/src/common/ISettings.d.ts new file mode 100644 index 00000000..e54031a3 --- /dev/null +++ b/src/common/ISettings.d.ts @@ -0,0 +1,16 @@ + +export interface ISettings { + alwaysOnTop: boolean; + microphone: string; + speaker: string; + pushToTalk: boolean; + serverURL: string; + pushToTalkShortcut: string; + deafenShortcut: string; + offsets: { + version: string; + data: string; + }, + hideCode: boolean; + enableSpatialAudio: boolean; +} diff --git a/src/main/GameReader.ts b/src/main/GameReader.ts index 2ddc244e..c9e30145 100644 --- a/src/main/GameReader.ts +++ b/src/main/GameReader.ts @@ -1,41 +1,33 @@ -import { DataType, findModule, getProcesses, ModuleObject, openProcess, ProcessObject, readBuffer, readMemory as readMemoryRaw } from "memoryjs"; -import * as Struct from 'structron'; +import { DataType, findModule, getProcesses, ModuleObject, openProcess, ProcessObject, readBuffer, readMemory as readMemoryRaw } from 'memoryjs'; +import Struct from 'structron'; import patcher from '../patcher'; -import { IOffsets } from "./hook"; +import { GameState, AmongUsState, Player } from '../common/AmongUsState'; +import { IOffsets } from './IOffsets'; -export interface AmongUsState { - gameState: GameState; - oldGameState: GameState; - lobbyCode: string; - players: Player[]; + +interface ValueType { + read(buffer: BufferSource, offset: number): T; + SIZE: number; } -export interface Player { - ptr: number; + +interface PlayerReport { + objectPtr: number; id: number; - name: string; - colorId: number; - hatId: number; - petId: number; - skinId: number; - disconnected: boolean; - isImpostor: boolean; - isDead: boolean; + name: number; + color: number; + hat: number; + pet: number; + skin: number; + disconnected: number; + impostor: number; + dead: number; taskPtr: number; - objectPtr: number; - isLocal: boolean; - - x: number; - y: number; - inVent: boolean; -} -export enum GameState { - LOBBY, TASKS, DISCUSSION, MENU, UNKNOWN } export default class GameReader { - reply: Function; + reply: (event: string, ...args: unknown[]) => void; offsets: IOffsets; - PlayerStruct: any; + PlayerStruct: Struct; menuUpdateTimer = 20; lastPlayerPtr = 0; @@ -47,10 +39,10 @@ export default class GameReader { amongUs: ProcessObject | null = null; gameAssembly: ModuleObject | null = null; - gameCode: string = 'MENU'; + gameCode = 'MENU'; checkProcessOpen(): void { - let processOpen = getProcesses().find(p => p.szExeFile === 'Among Us.exe'); + const processOpen = getProcesses().find(p => p.szExeFile === 'Among Us.exe'); if (!this.amongUs && processOpen) { // If process just opened try { this.amongUs = openProcess('Among Us.exe'); @@ -70,44 +62,44 @@ export default class GameReader { this.checkProcessOpen(); if (this.amongUs !== null && this.gameAssembly !== null) { let state = GameState.UNKNOWN; - let meetingHud = this.readMemory('pointer', this.gameAssembly.modBaseAddr, this.offsets.meetingHud); - let meetingHud_cachePtr = meetingHud === 0 ? 0 : this.readMemory('uint32', meetingHud, this.offsets.meetingHudCachePtr); - let meetingHudState = meetingHud_cachePtr === 0 ? 4 : this.readMemory('int', meetingHud, this.offsets.meetingHudState, 4); - let gameState = this.readMemory('int', this.gameAssembly.modBaseAddr, this.offsets.gameState); + const meetingHud = this.readMemory('pointer', this.gameAssembly.modBaseAddr, this.offsets.meetingHud); + const meetingHud_cachePtr = meetingHud === 0 ? 0 : this.readMemory('uint32', meetingHud, this.offsets.meetingHudCachePtr); + const meetingHudState = meetingHud_cachePtr === 0 ? 4 : this.readMemory('int', meetingHud, this.offsets.meetingHudState, 4); + const gameState = this.readMemory('int', this.gameAssembly.modBaseAddr, this.offsets.gameState); switch (gameState) { - case 0: - state = GameState.MENU; - this.exileCausesEnd = false; - break; - case 1: - case 3: + case 0: + state = GameState.MENU; + this.exileCausesEnd = false; + break; + case 1: + case 3: + state = GameState.LOBBY; + this.exileCausesEnd = false; + break; + default: + if (this.exileCausesEnd) state = GameState.LOBBY; - this.exileCausesEnd = false; - break; - default: - if (this.exileCausesEnd) - state = GameState.LOBBY; - else if (meetingHudState < 4) - state = GameState.DISCUSSION; - else - state = GameState.TASKS; - break; + else if (meetingHudState < 4) + state = GameState.DISCUSSION; + else + state = GameState.TASKS; + break; } - let allPlayersPtr = this.readMemory('ptr', this.gameAssembly.modBaseAddr, this.offsets.allPlayersPtr) & 0xffffffff; - let allPlayers = this.readMemory('ptr', allPlayersPtr, this.offsets.allPlayers); - let playerCount = this.readMemory('int' as 'int', allPlayersPtr, this.offsets.playerCount); + const allPlayersPtr = this.readMemory('ptr', this.gameAssembly.modBaseAddr, this.offsets.allPlayersPtr) & 0xffffffff; + const allPlayers = this.readMemory('ptr', allPlayersPtr, this.offsets.allPlayers); + const playerCount = this.readMemory('int' as const, allPlayersPtr, this.offsets.playerCount); let playerAddrPtr = allPlayers + this.offsets.playerAddrPtr; - let players = []; + const players = []; - let exiledPlayerId = this.readMemory('byte', this.gameAssembly.modBaseAddr, this.offsets.exiledPlayerId); + const exiledPlayerId = this.readMemory('byte', this.gameAssembly.modBaseAddr, this.offsets.exiledPlayerId); let impostors = 0, crewmates = 0; for (let i = 0; i < Math.min(playerCount, 10); i++) { - let { address, last } = this.offsetAddress(playerAddrPtr, this.offsets.player.offsets); - let playerData = readBuffer(this.amongUs.handle, address + last, this.offsets.player.bufferLength); - let player = this.parsePlayer(address + last, playerData); + const { address, last } = this.offsetAddress(playerAddrPtr, this.offsets.player.offsets); + const playerData = readBuffer(this.amongUs.handle, address + last, this.offsets.player.bufferLength); + const player = this.parsePlayer(address + last, playerData); playerAddrPtr += 4; players.push(player); @@ -134,14 +126,14 @@ export default class GameReader { } this.lastPlayerPtr = allPlayers; - let inGame = state === GameState.TASKS || state === GameState.DISCUSSION || state === GameState.LOBBY; + const inGame = state === GameState.TASKS || state === GameState.DISCUSSION || state === GameState.LOBBY; let newGameCode = 'MENU'; if (state === GameState.LOBBY) { newGameCode = this.readString( this.readMemory('int32', this.gameAssembly.modBaseAddr, this.offsets.gameCode) ); if (newGameCode) { - let split = newGameCode.split('\r\n'); + const split = newGameCode.split('\r\n'); if (split.length === 2) { newGameCode = split[1]; } else { @@ -157,13 +149,13 @@ export default class GameReader { } if (newGameCode) this.gameCode = newGameCode; - let newState = { + const newState = { lobbyCode: this.gameCode, players, gameState: state, oldGameState: this.oldGameState }; - let patch = patcher.diff(this.lastState, newState); + const patch = patcher.diff(this.lastState, newState); if (patch) { try { this.reply('gameState', newState); @@ -176,66 +168,66 @@ export default class GameReader { } } - constructor(reply: Function, offsets: IOffsets) { + constructor(reply: (event: string, ...args: unknown[]) => void, offsets: IOffsets) { this.reply = reply; this.offsets = offsets; this.PlayerStruct = new Struct(); - for (let member of offsets.player.struct) { - if (member.type === 'SKIP') { - this.PlayerStruct = this.PlayerStruct.addMember(Struct.TYPES.SKIP(member.skip!), member.name); + for (const member of offsets.player.struct) { + if (member.type === 'SKIP' && member.skip) { + this.PlayerStruct = this.PlayerStruct.addMember(Struct.TYPES.SKIP(member.skip), member.name); } else { - this.PlayerStruct = this.PlayerStruct.addMember(Struct.TYPES[member.type], member.name); + this.PlayerStruct = this.PlayerStruct.addMember(Struct.TYPES[member.type] as ValueType, member.name); } } } readMemory(dataType: DataType, address: number, offsets: number[], defaultParam?: T): T { + if (!this.amongUs) return defaultParam as T; if (address === 0) return defaultParam as T; - let { address: addr, last } = this.offsetAddress(address, offsets); + const { address: addr, last } = this.offsetAddress(address, offsets); if (addr === 0) return defaultParam as T; return readMemoryRaw( - this.amongUs!.handle, + this.amongUs.handle, addr + last, dataType ); } offsetAddress(address: number, offsets: number[]): { address: number, last: number } { + if (!this.amongUs) throw 'Among Us not open? Weird error'; address = address & 0xffffffff; for (let i = 0; i < offsets.length - 1; i++) { - address = readMemoryRaw(this.amongUs!.handle, address + offsets[i], 'uint32'); + address = readMemoryRaw(this.amongUs.handle, address + offsets[i], 'uint32'); if (address == 0) break; } - let last = offsets.length > 0 ? offsets[offsets.length - 1] : 0; + const last = offsets.length > 0 ? offsets[offsets.length - 1] : 0; return { address, last }; } readString(address: number): string { - if (address === 0) return ''; - let length = readMemoryRaw(this.amongUs!.handle, address + 0x8, 'int'); - // console.log(length); - // console.log("reading string", length, length << 1); - let buffer = readBuffer(this.amongUs!.handle, address + 0xC, length << 1); + if (address === 0 || !this.amongUs) return ''; + const length = readMemoryRaw(this.amongUs.handle, address + 0x8, 'int'); + const buffer = readBuffer(this.amongUs.handle, address + 0xC, length << 1); return buffer.toString('utf8').replace(/\0/g, ''); } parsePlayer(ptr: number, buffer: Buffer): Player { - let { data } = this.PlayerStruct.report(buffer, 0, {}); + const { data } = this.PlayerStruct.report(buffer, 0, {}); - let isLocal = this.readMemory('int', data.objectPtr, this.offsets.player.isLocal) !== 0; + const isLocal = this.readMemory('int', data.objectPtr, this.offsets.player.isLocal) !== 0; - let positionOffsets = isLocal ? [ + const positionOffsets = isLocal ? [ this.offsets.player.localX, this.offsets.player.localY ] : [ - this.offsets.player.remoteX, - this.offsets.player.remoteY - ]; + this.offsets.player.remoteX, + this.offsets.player.remoteY + ]; - let x = this.readMemory('float', data.objectPtr, positionOffsets[0]); - let y = this.readMemory('float', data.objectPtr, positionOffsets[1]); + const x = this.readMemory('float', data.objectPtr, positionOffsets[0]); + const y = this.readMemory('float', data.objectPtr, positionOffsets[1]); return { ptr, id: data.id, diff --git a/src/main/IOffsets.d.ts b/src/main/IOffsets.d.ts new file mode 100644 index 00000000..9d730e22 --- /dev/null +++ b/src/main/IOffsets.d.ts @@ -0,0 +1,28 @@ + +export interface IOffsets { + meetingHud: number[]; + meetingHudCachePtr: number[]; + meetingHudState: number[]; + gameState: number[]; + allPlayersPtr: number[]; + allPlayers: number[]; + playerCount: number[]; + playerAddrPtr: number; + exiledPlayerId: number[]; + gameCode: number[]; + player: { + isLocal: number[]; + localX: number[]; + localY: number[]; + remoteX: number[]; + remoteY: number[]; + bufferLength: number; + offsets: number[]; + inVent: number[]; + struct: { + type: 'INT' | 'INT_BE' | 'UINT' | 'UINT_BE' | 'SHORT' | 'SHORT_BE' | 'USHORT' | 'USHORT_BE' | 'FLOAT' | 'CHAR' | 'BYTE' | 'SKIP'; + skip?: number; + name: string; + }[]; + }; +} diff --git a/src/main/hook-ti.ts b/src/main/hook-ti.ts index 2e3d3288..fbd10bc7 100644 --- a/src/main/hook-ti.ts +++ b/src/main/hook-ti.ts @@ -1,38 +1,38 @@ /** * This module was automatically generated by `ts-interface-builder` */ -import * as t from "ts-interface-checker"; +import * as t from 'ts-interface-checker'; // tslint:disable:object-literal-key-quotes export const IOffsets = t.iface([], { - "meetingHud": t.array("number"), - "meetingHudCachePtr": t.array("number"), - "meetingHudState": t.array("number"), - "gameState": t.array("number"), - "allPlayersPtr": t.array("number"), - "allPlayers": t.array("number"), - "playerCount": t.array("number"), - "playerAddrPtr": "number", - "exiledPlayerId": t.array("number"), - "gameCode": t.array("number"), - "player": t.iface([], { - "isLocal": t.array("number"), - "localX": t.array("number"), - "localY": t.array("number"), - "remoteX": t.array("number"), - "remoteY": t.array("number"), - "offsets": t.array("number"), - "inVent": t.array("number"), - "bufferLength": "number", - "struct": t.array(t.iface([], { - "type": "string", - "skip": t.opt("number"), - "name": "string", - })), - }), + 'meetingHud': t.array('number'), + 'meetingHudCachePtr': t.array('number'), + 'meetingHudState': t.array('number'), + 'gameState': t.array('number'), + 'allPlayersPtr': t.array('number'), + 'allPlayers': t.array('number'), + 'playerCount': t.array('number'), + 'playerAddrPtr': 'number', + 'exiledPlayerId': t.array('number'), + 'gameCode': t.array('number'), + 'player': t.iface([], { + 'isLocal': t.array('number'), + 'localX': t.array('number'), + 'localY': t.array('number'), + 'remoteX': t.array('number'), + 'remoteY': t.array('number'), + 'offsets': t.array('number'), + 'inVent': t.array('number'), + 'bufferLength': 'number', + 'struct': t.array(t.iface([], { + 'type': 'string', + 'skip': t.opt('number'), + 'name': 'string', + })), + }), }); const exportedTypeSuite: t.ITypeSuite = { - IOffsets, + IOffsets, }; export default exportedTypeSuite; diff --git a/src/main/hook.ts b/src/main/hook.ts index a8644743..7c40ba13 100644 --- a/src/main/hook.ts +++ b/src/main/hook.ts @@ -7,51 +7,35 @@ import spawn from 'cross-spawn'; import GameReader from './GameReader'; import iohook from 'iohook'; import Store from 'electron-store'; -import { ISettings } from '../renderer/Settings'; +import { ISettings } from '../common/ISettings'; import axios, { AxiosError } from 'axios'; import { createCheckers } from 'ts-interface-checker'; import TI from './hook-ti'; import { existsSync, readFileSync } from 'fs'; +import { IOffsets } from './IOffsets'; const { IOffsets } = createCheckers(TI); -const store = new Store(); - -export interface IOffsets { - meetingHud: number[]; - meetingHudCachePtr: number[]; - meetingHudState: number[]; - gameState: number[]; - allPlayersPtr: number[]; - allPlayers: number[]; - playerCount: number[]; - playerAddrPtr: number; - exiledPlayerId: number[]; - gameCode: number[]; - player: { - isLocal: number[]; - localX: number[]; - localY: number[]; - remoteX: number[]; - remoteY: number[]; - bufferLength: number; - offsets: number[]; - inVent: number[]; - struct: { - type: string; - skip?: number; - name: string; - }[]; - } +interface IOHookEvent { + type: string + keychar?: number + keycode?: number + rawcode?: number + button?: number + clicks?: number + x?: number + y?: number } +const store = new Store(); + async function loadOffsets(event: Electron.IpcMainEvent): Promise { - const valuesFile = resolve((process.env.LOCALAPPDATA || '') + "Low", 'Innersloth/Among Us/Unity/6b8b0d91-4a20-4a00-a3e4-4da4a883a5f0/Analytics/values'); - let version: string = ''; + const valuesFile = resolve((process.env.LOCALAPPDATA || '') + 'Low', 'Innersloth/Among Us/Unity/6b8b0d91-4a20-4a00-a3e4-4da4a883a5f0/Analytics/values'); + let version = ''; if (existsSync(valuesFile)) { try { - let json = JSON.parse(readFileSync(valuesFile, 'utf8')); + const json = JSON.parse(readFileSync(valuesFile, 'utf8')); version = json.app_ver; } catch (e) { console.error(e); @@ -59,48 +43,56 @@ async function loadOffsets(event: Electron.IpcMainEvent): Promise { readingGame = true; // Register key events - iohook.on('keydown', (ev: any) => { - let shortcutKey = store.get('pushToTalkShortcut'); + iohook.on('keydown', (ev: IOHookEvent) => { + const shortcutKey = store.get('pushToTalkShortcut'); if (keyCodeMatches(shortcutKey as K, ev)) { event.reply('pushToTalk', true); } }); - iohook.on('keyup', (ev: any) => { - let shortcutKey = store.get('pushToTalkShortcut'); + iohook.on('keyup', (ev: IOHookEvent) => { + const shortcutKey = store.get('pushToTalkShortcut'); if (keyCodeMatches(shortcutKey as K, ev)) { event.reply('pushToTalk', false); } @@ -134,7 +126,7 @@ ipcMain.on('start', async (event) => { iohook.start(); // Read game memory - gameReader = new GameReader(event.reply, offsets); + gameReader = new GameReader(event.reply as (event: string, ...args: unknown[]) => void, offsets); ipcMain.on('initState', (event: Electron.IpcMainEvent) => { event.returnValue = gameReader.lastState; @@ -142,7 +134,7 @@ ipcMain.on('start', async (event) => { const frame = () => { gameReader.loop(); setTimeout(frame, 1000 / 20); - } + }; frame(); } else if (gameReader) { gameReader.amongUs = null; @@ -167,7 +159,7 @@ const keycodeMap = { }; type K = keyof typeof keycodeMap; -function keyCodeMatches(key: K, ev: any): boolean { +function keyCodeMatches(key: K, ev: IOHookEvent): boolean { if (keycodeMap[key]) return keycodeMap[key] === ev.keycode; else if (key.length === 1) @@ -201,9 +193,9 @@ ipcMain.on('openGame', () => { dialog.showErrorBox('Error', 'Please launch the game through Steam.'); } } -}) +}); ipcMain.on('relaunch', () => { app.relaunch(); app.quit(); -}); \ No newline at end of file +}); diff --git a/src/main/index.ts b/src/main/index.ts index dc0eaa0f..b271c0f6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,116 +1,109 @@ -'use strict' +'use strict'; import { autoUpdater } from 'electron-updater'; -import { app, BrowserWindow, ipcMain } from 'electron'; -import * as path from 'path' -import { format as formatUrl } from 'url' +import { app, BrowserWindow } from 'electron'; +import windowStateKeeper from 'electron-window-state'; +import { join as joinPath } from 'path'; +import { format as formatUrl } from 'url'; import './hook'; -const isDevelopment = process.env.NODE_ENV !== 'production' +const isDevelopment = process.env.NODE_ENV !== 'production'; // global reference to mainWindow (necessary to prevent window from being garbage collected) let mainWindow: BrowserWindow | null; app.commandLine.appendSwitch('disable-pinch'); +function createMainWindow() { + const mainWindowState = windowStateKeeper({}); + + const window = new BrowserWindow({ + width: 250, + height: 350, + maxWidth: 250, + minWidth: 250, + maxHeight: 350, + minHeight: 350, + x: mainWindowState.x, + y: mainWindowState.y, + + resizable: false, + frame: false, + fullscreenable: false, + maximizable: false, + transparent: true, + webPreferences: { + nodeIntegration: true, + enableRemoteModule: true, + webSecurity: false + } + }); + + mainWindowState.manage(window); + + if (isDevelopment) { + window.webContents.openDevTools(); + } + + if (isDevelopment) { + window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}?version=${autoUpdater.currentVersion.version}`); + } + else { + window.loadURL(formatUrl({ + pathname: joinPath(__dirname, 'index.html'), + protocol: 'file', + query: { + version: autoUpdater.currentVersion.version + }, + slashes: true + })); + } + + window.on('closed', () => { + mainWindow = null; + }); + + window.webContents.on('devtools-opened', () => { + window.focus(); + setImmediate(() => { + window.focus(); + }); + }); + + return window; +} + const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); } else { - // app.disableHardwareAcceleration(); autoUpdater.checkForUpdatesAndNotify(); - app.on('second-instance', (event, commandLine, workingDirectory) => { + app.on('second-instance', () => { // Someone tried to run a second instance, we should focus our window. if (mainWindow) { - if (mainWindow.isMinimized()) mainWindow.restore() - mainWindow.focus() + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); } - }) - - - function createMainWindow() { - const window = new BrowserWindow({ - width: 250, - height: 350, - resizable: false, - frame: false, - fullscreenable: false, - maximizable: false, - transparent: true, - webPreferences: { - nodeIntegration: true, - enableRemoteModule: true, - webSecurity: false - } - }); - - if (isDevelopment) { - window.webContents.openDevTools() - } - - if (isDevelopment) { - window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}?version=${autoUpdater.currentVersion.version}`) - } - else { - window.loadURL(formatUrl({ - pathname: path.join(__dirname, 'index.html'), - protocol: 'file', - query: { - version: autoUpdater.currentVersion.version - }, - slashes: true - })) - } - - window.on('closed', () => { - mainWindow = null - }) - - window.webContents.on('devtools-opened', () => { - window.focus() - setImmediate(() => { - window.focus() - }) - }) + }); - return window - } // quit application when all windows are closed app.on('window-all-closed', () => { // on macOS it is common for applications to stay open until the user explicitly quits if (process.platform !== 'darwin') { - app.quit() + app.quit(); } - }) + }); app.on('activate', () => { // on macOS it is common to re-create a window even after all windows have been closed if (mainWindow === null) { - mainWindow = createMainWindow() + mainWindow = createMainWindow(); } - }) + }); // create main BrowserWindow when electron is ready app.on('ready', () => { mainWindow = createMainWindow(); }); - - // ipcMain.on('alwaysOnTop', (event, onTop: boolean) => { - // if (mainWindow) { - // mainWindow.setAlwaysOnTop(onTop, 'floating', 1); - // mainWindow.setVisibleOnAllWorkspaces(true); - // mainWindow.setFullScreenable(false); - // } - // }); - - ipcMain.on('shortcut', (event, val) => { - event.returnValue = false; - // console.log('register', val); - // globalShortcut.unregisterAll(); - // event.returnValue = globalShortcut.register(val!, () => { - // console.log("push-to-talk"); - // }) - - }); } \ No newline at end of file diff --git a/src/main/memoryjs.d.ts b/src/main/memoryjs.d.ts index 53e26be6..42679025 100644 --- a/src/main/memoryjs.d.ts +++ b/src/main/memoryjs.d.ts @@ -1,6 +1,6 @@ declare module 'memoryjs' { - type Callback = (error: any, value: T) => void; + type Callback = (error: unknown, value: T) => void; // Processes @@ -18,6 +18,7 @@ declare module 'memoryjs' { export function openProcess(identifier: string, callback?: Callback): ProcessObject; export function getProcesses(callback?: Callback): ProcessObject[]; + export function getProcesses(processId: number, callback?: Callback): ModuleObject[]; // Modules @@ -30,19 +31,18 @@ declare module 'memoryjs' { } export function findModule(identifier: string, processId: number, callback?: Callback): ModuleObject; - export function getProcesses(processId: number, callback?: Callback): ModuleObject[]; // Memory export type Vector3 = { x: number, y: number, z: number }; export type Vector4 = { x: number, y: number, z: number, w: number }; - export type DataType = "byte" | "int" | "int32" | "uint32" | "int64" | "uint64" | "dword" | "short" | "long" | "float" | "double" | "bool" | "boolean" | "ptr" | "pointer" | "str" | "string" | "vec3" | "vector3" | "vec4" | "vector4"; + export type DataType = 'byte' | 'int' | 'int32' | 'uint32' | 'int64' | 'uint64' | 'dword' | 'short' | 'long' | 'float' | 'double' | 'bool' | 'boolean' | 'ptr' | 'pointer' | 'str' | 'string' | 'vec3' | 'vector3' | 'vec4' | 'vector4'; export function readMemory(handle: number, address: number, dataType: DataType, callback?: Callback): T; export function readBuffer(handle: number, address: number, size: number, callback?: Callback): Buffer; - export function writeMemory(handle: number, address: number, value: any, dataType: DataType): void; + export function writeMemory(handle: number, address: number, value: T, dataType: DataType): void; export function writeBuffer(handle: number, address: number, buffer: Buffer): void; @@ -58,7 +58,7 @@ declare module 'memoryjs' { export const T_FLOAT = 0x6; - export type FunctionArg = { type: number, value: any }; + export type FunctionArg = { type: number, value: unknown }; export interface FunctionResult { returnValue: T; @@ -67,6 +67,4 @@ declare module 'memoryjs' { export function callFunction(handle: number, args: FunctionArg[], returnType: number, address: number): FunctionResult; -} - -declare module 'structron'; \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/structron.d.ts b/src/main/structron.d.ts new file mode 100644 index 00000000..3e730932 --- /dev/null +++ b/src/main/structron.d.ts @@ -0,0 +1,55 @@ +declare module 'structron' { + type ReportOptions = { + monitorUsage: boolean; + } + + class Report { + constructor(buffer: BufferSource, options: ReportOptions); + toString(): string; + getUsage(): number; + data: T; + } + interface ValueType { + read(buffer: BufferSource, offset: number): T; + SIZE: number; + } + + type Rule = (...params: unknown[]) => (dataObj: unknown, buffer: BufferSource) => boolean; + class Struct implements ValueType { + + constructor(name?: string); + + addMember(type: ValueType, name: string): this; + addArray(type: ValueType, name: string, offsetMemberName: string, countMemberName: string, relative?: boolean): this; + addReference(type: ValueType, name: string, memberName: string, relative?: boolean): this; + addRule(rule: Rule): this; + read(buffer: BufferSource, offset: number, report?: Report): T; + report(buffer: BufferSource, offset: number, options: Partial): Report; + validate(buffer: BufferSource, offset?: number): boolean; + getOffsetByName(name: string): number; + + get SIZE(): number; + + static RULES: { + EQUAL: Rule; + } + + static TYPES: { + INT: ValueType; + INT_BE: ValueType; + UINT: ValueType; + UINT_BE: ValueType; + SHORT: ValueType; + SHORT_BE: ValueType; + USHORT: ValueType; + USHORT_BE: ValueType; + FLOAT: ValueType; + CHAR: ValueType; + BYTE: ValueType; + STRING(length: number, encoding: string | 'ascii'): ValueType; + NULL_TERMINATED_STRING(encoding: string | 'ascii'): ValueType; + SKIP(length: number): ValueType; + } + } + export = Struct; +} \ No newline at end of file diff --git a/src/patcher.ts b/src/patcher.ts index 189ad6d4..49741797 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -1,5 +1,5 @@ -import { create } from "jsondiffpatch"; -import { Player } from "./main/GameReader"; +import { create } from 'jsondiffpatch'; +import { Player } from './common/AmongUsState'; export default create({ objectHash: (obj: Player) => obj.ptr, diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 4e85c00e..c242cdd0 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,25 +1,20 @@ -import React, { createContext, useEffect, useReducer, useState } from 'react'; +import React, { useEffect, useReducer, useState } from 'react'; import ReactDOM from 'react-dom'; import Voice from './Voice'; import Menu from './Menu'; import { ipcRenderer, remote } from 'electron'; -import { AmongUsState } from '../main/GameReader'; -import Settings, { ISettings, settingsReducer } from './Settings'; +import { AmongUsState } from '../common/AmongUsState'; +import Settings, { settingsReducer } from './Settings'; +import { GameStateContext, SettingsContext } from './contexts'; let appVersion = ''; if (typeof window !== 'undefined' && window.location) { - let query = new URLSearchParams(window.location.search.substring(1)); + const query = new URLSearchParams(window.location.search.substring(1)); appVersion = (' v' + query.get('version')) || ''; } -enum AppState { MENU, VOICE }; - -export const GameStateContext = createContext({} as AmongUsState); -export const SettingsContext = createContext<[ISettings, React.Dispatch<{ - type: "set" | "setOne"; - action: ISettings | [string, any]; -}>]>(null as any); +enum AppState { MENU, VOICE } function App() { const [state, setState] = useState(AppState.MENU); @@ -31,7 +26,7 @@ function App() { microphone: 'Default', speaker: 'Default', pushToTalk: false, - serverIP: '54.193.94.35:9736', + serverURL: 'https://crewl.ink', pushToTalkShortcut: 'V', deafenShortcut: 'RControl', offsets: { @@ -39,7 +34,7 @@ function App() { data: '' }, hideCode: false, - stereoInLobby: true + enableSpatialAudio: true }); useEffect(() => { @@ -61,23 +56,23 @@ function App() { ipcRenderer.once('started', () => { if (shouldInit) setGameState(ipcRenderer.sendSync('initState')); - }) + }); return () => { ipcRenderer.off('gameOpen', onOpen); ipcRenderer.off('error', onError); ipcRenderer.off('gameState', onState); - } + }; }, []); let page; switch (state) { - case AppState.MENU: - page = ; - break; - case AppState.VOICE: - page = ; - break; + case AppState.MENU: + page = ; + break; + case AppState.VOICE: + page = ; + break; } return ( @@ -101,7 +96,7 @@ function App() { {page} - ) + ); } ReactDOM.render(, document.getElementById('app')); \ No newline at end of file diff --git a/src/renderer/Avatar.tsx b/src/renderer/Avatar.tsx index 3566ded3..2d0ab280 100644 --- a/src/renderer/Avatar.tsx +++ b/src/renderer/Avatar.tsx @@ -1,19 +1,12 @@ -import React, { useEffect, useRef } from "react"; -import Color from 'color'; -import { Player } from "../main/GameReader"; -// @ts-ignore -import alive from '../../static/alive.png'; -// @ts-ignore -import dead from '../../static/dead.png'; -import { backLayerHats, hatOffsets, hats, skins } from "./cosmetics"; -import Tooltip from "react-tooltip-lite"; +import React, { useEffect, useRef } from 'react'; +import { Player } from '../common/AmongUsState'; +import { backLayerHats, hatOffsets, hats, skins, players } from './cosmetics'; +import Tooltip from 'react-tooltip-lite'; export interface CanvasProps { src: string; hat: number; skin: number; - color: string; - shadow: string; isAlive: boolean; } @@ -26,24 +19,10 @@ export interface AvatarProps { deafened?: boolean; } -const playerColors = [ - ['#C51111', '#7A0838',], - ['#132ED1', '#09158E',], - ['#117F2D', '#0A4D2E',], - ['#ED54BA', '#AB2BAD',], - ['#EF7D0D', '#B33E15',], - ['#F5F557', '#C38823',], - ['#3F474E', '#1E1F26',], - ['#D6E0F0', '#8394BF',], - ['#6B2FBB', '#3B177C',], - ['#71491E', '#5E2615',], - ['#38FEDC', '#24A8BE',], - ['#50EF39', '#15A742',] -]; - -export default function Avatar({ talking, deafened, borderColor, isAlive, player, size }: AvatarProps) { - let color = playerColors[player.colorId]; - if (!color) color = playerColors[0]; +const Avatar: React.FC = function ({ talking, deafened, borderColor, isAlive, player, size }: AvatarProps) { + const status = isAlive ? 'alive' : 'dead'; + let image = players[status][player.colorId]; + if (!image) image = players[status][0]; return (
- + { deafened && @@ -61,9 +40,9 @@ export default function Avatar({ talking, deafened, borderColor, isAlive, player ); -} +}; -function Canvas({ src, hat, skin, color, shadow, isAlive }: CanvasProps) { +function Canvas({ src, hat, skin, isAlive }: CanvasProps) { const canvas = useRef(null); const hatImg = useRef(null); const skinImg = useRef(null); @@ -72,16 +51,26 @@ function Canvas({ src, hat, skin, color, shadow, isAlive }: CanvasProps) { useEffect(() => { (async () => { if (!canvas.current || !image.current || !hatImg.current || !skinImg.current) return; - const ctx = canvas.current.getContext('2d')!; + const ctx = canvas.current.getContext('2d'); + if (!ctx) return; if (!image.current.complete) { - await new Promise(r => image!.current!.onload = r); + await new Promise(r => { + if (image?.current) + image.current.onload = r; + }); } if (!hatImg.current.complete) { - await new Promise(r => hatImg!.current!.onload = r); + await new Promise(r => { + if (hatImg?.current) + hatImg.current.onload = r; + }); } if (!skinImg.current.complete) { - await new Promise(r => skinImg!.current!.onload = r); + await new Promise(r => { + if (skinImg?.current) + skinImg.current.onload = r; + }); } canvas.current.width = image.current.width; @@ -90,27 +79,16 @@ function Canvas({ src, hat, skin, color, shadow, isAlive }: CanvasProps) { ctx.drawImage(image.current, 0, 0); function drawHat() { - let hatY = 17 - hatOffsets[hat]; - ctx.drawImage(hatImg.current!, 0, hatY > 0 ? 0 : -hatY, hatImg.current!.width, hatImg.current!.height, canvas.current!.width / 2 - hatImg.current!.width / 2 + 2, Math.max(hatY, 0), hatImg.current!.width, hatImg.current!.height); + if (!ctx || !hatImg.current || !canvas.current) return; + const hatY = 17 - hatOffsets[hat]; + ctx.drawImage(hatImg.current, + 0, hatY > 0 ? 0 : -hatY, + hatImg.current.width, hatImg.current.height, + canvas.current.width / 2 - hatImg.current.width / 2 + 2, Math.max(hatY, 0), + hatImg.current.width, hatImg.current.height + ); } - - let data = ctx.getImageData(0, 0, image.current.width, image.current.height); - for (let i = 0; i < data.data.length; i += 4) { - let r = data.data[i], - g = data.data[i + 1], - b = data.data[i + 2]; - if (r !== 255 || g !== 255 || b !== 255) { - let pixelColor = Color('#000000') - .mix(Color(shadow), b / 255) - .mix(Color(color), r / 255) - .mix(Color('#9acad5'), g / 255); - data.data[i] = pixelColor.red(); - data.data[i + 1] = pixelColor.green(); - data.data[i + 2] = pixelColor.blue(); - } - } - ctx.putImageData(data, 0, 0); if (isAlive) { if (backLayerHats.has(hat)) ctx.globalCompositeOperation = 'destination-over'; @@ -121,7 +99,7 @@ function Canvas({ src, hat, skin, color, shadow, isAlive }: CanvasProps) { } })(); - }, [src, color, shadow, hat, skin, isAlive]); + }, [src, hat, skin, isAlive]); return ( <> @@ -130,5 +108,7 @@ function Canvas({ src, hat, skin, color, shadow, isAlive }: CanvasProps) { - ) -} \ No newline at end of file + ); +} + +export default Avatar; diff --git a/src/renderer/Footer.tsx b/src/renderer/Footer.tsx index 9676a347..b7bdc7c6 100644 --- a/src/renderer/Footer.tsx +++ b/src/renderer/Footer.tsx @@ -1,7 +1,7 @@ -import { shell } from "electron"; -import React from "react"; +import { shell } from 'electron'; +import React from 'react'; -export default function Footer() { +const Footer: React.FC = function () { return (
Made by Ottomated @@ -16,6 +16,11 @@ export default function Footer() { }}> + { + shell.openExternal('https://twitter.com/Ottomated_'); + }}> + + { shell.openExternal('https://paypal.me/ottomated'); }}> @@ -56,5 +61,7 @@ export default function Footer() {
- ) -} \ No newline at end of file + ); +}; + +export default Footer; diff --git a/src/renderer/Menu.tsx b/src/renderer/Menu.tsx index 1556e0b6..f96791e1 100644 --- a/src/renderer/Menu.tsx +++ b/src/renderer/Menu.tsx @@ -1,10 +1,14 @@ -import React from "react"; -import { ImpulseSpinner as Spinner } from "react-spinners-kit"; +import React from 'react'; +import { ImpulseSpinner as Spinner } from 'react-spinners-kit'; import { ipcRenderer } from 'electron'; import './css/menu.css'; -import Footer from "./Footer"; +import Footer from './Footer'; -export default function Menu({ errored }: { errored: boolean }) { +export interface MenuProps { + errored: boolean +} + +const Menu: React.FC = function ({ errored }: MenuProps) { return (
@@ -12,7 +16,11 @@ export default function Menu({ errored }: { errored: boolean }) { <> Error - Make sure that the Voice Server is correct in the settings and you are using the latest version of Among Us. If there was a recent update, CrewLink might not work for a few days. +
    +
  1. Use a different Voice Server in settings
  2. +
  3. Update Among Us
  4. +
  5. Wait for 24 hours after Among Us updates
  6. +
); -} \ No newline at end of file +}; + +export default Menu; \ No newline at end of file diff --git a/src/renderer/MicrophoneSoundBar.tsx b/src/renderer/MicrophoneSoundBar.tsx index 79e5e0ed..3cdd6a4d 100644 --- a/src/renderer/MicrophoneSoundBar.tsx +++ b/src/renderer/MicrophoneSoundBar.tsx @@ -1,52 +1,54 @@ -import React, { useContext, useEffect, useState } from 'react' -import { SettingsContext } from './App'; - -const TestMicrophoneButton: React.FC = () => { - const [{ microphone }] = useContext(SettingsContext) - const [error, setError] = useState(false) - const [rms, setRms] = useState(0) - - useEffect(() => { - setError(false) - - const ctx = new AudioContext() - const processor = ctx.createScriptProcessor(2048, 1, 1) - processor.connect(ctx.destination) - - const minUpdateRate = 50; - let lastRefreshTime = 0; - - const handleProcess = (event: AudioProcessingEvent) => { - // limit update frequency - if ( event.timeStamp - lastRefreshTime < minUpdateRate ) { - return; - } - - // update last refresh time - lastRefreshTime = event.timeStamp; - - const input = event.inputBuffer.getChannelData(0) - const total = input.reduce((acc, val) => acc + Math.abs(val), 0) - const rms = Math.sqrt(total / input.length) - setRms(rms) - } - - navigator.mediaDevices.getUserMedia({ audio: { deviceId: microphone ?? 'default' } }) - .then((stream) => { - const src = ctx.createMediaStreamSource(stream) - src.connect(processor) - processor.addEventListener('audioprocess', handleProcess) - }) - .catch(() => setError(true)) - - return () => { - processor.removeEventListener('audioprocess', handleProcess) - } - }, [microphone]) - - if (error) return

Could not connect to microphone

- - return
+import React, { useEffect, useState } from 'react'; + +interface TestMicProps { + microphone: string } -export default TestMicrophoneButton +const TestMicrophoneButton: React.FC = function ({ microphone } : TestMicProps) { + const [error, setError] = useState(false); + const [rms, setRms] = useState(0); + + useEffect(() => { + setError(false); + + const ctx = new AudioContext(); + const processor = ctx.createScriptProcessor(2048, 1, 1); + processor.connect(ctx.destination); + + const minUpdateRate = 50; + let lastRefreshTime = 0; + + const handleProcess = (event: AudioProcessingEvent) => { + // limit update frequency + if (event.timeStamp - lastRefreshTime < minUpdateRate) { + return; + } + + // update last refresh time + lastRefreshTime = event.timeStamp; + + const input = event.inputBuffer.getChannelData(0); + const total = input.reduce((acc, val) => acc + Math.abs(val), 0); + const rms = Math.min(50, Math.sqrt(total / input.length)); + setRms(rms); + }; + + navigator.mediaDevices.getUserMedia({ audio: { deviceId: microphone ?? 'default' } }) + .then((stream) => { + const src = ctx.createMediaStreamSource(stream); + src.connect(processor); + processor.addEventListener('audioprocess', handleProcess); + }) + .catch(() => setError(true)); + + return () => { + processor.removeEventListener('audioprocess', handleProcess); + }; + }, [microphone]); + + if (error) return

Could not connect to microphone

; + + return
; +}; + +export default TestMicrophoneButton; diff --git a/src/renderer/Settings.tsx b/src/renderer/Settings.tsx index 78d1008d..8ebdeb1e 100644 --- a/src/renderer/Settings.tsx +++ b/src/renderer/Settings.tsx @@ -1,13 +1,53 @@ import Store from 'electron-store'; -import React, { useContext, useEffect, useReducer, useState } from "react"; -import { SettingsContext } from "./App"; +import React, { useContext, useEffect, useReducer, useState } from 'react'; +import { SettingsContext } from './contexts'; +import Ajv from 'ajv'; import './css/settings.css'; import MicrophoneSoundBar from './MicrophoneSoundBar'; import TestSpeakersButton from './TestSpeakersButton'; +import { ISettings } from '../common/ISettings'; const keys = new Set(['Space', 'Backspace', 'Delete', 'Enter', 'Up', 'Down', 'Left', 'Right', 'Home', 'End', 'PageUp', 'PageDown', 'Escape', 'LControl', 'LShift', 'LAlt', 'RControl', 'RShift', 'RAlt']); +const validateURL = new Ajv({ + allErrors: true, + format: 'full' +}).compile({ + type: 'string', + format: 'uri' +}); + const store = new Store({ + migrations: { + '1.1.3': store => { + const serverIP = store.get('serverIP'); + if (typeof serverIP === 'string') { + const serverURL = `http://${serverIP}`; + if (validateURL(serverURL)) { + store.set('serverURL', serverURL); + } else { + console.warn('Error while parsing the old serverIP property. Default URL will be used instead.'); + } + + // @ts-ignore: Old serverIP property no longer exists in ISettings + store.delete('serverIP'); + } + }, + '1.1.5': store => { + const serverURL = store.get('serverURL'); + if (serverURL === 'http://54.193.94.35:9736') { + store.set('serverURL', 'https://crewl.ink'); + } + }, + '1.1.6': store => { + const enableSpatialAudio = store.get('stereoInLobby'); + if (typeof enableSpatialAudio === 'boolean') { + store.set('enableSpatialAudio', enableSpatialAudio); + } + // @ts-ignore + store.delete('stereoInLobby'); + } + }, schema: { alwaysOnTop: { type: 'boolean', @@ -25,9 +65,10 @@ const store = new Store({ type: 'boolean', default: false, }, - serverIP: { + serverURL: { type: 'string', - default: '54.193.94.35:9736' + default: 'https://crewl.ink', + format: 'uri' }, pushToTalkShortcut: { type: 'string', @@ -54,44 +95,35 @@ const store = new Store({ type: 'boolean', default: false }, - stereoInLobby: { + enableSpatialAudio: { type: 'boolean', default: true } } }); +store.onDidChange('serverURL', (newUrl) => { + if (newUrl === 'http://54.193.94.35:9736') { + store.set('serverURL', 'https://crewl.ink'); + } +}); + export interface SettingsProps { open: boolean; - onClose: any; + onClose: () => void; } -export interface ISettings { - alwaysOnTop: boolean; - microphone: string; - speaker: string; - pushToTalk: boolean; - serverIP: string; - pushToTalkShortcut: string; - deafenShortcut: string; - offsets: { - version: string; - data: string; - }, - hideCode: boolean; - stereoInLobby: boolean; -} export const settingsReducer = (state: ISettings, action: { - type: 'set' | 'setOne', action: [string, any] | ISettings + type: 'set' | 'setOne', action: [string, unknown] | ISettings }): ISettings => { if (action.type === 'set') return action.action as ISettings; - let v = (action.action as [string, any]); + const v = (action.action as [string, unknown]); store.set(v[0], v[1]); return { ...state, [v[0]]: v[1] }; -} +}; interface MediaDevice { id: string; @@ -99,8 +131,36 @@ interface MediaDevice { label: string; } -export default function Settings({ open, onClose }: SettingsProps) { +type URLInputProps = { + initialURL: string, + onValidURL: (url: string) => void +}; + +function URLInput({ initialURL, onValidURL }: URLInputProps) { + const [isValidURL, setURLValid] = useState(true); + const [currentURL, setCurrentURL] = useState(initialURL); + + useEffect(() => { + setCurrentURL(initialURL); + }, [initialURL]); + function onChange(event: React.ChangeEvent) { + let eventValue = event.target.value.trim(); + + setCurrentURL(eventValue); + + if (validateURL(eventValue)) { + setURLValid(true); + onValidURL(eventValue); + } else { + setURLValid(false); + } + } + + return ; +} + +const Settings: React.FC = function ({ open, onClose }: SettingsProps) { const [settings, setSettings] = useContext(SettingsContext); const [unsavedCount, setUnsavedCount] = useState(0); const unsaved = unsavedCount > 2; @@ -113,7 +173,7 @@ export default function Settings({ open, onClose }: SettingsProps) { useEffect(() => { setUnsavedCount(s => s + 1); - }, [settings.microphone, settings.speaker, settings.serverIP]); + }, [settings.microphone, settings.speaker, settings.serverURL, settings.enableSpatialAudio]); const [devices, setDevices] = useState([]); const [_, updateDevices] = useReducer((state) => state + 1, 0); @@ -123,11 +183,11 @@ export default function Settings({ open, onClose }: SettingsProps) { .map(d => { let label = d.label; if (d.deviceId === 'default') { - label = "Default"; + label = 'Default'; } else { - let match = /\((.+?)\)/.exec(d.label); + const match = /(.+?)\)/.exec(d.label); if (match && match[1]) - label = match[1]; + label = match[1] + ')'; } return { id: d.deviceId, @@ -172,15 +232,6 @@ export default function Settings({ open, onClose }: SettingsProps) { - {/*
{ - ipcRenderer.send('alwaysOnTop', !settings.alwaysOnTop); - setSettings({ - type: 'setOne', - action: ['alwaysOnTop', !settings.alwaysOnTop] - }); - }}> - Always on Top -
*/}
@@ -197,10 +248,8 @@ export default function Settings({ open, onClose }: SettingsProps) { )) } + {open && }
- - -
+ {open && }
- -
setSettings({ type: 'setOne', action: ['pushToTalk', false] @@ -244,10 +292,12 @@ export default function Settings({ open, onClose }: SettingsProps) {
- setSettings({ - type: 'setOne', - action: ['serverIP', ev.target.value] - })} value={settings.serverIP} /> + { + setSettings({ + type: 'setOne', + action: ['serverURL', url] + }); + }} />
setSettings({ type: 'setOne', @@ -258,11 +308,18 @@ export default function Settings({ open, onClose }: SettingsProps) {
setSettings({ type: 'setOne', - action: ['stereoInLobby', !settings.stereoInLobby] + action: ['enableSpatialAudio', !settings.enableSpatialAudio] })}> - - + + +
+
+ + Exit to apply changes +
- -} \ No newline at end of file + ; +}; + +export default Settings; diff --git a/src/renderer/TestSpeakersButton.tsx b/src/renderer/TestSpeakersButton.tsx index 44109092..70eecf7a 100644 --- a/src/renderer/TestSpeakersButton.tsx +++ b/src/renderer/TestSpeakersButton.tsx @@ -1,20 +1,26 @@ -import React, { useContext } from 'react' -import { SettingsContext } from './App'; +import React from 'react'; +// @ts-ignore +import chime from '../../static/chime.mp3'; +import { ExtendedAudioElement } from './Voice'; -const TestSpeakersButton = () => { - const [{ speaker }] = useContext(SettingsContext) +interface TestSpeakersProps { + speaker: string +} - const testSpeakers = () => { - const audio = new Audio(); - audio.src = "https://downloads.derock.dev/chime.mp3" +const TestSpeakersButton: React.FC = ({ speaker }: TestSpeakersProps) => { + const testSpeakers = () => { + const audio = new Audio() as ExtendedAudioElement; + audio.src = chime; - if (speaker.toLowerCase() !== 'default') - (audio as any).setSinkId(speaker) + if (speaker.toLowerCase() !== 'default') + audio.setSinkId(speaker); - audio.play(); - } + audio.play(); + }; - return -} + return ( + + ); +}; -export default TestSpeakersButton +export default TestSpeakersButton; diff --git a/src/renderer/Voice.tsx b/src/renderer/Voice.tsx index 6e7c94b2..ebaf4e08 100644 --- a/src/renderer/Voice.tsx +++ b/src/renderer/Voice.tsx @@ -1,19 +1,21 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import io, { Socket } from 'socket.io-client'; import Avatar from './Avatar'; -import { GameStateContext, SettingsContext } from './App'; -import { AmongUsState, GameState, Player } from '../main/GameReader'; +import { GameStateContext, SettingsContext } from './contexts'; +import { AmongUsState, GameState, Player } from '../common/AmongUsState'; import Peer from 'simple-peer'; import { ipcRenderer, remote } from 'electron'; import VAD from './vad'; -import { ISettings } from './Settings'; +import { ISettings } from '../common/ISettings'; + +export interface ExtendedAudioElement extends HTMLAudioElement { + setSinkId: (sinkId: string) => Promise; +} interface PeerConnections { [peer: string]: Peer.Instance; } -interface InCall { - [peer: string]: boolean; -} + interface AudioElements { [peer: string]: { element: HTMLAudioElement; @@ -21,9 +23,6 @@ interface AudioElements { pan: PannerNode; }; } -interface AudioListeners { - [peer: string]: any; -} interface SocketIdMap { [socketId: string]: number; @@ -31,7 +30,7 @@ interface SocketIdMap { interface ConnectionStuff { socket: typeof Socket; - stream: MediaStream; + stream?: MediaStream; pushToTalk: boolean; deafened: boolean; } @@ -44,19 +43,6 @@ interface OtherDead { [playerId: number]: boolean; // isTalking } -// function clamp(number: number, min: number, max: number): number { -// if (min > max) { -// let tmp = max; -// max = min; -// min = tmp; -// } -// return Math.max(min, Math.min(number, max)); -// } - -// function mapNumber(n: number, oldLow: number, oldHigh: number, newLow: number, newHigh: number): number { -// return clamp((n - oldLow) / (oldHigh - oldLow) * (newHigh - newLow) + newLow, newLow, newHigh); -// } - function calculateVoiceAudio(state: AmongUsState, settings: ISettings, me: Player, other: Player, gain: GainNode, pan: PannerNode): void { const audioContext = pan.context; pan.positionZ.setValueAtTime(-0.5, audioContext.currentTime); @@ -64,7 +50,7 @@ function calculateVoiceAudio(state: AmongUsState, settings: ISettings, me: Playe (other.x - me.x), (other.y - me.y) ]; - if (state.gameState === GameState.DISCUSSION || (state.gameState === GameState.LOBBY && !settings.stereoInLobby)) { + if (state.gameState === GameState.DISCUSSION || (state.gameState === GameState.LOBBY && !settings.enableSpatialAudio)) { panPos = [0, 0]; } if (isNaN(panPos[0])) panPos[0] = 999; @@ -90,21 +76,19 @@ function calculateVoiceAudio(state: AmongUsState, settings: ISettings, me: Playe pan.positionX.setValueAtTime(panPos[0], audioContext.currentTime); pan.positionY.setValueAtTime(panPos[1], audioContext.currentTime); } else if (state.gameState === GameState.TASKS) { - // const distance = Math.sqrt(Math.pow(me.x - other.x, 2) + Math.pow(me.y - other.y, 2)); gain.gain.value = 1; - // gain.gain.value = mapNumber(distance, 0, 2.66, 1, 0); pan.positionX.setValueAtTime(panPos[0], audioContext.currentTime); pan.positionY.setValueAtTime(panPos[1], audioContext.currentTime); } else { gain.gain.value = 0; } - if (gain.gain.value === 1 && Math.sqrt(Math.pow(me.x - other.x, 2) + Math.pow(me.y - other.y, 2)) > 7) { + if (gain.gain.value === 1 && Math.sqrt(Math.pow(panPos[0], 2) + Math.pow(panPos[1], 2)) > 7) { gain.gain.value = 0; } } -export default function Voice() { +const Voice: React.FC = function () { const [settings] = useContext(SettingsContext); const settingsRef = useRef(settings); const gameState = useContext(GameStateContext); @@ -120,23 +104,26 @@ export default function Voice() { const [deafenedState, setDeafened] = useState(false); const [connected, setConnected] = useState(false); + // Handle pushToTalk, if set useEffect(() => { if (!connectionStuff.current.stream) return; connectionStuff.current.stream.getAudioTracks()[0].enabled = !settings.pushToTalk; connectionStuff.current.pushToTalk = settings.pushToTalk; }, [settings.pushToTalk]); + // Add settings to settingsRef useEffect(() => { settingsRef.current = settings; }, [settings]); + // Set dead player data useEffect(() => { if (gameState.gameState === GameState.LOBBY) { setOtherDead({}); } else if (gameState.gameState !== GameState.TASKS) { if (!gameState.players) return; setOtherDead(old => { - for (let player of gameState.players) { + for (const player of gameState.players) { old[player.id] = player.isDead || player.disconnected; } return { ...old }; @@ -144,11 +131,15 @@ export default function Voice() { } }, [gameState.gameState]); - // const [audioContext] = useState(() => new AudioContext()); - const connectionStuff = useRef({ pushToTalk: settings.pushToTalk, deafened: false } as any); + const connectionStuff = useRef>({ + pushToTalk: settings.pushToTalk, + deafened: false, + }); + + // BIG ASS BLOB - Handle audio useEffect(() => { // Connect to voice relay server - connectionStuff.current.socket = io(`ws://${settings.serverIP}`, { transports: ['websocket'] }); + connectionStuff.current.socket = io(settings.serverURL, { transports: ['websocket'] }); const { socket } = connectionStuff.current; socket.on('connect', () => { @@ -159,13 +150,30 @@ export default function Voice() { }); // Initialize variables - let audioListener: any; - let audio: boolean | MediaTrackConstraints = true; - + let audioListener: { + connect: () => void; + destroy: () => void; + }; + const audio = { + deviceId: undefined as unknown as string, + autoGainControl: false, + channelCount: 2, + echoCancellation: false, + latency: 0, + noiseSuppression: false, + sampleRate: 48000, + sampleSize: 16, + googEchoCancellation: false, + googAutoGainControl: false, + googAutoGainControl2: false, + googNoiseSuppression: false, + googHighpassFilter: false, + googTypingNoiseDetection: false + }; // Get microphone settings if (settings.microphone.toLowerCase() !== 'default') - audio = { deviceId: settings.microphone }; + audio.deviceId = settings.microphone; navigator.getUserMedia({ video: false, audio }, async (stream) => { connectionStuff.current.stream = stream; @@ -177,33 +185,27 @@ export default function Voice() { stream.getAudioTracks()[0].enabled = !connectionStuff.current.deafened; setDeafened(connectionStuff.current.deafened); }); - ipcRenderer.on('pushToTalk', (_: any, pressing: boolean) => { + ipcRenderer.on('pushToTalk', (_: unknown, pressing: boolean) => { if (!connectionStuff.current.pushToTalk) return; if (!connectionStuff.current.deafened) { stream.getAudioTracks()[0].enabled = pressing; } - // console.log(stream.getAudioTracks()[0].enabled); }); const ac = new AudioContext(); - ac.createMediaStreamSource(stream) + ac.createMediaStreamSource(stream); audioListener = VAD(ac, ac.createMediaStreamSource(stream), undefined, { onVoiceStart: () => setTalking(true), onVoiceStop: () => setTalking(false), - // onUpdate: console.log, noiseCaptureDuration: 1, + stereo: false }); - // audioListener = audioActivity(stream, (level) => { - // setTalking(level > 0.1); - // }); const peerConnections: PeerConnections = {}; - const inCall: InCall = {}; audioElements.current = {}; - const audioListeners: AudioListeners = {}; const connect = (lobbyCode: string, playerId: number) => { - console.log("Connect called", lobbyCode, playerId); + console.log('Connect called', lobbyCode, playerId); socket.emit('leave'); Object.keys(peerConnections).forEach(k => { disconnectPeer(k); @@ -214,8 +216,9 @@ export default function Voice() { function disconnectPeer(peer: string) { const connection = peerConnections[peer]; - if (!connection) return; - delete inCall[peer]; + if (!connection) { + return; + } connection.destroy(); delete peerConnections[peer]; if (audioElements.current[peer]) { @@ -224,16 +227,12 @@ export default function Voice() { audioElements.current[peer].gain.disconnect(); delete audioElements.current[peer]; } - if (audioListeners[peer]) { - audioListeners[peer].destroy(); - } } socket.emit('join', lobbyCode, playerId); }; setConnect({ connect }); function createPeerConnection(peer: string, initiator: boolean) { - // console.log("Opening connection to ", peer, "Initiator: ", initiator); const connection = new Peer({ stream, initiator, config: { iceServers: [ @@ -246,17 +245,16 @@ export default function Voice() { peerConnections[peer] = connection; connection.on('stream', (stream: MediaStream) => { - let audio = document.createElement('audio'); + const audio = document.createElement('audio') as ExtendedAudioElement; document.body.appendChild(audio); audio.srcObject = stream; if (settings.speaker.toLowerCase() !== 'default') - (audio as any).setSinkId(settings.speaker); + audio.setSinkId(settings.speaker); const context = new AudioContext(); - var source = context.createMediaStreamSource(stream); - let gain = context.createGain(); - let pan = context.createPanner(); - // let compressor = context.createDynamicsCompressor(); + const source = context.createMediaStreamSource(stream); + const gain = context.createGain(); + const pan = context.createPanner(); pan.refDistance = 0.1; pan.panningModel = 'equalpower'; pan.distanceModel = 'linear'; @@ -269,7 +267,7 @@ export default function Voice() { VAD(context, gain, context.destination, { onVoiceStart: () => setTalking(true), onVoiceStop: () => setTalking(false), - // onUpdate: console.log, + stereo: settingsRef.current.enableSpatialAudio }); const setTalking = (talking: boolean) => { @@ -281,24 +279,7 @@ export default function Voice() { return socketPlayerIds; }); }; - // gain.connect(compressor); - // compressor.connect(); - - // console.log(pan, audio); - // pan.pan.setValueAtTime(-1, audioContext.currentTime); - // source.connect(pan); - // pan.connect(audioContext.destination); audioElements.current[peer] = { element: audio, gain, pan }; - - // audioListeners[peer] = audioActivity(stream, (level) => { - // setSocketPlayerIds(socketPlayerIds => { - // setOtherTalking(old => ({ - // ...old, - // [socketPlayerIds[peer]]: level - // })); - // return socketPlayerIds; - // }); - // }); }); connection.on('signal', (data) => { socket.emit('signal', { @@ -312,15 +293,18 @@ export default function Voice() { createPeerConnection(peer, true); setSocketPlayerIds(old => ({ ...old, [peer]: playerId })); }); - socket.on('signal', ({ data, from }: any) => { + socket.on('signal', ({ data, from }: { data: Peer.SignalData, from: string }) => { let connection: Peer.Instance; - if (peerConnections[from]) connection = peerConnections[from]; - else connection = createPeerConnection(from, false); + if (peerConnections[from]) { + connection = peerConnections[from]; + } else { + connection = createPeerConnection(from, false); + } connection.signal(data); }); socket.on('setId', (socketId: string, id: number) => { setSocketPlayerIds(old => ({ ...old, [socketId]: id })); - }) + }); socket.on('setIds', (ids: SocketIdMap) => { setSocketPlayerIds(ids); }); @@ -331,30 +315,35 @@ export default function Voice() { }); return () => { - connectionStuff.current.socket.close(); + connectionStuff.current.socket?.close(); audioListener.destroy(); - } + }; }, []); const myPlayer = useMemo(() => { - if (!gameState || !gameState.players) return undefined; - else return gameState.players.find(p => p.isLocal); - }, [gameState]); + if (!gameState || !gameState.players) { + return undefined; + } else { + return gameState.players.find((p) => p.isLocal); + } + }, [gameState.players]); const otherPlayers = useMemo(() => { let otherPlayers: Player[]; - if (!gameState || !gameState.players || gameState.lobbyCode === 'MENU' || !myPlayer) otherPlayers = []; + if (!gameState || !gameState.players || gameState.lobbyCode === 'MENU' || !myPlayer) return []; else otherPlayers = gameState.players.filter(p => !p.isLocal); - let playerSocketIds = {} as any; - for (let k of Object.keys(socketPlayerIds)) { + const playerSocketIds: { + [index: number]: string + } = {}; + for (const k of Object.keys(socketPlayerIds)) { playerSocketIds[socketPlayerIds[k]] = k; } - for (let player of otherPlayers) { + for (const player of otherPlayers) { const audio = audioElements.current[playerSocketIds[player.id]]; if (audio) { - calculateVoiceAudio(gameState, settingsRef.current, myPlayer!, player, audio.gain, audio.pan); + calculateVoiceAudio(gameState, settingsRef.current, myPlayer, player, audio.gain, audio.pan); if (connectionStuff.current.deafened) { audio.gain.gain.value = 0; } @@ -364,23 +353,27 @@ export default function Voice() { return otherPlayers; }, [gameState]); + // Connect to P2P negotiator, when lobby and connect code change useEffect(() => { if (connect?.connect && gameState.lobbyCode && myPlayer?.id !== undefined) { connect.connect(gameState.lobbyCode, myPlayer.id); } }, [connect?.connect, gameState?.lobbyCode]); + // Connect to P2P negotiator, when game mode change useEffect(() => { if (connect?.connect && gameState.lobbyCode && myPlayer?.id !== undefined && gameState.gameState === GameState.LOBBY && (gameState.oldGameState === GameState.DISCUSSION || gameState.oldGameState === GameState.TASKS)) { connect.connect(gameState.lobbyCode, myPlayer.id); } }, [gameState.gameState]); + // Emit player id to socket useEffect(() => { if (connectionStuff.current.socket && myPlayer && myPlayer.id !== undefined) { connectionStuff.current.socket.emit('id', myPlayer.id); } }, [myPlayer?.id]); + return (
@@ -407,7 +400,7 @@ export default function Voice() {
{ otherPlayers.map(player => { - let connected = Object.values(socketPlayerIds).includes(player.id); + const connected = Object.values(socketPlayerIds).includes(player.id); return (
- ) -} + ); +}; + +export default Voice; diff --git a/src/renderer/audio.d.ts b/src/renderer/audio.d.ts index 5e5e67c5..d5498513 100644 --- a/src/renderer/audio.d.ts +++ b/src/renderer/audio.d.ts @@ -1,4 +1 @@ -declare module 'audio-activity' { - export default function audioActivity(stream: MediaStream, callback: (level: number) => void): any; -} declare module 'analyser-frequency-average'; \ No newline at end of file diff --git a/src/renderer/contexts.tsx b/src/renderer/contexts.tsx new file mode 100644 index 00000000..b7b16649 --- /dev/null +++ b/src/renderer/contexts.tsx @@ -0,0 +1,11 @@ +import React, { createContext } from 'react'; +import { AmongUsState } from '../common/AmongUsState'; +import { ISettings } from '../common/ISettings'; + +type SettingsContextValue = [ISettings, React.Dispatch<{ + type: 'set' | 'setOne'; + action: ISettings | [string, unknown]; +}>] + +export const GameStateContext = createContext({} as AmongUsState); +export const SettingsContext = createContext(null as unknown as SettingsContextValue); diff --git a/src/renderer/cosmetics.ts b/src/renderer/cosmetics.ts index 7db74323..6a55a259 100644 --- a/src/renderer/cosmetics.ts +++ b/src/renderer/cosmetics.ts @@ -110,6 +110,44 @@ import skin13 from '../../static/skins/13.png';// @ts-ignore import skin14 from '../../static/skins/14.png';// @ts-ignore import skin15 from '../../static/skins/15.png'; +// @ts-ignore +import redAlive from '../../static/players/red-alive.png';// @ts-ignore +import blueAlive from '../../static/players/blue-alive.png';// @ts-ignore +import greenAlive from '../../static/players/green-alive.png';// @ts-ignore +import pinkAlive from '../../static/players/pink-alive.png';// @ts-ignore +import orangeAlive from '../../static/players/orange-alive.png';// @ts-ignore +import yellowAlive from '../../static/players/yellow-alive.png';// @ts-ignore +import blackAlive from '../../static/players/black-alive.png';// @ts-ignore +import whiteAlive from '../../static/players/white-alive.png';// @ts-ignore +import purpleAlive from '../../static/players/purple-alive.png';// @ts-ignore +import brownAlive from '../../static/players/brown-alive.png';// @ts-ignore +import cyanAlive from '../../static/players/cyan-alive.png';// @ts-ignore +import limeAlive from '../../static/players/lime-alive.png'; + +// @ts-ignore +import redDead from '../../static/players/red-dead.png';// @ts-ignore +import blueDead from '../../static/players/blue-dead.png';// @ts-ignore +import greenDead from '../../static/players/green-dead.png';// @ts-ignore +import pinkDead from '../../static/players/pink-dead.png';// @ts-ignore +import orangeDead from '../../static/players/orange-dead.png';// @ts-ignore +import yellowDead from '../../static/players/yellow-dead.png';// @ts-ignore +import blackDead from '../../static/players/black-dead.png';// @ts-ignore +import whiteDead from '../../static/players/white-dead.png';// @ts-ignore +import purpleDead from '../../static/players/purple-dead.png';// @ts-ignore +import brownDead from '../../static/players/brown-dead.png';// @ts-ignore +import cyanDead from '../../static/players/cyan-dead.png';// @ts-ignore +import limeDead from '../../static/players/lime-dead.png'; + +export interface PlayerImageColors { + alive: string[]; + dead: string[]; +} + +export const players: PlayerImageColors = { + alive: [redAlive, blueAlive, greenAlive, pinkAlive, orangeAlive, yellowAlive, blackAlive, whiteAlive, purpleAlive, brownAlive, cyanAlive, limeAlive], + dead: [redDead, blueDead, greenDead, pinkDead, orangeDead, yellowDead, blackDead, whiteDead, purpleDead, brownDead, cyanDead, limeDead], +}; + export const skins = [ skin1, skin2, @@ -129,7 +167,7 @@ export const skins = [ ]; export const hatOffsets = [ 45, 25, 50, 37/*T*/, 33, 60/*T*/, 70, 20, 27, 35, 41, 52, 35, 29, 40, 49, 34, 40, 25, 52, 55, 46, 41, 49, 46, 36, 44, 59, 44, 39, 30, 32, 37, 26, 61, 40, 43, 26, 50/*T*/, 51, 37, 44/*T*/, 30, 22, 40, 42, 8, 29, 32, 36, 28, 22, 39, 42, 24, 30, 47, 27, 52, 44, 26, 44, 48, 47, 42, 48, 50, 32, 44, 38, 56, 19, 27, 30, 42/*T*/, 43, 60, 34, 10, 45, 50, 33, 13, 2, 40/*T*/, 32, 32, 55, 22, 999, 26, 29, 43 -] +]; export const backLayerHats = new Set([38, 3, 5, 14, 28]); diff --git a/src/renderer/css/index.css b/src/renderer/css/index.css index f09f29cb..3bfa11ae 100644 --- a/src/renderer/css/index.css +++ b/src/renderer/css/index.css @@ -1,6 +1,5 @@ @import url('https://fonts.googleapis.com/css2?family=Varela&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@500&display=swap'); - body { background-color: #23272A; color: white; @@ -13,6 +12,7 @@ body { width: 100vw; height: 100vh; } + .root { padding-top: 24px; } @@ -77,6 +77,7 @@ body { .close { right: 2px; } + .settings { left: 2px; } @@ -143,11 +144,9 @@ hr { 0% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } - 100% { background-position: 0% 50%; } @@ -155,7 +154,7 @@ hr { ::-webkit-scrollbar { width: 8px; - margin-left:2px; + margin-left: 2px; } ::-webkit-scrollbar-track { diff --git a/src/renderer/css/menu.css b/src/renderer/css/menu.css index b0d63294..4f46620d 100644 --- a/src/renderer/css/menu.css +++ b/src/renderer/css/menu.css @@ -30,7 +30,7 @@ } .errormessage { - margin: 5px; + margin: 5px; } .title { @@ -51,6 +51,7 @@ justify-content: center; align-items: center; } + .footer .row { width: 100%; display: flex; diff --git a/src/renderer/css/settings.css b/src/renderer/css/settings.css index 5eb7eccc..e98d607c 100644 --- a/src/renderer/css/settings.css +++ b/src/renderer/css/settings.css @@ -6,7 +6,7 @@ backdrop-filter: blur(4px); position: absolute; top: 0; - left:0; + left: 0; z-index: 10; display: flex; flex-direction: column; @@ -46,6 +46,11 @@ input[type="text"] { padding: 5px; border-radius: 5px; } + +.form-control.m .input-error { + border-color: #b00020 +} + input[type="text"]:focus { border-color: white; } @@ -62,9 +67,44 @@ select { .settings-scroll { overflow-y: auto; - height: calc(100vh - 20px); + height: calc(100vh - 50px); display: flex; flex-direction: column; justify-content: start; align-items: center; + margin-bottom: 30px; + width: 100%; +} + +.microphone-bar { + width: 200px; + height: 10px; + background: #1d1d1d; + border: 1px solid rgba(255, 255, 255, 0.5); + border-radius: 5px; + margin: 5px auto; + overflow: hidden; +} + +.microphone-bar-inner { + background: #e74c3c; + height: 10px; + border-radius: 5px; +} + +.test-speakers { + width: fit-content; + margin: 5px auto; +} + +.settings-alert { + background: #f1c40f; + color: black; + position: absolute; + bottom: 20px; + left: 0; + height: 30px; + justify-content: center; + align-items: center; + width: 100vw; } \ No newline at end of file diff --git a/src/renderer/vad.ts b/src/renderer/vad.ts index bc8c8cf7..cce220e2 100644 --- a/src/renderer/vad.ts +++ b/src/renderer/vad.ts @@ -1,25 +1,29 @@ import analyserFrequency from 'analyser-frequency-average'; interface VADOptions { - fftSize?: number; - bufferLen?: number; - smoothingTimeConstant?: number; - minCaptureFreq?: number; - maxCaptureFreq?: number; - noiseCaptureDuration?: number; - minNoiseLevel?: number; - maxNoiseLevel?: number; - avgNoiseMultiplier?: number; - onVoiceStart?: () => void; - onVoiceStop?: () => void; - onUpdate?: (val: number) => void; + fftSize: number; + bufferLen: number; + smoothingTimeConstant: number; + minCaptureFreq: number; + maxCaptureFreq: number; + noiseCaptureDuration: number; + minNoiseLevel: number; + maxNoiseLevel: number; + avgNoiseMultiplier: number; + onVoiceStart: () => void; + onVoiceStop: () => void; + onUpdate: (val: number) => void; + stereo: boolean; } -export default function (audioContext: AudioContext, source: AudioNode, destination: AudioNode | undefined, opts: VADOptions) { +export default function (audioContext: AudioContext, source: AudioNode, destination: AudioNode | undefined, opts: Partial): { + connect: () => void, + destroy: () => void +} { opts = opts || {}; - var defaults = { + const defaults: VADOptions = { fftSize: 1024, bufferLen: 1024, smoothingTimeConstant: 0.2, @@ -29,41 +33,40 @@ export default function (audioContext: AudioContext, source: AudioNode, destinat minNoiseLevel: 0.3, // from 0 to 1 maxNoiseLevel: 0.7, // from 0 to 1 avgNoiseMultiplier: 1.2, - onVoiceStart: () => { }, - onVoiceStop: () => { }, - onUpdate: (_: number) => { } + onVoiceStart: function () { /* DO NOTHING */ }, + onVoiceStop: function () { /* DO NOTHING */ }, + onUpdate: function () { /* DO NOTHING */ }, + stereo: true }; - var options: any = {}; - for (var key in defaults) { - options[key] = opts.hasOwnProperty(key) ? (opts as any)[key] : (defaults as any)[key]; - } + const options: VADOptions = Object.assign({}, defaults, opts); - var baseLevel = 0; - var voiceScale = 1; - var activityCounter = 0; - var activityCounterMin = 0; - var activityCounterMax = 30; - var activityCounterThresh = 5; + let baseLevel = 0; + let voiceScale = 1; + let activityCounter = 0; + const activityCounterMin = 0; + const activityCounterMax = 30; + const activityCounterThresh = 5; - var envFreqRange: number[] = []; - var isNoiseCapturing = true; - var prevVadState: boolean | undefined = undefined; - var vadState = false; - var captureTimeout: any = null; + let envFreqRange: number[] = []; + let isNoiseCapturing = true; + let prevVadState: boolean | undefined = undefined; + let vadState = false; + let captureTimeout: number | null = null; // var source = audioContext.createMediaStreamSource(stream); - var analyser = audioContext.createAnalyser(); + const analyser = audioContext.createAnalyser(); analyser.smoothingTimeConstant = options.smoothingTimeConstant; analyser.fftSize = options.fftSize; - var scriptProcessorNode = audioContext.createScriptProcessor(options.bufferLen, 2, 2); + const channels = options.stereo ? 2 : 1; + const scriptProcessorNode = audioContext.createScriptProcessor(options.bufferLen, channels, channels); connect(); scriptProcessorNode.onaudioprocess = monitor; if (isNoiseCapturing) { //console.log('VAD: start noise capturing'); - captureTimeout = setTimeout(init, options.noiseCaptureDuration); + captureTimeout = setTimeout(init, options.noiseCaptureDuration) as unknown as number; } function init() { @@ -73,7 +76,7 @@ export default function (audioContext: AudioContext, source: AudioNode, destinat envFreqRange = envFreqRange.filter(function (val) { return val; }).sort(); - var averageEnvFreq = envFreqRange.length ? envFreqRange.reduce(function (p, c) { return Math.min(p, c) }, 1) : (options.minNoiseLevel || 0.1); + const averageEnvFreq = envFreqRange.length ? envFreqRange.reduce(function (p, c) { return Math.min(p, c); }, 1) : (options.minNoiseLevel || 0.1); baseLevel = averageEnvFreq * options.avgNoiseMultiplier; if (options.minNoiseLevel && baseLevel < options.minNoiseLevel) baseLevel = options.minNoiseLevel; @@ -113,18 +116,18 @@ export default function (audioContext: AudioContext, source: AudioNode, destinat if (destination) { for (let channel = 0; channel < event.outputBuffer.numberOfChannels; channel++) { - var inputData = event.inputBuffer.getChannelData(channel); - var outputData = event.outputBuffer.getChannelData(channel); - for (var sample = 0; sample < event.inputBuffer.length; sample++) { + const inputData = event.inputBuffer.getChannelData(channel); + const outputData = event.outputBuffer.getChannelData(channel); + for (let sample = 0; sample < event.inputBuffer.length; sample++) { // make output equal to the same as the input outputData[sample] = inputData[sample]; } } } - var frequencies = new Uint8Array(analyser.frequencyBinCount); + const frequencies = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(frequencies); - var average = analyserFrequency(analyser, frequencies, options.minCaptureFreq, options.maxCaptureFreq); + const average = analyserFrequency(analyser, frequencies, options.minCaptureFreq, options.maxCaptureFreq); if (isNoiseCapturing) { envFreqRange.push(average); return; @@ -154,4 +157,4 @@ export default function (audioContext: AudioContext, source: AudioNode, destinat } return { connect, destroy }; -}; +} diff --git a/static/alive.png b/static/alive.png deleted file mode 100644 index 8566d981..00000000 Binary files a/static/alive.png and /dev/null differ diff --git a/static/chime.mp3 b/static/chime.mp3 new file mode 100644 index 00000000..344ed313 Binary files /dev/null and b/static/chime.mp3 differ diff --git a/static/dead.png b/static/dead.png deleted file mode 100644 index 977acf0f..00000000 Binary files a/static/dead.png and /dev/null differ diff --git a/static/players/black-alive.png b/static/players/black-alive.png new file mode 100644 index 00000000..065a2201 Binary files /dev/null and b/static/players/black-alive.png differ diff --git a/static/players/black-dead.png b/static/players/black-dead.png new file mode 100644 index 00000000..127d9dea Binary files /dev/null and b/static/players/black-dead.png differ diff --git a/static/players/blue-alive.png b/static/players/blue-alive.png new file mode 100644 index 00000000..dddf7697 Binary files /dev/null and b/static/players/blue-alive.png differ diff --git a/static/players/blue-dead.png b/static/players/blue-dead.png new file mode 100644 index 00000000..82a7505c Binary files /dev/null and b/static/players/blue-dead.png differ diff --git a/static/players/brown-alive.png b/static/players/brown-alive.png new file mode 100644 index 00000000..d5903c55 Binary files /dev/null and b/static/players/brown-alive.png differ diff --git a/static/players/brown-dead.png b/static/players/brown-dead.png new file mode 100644 index 00000000..6cedd6af Binary files /dev/null and b/static/players/brown-dead.png differ diff --git a/static/players/cyan-alive.png b/static/players/cyan-alive.png new file mode 100644 index 00000000..4403cd64 Binary files /dev/null and b/static/players/cyan-alive.png differ diff --git a/static/players/cyan-dead.png b/static/players/cyan-dead.png new file mode 100644 index 00000000..515b339b Binary files /dev/null and b/static/players/cyan-dead.png differ diff --git a/static/players/green-alive.png b/static/players/green-alive.png new file mode 100644 index 00000000..5ed0a0e8 Binary files /dev/null and b/static/players/green-alive.png differ diff --git a/static/players/green-dead.png b/static/players/green-dead.png new file mode 100644 index 00000000..1f5e6207 Binary files /dev/null and b/static/players/green-dead.png differ diff --git a/static/players/lime-alive.png b/static/players/lime-alive.png new file mode 100644 index 00000000..3c3437d0 Binary files /dev/null and b/static/players/lime-alive.png differ diff --git a/static/players/lime-dead.png b/static/players/lime-dead.png new file mode 100644 index 00000000..ebd9d127 Binary files /dev/null and b/static/players/lime-dead.png differ diff --git a/static/players/orange-alive.png b/static/players/orange-alive.png new file mode 100644 index 00000000..be208127 Binary files /dev/null and b/static/players/orange-alive.png differ diff --git a/static/players/orange-dead.png b/static/players/orange-dead.png new file mode 100644 index 00000000..ddb71f46 Binary files /dev/null and b/static/players/orange-dead.png differ diff --git a/static/players/pink-alive.png b/static/players/pink-alive.png new file mode 100644 index 00000000..d07962de Binary files /dev/null and b/static/players/pink-alive.png differ diff --git a/static/players/pink-dead.png b/static/players/pink-dead.png new file mode 100644 index 00000000..7a445299 Binary files /dev/null and b/static/players/pink-dead.png differ diff --git a/static/players/purple-alive.png b/static/players/purple-alive.png new file mode 100644 index 00000000..3a921c44 Binary files /dev/null and b/static/players/purple-alive.png differ diff --git a/static/players/purple-dead.png b/static/players/purple-dead.png new file mode 100644 index 00000000..9686af7d Binary files /dev/null and b/static/players/purple-dead.png differ diff --git a/static/players/red-alive.png b/static/players/red-alive.png new file mode 100644 index 00000000..6db934cc Binary files /dev/null and b/static/players/red-alive.png differ diff --git a/static/players/red-dead.png b/static/players/red-dead.png new file mode 100644 index 00000000..486bf5cf Binary files /dev/null and b/static/players/red-dead.png differ diff --git a/static/players/white-alive.png b/static/players/white-alive.png new file mode 100644 index 00000000..fcb32693 Binary files /dev/null and b/static/players/white-alive.png differ diff --git a/static/players/white-dead.png b/static/players/white-dead.png new file mode 100644 index 00000000..e17c195c Binary files /dev/null and b/static/players/white-dead.png differ diff --git a/static/players/yellow-alive.png b/static/players/yellow-alive.png new file mode 100644 index 00000000..c2beaa7a Binary files /dev/null and b/static/players/yellow-alive.png differ diff --git a/static/players/yellow-dead.png b/static/players/yellow-dead.png new file mode 100644 index 00000000..237dac34 Binary files /dev/null and b/static/players/yellow-dead.png differ diff --git a/yarn.lock b/yarn.lock index bbacc8a5..3193fa5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1005,6 +1005,43 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@eslint/eslintrc@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76" + integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@nodelib/fs.scandir@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== + dependencies: + "@nodelib/fs.stat" "2.0.3" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== + dependencies: + "@nodelib/fs.scandir" "2.1.3" + fastq "^1.6.0" + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -1084,6 +1121,11 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb" integrity sha512-JCcp6J0GV66Y4ZMDAQCXot4xprYB+Zfd3meK9+INSJeVZwJmHAW30BBEEkPzXswMXuiyReUGOP3GxrADc9wPww== +"@types/json-schema@^7.0.3": + version "7.0.6" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" + integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1206,6 +1248,76 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.9.1.tgz#66758cbe129b965fe9c63b04b405d0cf5280868b" + integrity sha512-QRLDSvIPeI1pz5tVuurD+cStNR4sle4avtHhxA+2uyixWGFjKzJ+EaFVRW6dA/jOgjV5DTAjOxboQkRDE8cRlQ== + dependencies: + "@typescript-eslint/experimental-utils" "4.9.1" + "@typescript-eslint/scope-manager" "4.9.1" + debug "^4.1.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.9.1.tgz#86633e8395191d65786a808dc3df030a55267ae2" + integrity sha512-c3k/xJqk0exLFs+cWSJxIjqLYwdHCuLWhnpnikmPQD2+NGAx9KjLYlBDcSI81EArh9FDYSL6dslAUSwILeWOxg== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/scope-manager" "4.9.1" + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/typescript-estree" "4.9.1" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.9.1.tgz#2d74c4db5dd5117379a9659081a4d1ec02629055" + integrity sha512-Gv2VpqiomvQ2v4UL+dXlQcZ8zCX4eTkoIW+1aGVWT6yTO+6jbxsw7yQl2z2pPl/4B9qa5JXeIbhJpONKjXIy3g== + dependencies: + "@typescript-eslint/scope-manager" "4.9.1" + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/typescript-estree" "4.9.1" + debug "^4.1.1" + +"@typescript-eslint/scope-manager@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.9.1.tgz#cc2fde310b3f3deafe8436a924e784eaab265103" + integrity sha512-sa4L9yUfD/1sg9Kl8OxPxvpUcqxKXRjBeZxBuZSSV1v13hjfEJkn84n0An2hN8oLQ1PmEl2uA6FkI07idXeFgQ== + dependencies: + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/visitor-keys" "4.9.1" + +"@typescript-eslint/types@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.9.1.tgz#a1a7dd80e4e5ac2c593bc458d75dd1edaf77faa2" + integrity sha512-fjkT+tXR13ks6Le7JiEdagnwEFc49IkOyys7ueWQ4O8k4quKPwPJudrwlVOJCUQhXo45PrfIvIarcrEjFTNwUA== + +"@typescript-eslint/typescript-estree@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.9.1.tgz#6e5b86ff5a5f66809e1f347469fadeec69ac50bf" + integrity sha512-bzP8vqwX6Vgmvs81bPtCkLtM/Skh36NE6unu6tsDeU/ZFoYthlTXbBmpIrvosgiDKlWTfb2ZpPELHH89aQjeQw== + dependencies: + "@typescript-eslint/types" "4.9.1" + "@typescript-eslint/visitor-keys" "4.9.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@typescript-eslint/visitor-keys@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.9.1.tgz#d76374a58c4ead9e92b454d186fea63487b25ae1" + integrity sha512-9gspzc6UqLQHd7lXQS7oWs+hrYggspv/rk6zzEMhCbYwPE/sF7oxo7GAjkS35Tdlt7wguIG+ViWCPtVZHz/ybQ== + dependencies: + "@typescript-eslint/types" "4.9.1" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -1369,7 +1481,7 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-jsx@^5.0.0: +acorn-jsx@^5.0.0, acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== @@ -1384,6 +1496,11 @@ acorn@^6.2.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" @@ -1417,7 +1534,7 @@ ajv@^6.1.0, ajv@^6.10.1, ajv@^6.10.2, ajv@^6.12.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.12.2, ajv@^6.12.3, ajv@^6.9.1: +ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.6, ajv@^6.9.1: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1446,6 +1563,11 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -1598,6 +1720,11 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -1616,6 +1743,16 @@ array.prototype.flat@^1.2.3: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" +array.prototype.flatmap@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" + arraybuffer.slice@~0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" @@ -2212,6 +2349,14 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -2445,7 +2590,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -2464,27 +2609,11 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.4.tgz#dd51cd25cfee953d138fe4002372cc3d0e504cb6" - integrity sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e" - integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.4" - combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2736,7 +2865,7 @@ cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.3: +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" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2947,7 +3076,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@~0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= @@ -3064,6 +3193,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + dissolve@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/dissolve/-/dissolve-0.3.3.tgz#b97ef1ff2989c789cecfb03107e17411fa8be6e5" @@ -3112,6 +3248,13 @@ doctrine@1.5.0: esutils "^2.0.2" isarray "^1.0.0" +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -3372,6 +3515,14 @@ electron-webpack@^2.8.2: webpack-merge "^4.2.2" yargs "^15.3.1" +electron-window-state@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/electron-window-state/-/electron-window-state-5.0.3.tgz#4f36d09e3f953d87aff103bf010f460056050aa8" + integrity sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg== + dependencies: + jsonfile "^4.0.0" + mkdirp "^0.5.1" + electron@9.3.3: version "9.3.3" resolved "https://registry.yarnpkg.com/electron/-/electron-9.3.3.tgz#99a6619d5df68f97697a5d1d82ef3a8a63fcdf36" @@ -3481,6 +3632,13 @@ enhanced-resolve@^4.1.0: memory-fs "^0.5.0" tapable "^1.0.0" +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -3648,6 +3806,23 @@ eslint-plugin-import@^2.8.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" +eslint-plugin-react@^7.21.5: + version "7.21.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" + integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.18.1" + string.prototype.matchall "^4.0.2" + eslint-restricted-globals@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" @@ -3661,6 +3836,14 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.0.0, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-utils@^1.3.1: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" @@ -3668,11 +3851,23 @@ eslint-utils@^1.3.1: dependencies: eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + eslint@^5.16.0: version "5.16.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea" @@ -3715,6 +3910,49 @@ eslint@^5.16.0: table "^5.2.3" text-table "^0.2.0" +eslint@^7.15.0: + version "7.15.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.15.0.tgz#eb155fb8ed0865fcf5d903f76be2e5b6cd7e0bc7" + integrity sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.2.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^6.0.0" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + espree@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a" @@ -3724,12 +3962,21 @@ espree@^5.0.1: acorn-jsx "^5.0.0" eslint-visitor-keys "^1.0.0" +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1: +esquery@^1.0.1, esquery@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== @@ -3743,12 +3990,19 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + estraverse@^4.1.0, estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.1.0: +estraverse@^5.1.0, estraverse@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== @@ -3930,16 +4184,35 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== +fast-glob@^3.1.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastq@^1.6.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947" + integrity sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w== + dependencies: + reusify "^1.0.4" + faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -3980,6 +4253,13 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" +file-entry-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" + integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA== + dependencies: + flat-cache "^3.0.4" + file-loader@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.0.0.tgz#97bbfaab7a2460c07bcbd72d3a6922407f67649f" @@ -4097,11 +4377,24 @@ flat-cache@^2.0.1: rimraf "2.6.3" write "1.0.3" +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + flatted@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067" + integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -4296,6 +4589,15 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -4340,6 +4642,13 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob-parent@^5.0.0, glob-parent@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -4423,6 +4732,13 @@ globals@^11.1.0, globals@^11.7.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + globalthis@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" @@ -4430,6 +4746,18 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" +globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -4802,6 +5130,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + import-fresh@^3.0.0: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" @@ -4810,6 +5143,14 @@ import-fresh@^3.0.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-fresh@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.2.tgz#fc129c160c5d68235507f4331a6baad186bdbc3e" + integrity sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -4910,6 +5251,15 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" +internal-slot@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3" + integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g== + dependencies: + es-abstract "^1.17.0-next.1" + has "^1.0.3" + side-channel "^1.0.2" + interpret@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -4982,11 +5332,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -5023,6 +5368,13 @@ is-core-module@^2.0.0: dependencies: has "^1.0.3" +is-core-module@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -5106,7 +5458,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: +is-glob@^4.0.0, is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -5429,6 +5781,14 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -5504,6 +5864,14 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -5743,9 +6111,9 @@ memory-fs@^0.5.0: errno "^0.1.3" readable-stream "^2.0.1" -"memoryjs@https://github.com/Rob--/memoryjs": +"memoryjs@git://github.com/Rob--/memoryjs": version "3.3.1" - resolved "https://github.com/Rob--/memoryjs#79618efdefc2aac203ae7137eefa2cf30cf888ce" + resolved "git://github.com/Rob--/memoryjs#79618efdefc2aac203ae7137eefa2cf30cf888ce" dependencies: bindings "^1.5.0" concentrate "^0.2.3" @@ -5788,6 +6156,11 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -5817,7 +6190,7 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.0: +micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -6317,6 +6690,26 @@ object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" +object.entries@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" + integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.3.tgz#13cefcffa702dc67750314a3305e8cb3fad1d072" + integrity sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + object.getownpropertydescriptors@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" @@ -6399,6 +6792,18 @@ optionator@^0.8.2: type-check "~0.3.2" word-wrap "~1.2.3" +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + original@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" @@ -6678,6 +7083,11 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -6699,7 +7109,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.5: +picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -6867,6 +7277,11 @@ prebuild-install@^5.3.5: tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -6931,7 +7346,7 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.2: +prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7318,7 +7733,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp.prototype.flags@^1.2.0: +regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== @@ -7331,6 +7746,11 @@ regexpp@^2.0.1: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + regexpu-core@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" @@ -7506,6 +7926,14 @@ resolve@^1.13.1, resolve@^1.17.0: is-core-module "^2.0.0" path-parse "^1.0.6" +resolve@^1.18.1: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -7531,6 +7959,11 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + rimraf@2.6.3: version "2.6.3" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" @@ -7545,6 +7978,13 @@ rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: dependencies: glob "^7.1.3" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -7570,6 +8010,11 @@ run-async@^2.2.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-parallel@^1.1.9: + version "1.1.10" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef" + integrity sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw== + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -7695,6 +8140,13 @@ semver@^7.1.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== +semver@^7.2.1: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + semver@^7.3.2: version "7.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" @@ -7816,6 +8268,14 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel@^1.0.2, side-channel@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.3.tgz#cdc46b057550bbab63706210838df5d4c19519c3" + integrity sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g== + dependencies: + es-abstract "^1.18.0-next.0" + object-inspect "^1.8.0" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -7847,13 +8307,6 @@ simple-peer@^9.8.0: randombytes "^2.1.0" readable-stream "^3.6.0" -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - single-line-log@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" @@ -7861,6 +8314,11 @@ single-line-log@^1.1.2: dependencies: string-width "^1.0.1" +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -8191,6 +8649,19 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string.prototype.matchall@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz#24243399bc31b0a49d19e2b74171a15653ec996a" + integrity sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has-symbols "^1.0.1" + internal-slot "^1.0.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.3" + string.prototype.trimend@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373" @@ -8317,6 +8788,11 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + structron@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/structron/-/structron-0.2.4.tgz#dd37e702b638b3c9e14c38fc8730e336ee85c047" @@ -8670,6 +9146,18 @@ tslib@^1.10.0, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -8692,6 +9180,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + type-check@~0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -8951,6 +9446,11 @@ v8-compile-cache@2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== +v8-compile-cache@^2.0.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" + integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== + validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -9165,7 +9665,7 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" -word-wrap@~1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==