mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-25 06:39:53 -04:00
feat(gui): Display developer responses to feedback (#302)
This commit is contained in:
parent
f1e5cdfbfe
commit
53a994e6dc
19 changed files with 1216 additions and 45 deletions
|
@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- GUI: Feedback submitted can be responded to by the core developers. The responses will be displayed under the "Feedback" tab.
|
||||
|
||||
## [1.0.0-rc.17] - 2025-04-18
|
||||
|
||||
- GUI: The user will now be asked to approve the swap offer again before the Bitcoin lock transaction is published. Makers should take care to only assume a swap has been accepted by the taker if the Bitcoin lock transaction is detected (`Advancing state state=bitcoin lock transaction in mempool ...`). Swaps that have been safely aborted will not be displayed in the GUI anymore.
|
||||
|
|
|
@ -38,3 +38,25 @@ You can also check whether the current bindings are up to date:
|
|||
```bash
|
||||
yarn run check-bindings
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
Because the GUI is running in an embedded browser, we can't use the usual Browser extensions to debug the GUI. Instead we use standalone React DevTools / Redux DevTools.
|
||||
|
||||
### React DevTools
|
||||
|
||||
Run this command to start the React DevTools server. The frontend will connect to this server automatically:
|
||||
|
||||
```bash
|
||||
npx react-devtools
|
||||
```
|
||||
|
||||
### Redux DevTools
|
||||
|
||||
Run this command to start the Redux DevTools server. The frontend will connect to this server automatically. You can then debug the global Redux state. Observe how it changes over time, go back in time, see dispatch history, etc.
|
||||
|
||||
You may have to go to `Settings -> 'use local custom server' -> connect` inside the devtools window for the state to be reflected correctly.
|
||||
|
||||
```bash
|
||||
npx redux-devtools --hostname=localhost --port=8098 --open
|
||||
```
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@redux-devtools/remote": "^0.9.5",
|
||||
"@tauri-apps/cli": "^2.0.0",
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
|
|
|
@ -26,3 +26,49 @@ export interface Alert {
|
|||
body: string;
|
||||
severity: "info" | "warning" | "error";
|
||||
}
|
||||
|
||||
// Define the correct 9-element tuple type for PrimitiveDateTime
|
||||
export type PrimitiveDateTimeString = [
|
||||
number, // Year
|
||||
number, // Day of Year
|
||||
number, // Hour
|
||||
number, // Minute
|
||||
number, // Second
|
||||
number, // Nanosecond
|
||||
number, // Offset Hour
|
||||
number, // Offset Minute
|
||||
number // Offset Second
|
||||
];
|
||||
|
||||
export interface Feedback {
|
||||
id: string;
|
||||
created_at: PrimitiveDateTimeString;
|
||||
}
|
||||
|
||||
export interface Attachment {
|
||||
id: number;
|
||||
message_id: number;
|
||||
key: string;
|
||||
content: string;
|
||||
created_at: PrimitiveDateTimeString;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: number;
|
||||
feedback_id: string;
|
||||
is_from_staff: boolean;
|
||||
content: string;
|
||||
created_at: PrimitiveDateTimeString;
|
||||
attachments?: Attachment[];
|
||||
}
|
||||
|
||||
export interface MessageWithAttachments {
|
||||
message: Message;
|
||||
attachments: Attachment[];
|
||||
}
|
||||
|
||||
// Define type for Attachment data in request body
|
||||
export interface AttachmentInput {
|
||||
key: string;
|
||||
content: string;
|
||||
}
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
// - and to submit feedback
|
||||
// - fetch currency rates from CoinGecko
|
||||
|
||||
import { Alert, ExtendedMakerStatus } from "models/apiModel";
|
||||
import { Alert, Attachment, AttachmentInput, ExtendedMakerStatus, Feedback, Message, MessageWithAttachments, PrimitiveDateTimeString } from "models/apiModel";
|
||||
import { store } from "./store/storeRenderer";
|
||||
import { setBtcPrice, setXmrBtcRate, setXmrPrice } from "store/features/ratesSlice";
|
||||
import { FiatCurrency } from "store/features/settingsSlice";
|
||||
import { setAlerts } from "store/features/alertsSlice";
|
||||
import { registryConnectionFailed, setRegistryMakers } from "store/features/makersSlice";
|
||||
import logger from "utils/logger";
|
||||
import { setConversation } from "store/features/conversationsSlice";
|
||||
|
||||
const PUBLIC_REGISTRY_API_BASE_URL = "https://api.unstoppableswap.net";
|
||||
|
||||
|
@ -28,11 +29,14 @@ async function fetchAlertsViaHttp(): Promise<Alert[]> {
|
|||
}
|
||||
|
||||
export async function submitFeedbackViaHttp(
|
||||
body: string,
|
||||
attachedData: string,
|
||||
content: string,
|
||||
attachments?: AttachmentInput[]
|
||||
): Promise<string> {
|
||||
type Response = {
|
||||
feedbackId: string;
|
||||
type Response = string;
|
||||
|
||||
const requestPayload = {
|
||||
body: content,
|
||||
attachments: attachments || [], // Ensure attachments is always an array
|
||||
};
|
||||
|
||||
const response = await fetch(`${PUBLIC_REGISTRY_API_BASE_URL}/api/submit-feedback`, {
|
||||
|
@ -40,16 +44,56 @@ export async function submitFeedbackViaHttp(
|
|||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ body, attachedData }),
|
||||
body: JSON.stringify(requestPayload), // Send the corrected structure
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Status: ${response.status}`);
|
||||
const errorBody = await response.text();
|
||||
throw new Error(`Failed to submit feedback. Status: ${response.status}. Body: ${errorBody}`);
|
||||
}
|
||||
|
||||
const responseBody = (await response.json()) as Response;
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
return responseBody.feedbackId;
|
||||
export async function fetchFeedbackMessagesViaHttp(feedbackId: string): Promise<Message[]> {
|
||||
const response = await fetch(`${PUBLIC_REGISTRY_API_BASE_URL}/api/feedback/${feedbackId}/messages`);
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text();
|
||||
throw new Error(`Failed to fetch messages for feedback ${feedbackId}. Status: ${response.status}. Body: ${errorBody}`);
|
||||
}
|
||||
// Assuming the response is directly the Message[] array including attachments
|
||||
return (await response.json()) as Message[];
|
||||
}
|
||||
|
||||
export async function appendFeedbackMessageViaHttp(
|
||||
feedbackId: string,
|
||||
content: string,
|
||||
attachments?: AttachmentInput[]
|
||||
): Promise<number> {
|
||||
type Response = number;
|
||||
|
||||
const body = {
|
||||
feedback_id: feedbackId,
|
||||
content,
|
||||
attachments: attachments || [], // Ensure attachments is always an array
|
||||
};
|
||||
|
||||
const response = await fetch(`${PUBLIC_REGISTRY_API_BASE_URL}/api/append-feedback-message`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body), // Send new structure
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text();
|
||||
throw new Error(`Failed to append message for feedback ${feedbackId}. Status: ${response.status}. Body: ${errorBody}`);
|
||||
}
|
||||
|
||||
const responseBody = (await response.json()) as Response;
|
||||
return responseBody;
|
||||
}
|
||||
|
||||
async function fetchCurrencyPrice(currency: string, fiatCurrency: FiatCurrency): Promise<number> {
|
||||
|
@ -74,7 +118,6 @@ async function fetchXmrBtcRate(): Promise<number> {
|
|||
return lastTradePrice;
|
||||
}
|
||||
|
||||
|
||||
function fetchBtcPrice(fiatCurrency: FiatCurrency): Promise<number> {
|
||||
return fetchCurrencyPrice("bitcoin", fiatCurrency);
|
||||
}
|
||||
|
@ -108,7 +151,6 @@ export async function updateRates(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update public registry
|
||||
*/
|
||||
|
@ -127,4 +169,25 @@ export async function updatePublicRegistry(): Promise<void> {
|
|||
} catch (error) {
|
||||
logger.error(error, "Error fetching alerts");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all conversations
|
||||
* Goes through all feedback ids and fetches all the messages for each feedback id
|
||||
*/
|
||||
export async function fetchAllConversations(): Promise<void> {
|
||||
const feedbackIds = store.getState().conversations.knownFeedbackIds;
|
||||
|
||||
console.log("Fetching all conversations", feedbackIds);
|
||||
|
||||
for (const feedbackId of feedbackIds) {
|
||||
try {
|
||||
console.log("Fetching messages for feedback id", feedbackId);
|
||||
const messages = await fetchFeedbackMessagesViaHttp(feedbackId);
|
||||
console.log("Fetched messages for feedback id", feedbackId, messages);
|
||||
store.dispatch(setConversation({ feedbackId, messages }));
|
||||
} catch (error) {
|
||||
logger.error(error, "Error fetching messages for feedback id", feedbackId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ import { TauriSwapProgressEventWrapper, TauriContextStatusEvent, TauriLogEvent,
|
|||
import { contextStatusEventReceived, receivedCliLog, rpcSetBalance, timelockChangeEventReceived, rpcSetBackgroundRefundState, approvalEventReceived } from "store/features/rpcSlice";
|
||||
import { swapProgressEventReceived } from "store/features/swapSlice";
|
||||
import logger from "utils/logger";
|
||||
import { updatePublicRegistry, updateRates } from "./api";
|
||||
import { fetchAllConversations, updatePublicRegistry, updateRates } from "./api";
|
||||
import { checkContextAvailability, getSwapInfo, initializeContext, updateAllNodeStatuses } from "./rpc";
|
||||
import { store } from "./store/storeRenderer";
|
||||
|
||||
|
@ -16,6 +16,9 @@ const STATUS_UPDATE_INTERVAL = 2 * 60 * 1_000;
|
|||
// Update the exchange rate every 5 minutes
|
||||
const UPDATE_RATE_INTERVAL = 5 * 60 * 1_000;
|
||||
|
||||
// Fetch all conversations every 10 minutes
|
||||
const FETCH_CONVERSATIONS_INTERVAL = 10 * 60 * 1_000;
|
||||
|
||||
function setIntervalImmediate(callback: () => void, interval: number): void {
|
||||
callback();
|
||||
setInterval(callback, interval);
|
||||
|
@ -26,6 +29,7 @@ export async function setupBackgroundTasks(): Promise<void> {
|
|||
setIntervalImmediate(updatePublicRegistry, PROVIDER_UPDATE_INTERVAL);
|
||||
setIntervalImmediate(updateAllNodeStatuses, STATUS_UPDATE_INTERVAL);
|
||||
setIntervalImmediate(updateRates, UPDATE_RATE_INTERVAL);
|
||||
setIntervalImmediate(fetchAllConversations, FETCH_CONVERSATIONS_INTERVAL);
|
||||
|
||||
// // Setup Tauri event listeners
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { ThemeProvider } from "@material-ui/core/styles";
|
|||
import "@tauri-apps/plugin-shell";
|
||||
import { Route, MemoryRouter as Router, Routes } from "react-router-dom";
|
||||
import Navigation, { drawerWidth } from "./navigation/Navigation";
|
||||
import HelpPage from "./pages/help/HelpPage";
|
||||
import SettingsPage from "./pages/help/SettingsPage";
|
||||
import HistoryPage from "./pages/history/HistoryPage";
|
||||
import SwapPage from "./pages/swap/SwapPage";
|
||||
import WalletPage from "./pages/wallet/WalletPage";
|
||||
|
@ -14,6 +14,7 @@ import { themes } from "./theme";
|
|||
import { useEffect } from "react";
|
||||
import { setupBackgroundTasks } from "renderer/background";
|
||||
import "@fontsource/roboto";
|
||||
import FeedbackPage from "./pages/feedback/FeedbackPage";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
innerContent: {
|
||||
|
@ -54,7 +55,8 @@ function InnerContent() {
|
|||
<Route path="/swap" element={<SwapPage />} />
|
||||
<Route path="/history" element={<HistoryPage />} />
|
||||
<Route path="/wallet" element={<WalletPage />} />
|
||||
<Route path="/help" element={<HelpPage />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/feedback" element={<FeedbackPage />} />
|
||||
<Route path="/" element={<SwapPage />} />
|
||||
</Routes>
|
||||
</Box>
|
||||
|
|
|
@ -23,7 +23,7 @@ import TruncatedText from "renderer/components/other/TruncatedText";
|
|||
import { store } from "renderer/store/storeRenderer";
|
||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||
import { logsToRawString, parseDateString } from "utils/parseUtils";
|
||||
import { submitFeedbackViaHttp } from "../../../api";
|
||||
import { submitFeedbackViaHttp, AttachmentInput } from "../../../api";
|
||||
import LoadingButton from "../../other/LoadingButton";
|
||||
import { PiconeroAmount } from "../../other/Units";
|
||||
import { getLogsOfSwap, redactLogs } from "renderer/rpc";
|
||||
|
@ -31,26 +31,58 @@ import logger from "utils/logger";
|
|||
import { Label, Visibility } from "@material-ui/icons";
|
||||
import CliLogsBox from "renderer/components/other/RenderedCliLog";
|
||||
import { CliLog, parseCliLogString } from "models/cliModel";
|
||||
import { addFeedbackId } from "store/features/conversationsSlice";
|
||||
|
||||
async function submitFeedback(body: string, swapId: string | null, swapLogs: string | null, daemonLogs: string | null) {
|
||||
let attachedBody = "";
|
||||
const attachments: AttachmentInput[] = [];
|
||||
|
||||
if (swapId !== null) {
|
||||
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
|
||||
|
||||
if (swapInfo === undefined) {
|
||||
throw new Error(`Swap with id ${swapId} not found`);
|
||||
if (swapInfo) {
|
||||
// Add swap info as an attachment
|
||||
attachments.push({
|
||||
key: `swap_info_${swapId}.json`,
|
||||
content: JSON.stringify(swapInfo, null, 2), // Pretty print JSON
|
||||
});
|
||||
// Retrieve and add logs for the specific swap
|
||||
try {
|
||||
const logs = await getLogsOfSwap(swapId, false);
|
||||
attachments.push({
|
||||
key: `swap_logs_${swapId}.txt`,
|
||||
content: logs.logs.map((l) => JSON.stringify(l)).join("\n"),
|
||||
});
|
||||
} catch (logError) {
|
||||
logger.error(logError, "Failed to get logs for swap", { swapId });
|
||||
// Optionally add an attachment indicating log retrieval failure
|
||||
attachments.push({ key: `swap_logs_${swapId}.error`, content: "Failed to retrieve swap logs." });
|
||||
}
|
||||
} else {
|
||||
logger.warn("Selected swap info not found in state", { swapId });
|
||||
attachments.push({ key: `swap_info_${swapId}.error`, content: "Swap info not found." });
|
||||
}
|
||||
|
||||
attachedBody = `${JSON.stringify(swapInfo, null, 4)}\n\nLogs: ${swapLogs ?? ""}`;
|
||||
// Add swap logs as an attachment
|
||||
if (swapLogs) {
|
||||
attachments.push({
|
||||
key: `swap_logs_${swapId}.txt`,
|
||||
content: swapLogs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle daemon logs
|
||||
if (daemonLogs !== null) {
|
||||
attachedBody += `\n\nDaemon Logs: ${daemonLogs ?? ""}`;
|
||||
attachments.push({
|
||||
key: "daemon_logs.txt",
|
||||
content: daemonLogs,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Sending feedback with attachement: \`\n${attachedBody}\``)
|
||||
await submitFeedbackViaHttp(body, attachedBody);
|
||||
// Call the updated API function
|
||||
const feedbackId = await submitFeedbackViaHttp(body, attachments);
|
||||
|
||||
// Dispatch only the ID
|
||||
store.dispatch(addFeedbackId(feedbackId));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -152,7 +184,12 @@ export default function FeedbackDialog({
|
|||
|
||||
try {
|
||||
setPending(true);
|
||||
await submitFeedback(bodyText, selectedSwap, logsToRawString(swapLogs), logsToRawString(daemonLogs));
|
||||
await submitFeedback(
|
||||
bodyText,
|
||||
selectedSwap,
|
||||
logsToRawString(swapLogs ?? []),
|
||||
logsToRawString(daemonLogs ?? [])
|
||||
);
|
||||
enqueueSnackbar("Feedback submitted successfully!", {
|
||||
variant: "success",
|
||||
});
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import { Box, List } from "@material-ui/core";
|
||||
import { Box, List, Badge } from "@material-ui/core";
|
||||
import AccountBalanceWalletIcon from "@material-ui/icons/AccountBalanceWallet";
|
||||
import HelpOutlineIcon from "@material-ui/icons/HelpOutline";
|
||||
import HistoryOutlinedIcon from "@material-ui/icons/HistoryOutlined";
|
||||
import SwapHorizOutlinedIcon from "@material-ui/icons/SwapHorizOutlined";
|
||||
import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined';
|
||||
import RouteListItemIconButton from "./RouteListItemIconButton";
|
||||
import UnfinishedSwapsBadge from "./UnfinishedSwapsCountBadge";
|
||||
import { useTotalUnreadMessagesCount } from "store/hooks";
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
|
||||
export default function NavigationHeader() {
|
||||
const totalUnreadCount = useTotalUnreadMessagesCount();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<List>
|
||||
|
@ -21,8 +25,18 @@ export default function NavigationHeader() {
|
|||
<RouteListItemIconButton name="Wallet" route="/wallet">
|
||||
<AccountBalanceWalletIcon />
|
||||
</RouteListItemIconButton>
|
||||
<RouteListItemIconButton name="Help & Settings" route="/help">
|
||||
<HelpOutlineIcon />
|
||||
<RouteListItemIconButton name="Feedback" route="/feedback">
|
||||
<Badge
|
||||
badgeContent={totalUnreadCount}
|
||||
color="primary"
|
||||
overlap="rectangular"
|
||||
invisible={totalUnreadCount === 0}
|
||||
>
|
||||
<FeedbackOutlinedIcon />
|
||||
</Badge>
|
||||
</RouteListItemIconButton>
|
||||
<RouteListItemIconButton name="Settings" route="/settings">
|
||||
<SettingsIcon />
|
||||
</RouteListItemIconButton>
|
||||
</List>
|
||||
</Box>
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { Box, makeStyles } from "@material-ui/core";
|
||||
import FeedbackInfoBox from "../help/FeedbackInfoBox";
|
||||
import ConversationsBox from "../help/ConversationsBox";
|
||||
import ContactInfoBox from "../help/ContactInfoBox";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
outer: {
|
||||
display: "flex",
|
||||
gap: theme.spacing(2),
|
||||
flexDirection: "column",
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export default function FeedbackPage() {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Box className={classes.outer}>
|
||||
<FeedbackInfoBox />
|
||||
<ConversationsBox />
|
||||
<ContactInfoBox />
|
||||
</Box>
|
||||
);
|
||||
}
|
346
src-gui/src/renderer/components/pages/help/ConversationsBox.tsx
Normal file
346
src-gui/src/renderer/components/pages/help/ConversationsBox.tsx
Normal file
|
@ -0,0 +1,346 @@
|
|||
import { useState, useEffect, useMemo } from "react";
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
makeStyles,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
IconButton,
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Paper,
|
||||
Badge,
|
||||
TextField,
|
||||
CircularProgress,
|
||||
InputAdornment,
|
||||
Tooltip,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
Link,
|
||||
} from "@material-ui/core";
|
||||
import ChatIcon from '@material-ui/icons/Chat';
|
||||
import SendIcon from '@material-ui/icons/Send';
|
||||
import InfoBox from "renderer/components/modal/swap/InfoBox";
|
||||
import TruncatedText from "renderer/components/other/TruncatedText";
|
||||
import clsx from 'clsx';
|
||||
import { useAppSelector, useAppDispatch, useUnreadMessagesCount } from "store/hooks";
|
||||
import { markMessagesAsSeen } from "store/features/conversationsSlice";
|
||||
import { appendFeedbackMessageViaHttp, fetchAllConversations } from "renderer/api";
|
||||
import { useSnackbar } from "notistack";
|
||||
import logger from "utils/logger";
|
||||
import AttachmentIcon from '@material-ui/icons/Attachment';
|
||||
import { Message, PrimitiveDateTimeString } from "models/apiModel";
|
||||
import { formatDateTime } from "utils/conversionUtils";
|
||||
|
||||
// Styles
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
content: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: theme.spacing(2) },
|
||||
tableContainer: { maxHeight: 300 },
|
||||
dialogContent: { display: 'flex', flexDirection: 'column' },
|
||||
messagesContainer: { flexGrow: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: theme.spacing(1), maxHeight: 400, padding: theme.spacing(1) },
|
||||
messageRow: { display: 'flex', marginTop: theme.spacing(1) },
|
||||
staffRow: { justifyContent: 'flex-start' },
|
||||
userRow: { justifyContent: 'flex-end' },
|
||||
messageBubble: { padding: theme.spacing(1.5), borderRadius: theme.shape.borderRadius * 2, maxWidth: '75%', wordBreak: 'break-word', boxShadow: theme.shadows[1] },
|
||||
staffBubble: { border: `1px solid ${theme.palette.divider}`, color: theme.palette.text.primary, borderRadius: theme.spacing(2) },
|
||||
userBubble: { backgroundColor: theme.palette.primary.main, color: theme.palette.primary.contrastText, borderRadius: theme.spacing(2) },
|
||||
timestamp: { marginTop: theme.spacing(0.5), fontSize: '0.75rem', opacity: 0.7, textAlign: 'right' },
|
||||
inputArea: { flexShrink: 0, marginTop: theme.spacing(2) },
|
||||
attachmentList: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
paddingLeft: theme.spacing(2),
|
||||
},
|
||||
attachmentItem: {
|
||||
paddingTop: theme.spacing(0.5),
|
||||
paddingBottom: theme.spacing(0.5),
|
||||
}
|
||||
}));
|
||||
|
||||
// Hook: sorted feedback IDs by latest activity, then unread
|
||||
function useSortedFeedbackIds() {
|
||||
const ids = useAppSelector((s) => s.conversations.knownFeedbackIds || []);
|
||||
const conv = useAppSelector((s) => s.conversations.conversations);
|
||||
const seen = useAppSelector((s) => new Set(s.conversations.seenMessages));
|
||||
return useMemo(() => {
|
||||
const arr = ids.map((id) => {
|
||||
const msgs = conv[id] || [];
|
||||
const unread = msgs.filter((m) => m.is_from_staff && !seen.has(m.id.toString())).length;
|
||||
const latest = msgs.reduce((d, m) => {
|
||||
try {
|
||||
const formattedDate = formatDateTime(m.created_at);
|
||||
if (formattedDate.startsWith("Invalid")) return d;
|
||||
const t = new Date(formattedDate).getTime();
|
||||
return isNaN(t) ? d : Math.max(d, t);
|
||||
} catch(e) { return d; }
|
||||
}, 0);
|
||||
return { id, unread, latest };
|
||||
});
|
||||
arr.sort((a, b) => b.latest - a.latest || (b.unread > 0 ? 1 : 0) - (a.unread > 0 ? 1 : 0));
|
||||
return arr.map((x) => x.id);
|
||||
}, [ids, conv, seen]);
|
||||
}
|
||||
|
||||
// Main component
|
||||
export default function ConversationsBox() {
|
||||
const classes = useStyles();
|
||||
const sortedIds = useSortedFeedbackIds();
|
||||
const [openId, setOpenId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch conversations via API function (handles its own dispatch)
|
||||
fetchAllConversations();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<InfoBox
|
||||
title="Developer Responses"
|
||||
icon={null}
|
||||
loading={false}
|
||||
mainContent={
|
||||
<Box className={classes.content}>
|
||||
<Typography variant="subtitle2">
|
||||
View your past feedback submissions and any replies from the development team.
|
||||
</Typography>
|
||||
{sortedIds.length === 0 ? (
|
||||
<Typography variant="body2">No feedback submitted yet.</Typography>
|
||||
) : (
|
||||
<TableContainer component={Paper} className={classes.tableContainer}>
|
||||
<Table stickyHeader size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell style={{ width: '25%' }}>Last Message</TableCell>
|
||||
<TableCell style={{ width: '60%' }}>Preview</TableCell>
|
||||
<TableCell align="right" style={{ width: '15%' }} />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{sortedIds.map((id) => (
|
||||
<ConversationRow key={id} feedbackId={id} onOpen={setOpenId} />
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
additionalContent={
|
||||
openId && (
|
||||
<ConversationModal
|
||||
open={!!openId}
|
||||
onClose={() => setOpenId(null)}
|
||||
feedbackId={openId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Single row
|
||||
function ConversationRow({ feedbackId, onOpen }: { feedbackId: string, onOpen: (id: string) => void }) {
|
||||
const msgs = useAppSelector((s) => s.conversations.conversations[feedbackId] || []);
|
||||
const unread = useUnreadMessagesCount(feedbackId);
|
||||
const sorted = useMemo(
|
||||
() =>
|
||||
[...msgs].sort((a, b) => {
|
||||
try {
|
||||
const formattedDateA = formatDateTime(a.created_at);
|
||||
const formattedDateB = formatDateTime(b.created_at);
|
||||
if (formattedDateA.startsWith("Invalid")) return 1;
|
||||
if (formattedDateB.startsWith("Invalid")) return -1;
|
||||
const dateA = new Date(formattedDateA).getTime();
|
||||
const dateB = new Date(formattedDateB).getTime();
|
||||
if (isNaN(dateA)) return 1;
|
||||
if (isNaN(dateB)) return -1;
|
||||
return dateB - dateA;
|
||||
} catch (e) { return 0; }
|
||||
}),
|
||||
[msgs]
|
||||
);
|
||||
const lastMsg = sorted[0];
|
||||
const time = lastMsg ? formatDateTime(lastMsg.created_at) : '-';
|
||||
const content = lastMsg ? lastMsg.content : 'No messages yet';
|
||||
const preview = (() => {
|
||||
return content;
|
||||
})();
|
||||
const hasStaff = useMemo(() => msgs.some((m) => m.is_from_staff), [msgs]);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell style={{ width: '25%' }}>{time}</TableCell>
|
||||
<TableCell style={{ width: '60%' }}>
|
||||
"<TruncatedText limit={30}>{preview}</TruncatedText>"
|
||||
</TableCell>
|
||||
<TableCell align="right" style={{ width: '15%' }}>
|
||||
<Badge badgeContent={unread} color="primary" overlap="rectangular">
|
||||
<Tooltip title={hasStaff ? 'Open Conversation' : 'No developer has responded'} arrow>
|
||||
<span>
|
||||
<IconButton size="small" onClick={() => onOpen(feedbackId)} disabled={!hasStaff}>
|
||||
<ChatIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
// Modal
|
||||
function ConversationModal({ open, onClose, feedbackId }: { open: boolean, onClose: () => void, feedbackId: string }) {
|
||||
const classes = useStyles();
|
||||
const dispatch = useAppDispatch();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const msgs = useAppSelector((s): Message[] => s.conversations.conversations[feedbackId] || []);
|
||||
const seen = useAppSelector((s) => new Set(s.conversations.seenMessages));
|
||||
const [text, setText] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const unseen = msgs.filter((m) => !seen.has(m.id.toString()));
|
||||
if (unseen.length) dispatch(markMessagesAsSeen(unseen));
|
||||
}
|
||||
}, [open, msgs, seen, dispatch]);
|
||||
|
||||
const sorted = useMemo(
|
||||
() =>
|
||||
[...msgs].sort((a, b) => {
|
||||
try {
|
||||
const formattedDateA = formatDateTime(a.created_at);
|
||||
const formattedDateB = formatDateTime(b.created_at);
|
||||
if (formattedDateA.startsWith("Invalid")) return 1;
|
||||
if (formattedDateB.startsWith("Invalid")) return -1;
|
||||
const dateA = new Date(formattedDateA).getTime();
|
||||
const dateB = new Date(formattedDateB).getTime();
|
||||
if (isNaN(dateA)) return 1;
|
||||
if (isNaN(dateB)) return -1;
|
||||
return dateB - dateA;
|
||||
} catch(e) { return 0; }
|
||||
}),
|
||||
[msgs]
|
||||
);
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!text.trim()) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await appendFeedbackMessageViaHttp(feedbackId, text);
|
||||
setText('');
|
||||
enqueueSnackbar('Message sent successfully!', { variant: 'success' });
|
||||
fetchAllConversations();
|
||||
} catch (e) {
|
||||
logger.error(e, 'Send failed');
|
||||
enqueueSnackbar('Failed to send message. Please try again.', { variant: 'error' });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth scroll="paper">
|
||||
<DialogTitle>Conversation <TruncatedText limit={8} truncateMiddle>{feedbackId}</TruncatedText></DialogTitle>
|
||||
<DialogContent dividers className={classes.dialogContent}>
|
||||
{sorted.length === 0 ? (
|
||||
<Typography variant="body2">No messages in this conversation.</Typography>
|
||||
) : (
|
||||
<Box className={classes.messagesContainer}>
|
||||
{sorted.map((m: Message) => {
|
||||
const raw = m.content;
|
||||
return (
|
||||
<Box
|
||||
key={m.id}
|
||||
className={clsx(
|
||||
classes.messageRow,
|
||||
m.is_from_staff ? classes.staffRow : classes.userRow
|
||||
)}
|
||||
>
|
||||
<Box className={clsx(
|
||||
classes.messageBubble,
|
||||
m.is_from_staff ? classes.staffBubble : classes.userBubble
|
||||
)}
|
||||
>
|
||||
<Typography variant="body1" style={{ whiteSpace: 'pre-wrap' }}>
|
||||
{raw}
|
||||
</Typography>
|
||||
{m.attachments && m.attachments.length > 0 && (
|
||||
<List dense disablePadding className={classes.attachmentList}>
|
||||
{m.attachments.map((att) => (
|
||||
<ListItem key={att.id} className={classes.attachmentItem}>
|
||||
<ListItemIcon style={{ minWidth: 'auto', marginRight: '8px' }}>
|
||||
<AttachmentIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<Link
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
alert(`Attachment Key: ${att.key}\n\nContent:\n${att.content}`);
|
||||
}}
|
||||
variant="body2"
|
||||
color="inherit"
|
||||
underline="none"
|
||||
>
|
||||
{att.key}
|
||||
</Link>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
<Typography variant="caption" className={classes.timestamp}>
|
||||
{m.is_from_staff ? 'Developer' : 'You'} · {formatDateTime(m.created_at)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
<Box className={classes.inputArea}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
placeholder="Type your message..."
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
disabled={loading}
|
||||
multiline
|
||||
rowsMax={4}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={sendMessage}
|
||||
disabled={!text.trim() || loading}
|
||||
>
|
||||
{loading ? <CircularProgress size={24} /> : <SendIcon />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { Box, makeStyles } from "@material-ui/core";
|
||||
import ContactInfoBox from "./ContactInfoBox";
|
||||
import DonateInfoBox from "./DonateInfoBox";
|
||||
import FeedbackInfoBox from "./FeedbackInfoBox";
|
||||
import DaemonControlBox from "./DaemonControlBox";
|
||||
import SettingsBox from "./SettingsBox";
|
||||
import ExportDataBox from "./ExportDataBox";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
outer: {
|
||||
display: "flex",
|
||||
|
@ -16,7 +16,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
export default function HelpPage() {
|
||||
export default function SettingsPage() {
|
||||
const classes = useStyles();
|
||||
const location = useLocation();
|
||||
|
||||
|
@ -29,11 +29,9 @@ export default function HelpPage() {
|
|||
|
||||
return (
|
||||
<Box className={classes.outer}>
|
||||
<FeedbackInfoBox />
|
||||
<SettingsBox />
|
||||
<ExportDataBox />
|
||||
<DaemonControlBox />
|
||||
<ContactInfoBox />
|
||||
<DonateInfoBox />
|
||||
</Box>
|
||||
);
|
|
@ -1,9 +1,8 @@
|
|||
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
||||
import { combineReducers, configureStore, StoreEnhancer } from "@reduxjs/toolkit";
|
||||
import { persistReducer, persistStore } from "redux-persist";
|
||||
import sessionStorage from "redux-persist/lib/storage/session";
|
||||
import { reducers } from "store/combinedReducer";
|
||||
import { createMainListeners } from "store/middleware/storeListener";
|
||||
import { getNetworkName } from "store/config";
|
||||
import { LazyStore } from "@tauri-apps/plugin-store";
|
||||
|
||||
// Goal: Maintain application state across page reloads while allowing a clean slate on application restart
|
||||
|
@ -14,20 +13,42 @@ import { LazyStore } from "@tauri-apps/plugin-store";
|
|||
const rootPersistConfig = {
|
||||
key: "gui-global-state-store",
|
||||
storage: sessionStorage,
|
||||
blacklist: ["settings"],
|
||||
blacklist: ["settings", "conversations"],
|
||||
};
|
||||
|
||||
// Use Tauri's store plugin for persistent settings
|
||||
const tauriStore = new LazyStore("settings.bin");
|
||||
|
||||
// Helper to adapt Tauri storage to redux-persist (expects stringified JSON)
|
||||
const createTauriStorage = () => ({
|
||||
getItem: async (key: string): Promise<string | null> => {
|
||||
const value = await tauriStore.get<unknown>(key); // Use generic get
|
||||
return value == null ? null : JSON.stringify(value);
|
||||
},
|
||||
setItem: async (key: string, value: string): Promise<void> => {
|
||||
try {
|
||||
await tauriStore.set(key, JSON.parse(value));
|
||||
await tauriStore.save();
|
||||
} catch (err) {
|
||||
console.error(`Error parsing or setting item "${key}" in Tauri store:`, err);
|
||||
}
|
||||
},
|
||||
removeItem: async (key: string): Promise<void> => {
|
||||
await tauriStore.delete(key);
|
||||
await tauriStore.save();
|
||||
},
|
||||
});
|
||||
|
||||
// Configure how settings are stored and retrieved using Tauri's storage
|
||||
const settingsPersistConfig = {
|
||||
key: "settings",
|
||||
storage: {
|
||||
getItem: async (key: string) => tauriStore.get(key),
|
||||
setItem: async (key: string, value: unknown) => tauriStore.set(key, value),
|
||||
removeItem: async (key: string) => tauriStore.delete(key),
|
||||
},
|
||||
storage: createTauriStorage(),
|
||||
};
|
||||
|
||||
// Persist conversations across application restarts
|
||||
const conversationsPersistConfig = {
|
||||
key: "conversations",
|
||||
storage: createTauriStorage(),
|
||||
};
|
||||
|
||||
// Create a persisted version of the settings reducer
|
||||
|
@ -36,23 +57,55 @@ const persistedSettingsReducer = persistReducer(
|
|||
reducers.settings,
|
||||
);
|
||||
|
||||
// Create a persisted version of the conversations reducer
|
||||
const persistedConversationsReducer = persistReducer(
|
||||
conversationsPersistConfig,
|
||||
reducers.conversations,
|
||||
);
|
||||
|
||||
// Combine all reducers, using the persisted settings reducer
|
||||
const rootReducer = combineReducers({
|
||||
...reducers,
|
||||
settings: persistedSettingsReducer,
|
||||
conversations: persistedConversationsReducer,
|
||||
});
|
||||
|
||||
// Enable persistence for the entire application state
|
||||
const persistedReducer = persistReducer(rootPersistConfig, rootReducer);
|
||||
|
||||
// Set up the Redux store with persistence and custom middleware
|
||||
// Add DevTools Enhancer logic
|
||||
let remoteDevToolsEnhancer: StoreEnhancer | undefined;
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('Development mode detected, attempting to enable Redux DevTools Remote...');
|
||||
try {
|
||||
const { devToolsEnhancer } = await import('@redux-devtools/remote');
|
||||
remoteDevToolsEnhancer = devToolsEnhancer({
|
||||
name: 'UnstoppableSwap_RemoteInstance',
|
||||
realtime: true,
|
||||
hostname: 'localhost',
|
||||
port: 8098,
|
||||
});
|
||||
console.log('Redux DevTools Remote enhancer is ready.');
|
||||
} catch (e) {
|
||||
console.warn('Could not enable Redux DevTools Remote.', e);
|
||||
remoteDevToolsEnhancer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up the Redux store with persistence, middleware, and remote DevTools
|
||||
export const store = configureStore({
|
||||
reducer: persistedReducer,
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
// Disable serializable to silence warnings about non-serializable actions
|
||||
serializableCheck: false,
|
||||
}).prepend(createMainListeners().middleware),
|
||||
}).prepend(createMainListeners().middleware),
|
||||
enhancers: (getDefaultEnhancers) => {
|
||||
const defaultEnhancers = getDefaultEnhancers();
|
||||
return remoteDevToolsEnhancer
|
||||
? defaultEnhancers.concat(remoteDevToolsEnhancer)
|
||||
: defaultEnhancers;
|
||||
},
|
||||
});
|
||||
|
||||
// Create a persistor to manage the persisted store
|
||||
|
|
|
@ -6,6 +6,7 @@ import swapReducer from "./features/swapSlice";
|
|||
import torSlice from "./features/torSlice";
|
||||
import settingsSlice from "./features/settingsSlice";
|
||||
import nodesSlice from "./features/nodesSlice";
|
||||
import conversationsSlice from "./features/conversationsSlice";
|
||||
|
||||
export const reducers = {
|
||||
swap: swapReducer,
|
||||
|
@ -16,4 +17,5 @@ export const reducers = {
|
|||
rates: ratesSlice,
|
||||
settings: settingsSlice,
|
||||
nodes: nodesSlice,
|
||||
conversations: conversationsSlice,
|
||||
};
|
||||
|
|
54
src-gui/src/store/features/conversationsSlice.ts
Normal file
54
src-gui/src/store/features/conversationsSlice.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { Message } from "../../models/apiModel";
|
||||
|
||||
export interface ConversationsSlice {
|
||||
// List of feedback IDs we know of
|
||||
knownFeedbackIds: string[];
|
||||
// Maps feedback IDs to conversations using the updated Message type
|
||||
conversations: {
|
||||
[key: string]: Message[]; // Use the imported Message type
|
||||
};
|
||||
// Stores IDs for Messages that have been seen by the user
|
||||
seenMessages: string[];
|
||||
}
|
||||
|
||||
const initialState: ConversationsSlice = {
|
||||
knownFeedbackIds: [],
|
||||
conversations: {},
|
||||
seenMessages: [],
|
||||
};
|
||||
|
||||
const conversationsSlice = createSlice({
|
||||
name: "conversations",
|
||||
initialState,
|
||||
reducers: {
|
||||
addFeedbackId(slice, action: PayloadAction<string>) {
|
||||
// Only add if not already present
|
||||
if (!slice.knownFeedbackIds.includes(action.payload)) {
|
||||
slice.knownFeedbackIds.push(action.payload);
|
||||
}
|
||||
},
|
||||
// Removes a feedback id from the list of known ones
|
||||
// Also removes the conversation from the store
|
||||
removeFeedback(slice, action: PayloadAction<string>) {
|
||||
slice.knownFeedbackIds = slice.knownFeedbackIds.filter(
|
||||
(id) => id !== action.payload,
|
||||
);
|
||||
delete slice.conversations[action.payload];
|
||||
},
|
||||
// Sets the conversations for a given feedback id (Payload uses the correct Message type)
|
||||
setConversation(slice, action: PayloadAction<{feedbackId: string, messages: Message[]}>) {
|
||||
slice.conversations[action.payload.feedbackId] = action.payload.messages;
|
||||
},
|
||||
// Sets the seen messages for a given feedback id (Payload uses the correct Message type)
|
||||
markMessagesAsSeen(slice, action: PayloadAction<Message[]>) {
|
||||
const newSeenIds = action.payload
|
||||
.map((msg) => msg.id.toString())
|
||||
.filter(id => !slice.seenMessages.includes(id)); // Avoid duplicates
|
||||
slice.seenMessages.push(...newSeenIds);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { addFeedbackId, removeFeedback, setConversation, markMessagesAsSeen } = conversationsSlice.actions;
|
||||
export default conversationsSlice.reducer;
|
|
@ -146,4 +146,47 @@ export function usePendingApprovals(): PendingApprovalRequest[] {
|
|||
export function usePendingLockBitcoinApproval(): PendingLockBitcoinApprovalRequest[] {
|
||||
const approvals = usePendingApprovals();
|
||||
return approvals.filter((c) => c.content.details.type === "LockBitcoin");
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of unread messages from staff for a specific feedback conversation.
|
||||
* @param feedbackId The ID of the feedback conversation.
|
||||
* @returns The number of unread staff messages.
|
||||
*/
|
||||
export function useUnreadMessagesCount(feedbackId: string): number {
|
||||
const { conversationsMap, seenMessagesSet } = useAppSelector((state) => ({
|
||||
conversationsMap: state.conversations.conversations,
|
||||
// Convert seenMessages array to a Set for efficient lookup
|
||||
seenMessagesSet: new Set(state.conversations.seenMessages),
|
||||
}));
|
||||
|
||||
const messages = conversationsMap[feedbackId] || [];
|
||||
|
||||
const unreadStaffMessages = messages.filter(
|
||||
(msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()),
|
||||
);
|
||||
|
||||
return unreadStaffMessages.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total number of unread messages from staff across all feedback conversations.
|
||||
* @returns The total number of unread staff messages.
|
||||
*/
|
||||
export function useTotalUnreadMessagesCount(): number {
|
||||
const { conversationsMap, seenMessagesSet } = useAppSelector((state) => ({
|
||||
conversationsMap: state.conversations.conversations,
|
||||
seenMessagesSet: new Set(state.conversations.seenMessages),
|
||||
}));
|
||||
|
||||
let totalUnreadCount = 0;
|
||||
for (const feedbackId in conversationsMap) {
|
||||
const messages = conversationsMap[feedbackId] || [];
|
||||
const unreadStaffMessages = messages.filter(
|
||||
(msg) => msg.is_from_staff && !seenMessagesSet.has(msg.id.toString()),
|
||||
);
|
||||
totalUnreadCount += unreadStaffMessages.length;
|
||||
}
|
||||
|
||||
return totalUnreadCount;
|
||||
}
|
|
@ -3,9 +3,10 @@ import { getAllSwapInfos, checkBitcoinBalance, updateAllNodeStatuses, fetchSelle
|
|||
import logger from "utils/logger";
|
||||
import { contextStatusEventReceived } from "store/features/rpcSlice";
|
||||
import { addNode, setFetchFiatPrices, setFiatCurrency } from "store/features/settingsSlice";
|
||||
import { updateRates } from "renderer/api";
|
||||
import { fetchFeedbackMessagesViaHttp, updateRates } from "renderer/api";
|
||||
import { store } from "renderer/store/storeRenderer";
|
||||
import { swapProgressEventReceived } from "store/features/swapSlice";
|
||||
import { addFeedbackId, setConversation } from "store/features/conversationsSlice";
|
||||
|
||||
export function createMainListeners() {
|
||||
const listener = createListenerMiddleware();
|
||||
|
@ -78,5 +79,15 @@ export function createMainListeners() {
|
|||
},
|
||||
});
|
||||
|
||||
// Listener for when a feedback id is added
|
||||
listener.startListening({
|
||||
actionCreator: addFeedbackId,
|
||||
effect: async (action) => {
|
||||
// Whenever a new feedback id is added, fetch the messages and store them in the Redux store
|
||||
const messages = await fetchFeedbackMessagesViaHttp(action.payload);
|
||||
store.dispatch(setConversation({ feedbackId: action.payload, messages }));
|
||||
},
|
||||
});
|
||||
|
||||
return listener;
|
||||
}
|
||||
|
|
|
@ -74,4 +74,61 @@ export function bytesToMb(bytes: number): number {
|
|||
/// Get the markup of a maker's exchange rate compared to the market rate in percent
|
||||
export function getMarkup(makerPrice: number, marketPrice: number): number {
|
||||
return (makerPrice - marketPrice) / marketPrice * 100;
|
||||
}
|
||||
|
||||
// Updated function to parse 9-element tuple and format it
|
||||
export function formatDateTime(dateTime: [number, number, number, number, number, number, number, number, number] | null | undefined): string {
|
||||
if (!dateTime || !Array.isArray(dateTime) || dateTime.length !== 9) {
|
||||
// Basic validation for null, undefined, or incorrect structure
|
||||
return "Invalid Date Input";
|
||||
}
|
||||
|
||||
try {
|
||||
const [year, dayOfYear, hour, minute, second, nanoseconds, offsetH, offsetM, offsetS] = dateTime;
|
||||
|
||||
// More robust validation (example)
|
||||
if (year < 1970 || dayOfYear < 1 || dayOfYear > 366 || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 || nanoseconds < 0 || nanoseconds > 999999999) {
|
||||
return "Invalid Date Components";
|
||||
}
|
||||
|
||||
// Calculate total offset in seconds (handle potential non-zero offsets)
|
||||
const totalOffsetSeconds = (offsetH * 3600) + (offsetM * 60) + offsetS;
|
||||
|
||||
// Calculate milliseconds from nanoseconds
|
||||
const milliseconds = Math.floor(nanoseconds / 1_000_000);
|
||||
|
||||
// Create Date object for the start of the year *in UTC*
|
||||
const date = new Date(Date.UTC(year, 0, 1)); // Month is 0-indexed (January)
|
||||
|
||||
// Add (dayOfYear - 1) days to get the correct date *in UTC*
|
||||
date.setUTCDate(date.getUTCDate() + dayOfYear - 1);
|
||||
|
||||
// Set the time components *in UTC*
|
||||
date.setUTCHours(hour);
|
||||
date.setUTCMinutes(minute);
|
||||
date.setUTCSeconds(second);
|
||||
date.setUTCMilliseconds(milliseconds);
|
||||
|
||||
// Adjust for the timezone offset to get the correct UTC time
|
||||
// Subtract the offset because Date.UTC assumes UTC, but the components might be for a different offset
|
||||
date.setTime(date.getTime() - totalOffsetSeconds * 1000);
|
||||
|
||||
// Final validation
|
||||
if (isNaN(date.getTime())) {
|
||||
return "Invalid Calculated Date";
|
||||
}
|
||||
|
||||
// Format to a readable string (e.g., "YYYY-MM-DD HH:MM:SS UTC")
|
||||
const yyyy = date.getUTCFullYear();
|
||||
const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
|
||||
const dd = String(date.getUTCDate()).padStart(2, '0');
|
||||
const HH = String(date.getUTCHours()).padStart(2, '0');
|
||||
const MM = String(date.getUTCMinutes()).padStart(2, '0');
|
||||
const SS = String(date.getUTCSeconds()).padStart(2, '0');
|
||||
|
||||
return `${yyyy}-${mm}-${dd} ${HH}:${MM}:${SS} UTC`;
|
||||
|
||||
} catch (e) {
|
||||
return "Invalid Date Format";
|
||||
}
|
||||
}
|
|
@ -140,6 +140,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.23.2", "@babel/runtime@^7.26.9":
|
||||
version "7.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762"
|
||||
integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.25.9":
|
||||
version "7.25.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
|
||||
|
@ -516,6 +523,57 @@
|
|||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@redux-devtools/core@^4.1.1":
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@redux-devtools/core/-/core-4.1.1.tgz#4e0d6fe7d250f10d927872448f0085b6c48cd933"
|
||||
integrity sha512-ZyyJwiHX4DFDU0llk45tYSFPoIMekdoKLz0Q7soowpNOtchvTxruQx4Xy//Cohkwsw+DH8W1amdo4C/NYT6ARA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.26.9"
|
||||
"@redux-devtools/instrument" "^2.2.0"
|
||||
|
||||
"@redux-devtools/instrument@^2.2.0":
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@redux-devtools/instrument/-/instrument-2.2.0.tgz#bc9d015da693aa9fabdb32f4fd07ee4c1328eb95"
|
||||
integrity sha512-HKaL+ghBQ4ZQkM/kEQIKx8dNwz4E1oeiCDfdQlpPXxEi/BrisyrFFncAXb1y2HIJsLV9zSvQUR2jRtMDWgfi8w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.2"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@redux-devtools/remote@^0.9.5":
|
||||
version "0.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@redux-devtools/remote/-/remote-0.9.5.tgz#e0553026ea2d2f132246991c68dad57ac4d034e1"
|
||||
integrity sha512-ETOUWgB5n6yopU4xH6wSwwmcVQT6liGBJbrWHkJkXCbCq9j/VqXHQ7spNN398p59vDseFZWOPo8KXNI0Mvo1RQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.26.9"
|
||||
"@redux-devtools/instrument" "^2.2.0"
|
||||
"@redux-devtools/utils" "^3.1.1"
|
||||
jsan "^3.1.14"
|
||||
rn-host-detect "^1.2.0"
|
||||
socketcluster-client "^19.2.3"
|
||||
|
||||
"@redux-devtools/serialize@^0.4.2":
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@redux-devtools/serialize/-/serialize-0.4.2.tgz#564c0cf2e5cb119a1884b1994a51f6d2e138b9a5"
|
||||
integrity sha512-YVqZCChJld5l3Ni2psEZ5loe9x5xpf9J4ckz+7OJdzCNsplC7vzjnkQbFxE6+ULZbywRVp+nSBslTXmaXqAw4A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.2"
|
||||
jsan "^3.1.14"
|
||||
|
||||
"@redux-devtools/utils@^3.1.1":
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@redux-devtools/utils/-/utils-3.1.1.tgz#a0c0aecf2c2e0f02518d48450dda90b9fe6eeb11"
|
||||
integrity sha512-l+m3/8a7lcxULInBADIqE/3Tt2DkTJm5MAGVA/4czMCXW0VE+gdjkoRFqgZhTBoDJW1fi1z8pdL+4G/+R1rDJw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.26.9"
|
||||
"@redux-devtools/core" "^4.1.1"
|
||||
"@redux-devtools/serialize" "^0.4.2"
|
||||
"@types/get-params" "^0.1.2"
|
||||
get-params "^0.1.2"
|
||||
immutable "^4.3.7"
|
||||
jsan "^3.1.14"
|
||||
nanoid "^5.1.2"
|
||||
redux "^5.0.1"
|
||||
|
||||
"@reduxjs/toolkit@^2.3.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.4.0.tgz#29fd3a19530fc50d648a9b1e0132da0cd5618f19"
|
||||
|
@ -877,6 +935,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
||||
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
||||
|
||||
"@types/get-params@^0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/get-params/-/get-params-0.1.2.tgz#815f80eceb0f0e2f0bb00a2527c9d2e6e57e2a52"
|
||||
integrity sha512-ujqPyr1UDsOTDngJPV+WFbR0iHT5AfZKlNPMX6XOCnQcMhEqR+r64dVC/nwYCitqjR3DcpWofnOEAInUQmI/eA==
|
||||
|
||||
"@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494"
|
||||
|
@ -1129,6 +1192,28 @@ acorn@^8.14.0:
|
|||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
|
||||
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
|
||||
|
||||
ag-auth@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-auth/-/ag-auth-2.1.0.tgz#e6f9ecabbf23352456bd1e51ada4d6cf2382198d"
|
||||
integrity sha512-M4l+IErFmYPk0HAvolaPyvCMyn3oJ4aPHVMeVqlxJIynkHGhyTFiT+LX+jYY34pEdwM03TLkQUMHxpXBMuNmZg==
|
||||
dependencies:
|
||||
jsonwebtoken "^9.0.0"
|
||||
sc-errors "^3.0.0"
|
||||
|
||||
ag-channel@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-channel/-/ag-channel-5.0.0.tgz#c2c00dfbe372ae43e0466ec89e29aca1bbb2fb3e"
|
||||
integrity sha512-bArHkdqQxynim981t8FLZM5TfA0v7p081OlFdOxs6clB79GSGcGlOQMDa31DT9F5VMjzqNiJmhfGwinvfU/3Zg==
|
||||
dependencies:
|
||||
consumable-stream "^2.0.0"
|
||||
|
||||
ag-request@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ag-request/-/ag-request-1.1.0.tgz#62ef63c572510bbce34993a5d47e467d0040a17f"
|
||||
integrity sha512-d4K7QC1KnIpzcnUNNOeh1ddxmYMLiIdhdc1M8osxiHbZP/uoia4IINhhf2+1CrlnNJEPUoUH0Y58Sx0qeqoIvg==
|
||||
dependencies:
|
||||
sc-errors "^3.0.0"
|
||||
|
||||
ajv@^6.12.4:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
|
@ -1233,6 +1318,13 @@ assertion-error@^2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7"
|
||||
integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==
|
||||
|
||||
async-stream-emitter@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-stream-emitter/-/async-stream-emitter-7.0.1.tgz#c01832cddcc8f07d8ed528347803ec1517f8886d"
|
||||
integrity sha512-1bgA3iZ80rCBX2LocvsyZPy0QB3/xM+CsXBze2HDHLmshOqx2JlAANGq23djaJ48e9fpcKzTzS1QM0hAKKI0UQ==
|
||||
dependencies:
|
||||
stream-demux "^10.0.1"
|
||||
|
||||
atomic-sleep@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
|
||||
|
@ -1255,6 +1347,14 @@ base64-js@^1.3.1:
|
|||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
bl@^1.2.1:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
|
||||
integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==
|
||||
dependencies:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
|
@ -1287,6 +1387,19 @@ browserslist@^4.24.0:
|
|||
node-releases "^2.0.18"
|
||||
update-browserslist-db "^1.1.1"
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
buffer@^5.2.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
|
||||
integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==
|
||||
dependencies:
|
||||
base64-js "^1.3.1"
|
||||
ieee754 "^1.1.13"
|
||||
|
||||
buffer@^6.0.3:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
|
||||
|
@ -1352,6 +1465,15 @@ check-error@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc"
|
||||
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
|
||||
|
||||
clone-deep@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
||||
integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
|
||||
dependencies:
|
||||
is-plain-object "^2.0.4"
|
||||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
clsx@^1.0.4, clsx@^1.1.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
||||
|
@ -1379,11 +1501,26 @@ concat-map@0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
consumable-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/consumable-stream/-/consumable-stream-2.0.0.tgz#11d3c7281b747eb9efd31c199b3a8b1711bec654"
|
||||
integrity sha512-I6WA2JVYXs/68rEvi1ie3rZjP6qusTVFEQkbzR+WC+fY56TpwiGTIDJETsrnlxv5CsnmK69ps6CkYvIbpEEqBA==
|
||||
|
||||
consumable-stream@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/consumable-stream/-/consumable-stream-3.0.0.tgz#2bf140e0c5f9b63d6fa116ac6b05e53713d3cb41"
|
||||
integrity sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg==
|
||||
|
||||
convert-source-map@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||
|
||||
cross-spawn@^7.0.3, cross-spawn@^7.0.5:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
|
@ -1509,6 +1646,13 @@ dom-helpers@^5.0.1:
|
|||
"@babel/runtime" "^7.8.7"
|
||||
csstype "^3.0.2"
|
||||
|
||||
ecdsa-sig-formatter@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
|
||||
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
electron-to-chromium@^1.5.41:
|
||||
version "1.5.68"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.68.tgz#4f46be4d465ef00e2100d5557b66f4af70e3ce6c"
|
||||
|
@ -1970,6 +2114,11 @@ get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4:
|
|||
has-symbols "^1.0.3"
|
||||
hasown "^2.0.0"
|
||||
|
||||
get-params@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/get-params/-/get-params-0.1.2.tgz#bae0dfaba588a0c60d7834c0d8dc2ff60eeef2fe"
|
||||
integrity sha512-41eOxtlGgHQRbFyA8KTH+w+32Em3cRdfBud7j67ulzmIfmaHX9doq47s0fa4P5o9H64BZX9nrYI6sJvk46Op+Q==
|
||||
|
||||
get-stream@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
|
@ -2111,7 +2260,7 @@ hyphenate-style-name@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
|
||||
integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==
|
||||
|
||||
ieee754@^1.2.1:
|
||||
ieee754@^1.1.13, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
@ -2126,6 +2275,11 @@ immer@^10.0.3:
|
|||
resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc"
|
||||
integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
|
||||
|
||||
immutable@^4.3.7:
|
||||
version "4.3.7"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
|
||||
integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
|
||||
|
||||
import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
|
@ -2139,6 +2293,11 @@ imurmurhash@^0.1.4:
|
|||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
||||
|
||||
inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
internal-ip@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-7.0.0.tgz#5b1c6a9d7e188aa73a1b69717daf50c8d8ed774f"
|
||||
|
@ -2285,6 +2444,13 @@ is-number@^7.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
|
||||
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
|
||||
|
||||
is-plain-object@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||
integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
is-regex@^1.1.4:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.0.tgz#41b9d266e7eb7451312c64efc37e8a7d453077cf"
|
||||
|
@ -2361,11 +2527,21 @@ isarray@^2.0.5:
|
|||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
isobject@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
|
||||
|
||||
iterator.prototype@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.3.tgz#016c2abe0be3bbdb8319852884f60908ac62bf9c"
|
||||
|
@ -2401,6 +2577,11 @@ js-yaml@^4.1.0:
|
|||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsan@^3.1.14:
|
||||
version "3.1.14"
|
||||
resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.14.tgz#197fee2d260b85acacb049c1ffa41bd09fb1f213"
|
||||
integrity sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA==
|
||||
|
||||
jsesc@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
|
||||
|
@ -2426,6 +2607,22 @@ json5@^2.2.3:
|
|||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jsonwebtoken@^9.0.0:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
|
||||
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^7.5.4"
|
||||
|
||||
jss-plugin-camel-case@^10.5.1:
|
||||
version "10.10.0"
|
||||
resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz#27ea159bab67eb4837fa0260204eb7925d4daa1c"
|
||||
|
@ -2506,6 +2703,23 @@ jss@10.10.0, jss@^10.5.1:
|
|||
object.assign "^4.1.4"
|
||||
object.values "^1.1.6"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
keyv@^4.5.4:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||
|
@ -2513,6 +2727,11 @@ keyv@^4.5.4:
|
|||
dependencies:
|
||||
json-buffer "3.0.1"
|
||||
|
||||
kind-of@^6.0.2:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
|
||||
|
@ -2521,6 +2740,11 @@ levn@^0.4.1:
|
|||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
linked-list@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/linked-list/-/linked-list-2.1.0.tgz#fa7b63a6caf4b17862a1eb90d14ead4ee57649f2"
|
||||
integrity sha512-0GK/ylO6e5cv1PCOIdTRHxOaCgQ+0jKwHt+cHzkiCAZlx0KM5Id1bBAPad6g2mkvBNp1pNdmG0cohFGfqjkv9A==
|
||||
|
||||
locate-path@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
|
||||
|
@ -2528,11 +2752,46 @@ locate-path@^6.0.0:
|
|||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.merge@^4.6.2:
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
|
@ -2640,6 +2899,11 @@ nanoid@^3.3.7:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
|
||||
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
|
||||
|
||||
nanoid@^5.1.2:
|
||||
version "5.1.5"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.1.5.tgz#f7597f9d9054eb4da9548cdd53ca70f1790e87de"
|
||||
integrity sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==
|
||||
|
||||
native-fetch@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/native-fetch/-/native-fetch-3.0.0.tgz#06ccdd70e79e171c365c75117959cf4fe14a09bb"
|
||||
|
@ -2902,6 +3166,11 @@ prelude-ls@^1.2.1:
|
|||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
process-warning@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-4.0.0.tgz#581e3a7a1fb456c5f4fd239f76bce75897682d5a"
|
||||
|
@ -3020,6 +3289,19 @@ react@^18.2.0:
|
|||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
readable-stream@^2.3.5, readable-stream@~2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
||||
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^4.0.0:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09"
|
||||
|
@ -3117,6 +3399,11 @@ reusify@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||
|
||||
rn-host-detect@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.2.0.tgz#8b0396fc05631ec60c1cb8789e5070cdb04d0da0"
|
||||
integrity sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A==
|
||||
|
||||
rollup@^4.20.0:
|
||||
version "4.28.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.28.0.tgz#eb8d28ed43ef60a18f21d0734d230ee79dd0de77"
|
||||
|
@ -3161,11 +3448,16 @@ safe-array-concat@^1.1.2:
|
|||
has-symbols "^1.0.3"
|
||||
isarray "^2.0.5"
|
||||
|
||||
safe-buffer@~5.2.0:
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-regex-test@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377"
|
||||
|
@ -3180,6 +3472,16 @@ safe-stable-stringify@^2.3.1:
|
|||
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd"
|
||||
integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==
|
||||
|
||||
sc-errors@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sc-errors/-/sc-errors-3.0.0.tgz#df2e124f011be5fdd633e92d1de5ce6a6b4c1b85"
|
||||
integrity sha512-rIqv2HTPb9DVreZwK/DV0ytRUqyw2DbDcoB9XTKjEQL7oMEQKsfPA8V8dGGr7p8ZYfmvaRIGZ4Wu5qwvs/hGDA==
|
||||
|
||||
sc-formatter@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/sc-formatter/-/sc-formatter-4.0.0.tgz#2dda494a08e9d4cb069cbc9238a9f670adb3e7a6"
|
||||
integrity sha512-MgUIvuca+90fBrCWY5LdlU9YUWjlkPFwdpvmomcwQEu3t2id/6YHdG2nhB6o7nhRp4ocfmcXQTh00r/tJtynSg==
|
||||
|
||||
scheduler@^0.23.2:
|
||||
version "0.23.2"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
|
||||
|
@ -3197,6 +3499,11 @@ semver@^6.3.1:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.5.4:
|
||||
version "7.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
|
||||
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
|
||||
|
||||
semver@^7.6.0, semver@^7.6.2:
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||
|
@ -3224,6 +3531,13 @@ set-function-name@^2.0.1, set-function-name@^2.0.2:
|
|||
functions-have-names "^1.2.3"
|
||||
has-property-descriptors "^1.0.2"
|
||||
|
||||
shallow-clone@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
|
||||
integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
|
||||
dependencies:
|
||||
kind-of "^6.0.2"
|
||||
|
||||
shebang-command@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||
|
@ -3256,6 +3570,25 @@ signal-exit@^3.0.3:
|
|||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
|
||||
socketcluster-client@^19.2.3:
|
||||
version "19.2.3"
|
||||
resolved "https://registry.yarnpkg.com/socketcluster-client/-/socketcluster-client-19.2.3.tgz#89d8f6a215f8b6469ab3d93d1d4d0d9abf30c747"
|
||||
integrity sha512-kYHBTH+P0UXnHQQxTVK9//rSAgETWSaVe8A4wlDpTQPqzpTWn2bq2ARaiLgXx8WouKaS9XcOLDRQc58e2fFscg==
|
||||
dependencies:
|
||||
ag-auth "^2.1.0"
|
||||
ag-channel "^5.0.0"
|
||||
ag-request "^1.1.0"
|
||||
async-stream-emitter "^7.0.1"
|
||||
buffer "^5.2.1"
|
||||
clone-deep "^4.0.1"
|
||||
linked-list "^2.1.0"
|
||||
sc-errors "^3.0.0"
|
||||
sc-formatter "^4.0.0"
|
||||
stream-demux "^10.0.1"
|
||||
uuid "^8.3.2"
|
||||
vinyl-buffer "^1.0.1"
|
||||
ws "^8.18.0"
|
||||
|
||||
sonic-boom@^4.0.1:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.2.0.tgz#e59a525f831210fa4ef1896428338641ac1c124d"
|
||||
|
@ -3283,6 +3616,14 @@ std-env@^3.8.0:
|
|||
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5"
|
||||
integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==
|
||||
|
||||
stream-demux@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stream-demux/-/stream-demux-10.0.1.tgz#204b65fb8973c87cea65119e99622405b3dbcc10"
|
||||
integrity sha512-QjTYLJWpZxZ6uL5R1JzgOzjvao8zDx78ec+uOjHNeVc/9TuasYLldoVrYARZeT1xI1hFYuiKf13IM8b4wamhHg==
|
||||
dependencies:
|
||||
consumable-stream "^3.0.0"
|
||||
writable-consumable-stream "^4.1.0"
|
||||
|
||||
string.prototype.matchall@^4.0.11:
|
||||
version "4.0.11"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a"
|
||||
|
@ -3344,6 +3685,13 @@ string_decoder@^1.3.0:
|
|||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-final-newline@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||
|
@ -3373,6 +3721,14 @@ thread-stream@^3.0.0:
|
|||
dependencies:
|
||||
real-require "^0.2.0"
|
||||
|
||||
through2@^2.0.3:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
|
||||
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
|
||||
dependencies:
|
||||
readable-stream "~2.3.6"
|
||||
xtend "~4.0.1"
|
||||
|
||||
tiny-warning@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
|
@ -3533,16 +3889,34 @@ use-sync-external-store@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9"
|
||||
integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
uuid@^10.0.0:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
|
||||
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
|
||||
|
||||
uuid@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
varint@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0"
|
||||
integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==
|
||||
|
||||
vinyl-buffer@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vinyl-buffer/-/vinyl-buffer-1.0.1.tgz#96c1a3479b8c5392542c612029013b5b27f88bbf"
|
||||
integrity sha512-LRBE2/g3C1hSHL2k/FynSZcVTRhEw8sb08oKGt/0hukZXwrh2m8nfy+r5yLhGEk7eFFuclhyIuPct/Bxlxk6rg==
|
||||
dependencies:
|
||||
bl "^1.2.1"
|
||||
through2 "^2.0.3"
|
||||
|
||||
virtua@^0.33.2:
|
||||
version "0.33.7"
|
||||
resolved "https://registry.yarnpkg.com/virtua/-/virtua-0.33.7.tgz#bd46d7d31f257886e6245347354fb4e80e27441f"
|
||||
|
@ -3697,6 +4071,23 @@ wrappy@1:
|
|||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
writable-consumable-stream@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/writable-consumable-stream/-/writable-consumable-stream-4.1.0.tgz#e677076f91499159361d7917dce379cad624b344"
|
||||
integrity sha512-4cjCPd4Ayfbix0qqPCzMbnPPZKRh/cKeNCj05unybP3/sRkRAOxh7rSwbhxs3YB6G4/Z2p/2FRBEIQcTeB4jyw==
|
||||
dependencies:
|
||||
consumable-stream "^3.0.0"
|
||||
|
||||
ws@^8.18.0:
|
||||
version "8.18.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
|
||||
integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
|
||||
|
||||
xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
yallist@^3.0.2:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue