forked from Kieran/snort
Merge pull request #65 from v0l/caching
add user DB and cache nip-05 verifications
This commit is contained in:
commit
9ae65b0de8
@ -16,12 +16,15 @@
|
||||
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
|
||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||
"bech32": "^2.0.0",
|
||||
"dexie": "^3.2.2",
|
||||
"dexie-react-hooks": "^1.1.1",
|
||||
"light-bolt11-decoder": "^2.1.0",
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-intersection-observer": "^9.4.1",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-query": "^3.39.2",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-router-dom": "^6.5.0",
|
||||
"react-scripts": "5.0.1",
|
||||
|
16
src/db.ts
Normal file
16
src/db.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import Dexie, { Table } from 'dexie';
|
||||
|
||||
import type { User } from './nostr/types';
|
||||
|
||||
export class SnortDB extends Dexie {
|
||||
users!: Table<User>;
|
||||
|
||||
constructor() {
|
||||
super('snortDB');
|
||||
this.version(1).stores({
|
||||
users: '++pubkey, name, display_name, picture, nip05' // Primary key and indexed props
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new SnortDB();
|
@ -1,45 +1,55 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheck, faSpinner, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import './Nip05.css'
|
||||
|
||||
export function useIsVerified(nip05, pubkey) {
|
||||
const [isVerified, setIsVerified] = useState(false)
|
||||
const [couldNotVerify, setCouldNotVerify] = useState(false)
|
||||
const [name, domain] = nip05 ? nip05.split('@') : []
|
||||
|
||||
useEffect(() => {
|
||||
if (!nip05 || !pubkey) {
|
||||
return
|
||||
function fetchNip05Pubkey(name, domain) {
|
||||
if (!name || !domain) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
setCouldNotVerify(false)
|
||||
setIsVerified(false)
|
||||
|
||||
fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`)
|
||||
return fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(name)}`)
|
||||
.then((res) => res.json())
|
||||
.then(({ names }) => {
|
||||
if (names && names[name]) {
|
||||
setIsVerified(names[name] === pubkey)
|
||||
}
|
||||
const match = Object.keys(names).find(n => {
|
||||
return n.toLowerCase() === name.toLowerCase()
|
||||
})
|
||||
.catch((err) => {
|
||||
setCouldNotVerify(true)
|
||||
console.error("Couldn't verifiy nip05")
|
||||
return names[match]
|
||||
})
|
||||
}, [nip05, pubkey])
|
||||
|
||||
return { name, domain: domain?.toLowerCase(), isVerified, couldNotVerify }
|
||||
}
|
||||
|
||||
const Nip05 = ({ name, domain, isVerified, couldNotVerify }) => {
|
||||
const VERIFICATION_CACHE_TIME = 24 * 60 * 60 * 1000
|
||||
const VERIFICATION_STALE_TIMEOUT = 10 * 60 * 1000
|
||||
|
||||
export function useIsVerified(nip05, pubkey) {
|
||||
const [name, domain] = nip05 ? nip05.split('@') : []
|
||||
const address = domain && `${name}@${domain.toLowerCase()}`
|
||||
const { isLoading, isError, isSuccess, isIdle, data } = useQuery(
|
||||
['nip05', nip05],
|
||||
() => fetchNip05Pubkey(name, domain),
|
||||
{
|
||||
retry: false,
|
||||
cacheTime: VERIFICATION_CACHE_TIME,
|
||||
staleTime: VERIFICATION_STALE_TIMEOUT
|
||||
},
|
||||
)
|
||||
const isVerified = isSuccess && data === pubkey
|
||||
const cantVerify = isSuccess && data !== pubkey
|
||||
return { isVerified, couldNotVerify: isError || cantVerify }
|
||||
}
|
||||
|
||||
const Nip05 = ({ nip05, pubkey, defaultUsername = '' }) => {
|
||||
const [name, domain] = nip05 ? nip05.split('@') : []
|
||||
const isDefaultUser = name === '_'
|
||||
const { isVerified, couldNotVerify } = useIsVerified(nip05, pubkey)
|
||||
|
||||
return (
|
||||
<div className="flex nip05" onClick={(ev) => ev.stopPropagation()}>
|
||||
{!isDefaultUser && <div className="nick">{name}</div>}
|
||||
<div className={`domain ${isVerified ? 'text-gradient' : ''}`} data-domain={isVerified ? domain : ''}>
|
||||
<div className="nick">
|
||||
{isDefaultUser ? defaultUsername : name}
|
||||
</div>
|
||||
<div className={`domain text-gradient`} data-domain={domain?.toLowerCase()}>
|
||||
{domain}
|
||||
</div>
|
||||
<span className="badge">
|
||||
|
@ -9,6 +9,7 @@ import Text from "./Text";
|
||||
import { eventLink, hexToBech32 } from "../Util";
|
||||
import NoteFooter from "./NoteFooter";
|
||||
import NoteTime from "./NoteTime";
|
||||
import Nip05 from "./Nip05";
|
||||
|
||||
export default function Note(props) {
|
||||
const navigate = useNavigate();
|
||||
|
@ -17,4 +17,18 @@
|
||||
|
||||
.pfp a:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--gray-superlight);
|
||||
}
|
||||
|
||||
.pfp .profile-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: bold;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
|
||||
.pfp .nip05 {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
margin-top: -.2em;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import useProfile from "../feed/ProfileFeed";
|
||||
import { hexToBech32, profileLink } from "../Util";
|
||||
import LazyImage from "./LazyImage";
|
||||
import Nip05 from "./Nip05";
|
||||
|
||||
export default function ProfileImage({ pubkey, subHeader, showUsername = true, className, link }) {
|
||||
const navigate = useNavigate();
|
||||
@ -26,7 +27,18 @@ export default function ProfileImage({ pubkey, subHeader, showUsername = true, c
|
||||
<div className={`pfp ${className}`}>
|
||||
<LazyImage src={hasImage ? user.picture : Nostrich} onClick={() => navigate(link ?? profileLink(pubkey))} />
|
||||
{showUsername && (<div className="f-grow">
|
||||
<Link key={pubkey} to={link ?? profileLink(pubkey)}>{name}</Link>
|
||||
<Link key={pubkey} to={link ?? profileLink(pubkey)}>
|
||||
<div className="profile-name">
|
||||
<div>{name}</div>
|
||||
{user?.nip05 && (
|
||||
<Nip05
|
||||
nip05={user.nip05}
|
||||
pubkey={user.pubkey}
|
||||
defaultUsername={user.display_name || user.name}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
{subHeader ? <>{subHeader}</> : null}
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Component } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useLiveQuery } from "dexie-react-hooks";
|
||||
|
||||
import ReactTextareaAutocomplete from "@webscopeio/react-textarea-autocomplete";
|
||||
|
||||
// @ts-expect-error
|
||||
import Nip05, { useIsVerified } from "./Nip05";
|
||||
import Nip05 from "./Nip05";
|
||||
import "@webscopeio/react-textarea-autocomplete/style.css";
|
||||
import "./Textarea.css";
|
||||
// @ts-expect-error
|
||||
@ -11,10 +12,11 @@ import Nostrich from "../nostrich.jpg";
|
||||
// @ts-expect-error
|
||||
import { hexToBech32 } from "../Util";
|
||||
import type { User } from "../nostr/types";
|
||||
import { db } from "../db";
|
||||
|
||||
function searchUsers(query: string, users: Record<string, User>) {
|
||||
function searchUsers(query: string, users: User[]) {
|
||||
const q = query.toLowerCase()
|
||||
return Object.values(users).filter(({ name, display_name, about, nip05 }) => {
|
||||
return users.filter(({ name, display_name, about, nip05 }: User) => {
|
||||
return name?.toLowerCase().includes(q)
|
||||
|| display_name?.toLowerCase().includes(q)
|
||||
|| about?.toLowerCase().includes(q)
|
||||
@ -23,7 +25,6 @@ function searchUsers(query: string, users: Record<string, User>) {
|
||||
}
|
||||
|
||||
const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: User) => {
|
||||
const { isVerified, couldNotVerify, name, domain } = useIsVerified(nip05, pubkey)
|
||||
return (
|
||||
<div key={pubkey} className="user-item">
|
||||
<div className="user-picture">
|
||||
@ -31,30 +32,39 @@ const UserItem = ({ pubkey, display_name, picture, nip05, ...rest }: User) => {
|
||||
</div>
|
||||
<div className="user-details">
|
||||
<strong>{display_name || rest.name}</strong>
|
||||
<Nip05 name={name} domain={domain} isVerified={isVerified} couldNotVerify={couldNotVerify} />
|
||||
<Nip05 nip05={nip05} pubkey={pubkey} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default class Textarea extends Component {
|
||||
render() {
|
||||
// @ts-expect-error
|
||||
const { users, onChange, ...rest } = this.props
|
||||
function normalizeUser({ pubkey, picture, nip05, name, display_name }: User) {
|
||||
return { pubkey, nip05, name, picture, display_name }
|
||||
}
|
||||
|
||||
const Textarea = ({ users, onChange, ...rest }: any) => {
|
||||
const normalizedUsers = Object.keys(users).reduce((acc, pk) => {
|
||||
return {...acc, [pk]: normalizeUser(users[pk]) }
|
||||
}, {})
|
||||
const dbUsers = useLiveQuery(
|
||||
() => db.users.toArray().then(usrs => {
|
||||
return usrs.reduce((acc, usr) => {
|
||||
return { ...acc, [usr.pubkey]: normalizeUser(usr)}
|
||||
}, {})
|
||||
})
|
||||
)
|
||||
const allUsers: User[] = Object.values({...normalizedUsers, ...dbUsers})
|
||||
|
||||
return (
|
||||
<ReactTextareaAutocomplete
|
||||
{...rest}
|
||||
loadingComponent={() => <span>Loading....</span>}
|
||||
placeholder="Say something!"
|
||||
ref={rta => {
|
||||
// @ts-expect-error
|
||||
this.rta = rta;
|
||||
}}
|
||||
onChange={onChange}
|
||||
trigger={{
|
||||
"@": {
|
||||
afterWhitespace: true,
|
||||
dataProvider: token => searchUsers(token, users),
|
||||
dataProvider: token => dbUsers ? searchUsers(token, allUsers) : [],
|
||||
component: (props: any) => <UserItem {...props.entity} />,
|
||||
output: (item: any) => `@${hexToBech32("npub", item.pubkey)}`
|
||||
}
|
||||
@ -62,4 +72,5 @@ export default class Textarea extends Component {
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Textarea
|
||||
|
@ -4,6 +4,7 @@ import EventKind from "../nostr/EventKind";
|
||||
import { Subscriptions } from "../nostr/Subscriptions";
|
||||
import { addDirectMessage, addNotifications, setFollows, setRelays } from "../state/Login";
|
||||
import { setUserData } from "../state/Users";
|
||||
import { db } from "../db";
|
||||
import useSubscription from "./Subscription";
|
||||
import { mapEventToProfile } from "./UsersFeed";
|
||||
|
||||
@ -41,7 +42,8 @@ export default function useLoginFeed() {
|
||||
useEffect(() => {
|
||||
let contactList = notes.filter(a => a.kind === EventKind.ContactList);
|
||||
let notifications = notes.filter(a => a.kind === EventKind.TextNote);
|
||||
let metadata = notes.filter(a => a.kind === EventKind.SetMetadata).map(a => mapEventToProfile(a));
|
||||
let metadata = notes.filter(a => a.kind === EventKind.SetMetadata)
|
||||
let profiles = metadata.map(a => mapEventToProfile(a));
|
||||
let dms = notes.filter(a => a.kind === EventKind.DirectMessage);
|
||||
|
||||
for (let cl of contactList) {
|
||||
@ -60,7 +62,11 @@ export default function useLoginFeed() {
|
||||
}
|
||||
}
|
||||
dispatch(addNotifications(notifications));
|
||||
dispatch(setUserData(metadata));
|
||||
dispatch(setUserData(profiles));
|
||||
const userMetadata = metadata.map(ev => {
|
||||
return {...JSON.parse(ev.content), pubkey: ev.pubkey }
|
||||
})
|
||||
db.users.bulkPut(metadata);
|
||||
dispatch(addDirectMessage(dms));
|
||||
}, [notes]);
|
||||
}
|
@ -2,6 +2,7 @@ import { useEffect, useMemo } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { ProfileCacheExpire } from "../Const";
|
||||
import EventKind from "../nostr/EventKind";
|
||||
import { db } from "../db";
|
||||
import { Subscriptions } from "../nostr/Subscriptions";
|
||||
import { setUserData } from "../state/Users";
|
||||
import useSubscription from "./Subscription";
|
||||
@ -34,7 +35,12 @@ export default function useUsersCache() {
|
||||
const results = useSubscription(sub);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setUserData(results.notes.map(a => mapEventToProfile(a))));
|
||||
const userData = results.notes.map(a => mapEventToProfile(a));
|
||||
dispatch(setUserData(userData));
|
||||
const profiles = results.notes.map(ev => {
|
||||
return {...JSON.parse(ev.content), pubkey: ev.pubkey }
|
||||
});
|
||||
db.users.bulkPut(profiles);
|
||||
}, [results]);
|
||||
|
||||
return results;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import './index.css';
|
||||
|
||||
import React from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { Provider } from 'react-redux'
|
||||
import {
|
||||
@ -28,6 +29,11 @@ import ChatPage from './pages/ChatPage';
|
||||
*/
|
||||
export const System = new NostrSystem();
|
||||
|
||||
/**
|
||||
* HTTP query provider
|
||||
*/
|
||||
const HTTP = new QueryClient()
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
element: <Layout />,
|
||||
@ -81,7 +87,9 @@ const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={Store}>
|
||||
<QueryClientProvider client={HTTP}>
|
||||
<RouterProvider router={router} />
|
||||
</QueryClientProvider>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
export type User = {
|
||||
export interface User {
|
||||
name?: string
|
||||
about?: string
|
||||
display_name?: string
|
||||
|
@ -13,7 +13,7 @@ import { extractLnAddress, parseId, hexToBech32 } from "../Util";
|
||||
import Timeline from "../element/Timeline";
|
||||
import Text from '../element/Text'
|
||||
import LNURLTip from "../element/LNURLTip";
|
||||
import Nip05, { useIsVerified } from "../element/Nip05";
|
||||
import Nip05 from "../element/Nip05";
|
||||
import Copy from "../element/Copy";
|
||||
import ProfilePreview from "../element/ProfilePreview";
|
||||
import FollowersList from "../element/FollowersList";
|
||||
@ -37,9 +37,9 @@ export default function ProfilePage() {
|
||||
const [showLnQr, setShowLnQr] = useState(false);
|
||||
const [tab, setTab] = useState(ProfileTab.Notes);
|
||||
const about = Text({ content: user?.about })
|
||||
const { name, domain, isVerified, couldNotVerify } = useIsVerified(user?.nip05, user?.pubkey)
|
||||
const avatarUrl = (user?.picture?.length ?? 0) === 0 ? Nostrich : user?.picture
|
||||
const backgroundImage = `url(${avatarUrl})`
|
||||
const domain = user?.nip05 && user.nip05.split('@')[1]
|
||||
|
||||
useEffect(() => {
|
||||
setTab(ProfileTab.Notes);
|
||||
@ -50,7 +50,7 @@ export default function ProfilePage() {
|
||||
<div className="name">
|
||||
<h2>{user?.display_name || user?.name || 'Nostrich'}</h2>
|
||||
<Copy text={params.id} />
|
||||
{user?.nip05 && <Nip05 name={name} domain={domain} isVerified={isVerified} couldNotVerify={couldNotVerify} />}
|
||||
{user?.nip05 && <Nip05 nip05={user.nip05} pubkey={user.pubkey} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -59,7 +59,7 @@ export default function ProfilePage() {
|
||||
const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || "");
|
||||
return (
|
||||
<div className="details">
|
||||
<p>{about}</p>
|
||||
<div>{about}</div>
|
||||
|
||||
{user?.website && (
|
||||
<div className="website f-ellipsis">
|
||||
@ -103,7 +103,7 @@ export default function ProfilePage() {
|
||||
function avatar() {
|
||||
return (
|
||||
<div className="avatar-wrapper">
|
||||
<div style={{ '--img-url': backgroundImage }} className="avatar" data-domain={isVerified ? domain : ''}>
|
||||
<div style={{ '--img-url': backgroundImage }} className="avatar" data-domain={domain?.toLowerCase()}>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import { ProfileCacheExpire } from '../Const';
|
||||
import { db } from '../db';
|
||||
|
||||
const UsersSlice = createSlice({
|
||||
name: "Users",
|
||||
@ -67,7 +68,13 @@ const UsersSlice = createSlice({
|
||||
};
|
||||
}
|
||||
state.users[x.pubkey] = x;
|
||||
window.localStorage.setItem(`user:${x.pubkey}`, JSON.stringify(x));
|
||||
db.users.put({
|
||||
pubkey: x.pubkey,
|
||||
name: x.name,
|
||||
display_name: x.display_name,
|
||||
nip05: x.nip05,
|
||||
picture: x.picture,
|
||||
})
|
||||
|
||||
state.users = {
|
||||
...state.users
|
||||
|
87
yarn.lock
87
yarn.lock
@ -1024,7 +1024,7 @@
|
||||
"@babel/helper-validator-option" "^7.18.6"
|
||||
"@babel/plugin-transform-typescript" "^7.18.6"
|
||||
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.20.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.7.tgz#fcb41a5a70550e04a7b708037c7c32f7f356d8fd"
|
||||
integrity sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==
|
||||
@ -2868,6 +2868,11 @@ bfj@^7.0.2:
|
||||
hoopy "^0.1.4"
|
||||
tryer "^1.0.1"
|
||||
|
||||
big-integer@^1.6.16:
|
||||
version "1.6.51"
|
||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
|
||||
integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
|
||||
|
||||
big.js@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
@ -2943,6 +2948,20 @@ braces@^3.0.2, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
broadcast-channel@^3.4.1:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937"
|
||||
integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.2"
|
||||
detect-node "^2.1.0"
|
||||
js-sha3 "0.8.0"
|
||||
microseconds "0.2.0"
|
||||
nano-time "1.0.0"
|
||||
oblivious-set "1.0.0"
|
||||
rimraf "3.0.2"
|
||||
unload "2.2.0"
|
||||
|
||||
browser-process-hrtime@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
|
||||
@ -3682,7 +3701,7 @@ detect-newline@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
||||
|
||||
detect-node@^2.0.4:
|
||||
detect-node@^2.0.4, detect-node@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
|
||||
integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
|
||||
@ -3704,6 +3723,16 @@ detective@^5.2.1:
|
||||
defined "^1.0.0"
|
||||
minimist "^1.2.6"
|
||||
|
||||
dexie-react-hooks@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/dexie-react-hooks/-/dexie-react-hooks-1.1.1.tgz#ff405cc89e5d899ddbac5e40d593f83f9a74106a"
|
||||
integrity sha512-Cam5JP6PxHN564RvWEoe8cqLhosW0O4CAZ9XEVYeGHJBa6KEJlOpd9CUpV3kmU9dm2MrW97/lk7qkf1xpij7gA==
|
||||
|
||||
dexie@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/dexie/-/dexie-3.2.2.tgz#fa6f2a3c0d6ed0766f8d97a03720056f88fe0e01"
|
||||
integrity sha512-q5dC3HPmir2DERlX+toCBbHQXW5MsyrFqPFcovkH9N2S/UW/H3H5AWAB6iEOExeraAu+j+zRDG+zg/D7YhH0qg==
|
||||
|
||||
didyoumean@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
|
||||
@ -5974,6 +6003,11 @@ js-sdsl@^4.1.4:
|
||||
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0"
|
||||
integrity sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==
|
||||
|
||||
js-sha3@0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
|
||||
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
@ -6288,6 +6322,14 @@ makeerror@1.0.12:
|
||||
dependencies:
|
||||
tmpl "1.0.5"
|
||||
|
||||
match-sorter@^6.0.2:
|
||||
version "6.3.1"
|
||||
resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda"
|
||||
integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
remove-accents "0.4.2"
|
||||
|
||||
mdast-util-definitions@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz#2c1d684b28e53f84938bb06317944bee8efa79db"
|
||||
@ -6580,6 +6622,11 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
|
||||
braces "^3.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
microseconds@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
|
||||
integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
|
||||
|
||||
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
|
||||
version "1.52.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||
@ -6668,6 +6715,13 @@ multicast-dns@^7.2.5:
|
||||
dns-packet "^5.2.2"
|
||||
thunky "^1.0.2"
|
||||
|
||||
nano-time@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
|
||||
integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==
|
||||
dependencies:
|
||||
big-integer "^1.6.16"
|
||||
|
||||
nanoid@^3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
|
||||
@ -6840,6 +6894,11 @@ object.values@^1.1.0, object.values@^1.1.6:
|
||||
define-properties "^1.1.4"
|
||||
es-abstract "^1.20.4"
|
||||
|
||||
oblivious-set@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
|
||||
integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
|
||||
|
||||
obuf@^1.0.0, obuf@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
|
||||
@ -7893,6 +7952,15 @@ react-markdown@^8.0.4:
|
||||
unist-util-visit "^4.0.0"
|
||||
vfile "^5.0.0"
|
||||
|
||||
react-query@^3.39.2:
|
||||
version "3.39.2"
|
||||
resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.2.tgz#9224140f0296f01e9664b78ed6e4f69a0cc9216f"
|
||||
integrity sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
broadcast-channel "^3.4.1"
|
||||
match-sorter "^6.0.2"
|
||||
|
||||
react-redux@^8.0.5:
|
||||
version "8.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd"
|
||||
@ -8140,6 +8208,11 @@ remark-rehype@^10.0.0:
|
||||
mdast-util-to-hast "^12.1.0"
|
||||
unified "^10.0.0"
|
||||
|
||||
remove-accents@0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
|
||||
integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==
|
||||
|
||||
renderkid@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a"
|
||||
@ -8232,7 +8305,7 @@ reusify@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||
|
||||
rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||
@ -9216,6 +9289,14 @@ universalify@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
|
||||
|
||||
unload@2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7"
|
||||
integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
detect-node "^2.0.4"
|
||||
|
||||
unpipe@1.0.0, unpipe@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||
|
Loading…
Reference in New Issue
Block a user