Skip to content

Commit

Permalink
feat(ui): display alert when no witness availablity (#876)
Browse files Browse the repository at this point in the history
Co-authored-by: Vu Van Duc <[email protected]>
  • Loading branch information
Sotatek-DukeVu and Vu Van Duc authored Dec 19, 2024
1 parent 7b257d9 commit 605c48a
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 16 deletions.
4 changes: 4 additions & 0 deletions src/locales/en/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1810,6 +1810,10 @@
"text": "Something went wrong. Please try again.",
"button": "OK"
},
"nowitnesserror": {
"text": "Your SSI agent is misconfigured. For additional information, contact your SSI Cloud Agent provider.",
"button": "OK"
},
"readmore": {
"more": "Read more",
"less": "Read less"
Expand Down
8 changes: 8 additions & 0 deletions src/store/reducers/stateCache/stateCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ const stateCacheSlice = createSlice({
showConnections: (state, action: PayloadAction<boolean>) => {
state.showConnections = action.payload;
},
showNoWitnessAlert: (state, action: PayloadAction<boolean | undefined>) => {
state.showNoWitnessAlert = action.payload;
},
},
});

Expand All @@ -190,6 +193,7 @@ const {
showGenericError,
showConnections,
removeToastMessage,
showNoWitnessAlert
} = stateCacheSlice.actions;

const getStateCache = (state: RootState) => state.stateCache;
Expand All @@ -214,6 +218,8 @@ const getShowCommonError = (state: RootState) =>
state.stateCache.showGenericError;
const getShowConnections = (state: RootState) =>
state.stateCache.showConnections;
const getShowNoWitnessAlert = (state: RootState) =>
state.stateCache.showNoWitnessAlert;
const getToastMgs = (state: RootState) => state.stateCache.toastMsgs;

