An ATproto social media client -- with an independent Appview.

Account switcher (#85)

* Update the account-create and signin views to use the design system.

Also:
- Add borderDark to the theme
- Start to an account selector in the signin flow

* Dark mode fixes in signin ui

* Track multiple active accounts and provide account-switching UI

* Add test tooling for an in-memory pds

* Add complete integration tests for login and the account switcher

authored by

Paul Frazee and committed by
GitHub
9027882f 439305b5

+2403 -655
+57
__mocks__/@gorhom/bottom-sheet.tsx
··· 1 + import React from 'react' 2 + import {View, ScrollView, Modal, FlatList, TextInput} from 'react-native' 3 + 4 + const BottomSheetModalContext = React.createContext(null) 5 + 6 + const BottomSheetModalProvider = (props: any) => { 7 + return <BottomSheetModalContext.Provider {...props} value={{}} /> 8 + } 9 + class BottomSheet extends React.Component { 10 + snapToIndex() {} 11 + snapToPosition() {} 12 + expand() {} 13 + collapse() {} 14 + close() { 15 + this.props.onClose?.() 16 + } 17 + forceClose() {} 18 + 19 + render() { 20 + return <View>{this.props.children}</View> 21 + } 22 + } 23 + const BottomSheetModal = (props: any) => <Modal {...props} /> 24 + 25 + const BottomSheetBackdrop = (props: any) => <View {...props} /> 26 + const BottomSheetHandle = (props: any) => <View {...props} /> 27 + const BottomSheetFooter = (props: any) => <View {...props} /> 28 + const BottomSheetScrollView = (props: any) => <ScrollView {...props} /> 29 + const BottomSheetFlatList = (props: any) => <FlatList {...props} /> 30 + const BottomSheetTextInput = (props: any) => <TextInput {...props} /> 31 + 32 + const useBottomSheet = jest.fn() 33 + const useBottomSheetModal = jest.fn() 34 + const useBottomSheetSpringConfigs = jest.fn() 35 + const useBottomSheetTimingConfigs = jest.fn() 36 + const useBottomSheetInternal = jest.fn() 37 + const useBottomSheetDynamicSnapPoints = jest.fn() 38 + 39 + export {useBottomSheet} 40 + export {useBottomSheetModal} 41 + export {useBottomSheetSpringConfigs} 42 + export {useBottomSheetTimingConfigs} 43 + export {useBottomSheetInternal} 44 + export {useBottomSheetDynamicSnapPoints} 45 + 46 + export { 47 + BottomSheetModalProvider, 48 + BottomSheetBackdrop, 49 + BottomSheetHandle, 50 + BottomSheetModal, 51 + BottomSheetFooter, 52 + BottomSheetScrollView, 53 + BottomSheetFlatList, 54 + BottomSheetTextInput, 55 + } 56 + 57 + export default BottomSheet
+241
__tests__/accounts.test.tsx
··· 1 + import React from 'react' 2 + import {MobileShell} from '../src/view/shell/mobile' 3 + import {cleanup, fireEvent, render, waitFor} from '../jest/test-utils' 4 + import {createServer, TestPDS} from '../jest/test-pds' 5 + import {RootStoreModel, setupState} from '../src/state' 6 + 7 + const WAIT_OPTS = {timeout: 5e3} 8 + 9 + describe('Account flows', () => { 10 + let pds: TestPDS | undefined 11 + let rootStore: RootStoreModel | undefined 12 + beforeAll(async () => { 13 + jest.useFakeTimers() 14 + pds = await createServer() 15 + rootStore = await setupState(pds.pdsUrl) 16 + }) 17 + 18 + afterAll(async () => { 19 + jest.clearAllMocks() 20 + cleanup() 21 + await pds?.close() 22 + }) 23 + 24 + it('renders initial screen', () => { 25 + const {getByTestId} = render(<MobileShell />, rootStore) 26 + const signUpScreen = getByTestId('signinOrCreateAccount') 27 + 28 + expect(signUpScreen).toBeTruthy() 29 + }) 30 + 31 + it('completes signin to the server', async () => { 32 + const {getByTestId} = render(<MobileShell />, rootStore) 33 + 34 + // move to signin view 35 + fireEvent.press(getByTestId('signInButton')) 36 + expect(getByTestId('signIn')).toBeTruthy() 37 + expect(getByTestId('loginForm')).toBeTruthy() 38 + 39 + // input the target server 40 + expect(getByTestId('loginSelectServiceButton')).toBeTruthy() 41 + fireEvent.press(getByTestId('loginSelectServiceButton')) 42 + expect(getByTestId('serverInputModal')).toBeTruthy() 43 + fireEvent.changeText( 44 + getByTestId('customServerTextInput'), 45 + pds?.pdsUrl || '', 46 + ) 47 + fireEvent.press(getByTestId('customServerSelectBtn')) 48 + await waitFor(() => { 49 + expect(getByTestId('loginUsernameInput')).toBeTruthy() 50 + }, WAIT_OPTS) 51 + 52 + // enter username & pass 53 + fireEvent.changeText(getByTestId('loginUsernameInput'), 'alice') 54 + fireEvent.changeText(getByTestId('loginPasswordInput'), 'hunter2') 55 + await waitFor(() => { 56 + expect(getByTestId('loginNextButton')).toBeTruthy() 57 + }, WAIT_OPTS) 58 + fireEvent.press(getByTestId('loginNextButton')) 59 + 60 + // signed in 61 + await waitFor(() => { 62 + expect(getByTestId('homeFeed')).toBeTruthy() 63 + expect(rootStore?.me?.displayName).toBe('Alice') 64 + expect(rootStore?.me?.handle).toBe('alice.test') 65 + expect(rootStore?.session.accounts.length).toBe(1) 66 + }, WAIT_OPTS) 67 + expect(rootStore?.me?.displayName).toBe('Alice') 68 + expect(rootStore?.me?.handle).toBe('alice.test') 69 + expect(rootStore?.session.accounts.length).toBe(1) 70 + }) 71 + 72 + it('opens the login screen when "add account" is pressed', async () => { 73 + const {getByTestId, getAllByTestId} = render(<MobileShell />, rootStore) 74 + await waitFor(() => expect(getByTestId('homeFeed')).toBeTruthy(), WAIT_OPTS) 75 + 76 + // open side menu 77 + fireEvent.press(getAllByTestId('viewHeaderBackOrMenuBtn')[0]) 78 + await waitFor(() => expect(getByTestId('menuView')).toBeTruthy(), WAIT_OPTS) 79 + 80 + // nav to settings 81 + fireEvent.press(getByTestId('menuItemButton-Settings')) 82 + await waitFor( 83 + () => expect(getByTestId('settingsScreen')).toBeTruthy(), 84 + WAIT_OPTS, 85 + ) 86 + 87 + // press '+ new account' in switcher 88 + fireEvent.press(getByTestId('switchToNewAccountBtn')) 89 + await waitFor( 90 + () => expect(getByTestId('signinOrCreateAccount')).toBeTruthy(), 91 + WAIT_OPTS, 92 + ) 93 + }) 94 + 95 + it('shows the "choose account" form when a previous session has been created', async () => { 96 + const {getByTestId} = render(<MobileShell />, rootStore) 97 + 98 + // move to signin view 99 + fireEvent.press(getByTestId('signInButton')) 100 + expect(getByTestId('signIn')).toBeTruthy() 101 + expect(getByTestId('chooseAccountForm')).toBeTruthy() 102 + }) 103 + 104 + it('logs directly into the account due to still possessing session tokens', async () => { 105 + const {getByTestId} = render(<MobileShell />, rootStore) 106 + 107 + // move to signin view 108 + fireEvent.press(getByTestId('signInButton')) 109 + expect(getByTestId('signIn')).toBeTruthy() 110 + expect(getByTestId('chooseAccountForm')).toBeTruthy() 111 + 112 + // select the previous account 113 + fireEvent.press(getByTestId('chooseAccountBtn-alice.test')) 114 + 115 + // signs in immediately 116 + await waitFor(() => { 117 + expect(getByTestId('homeFeed')).toBeTruthy() 118 + expect(rootStore?.me?.displayName).toBe('Alice') 119 + expect(rootStore?.me?.handle).toBe('alice.test') 120 + expect(rootStore?.session.accounts.length).toBe(1) 121 + }, WAIT_OPTS) 122 + expect(rootStore?.me?.displayName).toBe('Alice') 123 + expect(rootStore?.me?.handle).toBe('alice.test') 124 + expect(rootStore?.session.accounts.length).toBe(1) 125 + }) 126 + 127 + it('logs into a second account via the switcher', async () => { 128 + const {getByTestId, getAllByTestId} = render(<MobileShell />, rootStore) 129 + await waitFor(() => expect(getByTestId('homeFeed')).toBeTruthy(), WAIT_OPTS) 130 + 131 + // open side menu 132 + fireEvent.press(getAllByTestId('viewHeaderBackOrMenuBtn')[0]) 133 + await waitFor(() => expect(getByTestId('menuView')).toBeTruthy(), WAIT_OPTS) 134 + 135 + // nav to settings 136 + fireEvent.press(getByTestId('menuItemButton-Settings')) 137 + await waitFor( 138 + () => expect(getByTestId('settingsScreen')).toBeTruthy(), 139 + WAIT_OPTS, 140 + ) 141 + 142 + // press '+ new account' in switcher 143 + fireEvent.press(getByTestId('switchToNewAccountBtn')) 144 + await waitFor( 145 + () => expect(getByTestId('signinOrCreateAccount')).toBeTruthy(), 146 + WAIT_OPTS, 147 + ) 148 + 149 + // move to signin view 150 + fireEvent.press(getByTestId('signInButton')) 151 + expect(getByTestId('signIn')).toBeTruthy() 152 + expect(getByTestId('chooseAccountForm')).toBeTruthy() 153 + 154 + // select a new account 155 + fireEvent.press(getByTestId('chooseNewAccountBtn')) 156 + expect(getByTestId('loginForm')).toBeTruthy() 157 + 158 + // input the target server 159 + expect(getByTestId('loginSelectServiceButton')).toBeTruthy() 160 + fireEvent.press(getByTestId('loginSelectServiceButton')) 161 + expect(getByTestId('serverInputModal')).toBeTruthy() 162 + fireEvent.changeText( 163 + getByTestId('customServerTextInput'), 164 + pds?.pdsUrl || '', 165 + ) 166 + fireEvent.press(getByTestId('customServerSelectBtn')) 167 + await waitFor( 168 + () => expect(getByTestId('loginUsernameInput')).toBeTruthy(), 169 + WAIT_OPTS, 170 + ) 171 + 172 + // enter username & pass 173 + fireEvent.changeText(getByTestId('loginUsernameInput'), 'bob') 174 + fireEvent.changeText(getByTestId('loginPasswordInput'), 'hunter2') 175 + await waitFor( 176 + () => expect(getByTestId('loginNextButton')).toBeTruthy(), 177 + WAIT_OPTS, 178 + ) 179 + fireEvent.press(getByTestId('loginNextButton')) 180 + 181 + // signed in 182 + await waitFor(() => { 183 + expect(getByTestId('settingsScreen')).toBeTruthy() // we go back to settings in this situation 184 + expect(rootStore?.me?.displayName).toBe('Bob') 185 + expect(rootStore?.me?.handle).toBe('bob.test') 186 + expect(rootStore?.session.accounts.length).toBe(2) 187 + }, WAIT_OPTS) 188 + expect(rootStore?.me?.displayName).toBe('Bob') 189 + expect(rootStore?.me?.handle).toBe('bob.test') 190 + expect(rootStore?.session.accounts.length).toBe(2) 191 + }) 192 + 193 + it('can instantly switch between accounts', async () => { 194 + const {getByTestId} = render(<MobileShell />, rootStore) 195 + await waitFor( 196 + () => expect(getByTestId('settingsScreen')).toBeTruthy(), 197 + WAIT_OPTS, 198 + ) 199 + 200 + // select the alice account 201 + fireEvent.press(getByTestId('switchToAccountBtn-alice.test')) 202 + 203 + // swapped account 204 + await waitFor(() => { 205 + expect(rootStore?.me?.displayName).toBe('Alice') 206 + expect(rootStore?.me?.handle).toBe('alice.test') 207 + expect(rootStore?.session.accounts.length).toBe(2) 208 + }, WAIT_OPTS) 209 + expect(rootStore?.me?.displayName).toBe('Alice') 210 + expect(rootStore?.me?.handle).toBe('alice.test') 211 + expect(rootStore?.session.accounts.length).toBe(2) 212 + }) 213 + 214 + it('will prompt for a password if you sign out', async () => { 215 + const {getByTestId} = render(<MobileShell />, rootStore) 216 + await waitFor( 217 + () => expect(getByTestId('settingsScreen')).toBeTruthy(), 218 + WAIT_OPTS, 219 + ) 220 + 221 + // press the sign out button 222 + fireEvent.press(getByTestId('signOutBtn')) 223 + 224 + // in the logged out state 225 + await waitFor( 226 + () => expect(getByTestId('signinOrCreateAccount')).toBeTruthy(), 227 + WAIT_OPTS, 228 + ) 229 + 230 + // move to signin view 231 + fireEvent.press(getByTestId('signInButton')) 232 + expect(getByTestId('signIn')).toBeTruthy() 233 + expect(getByTestId('chooseAccountForm')).toBeTruthy() 234 + 235 + // select an existing account 236 + fireEvent.press(getByTestId('chooseAccountBtn-alice.test')) 237 + 238 + // goes to login screen instead of straight back to settings 239 + expect(getByTestId('loginForm')).toBeTruthy() 240 + }) 241 + })
-126
__tests__/view/com/login/Signin.test.tsx
··· 1 - import React from 'react' 2 - import {Signin} from '../../../../src/view/com/login/Signin' 3 - import {cleanup, fireEvent, render} from '../../../../jest/test-utils' 4 - import {SessionServiceClient, sessionClient as AtpApi} from '@atproto/api' 5 - import { 6 - mockedSessionStore, 7 - mockedShellStore, 8 - } from '../../../../__mocks__/state-mock' 9 - import {Keyboard} from 'react-native' 10 - 11 - describe('Signin', () => { 12 - const requestPasswordResetMock = jest.fn() 13 - const resetPasswordMock = jest.fn() 14 - jest.spyOn(AtpApi, 'service').mockReturnValue({ 15 - com: { 16 - atproto: { 17 - account: { 18 - requestPasswordReset: requestPasswordResetMock, 19 - resetPassword: resetPasswordMock, 20 - }, 21 - }, 22 - }, 23 - } as unknown as SessionServiceClient) 24 - const mockedProps = { 25 - onPressBack: jest.fn(), 26 - } 27 - afterAll(() => { 28 - jest.clearAllMocks() 29 - cleanup() 30 - }) 31 - 32 - it('renders logs in form', async () => { 33 - const {findByTestId} = render(<Signin {...mockedProps} />) 34 - 35 - const loginFormView = await findByTestId('loginFormView') 36 - expect(loginFormView).toBeTruthy() 37 - 38 - const loginUsernameInput = await findByTestId('loginUsernameInput') 39 - expect(loginUsernameInput).toBeTruthy() 40 - 41 - fireEvent.changeText(loginUsernameInput, 'testusername') 42 - 43 - const loginPasswordInput = await findByTestId('loginPasswordInput') 44 - expect(loginPasswordInput).toBeTruthy() 45 - 46 - fireEvent.changeText(loginPasswordInput, 'test pass') 47 - 48 - const loginNextButton = await findByTestId('loginNextButton') 49 - expect(loginNextButton).toBeTruthy() 50 - 51 - fireEvent.press(loginNextButton) 52 - 53 - expect(mockedSessionStore.login).toHaveBeenCalled() 54 - }) 55 - 56 - it('renders selects service from login form', async () => { 57 - const keyboardSpy = jest.spyOn(Keyboard, 'dismiss') 58 - const {findByTestId} = render(<Signin {...mockedProps} />) 59 - 60 - const loginSelectServiceButton = await findByTestId( 61 - 'loginSelectServiceButton', 62 - ) 63 - expect(loginSelectServiceButton).toBeTruthy() 64 - 65 - fireEvent.press(loginSelectServiceButton) 66 - 67 - expect(mockedShellStore.openModal).toHaveBeenCalled() 68 - expect(keyboardSpy).toHaveBeenCalled() 69 - }) 70 - 71 - it('renders new password form', async () => { 72 - const {findByTestId} = render(<Signin {...mockedProps} />) 73 - 74 - const forgotPasswordButton = await findByTestId('forgotPasswordButton') 75 - expect(forgotPasswordButton).toBeTruthy() 76 - 77 - fireEvent.press(forgotPasswordButton) 78 - const forgotPasswordView = await findByTestId('forgotPasswordView') 79 - expect(forgotPasswordView).toBeTruthy() 80 - 81 - const forgotPasswordEmail = await findByTestId('forgotPasswordEmail') 82 - expect(forgotPasswordEmail).toBeTruthy() 83 - fireEvent.changeText(forgotPasswordEmail, 'test@email.com') 84 - 85 - const newPasswordButton = await findByTestId('newPasswordButton') 86 - expect(newPasswordButton).toBeTruthy() 87 - fireEvent.press(newPasswordButton) 88 - 89 - expect(requestPasswordResetMock).toHaveBeenCalled() 90 - 91 - const newPasswordView = await findByTestId('newPasswordView') 92 - expect(newPasswordView).toBeTruthy() 93 - 94 - const newPasswordInput = await findByTestId('newPasswordInput') 95 - expect(newPasswordInput).toBeTruthy() 96 - const resetCodeInput = await findByTestId('resetCodeInput') 97 - expect(resetCodeInput).toBeTruthy() 98 - 99 - fireEvent.changeText(newPasswordInput, 'test pass') 100 - fireEvent.changeText(resetCodeInput, 'test reset code') 101 - 102 - const setNewPasswordButton = await findByTestId('setNewPasswordButton') 103 - expect(setNewPasswordButton).toBeTruthy() 104 - 105 - fireEvent.press(setNewPasswordButton) 106 - 107 - expect(resetPasswordMock).toHaveBeenCalled() 108 - }) 109 - 110 - it('renders forgot password form', async () => { 111 - const {findByTestId} = render(<Signin {...mockedProps} />) 112 - 113 - const forgotPasswordButton = await findByTestId('forgotPasswordButton') 114 - expect(forgotPasswordButton).toBeTruthy() 115 - 116 - fireEvent.press(forgotPasswordButton) 117 - const forgotPasswordSelectServiceButton = await findByTestId( 118 - 'forgotPasswordSelectServiceButton', 119 - ) 120 - expect(forgotPasswordSelectServiceButton).toBeTruthy() 121 - 122 - fireEvent.press(forgotPasswordSelectServiceButton) 123 - 124 - expect(mockedShellStore.openModal).toHaveBeenCalled() 125 - }) 126 - })
+3 -3
__tests__/view/shell/mobile/Menu.test.tsx
··· 46 46 }) 47 47 48 48 it("presses notifications menu item' button", () => { 49 - const {getAllByTestId} = render(<Menu {...mockedProps} />) 49 + const {getByTestId} = render(<Menu {...mockedProps} />) 50 50 51 - const menuItemButton = getAllByTestId('menuItemButton') 52 - fireEvent.press(menuItemButton[1]) 51 + const menuItemButton = getByTestId('menuItemButton-Notifications') 52 + fireEvent.press(menuItemButton) 53 53 54 54 expect(onCloseMock).toHaveBeenCalled() 55 55 expect(mockedNavigationStore.switchTo).toHaveBeenCalledWith(1, true)
-13
jest/jestSetup.js
··· 25 25 } 26 26 }) 27 27 28 - jest.mock('@gorhom/bottom-sheet', () => { 29 - const react = require('react-native') 30 - return { 31 - __esModule: true, 32 - default: react.View, 33 - namedExport: { 34 - ...require('react-native-reanimated/mock'), 35 - ...jest.requireActual('@gorhom/bottom-sheet'), 36 - BottomSheetFlatList: react.FlatList, 37 - }, 38 - } 39 - }) 40 - 41 28 jest.mock('rn-fetch-blob', () => ({ 42 29 config: jest.fn().mockReturnThis(), 43 30 cancel: jest.fn(),
+199
jest/test-pds.ts
··· 1 + import {AddressInfo} from 'net' 2 + import os from 'os' 3 + import path from 'path' 4 + import * as crypto from '@atproto/crypto' 5 + import PDSServer, { 6 + Database as PDSDatabase, 7 + MemoryBlobStore, 8 + ServerConfig as PDSServerConfig, 9 + } from '@atproto/pds' 10 + import * as plc from '@atproto/plc' 11 + import AtpApi, {ServiceClient} from '@atproto/api' 12 + 13 + export interface TestUser { 14 + email: string 15 + did: string 16 + declarationCid: string 17 + handle: string 18 + password: string 19 + api: ServiceClient 20 + } 21 + 22 + export interface TestUsers { 23 + alice: TestUser 24 + bob: TestUser 25 + carla: TestUser 26 + } 27 + 28 + export interface TestPDS { 29 + pdsUrl: string 30 + users: TestUsers 31 + close: () => Promise<void> 32 + } 33 + 34 + // NOTE 35 + // deterministic date generator 36 + // we use this to ensure the mock dataset is always the same 37 + // which is very useful when testing 38 + function* dateGen() { 39 + let start = 1657846031914 40 + while (true) { 41 + yield new Date(start).toISOString() 42 + start += 1e3 43 + } 44 + return '' 45 + } 46 + 47 + export async function createServer(): Promise<TestPDS> { 48 + const keypair = await crypto.EcdsaKeypair.create() 49 + 50 + // run plc server 51 + const plcDb = plc.Database.memory() 52 + await plcDb.migrateToLatestOrThrow() 53 + const plcServer = plc.PlcServer.create({db: plcDb}) 54 + const plcListener = await plcServer.start() 55 + const plcPort = (plcListener.address() as AddressInfo).port 56 + const plcUrl = `http://localhost:${plcPort}` 57 + 58 + const recoveryKey = (await crypto.EcdsaKeypair.create()).did() 59 + 60 + const plcClient = new plc.PlcClient(plcUrl) 61 + const serverDid = await plcClient.createDid( 62 + keypair, 63 + recoveryKey, 64 + 'localhost', 65 + 'https://pds.public.url', 66 + ) 67 + 68 + const blobstoreLoc = path.join(os.tmpdir(), crypto.randomStr(5, 'base32')) 69 + 70 + const cfg = new PDSServerConfig({ 71 + debugMode: true, 72 + version: '0.0.0', 73 + scheme: 'http', 74 + hostname: 'localhost', 75 + serverDid, 76 + recoveryKey, 77 + adminPassword: 'admin-pass', 78 + inviteRequired: false, 79 + didPlcUrl: plcUrl, 80 + jwtSecret: 'jwt-secret', 81 + availableUserDomains: ['.test'], 82 + appUrlPasswordReset: 'app://forgot-password', 83 + emailNoReplyAddress: 'noreply@blueskyweb.xyz', 84 + publicUrl: 'https://pds.public.url', 85 + imgUriSalt: '9dd04221f5755bce5f55f47464c27e1e', 86 + imgUriKey: 87 + 'f23ecd142835025f42c3db2cf25dd813956c178392760256211f9d315f8ab4d8', 88 + dbPostgresUrl: process.env.DB_POSTGRES_URL, 89 + blobstoreLocation: `${blobstoreLoc}/blobs`, 90 + blobstoreTmp: `${blobstoreLoc}/tmp`, 91 + }) 92 + 93 + const db = PDSDatabase.memory() 94 + await db.migrateToLatestOrThrow() 95 + const blobstore = new MemoryBlobStore() 96 + 97 + const pds = PDSServer.create({db, blobstore, keypair, config: cfg}) 98 + const pdsServer = await pds.start() 99 + const pdsPort = (pdsServer.address() as AddressInfo).port 100 + const pdsUrl = `http://localhost:${pdsPort}` 101 + const testUsers = await genMockData(pdsUrl) 102 + 103 + return { 104 + pdsUrl, 105 + users: testUsers, 106 + async close() { 107 + await pds.destroy() 108 + await plcServer.destroy() 109 + }, 110 + } 111 + } 112 + 113 + async function genMockData(pdsUrl: string): Promise<TestUsers> { 114 + const date = dateGen() 115 + 116 + const clients = { 117 + loggedout: AtpApi.service(pdsUrl), 118 + alice: AtpApi.service(pdsUrl), 119 + bob: AtpApi.service(pdsUrl), 120 + carla: AtpApi.service(pdsUrl), 121 + } 122 + const users: TestUser[] = [ 123 + { 124 + email: 'alice@test.com', 125 + did: '', 126 + declarationCid: '', 127 + handle: 'alice.test', 128 + password: 'hunter2', 129 + api: clients.alice, 130 + }, 131 + { 132 + email: 'bob@test.com', 133 + did: '', 134 + declarationCid: '', 135 + handle: 'bob.test', 136 + password: 'hunter2', 137 + api: clients.bob, 138 + }, 139 + { 140 + email: 'carla@test.com', 141 + did: '', 142 + declarationCid: '', 143 + handle: 'carla.test', 144 + password: 'hunter2', 145 + api: clients.carla, 146 + }, 147 + ] 148 + const alice = users[0] 149 + const bob = users[1] 150 + const carla = users[2] 151 + 152 + let _i = 1 153 + for (const user of users) { 154 + const res = await clients.loggedout.com.atproto.account.create({ 155 + email: user.email, 156 + handle: user.handle, 157 + password: user.password, 158 + }) 159 + user.api.setHeader('Authorization', `Bearer ${res.data.accessJwt}`) 160 + const {data: profile} = await user.api.app.bsky.actor.getProfile({ 161 + actor: user.handle, 162 + }) 163 + user.did = res.data.did 164 + user.declarationCid = profile.declaration.cid 165 + await user.api.app.bsky.actor.profile.create( 166 + {did: user.did}, 167 + { 168 + displayName: ucfirst(user.handle).slice(0, -5), 169 + description: `Test user ${_i++}`, 170 + }, 171 + ) 172 + } 173 + 174 + // everybody follows everybody 175 + const follow = async (author: TestUser, subject: TestUser) => { 176 + await author.api.app.bsky.graph.follow.create( 177 + {did: author.did}, 178 + { 179 + subject: { 180 + did: subject.did, 181 + declarationCid: subject.declarationCid, 182 + }, 183 + createdAt: date.next().value, 184 + }, 185 + ) 186 + } 187 + await follow(alice, bob) 188 + await follow(alice, carla) 189 + await follow(bob, alice) 190 + await follow(bob, carla) 191 + await follow(carla, alice) 192 + await follow(carla, bob) 193 + 194 + return {alice, bob, carla} 195 + } 196 + 197 + function ucfirst(str: string): string { 198 + return str.at(0)?.toUpperCase() + str.slice(1) 199 + }
+6 -7
jest/test-utils.tsx
··· 4 4 import {RootSiblingParent} from 'react-native-root-siblings' 5 5 import {SafeAreaProvider} from 'react-native-safe-area-context' 6 6 import {RootStoreProvider} from '../src/state' 7 + import {ThemeProvider} from '../src/view/lib/ThemeContext' 7 8 import {mockedRootStore} from '../__mocks__/state-mock' 8 9 9 - const customRender = (ui: any, storeMock?: any) => 10 + const customRender = (ui: any, rootStore?: any) => 10 11 render( 11 12 // eslint-disable-next-line react-native/no-inline-styles 12 13 <GestureHandlerRootView style={{flex: 1}}> 13 14 <RootSiblingParent> 14 15 <RootStoreProvider 15 - value={ 16 - storeMock != null 17 - ? {...mockedRootStore, ...storeMock} 18 - : mockedRootStore 19 - }> 20 - <SafeAreaProvider>{ui}</SafeAreaProvider> 16 + value={rootStore != null ? rootStore : mockedRootStore}> 17 + <ThemeProvider theme="light"> 18 + <SafeAreaProvider>{ui}</SafeAreaProvider> 19 + </ThemeProvider> 21 20 </RootStoreProvider> 22 21 </RootSiblingParent> 23 22 </GestureHandlerRootView>,
+4 -2
package.json
··· 8 8 "web": "react-scripts start", 9 9 "start": "react-native start", 10 10 "clean-cache": "rm -rf node_modules/.cache/babel-loader/*", 11 - "test": "jest", 11 + "test": "jest --forceExit", 12 12 "test-watch": "jest --watchAll", 13 13 "test-ci": "jest --ci --forceExit --reporters=default --reporters=jest-junit", 14 14 "test-coverage": "jest --coverage", ··· 64 64 "react-native-version-number": "^0.3.6", 65 65 "react-native-web": "^0.17.7", 66 66 "rn-fetch-blob": "^0.12.0", 67 - "tlds": "^1.234.0" 67 + "tlds": "^1.234.0", 68 + "zod": "^3.20.2" 68 69 }, 69 70 "devDependencies": { 71 + "@atproto/pds": "^0.0.1", 70 72 "@babel/core": "^7.12.9", 71 73 "@babel/preset-env": "^7.14.0", 72 74 "@babel/runtime": "^7.12.5",
+2 -2
src/state/index.ts
··· 13 13 const ROOT_STATE_STORAGE_KEY = 'root' 14 14 const STATE_FETCH_INTERVAL = 15e3 15 15 16 - export async function setupState() { 16 + export async function setupState(serviceUri = DEFAULT_SERVICE) { 17 17 let rootStore: RootStoreModel 18 18 let data: any 19 19 20 20 libapi.doPolyfill() 21 21 22 - const api = AtpApi.service(DEFAULT_SERVICE) as SessionServiceClient 22 + const api = AtpApi.service(serviceUri) as SessionServiceClient 23 23 rootStore = new RootStoreModel(api) 24 24 try { 25 25 data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {}
+159 -59
src/state/models/session.ts
··· 6 6 ComAtprotoServerGetAccountsConfig as GetAccountsConfig, 7 7 } from '@atproto/api' 8 8 import {isObj, hasProp} from '../lib/type-guards' 9 + import {z} from 'zod' 9 10 import {RootStoreModel} from './root-store' 10 11 import {isNetworkError} from '../../lib/errors' 11 12 12 13 export type ServiceDescription = GetAccountsConfig.OutputSchema 13 14 14 - interface SessionData { 15 - service: string 16 - refreshJwt: string 17 - accessJwt: string 18 - handle: string 19 - did: string 20 - } 15 + export const sessionData = z.object({ 16 + service: z.string(), 17 + refreshJwt: z.string(), 18 + accessJwt: z.string(), 19 + handle: z.string(), 20 + did: z.string(), 21 + }) 22 + export type SessionData = z.infer<typeof sessionData> 23 + 24 + export const accountData = z.object({ 25 + service: z.string(), 26 + refreshJwt: z.string().optional(), 27 + accessJwt: z.string().optional(), 28 + handle: z.string(), 29 + did: z.string(), 30 + displayName: z.string().optional(), 31 + aviUrl: z.string().optional(), 32 + }) 33 + export type AccountData = z.infer<typeof accountData> 21 34 22 35 export class SessionModel { 36 + /** 37 + * Current session data 38 + */ 23 39 data: SessionData | null = null 40 + /** 41 + * A listing of the currently & previous sessions, used for account switching 42 + */ 43 + accounts: AccountData[] = [] 24 44 online = false 25 45 attemptingConnect = false 26 - private _connectPromise: Promise<void> | undefined 46 + private _connectPromise: Promise<boolean> | undefined 27 47 28 48 constructor(public rootStore: RootStoreModel) { 29 49 makeAutoObservable(this, { ··· 37 57 return this.data !== null 38 58 } 39 59 60 + get hasAccounts() { 61 + return this.accounts.length >= 1 62 + } 63 + 64 + get switchableAccounts() { 65 + return this.accounts.filter(acct => acct.did !== this.data?.did) 66 + } 67 + 40 68 serialize(): unknown { 41 69 return { 42 70 data: this.data, 71 + accounts: this.accounts, 43 72 } 44 73 } 45 74 46 75 hydrate(v: unknown) { 76 + this.accounts = [] 47 77 if (isObj(v)) { 48 - if (hasProp(v, 'data') && isObj(v.data)) { 49 - const data: SessionData = { 50 - service: '', 51 - refreshJwt: '', 52 - accessJwt: '', 53 - handle: '', 54 - did: '', 55 - } 56 - if (hasProp(v.data, 'service') && typeof v.data.service === 'string') { 57 - data.service = v.data.service 58 - } 59 - if ( 60 - hasProp(v.data, 'refreshJwt') && 61 - typeof v.data.refreshJwt === 'string' 62 - ) { 63 - data.refreshJwt = v.data.refreshJwt 64 - } 65 - if ( 66 - hasProp(v.data, 'accessJwt') && 67 - typeof v.data.accessJwt === 'string' 68 - ) { 69 - data.accessJwt = v.data.accessJwt 70 - } 71 - if (hasProp(v.data, 'handle') && typeof v.data.handle === 'string') { 72 - data.handle = v.data.handle 73 - } 74 - if (hasProp(v.data, 'did') && typeof v.data.did === 'string') { 75 - data.did = v.data.did 76 - } 77 - if ( 78 - data.service && 79 - data.refreshJwt && 80 - data.accessJwt && 81 - data.handle && 82 - data.did 83 - ) { 84 - this.data = data 78 + if (hasProp(v, 'data') && sessionData.safeParse(v.data)) { 79 + this.data = v.data as SessionData 80 + } 81 + if (hasProp(v, 'accounts') && Array.isArray(v.accounts)) { 82 + for (const account of v.accounts) { 83 + if (accountData.safeParse(account)) { 84 + this.accounts.push(account as AccountData) 85 + } 85 86 } 86 87 } 87 88 } ··· 113 114 } 114 115 } 115 116 117 + /** 118 + * Sets up the XRPC API, must be called before connecting to a service 119 + */ 116 120 private configureApi(): boolean { 117 121 if (!this.data) { 118 122 return false ··· 137 141 return true 138 142 } 139 143 140 - async connect(): Promise<void> { 144 + /** 145 + * Upserts the current session into the accounts 146 + */ 147 + private addSessionToAccounts() { 148 + if (!this.data) { 149 + return 150 + } 151 + const existingAccount = this.accounts.find( 152 + acc => acc.service === this.data?.service && acc.did === this.data.did, 153 + ) 154 + const newAccount = { 155 + service: this.data.service, 156 + refreshJwt: this.data.refreshJwt, 157 + accessJwt: this.data.accessJwt, 158 + handle: this.data.handle, 159 + did: this.data.did, 160 + displayName: this.rootStore.me.displayName, 161 + aviUrl: this.rootStore.me.avatar, 162 + } 163 + if (!existingAccount) { 164 + this.accounts.push(newAccount) 165 + } else { 166 + this.accounts = this.accounts 167 + .filter( 168 + acc => 169 + !(acc.service === this.data?.service && acc.did === this.data.did), 170 + ) 171 + .concat([newAccount]) 172 + } 173 + } 174 + 175 + /** 176 + * Clears any session tokens from the accounts; used on logout. 177 + */ 178 + private clearSessionTokensFromAccounts() { 179 + this.accounts = this.accounts.map(acct => ({ 180 + service: acct.service, 181 + handle: acct.handle, 182 + did: acct.did, 183 + displayName: acct.displayName, 184 + aviUrl: acct.aviUrl, 185 + })) 186 + } 187 + 188 + /** 189 + * Fetches the current session from the service, if possible. 190 + * Requires an existing session (.data) to be populated with access tokens. 191 + */ 192 + async connect(): Promise<boolean> { 141 193 if (this._connectPromise) { 142 194 return this._connectPromise 143 195 } 144 196 this._connectPromise = this._connect() 145 - await this._connectPromise 197 + const res = await this._connectPromise 146 198 this._connectPromise = undefined 199 + return res 147 200 } 148 201 149 - private async _connect(): Promise<void> { 202 + private async _connect(): Promise<boolean> { 150 203 this.attemptingConnect = true 151 204 if (!this.configureApi()) { 152 - return 205 + return false 153 206 } 154 207 155 208 try { ··· 159 212 if (this.rootStore.me.did !== sess.data.did) { 160 213 this.rootStore.me.clear() 161 214 } 162 - this.rootStore.me.load().catch(e => { 163 - this.rootStore.log.error('Failed to fetch local user information', e) 164 - }) 165 - return // success 215 + this.rootStore.me 216 + .load() 217 + .catch(e => { 218 + this.rootStore.log.error( 219 + 'Failed to fetch local user information', 220 + e, 221 + ) 222 + }) 223 + .then(() => { 224 + this.addSessionToAccounts() 225 + }) 226 + return true // success 166 227 } 167 228 } catch (e: any) { 168 229 if (isNetworkError(e)) { 169 230 this.setOnline(false, false) // connection issue 170 - return 231 + return false 171 232 } else { 172 233 this.clear() // invalid session cached 173 234 } 174 235 } 175 236 176 237 this.setOnline(false, false) 238 + return false 177 239 } 178 240 241 + /** 242 + * Helper to fetch the accounts config settings from an account. 243 + */ 179 244 async describeService(service: string): Promise<ServiceDescription> { 180 245 const api = AtpApi.service(service) as SessionServiceClient 181 246 const res = await api.com.atproto.server.getAccountsConfig({}) 182 247 return res.data 183 248 } 184 249 250 + /** 251 + * Create a new session. 252 + */ 185 253 async login({ 186 254 service, 187 255 handle, ··· 203 271 }) 204 272 this.configureApi() 205 273 this.setOnline(true, false) 206 - this.rootStore.me.load().catch(e => { 207 - this.rootStore.log.error('Failed to fetch local user information', e) 274 + this.rootStore.me 275 + .load() 276 + .catch(e => { 277 + this.rootStore.log.error('Failed to fetch local user information', e) 278 + }) 279 + .then(() => { 280 + this.addSessionToAccounts() 281 + }) 282 + } 283 + } 284 + 285 + /** 286 + * Attempt to resume a session that we still have access tokens for. 287 + */ 288 + async resumeSession(account: AccountData): Promise<boolean> { 289 + if (account.accessJwt && account.refreshJwt) { 290 + this.setState({ 291 + service: account.service, 292 + accessJwt: account.accessJwt, 293 + refreshJwt: account.refreshJwt, 294 + handle: account.handle, 295 + did: account.did, 208 296 }) 297 + } else { 298 + return false 209 299 } 300 + return this.connect() 210 301 } 211 302 212 303 async createAccount({ ··· 239 330 }) 240 331 this.rootStore.onboard.start() 241 332 this.configureApi() 242 - this.rootStore.me.load().catch(e => { 243 - this.rootStore.log.error('Failed to fetch local user information', e) 244 - }) 333 + this.rootStore.me 334 + .load() 335 + .catch(e => { 336 + this.rootStore.log.error('Failed to fetch local user information', e) 337 + }) 338 + .then(() => { 339 + this.addSessionToAccounts() 340 + }) 245 341 } 246 342 } 247 343 344 + /** 345 + * Close all sessions across all accounts. 346 + */ 248 347 async logout() { 249 348 if (this.hasSession) { 250 349 this.rootStore.api.com.atproto.session.delete().catch((e: any) => { ··· 254 353 ) 255 354 }) 256 355 } 356 + this.clearSessionTokensFromAccounts() 257 357 this.rootStore.clearAll() 258 358 } 259 359 }
+173 -154
src/view/com/login/CreateAccount.tsx
··· 12 12 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 13 13 import {ComAtprotoAccountCreate} from '@atproto/api' 14 14 import * as EmailValidator from 'email-validator' 15 - import {Logo} from './Logo' 15 + import {LogoTextHero} from './Logo' 16 16 import {Picker} from '../util/Picker' 17 17 import {TextLink} from '../util/Link' 18 18 import {Text} from '../util/text/Text' ··· 25 25 import {useStores, DEFAULT_SERVICE} from '../../../state' 26 26 import {ServiceDescription} from '../../../state/models/session' 27 27 import {ServerInputModal} from '../../../state/models/shell-ui' 28 + import {usePalette} from '../../lib/hooks/usePalette' 28 29 29 30 export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { 31 + const pal = usePalette('default') 30 32 const store = useStores() 31 33 const [isProcessing, setIsProcessing] = useState<boolean>(false) 32 34 const [serviceUrl, setServiceUrl] = useState<string>(DEFAULT_SERVICE) ··· 114 116 } 115 117 } 116 118 117 - const Policies = () => { 118 - if (!serviceDescription) { 119 - return <View /> 120 - } 121 - const tos = validWebLink(serviceDescription.links?.termsOfService) 122 - const pp = validWebLink(serviceDescription.links?.privacyPolicy) 123 - if (!tos && !pp) { 124 - return ( 125 - <View style={styles.policies}> 126 - <View style={[styles.errorIcon, s.mt2]}> 127 - <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> 128 - </View> 129 - <Text style={[s.white, s.pl5, s.flex1]}> 130 - This service has not provided terms of service or a privacy policy. 131 - </Text> 132 - </View> 133 - ) 134 - } 135 - const els = [] 136 - if (tos) { 137 - els.push( 138 - <TextLink 139 - key="tos" 140 - href={tos} 141 - text="Terms of Service" 142 - style={[s.white, s.underline]} 143 - />, 144 - ) 145 - } 146 - if (pp) { 147 - els.push( 148 - <TextLink 149 - key="pp" 150 - href={pp} 151 - text="Privacy Policy" 152 - style={[s.white, s.underline]} 153 - />, 154 - ) 155 - } 156 - if (els.length === 2) { 157 - els.splice( 158 - 1, 159 - 0, 160 - <Text key="and" style={s.white}> 161 - {' '} 162 - and{' '} 163 - </Text>, 164 - ) 165 - } 166 - return ( 167 - <View style={styles.policies}> 168 - <Text style={s.white}> 169 - By creating an account you agree to the {els}. 170 - </Text> 171 - </View> 172 - ) 173 - } 174 - 175 119 const isReady = !!email && !!password && !!handle && is13 176 120 return ( 177 - <ScrollView testID="createAccount" style={{flex: 1}}> 178 - <KeyboardAvoidingView behavior="padding" style={{flex: 1}}> 179 - <View style={styles.logoHero}> 180 - <Logo /> 181 - </View> 121 + <ScrollView testID="createAccount" style={pal.view}> 122 + <KeyboardAvoidingView behavior="padding"> 123 + <LogoTextHero /> 182 124 {error ? ( 183 125 <View style={[styles.error, styles.errorFloating]}> 184 - <View style={styles.errorIcon}> 126 + <View style={[styles.errorIcon]}> 185 127 <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> 186 128 </View> 187 129 <View style={s.flex1}> ··· 189 131 </View> 190 132 </View> 191 133 ) : undefined} 192 - <View style={[styles.group]}> 193 - <View style={styles.groupTitle}> 194 - <Text style={[s.white, s.f18, s.bold]}>Create a new account</Text> 195 - </View> 196 - <View style={styles.groupContent}> 197 - <FontAwesomeIcon icon="globe" style={styles.groupContentIcon} /> 134 + <View style={styles.groupLabel}> 135 + <Text type="sm-bold" style={pal.text}> 136 + Service provider 137 + </Text> 138 + </View> 139 + <View style={[pal.borderDark, styles.group]}> 140 + <View 141 + style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 142 + <FontAwesomeIcon 143 + icon="globe" 144 + style={[pal.textLight, styles.groupContentIcon]} 145 + /> 198 146 <TouchableOpacity 199 147 testID="registerSelectServiceButton" 200 148 style={styles.textBtn} 201 149 onPress={onPressSelectService}> 202 - <Text style={styles.textBtnLabel}> 150 + <Text type="xl" style={[pal.text, styles.textBtnLabel]}> 203 151 {toNiceDomain(serviceUrl)} 204 152 </Text> 205 - <View style={styles.textBtnFakeInnerBtn}> 153 + <View style={[pal.btn, styles.textBtnFakeInnerBtn]}> 206 154 <FontAwesomeIcon 207 155 icon="pen" 208 156 size={12} 209 - style={styles.textBtnFakeInnerBtnIcon} 157 + style={[pal.textLight, styles.textBtnFakeInnerBtnIcon]} 210 158 /> 211 - <Text style={styles.textBtnFakeInnerBtnLabel}>Change</Text> 159 + <Text style={[pal.textLight]}>Change</Text> 212 160 </View> 213 161 </TouchableOpacity> 214 162 </View> 215 - {serviceDescription ? ( 216 - <> 163 + </View> 164 + {serviceDescription ? ( 165 + <> 166 + <View style={styles.groupLabel}> 167 + <Text type="sm-bold" style={pal.text}> 168 + Account details 169 + </Text> 170 + </View> 171 + <View style={[pal.borderDark, styles.group]}> 217 172 {serviceDescription?.inviteCodeRequired ? ( 218 - <View style={styles.groupContent}> 173 + <View 174 + style={[pal.border, styles.groupContent, styles.noTopBorder]}> 219 175 <FontAwesomeIcon 220 176 icon="ticket" 221 - style={styles.groupContentIcon} 177 + style={[pal.textLight, styles.groupContentIcon]} 222 178 /> 223 179 <TextInput 224 - style={[styles.textInput]} 180 + style={[pal.text, styles.textInput]} 225 181 placeholder="Invite code" 226 - placeholderTextColor={colors.blue0} 182 + placeholderTextColor={pal.colors.textLight} 227 183 autoCapitalize="none" 228 184 autoCorrect={false} 229 185 autoFocus ··· 233 189 /> 234 190 </View> 235 191 ) : undefined} 236 - <View style={styles.groupContent}> 192 + <View style={[pal.border, styles.groupContent]}> 237 193 <FontAwesomeIcon 238 194 icon="envelope" 239 - style={styles.groupContentIcon} 195 + style={[pal.textLight, styles.groupContentIcon]} 240 196 /> 241 197 <TextInput 242 198 testID="registerEmailInput" 243 - style={[styles.textInput]} 199 + style={[pal.text, styles.textInput]} 244 200 placeholder="Email address" 245 - placeholderTextColor={colors.blue0} 201 + placeholderTextColor={pal.colors.textLight} 246 202 autoCapitalize="none" 247 203 autoCorrect={false} 248 204 value={email} ··· 250 206 editable={!isProcessing} 251 207 /> 252 208 </View> 253 - <View style={styles.groupContent}> 254 - <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} /> 209 + <View style={[pal.border, styles.groupContent]}> 210 + <FontAwesomeIcon 211 + icon="lock" 212 + style={[pal.textLight, styles.groupContentIcon]} 213 + /> 255 214 <TextInput 256 215 testID="registerPasswordInput" 257 - style={[styles.textInput]} 216 + style={[pal.text, styles.textInput]} 258 217 placeholder="Choose your password" 259 - placeholderTextColor={colors.blue0} 218 + placeholderTextColor={pal.colors.textLight} 260 219 autoCapitalize="none" 261 220 autoCorrect={false} 262 221 secureTextEntry ··· 265 224 editable={!isProcessing} 266 225 /> 267 226 </View> 268 - </> 269 - ) : undefined} 270 - </View> 227 + </View> 228 + </> 229 + ) : undefined} 271 230 {serviceDescription ? ( 272 231 <> 273 - <View style={styles.group}> 274 - <View style={styles.groupTitle}> 275 - <Text style={[s.white, s.f18, s.bold]}> 276 - Choose your username 277 - </Text> 278 - </View> 279 - <View style={styles.groupContent}> 280 - <FontAwesomeIcon icon="at" style={styles.groupContentIcon} /> 232 + <View style={styles.groupLabel}> 233 + <Text type="sm-bold" style={pal.text}> 234 + Choose your username 235 + </Text> 236 + </View> 237 + <View style={[pal.border, styles.group]}> 238 + <View 239 + style={[pal.border, styles.groupContent, styles.noTopBorder]}> 240 + <FontAwesomeIcon 241 + icon="at" 242 + style={[pal.textLight, styles.groupContentIcon]} 243 + /> 281 244 <TextInput 282 245 testID="registerHandleInput" 283 - style={[styles.textInput]} 246 + style={[pal.text, styles.textInput]} 284 247 placeholder="eg alice" 285 - placeholderTextColor={colors.blue0} 248 + placeholderTextColor={pal.colors.textLight} 286 249 autoCapitalize="none" 287 250 value={handle} 288 251 onChangeText={v => setHandle(makeValidHandle(v))} ··· 290 253 /> 291 254 </View> 292 255 {serviceDescription.availableUserDomains.length > 1 && ( 293 - <View style={styles.groupContent}> 256 + <View style={[pal.border, styles.groupContent]}> 294 257 <FontAwesomeIcon 295 258 icon="globe" 296 259 style={styles.groupContentIcon} 297 260 /> 298 261 <Picker 299 - style={styles.picker} 262 + style={[pal.text, styles.picker]} 300 263 labelStyle={styles.pickerLabel} 301 - iconStyle={styles.pickerIcon} 264 + iconStyle={pal.textLight} 302 265 value={userDomain} 303 266 items={serviceDescription.availableUserDomains.map(d => ({ 304 267 label: `.${d}`, ··· 309 272 /> 310 273 </View> 311 274 )} 312 - <View style={styles.groupContent}> 313 - <Text style={[s.white, s.p10]}> 275 + <View style={[pal.border, styles.groupContent]}> 276 + <Text style={[pal.textLight, s.p10]}> 314 277 Your full username will be{' '} 315 - <Text style={[s.white, s.bold]}> 278 + <Text type="md-bold" style={pal.textLight}> 316 279 @{createFullHandle(handle, userDomain)} 317 280 </Text> 318 281 </Text> 319 282 </View> 320 283 </View> 321 - <View style={[styles.group]}> 322 - <View style={styles.groupTitle}> 323 - <Text style={[s.white, s.f18, s.bold]}>Legal</Text> 324 - </View> 325 - <View style={styles.groupContent}> 284 + <View style={styles.groupLabel}> 285 + <Text type="sm-bold" style={pal.text}> 286 + Legal 287 + </Text> 288 + </View> 289 + <View style={[pal.border, styles.group]}> 290 + <View 291 + style={[pal.border, styles.groupContent, styles.noTopBorder]}> 326 292 <TouchableOpacity 327 293 testID="registerIs13Input" 328 294 style={styles.textBtn} 329 295 onPress={() => setIs13(!is13)}> 330 - <View style={is13 ? styles.checkboxFilled : styles.checkbox}> 296 + <View 297 + style={[ 298 + pal.border, 299 + is13 ? styles.checkboxFilled : styles.checkbox, 300 + ]}> 331 301 {is13 && ( 332 302 <FontAwesomeIcon icon="check" style={s.blue3} size={14} /> 333 303 )} 334 304 </View> 335 - <Text style={[styles.textBtnLabel, s.f16]}> 305 + <Text style={[pal.text, styles.textBtnLabel]}> 336 306 I am 13 years old or older 337 307 </Text> 338 308 </TouchableOpacity> 339 309 </View> 340 310 </View> 341 - <Policies /> 311 + <Policies serviceDescription={serviceDescription} /> 342 312 </> 343 313 ) : undefined} 344 314 <View style={[s.flexRow, s.pl20, s.pr20]}> 345 315 <TouchableOpacity onPress={onPressBack}> 346 - <Text style={[s.white, s.f18, s.pl5]}>Back</Text> 316 + <Text type="xl" style={pal.link}> 317 + Back 318 + </Text> 347 319 </TouchableOpacity> 348 320 <View style={s.flex1} /> 349 321 {isReady ? ( ··· 351 323 testID="createAccountButton" 352 324 onPress={onPressNext}> 353 325 {isProcessing ? ( 354 - <ActivityIndicator color="#fff" /> 326 + <ActivityIndicator /> 355 327 ) : ( 356 - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text> 328 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 329 + Next 330 + </Text> 357 331 )} 358 332 </TouchableOpacity> 359 333 ) : !serviceDescription && error ? ( 360 334 <TouchableOpacity 361 335 testID="registerRetryButton" 362 336 onPress={onPressRetryConnect}> 363 - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Retry</Text> 337 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 338 + Retry 339 + </Text> 364 340 </TouchableOpacity> 365 341 ) : !serviceDescription ? ( 366 342 <> 367 343 <ActivityIndicator color="#fff" /> 368 - <Text style={[s.white, s.f18, s.pl5, s.pr5]}>Connecting...</Text> 344 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 345 + Connecting... 346 + </Text> 369 347 </> 370 348 ) : undefined} 371 349 </View> ··· 375 353 ) 376 354 } 377 355 356 + const Policies = ({ 357 + serviceDescription, 358 + }: { 359 + serviceDescription: ServiceDescription 360 + }) => { 361 + const pal = usePalette('default') 362 + if (!serviceDescription) { 363 + return <View /> 364 + } 365 + const tos = validWebLink(serviceDescription.links?.termsOfService) 366 + const pp = validWebLink(serviceDescription.links?.privacyPolicy) 367 + if (!tos && !pp) { 368 + return ( 369 + <View style={styles.policies}> 370 + <View style={[styles.errorIcon, {borderColor: pal.colors.text}, s.mt2]}> 371 + <FontAwesomeIcon icon="exclamation" style={pal.textLight} size={10} /> 372 + </View> 373 + <Text style={[pal.textLight, s.pl5, s.flex1]}> 374 + This service has not provided terms of service or a privacy policy. 375 + </Text> 376 + </View> 377 + ) 378 + } 379 + const els = [] 380 + if (tos) { 381 + els.push( 382 + <TextLink 383 + key="tos" 384 + href={tos} 385 + text="Terms of Service" 386 + style={[pal.link, s.underline]} 387 + />, 388 + ) 389 + } 390 + if (pp) { 391 + els.push( 392 + <TextLink 393 + key="pp" 394 + href={pp} 395 + text="Privacy Policy" 396 + style={[pal.link, s.underline]} 397 + />, 398 + ) 399 + } 400 + if (els.length === 2) { 401 + els.splice( 402 + 1, 403 + 0, 404 + <Text key="and" style={pal.textLight}> 405 + {' '} 406 + and{' '} 407 + </Text>, 408 + ) 409 + } 410 + return ( 411 + <View style={styles.policies}> 412 + <Text style={pal.textLight}> 413 + By creating an account you agree to the {els}. 414 + </Text> 415 + </View> 416 + ) 417 + } 418 + 378 419 function validWebLink(url?: string): string | undefined { 379 420 return url && (url.startsWith('http://') || url.startsWith('https://')) 380 421 ? url ··· 382 423 } 383 424 384 425 const styles = StyleSheet.create({ 426 + noTopBorder: { 427 + borderTopWidth: 0, 428 + }, 385 429 logoHero: { 386 430 paddingTop: 30, 387 431 paddingBottom: 40, 388 432 }, 389 433 group: { 390 434 borderWidth: 1, 391 - borderColor: colors.white, 392 435 borderRadius: 10, 393 436 marginBottom: 20, 394 437 marginHorizontal: 20, 395 - backgroundColor: colors.blue3, 396 438 }, 397 - groupTitle: { 398 - flexDirection: 'row', 399 - alignItems: 'center', 400 - paddingVertical: 8, 401 - paddingHorizontal: 12, 439 + groupLabel: { 440 + paddingHorizontal: 20, 441 + paddingBottom: 5, 402 442 }, 403 443 groupContent: { 404 444 borderTopWidth: 1, 405 - borderTopColor: colors.blue1, 406 445 flexDirection: 'row', 407 446 alignItems: 'center', 408 447 }, 409 448 groupContentIcon: { 410 - color: 'white', 411 449 marginLeft: 10, 412 450 }, 413 451 textInput: { 414 452 flex: 1, 415 453 width: '100%', 416 - backgroundColor: colors.blue3, 417 - color: colors.white, 418 454 paddingVertical: 10, 419 455 paddingHorizontal: 12, 420 - fontSize: 18, 456 + fontSize: 17, 457 + letterSpacing: 0.25, 458 + fontWeight: '400', 421 459 borderRadius: 10, 422 460 }, 423 461 textBtn: { ··· 427 465 }, 428 466 textBtnLabel: { 429 467 flex: 1, 430 - color: colors.white, 431 468 paddingVertical: 10, 432 469 paddingHorizontal: 12, 433 - fontSize: 18, 434 470 }, 435 471 textBtnFakeInnerBtn: { 436 472 flexDirection: 'row', 437 473 alignItems: 'center', 438 - backgroundColor: colors.blue2, 439 474 borderRadius: 6, 440 475 paddingVertical: 6, 441 476 paddingHorizontal: 8, 442 477 marginHorizontal: 6, 443 478 }, 444 479 textBtnFakeInnerBtnIcon: { 445 - color: colors.white, 446 480 marginRight: 4, 447 481 }, 448 - textBtnFakeInnerBtnLabel: { 449 - color: colors.white, 450 - }, 451 482 picker: { 452 483 flex: 1, 453 484 width: '100%', 454 - backgroundColor: colors.blue3, 455 - color: colors.white, 456 485 paddingVertical: 10, 457 486 paddingHorizontal: 12, 458 - fontSize: 18, 487 + fontSize: 17, 459 488 borderRadius: 10, 460 489 }, 461 490 pickerLabel: { 462 - color: colors.white, 463 - fontSize: 18, 464 - }, 465 - pickerIcon: { 466 - color: colors.white, 491 + fontSize: 17, 467 492 }, 468 493 checkbox: { 469 494 borderWidth: 1, 470 - borderColor: colors.white, 471 495 borderRadius: 2, 472 496 width: 16, 473 497 height: 16, ··· 475 499 }, 476 500 checkboxFilled: { 477 501 borderWidth: 1, 478 - borderColor: colors.white, 479 - backgroundColor: colors.white, 480 502 borderRadius: 2, 481 503 width: 16, 482 504 height: 16, ··· 489 511 paddingBottom: 20, 490 512 }, 491 513 error: { 492 - borderWidth: 1, 493 - borderColor: colors.red5, 494 514 backgroundColor: colors.red4, 495 515 flexDirection: 'row', 496 516 alignItems: 'center', ··· 509 529 errorIcon: { 510 530 borderWidth: 1, 511 531 borderColor: colors.white, 512 - color: colors.white, 513 532 borderRadius: 30, 514 533 width: 16, 515 534 height: 16,
+34 -8
src/view/com/login/Logo.tsx
··· 1 1 import React from 'react' 2 2 import {StyleSheet, View} from 'react-native' 3 + import LinearGradient from 'react-native-linear-gradient' 3 4 import Svg, {Circle, Line, Text as SvgText} from 'react-native-svg' 5 + import {s, gradients} from '../../lib/styles' 6 + import {Text} from '../util/text/Text' 4 7 5 - export const Logo = () => { 8 + export const Logo = ({color, size = 100}: {color: string; size?: number}) => { 6 9 return ( 7 10 <View style={styles.logo}> 8 - <Svg width="100" height="100"> 11 + <Svg width={size} height={size} viewBox="0 0 100 100"> 9 12 <Circle 10 13 cx="50" 11 14 cy="50" 12 15 r="46" 13 16 fill="none" 14 - stroke="white" 17 + stroke={color} 15 18 strokeWidth={2} 16 19 /> 17 - <Line stroke="white" strokeWidth={1} x1="30" x2="30" y1="0" y2="100" /> 18 - <Line stroke="white" strokeWidth={1} x1="74" x2="74" y1="0" y2="100" /> 19 - <Line stroke="white" strokeWidth={1} x1="0" x2="100" y1="22" y2="22" /> 20 - <Line stroke="white" strokeWidth={1} x1="0" x2="100" y1="74" y2="74" /> 20 + <Line stroke={color} strokeWidth={1} x1="30" x2="30" y1="0" y2="100" /> 21 + <Line stroke={color} strokeWidth={1} x1="74" x2="74" y1="0" y2="100" /> 22 + <Line stroke={color} strokeWidth={1} x1="0" x2="100" y1="22" y2="22" /> 23 + <Line stroke={color} strokeWidth={1} x1="0" x2="100" y1="74" y2="74" /> 21 24 <SvgText 22 25 fill="none" 23 - stroke="white" 26 + stroke={color} 24 27 strokeWidth={2} 25 28 fontSize="60" 26 29 fontWeight="bold" ··· 34 37 ) 35 38 } 36 39 40 + export const LogoTextHero = () => { 41 + return ( 42 + <LinearGradient 43 + colors={[gradients.blue.start, gradients.blue.end]} 44 + start={{x: 0, y: 0}} 45 + end={{x: 1, y: 1}} 46 + style={[styles.textHero]}> 47 + <Logo color="white" size={40} /> 48 + <Text type="title-lg" style={[s.white, s.pl10]}> 49 + Bluesky 50 + </Text> 51 + </LinearGradient> 52 + ) 53 + } 54 + 37 55 const styles = StyleSheet.create({ 38 56 logo: { 39 57 flexDirection: 'row', 40 58 justifyContent: 'center', 59 + }, 60 + textHero: { 61 + flexDirection: 'row', 62 + alignItems: 'center', 63 + justifyContent: 'center', 64 + paddingRight: 20, 65 + paddingVertical: 15, 66 + marginBottom: 20, 41 67 }, 42 68 })
+388 -201
src/view/com/login/Signin.tsx
··· 11 11 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 12 12 import * as EmailValidator from 'email-validator' 13 13 import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api' 14 - import {Logo} from './Logo' 14 + import {LogoTextHero} from './Logo' 15 15 import {Text} from '../util/text/Text' 16 + import {UserAvatar} from '../util/UserAvatar' 16 17 import {s, colors} from '../../lib/styles' 17 18 import {createFullHandle, toNiceDomain} from '../../../lib/strings' 18 19 import {useStores, RootStoreModel, DEFAULT_SERVICE} from '../../../state' 19 20 import {ServiceDescription} from '../../../state/models/session' 20 21 import {ServerInputModal} from '../../../state/models/shell-ui' 22 + import {AccountData} from '../../../state/models/session' 21 23 import {isNetworkError} from '../../../lib/errors' 24 + import {usePalette} from '../../lib/hooks/usePalette' 22 25 23 26 enum Forms { 24 27 Login, 28 + ChooseAccount, 25 29 ForgotPassword, 26 30 SetNewPassword, 27 31 PasswordUpdated, 28 32 } 29 33 30 34 export const Signin = ({onPressBack}: {onPressBack: () => void}) => { 35 + const pal = usePalette('default') 31 36 const store = useStores() 32 37 const [error, setError] = useState<string>('') 33 38 const [retryDescribeTrigger, setRetryDescribeTrigger] = useState<any>({}) ··· 35 40 const [serviceDescription, setServiceDescription] = useState< 36 41 ServiceDescription | undefined 37 42 >(undefined) 38 - const [currentForm, setCurrentForm] = useState<Forms>(Forms.Login) 43 + const [initialHandle, setInitialHandle] = useState<string>('') 44 + const [currentForm, setCurrentForm] = useState<Forms>( 45 + store.session.hasAccounts ? Forms.ChooseAccount : Forms.Login, 46 + ) 47 + 48 + const onSelectAccount = (account?: AccountData) => { 49 + if (account?.service) { 50 + setServiceUrl(account.service) 51 + } 52 + setInitialHandle(account?.handle || '') 53 + setCurrentForm(Forms.Login) 54 + } 39 55 40 56 const gotoForm = (form: Forms) => () => { 41 57 setError('') ··· 73 89 const onPressRetryConnect = () => setRetryDescribeTrigger({}) 74 90 75 91 return ( 76 - <KeyboardAvoidingView testID="signIn" behavior="padding" style={{flex: 1}}> 77 - <View style={styles.logoHero}> 78 - <Logo /> 79 - </View> 92 + <KeyboardAvoidingView testID="signIn" behavior="padding" style={[pal.view]}> 80 93 {currentForm === Forms.Login ? ( 81 94 <LoginForm 82 95 store={store} 83 96 error={error} 84 97 serviceUrl={serviceUrl} 85 98 serviceDescription={serviceDescription} 99 + initialHandle={initialHandle} 86 100 setError={setError} 87 101 setServiceUrl={setServiceUrl} 88 102 onPressBack={onPressBack} ··· 90 104 onPressRetryConnect={onPressRetryConnect} 91 105 /> 92 106 ) : undefined} 107 + {currentForm === Forms.ChooseAccount ? ( 108 + <ChooseAccountForm 109 + store={store} 110 + onSelectAccount={onSelectAccount} 111 + onPressBack={onPressBack} 112 + /> 113 + ) : undefined} 93 114 {currentForm === Forms.ForgotPassword ? ( 94 115 <ForgotPasswordForm 95 116 store={store} ··· 119 140 ) 120 141 } 121 142 143 + const ChooseAccountForm = ({ 144 + store, 145 + onSelectAccount, 146 + onPressBack, 147 + }: { 148 + store: RootStoreModel 149 + onSelectAccount: (account?: AccountData) => void 150 + onPressBack: () => void 151 + }) => { 152 + const pal = usePalette('default') 153 + const [isProcessing, setIsProcessing] = React.useState(false) 154 + 155 + const onTryAccount = async (account: AccountData) => { 156 + if (account.accessJwt && account.refreshJwt) { 157 + setIsProcessing(true) 158 + if (await store.session.resumeSession(account)) { 159 + setIsProcessing(false) 160 + return 161 + } 162 + setIsProcessing(false) 163 + } 164 + onSelectAccount(account) 165 + } 166 + 167 + return ( 168 + <View testID="chooseAccountForm"> 169 + <LogoTextHero /> 170 + <Text type="sm-bold" style={[pal.text, styles.groupLabel]}> 171 + Sign in as... 172 + </Text> 173 + {store.session.accounts.map(account => ( 174 + <TouchableOpacity 175 + testID={`chooseAccountBtn-${account.handle}`} 176 + key={account.did} 177 + style={[pal.borderDark, styles.group, s.mb5]} 178 + onPress={() => onTryAccount(account)}> 179 + <View 180 + style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 181 + <View style={s.p10}> 182 + <UserAvatar 183 + displayName={account.displayName} 184 + handle={account.handle} 185 + avatar={account.aviUrl} 186 + size={30} 187 + /> 188 + </View> 189 + <Text style={styles.accountText}> 190 + <Text type="lg-bold" style={pal.text}> 191 + {account.displayName || account.handle}{' '} 192 + </Text> 193 + <Text type="lg" style={[pal.textLight]}> 194 + {account.handle} 195 + </Text> 196 + </Text> 197 + <FontAwesomeIcon 198 + icon="angle-right" 199 + size={16} 200 + style={[pal.text, s.mr10]} 201 + /> 202 + </View> 203 + </TouchableOpacity> 204 + ))} 205 + <TouchableOpacity 206 + testID="chooseNewAccountBtn" 207 + style={[pal.borderDark, styles.group]} 208 + onPress={() => onSelectAccount(undefined)}> 209 + <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 210 + <View style={s.p10}> 211 + <View 212 + style={[pal.btn, {width: 30, height: 30, borderRadius: 15}]} 213 + /> 214 + </View> 215 + <Text style={styles.accountText}> 216 + <Text type="lg" style={pal.text}> 217 + Other account 218 + </Text> 219 + </Text> 220 + <FontAwesomeIcon 221 + icon="angle-right" 222 + size={16} 223 + style={[pal.text, s.mr10]} 224 + /> 225 + </View> 226 + </TouchableOpacity> 227 + <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 228 + <TouchableOpacity onPress={onPressBack}> 229 + <Text type="xl" style={[pal.link, s.pl5]}> 230 + Back 231 + </Text> 232 + </TouchableOpacity> 233 + <View style={s.flex1} /> 234 + {isProcessing && <ActivityIndicator />} 235 + </View> 236 + </View> 237 + ) 238 + } 239 + 122 240 const LoginForm = ({ 123 241 store, 124 242 error, 125 243 serviceUrl, 126 244 serviceDescription, 245 + initialHandle, 127 246 setError, 128 247 setServiceUrl, 129 248 onPressRetryConnect, ··· 134 253 error: string 135 254 serviceUrl: string 136 255 serviceDescription: ServiceDescription | undefined 256 + initialHandle: string 137 257 setError: (v: string) => void 138 258 setServiceUrl: (v: string) => void 139 259 onPressRetryConnect: () => void 140 260 onPressBack: () => void 141 261 onPressForgotPassword: () => void 142 262 }) => { 263 + const pal = usePalette('default') 143 264 const [isProcessing, setIsProcessing] = useState<boolean>(false) 144 - const [handle, setHandle] = useState<string>('') 265 + const [handle, setHandle] = useState<string>(initialHandle) 145 266 const [password, setPassword] = useState<string>('') 146 267 147 268 const onPressSelectService = () => { ··· 197 318 198 319 const isReady = !!serviceDescription && !!handle && !!password 199 320 return ( 200 - <> 201 - <View testID="loginFormView" style={styles.group}> 202 - <TouchableOpacity 203 - testID="loginSelectServiceButton" 204 - style={[styles.groupTitle, {paddingRight: 0, paddingVertical: 6}]} 205 - onPress={onPressSelectService}> 206 - <Text style={[s.flex1, s.white, s.f18, s.bold]} numberOfLines={1}> 207 - Sign in to {toNiceDomain(serviceUrl)} 208 - </Text> 209 - <View style={styles.textBtnFakeInnerBtn}> 210 - <FontAwesomeIcon 211 - icon="pen" 212 - size={12} 213 - style={styles.textBtnFakeInnerBtnIcon} 214 - /> 215 - <Text style={styles.textBtnFakeInnerBtnLabel}>Change</Text> 216 - </View> 217 - </TouchableOpacity> 218 - <View style={styles.groupContent}> 219 - <FontAwesomeIcon icon="at" style={styles.groupContentIcon} /> 321 + <View testID="loginForm"> 322 + <LogoTextHero /> 323 + <Text type="sm-bold" style={[pal.text, styles.groupLabel]}> 324 + Sign into 325 + </Text> 326 + <View style={[pal.borderDark, styles.group]}> 327 + <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 328 + <FontAwesomeIcon 329 + icon="globe" 330 + style={[pal.textLight, styles.groupContentIcon]} 331 + /> 332 + <TouchableOpacity 333 + testID="loginSelectServiceButton" 334 + style={styles.textBtn} 335 + onPress={onPressSelectService}> 336 + <Text type="xl" style={[pal.text, styles.textBtnLabel]}> 337 + {toNiceDomain(serviceUrl)} 338 + </Text> 339 + <View style={[pal.btn, styles.textBtnFakeInnerBtn]}> 340 + <FontAwesomeIcon icon="pen" size={12} style={pal.textLight} /> 341 + </View> 342 + </TouchableOpacity> 343 + </View> 344 + </View> 345 + <Text type="sm-bold" style={[pal.text, styles.groupLabel]}> 346 + Account 347 + </Text> 348 + <View style={[pal.borderDark, styles.group]}> 349 + <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 350 + <FontAwesomeIcon 351 + icon="at" 352 + style={[pal.textLight, styles.groupContentIcon]} 353 + /> 220 354 <TextInput 221 355 testID="loginUsernameInput" 222 - style={styles.textInput} 356 + style={[pal.text, styles.textInput]} 223 357 placeholder="Username" 224 - placeholderTextColor={colors.blue0} 358 + placeholderTextColor={pal.colors.textLight} 225 359 autoCapitalize="none" 226 360 autoFocus 227 361 autoCorrect={false} ··· 230 364 editable={!isProcessing} 231 365 /> 232 366 </View> 233 - <View style={styles.groupContent}> 234 - <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} /> 367 + <View style={[pal.borderDark, styles.groupContent]}> 368 + <FontAwesomeIcon 369 + icon="lock" 370 + style={[pal.textLight, styles.groupContentIcon]} 371 + /> 235 372 <TextInput 236 373 testID="loginPasswordInput" 237 - style={styles.textInput} 374 + style={[pal.text, styles.textInput]} 238 375 placeholder="Password" 239 - placeholderTextColor={colors.blue0} 376 + placeholderTextColor={pal.colors.textLight} 240 377 autoCapitalize="none" 241 378 autoCorrect={false} 242 379 secureTextEntry ··· 248 385 testID="forgotPasswordButton" 249 386 style={styles.textInputInnerBtn} 250 387 onPress={onPressForgotPassword}> 251 - <Text style={styles.textInputInnerBtnLabel}>Forgot</Text> 388 + <Text style={pal.link}>Forgot</Text> 252 389 </TouchableOpacity> 253 390 </View> 254 391 </View> ··· 264 401 ) : undefined} 265 402 <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 266 403 <TouchableOpacity onPress={onPressBack}> 267 - <Text style={[s.white, s.f18, s.pl5]}>Back</Text> 404 + <Text type="xl" style={[pal.link, s.pl5]}> 405 + Back 406 + </Text> 268 407 </TouchableOpacity> 269 408 <View style={s.flex1} /> 270 409 {!serviceDescription && error ? ( 271 410 <TouchableOpacity 272 411 testID="loginRetryButton" 273 412 onPress={onPressRetryConnect}> 274 - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Retry</Text> 413 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 414 + Retry 415 + </Text> 275 416 </TouchableOpacity> 276 417 ) : !serviceDescription ? ( 277 418 <> 278 - <ActivityIndicator color="#fff" /> 279 - <Text style={[s.white, s.f18, s.pl10]}>Connecting...</Text> 419 + <ActivityIndicator /> 420 + <Text type="xl" style={[pal.textLight, s.pl10]}> 421 + Connecting... 422 + </Text> 280 423 </> 281 424 ) : isProcessing ? ( 282 - <ActivityIndicator color="#fff" /> 425 + <ActivityIndicator /> 283 426 ) : isReady ? ( 284 427 <TouchableOpacity testID="loginNextButton" onPress={onPressNext}> 285 - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text> 428 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 429 + Next 430 + </Text> 286 431 </TouchableOpacity> 287 432 ) : undefined} 288 433 </View> 289 - </> 434 + </View> 290 435 ) 291 436 } 292 437 ··· 309 454 onPressBack: () => void 310 455 onEmailSent: () => void 311 456 }) => { 457 + const pal = usePalette('default') 312 458 const [isProcessing, setIsProcessing] = useState<boolean>(false) 313 459 const [email, setEmail] = useState<string>('') 314 460 ··· 344 490 345 491 return ( 346 492 <> 347 - <Text style={styles.screenTitle}>Reset password</Text> 348 - <Text style={styles.instructions}> 349 - Enter the email you used to create your account. We'll send you a "reset 350 - code" so you can set a new password. 351 - </Text> 352 - <View testID="forgotPasswordView" style={styles.group}> 353 - <TouchableOpacity 354 - testID="forgotPasswordSelectServiceButton" 355 - style={[styles.groupContent, {borderTopWidth: 0}]} 356 - onPress={onPressSelectService}> 357 - <FontAwesomeIcon icon="globe" style={styles.groupContentIcon} /> 358 - <Text style={styles.textInput} numberOfLines={1}> 359 - {toNiceDomain(serviceUrl)} 360 - </Text> 361 - <View style={styles.textBtnFakeInnerBtn}> 493 + <LogoTextHero /> 494 + <View> 495 + <Text type="title-lg" style={[pal.text, styles.screenTitle]}> 496 + Reset password 497 + </Text> 498 + <Text type="md" style={[pal.text, styles.instructions]}> 499 + Enter the email you used to create your account. We'll send you a 500 + "reset code" so you can set a new password. 501 + </Text> 502 + <View 503 + testID="forgotPasswordView" 504 + style={[pal.borderDark, pal.view, styles.group]}> 505 + <TouchableOpacity 506 + testID="forgotPasswordSelectServiceButton" 507 + style={[pal.borderDark, styles.groupContent, styles.noTopBorder]} 508 + onPress={onPressSelectService}> 509 + <FontAwesomeIcon 510 + icon="globe" 511 + style={[pal.textLight, styles.groupContentIcon]} 512 + /> 513 + <Text style={[pal.text, styles.textInput]} numberOfLines={1}> 514 + {toNiceDomain(serviceUrl)} 515 + </Text> 516 + <View style={[pal.btn, styles.textBtnFakeInnerBtn]}> 517 + <FontAwesomeIcon icon="pen" size={12} style={pal.text} /> 518 + </View> 519 + </TouchableOpacity> 520 + <View style={[pal.borderDark, styles.groupContent]}> 362 521 <FontAwesomeIcon 363 - icon="pen" 364 - size={12} 365 - style={styles.textBtnFakeInnerBtnIcon} 522 + icon="envelope" 523 + style={[pal.textLight, styles.groupContentIcon]} 366 524 /> 367 - <Text style={styles.textBtnFakeInnerBtnLabel}>Change</Text> 525 + <TextInput 526 + testID="forgotPasswordEmail" 527 + style={[pal.text, styles.textInput]} 528 + placeholder="Email address" 529 + placeholderTextColor={pal.colors.textLight} 530 + autoCapitalize="none" 531 + autoFocus 532 + autoCorrect={false} 533 + value={email} 534 + onChangeText={setEmail} 535 + editable={!isProcessing} 536 + /> 368 537 </View> 369 - </TouchableOpacity> 370 - <View style={styles.groupContent}> 371 - <FontAwesomeIcon icon="envelope" style={styles.groupContentIcon} /> 372 - <TextInput 373 - testID="forgotPasswordEmail" 374 - style={styles.textInput} 375 - placeholder="Email address" 376 - placeholderTextColor={colors.blue0} 377 - autoCapitalize="none" 378 - autoFocus 379 - autoCorrect={false} 380 - value={email} 381 - onChangeText={setEmail} 382 - editable={!isProcessing} 383 - /> 384 538 </View> 385 - </View> 386 - {error ? ( 387 - <View style={styles.error}> 388 - <View style={styles.errorIcon}> 389 - <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> 539 + {error ? ( 540 + <View style={styles.error}> 541 + <View style={styles.errorIcon}> 542 + <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> 543 + </View> 544 + <View style={s.flex1}> 545 + <Text style={[s.white, s.bold]}>{error}</Text> 546 + </View> 390 547 </View> 391 - <View style={s.flex1}> 392 - <Text style={[s.white, s.bold]}>{error}</Text> 393 - </View> 394 - </View> 395 - ) : undefined} 396 - <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 397 - <TouchableOpacity onPress={onPressBack}> 398 - <Text style={[s.white, s.f18, s.pl5]}>Back</Text> 399 - </TouchableOpacity> 400 - <View style={s.flex1} /> 401 - {!serviceDescription || isProcessing ? ( 402 - <ActivityIndicator color="#fff" /> 403 - ) : !email ? ( 404 - <Text style={[s.blue1, s.f18, s.bold, s.pr5]}>Next</Text> 405 - ) : ( 406 - <TouchableOpacity testID="newPasswordButton" onPress={onPressNext}> 407 - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text> 548 + ) : undefined} 549 + <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 550 + <TouchableOpacity onPress={onPressBack}> 551 + <Text type="xl" style={[pal.link, s.pl5]}> 552 + Back 553 + </Text> 408 554 </TouchableOpacity> 409 - )} 410 - {!serviceDescription || isProcessing ? ( 411 - <Text style={[s.white, s.f18, s.pl10]}>Processing...</Text> 412 - ) : undefined} 555 + <View style={s.flex1} /> 556 + {!serviceDescription || isProcessing ? ( 557 + <ActivityIndicator /> 558 + ) : !email ? ( 559 + <Text type="xl-bold" style={[pal.link, s.pr5, {opacity: 0.5}]}> 560 + Next 561 + </Text> 562 + ) : ( 563 + <TouchableOpacity testID="newPasswordButton" onPress={onPressNext}> 564 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 565 + Next 566 + </Text> 567 + </TouchableOpacity> 568 + )} 569 + {!serviceDescription || isProcessing ? ( 570 + <Text type="xl" style={[pal.textLight, s.pl10]}> 571 + Processing... 572 + </Text> 573 + ) : undefined} 574 + </View> 413 575 </View> 414 576 </> 415 577 ) ··· 430 592 onPressBack: () => void 431 593 onPasswordSet: () => void 432 594 }) => { 595 + const pal = usePalette('default') 433 596 const [isProcessing, setIsProcessing] = useState<boolean>(false) 434 597 const [resetCode, setResetCode] = useState<string>('') 435 598 const [password, setPassword] = useState<string>('') ··· 458 621 459 622 return ( 460 623 <> 461 - <Text style={styles.screenTitle}>Set new password</Text> 462 - <Text style={styles.instructions}> 463 - You will receive an email with a "reset code." Enter that code here, 464 - then enter your new password. 465 - </Text> 466 - <View testID="newPasswordView" style={styles.group}> 467 - <View style={[styles.groupContent, {borderTopWidth: 0}]}> 468 - <FontAwesomeIcon icon="ticket" style={styles.groupContentIcon} /> 469 - <TextInput 470 - testID="resetCodeInput" 471 - style={[styles.textInput]} 472 - placeholder="Reset code" 473 - placeholderTextColor={colors.blue0} 474 - autoCapitalize="none" 475 - autoCorrect={false} 476 - autoFocus 477 - value={resetCode} 478 - onChangeText={setResetCode} 479 - editable={!isProcessing} 480 - /> 481 - </View> 482 - <View style={styles.groupContent}> 483 - <FontAwesomeIcon icon="lock" style={styles.groupContentIcon} /> 484 - <TextInput 485 - testID="newPasswordInput" 486 - style={styles.textInput} 487 - placeholder="New password" 488 - placeholderTextColor={colors.blue0} 489 - autoCapitalize="none" 490 - autoCorrect={false} 491 - secureTextEntry 492 - value={password} 493 - onChangeText={setPassword} 494 - editable={!isProcessing} 495 - /> 496 - </View> 497 - </View> 498 - {error ? ( 499 - <View style={styles.error}> 500 - <View style={styles.errorIcon}> 501 - <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> 624 + <LogoTextHero /> 625 + <View> 626 + <Text type="title-lg" style={[pal.text, styles.screenTitle]}> 627 + Set new password 628 + </Text> 629 + <Text type="lg" style={[pal.text, styles.instructions]}> 630 + You will receive an email with a "reset code." Enter that code here, 631 + then enter your new password. 632 + </Text> 633 + <View 634 + testID="newPasswordView" 635 + style={[pal.view, pal.borderDark, styles.group]}> 636 + <View 637 + style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> 638 + <FontAwesomeIcon 639 + icon="ticket" 640 + style={[pal.textLight, styles.groupContentIcon]} 641 + /> 642 + <TextInput 643 + testID="resetCodeInput" 644 + style={[pal.text, styles.textInput]} 645 + placeholder="Reset code" 646 + placeholderTextColor={pal.colors.textLight} 647 + autoCapitalize="none" 648 + autoCorrect={false} 649 + autoFocus 650 + value={resetCode} 651 + onChangeText={setResetCode} 652 + editable={!isProcessing} 653 + /> 502 654 </View> 503 - <View style={s.flex1}> 504 - <Text style={[s.white, s.bold]}>{error}</Text> 655 + <View style={[pal.borderDark, styles.groupContent]}> 656 + <FontAwesomeIcon 657 + icon="lock" 658 + style={[pal.textLight, styles.groupContentIcon]} 659 + /> 660 + <TextInput 661 + testID="newPasswordInput" 662 + style={[pal.text, styles.textInput]} 663 + placeholder="New password" 664 + placeholderTextColor={pal.colors.textLight} 665 + autoCapitalize="none" 666 + autoCorrect={false} 667 + secureTextEntry 668 + value={password} 669 + onChangeText={setPassword} 670 + editable={!isProcessing} 671 + /> 505 672 </View> 506 673 </View> 507 - ) : undefined} 508 - <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 509 - <TouchableOpacity onPress={onPressBack}> 510 - <Text style={[s.white, s.f18, s.pl5]}>Back</Text> 511 - </TouchableOpacity> 512 - <View style={s.flex1} /> 513 - {isProcessing ? ( 514 - <ActivityIndicator color="#fff" /> 515 - ) : !resetCode || !password ? ( 516 - <Text style={[s.blue1, s.f18, s.bold, s.pr5]}>Next</Text> 517 - ) : ( 518 - <TouchableOpacity testID="setNewPasswordButton" onPress={onPressNext}> 519 - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Next</Text> 520 - </TouchableOpacity> 521 - )} 522 - {isProcessing ? ( 523 - <Text style={[s.white, s.f18, s.pl10]}>Updating...</Text> 674 + {error ? ( 675 + <View style={styles.error}> 676 + <View style={styles.errorIcon}> 677 + <FontAwesomeIcon icon="exclamation" style={s.white} size={10} /> 678 + </View> 679 + <View style={s.flex1}> 680 + <Text style={[s.white, s.bold]}>{error}</Text> 681 + </View> 682 + </View> 524 683 ) : undefined} 684 + <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 685 + <TouchableOpacity onPress={onPressBack}> 686 + <Text type="xl" style={[pal.link, s.pl5]}> 687 + Back 688 + </Text> 689 + </TouchableOpacity> 690 + <View style={s.flex1} /> 691 + {isProcessing ? ( 692 + <ActivityIndicator /> 693 + ) : !resetCode || !password ? ( 694 + <Text type="xl-bold" style={[pal.link, s.pr5, {opacity: 0.5}]}> 695 + Next 696 + </Text> 697 + ) : ( 698 + <TouchableOpacity 699 + testID="setNewPasswordButton" 700 + onPress={onPressNext}> 701 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 702 + Next 703 + </Text> 704 + </TouchableOpacity> 705 + )} 706 + {isProcessing ? ( 707 + <Text type="xl" style={[pal.textLight, s.pl10]}> 708 + Updating... 709 + </Text> 710 + ) : undefined} 711 + </View> 525 712 </View> 526 713 </> 527 714 ) 528 715 } 529 716 530 717 const PasswordUpdatedForm = ({onPressNext}: {onPressNext: () => void}) => { 718 + const pal = usePalette('default') 531 719 return ( 532 720 <> 533 - <Text style={styles.screenTitle}>Password updated!</Text> 534 - <Text style={styles.instructions}> 535 - You can now sign in with your new password. 536 - </Text> 537 - <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 538 - <View style={s.flex1} /> 539 - <TouchableOpacity onPress={onPressNext}> 540 - <Text style={[s.white, s.f18, s.bold, s.pr5]}>Okay</Text> 541 - </TouchableOpacity> 721 + <LogoTextHero /> 722 + <View> 723 + <Text type="title-lg" style={[pal.text, styles.screenTitle]}> 724 + Password updated! 725 + </Text> 726 + <Text type="lg" style={[pal.text, styles.instructions]}> 727 + You can now sign in with your new password. 728 + </Text> 729 + <View style={[s.flexRow, s.alignCenter, s.pl20, s.pr20]}> 730 + <View style={s.flex1} /> 731 + <TouchableOpacity onPress={onPressNext}> 732 + <Text type="xl-bold" style={[pal.link, s.pr5]}> 733 + Okay 734 + </Text> 735 + </TouchableOpacity> 736 + </View> 542 737 </View> 543 738 </> 544 739 ) ··· 546 741 547 742 const styles = StyleSheet.create({ 548 743 screenTitle: { 549 - color: colors.white, 550 - fontSize: 26, 551 744 marginBottom: 10, 552 745 marginHorizontal: 20, 553 746 }, 554 747 instructions: { 555 - color: colors.white, 556 - fontSize: 16, 557 748 marginBottom: 20, 558 749 marginHorizontal: 20, 559 750 }, 560 - logoHero: { 561 - paddingTop: 30, 562 - paddingBottom: 40, 563 - }, 564 751 group: { 565 752 borderWidth: 1, 566 - borderColor: colors.white, 567 753 borderRadius: 10, 568 754 marginBottom: 20, 569 755 marginHorizontal: 20, 570 - backgroundColor: colors.blue3, 571 756 }, 572 - groupTitle: { 573 - flexDirection: 'row', 574 - alignItems: 'center', 575 - paddingVertical: 8, 576 - paddingHorizontal: 12, 757 + groupLabel: { 758 + paddingHorizontal: 20, 759 + paddingBottom: 5, 577 760 }, 578 761 groupContent: { 579 762 borderTopWidth: 1, 580 - borderTopColor: colors.blue1, 581 763 flexDirection: 'row', 582 764 alignItems: 'center', 583 765 }, 766 + noTopBorder: { 767 + borderTopWidth: 0, 768 + }, 584 769 groupContentIcon: { 585 - color: 'white', 586 770 marginLeft: 10, 587 771 }, 588 772 textInput: { 589 773 flex: 1, 590 774 width: '100%', 591 - backgroundColor: colors.blue3, 592 - color: colors.white, 593 775 paddingVertical: 10, 594 776 paddingHorizontal: 12, 595 - fontSize: 18, 777 + fontSize: 17, 778 + letterSpacing: 0.25, 779 + fontWeight: '400', 596 780 borderRadius: 10, 597 781 }, 598 782 textInputInnerBtn: { ··· 602 786 paddingHorizontal: 8, 603 787 marginHorizontal: 6, 604 788 }, 605 - textInputInnerBtnLabel: { 606 - color: colors.white, 789 + textBtn: { 790 + flexDirection: 'row', 791 + flex: 1, 792 + alignItems: 'center', 793 + }, 794 + textBtnLabel: { 795 + flex: 1, 796 + paddingVertical: 10, 797 + paddingHorizontal: 12, 607 798 }, 608 799 textBtnFakeInnerBtn: { 609 800 flexDirection: 'row', 610 801 alignItems: 'center', 611 - backgroundColor: colors.blue2, 612 802 borderRadius: 6, 613 803 paddingVertical: 6, 614 804 paddingHorizontal: 8, 615 805 marginHorizontal: 6, 616 806 }, 617 - textBtnFakeInnerBtnIcon: { 618 - color: colors.white, 619 - marginRight: 4, 620 - }, 621 - textBtnFakeInnerBtnLabel: { 622 - color: colors.white, 807 + accountText: { 808 + flex: 1, 809 + flexDirection: 'row', 810 + alignItems: 'baseline', 811 + paddingVertical: 10, 623 812 }, 624 813 error: { 625 - borderWidth: 1, 626 - borderColor: colors.red5, 627 814 backgroundColor: colors.red4, 628 815 flexDirection: 'row', 629 816 alignItems: 'center',
+3 -1
src/view/com/modals/ServerInput.tsx
··· 33 33 } 34 34 35 35 return ( 36 - <View style={s.flex1}> 36 + <View style={s.flex1} testID="serverInputModal"> 37 37 <Text style={[s.textCenter, s.bold, s.f18]}>Choose Service</Text> 38 38 <BottomSheetScrollView style={styles.inner}> 39 39 <View style={styles.group}> ··· 64 64 <Text style={styles.label}>Other service</Text> 65 65 <View style={{flexDirection: 'row'}}> 66 66 <BottomSheetTextInput 67 + testID="customServerTextInput" 67 68 style={styles.textInput} 68 69 placeholder="e.g. https://bsky.app" 69 70 placeholderTextColor={colors.gray4} ··· 74 75 onChangeText={setCustomUrl} 75 76 /> 76 77 <TouchableOpacity 78 + testID="customServerSelectBtn" 77 79 style={styles.textInputBtn} 78 80 onPress={() => doSelect(customUrl)}> 79 81 <FontAwesomeIcon
+1
src/view/com/util/ViewHeader.tsx
··· 49 49 return ( 50 50 <View style={[styles.header, pal.view]}> 51 51 <TouchableOpacity 52 + testID="viewHeaderBackOrMenuBtn" 52 53 onPress={canGoBack ? onPressBack : onPressMenu} 53 54 hitSlop={BACK_HITSLOP} 54 55 style={canGoBack ? styles.backIcon : styles.backIconWide}>
+1
src/view/lib/ThemeContext.tsx
··· 18 18 textInverted: string 19 19 link: string 20 20 border: string 21 + borderDark: string 21 22 icon: string 22 23 [k: string]: string 23 24 }
+4
src/view/lib/hooks/usePalette.ts
··· 6 6 view: ViewStyle 7 7 btn: ViewStyle 8 8 border: ViewStyle 9 + borderDark: ViewStyle 9 10 text: TextStyle 10 11 textLight: TextStyle 11 12 textInverted: TextStyle ··· 24 25 }, 25 26 border: { 26 27 borderColor: palette.border, 28 + }, 29 + borderDark: { 30 + borderColor: palette.borderDark, 27 31 }, 28 32 text: { 29 33 color: palette.text,
+7
src/view/lib/themes.ts
··· 13 13 textInverted: colors.white, 14 14 link: colors.blue3, 15 15 border: '#f0e9e9', 16 + borderDark: '#e0d9d9', 16 17 icon: colors.gray3, 17 18 18 19 // non-standard ··· 32 33 textInverted: colors.blue3, 33 34 link: colors.blue0, 34 35 border: colors.blue4, 36 + borderDark: colors.blue5, 35 37 icon: colors.blue4, 36 38 }, 37 39 secondary: { ··· 42 44 textInverted: colors.green4, 43 45 link: colors.green1, 44 46 border: colors.green4, 47 + borderDark: colors.green5, 45 48 icon: colors.green4, 46 49 }, 47 50 inverted: { ··· 52 55 textInverted: colors.black, 53 56 link: colors.blue2, 54 57 border: colors.gray3, 58 + borderDark: colors.gray2, 55 59 icon: colors.gray5, 56 60 }, 57 61 error: { ··· 62 66 textInverted: colors.red3, 63 67 link: colors.red1, 64 68 border: colors.red4, 69 + borderDark: colors.red5, 65 70 icon: colors.red4, 66 71 }, 67 72 }, ··· 257 262 textInverted: colors.black, 258 263 link: colors.blue3, 259 264 border: colors.gray6, 265 + borderDark: colors.gray5, 260 266 icon: colors.gray5, 261 267 262 268 // non-standard ··· 284 290 textInverted: colors.white, 285 291 link: colors.blue3, 286 292 border: colors.gray3, 293 + borderDark: colors.gray4, 287 294 icon: colors.gray1, 288 295 }, 289 296 },
+49 -24
src/view/screens/Login.tsx
··· 1 1 import React, {useState} from 'react' 2 2 import { 3 + SafeAreaView, 3 4 StyleSheet, 4 5 TouchableOpacity, 5 6 View, 6 7 useWindowDimensions, 7 8 } from 'react-native' 8 9 import Svg, {Line} from 'react-native-svg' 10 + import LinearGradient from 'react-native-linear-gradient' 9 11 import {observer} from 'mobx-react-lite' 10 12 import {Signin} from '../com/login/Signin' 11 13 import {Logo} from '../com/login/Logo' 12 14 import {CreateAccount} from '../com/login/CreateAccount' 13 15 import {Text} from '../com/util/text/Text' 16 + import {ErrorBoundary} from '../com/util/ErrorBoundary' 14 17 import {s, colors} from '../lib/styles' 18 + import {usePalette} from '../lib/hooks/usePalette' 15 19 16 20 enum ScreenState { 17 21 SigninOrCreateAccount, ··· 31 35 return ( 32 36 <> 33 37 <View style={styles.hero}> 34 - <Logo /> 38 + <Logo color="white" /> 35 39 <Text style={styles.title}>Bluesky</Text> 36 40 <Text style={styles.subtitle}>[ private beta ]</Text> 37 41 </View> ··· 76 80 77 81 export const Login = observer( 78 82 (/*{navigation}: RootTabsScreenProps<'Login'>*/) => { 83 + const pal = usePalette('default') 79 84 const [screenState, setScreenState] = useState<ScreenState>( 80 85 ScreenState.SigninOrCreateAccount, 81 86 ) 82 87 88 + if (screenState === ScreenState.SigninOrCreateAccount) { 89 + return ( 90 + <LinearGradient 91 + colors={['#007CFF', '#00BCFF']} 92 + start={{x: 0, y: 0.8}} 93 + end={{x: 0, y: 1}} 94 + style={styles.container}> 95 + <SafeAreaView testID="noSessionView" style={styles.container}> 96 + <ErrorBoundary> 97 + <SigninOrCreateAccount 98 + onPressSignin={() => setScreenState(ScreenState.Signin)} 99 + onPressCreateAccount={() => 100 + setScreenState(ScreenState.CreateAccount) 101 + } 102 + /> 103 + </ErrorBoundary> 104 + </SafeAreaView> 105 + </LinearGradient> 106 + ) 107 + } 108 + 83 109 return ( 84 - <View style={styles.outer}> 85 - {screenState === ScreenState.SigninOrCreateAccount ? ( 86 - <SigninOrCreateAccount 87 - onPressSignin={() => setScreenState(ScreenState.Signin)} 88 - onPressCreateAccount={() => 89 - setScreenState(ScreenState.CreateAccount) 90 - } 91 - /> 92 - ) : undefined} 93 - {screenState === ScreenState.Signin ? ( 94 - <Signin 95 - onPressBack={() => 96 - setScreenState(ScreenState.SigninOrCreateAccount) 97 - } 98 - /> 99 - ) : undefined} 100 - {screenState === ScreenState.CreateAccount ? ( 101 - <CreateAccount 102 - onPressBack={() => 103 - setScreenState(ScreenState.SigninOrCreateAccount) 104 - } 105 - /> 106 - ) : undefined} 110 + <View style={[styles.container, pal.view]}> 111 + <SafeAreaView testID="noSessionView" style={styles.container}> 112 + <ErrorBoundary> 113 + {screenState === ScreenState.Signin ? ( 114 + <Signin 115 + onPressBack={() => 116 + setScreenState(ScreenState.SigninOrCreateAccount) 117 + } 118 + /> 119 + ) : undefined} 120 + {screenState === ScreenState.CreateAccount ? ( 121 + <CreateAccount 122 + onPressBack={() => 123 + setScreenState(ScreenState.SigninOrCreateAccount) 124 + } 125 + /> 126 + ) : undefined} 127 + </ErrorBoundary> 128 + </SafeAreaView> 107 129 </View> 108 130 ) 109 131 }, 110 132 ) 111 133 112 134 const styles = StyleSheet.create({ 135 + container: { 136 + height: '100%', 137 + }, 113 138 outer: { 114 139 flex: 1, 115 140 },
+99 -17
src/view/screens/Settings.tsx
··· 1 1 import React, {useEffect} from 'react' 2 - import {StyleSheet, TouchableOpacity, View} from 'react-native' 2 + import { 3 + ActivityIndicator, 4 + ScrollView, 5 + StyleSheet, 6 + TouchableOpacity, 7 + View, 8 + } from 'react-native' 9 + import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 3 10 import {observer} from 'mobx-react-lite' 4 11 import {useStores} from '../../state' 5 12 import {ScreenParams} from '../routes' ··· 7 14 import {ViewHeader} from '../com/util/ViewHeader' 8 15 import {Link} from '../com/util/Link' 9 16 import {Text} from '../com/util/text/Text' 17 + import * as Toast from '../com/util/Toast' 10 18 import {UserAvatar} from '../com/util/UserAvatar' 11 19 import {usePalette} from '../lib/hooks/usePalette' 20 + import {AccountData} from '../../state/models/session' 12 21 13 22 export const Settings = observer(function Settings({ 14 23 navIdx, ··· 16 25 }: ScreenParams) { 17 26 const pal = usePalette('default') 18 27 const store = useStores() 28 + const [isSwitching, setIsSwitching] = React.useState(false) 19 29 20 30 useEffect(() => { 21 31 if (!visible) { ··· 25 35 store.nav.setTitle(navIdx, 'Settings') 26 36 }, [visible, store]) 27 37 38 + const onPressSwitchAccount = async (acct: AccountData) => { 39 + setIsSwitching(true) 40 + if (await store.session.resumeSession(acct)) { 41 + setIsSwitching(false) 42 + Toast.show(`Signed in as ${acct.displayName || acct.handle}`) 43 + return 44 + } 45 + setIsSwitching(false) 46 + Toast.show('Sorry! We need you to enter your password.') 47 + store.session.clear() 48 + } 49 + const onPressAddAccount = () => { 50 + store.session.clear() 51 + } 28 52 const onPressSignout = () => { 29 53 store.session.logout() 30 54 } 31 55 32 56 return ( 33 - <View style={[s.flex1]}> 57 + <View style={[s.h100pct]} testID="settingsScreen"> 34 58 <ViewHeader title="Settings" /> 35 - <View style={[s.mt10, s.pl10, s.pr10, s.flex1]}> 59 + <ScrollView style={[s.mt10, s.pl10, s.pr10, s.h100pct]}> 36 60 <View style={[s.flexRow]}> 37 - <Text type="xl" style={pal.text}> 61 + <Text type="xl-bold" style={pal.text}> 38 62 Signed in as 39 63 </Text> 40 64 <View style={s.flex1} /> 41 - <TouchableOpacity onPress={onPressSignout}> 65 + <TouchableOpacity 66 + testID="signOutBtn" 67 + onPress={isSwitching ? undefined : onPressSignout}> 42 68 <Text type="xl-medium" style={pal.link}> 43 69 Sign out 44 70 </Text> 45 71 </TouchableOpacity> 46 72 </View> 47 - <Link 48 - href={`/profile/${store.me.handle}`} 49 - title="Your profile" 50 - noFeedback> 73 + {isSwitching ? ( 51 74 <View style={[pal.view, styles.profile]}> 75 + <ActivityIndicator /> 76 + </View> 77 + ) : ( 78 + <Link 79 + href={`/profile/${store.me.handle}`} 80 + title="Your profile" 81 + noFeedback> 82 + <View style={[pal.view, styles.profile]}> 83 + <UserAvatar 84 + size={40} 85 + displayName={store.me.displayName} 86 + handle={store.me.handle || ''} 87 + avatar={store.me.avatar} 88 + /> 89 + <View style={[s.ml10]}> 90 + <Text type="xl-bold" style={pal.text}> 91 + {store.me.displayName || store.me.handle} 92 + </Text> 93 + <Text style={pal.textLight}>@{store.me.handle}</Text> 94 + </View> 95 + </View> 96 + </Link> 97 + )} 98 + <Text type="sm-medium" style={pal.text}> 99 + Switch to: 100 + </Text> 101 + {store.session.switchableAccounts.map(account => ( 102 + <TouchableOpacity 103 + testID={`switchToAccountBtn-${account.handle}`} 104 + key={account.did} 105 + style={[ 106 + pal.view, 107 + styles.profile, 108 + s.mb2, 109 + isSwitching && styles.dimmed, 110 + ]} 111 + onPress={ 112 + isSwitching ? undefined : () => onPressSwitchAccount(account) 113 + }> 52 114 <UserAvatar 53 115 size={40} 54 - displayName={store.me.displayName} 55 - handle={store.me.handle || ''} 56 - avatar={store.me.avatar} 116 + displayName={account.displayName} 117 + handle={account.handle || ''} 118 + avatar={account.aviUrl} 57 119 /> 58 120 <View style={[s.ml10]}> 59 121 <Text type="xl-bold" style={pal.text}> 60 - {store.me.displayName || store.me.handle} 122 + {account.displayName || account.handle} 61 123 </Text> 62 - <Text style={pal.textLight}>@{store.me.handle}</Text> 124 + <Text style={pal.textLight}>@{account.handle}</Text> 63 125 </View> 126 + </TouchableOpacity> 127 + ))} 128 + <TouchableOpacity 129 + testID="switchToNewAccountBtn" 130 + style={[ 131 + pal.view, 132 + styles.profile, 133 + s.mb2, 134 + {alignItems: 'center'}, 135 + isSwitching && styles.dimmed, 136 + ]} 137 + onPress={isSwitching ? undefined : onPressAddAccount}> 138 + <FontAwesomeIcon icon="plus" /> 139 + <View style={[s.ml5]}> 140 + <Text type="md-medium" style={pal.text}> 141 + Add account 142 + </Text> 64 143 </View> 65 - </Link> 66 - <View style={s.flex1} /> 144 + </TouchableOpacity> 145 + <View style={{height: 50}} /> 67 146 <Text type="sm-medium" style={[s.mb5]}> 68 147 Developer tools 69 148 </Text> ··· 80 159 <Text style={pal.link}>Storybook</Text> 81 160 </Link> 82 161 <View style={s.footerSpacer} /> 83 - </View> 162 + </ScrollView> 84 163 </View> 85 164 ) 86 165 }) 87 166 88 167 const styles = StyleSheet.create({ 168 + dimmed: { 169 + opacity: 0.5, 170 + }, 89 171 title: { 90 172 fontSize: 32, 91 173 fontWeight: 'bold',
+1 -1
src/view/shell/mobile/Menu.tsx
··· 62 62 onPress?: () => void 63 63 }) => ( 64 64 <TouchableOpacity 65 - testID="menuItemButton" 65 + testID={`menuItemButton-${label}`} 66 66 style={styles.menuItem} 67 67 onPress={onPress ? onPress : () => onNavigate(url || '/')}> 68 68 <View style={[styles.menuItemIconWrapper]}>
+4 -14
src/view/shell/mobile/index.tsx
··· 5 5 Easing, 6 6 FlatList, 7 7 GestureResponderEvent, 8 - SafeAreaView, 9 8 StatusBar, 10 9 StyleSheet, 11 10 TouchableOpacity, ··· 16 15 ViewStyle, 17 16 } from 'react-native' 18 17 import {ScreenContainer, Screen} from 'react-native-screens' 19 - import LinearGradient from 'react-native-linear-gradient' 20 18 import {useSafeAreaInsets} from 'react-native-safe-area-context' 21 19 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 22 20 import {IconProp} from '@fortawesome/fontawesome-svg-core' ··· 34 32 import {ErrorBoundary} from '../../com/util/ErrorBoundary' 35 33 import {TabsSelector} from './TabsSelector' 36 34 import {Composer} from './Composer' 37 - import {s, colors} from '../../lib/styles' 35 + import {colors} from '../../lib/styles' 38 36 import {clamp} from '../../../lib/numbers' 39 37 import { 40 38 GridIcon, ··· 323 321 324 322 if (!store.session.hasSession) { 325 323 return ( 326 - <LinearGradient 327 - colors={['#007CFF', '#00BCFF']} 328 - start={{x: 0, y: 0.8}} 329 - end={{x: 0, y: 1}} 330 - style={styles.outerContainer}> 331 - <SafeAreaView testID="noSessionView" style={styles.innerContainer}> 332 - <ErrorBoundary> 333 - <Login /> 334 - </ErrorBoundary> 335 - </SafeAreaView> 324 + <View style={styles.outerContainer}> 325 + <Login /> 336 326 <Modal /> 337 - </LinearGradient> 327 + </View> 338 328 ) 339 329 } 340 330 if (store.onboard.isOnboarding) {
+968 -23
yarn.lock
··· 27 27 "@atproto/xrpc" "*" 28 28 typed-emitter "^2.1.0" 29 29 30 + "@atproto/auth@*": 31 + version "0.0.1" 32 + resolved "https://registry.yarnpkg.com/@atproto/auth/-/auth-0.0.1.tgz#0ae07bfb6e4e86605504a20f0302e448ba3f8b0e" 33 + integrity sha512-eom7V/LmXttlFE31TcOJ0BInTszkm5ZBS2mqoLqbnA5ZTcTsgQsMKhGzARFf2zwBM9h8pbVa1XMI83gnrTHfxA== 34 + dependencies: 35 + "@atproto/crypto" "*" 36 + "@atproto/did-resolver" "*" 37 + "@ucans/core" "0.11.0" 38 + uint8arrays "3.0.0" 39 + 40 + "@atproto/common@*": 41 + version "0.0.1" 42 + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.0.1.tgz#228a5d19574113ad69888fd1c617fe56f8b301ac" 43 + integrity sha512-SdFoMx5Rdx7iUke3tCe7oiRUpTJXbTFgRJafYWehw/3dY2pFcUu4fK1z2CG0VbmyFUzR0J6TejScFpaYcP03uw== 44 + dependencies: 45 + "@ipld/dag-cbor" "^7.0.3" 46 + multiformats "^9.6.4" 47 + pino "^8.6.1" 48 + zod "^3.14.2" 49 + 50 + "@atproto/crypto@*": 51 + version "0.0.1" 52 + resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.0.1.tgz#2eb3528f895336ce58326393d458370125a10bf7" 53 + integrity sha512-+Wb6xfEXn+14FyKlUkkKobN0hQwcaT4h4rqW2WeWbaYvsYuhMPzMrt8UsXzQ17cy9gYfu6FqR3C4fAiWgSdBZA== 54 + dependencies: 55 + "@noble/secp256k1" "^1.7.0" 56 + "@ucans/core" "0.11.0" 57 + big-integer "^1.6.51" 58 + multiformats "^9.6.4" 59 + one-webcrypto "^1.0.3" 60 + uint8arrays "3.0.0" 61 + 62 + "@atproto/did-resolver@*": 63 + version "0.0.1" 64 + resolved "https://registry.yarnpkg.com/@atproto/did-resolver/-/did-resolver-0.0.1.tgz#e54c1b7fddff2cd6adf87c044b4a3b6f00d5eff7" 65 + integrity sha512-sdva3+nydMaWXwHJED558UZdVZuajfC2CHcsIZz0pQybicm3VI+khkf42ClZeOhf4Bwa4V4SOaaAqwyf86bDew== 66 + dependencies: 67 + "@atproto/common" "*" 68 + "@atproto/crypto" "*" 69 + axios "^0.24.0" 70 + did-resolver "^4.0.0" 71 + 72 + "@atproto/handle@*": 73 + version "0.0.1" 74 + resolved "https://registry.yarnpkg.com/@atproto/handle/-/handle-0.0.1.tgz#783f88aaef1f57920deb61da8d72e5191cd9d515" 75 + integrity sha512-foWqpzyVufo6/LxHeqBqoz9KhoLIGpIQ3zqYXlJWX4YD6OlFq3RfrGYbJrqHIiHhe1xgm6GIgEax8V6QIEbATA== 76 + dependencies: 77 + "@sideway/address" "^5.0.0" 78 + 30 79 "@atproto/lexicon@*", "@atproto/lexicon@^0.0.4": 31 80 version "0.0.4" 32 81 resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.0.4.tgz#f0a6688ad54adb2ec4a8d1f11fcbf45e96203c4b" ··· 41 90 resolved "https://registry.yarnpkg.com/@atproto/nsid/-/nsid-0.0.1.tgz#0cdc00cefe8f0b1385f352b9f57b3ad37fff09a4" 42 91 integrity sha512-t5M6/CzWBVYoBbIvfKDpqPj/+ZmyoK9ydZSStcTXosJ27XXwOPhz0VDUGKK2SM9G5Y7TPes8S5KTAU0UdVYFCw== 43 92 93 + "@atproto/pds@^0.0.1": 94 + version "0.0.1" 95 + resolved "https://registry.yarnpkg.com/@atproto/pds/-/pds-0.0.1.tgz#4492595cd0796a3dc4c10836cd121bb3b132f858" 96 + integrity sha512-8CaAIBzri05CN2gkMtXnUlNxCdUwaWp1qz4b9RXpMQ83Pk1+j3nw09fsml0kjlMCPcWokTF+iGJXrrTL3XVjOw== 97 + dependencies: 98 + "@atproto/auth" "*" 99 + "@atproto/common" "*" 100 + "@atproto/crypto" "*" 101 + "@atproto/did-resolver" "*" 102 + "@atproto/handle" "*" 103 + "@atproto/lexicon" "*" 104 + "@atproto/plc" "*" 105 + "@atproto/repo" "*" 106 + "@atproto/uri" "*" 107 + "@atproto/xrpc-server" "*" 108 + better-sqlite3 "^7.6.2" 109 + bytes "^3.1.2" 110 + cors "^2.8.5" 111 + dotenv "^16.0.0" 112 + express "^4.17.2" 113 + express-async-errors "^3.1.1" 114 + file-type "^16.5.4" 115 + handlebars "^4.7.7" 116 + http-errors "^2.0.0" 117 + http-terminator "^3.2.0" 118 + jsonwebtoken "^8.5.1" 119 + kysely "^0.22.0" 120 + multiformats "^9.6.4" 121 + nodemailer "^6.8.0" 122 + nodemailer-html-to-text "^3.2.0" 123 + p-queue "^6.6.2" 124 + pg "^8.8.0" 125 + pino "^8.6.1" 126 + pino-http "^8.2.1" 127 + sharp "^0.31.2" 128 + uint8arrays "3.0.0" 129 + 130 + "@atproto/plc@*": 131 + version "0.0.1" 132 + resolved "https://registry.yarnpkg.com/@atproto/plc/-/plc-0.0.1.tgz#713de881fd2b803a0f1afbee57735de8382a8ed3" 133 + integrity sha512-9JM027ioAb6rG+2F/p89DJlIXBOH85rGWXFcG3dImZJ8SalFqRZ0/7gtdFN387IZ/HNAWRmmFaAxMEmJ9NgKpQ== 134 + dependencies: 135 + "@atproto/common" "*" 136 + "@atproto/crypto" "*" 137 + "@ipld/dag-cbor" "^7.0.3" 138 + async-mutex "^0.4.0" 139 + axios "^0.27.2" 140 + better-sqlite3 "^7.6.2" 141 + cors "^2.8.5" 142 + dotenv "^16.0.2" 143 + express "^4.17.2" 144 + express-async-errors "^3.1.1" 145 + http-terminator "^3.2.0" 146 + kysely "^0.22.0" 147 + pg "^8.8.0" 148 + pino "^8.6.1" 149 + pino-http "^8.2.1" 150 + uint8arrays "3.0.0" 151 + zod "^3.14.2" 152 + 153 + "@atproto/repo@*": 154 + version "0.0.1" 155 + resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.0.1.tgz#41c63943a7e6a0942fc3e721c05d8c836c2fcfc2" 156 + integrity sha512-tBZjaeaRL7fJynZCA5F+ZjRQuf5fpL7Cj5VqP6KtXYacuNP/LufwrHARSOwxJMMZpPOoWmwv4R8bETiQozehEA== 157 + dependencies: 158 + "@atproto/auth" "*" 159 + "@atproto/common" "*" 160 + "@atproto/nsid" "*" 161 + "@ipld/car" "^3.2.3" 162 + "@ipld/dag-cbor" "^7.0.0" 163 + multiformats "^9.6.4" 164 + uint8arrays "3.0.0" 165 + zod "^3.14.2" 166 + 167 + "@atproto/uri@*": 168 + version "0.0.1" 169 + resolved "https://registry.yarnpkg.com/@atproto/uri/-/uri-0.0.1.tgz#bfab68eda17ec987647f10d102168d417bc8a326" 170 + integrity sha512-Tm+20Bxdie+a4yvberrfWaDhrze/p3AvA5v5IV6XyZJYu2+fnionUrufUjkcs3PIWeSd6VMgVcRp3GaoiUvSvQ== 171 + 172 + "@atproto/xrpc-server@*": 173 + version "0.0.1" 174 + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.0.1.tgz#62891d8e24b0813a7006d8ba947716b7c69e5667" 175 + integrity sha512-W9pb9k9wgDlZdDF3eIDMXhEs1trg3zSRd70f1BfN22h+Or4wsoq5dAxXg6q9os3+DNkVkD9BWeRwVppCF6FxGg== 176 + dependencies: 177 + "@atproto/common" "*" 178 + "@atproto/lexicon" "*" 179 + express "^4.17.2" 180 + http-errors "^2.0.0" 181 + mime-types "^2.1.35" 182 + zod "^3.14.2" 183 + 44 184 "@atproto/xrpc@*", "@atproto/xrpc@^0.0.3": 45 185 version "0.0.3" 46 186 resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.0.3.tgz#510028753d51dffd754ee4f96897b74bcba50bda" ··· 1387 1527 dependencies: 1388 1528 nanoid "^3.3.1" 1389 1529 1530 + "@hapi/hoek@^10.0.0": 1531 + version "10.0.1" 1532 + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-10.0.1.tgz#ee9da297fabc557e1c040a0f44ee89c266ccc306" 1533 + integrity sha512-CvlW7jmOhWzuqOqiJQ3rQVLMcREh0eel4IBnxDx2FAcK8g7qoJRQK4L1CPBASoCY6y8e6zuCy3f2g+HWdkzcMw== 1534 + 1390 1535 "@hapi/hoek@^9.0.0": 1391 1536 version "9.3.0" 1392 1537 resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" ··· 1418 1563 resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" 1419 1564 integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== 1420 1565 1566 + "@ipld/car@^3.2.3": 1567 + version "3.2.4" 1568 + resolved "https://registry.yarnpkg.com/@ipld/car/-/car-3.2.4.tgz#115951ba2255ec51d865773a074e422c169fb01c" 1569 + integrity sha512-rezKd+jk8AsTGOoJKqzfjLJ3WVft7NZNH95f0pfPbicROvzTyvHCNy567HzSUd6gRXZ9im29z5ZEv9Hw49jSYw== 1570 + dependencies: 1571 + "@ipld/dag-cbor" "^7.0.0" 1572 + multiformats "^9.5.4" 1573 + varint "^6.0.0" 1574 + 1575 + "@ipld/dag-cbor@^7.0.0", "@ipld/dag-cbor@^7.0.3": 1576 + version "7.0.3" 1577 + resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e" 1578 + integrity sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA== 1579 + dependencies: 1580 + cborg "^1.6.0" 1581 + multiformats "^9.5.4" 1582 + 1421 1583 "@istanbuljs/load-nyc-config@^1.0.0": 1422 1584 version "1.1.0" 1423 1585 resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" ··· 1921 2083 dependencies: 1922 2084 eslint-scope "5.1.1" 1923 2085 2086 + "@noble/secp256k1@^1.7.0": 2087 + version "1.7.1" 2088 + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" 2089 + integrity sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw== 2090 + 1924 2091 "@nodelib/fs.scandir@2.1.5": 1925 2092 version "2.1.5" 1926 2093 resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" ··· 2250 2417 dependencies: 2251 2418 "@hapi/hoek" "^9.0.0" 2252 2419 2420 + "@sideway/address@^5.0.0": 2421 + version "5.0.0" 2422 + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-5.0.0.tgz#015f191a4a29e2b2f9ad1aabe7465c3088241536" 2423 + integrity sha512-IEZ3Gi972M1yubSPhcpzpVTT/Vb46F9L0W+K/GhqvWv6aAvVbNNVsYFekXWEemHHFfTVrxFcURrzsPGPPKkxKQ== 2424 + dependencies: 2425 + "@hapi/hoek" "^10.0.0" 2426 + 2253 2427 "@sideway/formula@^3.0.0": 2254 2428 version "3.0.1" 2255 2429 resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" ··· 2416 2590 integrity sha512-seV+qebsbX4E5CWk/wizU1+2wVLsPyqEzG7sTgrhJ81cgAawg7ay06fIZR9IS75pDeWn2KZVd4mGk1pjJ3i3Zw== 2417 2591 dependencies: 2418 2592 pretty-format "^29.0.0" 2593 + 2594 + "@tokenizer/token@^0.3.0": 2595 + version "0.3.0" 2596 + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" 2597 + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== 2419 2598 2420 2599 "@tootallnate/once@1": 2421 2600 version "1.1.2" ··· 2959 3138 dependencies: 2960 3139 "@typescript-eslint/types" "5.48.2" 2961 3140 eslint-visitor-keys "^3.3.0" 3141 + 3142 + "@ucans/core@0.11.0": 3143 + version "0.11.0" 3144 + resolved "https://registry.yarnpkg.com/@ucans/core/-/core-0.11.0.tgz#8201680294d980f2b1f5edfaf77b42a86d3a5688" 3145 + integrity sha512-SHX67e313kKBaur5Cp+6WFeOLC7aBhkf1i1jIFpFb9f0f1cvM/lC3mjzOyUBeDg3QwmcN5QSZzaogVFvuVvzvg== 3146 + dependencies: 3147 + uint8arrays "3.0.0" 2962 3148 2963 3149 "@webassemblyjs/ast@1.11.1": 2964 3150 version "1.11.1" ··· 3202 3388 dependencies: 3203 3389 fast-deep-equal "^3.1.3" 3204 3390 3205 - ajv@^6.10.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: 3391 + ajv@^6.10.0, ajv@^6.11.0, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: 3206 3392 version "6.12.6" 3207 3393 resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" 3208 3394 integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== ··· 3445 3631 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" 3446 3632 integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== 3447 3633 3634 + async-mutex@^0.4.0: 3635 + version "0.4.0" 3636 + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.0.tgz#ae8048cd4d04ace94347507504b3cf15e631c25f" 3637 + integrity sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA== 3638 + dependencies: 3639 + tslib "^2.4.0" 3640 + 3448 3641 async@^3.2.2, async@^3.2.3: 3449 3642 version "3.2.4" 3450 3643 resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" ··· 3465 3658 resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" 3466 3659 integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== 3467 3660 3661 + atomic-sleep@^1.0.0: 3662 + version "1.0.0" 3663 + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" 3664 + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== 3665 + 3468 3666 autoprefixer@^10.4.13: 3469 3667 version "10.4.13" 3470 3668 resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.13.tgz#b5136b59930209a321e9fa3dca2e7c4d223e83a8" ··· 3482 3680 resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.1.tgz#79cccdee3e3ab61a8f42c458d4123a6768e6fbce" 3483 3681 integrity sha512-lCZN5XRuOnpG4bpMq8v0khrWtUOn+i8lZSb6wHZH56ZfbIEv6XwJV84AAueh9/zi7qPVJ/E4yz6fmsiyOmXR4w== 3484 3682 3683 + axios@^0.24.0: 3684 + version "0.24.0" 3685 + resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" 3686 + integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== 3687 + dependencies: 3688 + follow-redirects "^1.14.4" 3689 + 3690 + axios@^0.27.2: 3691 + version "0.27.2" 3692 + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" 3693 + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== 3694 + dependencies: 3695 + follow-redirects "^1.14.9" 3696 + form-data "^4.0.0" 3697 + 3485 3698 axobject-query@^2.2.0: 3486 3699 version "2.2.0" 3487 3700 resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" ··· 3735 3948 resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" 3736 3949 integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== 3737 3950 3951 + better-sqlite3@^7.6.2: 3952 + version "7.6.2" 3953 + resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-7.6.2.tgz#47cd8cad5b9573cace535f950ac321166bc31384" 3954 + integrity sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg== 3955 + dependencies: 3956 + bindings "^1.5.0" 3957 + prebuild-install "^7.1.0" 3958 + 3738 3959 bfj@^7.0.2: 3739 3960 version "7.0.2" 3740 3961 resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" ··· 3745 3966 hoopy "^0.1.4" 3746 3967 tryer "^1.0.1" 3747 3968 3969 + big-integer@^1.6.51: 3970 + version "1.6.51" 3971 + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" 3972 + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== 3973 + 3748 3974 big.js@^5.2.2: 3749 3975 version "5.2.2" 3750 3976 resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" ··· 3755 3981 resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 3756 3982 integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 3757 3983 3758 - bl@^4.1.0: 3984 + bindings@^1.5.0: 3985 + version "1.5.0" 3986 + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 3987 + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 3988 + dependencies: 3989 + file-uri-to-path "1.0.0" 3990 + 3991 + bl@^4.0.3, bl@^4.1.0: 3759 3992 version "4.1.0" 3760 3993 resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" 3761 3994 integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== ··· 3802 4035 resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" 3803 4036 integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== 3804 4037 4038 + boolean@^3.1.4: 4039 + version "3.2.0" 4040 + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" 4041 + integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== 4042 + 3805 4043 brace-expansion@^1.1.7: 3806 4044 version "1.1.11" 3807 4045 resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" ··· 3862 4100 dependencies: 3863 4101 node-int64 "^0.4.0" 3864 4102 4103 + buffer-equal-constant-time@1.0.1: 4104 + version "1.0.1" 4105 + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 4106 + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== 4107 + 3865 4108 buffer-from@^1.0.0: 3866 4109 version "1.1.2" 3867 4110 resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 3868 4111 integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 3869 4112 4113 + buffer-writer@2.0.0: 4114 + version "2.0.0" 4115 + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" 4116 + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== 4117 + 3870 4118 buffer@^5.4.3, buffer@^5.5.0: 3871 4119 version "5.7.1" 3872 4120 resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" ··· 3875 4123 base64-js "^1.3.1" 3876 4124 ieee754 "^1.1.13" 3877 4125 4126 + buffer@^6.0.3: 4127 + version "6.0.3" 4128 + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" 4129 + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== 4130 + dependencies: 4131 + base64-js "^1.3.1" 4132 + ieee754 "^1.2.1" 4133 + 3878 4134 builtin-modules@^3.1.0: 3879 4135 version "3.3.0" 3880 4136 resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" ··· 3885 4141 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 3886 4142 integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== 3887 4143 3888 - bytes@3.1.2: 4144 + bytes@3.1.2, bytes@^3.1.2: 3889 4145 version "3.1.2" 3890 4146 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 3891 4147 integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== ··· 3980 4236 resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" 3981 4237 integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== 3982 4238 4239 + cborg@^1.6.0: 4240 + version "1.10.0" 4241 + resolved "https://registry.yarnpkg.com/cborg/-/cborg-1.10.0.tgz#0fe157961dd47b537ccb84dc9ba681de8b699013" 4242 + integrity sha512-/eM0JCaL99HDHxjySNQJLaolZFVdl6VA0/hEKIoiQPcQzE5LrG5QHdml0HaBt31brgB9dNe1zMr3f8IVrpotRQ== 4243 + 3983 4244 chalk@^2.0.0, chalk@^2.4.1: 3984 4245 version "2.4.2" 3985 4246 resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" ··· 4026 4287 readdirp "~3.6.0" 4027 4288 optionalDependencies: 4028 4289 fsevents "~2.3.2" 4290 + 4291 + chownr@^1.1.1: 4292 + version "1.1.4" 4293 + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" 4294 + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== 4029 4295 4030 4296 chrome-trace-event@^1.0.2: 4031 4297 version "1.0.3" ··· 4163 4429 resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 4164 4430 integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== 4165 4431 4166 - color-name@^1.1.4, color-name@~1.1.4: 4432 + color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: 4167 4433 version "1.1.4" 4168 4434 resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 4169 4435 integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 4170 4436 4437 + color-string@^1.9.0: 4438 + version "1.9.1" 4439 + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" 4440 + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== 4441 + dependencies: 4442 + color-name "^1.0.0" 4443 + simple-swizzle "^0.2.2" 4444 + 4445 + color@^4.2.3: 4446 + version "4.2.3" 4447 + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" 4448 + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== 4449 + dependencies: 4450 + color-convert "^2.0.1" 4451 + color-string "^1.9.0" 4452 + 4171 4453 colord@^2.9.1: 4172 4454 version "2.9.3" 4173 4455 resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" ··· 4343 4625 version "1.0.3" 4344 4626 resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" 4345 4627 integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== 4628 + 4629 + cors@^2.8.5: 4630 + version "2.8.5" 4631 + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 4632 + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 4633 + dependencies: 4634 + object-assign "^4" 4635 + vary "^1" 4346 4636 4347 4637 cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: 4348 4638 version "5.2.1" ··· 4679 4969 resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" 4680 4970 integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== 4681 4971 4972 + decompress-response@^6.0.0: 4973 + version "6.0.0" 4974 + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" 4975 + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== 4976 + dependencies: 4977 + mimic-response "^3.1.0" 4978 + 4682 4979 dedent@^0.7.0: 4683 4980 version "0.7.0" 4684 4981 resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" 4685 4982 integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== 4983 + 4984 + deep-extend@^0.6.0: 4985 + version "0.6.0" 4986 + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 4987 + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 4686 4988 4687 4989 deep-is@^0.1.3, deep-is@~0.1.3: 4688 4990 version "0.1.4" ··· 4753 5055 resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" 4754 5056 integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== 4755 5057 5058 + delay@^5.0.0: 5059 + version "5.0.0" 5060 + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" 5061 + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== 5062 + 4756 5063 delayed-stream@~1.0.0: 4757 5064 version "1.0.0" 4758 5065 resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" ··· 4796 5103 resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" 4797 5104 integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== 4798 5105 5106 + detect-libc@^2.0.0, detect-libc@^2.0.1: 5107 + version "2.0.1" 5108 + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" 5109 + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== 5110 + 4799 5111 detect-newline@^3.0.0: 4800 5112 version "3.1.0" 4801 5113 resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" ··· 4822 5134 acorn-node "^1.8.2" 4823 5135 defined "^1.0.0" 4824 5136 minimist "^1.2.6" 5137 + 5138 + did-resolver@^4.0.0: 5139 + version "4.0.1" 5140 + resolved "https://registry.yarnpkg.com/did-resolver/-/did-resolver-4.0.1.tgz#11bb3f19ed1c8f53f4af4702912fa9f7852fc305" 5141 + integrity sha512-eHs2VLKhcANmh08S87PKvOauIAmSOd7nb7AlhNxcvOyDAIGQY1UfbiqI1VOW5IDKvOO6aEWY+5edOt1qrCp1Eg== 4825 5142 4826 5143 didyoumean@^1.2.2: 4827 5144 version "1.2.2" ··· 4989 5306 resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" 4990 5307 integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== 4991 5308 4992 - dotenv@^16.0.3: 5309 + dotenv@^16.0.0, dotenv@^16.0.2, dotenv@^16.0.3: 4993 5310 version "16.0.3" 4994 5311 resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" 4995 5312 integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== ··· 4998 5315 version "0.1.2" 4999 5316 resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" 5000 5317 integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== 5318 + 5319 + ecdsa-sig-formatter@1.0.11: 5320 + version "1.0.11" 5321 + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 5322 + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 5323 + dependencies: 5324 + safe-buffer "^5.0.1" 5001 5325 5002 5326 ee-first@1.1.1: 5003 5327 version "1.1.1" ··· 5056 5380 resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 5057 5381 integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== 5058 5382 5059 - end-of-stream@^1.1.0: 5383 + end-of-stream@^1.1.0, end-of-stream@^1.4.1: 5060 5384 version "1.4.4" 5061 5385 resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 5062 5386 integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== ··· 5584 5908 resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 5585 5909 integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 5586 5910 5587 - eventemitter3@^4.0.0: 5911 + eventemitter3@^4.0.0, eventemitter3@^4.0.4: 5588 5912 version "4.0.7" 5589 5913 resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" 5590 5914 integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== 5591 5915 5592 - events@^3.2.0: 5916 + events@^3.2.0, events@^3.3.0: 5593 5917 version "3.3.0" 5594 5918 resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 5595 5919 integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== ··· 5640 5964 snapdragon "^0.8.1" 5641 5965 to-regex "^3.0.1" 5642 5966 5967 + expand-template@^2.0.3: 5968 + version "2.0.3" 5969 + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" 5970 + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== 5971 + 5643 5972 expect@^27.5.1: 5644 5973 version "27.5.1" 5645 5974 resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" ··· 5661 5990 jest-message-util "^29.3.1" 5662 5991 jest-util "^29.3.1" 5663 5992 5664 - express@^4.17.3: 5993 + express-async-errors@^3.1.1: 5994 + version "3.1.1" 5995 + resolved "https://registry.yarnpkg.com/express-async-errors/-/express-async-errors-3.1.1.tgz#6053236d61d21ddef4892d6bd1d736889fc9da41" 5996 + integrity sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng== 5997 + 5998 + express@^4.17.2, express@^4.17.3: 5665 5999 version "4.18.2" 5666 6000 resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" 5667 6001 integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== ··· 5753 6087 resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 5754 6088 integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 5755 6089 6090 + fast-json-stringify@^2.7.10: 6091 + version "2.7.13" 6092 + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz#277aa86c2acba4d9851bd6108ed657aa327ed8c0" 6093 + integrity sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA== 6094 + dependencies: 6095 + ajv "^6.11.0" 6096 + deepmerge "^4.2.2" 6097 + rfdc "^1.2.0" 6098 + string-similarity "^4.0.1" 6099 + 5756 6100 fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: 5757 6101 version "2.0.6" 5758 6102 resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" ··· 5763 6107 resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75" 5764 6108 integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== 5765 6109 6110 + fast-printf@^1.6.9: 6111 + version "1.6.9" 6112 + resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.9.tgz#212f56570d2dc8ccdd057ee93d50dd414d07d676" 6113 + integrity sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg== 6114 + dependencies: 6115 + boolean "^3.1.4" 6116 + 6117 + fast-redact@^3.1.1: 6118 + version "3.1.2" 6119 + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa" 6120 + integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw== 6121 + 6122 + fast-url-parser@^1.1.3: 6123 + version "1.1.3" 6124 + resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" 6125 + integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ== 6126 + dependencies: 6127 + punycode "^1.3.2" 6128 + 5766 6129 fastq@^1.6.0: 5767 6130 version "1.15.0" 5768 6131 resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" ··· 5816 6179 dependencies: 5817 6180 loader-utils "^2.0.0" 5818 6181 schema-utils "^3.0.0" 6182 + 6183 + file-type@^16.5.4: 6184 + version "16.5.4" 6185 + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" 6186 + integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== 6187 + dependencies: 6188 + readable-web-to-node-stream "^3.0.0" 6189 + strtok3 "^6.2.4" 6190 + token-types "^4.1.1" 6191 + 6192 + file-uri-to-path@1.0.0: 6193 + version "1.0.0" 6194 + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" 6195 + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== 5819 6196 5820 6197 filelist@^1.0.1: 5821 6198 version "1.0.4" ··· 5936 6313 resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.185.2.tgz#cb7ee57f77377d6c5d69a469e980f6332a15e492" 5937 6314 integrity sha512-2hJ5ACYeJCzNtiVULov6pljKOLygy0zddoqSI1fFetM+XRPpRshFdGEijtqlamA1XwyZ+7rhryI6FQFzvtLWUQ== 5938 6315 5939 - follow-redirects@^1.0.0: 6316 + follow-redirects@^1.0.0, follow-redirects@^1.14.4, follow-redirects@^1.14.9: 5940 6317 version "1.15.2" 5941 6318 resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" 5942 6319 integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== ··· 5974 6351 combined-stream "^1.0.8" 5975 6352 mime-types "^2.1.12" 5976 6353 6354 + form-data@^4.0.0: 6355 + version "4.0.0" 6356 + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" 6357 + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== 6358 + dependencies: 6359 + asynckit "^0.4.0" 6360 + combined-stream "^1.0.8" 6361 + mime-types "^2.1.12" 6362 + 5977 6363 forwarded@0.2.0: 5978 6364 version "0.2.0" 5979 6365 resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" ··· 5996 6382 resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 5997 6383 integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== 5998 6384 6385 + fs-constants@^1.0.0: 6386 + version "1.0.0" 6387 + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" 6388 + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== 6389 + 5999 6390 fs-extra@^10.0.0: 6000 6391 version "10.1.0" 6001 6392 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" ··· 6113 6504 resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" 6114 6505 integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== 6115 6506 6507 + github-from-package@0.0.0: 6508 + version "0.0.0" 6509 + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" 6510 + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== 6511 + 6116 6512 glob-parent@^5.1.2, glob-parent@~5.1.2: 6117 6513 version "5.1.2" 6118 6514 resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" ··· 6184 6580 dependencies: 6185 6581 type-fest "^0.20.2" 6186 6582 6583 + globalthis@^1.0.2: 6584 + version "1.0.3" 6585 + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" 6586 + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== 6587 + dependencies: 6588 + define-properties "^1.1.3" 6589 + 6187 6590 globby@^11.0.4, globby@^11.1.0: 6188 6591 version "11.1.0" 6189 6592 resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" ··· 6224 6627 version "2.0.1" 6225 6628 resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" 6226 6629 integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== 6630 + 6631 + handlebars@^4.7.7: 6632 + version "4.7.7" 6633 + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" 6634 + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== 6635 + dependencies: 6636 + minimist "^1.2.5" 6637 + neo-async "^2.6.0" 6638 + source-map "^0.6.1" 6639 + wordwrap "^1.0.0" 6640 + optionalDependencies: 6641 + uglify-js "^3.1.4" 6227 6642 6228 6643 harmony-reflect@^1.4.6: 6229 6644 version "1.6.2" ··· 6378 6793 relateurl "^0.2.7" 6379 6794 terser "^5.10.0" 6380 6795 6796 + html-to-text@7.1.1: 6797 + version "7.1.1" 6798 + resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-7.1.1.tgz#69de8d85b91646b4bc14fdf4f850e9e046efff15" 6799 + integrity sha512-c9QWysrfnRZevVpS8MlE7PyOdSuIOjg8Bt8ZE10jMU/BEngA6j3llj4GRfAmtQzcd1FjKE0sWu5IHXRUH9YxIQ== 6800 + dependencies: 6801 + deepmerge "^4.2.2" 6802 + he "^1.2.0" 6803 + htmlparser2 "^6.1.0" 6804 + minimist "^1.2.5" 6805 + 6381 6806 html-webpack-plugin@^5.5.0: 6382 6807 version "5.5.0" 6383 6808 resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" ··· 6404 6829 resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" 6405 6830 integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== 6406 6831 6407 - http-errors@2.0.0: 6832 + http-errors@2.0.0, http-errors@^2.0.0: 6408 6833 version "2.0.0" 6409 6834 resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" 6410 6835 integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== ··· 6459 6884 follow-redirects "^1.0.0" 6460 6885 requires-port "^1.0.0" 6461 6886 6887 + http-terminator@^3.2.0: 6888 + version "3.2.0" 6889 + resolved "https://registry.yarnpkg.com/http-terminator/-/http-terminator-3.2.0.tgz#bc158d2694b733ca4fbf22a35065a81a609fb3e9" 6890 + integrity sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g== 6891 + dependencies: 6892 + delay "^5.0.0" 6893 + p-wait-for "^3.2.0" 6894 + roarr "^7.0.4" 6895 + type-fest "^2.3.3" 6896 + 6462 6897 https-proxy-agent@^5.0.0: 6463 6898 version "5.0.1" 6464 6899 resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" ··· 6513 6948 dependencies: 6514 6949 harmony-reflect "^1.4.6" 6515 6950 6516 - ieee754@^1.1.13: 6951 + ieee754@^1.1.13, ieee754@^1.2.1: 6517 6952 version "1.2.1" 6518 6953 resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 6519 6954 integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ··· 6585 7020 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 6586 7021 integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== 6587 7022 6588 - ini@^1.3.5: 7023 + ini@^1.3.5, ini@~1.3.0: 6589 7024 version "1.3.8" 6590 7025 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" 6591 7026 integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== ··· 6647 7082 version "0.2.1" 6648 7083 resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 6649 7084 integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== 7085 + 7086 + is-arrayish@^0.3.1: 7087 + version "0.3.2" 7088 + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" 7089 + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== 6650 7090 6651 7091 is-bigint@^1.0.1: 6652 7092 version "1.0.4" ··· 8050 8490 resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" 8051 8491 integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== 8052 8492 8493 + jsonwebtoken@^8.5.1: 8494 + version "8.5.1" 8495 + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" 8496 + integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== 8497 + dependencies: 8498 + jws "^3.2.2" 8499 + lodash.includes "^4.3.0" 8500 + lodash.isboolean "^3.0.3" 8501 + lodash.isinteger "^4.0.4" 8502 + lodash.isnumber "^3.0.3" 8503 + lodash.isplainobject "^4.0.6" 8504 + lodash.isstring "^4.0.1" 8505 + lodash.once "^4.0.0" 8506 + ms "^2.1.1" 8507 + semver "^5.6.0" 8508 + 8053 8509 "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.2: 8054 8510 version "3.3.3" 8055 8511 resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" ··· 8058 8514 array-includes "^3.1.5" 8059 8515 object.assign "^4.1.3" 8060 8516 8517 + jwa@^1.4.1: 8518 + version "1.4.1" 8519 + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" 8520 + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== 8521 + dependencies: 8522 + buffer-equal-constant-time "1.0.1" 8523 + ecdsa-sig-formatter "1.0.11" 8524 + safe-buffer "^5.0.1" 8525 + 8526 + jws@^3.2.2: 8527 + version "3.2.2" 8528 + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" 8529 + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== 8530 + dependencies: 8531 + jwa "^1.4.1" 8532 + safe-buffer "^5.0.1" 8533 + 8061 8534 kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: 8062 8535 version "3.2.2" 8063 8536 resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" ··· 8091 8564 version "2.0.5" 8092 8565 resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" 8093 8566 integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== 8567 + 8568 + kysely@^0.22.0: 8569 + version "0.22.0" 8570 + resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.22.0.tgz#8aac53942da3cadc604d7d154a746d983fe8f7b9" 8571 + integrity sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ== 8094 8572 8095 8573 language-subtag-registry@^0.3.20: 8096 8574 version "0.3.22" ··· 8186 8664 resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" 8187 8665 integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== 8188 8666 8667 + lodash.includes@^4.3.0: 8668 + version "4.3.0" 8669 + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" 8670 + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== 8671 + 8672 + lodash.isboolean@^3.0.3: 8673 + version "3.0.3" 8674 + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" 8675 + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== 8676 + 8189 8677 lodash.isequal@^4.5.0: 8190 8678 version "4.5.0" 8191 8679 resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" 8192 8680 integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== 8193 8681 8682 + lodash.isinteger@^4.0.4: 8683 + version "4.0.4" 8684 + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" 8685 + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== 8686 + 8687 + lodash.isnumber@^3.0.3: 8688 + version "3.0.3" 8689 + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" 8690 + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== 8691 + 8692 + lodash.isplainobject@^4.0.6: 8693 + version "4.0.6" 8694 + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 8695 + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== 8696 + 8697 + lodash.isstring@^4.0.1: 8698 + version "4.0.1" 8699 + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" 8700 + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== 8701 + 8194 8702 lodash.memoize@^4.1.2: 8195 8703 version "4.1.2" 8196 8704 resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" ··· 8205 8713 version "4.5.0" 8206 8714 resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" 8207 8715 integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg== 8716 + 8717 + lodash.once@^4.0.0: 8718 + version "4.1.1" 8719 + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" 8720 + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== 8208 8721 8209 8722 lodash.sortby@^4.7.0: 8210 8723 version "4.7.0" ··· 8781 9294 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 8782 9295 integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 8783 9296 8784 - mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: 9297 + mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@^2.1.35, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: 8785 9298 version "2.1.35" 8786 9299 resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 8787 9300 integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== ··· 8803 9316 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 8804 9317 integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== 8805 9318 9319 + mimic-response@^3.1.0: 9320 + version "3.1.0" 9321 + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" 9322 + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== 9323 + 8806 9324 min-indent@^1.0.0: 8807 9325 version "1.0.1" 8808 9326 resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" ··· 8834 9352 dependencies: 8835 9353 brace-expansion "^2.0.1" 8836 9354 8837 - minimist@^1.2.0, minimist@^1.2.6: 9355 + minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: 8838 9356 version "1.2.7" 8839 9357 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" 8840 9358 integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== ··· 8847 9365 for-in "^1.0.2" 8848 9366 is-extendable "^1.0.1" 8849 9367 9368 + mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: 9369 + version "0.5.3" 9370 + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" 9371 + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== 9372 + 8850 9373 mkdirp@^0.5.1, mkdirp@~0.5.1: 8851 9374 version "0.5.6" 8852 9375 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" ··· 8891 9414 dependencies: 8892 9415 dns-packet "^5.2.2" 8893 9416 thunky "^1.0.2" 9417 + 9418 + multiformats@^9.4.2, multiformats@^9.5.4, multiformats@^9.6.4: 9419 + version "9.9.0" 9420 + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" 9421 + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== 8894 9422 8895 9423 nanoid@^3.3.1, nanoid@^3.3.4: 8896 9424 version "3.3.4" ··· 8914 9442 snapdragon "^0.8.1" 8915 9443 to-regex "^3.0.1" 8916 9444 9445 + napi-build-utils@^1.0.1: 9446 + version "1.0.2" 9447 + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" 9448 + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== 9449 + 8917 9450 natural-compare-lite@^1.4.0: 8918 9451 version "1.4.0" 8919 9452 resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" ··· 8929 9462 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 8930 9463 integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 8931 9464 8932 - neo-async@^2.5.0, neo-async@^2.6.2: 9465 + neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.2: 8933 9466 version "2.6.2" 8934 9467 resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" 8935 9468 integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== ··· 8952 9485 resolved "https://registry.yarnpkg.com/nocache/-/nocache-3.0.4.tgz#5b37a56ec6e09fc7d401dceaed2eab40c8bfdf79" 8953 9486 integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw== 8954 9487 9488 + node-abi@^3.3.0: 9489 + version "3.31.0" 9490 + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.31.0.tgz#dfb2ea3d01188eb80859f69bb4a4354090c1b355" 9491 + integrity sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ== 9492 + dependencies: 9493 + semver "^7.3.5" 9494 + 9495 + node-addon-api@^5.0.0: 9496 + version "5.1.0" 9497 + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" 9498 + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== 9499 + 8955 9500 node-dir@^0.1.17: 8956 9501 version "0.1.17" 8957 9502 resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" ··· 8985 9530 version "1.15.0" 8986 9531 resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" 8987 9532 integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== 9533 + 9534 + nodemailer-html-to-text@^3.2.0: 9535 + version "3.2.0" 9536 + resolved "https://registry.yarnpkg.com/nodemailer-html-to-text/-/nodemailer-html-to-text-3.2.0.tgz#91b959491fef8f7d91796047abb728aa86d4a12b" 9537 + integrity sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg== 9538 + dependencies: 9539 + html-to-text "7.1.1" 9540 + 9541 + nodemailer@^6.8.0: 9542 + version "6.9.0" 9543 + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.0.tgz#a17488ff470ff9edf1bb31d9ec23079bc94f7dd3" 9544 + integrity sha512-jFaCEGTeT3E/m/5R2MHWiyQH3pSARECRUDM+1hokOYc3lQAAG7ASuy+2jIsYVf+RVa9zePopSQwKNVFH8DKUpA== 8988 9545 8989 9546 normalize-css-color@^1.0.2: 8990 9547 version "1.0.2" ··· 9054 9611 resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.73.7.tgz#14c9b6ddc26cf99144f59eb542d7ae956e6b3192" 9055 9612 integrity sha512-DfelfvR843KADhSUATGGhuepVMRcf5VQX+6MQLy5AW0BKDLlO7Usj6YZeAAZP7P86QwsoTxB0RXCFiA7t6S1IQ== 9056 9613 9057 - object-assign@^4.1.0, object-assign@^4.1.1: 9614 + object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1: 9058 9615 version "4.1.1" 9059 9616 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 9060 9617 integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== ··· 9157 9714 resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" 9158 9715 integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== 9159 9716 9717 + on-exit-leak-free@^2.1.0: 9718 + version "2.1.0" 9719 + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4" 9720 + integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w== 9721 + 9160 9722 on-finished@2.4.1: 9161 9723 version "2.4.1" 9162 9724 resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" ··· 9183 9745 dependencies: 9184 9746 wrappy "1" 9185 9747 9748 + one-webcrypto@^1.0.3: 9749 + version "1.0.3" 9750 + resolved "https://registry.yarnpkg.com/one-webcrypto/-/one-webcrypto-1.0.3.tgz#f951243cde29b79b6745ad14966fc598a609997c" 9751 + integrity sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q== 9752 + 9186 9753 onetime@^5.1.0, onetime@^5.1.2: 9187 9754 version "5.1.2" 9188 9755 resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" ··· 9295 9862 dependencies: 9296 9863 p-limit "^3.0.2" 9297 9864 9865 + p-queue@^6.6.2: 9866 + version "6.6.2" 9867 + resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" 9868 + integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== 9869 + dependencies: 9870 + eventemitter3 "^4.0.4" 9871 + p-timeout "^3.2.0" 9872 + 9298 9873 p-retry@^4.5.0: 9299 9874 version "4.6.2" 9300 9875 resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" ··· 9303 9878 "@types/retry" "0.12.0" 9304 9879 retry "^0.13.1" 9305 9880 9881 + p-timeout@^3.0.0, p-timeout@^3.2.0: 9882 + version "3.2.0" 9883 + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" 9884 + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== 9885 + dependencies: 9886 + p-finally "^1.0.0" 9887 + 9306 9888 p-try@^2.0.0: 9307 9889 version "2.2.0" 9308 9890 resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 9309 9891 integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 9310 9892 9893 + p-wait-for@^3.2.0: 9894 + version "3.2.0" 9895 + resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.2.0.tgz#640429bcabf3b0dd9f492c31539c5718cb6a3f1f" 9896 + integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA== 9897 + dependencies: 9898 + p-timeout "^3.0.0" 9899 + 9900 + packet-reader@1.0.0: 9901 + version "1.0.0" 9902 + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" 9903 + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== 9904 + 9311 9905 param-case@^3.0.4: 9312 9906 version "3.0.4" 9313 9907 resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" ··· 9404 9998 resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 9405 9999 integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 9406 10000 10001 + peek-readable@^4.1.0: 10002 + version "4.1.0" 10003 + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" 10004 + integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== 10005 + 9407 10006 performance-now@^2.1.0: 9408 10007 version "2.1.0" 9409 10008 resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 9410 10009 integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== 9411 10010 10011 + pg-connection-string@^2.5.0: 10012 + version "2.5.0" 10013 + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" 10014 + integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== 10015 + 10016 + pg-int8@1.0.1: 10017 + version "1.0.1" 10018 + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" 10019 + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== 10020 + 10021 + pg-pool@^3.5.2: 10022 + version "3.5.2" 10023 + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.5.2.tgz#ed1bed1fb8d79f1c6fd5fb1c99e990fbf9ddf178" 10024 + integrity sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w== 10025 + 10026 + pg-protocol@^1.5.0: 10027 + version "1.5.0" 10028 + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" 10029 + integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== 10030 + 10031 + pg-types@^2.1.0: 10032 + version "2.2.0" 10033 + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" 10034 + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== 10035 + dependencies: 10036 + pg-int8 "1.0.1" 10037 + postgres-array "~2.0.0" 10038 + postgres-bytea "~1.0.0" 10039 + postgres-date "~1.0.4" 10040 + postgres-interval "^1.1.0" 10041 + 10042 + pg@^8.8.0: 10043 + version "8.8.0" 10044 + resolved "https://registry.yarnpkg.com/pg/-/pg-8.8.0.tgz#a77f41f9d9ede7009abfca54667c775a240da686" 10045 + integrity sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw== 10046 + dependencies: 10047 + buffer-writer "2.0.0" 10048 + packet-reader "1.0.0" 10049 + pg-connection-string "^2.5.0" 10050 + pg-pool "^3.5.2" 10051 + pg-protocol "^1.5.0" 10052 + pg-types "^2.1.0" 10053 + pgpass "1.x" 10054 + 10055 + pgpass@1.x: 10056 + version "1.0.5" 10057 + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" 10058 + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== 10059 + dependencies: 10060 + split2 "^4.1.0" 10061 + 9412 10062 picocolors@^0.2.1: 9413 10063 version "0.2.1" 9414 10064 resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" ··· 9433 10083 version "4.0.1" 9434 10084 resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" 9435 10085 integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== 10086 + 10087 + pino-abstract-transport@v1.0.0: 10088 + version "1.0.0" 10089 + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3" 10090 + integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA== 10091 + dependencies: 10092 + readable-stream "^4.0.0" 10093 + split2 "^4.0.0" 10094 + 10095 + pino-http@^8.2.1: 10096 + version "8.3.1" 10097 + resolved "https://registry.yarnpkg.com/pino-http/-/pino-http-8.3.1.tgz#d33ade22044ce6efa7b5ed512e1bd7afdf9904ba" 10098 + integrity sha512-wqQMH2H+Di4ur5gaCYvWOMYhGmmmygHsgJduKr2HASSoegNsxfxiCDGxYYV8muOB8jKDHPoBXKGp03af+JESqg== 10099 + dependencies: 10100 + fast-url-parser "^1.1.3" 10101 + get-caller-file "^2.0.5" 10102 + pino "^8.0.0" 10103 + pino-std-serializers "^6.0.0" 10104 + process-warning "^2.0.0" 10105 + 10106 + pino-std-serializers@^6.0.0: 10107 + version "6.1.0" 10108 + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz#307490fd426eefc95e06067e85d8558603e8e844" 10109 + integrity sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g== 10110 + 10111 + pino@^8.0.0, pino@^8.6.1: 10112 + version "8.8.0" 10113 + resolved "https://registry.yarnpkg.com/pino/-/pino-8.8.0.tgz#1f0d6695a224aa06afc7ad60f2ccc4772d3b9233" 10114 + integrity sha512-cF8iGYeu2ODg2gIwgAHcPrtR63ILJz3f7gkogaHC/TXVVXxZgInmNYiIpDYEwgEkxZti2Se6P2W2DxlBIZe6eQ== 10115 + dependencies: 10116 + atomic-sleep "^1.0.0" 10117 + fast-redact "^3.1.1" 10118 + on-exit-leak-free "^2.1.0" 10119 + pino-abstract-transport v1.0.0 10120 + pino-std-serializers "^6.0.0" 10121 + process-warning "^2.0.0" 10122 + quick-format-unescaped "^4.0.3" 10123 + real-require "^0.2.0" 10124 + safe-stable-stringify "^2.3.1" 10125 + sonic-boom "^3.1.0" 10126 + thread-stream "^2.0.0" 9436 10127 9437 10128 pirates@^4.0.4, pirates@^4.0.5: 9438 10129 version "4.0.5" ··· 10017 10708 picocolors "^1.0.0" 10018 10709 source-map-js "^1.0.2" 10019 10710 10711 + postgres-array@~2.0.0: 10712 + version "2.0.0" 10713 + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" 10714 + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== 10715 + 10716 + postgres-bytea@~1.0.0: 10717 + version "1.0.0" 10718 + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" 10719 + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== 10720 + 10721 + postgres-date@~1.0.4: 10722 + version "1.0.7" 10723 + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" 10724 + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== 10725 + 10726 + postgres-interval@^1.1.0: 10727 + version "1.2.0" 10728 + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" 10729 + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== 10730 + dependencies: 10731 + xtend "^4.0.0" 10732 + 10733 + prebuild-install@^7.1.0, prebuild-install@^7.1.1: 10734 + version "7.1.1" 10735 + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" 10736 + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== 10737 + dependencies: 10738 + detect-libc "^2.0.0" 10739 + expand-template "^2.0.3" 10740 + github-from-package "0.0.0" 10741 + minimist "^1.2.3" 10742 + mkdirp-classic "^0.5.3" 10743 + napi-build-utils "^1.0.1" 10744 + node-abi "^3.3.0" 10745 + pump "^3.0.0" 10746 + rc "^1.2.7" 10747 + simple-get "^4.0.0" 10748 + tar-fs "^2.0.0" 10749 + tunnel-agent "^0.6.0" 10750 + 10020 10751 prelude-ls@^1.2.1: 10021 10752 version "1.2.1" 10022 10753 resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" ··· 10095 10826 resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 10096 10827 integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 10097 10828 10829 + process-warning@^2.0.0: 10830 + version "2.1.0" 10831 + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.1.0.tgz#1e60e3bfe8183033bbc1e702c2da74f099422d1a" 10832 + integrity sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg== 10833 + 10834 + process@^0.11.10: 10835 + version "0.11.10" 10836 + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 10837 + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== 10838 + 10098 10839 promise@^7.1.1: 10099 10840 version "7.3.1" 10100 10841 resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" ··· 10147 10888 end-of-stream "^1.1.0" 10148 10889 once "^1.3.1" 10149 10890 10891 + punycode@^1.3.2: 10892 + version "1.4.1" 10893 + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 10894 + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== 10895 + 10150 10896 punycode@^2.1.0, punycode@^2.1.1: 10151 10897 version "2.1.1" 10152 10898 resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" ··· 10174 10920 resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 10175 10921 integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 10176 10922 10923 + quick-format-unescaped@^4.0.3: 10924 + version "4.0.4" 10925 + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" 10926 + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== 10927 + 10177 10928 quick-lru@^5.1.1: 10178 10929 version "5.1.1" 10179 10930 resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" ··· 10207 10958 http-errors "2.0.0" 10208 10959 iconv-lite "0.4.24" 10209 10960 unpipe "1.0.0" 10961 + 10962 + rc@^1.2.7: 10963 + version "1.2.8" 10964 + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" 10965 + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== 10966 + dependencies: 10967 + deep-extend "^0.6.0" 10968 + ini "~1.3.0" 10969 + minimist "^1.2.0" 10970 + strip-json-comments "~2.0.1" 10210 10971 10211 10972 react-app-polyfill@^3.0.0: 10212 10973 version "3.0.0" ··· 10617 11378 string_decoder "~1.1.1" 10618 11379 util-deprecate "~1.0.1" 10619 11380 10620 - readable-stream@^3.0.6, readable-stream@^3.4.0: 11381 + readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: 10621 11382 version "3.6.0" 10622 11383 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" 10623 11384 integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== ··· 10626 11387 string_decoder "^1.1.1" 10627 11388 util-deprecate "^1.0.1" 10628 11389 11390 + readable-stream@^4.0.0: 11391 + version "4.3.0" 11392 + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.3.0.tgz#0914d0c72db03b316c9733bb3461d64a3cc50cba" 11393 + integrity sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ== 11394 + dependencies: 11395 + abort-controller "^3.0.0" 11396 + buffer "^6.0.3" 11397 + events "^3.3.0" 11398 + process "^0.11.10" 11399 + 11400 + readable-web-to-node-stream@^3.0.0: 11401 + version "3.0.2" 11402 + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" 11403 + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== 11404 + dependencies: 11405 + readable-stream "^3.6.0" 11406 + 10629 11407 readdirp@~3.6.0: 10630 11408 version "3.6.0" 10631 11409 resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" ··· 10637 11415 version "1.3.0" 10638 11416 resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c" 10639 11417 integrity sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg== 11418 + 11419 + real-require@^0.2.0: 11420 + version "0.2.0" 11421 + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" 11422 + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== 10640 11423 10641 11424 recast@^0.20.4: 10642 11425 version "0.20.5" ··· 10868 11651 resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 10869 11652 integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 10870 11653 11654 + rfdc@^1.2.0: 11655 + version "1.3.0" 11656 + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" 11657 + integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== 11658 + 10871 11659 rimraf@^3.0.0, rimraf@^3.0.2: 10872 11660 version "3.0.2" 10873 11661 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" ··· 10895 11683 base-64 "0.1.0" 10896 11684 glob "7.0.6" 10897 11685 11686 + roarr@^7.0.4: 11687 + version "7.14.2" 11688 + resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.14.2.tgz#2d4865b9f06779901258f1a5a8f6b4315fc46f5f" 11689 + integrity sha512-9vC/n53oTJEyAl0ZJczKjJ5mJheb2DaqiaNSnxDWrqiRTrozxSvSq05yCTN+Fc7e5mhDRTTZ14RlMu1x4tEc0w== 11690 + dependencies: 11691 + boolean "^3.1.4" 11692 + fast-json-stringify "^2.7.10" 11693 + fast-printf "^1.6.9" 11694 + globalthis "^1.0.2" 11695 + safe-stable-stringify "^2.4.1" 11696 + semver-compare "^1.0.0" 11697 + 10898 11698 rollup-plugin-terser@^7.0.0: 10899 11699 version "7.0.2" 10900 11700 resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" ··· 10931 11731 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 10932 11732 integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 10933 11733 10934 - safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: 11734 + safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: 10935 11735 version "5.2.1" 10936 11736 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 10937 11737 integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== ··· 10951 11751 integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== 10952 11752 dependencies: 10953 11753 ret "~0.1.10" 11754 + 11755 + safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.4.1: 11756 + version "2.4.2" 11757 + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.2.tgz#ec7b037768098bf65310d1d64370de0dc02353aa" 11758 + integrity sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA== 10954 11759 10955 11760 "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": 10956 11761 version "2.1.2" ··· 11046 11851 dependencies: 11047 11852 node-forge "^1" 11048 11853 11854 + semver-compare@^1.0.0: 11855 + version "1.0.0" 11856 + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" 11857 + integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== 11858 + 11049 11859 semver@7.3.8, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: 11050 11860 version "7.3.8" 11051 11861 resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" ··· 11161 11971 dependencies: 11162 11972 kind-of "^6.0.2" 11163 11973 11974 + sharp@^0.31.2: 11975 + version "0.31.3" 11976 + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.31.3.tgz#60227edc5c2be90e7378a210466c99aefcf32688" 11977 + integrity sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg== 11978 + dependencies: 11979 + color "^4.2.3" 11980 + detect-libc "^2.0.1" 11981 + node-addon-api "^5.0.0" 11982 + prebuild-install "^7.1.1" 11983 + semver "^7.3.8" 11984 + simple-get "^4.0.1" 11985 + tar-fs "^2.1.1" 11986 + tunnel-agent "^0.6.0" 11987 + 11164 11988 shebang-command@^1.2.0: 11165 11989 version "1.2.0" 11166 11990 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" ··· 11204 12028 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" 11205 12029 integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== 11206 12030 12031 + simple-concat@^1.0.0: 12032 + version "1.0.1" 12033 + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" 12034 + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== 12035 + 12036 + simple-get@^4.0.0, simple-get@^4.0.1: 12037 + version "4.0.1" 12038 + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" 12039 + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== 12040 + dependencies: 12041 + decompress-response "^6.0.0" 12042 + once "^1.3.1" 12043 + simple-concat "^1.0.0" 12044 + 12045 + simple-swizzle@^0.2.2: 12046 + version "0.2.2" 12047 + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" 12048 + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== 12049 + dependencies: 12050 + is-arrayish "^0.3.1" 12051 + 11207 12052 sisteransi@^1.0.5: 11208 12053 version "1.0.5" 11209 12054 resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" ··· 11267 12112 uuid "^8.3.2" 11268 12113 websocket-driver "^0.7.4" 11269 12114 12115 + sonic-boom@^3.1.0: 12116 + version "3.2.1" 12117 + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.2.1.tgz#972ceab831b5840a08a002fa95a672008bda1c38" 12118 + integrity sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A== 12119 + dependencies: 12120 + atomic-sleep "^1.0.0" 12121 + 11270 12122 source-list-map@^2.0.0, source-list-map@^2.0.1: 11271 12123 version "2.0.1" 11272 12124 resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" ··· 11375 12227 dependencies: 11376 12228 extend-shallow "^3.0.0" 11377 12229 12230 + split2@^4.0.0, split2@^4.1.0: 12231 + version "4.1.0" 12232 + resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" 12233 + integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== 12234 + 11378 12235 sprintf-js@~1.0.2: 11379 12236 version "1.0.3" 11380 12237 resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" ··· 11448 12305 resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" 11449 12306 integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== 11450 12307 12308 + string-similarity@^4.0.1: 12309 + version "4.0.4" 12310 + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" 12311 + integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== 12312 + 11451 12313 string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 11452 12314 version "4.2.3" 11453 12315 resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" ··· 11570 12432 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 11571 12433 integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 11572 12434 12435 + strip-json-comments@~2.0.1: 12436 + version "2.0.1" 12437 + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 12438 + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== 12439 + 12440 + strtok3@^6.2.4: 12441 + version "6.3.0" 12442 + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" 12443 + integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== 12444 + dependencies: 12445 + "@tokenizer/token" "^0.3.0" 12446 + peek-readable "^4.1.0" 12447 + 11573 12448 style-loader@^3.3.1: 11574 12449 version "3.3.1" 11575 12450 resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" ··· 11703 12578 resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" 11704 12579 integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== 11705 12580 12581 + tar-fs@^2.0.0, tar-fs@^2.1.1: 12582 + version "2.1.1" 12583 + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" 12584 + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== 12585 + dependencies: 12586 + chownr "^1.1.1" 12587 + mkdirp-classic "^0.5.2" 12588 + pump "^3.0.0" 12589 + tar-stream "^2.1.4" 12590 + 12591 + tar-stream@^2.1.4: 12592 + version "2.2.0" 12593 + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" 12594 + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== 12595 + dependencies: 12596 + bl "^4.0.3" 12597 + end-of-stream "^1.4.1" 12598 + fs-constants "^1.0.0" 12599 + inherits "^2.0.3" 12600 + readable-stream "^3.1.1" 12601 + 11706 12602 temp-dir@^2.0.0: 11707 12603 version "2.0.0" 11708 12604 resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" ··· 11776 12672 resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 11777 12673 integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== 11778 12674 12675 + thread-stream@^2.0.0: 12676 + version "2.3.0" 12677 + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.3.0.tgz#4fc07fb39eff32ae7bad803cb7dd9598349fed33" 12678 + integrity sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA== 12679 + dependencies: 12680 + real-require "^0.2.0" 12681 + 11779 12682 throat@^5.0.0: 11780 12683 version "5.0.0" 11781 12684 resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" ··· 11851 12754 resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 11852 12755 integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 11853 12756 12757 + token-types@^4.1.1: 12758 + version "4.2.1" 12759 + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" 12760 + integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== 12761 + dependencies: 12762 + "@tokenizer/token" "^0.3.0" 12763 + ieee754 "^1.2.1" 12764 + 11854 12765 tough-cookie@^4.0.0: 11855 12766 version "4.1.2" 11856 12767 resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" ··· 11900 12811 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 11901 12812 integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 11902 12813 11903 - tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0: 12814 + tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: 11904 12815 version "2.4.1" 11905 12816 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" 11906 12817 integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== ··· 11912 12823 dependencies: 11913 12824 tslib "^1.8.1" 11914 12825 12826 + tunnel-agent@^0.6.0: 12827 + version "0.6.0" 12828 + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 12829 + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== 12830 + dependencies: 12831 + safe-buffer "^5.0.1" 12832 + 11915 12833 type-check@^0.4.0, type-check@~0.4.0: 11916 12834 version "0.4.0" 11917 12835 resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" ··· 11951 12869 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" 11952 12870 integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== 11953 12871 12872 + type-fest@^2.3.3: 12873 + version "2.19.0" 12874 + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" 12875 + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== 12876 + 11954 12877 type-is@~1.6.18: 11955 12878 version "1.6.18" 11956 12879 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" ··· 11990 12913 dependencies: 11991 12914 commander "~2.13.0" 11992 12915 source-map "~0.6.1" 12916 + 12917 + uglify-js@^3.1.4: 12918 + version "3.17.4" 12919 + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" 12920 + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== 12921 + 12922 + uint8arrays@3.0.0: 12923 + version "3.0.0" 12924 + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz#260869efb8422418b6f04e3fac73a3908175c63b" 12925 + integrity sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA== 12926 + dependencies: 12927 + multiformats "^9.4.2" 11993 12928 11994 12929 unbox-primitive@^1.0.2: 11995 12930 version "1.0.2" ··· 12175 13110 "@types/istanbul-lib-coverage" "^2.0.1" 12176 13111 convert-source-map "^1.6.0" 12177 13112 12178 - vary@~1.1.2: 13113 + varint@^6.0.0: 13114 + version "6.0.0" 13115 + resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" 13116 + integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== 13117 + 13118 + vary@^1, vary@~1.1.2: 12179 13119 version "1.1.2" 12180 13120 resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 12181 13121 integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== ··· 12459 13399 resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" 12460 13400 integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== 12461 13401 13402 + wordwrap@^1.0.0: 13403 + version "1.0.0" 13404 + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 13405 + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== 13406 + 12462 13407 workbox-background-sync@6.5.4: 12463 13408 version "6.5.4" 12464 13409 resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9" ··· 12710 13655 resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" 12711 13656 integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== 12712 13657 12713 - xtend@^4.0.2, xtend@~4.0.1: 13658 + xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: 12714 13659 version "4.0.2" 12715 13660 resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 12716 13661 integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== ··· 12806 13751 resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 12807 13752 integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== 12808 13753 12809 - zod@^3.14.2: 13754 + zod@^3.14.2, zod@^3.20.2: 12810 13755 version "3.20.2" 12811 13756 resolved "https://registry.yarnpkg.com/zod/-/zod-3.20.2.tgz#068606642c8f51b3333981f91c0a8ab37dfc2807" 12812 13757 integrity sha512-1MzNQdAvO+54H+EaK5YpyEy0T+Ejo/7YLHS93G3RnYWh5gaotGHwGeN/ZO687qEDU2y4CdStQYXVHIgrUl5UVQ==