export type {
Expand All @@ -223,6 +229,8 @@ export type {
};

export {
showNoWitnessAlert,
getShowNoWitnessAlert,
dequeueIncomingRequest,
enqueueIncomingRequest,
getAuthentication,
Expand Down
1 change: 1 addition & 0 deletions src/store/reducers/stateCache/stateCache.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ interface StateCacheProps {
queueIncomingRequest: QueueProps<IncomingRequestProps>;
cameraDirection?: LensFacing;
showGenericError?: boolean;
showNoWitnessAlert?: boolean;
showConnections: boolean;
toastMsgs: ToastStackItem[];
}
Expand Down
192 changes: 191 additions & 1 deletion src/ui/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import { render, waitFor } from "@testing-library/react";
import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom";
import configureStore from "redux-mock-store";
import { IdentifierService } from "../core/agent/services";
import Eng_Trans from "../locales/en/en.json";
import { TabsRoutePath } from "../routes/paths";
import { store } from "../store";
import { showGenericError } from "../store/reducers/stateCache";
import { showGenericError, showNoWitnessAlert } from "../store/reducers/stateCache";
import { App } from "./App";
import { OperationType } from "./globals/types";

const mockInitDatabase = jest.fn();
const getAvailableWitnessesMock = jest.fn();

jest.mock("../core/agent/agent", () => ({
Agent: {
Expand All @@ -27,6 +29,7 @@ jest.mock("../core/agent/agent", () => ({
getIdentifiers: jest.fn().mockResolvedValue([]),
syncKeriaIdentifiers: jest.fn(),
onIdentifierAdded: jest.fn(),
getAvailableWitnesses: () => getAvailableWitnessesMock()
},
connections: {
getConnections: jest.fn().mockResolvedValue([]),
Expand Down Expand Up @@ -224,6 +227,7 @@ describe("App", () => {
isNativeMock.mockImplementation(() => false);
mockInitDatabase.mockClear();
getPlatformsMock.mockImplementation(() => ["android"]);
getAvailableWitnessesMock.mockClear();
});

test("Mobile header hidden when app not in preview mode", async () => {
Expand Down Expand Up @@ -519,3 +523,189 @@ describe("App", () => {
});
});
});

describe("Witness availability", () => {
test("No witness availability", async () => {
getAvailableWitnessesMock.mockImplementation(() => Promise.resolve([]));

const initialState = {
stateCache: {
isOnline: true,
routes: [{ path: TabsRoutePath.ROOT }],
authentication: {
loggedIn: true,
userName: "",
time: Date.now(),
passcodeIsSet: true,
seedPhraseIsSet: true,
passwordIsSet: false,
passwordIsSkipped: true,
ssiAgentIsSet: true,
recoveryWalletProgress: false,
loginAttempt: {
attempts: 0,
lockedUntil: Date.now(),
},
},
toastMsgs: [],
queueIncomingRequest: {
isProcessing: false,
queues: [],
isPaused: false,
},
},
seedPhraseCache: {
seedPhrase: "",
bran: "",
},
identifiersCache: {
identifiers: [],
favourites: [],
multiSigGroup: {
groupId: "",
connections: [],
},
},
credsCache: { creds: [], favourites: [] },
credsArchivedCache: { creds: [] },
connectionsCache: {
connections: {},
multisigConnections: {},
},
walletConnectionsCache: {
walletConnections: [],
connectedWallet: null,
pendingConnection: null,
},
viewTypeCache: {
identifier: {
viewType: null,
favouriteIndex: 0,
},
credential: {
viewType: null,
favouriteIndex: 0,
},
},
biometricsCache: {
enabled: false,
},
ssiAgentCache: {
bootUrl: "",
connectUrl: "",
},
notificationsCache: {
notifications: [],
},
};

const storeMocked = {
...mockStore(initialState),
dispatch: dispatchMock,
};

render(
<Provider store={storeMocked}>
<MemoryRouter initialEntries={[TabsRoutePath.IDENTIFIERS]}>
<App />
</MemoryRouter>
</Provider>
);

await waitFor(() => {
expect(dispatchMock).toBeCalledWith(showNoWitnessAlert(true));
});
});

test("Throw error", async () => {
getAvailableWitnessesMock.mockImplementation(() => Promise.reject(new Error(IdentifierService.MISCONFIGURED_AGENT_CONFIGURATION)));

const initialState = {
stateCache: {
isOnline: true,
routes: [{ path: TabsRoutePath.ROOT }],
authentication: {
loggedIn: true,
userName: "",
time: Date.now(),
passcodeIsSet: true,
seedPhraseIsSet: true,
passwordIsSet: false,
passwordIsSkipped: true,
ssiAgentIsSet: true,
recoveryWalletProgress: false,
loginAttempt: {
attempts: 0,
lockedUntil: Date.now(),
},
},
toastMsgs: [],
queueIncomingRequest: {
isProcessing: false,
queues: [],
isPaused: false,
},
},
seedPhraseCache: {
seedPhrase: "",
bran: "",
},
identifiersCache: {
identifiers: [],
favourites: [],
multiSigGroup: {
groupId: "",
connections: [],
},
},
credsCache: { creds: [], favourites: [] },
credsArchivedCache: { creds: [] },
connectionsCache: {
connections: {},
multisigConnections: {},
},
walletConnectionsCache: {
walletConnections: [],
connectedWallet: null,
pendingConnection: null,
},
viewTypeCache: {
identifier: {
viewType: null,
favouriteIndex: 0,
},
credential: {
viewType: null,
favouriteIndex: 0,
},
},
biometricsCache: {
enabled: false,
},
ssiAgentCache: {
bootUrl: "",
connectUrl: "",
},
notificationsCache: {
notifications: [],
},
};

const storeMocked = {
...mockStore(initialState),
dispatch: dispatchMock,
};

render(
<Provider store={storeMocked}>
<MemoryRouter initialEntries={[TabsRoutePath.IDENTIFIERS]}>
<App />
</MemoryRouter>
</Provider>
);

await waitFor(() => {
expect(dispatchMock).toBeCalledWith(showNoWitnessAlert(true));
});
});
});
3 changes: 2 additions & 1 deletion src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import { AppOffline } from "./components/AppOffline";
import { AppWrapper } from "./components/AppWrapper";
import { ToastStack } from "./components/CustomToast/ToastStack";
import { GenericError } from "./components/Error";
import { GenericError, NoWitnessAlert } from "./components/Error";
import { InputRequest } from "./components/InputRequest";
import { SidePage } from "./components/SidePage";
import { OperationType } from "./globals/types";
Expand Down Expand Up @@ -140,6 +140,7 @@ const App = () => {
<InputRequest />
<SidePage />
<GenericError />
<NoWitnessAlert />
<ToastStack />
</StrictMode>
</AppWrapper>
Expand Down
4 changes: 2 additions & 2 deletions src/ui/components/AppWrapper/AppWrapper.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { store } from "../../../store";
import { updateOrAddConnectionCache } from "../../../store/reducers/connectionsCache";
import { updateOrAddCredsCache } from "../../../store/reducers/credsCache";
import { updateIsPending } from "../../../store/reducers/identifiersCache";
import { setNotificationsCache } from "../../../store/reducers/notificationsCache";
import {
setQueueIncomingRequest,
setToastMsg,
Expand Down Expand Up @@ -66,6 +65,7 @@ jest.mock("../../../core/agent/agent", () => ({
getIdentifiers: jest.fn().mockResolvedValue([]),
syncKeriaIdentifiers: jest.fn(),
onIdentifierAdded: jest.fn(),
getAvailableWitnesses: jest.fn()
},
multiSigs: {
getMultisigIcpDetails: jest.fn().mockResolvedValue({}),
Expand Down Expand Up @@ -378,4 +378,4 @@ describe("Signify operation state changed handler", () => {
setToastMsg(ToastMsgType.IDENTIFIER_UPDATED)
);
});
});
});
26 changes: 26 additions & 0 deletions src/ui/components/AppWrapper/AppWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
setPauseQueueIncomingRequest,
setQueueIncomingRequest,
setToastMsg,
showNoWitnessAlert,
} from "../../../store/reducers/stateCache";
import { IncomingRequestType } from "../../../store/reducers/stateCache/stateCache.types";
import {
Expand All @@ -79,6 +80,7 @@ import {
} from "../../../core/agent/event.types";
import { IdentifiersFilters } from "../../pages/Identifiers/Identifiers.types";
import { CredentialsFilters } from "../../pages/Credentials/Credentials.types";
import { IdentifierService } from "../../../core/agent/services";

const connectionStateChangedHandler = async (
event: ConnectionStateChangedEvent,
Expand Down Expand Up @@ -193,6 +195,30 @@ const AppWrapper = (props: { children: ReactNode }) => {
[dispatch]
);

const checkWitness = useCallback(async () => {
if(!authentication.ssiAgentIsSet || !isOnline) return;

try {
const witness = await Agent.agent.identifiers.getAvailableWitnesses();

if (witness.length === 0)
throw new Error(IdentifierService.NO_WITNESSES_AVAILABLE)

} catch(e) {
const errorMessage = (e as Error).message;
if(errorMessage.includes(IdentifierService.NO_WITNESSES_AVAILABLE) || errorMessage.includes(IdentifierService.MISCONFIGURED_AGENT_CONFIGURATION)) {
dispatch(showNoWitnessAlert(true));
return;
}

throw e;
}
}, [authentication.ssiAgentIsSet, dispatch, isOnline]);

useEffect(() => {
checkWitness();
}, [checkWitness])

useEffect(() => {
initApp();
}, []);
Expand Down
Loading

0 comments on commit 605c48a

Please sign in to comment.