mirror of
https://github.com/CryptKeeperZK/crypt-keeper-extension.git
synced 2026-01-08 21:47:56 -05:00
feat: connection service integration (#1102)
- [x] Add connect/disconnect events - [x] Get rid of origin url from metadata - [x] Get rid of connected identity from identity service - [x] Use connection service for one-to-many relation between identities and origins --------- Co-authored-by: 0xmad <0xmad@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,6 @@ MANIFEST_VERSION=3
|
||||
VERIFIABLE_CREDENTIALS=false
|
||||
WEBPACK_ANALYZER=
|
||||
MERKLE_MOCK_SERVER=https://ckmock.appliedzkp.org/
|
||||
TEST_GROUP_ID=90694543209366256629502773954857
|
||||
TEST_GROUP_API_KEY=cc6c5160-9042-483e-b83b-0ffb1a9bf4d2
|
||||
TEST_GROUP_ID=52052650962124550187958196807658
|
||||
TEST_GROUP_API_KEY=d53fad2c-4510-4f51-a4f2-f9f706d839e7
|
||||
TEST_GROUP_INVITE_CODE=
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import identity from "lodash/identity";
|
||||
import pickBy from "lodash/pickBy";
|
||||
import browser, { Windows } from "webextension-polyfill";
|
||||
|
||||
import type { IReduxAction, IZkMetadata } from "@cryptkeeperzk/types";
|
||||
@@ -19,7 +21,7 @@ interface CreateTabArgs {
|
||||
}
|
||||
|
||||
interface OpenPopupArgs {
|
||||
params?: Record<string, string>;
|
||||
params?: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
export default class BrowserUtils {
|
||||
@@ -51,7 +53,9 @@ export default class BrowserUtils {
|
||||
|
||||
const tabs = await browser.tabs.query({ lastFocusedWindow: true });
|
||||
const index = tabs.findIndex((tab) => tab.active && tab.highlighted);
|
||||
const searchParams = params ? `?${new URLSearchParams(params).toString()}` : "";
|
||||
const searchParams = params
|
||||
? `?${new URLSearchParams(pickBy(params, identity) as Record<string, string>).toString()}`
|
||||
: "";
|
||||
const tab = await this.createTab({
|
||||
url: `popup.html#/${searchParams}`,
|
||||
active: index >= 0,
|
||||
|
||||
@@ -6,6 +6,7 @@ import Handler from "@src/background/controllers/handler";
|
||||
import RequestManager from "@src/background/controllers/requestManager";
|
||||
import ApprovalService from "@src/background/services/approval";
|
||||
import BackupService from "@src/background/services/backup";
|
||||
import ConnectionService from "@src/background/services/connection";
|
||||
import VerifiableCredentialsService from "@src/background/services/credentials";
|
||||
import GroupService from "@src/background/services/group";
|
||||
import HistoryService from "@src/background/services/history";
|
||||
@@ -72,6 +73,8 @@ export default class CryptKeeperController {
|
||||
|
||||
private protocolService: ProtocolService;
|
||||
|
||||
private connectionService: ConnectionService;
|
||||
|
||||
constructor() {
|
||||
this.handler = new Handler();
|
||||
this.requestManager = RequestManager.getInstance();
|
||||
@@ -87,12 +90,14 @@ export default class CryptKeeperController {
|
||||
this.verifiableCredentialsService = VerifiableCredentialsService.getInstance();
|
||||
this.groupService = GroupService.getInstance();
|
||||
this.protocolService = ProtocolService.getInstance();
|
||||
this.connectionService = ConnectionService.getInstance();
|
||||
this.backupService = BackupService.getInstance()
|
||||
.add(BackupableServices.LOCK, this.lockService)
|
||||
.add(BackupableServices.WALLET, this.walletService)
|
||||
.add(BackupableServices.APPROVAL, this.approvalService)
|
||||
.add(BackupableServices.IDENTITY, this.zkIdentityService)
|
||||
.add(BackupableServices.VERIFIABLE_CREDENTIALS, this.verifiableCredentialsService);
|
||||
.add(BackupableServices.VERIFIABLE_CREDENTIALS, this.verifiableCredentialsService)
|
||||
.add(BackupableServices.CONNECTIONS, this.connectionService);
|
||||
}
|
||||
|
||||
handle = (request: IRequestHandler, sender: Runtime.MessageSender): Promise<unknown> =>
|
||||
@@ -106,42 +111,42 @@ export default class CryptKeeperController {
|
||||
RPCExternalAction.GENERATE_SEMAPHORE_PROOF,
|
||||
this.lockService.ensure,
|
||||
this.approvalService.isOriginApproved,
|
||||
this.injectorService.isConnected,
|
||||
this.connectionService.isOriginConnected,
|
||||
this.protocolService.generateSemaphoreProof,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCExternalAction.GENERATE_RLN_PROOF,
|
||||
this.lockService.ensure,
|
||||
this.approvalService.isOriginApproved,
|
||||
this.injectorService.isConnected,
|
||||
this.connectionService.isOriginConnected,
|
||||
this.protocolService.generateRLNProof,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCExternalAction.REVEAL_CONNECTED_IDENTITY_COMMITMENT,
|
||||
this.lockService.ensure,
|
||||
this.approvalService.isOriginApproved,
|
||||
this.injectorService.isConnected,
|
||||
this.zkIdentityService.revealConnectedIdentityCommitmentRequest,
|
||||
this.connectionService.isOriginConnected,
|
||||
this.connectionService.revealConnectedIdentityCommitmentRequest,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCExternalAction.JOIN_GROUP,
|
||||
this.lockService.ensure,
|
||||
this.approvalService.isOriginApproved,
|
||||
this.injectorService.isConnected,
|
||||
this.connectionService.isOriginConnected,
|
||||
this.groupService.joinGroupRequest,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCExternalAction.GENERATE_GROUP_MERKLE_PROOF,
|
||||
this.lockService.ensure,
|
||||
this.approvalService.isOriginApproved,
|
||||
this.injectorService.isConnected,
|
||||
this.connectionService.isOriginConnected,
|
||||
this.groupService.generateGroupMerkleProofRequest,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCExternalAction.IMPORT_IDENTITY,
|
||||
this.lockService.ensure,
|
||||
this.approvalService.isOriginApproved,
|
||||
this.injectorService.isConnected,
|
||||
this.connectionService.isOriginConnected,
|
||||
this.zkIdentityService.importRequest,
|
||||
);
|
||||
|
||||
@@ -152,83 +157,62 @@ export default class CryptKeeperController {
|
||||
this.lockService.unlock,
|
||||
this.approvalService.unlock,
|
||||
this.zkIdentityService.unlock,
|
||||
this.connectionService.unlock,
|
||||
this.lockService.onUnlocked,
|
||||
this.approvalService.onUnlocked,
|
||||
this.zkIdentityService.onUnlocked,
|
||||
this.connectionService.onUnlocked,
|
||||
);
|
||||
|
||||
this.handler.add(
|
||||
RPCInternalAction.LOCK,
|
||||
this.lockService.lock,
|
||||
this.zkIdentityService.lock,
|
||||
this.approvalService.lock,
|
||||
);
|
||||
|
||||
/**
|
||||
* Return status of background process
|
||||
* @returns {Object} status Background process status
|
||||
* @returns {boolean} status.isInitialized has background process been initialized
|
||||
* @returns {boolean} status.isUnlocked is background process unlocked
|
||||
*/
|
||||
this.handler.add(RPCInternalAction.GET_STATUS, this.lockService.getStatus);
|
||||
|
||||
// requests
|
||||
// Requests
|
||||
this.handler.add(RPCInternalAction.GET_PENDING_REQUESTS, this.requestManager.getRequests);
|
||||
this.handler.add(RPCInternalAction.FINALIZE_REQUEST, this.requestManager.finalizeRequest);
|
||||
|
||||
// lock
|
||||
// Lock
|
||||
this.handler.add(RPCInternalAction.SETUP_PASSWORD, this.lockService.setupPassword);
|
||||
this.handler.add(RPCInternalAction.RESET_PASSWORD, this.lockService.resetPassword);
|
||||
this.handler.add(RPCInternalAction.CHECK_PASSWORD, this.lockService.ensure, this.lockService.checkPassword);
|
||||
|
||||
// Identities
|
||||
this.handler.add(RPCInternalAction.GET_IDENTITIES, this.lockService.ensure, this.zkIdentityService.getIdentities);
|
||||
this.handler.add(
|
||||
RPCInternalAction.GET_CONNECTED_IDENTITY_DATA,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.getConnectedIdentityData,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCInternalAction.GET_CONNECTED_IDENTITY_COMMITMENT,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.getConnectedIdentityCommitment,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCInternalAction.CONNECT_IDENTITY,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.connectIdentity,
|
||||
);
|
||||
this.handler.add(RPCInternalAction.IMPORT_IDENTITY, this.lockService.ensure, this.zkIdentityService.import);
|
||||
this.handler.add(
|
||||
RPCInternalAction.CONNECT_IDENTITY_REQUEST,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.connectIdentityRequest,
|
||||
);
|
||||
// Connections
|
||||
this.handler.add(RPCInternalAction.CONNECT, this.lockService.ensure, this.connectionService.connect);
|
||||
this.handler.add(
|
||||
RPCInternalAction.REVEAL_CONNECTED_IDENTITY_COMMITMENT,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.revealConnectedIdentityCommitment,
|
||||
this.connectionService.revealConnectedIdentityCommitment,
|
||||
);
|
||||
this.handler.add(RPCInternalAction.GET_CONNECTIONS, this.lockService.ensure, this.connectionService.getConnections);
|
||||
|
||||
// Identities
|
||||
this.handler.add(RPCInternalAction.GET_IDENTITIES, this.lockService.ensure, this.zkIdentityService.getIdentities);
|
||||
this.handler.add(RPCInternalAction.IMPORT_IDENTITY, this.lockService.ensure, this.zkIdentityService.import);
|
||||
this.handler.add(
|
||||
RPCInternalAction.SET_IDENTITY_NAME,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.setIdentityName,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCInternalAction.SET_IDENTITY_HOST,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.setIdentityHost,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCInternalAction.CREATE_IDENTITY_REQUEST,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.createIdentityRequest,
|
||||
);
|
||||
this.handler.add(RPCInternalAction.CREATE_IDENTITY, this.lockService.ensure, this.zkIdentityService.createIdentity);
|
||||
this.handler.add(RPCInternalAction.DELETE_IDENTITY, this.lockService.ensure, this.zkIdentityService.deleteIdentity);
|
||||
this.handler.add(
|
||||
RPCInternalAction.DELETE_IDENTITY,
|
||||
this.lockService.ensure,
|
||||
this.zkIdentityService.deleteIdentity,
|
||||
this.connectionService.disconnect,
|
||||
);
|
||||
this.handler.add(
|
||||
RPCInternalAction.DELETE_ALL_IDENTITIES,
|
||||
this.lockService.ensure,
|
||||
this.connectionService.clear,
|
||||
this.zkIdentityService.deleteAllIdentities,
|
||||
);
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ describe("background/services/approval", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should not approve duplicated urlOrigin after unlock", async () => {
|
||||
test("should not approve duplicated url origin after unlock", async () => {
|
||||
await approvalService.unlock();
|
||||
await approvalService.add({ urlOrigin: mockDefaultHosts[0], canSkipApprove: true });
|
||||
const hosts = approvalService.getAllowedHosts();
|
||||
@@ -176,7 +176,7 @@ describe("background/services/approval", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should remove approved urlOrigin properly", async () => {
|
||||
test("should remove approved url origin properly", async () => {
|
||||
await approvalService.unlock();
|
||||
await approvalService.remove({ urlOrigin: mockDefaultHosts[0] });
|
||||
const hosts = approvalService.getAllowedHosts();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { EventName } from "@cryptkeeperzk/providers";
|
||||
import { ZkIdentitySemaphore } from "@cryptkeeperzk/zk";
|
||||
import { bigintToHex } from "bigint-conversion";
|
||||
import omit from "lodash/omit";
|
||||
import pick from "lodash/pick";
|
||||
|
||||
@@ -6,17 +8,26 @@ import BrowserUtils from "@src/background/controllers/browserUtils";
|
||||
import { type BackupData, IBackupable } from "@src/background/services/backup";
|
||||
import BaseService from "@src/background/services/base";
|
||||
import CryptoService, { ECryptMode } from "@src/background/services/crypto";
|
||||
import HistoryService from "@src/background/services/history";
|
||||
import SimpleStorage from "@src/background/services/storage";
|
||||
import ZkIdentityService from "@src/background/services/zkIdentity";
|
||||
import { Paths } from "@src/constants";
|
||||
import { OperationType } from "@src/types";
|
||||
|
||||
import type { IIdenityConnection, IZkMetadata, IConnectArgs, IIdentityMetadata } from "@cryptkeeperzk/types";
|
||||
import type {
|
||||
IIdentityConnection,
|
||||
IZkMetadata,
|
||||
IConnectArgs,
|
||||
IRevealConnectedIdentityCommitmentArgs,
|
||||
IDeleteIdentityArgs,
|
||||
} from "@cryptkeeperzk/types";
|
||||
|
||||
const CONNECTIONS_STORAGE_KEY = "@@CONNECTIONS@@";
|
||||
|
||||
export default class ConnectionService extends BaseService implements IBackupable {
|
||||
private static INSTANCE?: ConnectionService;
|
||||
|
||||
private connections: Map<string, IIdenityConnection>;
|
||||
private connections: Map<string, IIdentityConnection>;
|
||||
|
||||
private readonly connectionsStorage: SimpleStorage;
|
||||
|
||||
@@ -26,6 +37,8 @@ export default class ConnectionService extends BaseService implements IBackupabl
|
||||
|
||||
private readonly zkIdenityService: ZkIdentityService;
|
||||
|
||||
private readonly historyService: HistoryService;
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.connections = new Map();
|
||||
@@ -33,6 +46,7 @@ export default class ConnectionService extends BaseService implements IBackupabl
|
||||
this.cryptoService = CryptoService.getInstance();
|
||||
this.browserController = BrowserUtils.getInstance();
|
||||
this.zkIdenityService = ZkIdentityService.getInstance();
|
||||
this.historyService = HistoryService.getInstance();
|
||||
}
|
||||
|
||||
static getInstance = (): ConnectionService => {
|
||||
@@ -55,7 +69,7 @@ export default class ConnectionService extends BaseService implements IBackupabl
|
||||
|
||||
if (encryped) {
|
||||
const decrypted = this.cryptoService.decrypt(encryped, { mode: ECryptMode.MNEMONIC });
|
||||
this.connections = new Map(JSON.parse(decrypted) as Iterable<[string, IIdenityConnection]>);
|
||||
this.connections = new Map(JSON.parse(decrypted) as Iterable<[string, IIdentityConnection]>);
|
||||
}
|
||||
|
||||
this.isUnlocked = true;
|
||||
@@ -75,25 +89,51 @@ export default class ConnectionService extends BaseService implements IBackupabl
|
||||
throw new Error("CryptKeeper: identity is not found");
|
||||
}
|
||||
|
||||
const connection = this.getIdentityConnection(commitment, identity.metadata);
|
||||
const connection = { ...pick(identity.metadata, ["name"]), commitment, urlOrigin };
|
||||
this.connections.set(urlOrigin, connection);
|
||||
await this.save();
|
||||
|
||||
await this.browserController.pushEvent(
|
||||
{ type: EventName.CONNECT_IDENTITY, payload: omit(connection, ["commitment"]) },
|
||||
{ type: EventName.CONNECT, payload: omit(connection, ["commitment"]) },
|
||||
{ urlOrigin },
|
||||
);
|
||||
};
|
||||
|
||||
disconnect = async (payload: unknown, { urlOrigin }: IZkMetadata): Promise<void> => {
|
||||
await this.isOriginConnected(payload, { urlOrigin });
|
||||
this.connections.delete(urlOrigin!);
|
||||
disconnect = async (
|
||||
payload: Partial<IDeleteIdentityArgs> | undefined,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<boolean> => {
|
||||
const connection =
|
||||
this.connections.get(urlOrigin!) ||
|
||||
[...this.connections.values()].find(({ commitment }) => commitment === payload?.identityCommitment);
|
||||
const connectionOrigin = connection?.urlOrigin;
|
||||
|
||||
if (!connectionOrigin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.isOriginConnected(payload, { urlOrigin: connectionOrigin });
|
||||
|
||||
this.connections.delete(connectionOrigin);
|
||||
await this.save();
|
||||
|
||||
await this.browserController.pushEvent(
|
||||
{ type: EventName.DISCONNECT_IDENTITY, payload: { urlOrigin } },
|
||||
{ urlOrigin },
|
||||
{ type: EventName.DISCONNECT, payload: { urlOrigin: connectionOrigin } },
|
||||
{ urlOrigin: connectionOrigin },
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
clear = async (): Promise<void> => {
|
||||
await Promise.all(
|
||||
[...this.connections.keys()].map((urlOrigin) =>
|
||||
this.browserController.pushEvent({ type: EventName.DISCONNECT, payload: { urlOrigin } }, { urlOrigin }),
|
||||
),
|
||||
);
|
||||
|
||||
this.connections.clear();
|
||||
await this.connectionsStorage.clear();
|
||||
};
|
||||
|
||||
isOriginConnected = (payload: unknown, { urlOrigin }: IZkMetadata): unknown => {
|
||||
@@ -110,7 +150,50 @@ export default class ConnectionService extends BaseService implements IBackupabl
|
||||
return payload;
|
||||
};
|
||||
|
||||
getConnections = (): Map<string, IIdenityConnection> => this.connections;
|
||||
getConnections = (): Record<string, IIdentityConnection> => Object.fromEntries(this.connections.entries());
|
||||
|
||||
connectRequest = async (_: unknown, { urlOrigin }: IZkMetadata): Promise<void> => {
|
||||
await this.browserController.openPopup({ params: { redirect: Paths.CONNECT_IDENTITY, urlOrigin } });
|
||||
};
|
||||
|
||||
getConnectedIdentity = (urlOrigin: string): ZkIdentitySemaphore | undefined => {
|
||||
const connection = this.connections.get(urlOrigin);
|
||||
|
||||
if (!connection) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.zkIdenityService.getIdentity(connection.commitment);
|
||||
};
|
||||
|
||||
revealConnectedIdentityCommitmentRequest = async (_: unknown, { urlOrigin }: IZkMetadata): Promise<void> => {
|
||||
await this.browserController.openPopup({
|
||||
params: { redirect: Paths.REVEAL_IDENTITY_COMMITMENT, urlOrigin },
|
||||
});
|
||||
};
|
||||
|
||||
revealConnectedIdentityCommitment = async (
|
||||
{ url }: IRevealConnectedIdentityCommitmentArgs,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<void> => {
|
||||
const appOrigin = url || urlOrigin;
|
||||
const connectedIdentity = this.getConnectedIdentity(appOrigin!);
|
||||
|
||||
if (!connectedIdentity) {
|
||||
throw new Error("CryptKeeper: No connected identity found");
|
||||
}
|
||||
|
||||
const commitment = bigintToHex(connectedIdentity.genIdentityCommitment());
|
||||
|
||||
await this.browserController.pushEvent(
|
||||
{ type: EventName.REVEAL_COMMITMENT, payload: { commitment } },
|
||||
{ urlOrigin: appOrigin },
|
||||
);
|
||||
|
||||
await this.historyService.trackOperation(OperationType.REVEAL_IDENTITY_COMMITMENT, {
|
||||
identity: { commitment, metadata: connectedIdentity.metadata },
|
||||
});
|
||||
};
|
||||
|
||||
downloadStorage = (): Promise<string | null> => this.connectionsStorage.get<string>();
|
||||
|
||||
@@ -149,19 +232,12 @@ export default class ConnectionService extends BaseService implements IBackupabl
|
||||
const backup = this.cryptoService.decrypt(encryptedBackup, { secret: backupPassword });
|
||||
await this.unlock();
|
||||
|
||||
const backupAllowedHosts = new Map(JSON.parse(backup) as Iterable<[string, IIdenityConnection]>);
|
||||
const backupAllowedHosts = new Map(JSON.parse(backup) as Iterable<[string, IIdentityConnection]>);
|
||||
this.connections = new Map([...this.connections, ...backupAllowedHosts]);
|
||||
|
||||
await this.save();
|
||||
};
|
||||
|
||||
private getIdentityConnection(commitment: string, metadata: IIdentityMetadata): IIdenityConnection {
|
||||
return {
|
||||
...pick(metadata, ["name", "urlOrigin"]),
|
||||
commitment,
|
||||
};
|
||||
}
|
||||
|
||||
private async save(): Promise<void> {
|
||||
const serialized = JSON.stringify(Array.from(this.connections.entries()));
|
||||
const newData = this.cryptoService.encrypt(serialized, { mode: ECryptMode.MNEMONIC });
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { EventName } from "@cryptkeeperzk/providers";
|
||||
import browser from "webextension-polyfill";
|
||||
|
||||
import SimpleStorage from "@src/background/services/storage";
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
|
||||
import type { IConnectArgs, IZkMetadata } from "@cryptkeeperzk/types";
|
||||
|
||||
import ConnectionService from "..";
|
||||
|
||||
const mockDefaultHosts = [mockDefaultIdentity.metadata.urlOrigin];
|
||||
const mockSerializedConnections = JSON.stringify([
|
||||
[
|
||||
mockDefaultHosts[0],
|
||||
"http://localhost:3000",
|
||||
{
|
||||
name: mockDefaultIdentity.metadata.name,
|
||||
urlOrigin: mockDefaultHosts[0],
|
||||
commitment: mockDefaultIdentity.commitment,
|
||||
urlOrigin: "http://localhost:3000",
|
||||
},
|
||||
],
|
||||
]);
|
||||
@@ -44,11 +47,19 @@ interface MockStorage {
|
||||
describe("background/services/connection", () => {
|
||||
const connectionService = ConnectionService.getInstance();
|
||||
|
||||
const defaultMetadata: IZkMetadata = { urlOrigin: mockDefaultHosts[0] };
|
||||
const defaultMetadata: IZkMetadata = { urlOrigin: "http://localhost:3000" };
|
||||
|
||||
const defaultTabs = [{ id: 1, url: defaultMetadata.urlOrigin }, { id: 2, url: defaultMetadata.urlOrigin }, { id: 3 }];
|
||||
|
||||
const defaultPopupTab = { id: 3, active: true, highlighted: true };
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.NODE_ENV = "test";
|
||||
|
||||
(browser.tabs.create as jest.Mock).mockResolvedValue(defaultPopupTab);
|
||||
|
||||
(browser.tabs.query as jest.Mock).mockResolvedValue(defaultTabs);
|
||||
|
||||
(SimpleStorage as jest.Mock).mock.instances.forEach((instance: MockStorage) => {
|
||||
instance.get.mockResolvedValue(mockSerializedConnections);
|
||||
instance.set.mockResolvedValue(undefined);
|
||||
@@ -65,6 +76,14 @@ describe("background/services/connection", () => {
|
||||
instance.set.mockClear();
|
||||
instance.clear.mockClear();
|
||||
});
|
||||
|
||||
(browser.tabs.create as jest.Mock).mockClear();
|
||||
|
||||
(browser.tabs.query as jest.Mock).mockClear();
|
||||
|
||||
(browser.tabs.sendMessage as jest.Mock).mockClear();
|
||||
|
||||
(browser.windows.create as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
describe("unlock", () => {
|
||||
@@ -112,15 +131,33 @@ describe("background/services/connection", () => {
|
||||
await connectionService.unlock();
|
||||
});
|
||||
|
||||
test("should connect identity properly", async () => {
|
||||
test("should request connection properly", async () => {
|
||||
await connectionService.connectRequest({}, defaultMetadata);
|
||||
|
||||
const defaultOptions = {
|
||||
tabId: defaultPopupTab.id,
|
||||
type: "popup",
|
||||
focused: true,
|
||||
width: 385,
|
||||
height: 610,
|
||||
};
|
||||
|
||||
expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });
|
||||
expect(browser.windows.create).toBeCalledTimes(1);
|
||||
expect(browser.windows.create).toBeCalledWith(defaultOptions);
|
||||
});
|
||||
|
||||
test("should connect properly", async () => {
|
||||
const [storage] = (SimpleStorage as jest.Mock).mock.instances as [MockStorage];
|
||||
|
||||
await connectionService.connect(defaultArgs, defaultMetadata);
|
||||
const connections = connectionService.getConnections();
|
||||
const identity = connectionService.getConnectedIdentity(defaultMetadata.urlOrigin!);
|
||||
|
||||
expect(storage.set).toBeCalledTimes(1);
|
||||
expect(storage.set).toBeCalledWith(mockSerializedConnections);
|
||||
expect(connections.size).toBe(1);
|
||||
expect(Object.entries(connections)).toHaveLength(1);
|
||||
expect(identity?.metadata.name).toBe(mockDefaultIdentity.metadata.name);
|
||||
});
|
||||
|
||||
test("should throw error if there is no url origin", async () => {
|
||||
@@ -141,26 +178,91 @@ describe("background/services/connection", () => {
|
||||
await connectionService.unlock();
|
||||
});
|
||||
|
||||
test("should disconnect identity properly", async () => {
|
||||
test("should disconnect properly", async () => {
|
||||
const [storage] = (SimpleStorage as jest.Mock).mock.instances as [MockStorage];
|
||||
|
||||
await connectionService.disconnect({}, defaultMetadata);
|
||||
const connections = connectionService.getConnections();
|
||||
const identity = connectionService.getConnectedIdentity(defaultMetadata.urlOrigin!);
|
||||
|
||||
expect(storage.set).toBeCalledTimes(1);
|
||||
expect(storage.set).toBeCalledWith(JSON.stringify([]));
|
||||
expect(connections.size).toBe(0);
|
||||
expect(Object.entries(connections)).toHaveLength(0);
|
||||
expect(identity).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should throw error if there is no url origin", async () => {
|
||||
await expect(connectionService.disconnect({}, { urlOrigin: "" })).rejects.toThrowError(
|
||||
"CryptKeeper: origin is not provided",
|
||||
test("should disconnect with commitment properly", async () => {
|
||||
const [storage] = (SimpleStorage as jest.Mock).mock.instances as [MockStorage];
|
||||
|
||||
await connectionService.disconnect(
|
||||
{ identityCommitment: mockDefaultConnection.commitment },
|
||||
{ urlOrigin: undefined },
|
||||
);
|
||||
const connections = connectionService.getConnections();
|
||||
const identity = connectionService.getConnectedIdentity(defaultMetadata.urlOrigin!);
|
||||
|
||||
expect(storage.set).toBeCalledTimes(1);
|
||||
expect(storage.set).toBeCalledWith(JSON.stringify([]));
|
||||
expect(Object.entries(connections)).toHaveLength(0);
|
||||
expect(identity).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should throw error if origin is not connected", async () => {
|
||||
await expect(connectionService.disconnect({}, { urlOrigin: "unknown" })).rejects.toThrowError(
|
||||
"CryptKeeper: origin is not connected",
|
||||
test("should return false if there is no url origin", async () => {
|
||||
await expect(connectionService.disconnect({}, { urlOrigin: "" })).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clear", () => {
|
||||
beforeEach(async () => {
|
||||
await connectionService.unlock();
|
||||
});
|
||||
|
||||
test("should clear storage properly", async () => {
|
||||
await connectionService.clear();
|
||||
const connections = connectionService.getConnections();
|
||||
|
||||
expect(Object.entries(connections)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reveal commitment", () => {
|
||||
beforeEach(async () => {
|
||||
await connectionService.unlock();
|
||||
});
|
||||
|
||||
test("should request reveal connected identity commitment", async () => {
|
||||
await connectionService.revealConnectedIdentityCommitmentRequest({}, defaultMetadata);
|
||||
|
||||
const defaultOptions = {
|
||||
tabId: defaultPopupTab.id,
|
||||
type: "popup",
|
||||
focused: true,
|
||||
width: 385,
|
||||
height: 610,
|
||||
};
|
||||
|
||||
expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });
|
||||
expect(browser.windows.create).toBeCalledWith(defaultOptions);
|
||||
});
|
||||
|
||||
test("should reveal connected identity commitment", async () => {
|
||||
await connectionService.revealConnectedIdentityCommitment({}, defaultMetadata);
|
||||
|
||||
expect(browser.tabs.query).toBeCalledWith({});
|
||||
expect(browser.tabs.sendMessage).toBeCalledTimes(2);
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(1, defaultTabs[0].id, {
|
||||
type: EventName.REVEAL_COMMITMENT,
|
||||
payload: { commitment: mockDefaultConnection.commitment },
|
||||
});
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(2, defaultTabs[1].id, {
|
||||
type: EventName.REVEAL_COMMITMENT,
|
||||
payload: { commitment: mockDefaultConnection.commitment },
|
||||
});
|
||||
});
|
||||
|
||||
test("should not reveal identity commitment if there is not connection", async () => {
|
||||
await expect(connectionService.revealConnectedIdentityCommitment({}, { urlOrigin: "unknown" })).rejects.toThrow(
|
||||
"CryptKeeper: No connected identity found",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -207,11 +309,11 @@ describe("background/services/connection", () => {
|
||||
});
|
||||
|
||||
test("should upload encrypted connections", async () => {
|
||||
const [storage] = (SimpleStorage as jest.Mock).mock.instances as [MockStorage];
|
||||
|
||||
await connectionService.uploadEncryptedStorage("encrypted", "password");
|
||||
|
||||
(SimpleStorage as jest.Mock).mock.instances.forEach((instance: MockStorage) => {
|
||||
expect(instance.set).toBeCalledTimes(1);
|
||||
});
|
||||
expect(storage.set).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("should not upload encrypted connections if there is no data", async () => {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { EventName } from "@cryptkeeperzk/providers";
|
||||
import { bigintToHex } from "bigint-conversion";
|
||||
import browser from "webextension-polyfill";
|
||||
|
||||
import BrowserUtils from "@src/background/controllers/browserUtils";
|
||||
import { BandadaService } from "@src/background/services/bandada";
|
||||
import ConnectionService from "@src/background/services/connection";
|
||||
import HistoryService from "@src/background/services/history";
|
||||
import NotificationService from "@src/background/services/notification";
|
||||
import ZkIdentityService from "@src/background/services/zkIdentity";
|
||||
import { Paths } from "@src/constants";
|
||||
import { OperationType } from "@src/types";
|
||||
|
||||
@@ -15,6 +16,7 @@ import type {
|
||||
IIdentityData,
|
||||
IJoinGroupMemberArgs,
|
||||
IMerkleProof,
|
||||
IZkMetadata,
|
||||
} from "@cryptkeeperzk/types";
|
||||
|
||||
export default class GroupService {
|
||||
@@ -22,7 +24,7 @@ export default class GroupService {
|
||||
|
||||
private bandadaSevice: BandadaService;
|
||||
|
||||
private zkIdentityService: ZkIdentityService;
|
||||
private connectionService: ConnectionService;
|
||||
|
||||
private historyService: HistoryService;
|
||||
|
||||
@@ -32,7 +34,7 @@ export default class GroupService {
|
||||
|
||||
private constructor() {
|
||||
this.bandadaSevice = BandadaService.getInstance();
|
||||
this.zkIdentityService = ZkIdentityService.getInstance();
|
||||
this.connectionService = ConnectionService.getInstance();
|
||||
this.historyService = HistoryService.getInstance();
|
||||
this.notificationService = NotificationService.getInstance();
|
||||
this.browserController = BrowserUtils.getInstance();
|
||||
@@ -46,19 +48,26 @@ export default class GroupService {
|
||||
return GroupService.INSTANCE;
|
||||
}
|
||||
|
||||
joinGroupRequest = async ({ groupId, apiKey, inviteCode }: IJoinGroupMemberArgs): Promise<void> => {
|
||||
joinGroupRequest = async (
|
||||
{ groupId, apiKey, inviteCode }: IJoinGroupMemberArgs,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<void> => {
|
||||
await this.browserController.openPopup({
|
||||
params: {
|
||||
redirect: Paths.JOIN_GROUP,
|
||||
groupId,
|
||||
apiKey: apiKey ?? "",
|
||||
inviteCode: inviteCode ?? "",
|
||||
apiKey,
|
||||
inviteCode,
|
||||
urlOrigin,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
joinGroup = async ({ groupId, apiKey, inviteCode }: IJoinGroupMemberArgs): Promise<boolean> => {
|
||||
const identity = await this.getConnectedIdentity();
|
||||
joinGroup = async (
|
||||
{ groupId, apiKey, inviteCode }: IJoinGroupMemberArgs,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<boolean> => {
|
||||
const identity = this.getConnectedIdentity(urlOrigin!);
|
||||
|
||||
const result = await this.bandadaSevice.addMember({ groupId, apiKey, inviteCode, identity });
|
||||
|
||||
@@ -72,25 +81,29 @@ export default class GroupService {
|
||||
},
|
||||
});
|
||||
|
||||
await this.browserController.pushEvent(
|
||||
{ type: EventName.JOIN_GROUP, payload: { groupId } },
|
||||
{ urlOrigin: identity.metadata.urlOrigin! },
|
||||
);
|
||||
await this.browserController.pushEvent({ type: EventName.JOIN_GROUP, payload: { groupId } }, { urlOrigin });
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
generateGroupMerkleProofRequest = async ({ groupId }: IGenerateGroupMerkleProofArgs): Promise<void> => {
|
||||
generateGroupMerkleProofRequest = async (
|
||||
{ groupId }: IGenerateGroupMerkleProofArgs,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<void> => {
|
||||
await this.browserController.openPopup({
|
||||
params: {
|
||||
redirect: Paths.GROUP_MERKLE_PROOF,
|
||||
groupId,
|
||||
urlOrigin,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
generateGroupMerkleProof = async ({ groupId }: IGenerateGroupMerkleProofArgs): Promise<IMerkleProof> => {
|
||||
const identity = await this.getConnectedIdentity();
|
||||
generateGroupMerkleProof = async (
|
||||
{ groupId }: IGenerateGroupMerkleProofArgs,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<IMerkleProof> => {
|
||||
const identity = this.getConnectedIdentity(urlOrigin!);
|
||||
|
||||
const merkleProof = await this.bandadaSevice.generateMerkleProof({ groupId, identity });
|
||||
|
||||
@@ -105,31 +118,31 @@ export default class GroupService {
|
||||
|
||||
await this.browserController.pushEvent(
|
||||
{ type: EventName.GROUP_MERKLE_PROOF, payload: { merkleProof } },
|
||||
{ urlOrigin: identity.metadata.urlOrigin! },
|
||||
{ urlOrigin },
|
||||
);
|
||||
|
||||
return merkleProof;
|
||||
};
|
||||
|
||||
checkGroupMembership = async ({ groupId }: ICheckGroupMembershipArgs): Promise<boolean> => {
|
||||
const identity = await this.getConnectedIdentity();
|
||||
checkGroupMembership = async (
|
||||
{ groupId }: ICheckGroupMembershipArgs,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<boolean> => {
|
||||
const identity = this.getConnectedIdentity(urlOrigin!);
|
||||
|
||||
return this.bandadaSevice.checkGroupMembership({ groupId, identity });
|
||||
};
|
||||
|
||||
private getConnectedIdentity = async (): Promise<IIdentityData> => {
|
||||
const [commitment, identity] = await Promise.all([
|
||||
this.zkIdentityService.getConnectedIdentityCommitment(),
|
||||
this.zkIdentityService.getConnectedIdentity(),
|
||||
]);
|
||||
private getConnectedIdentity = (urlOrigin: string): IIdentityData => {
|
||||
const connectedIdenity = this.connectionService.getConnectedIdentity(urlOrigin);
|
||||
|
||||
if (!commitment || !identity) {
|
||||
if (!connectedIdenity) {
|
||||
throw new Error("No connected identity found");
|
||||
}
|
||||
|
||||
return {
|
||||
commitment,
|
||||
metadata: identity.metadata,
|
||||
commitment: bigintToHex(connectedIdenity.genIdentityCommitment()),
|
||||
metadata: connectedIdenity.metadata,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import browser from "webextension-polyfill";
|
||||
|
||||
import { defaultMerkleProof, mockDefaultIdentity, mockDefaultIdentityCommitment } from "@src/config/mock/zk";
|
||||
import { defaultMerkleProof, mockDefaultConnection, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
|
||||
import type {
|
||||
ICheckGroupMembershipArgs,
|
||||
IGenerateGroupMerkleProofArgs,
|
||||
IIdentityData,
|
||||
IJoinGroupMemberArgs,
|
||||
IZkMetadata,
|
||||
} from "@cryptkeeperzk/types";
|
||||
|
||||
import GroupService from "..";
|
||||
|
||||
const mockGetConnectedIdentityCommitment = jest.fn(() => Promise.resolve(mockDefaultIdentityCommitment));
|
||||
const mockGetConnectedIdentity = jest.fn(() => Promise.resolve(mockDefaultIdentity));
|
||||
const mockGetConnectedIdentity = jest.fn(() => mockDefaultIdentity);
|
||||
|
||||
jest.mock("@src/background/services/bandada", (): unknown => ({
|
||||
BandadaService: {
|
||||
@@ -24,9 +24,8 @@ jest.mock("@src/background/services/bandada", (): unknown => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("@src/background/services/zkIdentity", (): unknown => ({
|
||||
jest.mock("@src/background/services/connection", (): unknown => ({
|
||||
getInstance: jest.fn(() => ({
|
||||
getConnectedIdentityCommitment: mockGetConnectedIdentityCommitment,
|
||||
getConnectedIdentity: mockGetConnectedIdentity,
|
||||
})),
|
||||
}));
|
||||
@@ -45,10 +44,12 @@ jest.mock("@src/background/services/notification", (): unknown => ({
|
||||
}));
|
||||
|
||||
describe("background/services/group/GroupService", () => {
|
||||
beforeEach(() => {
|
||||
mockGetConnectedIdentityCommitment.mockResolvedValue(mockDefaultIdentityCommitment);
|
||||
const metadata: IZkMetadata = {
|
||||
urlOrigin: mockDefaultConnection.urlOrigin,
|
||||
};
|
||||
|
||||
mockGetConnectedIdentity.mockResolvedValue(mockDefaultIdentity);
|
||||
beforeEach(() => {
|
||||
mockGetConnectedIdentity.mockReturnValue(mockDefaultIdentity);
|
||||
|
||||
(browser.tabs.create as jest.Mock).mockResolvedValue({});
|
||||
|
||||
@@ -68,23 +69,23 @@ describe("background/services/group/GroupService", () => {
|
||||
test("should request group joining properly", async () => {
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
await expect(service.joinGroupRequest(defaultArgs)).resolves.toBeUndefined();
|
||||
await expect(service.joinGroupRequest({ groupId: defaultArgs.groupId })).resolves.toBeUndefined();
|
||||
await expect(service.joinGroupRequest(defaultArgs, metadata)).resolves.toBeUndefined();
|
||||
await expect(service.joinGroupRequest({ groupId: defaultArgs.groupId }, metadata)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("should join group properly", async () => {
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
const result = await service.joinGroup(defaultArgs);
|
||||
const result = await service.joinGroup(defaultArgs, metadata);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("should throw error if there is no connected identity", async () => {
|
||||
mockGetConnectedIdentityCommitment.mockResolvedValue("");
|
||||
mockGetConnectedIdentity.mockReturnValue(undefined as unknown as IIdentityData);
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
await expect(service.joinGroup(defaultArgs)).rejects.toThrowError("No connected identity found");
|
||||
await expect(service.joinGroup(defaultArgs, metadata)).rejects.toThrowError("No connected identity found");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,23 +97,24 @@ describe("background/services/group/GroupService", () => {
|
||||
test("should request generate group merkle proof properly", async () => {
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
await expect(service.generateGroupMerkleProofRequest(defaultArgs)).resolves.toBeUndefined();
|
||||
await expect(service.generateGroupMerkleProofRequest(defaultArgs, metadata)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test("should generate proof properly ", async () => {
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
const result = await service.generateGroupMerkleProof(defaultArgs);
|
||||
const result = await service.generateGroupMerkleProof(defaultArgs, metadata);
|
||||
|
||||
expect(result).toStrictEqual(defaultMerkleProof);
|
||||
});
|
||||
|
||||
test("should throw error if there is no connected identity", async () => {
|
||||
mockGetConnectedIdentityCommitment.mockResolvedValue("");
|
||||
mockGetConnectedIdentity.mockResolvedValue(undefined as unknown as IIdentityData);
|
||||
mockGetConnectedIdentity.mockReturnValue(undefined as unknown as IIdentityData);
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
await expect(service.generateGroupMerkleProof(defaultArgs)).rejects.toThrowError("No connected identity found");
|
||||
await expect(service.generateGroupMerkleProof(defaultArgs, metadata)).rejects.toThrowError(
|
||||
"No connected identity found",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,17 +126,18 @@ describe("background/services/group/GroupService", () => {
|
||||
test("should check membership properly ", async () => {
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
const result = await service.checkGroupMembership(defaultArgs);
|
||||
const result = await service.checkGroupMembership(defaultArgs, metadata);
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("should throw error if there is no connected identity", async () => {
|
||||
mockGetConnectedIdentityCommitment.mockResolvedValue("");
|
||||
mockGetConnectedIdentity.mockResolvedValue(undefined as unknown as IIdentityData);
|
||||
mockGetConnectedIdentity.mockReturnValue(undefined as unknown as IIdentityData);
|
||||
const service = GroupService.getInstance();
|
||||
|
||||
await expect(service.checkGroupMembership(defaultArgs)).rejects.toThrowError("No connected identity found");
|
||||
await expect(service.checkGroupMembership(defaultArgs, metadata)).rejects.toThrowError(
|
||||
"No connected identity found",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,17 +4,39 @@ import {
|
||||
type ConnectedIdentityMetadata,
|
||||
type IHostPermission,
|
||||
type IConnectionOptions,
|
||||
type IConnectionData,
|
||||
} from "@cryptkeeperzk/types";
|
||||
import pick from "lodash/pick";
|
||||
|
||||
import { InjectorHandler } from "./InjectorHandler";
|
||||
import BrowserUtils from "@src/background/controllers/browserUtils";
|
||||
import RequestManager from "@src/background/controllers/requestManager";
|
||||
import ApprovalService from "@src/background/services/approval";
|
||||
import ConnectionService from "@src/background/services/connection";
|
||||
import LockerService from "@src/background/services/lock";
|
||||
import ZkIdentityService from "@src/background/services/zkIdentity";
|
||||
|
||||
export class InjectorService {
|
||||
private static INSTANCE?: InjectorService;
|
||||
|
||||
private injectorHandler: InjectorHandler;
|
||||
private readonly lockerService: LockerService;
|
||||
|
||||
private constructor() {
|
||||
this.injectorHandler = new InjectorHandler();
|
||||
private readonly approvalService: ApprovalService;
|
||||
|
||||
private readonly browserService: BrowserUtils;
|
||||
|
||||
private readonly requestManager: RequestManager;
|
||||
|
||||
private readonly connectionService: ConnectionService;
|
||||
|
||||
private readonly zkIdentityService: ZkIdentityService;
|
||||
|
||||
constructor() {
|
||||
this.approvalService = ApprovalService.getInstance();
|
||||
this.browserService = BrowserUtils.getInstance();
|
||||
this.lockerService = LockerService.getInstance();
|
||||
this.requestManager = RequestManager.getInstance();
|
||||
this.connectionService = ConnectionService.getInstance();
|
||||
this.zkIdentityService = ZkIdentityService.getInstance();
|
||||
}
|
||||
|
||||
static getInstance(): InjectorService {
|
||||
@@ -25,49 +47,72 @@ export class InjectorService {
|
||||
return InjectorService.INSTANCE;
|
||||
}
|
||||
|
||||
isConnected = async (payload: unknown, { urlOrigin }: IZkMetadata): Promise<unknown> => {
|
||||
await this.injectorHandler.getConnectedIdentityMetadata({}, { urlOrigin });
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
getConnectedIdentityMetadata = async (
|
||||
_: unknown,
|
||||
{ urlOrigin }: IZkMetadata,
|
||||
): Promise<ConnectedIdentityMetadata | undefined> => {
|
||||
const { isApproved } = this.injectorHandler.getConnectionApprovalData({ urlOrigin });
|
||||
|
||||
if (!isApproved) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.injectorHandler.getConnectedIdentityMetadata({}, { urlOrigin });
|
||||
};
|
||||
|
||||
connect = async ({ isChangeIdentity }: IConnectionOptions, { urlOrigin }: IZkMetadata): Promise<void> => {
|
||||
const { isApproved, canSkipApprove } = await this.injectorHandler.checkApproval({ urlOrigin });
|
||||
await this.checkUnlockStatus();
|
||||
|
||||
try {
|
||||
if (isApproved) {
|
||||
await this.injectorHandler.getApprovalService().add({ urlOrigin: urlOrigin!, canSkipApprove });
|
||||
} else {
|
||||
const hostPermission = (await this.injectorHandler.newRequest(PendingRequestType.APPROVE, {
|
||||
urlOrigin,
|
||||
})) as IHostPermission;
|
||||
const { isApproved, canSkipApprove, isConnected } = this.getConnectionData({ urlOrigin });
|
||||
|
||||
await this.injectorHandler.getApprovalService().add({
|
||||
urlOrigin: hostPermission.urlOrigin,
|
||||
canSkipApprove: hostPermission.canSkipApprove,
|
||||
if (!isApproved) {
|
||||
const { canSkipApprove: skipApprove } = await this.newRequest<IHostPermission>(PendingRequestType.APPROVE, {
|
||||
urlOrigin,
|
||||
});
|
||||
await this.approvalService.add({ urlOrigin: urlOrigin!, canSkipApprove: skipApprove });
|
||||
} else {
|
||||
await this.approvalService.add({ urlOrigin: urlOrigin!, canSkipApprove });
|
||||
}
|
||||
|
||||
const connectedIdentity = await this.injectorHandler.getZkIdentityService().getConnectedIdentity();
|
||||
|
||||
if (connectedIdentity?.metadata.urlOrigin !== urlOrigin || isChangeIdentity) {
|
||||
await this.injectorHandler.getZkIdentityService().connectIdentityRequest({ urlOrigin: urlOrigin! });
|
||||
if (!isConnected || isChangeIdentity) {
|
||||
await this.connectionService.connectRequest({}, { urlOrigin: urlOrigin! });
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`CryptKeeper: error in the connect request, ${(error as Error).message}`);
|
||||
}
|
||||
};
|
||||
|
||||
getConnectedIdentityMetadata = (_: unknown, { urlOrigin }: IZkMetadata): ConnectedIdentityMetadata | undefined => {
|
||||
const { isApproved, isConnected } = this.getConnectionData({ urlOrigin });
|
||||
|
||||
if (!isApproved || !isConnected) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const connectedIdentity = this.connectionService.getConnectedIdentity(urlOrigin!);
|
||||
return pick(connectedIdentity?.metadata, ["name"]) as ConnectedIdentityMetadata | undefined;
|
||||
};
|
||||
|
||||
private newRequest = async <T>(type: PendingRequestType, payload: unknown): Promise<T> => {
|
||||
const response = await this.requestManager.newRequest(type, payload);
|
||||
await this.browserService.closePopup();
|
||||
|
||||
return response as T;
|
||||
};
|
||||
|
||||
private getConnectionData = ({ urlOrigin }: IZkMetadata): IConnectionData => {
|
||||
if (!urlOrigin) {
|
||||
throw new Error("CryptKeeper: Origin is not set");
|
||||
}
|
||||
|
||||
const isApproved = this.approvalService.isApproved(urlOrigin);
|
||||
const canSkipApprove = this.approvalService.canSkipApprove(urlOrigin);
|
||||
const identity = this.connectionService.getConnectedIdentity(urlOrigin);
|
||||
|
||||
return { isApproved, isConnected: Boolean(identity), canSkipApprove };
|
||||
};
|
||||
|
||||
private checkUnlockStatus = async () => {
|
||||
const { isUnlocked, isInitialized } = await this.lockerService.getStatus();
|
||||
|
||||
if (!isUnlocked) {
|
||||
await this.browserService.openPopup();
|
||||
await this.lockerService.awaitUnlock();
|
||||
|
||||
if (isInitialized) {
|
||||
await this.approvalService.awaitUnlock();
|
||||
await this.zkIdentityService.awaitUnlock();
|
||||
await this.connectionService.awaitUnlock();
|
||||
await this.browserService.closePopup();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import {
|
||||
PendingRequestType,
|
||||
type IConnectionApprovalData,
|
||||
type IZkMetadata,
|
||||
type ConnectedIdentityMetadata,
|
||||
} from "@cryptkeeperzk/types";
|
||||
|
||||
import BrowserUtils from "@src/background/controllers/browserUtils";
|
||||
import RequestManager from "@src/background/controllers/requestManager";
|
||||
import ApprovalService from "@src/background/services/approval";
|
||||
import LockerService from "@src/background/services/lock";
|
||||
import ZkIdentityService from "@src/background/services/zkIdentity";
|
||||
|
||||
export class InjectorHandler {
|
||||
private readonly lockerService: LockerService;
|
||||
|
||||
private readonly approvalService: ApprovalService;
|
||||
|
||||
private readonly browserService: BrowserUtils;
|
||||
|
||||
private readonly requestManager: RequestManager;
|
||||
|
||||
private readonly zkIdentityService: ZkIdentityService;
|
||||
|
||||
constructor() {
|
||||
this.approvalService = ApprovalService.getInstance();
|
||||
this.browserService = BrowserUtils.getInstance();
|
||||
this.lockerService = LockerService.getInstance();
|
||||
this.requestManager = RequestManager.getInstance();
|
||||
this.zkIdentityService = ZkIdentityService.getInstance();
|
||||
}
|
||||
|
||||
getApprovalService = (): ApprovalService => this.approvalService;
|
||||
|
||||
getZkIdentityService = (): ZkIdentityService => this.zkIdentityService;
|
||||
|
||||
getConnectedIdentityMetadata = async (_: unknown, meta?: IZkMetadata): Promise<ConnectedIdentityMetadata> => {
|
||||
const connectedIdentityMetadata = await this.zkIdentityService.getConnectedIdentityData({}, meta);
|
||||
|
||||
if (!connectedIdentityMetadata) {
|
||||
throw new Error(`CryptKeeper: identity metadata is not found`);
|
||||
}
|
||||
|
||||
return connectedIdentityMetadata;
|
||||
};
|
||||
|
||||
newRequest = async (type: PendingRequestType, payload: unknown): Promise<unknown> => {
|
||||
const response = await this.requestManager.newRequest(type, payload);
|
||||
await this.browserService.closePopup();
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
checkApproval = async ({ urlOrigin }: IZkMetadata): Promise<IConnectionApprovalData> => {
|
||||
await this.checkUnlockStatus();
|
||||
const { isApproved, canSkipApprove } = this.getConnectionApprovalData({ urlOrigin });
|
||||
return { isApproved, canSkipApprove };
|
||||
};
|
||||
|
||||
getConnectionApprovalData = ({ urlOrigin }: IZkMetadata): IConnectionApprovalData => {
|
||||
if (!urlOrigin) {
|
||||
throw new Error("CryptKeeper: Origin is not set");
|
||||
}
|
||||
|
||||
const isApproved = this.approvalService.isApproved(urlOrigin);
|
||||
const canSkipApprove = this.approvalService.canSkipApprove(urlOrigin);
|
||||
|
||||
return { isApproved, canSkipApprove };
|
||||
};
|
||||
|
||||
private checkUnlockStatus = async () => {
|
||||
const { isUnlocked, isInitialized } = await this.lockerService.getStatus();
|
||||
|
||||
if (!isUnlocked) {
|
||||
await this.browserService.openPopup();
|
||||
await this.lockerService.awaitUnlock();
|
||||
|
||||
if (isInitialized) {
|
||||
await this.approvalService.awaitUnlock();
|
||||
await this.zkIdentityService.awaitUnlock();
|
||||
await this.browserService.closePopup();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,17 +2,16 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { PendingRequestType, type ConnectedIdentityMetadata, type IZkMetadata } from "@cryptkeeperzk/types";
|
||||
import { PendingRequestType, type ConnectedIdentityMetadata } from "@cryptkeeperzk/types";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import pushMessage from "@src/util/pushMessage";
|
||||
|
||||
import { InjectorService } from "..";
|
||||
|
||||
const mockDefaultUrlOrigin = "http://localhost:3000";
|
||||
const mockSerializedIdentity = "identity";
|
||||
const mockConnectedIdentity: ConnectedIdentityMetadata = {
|
||||
name: "Account 1",
|
||||
name: "Account #1",
|
||||
};
|
||||
const mockNewRequest = jest.fn((type: PendingRequestType, { urlOrigin }: { urlOrigin: string }): Promise<unknown> => {
|
||||
if (urlOrigin.includes("reject")) {
|
||||
@@ -27,17 +26,7 @@ const mockNewRequest = jest.fn((type: PendingRequestType, { urlOrigin }: { urlOr
|
||||
});
|
||||
|
||||
const mockGetConnectedIdentity = jest.fn();
|
||||
const mockGetConnectedIdentityData = jest.fn(
|
||||
(_: unknown, meta?: IZkMetadata): Promise<ConnectedIdentityMetadata | undefined> => {
|
||||
if (meta?.urlOrigin === mockDefaultUrlOrigin || meta?.urlOrigin === "new-urlOrigin") {
|
||||
return Promise.resolve({ ...mockConnectedIdentity, ...meta });
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
);
|
||||
const mockConnectRequest = jest.fn();
|
||||
const mockConnectIdentityRequest = jest.fn();
|
||||
const mockIsApproved = jest.fn(
|
||||
(urlOrigin) =>
|
||||
urlOrigin === mockDefaultUrlOrigin ||
|
||||
@@ -49,7 +38,6 @@ const mockIsApproved = jest.fn(
|
||||
const mockCanSkip = jest.fn((urlOrigin) => urlOrigin === mockDefaultUrlOrigin);
|
||||
const mockGetStatus = jest.fn(() => Promise.resolve({ isUnlocked: false, isInitialized: false }));
|
||||
const mockAwaitLockServiceUnlock = jest.fn();
|
||||
const mockAwaitZkIdentityServiceUnlock = jest.fn();
|
||||
const mockAwaitApprovalServiceUnlock = jest.fn();
|
||||
|
||||
Object.defineProperty(global, "chrome", {
|
||||
@@ -88,20 +76,24 @@ jest.mock("@src/background/services/approval", (): unknown => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock("@src/background/services/lock", (): unknown => ({
|
||||
jest.mock("@src/background/services/connection", (): unknown => ({
|
||||
getInstance: jest.fn(() => ({
|
||||
getStatus: mockGetStatus,
|
||||
awaitUnlock: mockAwaitLockServiceUnlock,
|
||||
getConnectedIdentity: mockGetConnectedIdentity,
|
||||
connectRequest: mockConnectRequest,
|
||||
awaitUnlock: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock("@src/background/services/zkIdentity", (): unknown => ({
|
||||
getInstance: jest.fn(() => ({
|
||||
getConnectedIdentity: mockGetConnectedIdentity,
|
||||
getConnectedIdentityData: mockGetConnectedIdentityData,
|
||||
connectRequest: mockConnectRequest,
|
||||
awaitUnlock: mockAwaitZkIdentityServiceUnlock,
|
||||
connectIdentityRequest: mockConnectIdentityRequest,
|
||||
awaitUnlock: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock("@src/background/services/lock", (): unknown => ({
|
||||
getInstance: jest.fn(() => ({
|
||||
getStatus: mockGetStatus,
|
||||
awaitUnlock: mockAwaitLockServiceUnlock,
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -117,9 +109,7 @@ describe("background/services/injector", () => {
|
||||
const defaultMetadata = { urlOrigin: mockDefaultUrlOrigin };
|
||||
|
||||
beforeEach(() => {
|
||||
(pushMessage as jest.Mock).mockReset();
|
||||
|
||||
mockGetConnectedIdentity.mockResolvedValue({ serialize: () => mockSerializedIdentity });
|
||||
mockGetConnectedIdentity.mockReturnValue({ ...mockDefaultIdentity, serialize: () => mockSerializedIdentity });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -127,96 +117,78 @@ describe("background/services/injector", () => {
|
||||
});
|
||||
|
||||
describe("get connected identity metadata", () => {
|
||||
test("should return connected metadata properly if all checks are passed", async () => {
|
||||
test("should return connected metadata properly if all checks are passed", () => {
|
||||
mockGetStatus.mockResolvedValueOnce({ isUnlocked: true, isInitialized: true });
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
const result = await service.getConnectedIdentityMetadata({}, { urlOrigin: mockDefaultUrlOrigin });
|
||||
expect(result).toStrictEqual({ ...mockConnectedIdentity, urlOrigin: mockDefaultUrlOrigin });
|
||||
const result = service.getConnectedIdentityMetadata({}, { urlOrigin: mockDefaultUrlOrigin });
|
||||
expect(result).toStrictEqual(mockConnectedIdentity);
|
||||
expect(mockIsApproved).toBeCalledTimes(1);
|
||||
expect(mockCanSkip).toBeCalledTimes(1);
|
||||
expect(mockAwaitLockServiceUnlock).toBeCalledTimes(0);
|
||||
expect(mockGetConnectedIdentityData).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("should throw error if there is no urlOrigin", async () => {
|
||||
test("should throw error if there is no url origin", () => {
|
||||
mockGetStatus.mockResolvedValueOnce({ isUnlocked: true, isInitialized: true });
|
||||
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
await expect(service.getConnectedIdentityMetadata({}, { urlOrigin: "" })).rejects.toThrow(
|
||||
expect(() => service.getConnectedIdentityMetadata({}, { urlOrigin: "" })).toThrow(
|
||||
"CryptKeeper: Origin is not set",
|
||||
);
|
||||
});
|
||||
|
||||
test("should send undefined if origin isn't approved", async () => {
|
||||
test("should return undefined if origin isn't approved", () => {
|
||||
mockGetStatus.mockResolvedValueOnce({ isUnlocked: false, isInitialized: true });
|
||||
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
const result = await service.getConnectedIdentityMetadata({}, { urlOrigin: "new-urlOrigin" });
|
||||
const result = service.getConnectedIdentityMetadata({}, { urlOrigin: "new-urlOrigin" });
|
||||
|
||||
expect(result).toStrictEqual(undefined);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should throw error if no connected identity found", async () => {
|
||||
test("should return undefined if no connected identity found", () => {
|
||||
mockGetStatus.mockResolvedValueOnce({ isUnlocked: true, isInitialized: true });
|
||||
mockGetConnectedIdentity.mockReturnValue(undefined);
|
||||
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
await expect(service.getConnectedIdentityMetadata({}, { urlOrigin: "empty_connected_identity" })).rejects.toThrow(
|
||||
"CryptKeeper: identity metadata is not found",
|
||||
);
|
||||
});
|
||||
});
|
||||
const result = service.getConnectedIdentityMetadata({}, { urlOrigin: "empty_connected_identity" });
|
||||
|
||||
describe("checks", () => {
|
||||
test("should check if origin is connected properly", async () => {
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
const result = await service.isConnected({}, { urlOrigin: mockDefaultUrlOrigin });
|
||||
|
||||
expect(result).toStrictEqual({});
|
||||
});
|
||||
|
||||
test("should throw error if origin is not connected", async () => {
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
await expect(service.isConnected({}, { urlOrigin: "" })).rejects.toThrowError(
|
||||
"CryptKeeper: identity metadata is not found",
|
||||
);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("connect", () => {
|
||||
test("should connect properly if not connected identity found", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue(undefined);
|
||||
mockGetConnectedIdentity.mockReturnValue(undefined);
|
||||
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
await expect(service.connect({ isChangeIdentity: false }, defaultMetadata)).resolves.not.toThrowError();
|
||||
expect(mockConnectIdentityRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockConnectRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("should connect properly if connected identity found", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue(mockDefaultIdentity);
|
||||
mockGetConnectedIdentity.mockReturnValue(mockDefaultIdentity);
|
||||
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
await expect(service.connect({ isChangeIdentity: false }, defaultMetadata)).resolves.not.toThrowError();
|
||||
expect(mockConnectIdentityRequest).toHaveBeenCalledTimes(0);
|
||||
expect(mockConnectRequest).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("should connect properly if change identity request added", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue(mockDefaultIdentity);
|
||||
mockGetConnectedIdentity.mockReturnValue(mockDefaultIdentity);
|
||||
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
await expect(service.connect({ isChangeIdentity: true }, defaultMetadata)).resolves.not.toThrowError();
|
||||
expect(mockConnectIdentityRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockConnectRequest).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("should throw error if there is no urlOrigin", async () => {
|
||||
test("should throw error if there is no url origin", async () => {
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
await expect(service.connect({ isChangeIdentity: false }, { urlOrigin: "" })).rejects.toThrow(
|
||||
@@ -225,7 +197,7 @@ describe("background/services/injector", () => {
|
||||
});
|
||||
|
||||
test("should connect with approval request properly", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue(mockDefaultIdentity);
|
||||
mockGetConnectedIdentity.mockReturnValue(mockDefaultIdentity);
|
||||
|
||||
const service = InjectorService.getInstance();
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ describe("background/services/locker", () => {
|
||||
await lockService.lock();
|
||||
|
||||
(browser.tabs.sendMessage as jest.Mock).mockClear();
|
||||
(pushMessage as jest.Mock).mockReset();
|
||||
(pushMessage as jest.Mock).mockClear();
|
||||
(browser.tabs.sendMessage as jest.Mock).mockRejectedValueOnce(false).mockResolvedValue(true);
|
||||
|
||||
(SimpleStorage as jest.Mock).mock.instances.forEach((instance: MockStorage) => {
|
||||
|
||||
@@ -14,8 +14,8 @@ import browser from "webextension-polyfill";
|
||||
import BrowserUtils from "@src/background/controllers/browserUtils";
|
||||
import RequestManager from "@src/background/controllers/requestManager";
|
||||
import ApprovalService from "@src/background/services/approval";
|
||||
import ConnectionService from "@src/background/services/connection";
|
||||
import { validateMerkleProofSource } from "@src/background/services/validation";
|
||||
import ZkIdentityService from "@src/background/services/zkIdentity";
|
||||
import { closeChromeOffscreen, createChromeOffscreen, getBrowserPlatform } from "@src/background/shared/utils";
|
||||
import { BrowserPlatform, RPCInternalAction } from "@src/constants";
|
||||
import pushMessage from "@src/util/pushMessage";
|
||||
@@ -29,7 +29,7 @@ export default class ProtocolService {
|
||||
|
||||
private readonly browserService: BrowserUtils;
|
||||
|
||||
private readonly zkIdentityService: ZkIdentityService;
|
||||
private readonly connectionService: ConnectionService;
|
||||
|
||||
private readonly approvalService: ApprovalService;
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class ProtocolService {
|
||||
this.zkProofService = ZkProofService.getInstance();
|
||||
this.browserService = BrowserUtils.getInstance();
|
||||
this.requestManager = RequestManager.getInstance();
|
||||
this.zkIdentityService = ZkIdentityService.getInstance();
|
||||
this.connectionService = ConnectionService.getInstance();
|
||||
this.approvalService = ApprovalService.getInstance();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export default class ProtocolService {
|
||||
const browserPlatform = getBrowserPlatform();
|
||||
|
||||
if (browserPlatform === BrowserPlatform.Firefox) {
|
||||
const identity = await this.zkIdentityService.getConnectedIdentity();
|
||||
const identity = this.connectionService.getConnectedIdentity(urlOrigin!);
|
||||
const proof = await this.zkProofService.generateSemaphoreProof(identity, checkedSemaphoreProofRequest);
|
||||
|
||||
return proof;
|
||||
@@ -98,7 +98,7 @@ export default class ProtocolService {
|
||||
const browserPlatform = getBrowserPlatform();
|
||||
|
||||
if (browserPlatform === BrowserPlatform.Firefox) {
|
||||
const identity = await this.zkIdentityService.getConnectedIdentity();
|
||||
const identity = this.connectionService.getConnectedIdentity(urlOrigin!);
|
||||
const proof = await this.zkProofService.generateRLNProof(identity, checkedRLNProofRequest);
|
||||
|
||||
return proof;
|
||||
@@ -138,7 +138,7 @@ export default class ProtocolService {
|
||||
merkleProofSource,
|
||||
});
|
||||
|
||||
const identity = await this.zkIdentityService.getConnectedIdentity();
|
||||
const identity = this.connectionService.getConnectedIdentity(urlOrigin);
|
||||
const identitySerialized = identity?.serialize();
|
||||
|
||||
if (!identity || !identitySerialized) {
|
||||
@@ -191,7 +191,7 @@ export default class ProtocolService {
|
||||
merkleProofSource,
|
||||
});
|
||||
|
||||
const identity = await this.zkIdentityService.getConnectedIdentity();
|
||||
const identity = this.connectionService.getConnectedIdentity(urlOrigin);
|
||||
const identitySerialized = identity?.serialize();
|
||||
|
||||
if (!identity || !identitySerialized) {
|
||||
|
||||
@@ -72,7 +72,7 @@ jest.mock("@src/background/services/approval", (): unknown => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock("@src/background/services/zkIdentity", (): unknown => ({
|
||||
jest.mock("@src/background/services/connection", (): unknown => ({
|
||||
getInstance: jest.fn(() => ({
|
||||
getConnectedIdentity: mockGetConnectedIdentity,
|
||||
})),
|
||||
@@ -111,7 +111,7 @@ describe("background/services/protocol", () => {
|
||||
|
||||
mockGenerateRLNProof.mockResolvedValue(emptyFullProof);
|
||||
|
||||
mockGetConnectedIdentity.mockResolvedValue({ serialize: () => mockSerializedIdentity });
|
||||
mockGetConnectedIdentity.mockReturnValue({ serialize: () => mockSerializedIdentity });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -136,7 +136,7 @@ describe("background/services/protocol", () => {
|
||||
});
|
||||
|
||||
test("should throw error if there is no connected identity", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue(undefined);
|
||||
mockGetConnectedIdentity.mockReturnValue(undefined);
|
||||
|
||||
const service = ProtocolService.getInstance();
|
||||
|
||||
@@ -147,7 +147,7 @@ describe("background/services/protocol", () => {
|
||||
});
|
||||
|
||||
test("should throw error there is no circuit and zkey files", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue({
|
||||
mockGetConnectedIdentity.mockReturnValue({
|
||||
serialize: () => mockSerializedIdentity,
|
||||
genIdentityCommitment: () => "mockIdentityCommitment",
|
||||
});
|
||||
@@ -161,7 +161,7 @@ describe("background/services/protocol", () => {
|
||||
});
|
||||
|
||||
test("should throw error if user rejected semaphore approve request", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue({
|
||||
mockGetConnectedIdentity.mockReturnValue({
|
||||
serialize: () => mockSerializedIdentity,
|
||||
genIdentityCommitment: () => "mockIdentityCommitment",
|
||||
});
|
||||
@@ -173,7 +173,7 @@ describe("background/services/protocol", () => {
|
||||
});
|
||||
|
||||
test("should generate semaphore proof properly on firefox platform browsers", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue({
|
||||
mockGetConnectedIdentity.mockReturnValue({
|
||||
serialize: () => mockSerializedIdentity,
|
||||
genIdentityCommitment: () => "mockIdentityCommitment",
|
||||
});
|
||||
@@ -336,7 +336,7 @@ describe("background/services/protocol", () => {
|
||||
});
|
||||
|
||||
test("should throw error if there is no connected identity", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue(undefined);
|
||||
mockGetConnectedIdentity.mockReturnValue(undefined);
|
||||
|
||||
const service = ProtocolService.getInstance();
|
||||
|
||||
@@ -347,7 +347,7 @@ describe("background/services/protocol", () => {
|
||||
});
|
||||
|
||||
test("should throw error if user rejected semaphore approve request", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue({
|
||||
mockGetConnectedIdentity.mockReturnValue({
|
||||
serialize: () => mockSerializedIdentity,
|
||||
genIdentityCommitment: () => "mockIdentityCommitment",
|
||||
});
|
||||
@@ -382,7 +382,7 @@ describe("background/services/protocol", () => {
|
||||
});
|
||||
|
||||
test("should throw error there is no circuit and zkey files", async () => {
|
||||
mockGetConnectedIdentity.mockResolvedValue({
|
||||
mockGetConnectedIdentity.mockReturnValue({
|
||||
serialize: () => mockSerializedIdentity,
|
||||
genIdentityCommitment: () => "mockIdentityCommitment",
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
import { EventName } from "@cryptkeeperzk/providers";
|
||||
import {
|
||||
EWallet,
|
||||
type ConnectedIdentityMetadata,
|
||||
@@ -7,20 +6,18 @@ import {
|
||||
type IImportIdentityArgs,
|
||||
} from "@cryptkeeperzk/types";
|
||||
import { createNewIdentity } from "@cryptkeeperzk/zk";
|
||||
import pick from "lodash/pick";
|
||||
import browser from "webextension-polyfill";
|
||||
|
||||
import SimpleStorage from "@src/background/services/storage";
|
||||
import ZkIdentityService from "@src/background/services/zkIdentity";
|
||||
import { ZERO_ADDRESS } from "@src/config/const";
|
||||
import {
|
||||
mockDefaultConnection,
|
||||
mockDefaultIdentity,
|
||||
mockDefaultIdentityCommitment,
|
||||
mockDefaultNullifier,
|
||||
mockDefaultTrapdoor,
|
||||
} from "@src/config/mock/zk";
|
||||
import { setStatus } from "@src/ui/ducks/app";
|
||||
import { setConnectedIdentity, setIdentities } from "@src/ui/ducks/identities";
|
||||
import pushMessage from "@src/util/pushMessage";
|
||||
|
||||
const mockDefaultIdentities = [[mockDefaultIdentityCommitment, JSON.stringify(mockDefaultIdentity)]];
|
||||
@@ -89,8 +86,8 @@ describe("background/services/zkIdentity", () => {
|
||||
const zkIdentityService = ZkIdentityService.getInstance();
|
||||
|
||||
const defaultTabs = [
|
||||
{ id: 1, url: mockDefaultIdentity.metadata.urlOrigin },
|
||||
{ id: 2, url: mockDefaultIdentity.metadata.urlOrigin },
|
||||
{ id: 1, url: mockDefaultConnection.urlOrigin },
|
||||
{ id: 2, url: mockDefaultConnection.urlOrigin },
|
||||
{ id: 3 },
|
||||
];
|
||||
|
||||
@@ -99,7 +96,7 @@ describe("background/services/zkIdentity", () => {
|
||||
const defaultNewIdentity = {
|
||||
serialize: () => JSON.stringify({ secret: "1234", metadata: mockDefaultIdentity.metadata }),
|
||||
genIdentityCommitment: () => "15206603389158210388485662342360617949291660595274505642693885456541816400292",
|
||||
metadata: { urlOrigin: "http://localhost:3000" } as ConnectedIdentityMetadata,
|
||||
metadata: { name: "Account #1" } as ConnectedIdentityMetadata,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -133,31 +130,13 @@ describe("background/services/zkIdentity", () => {
|
||||
});
|
||||
|
||||
describe("unlock", () => {
|
||||
test("should unlock properly and set connected identity", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
test("should unlock properly", async () => {
|
||||
const [identityStorage] = (SimpleStorage as jest.Mock).mock.instances as [MockStorage];
|
||||
identityStorage.get.mockResolvedValue(mockSerializedDefaultIdentities);
|
||||
connectedIdentityStorage.get.mockResolvedValue(mockDefaultIdentityCommitment);
|
||||
|
||||
const expectConnectIdentityAction = setConnectedIdentity(
|
||||
pick(mockDefaultIdentity.metadata, ["name", "urlOrigin"]),
|
||||
);
|
||||
|
||||
const expectSetIdentitiesAction = setIdentities([
|
||||
{ commitment: mockDefaultIdentityCommitment, metadata: mockDefaultIdentity.metadata },
|
||||
]);
|
||||
|
||||
const result = await zkIdentityService.unlock();
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(pushMessage).toBeCalledTimes(2);
|
||||
expect(pushMessage).toBeCalledWith(expectConnectIdentityAction);
|
||||
expect(pushMessage).toHaveBeenNthCalledWith(2, expectSetIdentitiesAction);
|
||||
expect(browser.tabs.sendMessage).toBeCalledTimes(2);
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(1, defaultTabs[0].id, expectConnectIdentityAction);
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(2, defaultTabs[1].id, expectConnectIdentityAction);
|
||||
});
|
||||
|
||||
test("should unlock properly with empty store", async () => {
|
||||
@@ -190,54 +169,6 @@ describe("background/services/zkIdentity", () => {
|
||||
expect(await zkIdentityService.awaitUnlock()).toBeUndefined();
|
||||
});
|
||||
|
||||
describe("set connected identity", () => {
|
||||
beforeEach(async () => {
|
||||
await zkIdentityService.unlock();
|
||||
});
|
||||
|
||||
test("should set connected identity properly", async () => {
|
||||
const expectConnectIdentityAction = setConnectedIdentity(
|
||||
pick(mockDefaultIdentity.metadata, ["name", "urlOrigin"]),
|
||||
);
|
||||
const expectedSetIdentitiesAction = setIdentities([
|
||||
{ commitment: mockDefaultIdentityCommitment, metadata: mockDefaultIdentity.metadata },
|
||||
]);
|
||||
|
||||
const result = await zkIdentityService.connectIdentity({
|
||||
identityCommitment: mockDefaultIdentityCommitment,
|
||||
urlOrigin: "http://localhost:3000",
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(pushMessage).toBeCalledTimes(2);
|
||||
expect(pushMessage).toHaveBeenNthCalledWith(1, expectConnectIdentityAction);
|
||||
expect(pushMessage).toHaveBeenNthCalledWith(2, expectedSetIdentitiesAction);
|
||||
expect(browser.tabs.sendMessage).toBeCalledTimes(4);
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(1, defaultTabs[0].id, expectConnectIdentityAction);
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(2, defaultTabs[1].id, expectConnectIdentityAction);
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(3, defaultTabs[0].id, setStatus(mockDefaultStatus));
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(4, defaultTabs[1].id, setStatus(mockDefaultStatus));
|
||||
});
|
||||
|
||||
test("should not set connected identity if there is no any saved identities", async () => {
|
||||
(SimpleStorage as jest.Mock).mock.instances.forEach((instance: MockStorage) => {
|
||||
instance.get.mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
await zkIdentityService.lock();
|
||||
await zkIdentityService.unlock();
|
||||
|
||||
const result = await zkIdentityService.connectIdentity({
|
||||
identityCommitment: mockDefaultIdentityCommitment,
|
||||
urlOrigin: "http://localhost:3000",
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(pushMessage).not.toBeCalled();
|
||||
expect(browser.tabs.sendMessage).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("set identity name", () => {
|
||||
beforeEach(async () => {
|
||||
await zkIdentityService.unlock();
|
||||
@@ -262,48 +193,22 @@ describe("background/services/zkIdentity", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("set identity url origin", () => {
|
||||
beforeEach(async () => {
|
||||
await zkIdentityService.unlock();
|
||||
});
|
||||
|
||||
test("should set identity url origin properly", async () => {
|
||||
const result = await zkIdentityService.setIdentityHost({
|
||||
identityCommitment: mockDefaultIdentityCommitment,
|
||||
urlOrigin: "http://localhost:3000",
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("should not set identity url origin if there is no such identity", async () => {
|
||||
const result = await zkIdentityService.setIdentityHost({
|
||||
identityCommitment: "unknown",
|
||||
urlOrigin: "http://localhost:3000",
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("delete identity", () => {
|
||||
beforeEach(async () => {
|
||||
await zkIdentityService.unlock();
|
||||
});
|
||||
|
||||
test("should delete identity properly", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
const [identityStorage] = (SimpleStorage as jest.Mock).mock.instances as [MockStorage];
|
||||
identityStorage.get.mockReturnValue(mockSerializedDefaultIdentities);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
const result = await zkIdentityService.deleteIdentity({
|
||||
identityCommitment: mockDefaultIdentityCommitment,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(result).toStrictEqual({
|
||||
identityCommitment: mockDefaultIdentityCommitment,
|
||||
});
|
||||
});
|
||||
|
||||
test("should not delete identity if there is no any identity", async () => {
|
||||
@@ -314,9 +219,9 @@ describe("background/services/zkIdentity", () => {
|
||||
await zkIdentityService.lock();
|
||||
await zkIdentityService.unlock();
|
||||
|
||||
const result = await zkIdentityService.deleteIdentity({ identityCommitment: mockDefaultIdentityCommitment });
|
||||
|
||||
expect(result).toBe(false);
|
||||
await expect(
|
||||
zkIdentityService.deleteIdentity({ identityCommitment: mockDefaultIdentityCommitment }),
|
||||
).rejects.toThrowError("CryptKeeper: no identity found");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -326,22 +231,8 @@ describe("background/services/zkIdentity", () => {
|
||||
});
|
||||
|
||||
test("should delete all identities properly", async () => {
|
||||
const isIdentitySet = await zkIdentityService.connectIdentity({
|
||||
identityCommitment: mockDefaultIdentityCommitment,
|
||||
urlOrigin: "http://localhost:3000",
|
||||
});
|
||||
const result = await zkIdentityService.deleteAllIdentities();
|
||||
|
||||
expect(isIdentitySet).toBe(true);
|
||||
expect(result).toBe(true);
|
||||
expect(pushMessage).toBeCalledTimes(4);
|
||||
});
|
||||
|
||||
test("should delete all identities properly without connected identity", async () => {
|
||||
const connectedIdentity = await zkIdentityService.getConnectedIdentity();
|
||||
const result = await zkIdentityService.deleteAllIdentities();
|
||||
|
||||
expect(connectedIdentity).toBeUndefined();
|
||||
expect(result).toBe(true);
|
||||
expect(pushMessage).toBeCalledTimes(1);
|
||||
});
|
||||
@@ -360,191 +251,6 @@ describe("background/services/zkIdentity", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("get connected identity", () => {
|
||||
beforeEach(async () => {
|
||||
await zkIdentityService.unlock();
|
||||
});
|
||||
|
||||
test("should get connected identity properly", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
|
||||
identityStorage.get.mockReturnValue(mockSerializedDefaultIdentities);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
const connectedIdentity = await zkIdentityService.getConnectedIdentity();
|
||||
|
||||
expect(connectedIdentity).toBeDefined();
|
||||
});
|
||||
|
||||
test("should get connected identity data properly", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
|
||||
identityStorage.get.mockReturnValue(mockSerializedDefaultIdentities);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
const data = await zkIdentityService.getConnectedIdentityData(
|
||||
{},
|
||||
{ urlOrigin: mockDefaultIdentity.metadata.urlOrigin },
|
||||
);
|
||||
|
||||
expect(data).toStrictEqual(pick(mockDefaultIdentity.metadata, ["name", "urlOrigin"]));
|
||||
});
|
||||
|
||||
test("should no get connected identity data if origin is not the same properly", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
|
||||
identityStorage.get.mockReturnValue(mockSerializedDefaultIdentities);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
const data = await zkIdentityService.getConnectedIdentityData({}, { urlOrigin: "unknown" });
|
||||
|
||||
expect(data).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should get connected identity commitment properly", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
|
||||
identityStorage.get.mockReturnValue(mockSerializedDefaultIdentities);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
const commitment = await zkIdentityService.getConnectedIdentityCommitment();
|
||||
|
||||
expect(commitment).toBe(mockDefaultIdentityCommitment);
|
||||
});
|
||||
|
||||
test("should not get connected identity if there is no any connected identity", async () => {
|
||||
const identity = await zkIdentityService.getConnectedIdentity();
|
||||
const data = await zkIdentityService.getConnectedIdentityData(
|
||||
{},
|
||||
{ urlOrigin: mockDefaultIdentity.metadata.urlOrigin },
|
||||
);
|
||||
|
||||
expect(identity).toBeUndefined();
|
||||
expect(data).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should not get connected identity if there is no connected urlOrigin", async () => {
|
||||
const identity = await zkIdentityService.getConnectedIdentity();
|
||||
const data = await zkIdentityService.getConnectedIdentityData({}, { urlOrigin: "" });
|
||||
|
||||
expect(identity).toBeUndefined();
|
||||
expect(data).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should not get connected identity if there is no any identity", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
identityStorage.get.mockReturnValue(undefined);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
await zkIdentityService.lock();
|
||||
await zkIdentityService.unlock();
|
||||
|
||||
const result = await zkIdentityService.getConnectedIdentity();
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should not read connected identity data if there is no any identity", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
identityStorage.get.mockReturnValue(undefined);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
await zkIdentityService.lock();
|
||||
await zkIdentityService.unlock();
|
||||
|
||||
const result = await zkIdentityService.getConnectedIdentityData({}, {});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should not get connected identity commitment if there is no any identity", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
identityStorage.get.mockReturnValue(undefined);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
await zkIdentityService.lock();
|
||||
await zkIdentityService.unlock();
|
||||
|
||||
const result = await zkIdentityService.getConnectedIdentityCommitment();
|
||||
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
test("should request identity commitment modal properly", async () => {
|
||||
await zkIdentityService.revealConnectedIdentityCommitmentRequest();
|
||||
|
||||
expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });
|
||||
|
||||
const defaultOptions = {
|
||||
tabId: defaultPopupTab.id,
|
||||
type: "popup",
|
||||
focused: true,
|
||||
width: 385,
|
||||
height: 610,
|
||||
};
|
||||
|
||||
expect(browser.windows.create).toBeCalledWith(defaultOptions);
|
||||
});
|
||||
|
||||
test("should reveal identity commitment properly", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
|
||||
identityStorage.get.mockReturnValue(mockSerializedDefaultIdentities);
|
||||
connectedIdentityStorage.get.mockReturnValue(mockDefaultIdentityCommitment);
|
||||
|
||||
await zkIdentityService.revealConnectedIdentityCommitment();
|
||||
|
||||
expect(browser.tabs.query).toBeCalledWith({});
|
||||
expect(browser.tabs.sendMessage).toBeCalledTimes(2);
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(1, defaultTabs[0].id, {
|
||||
type: EventName.REVEAL_COMMITMENT,
|
||||
payload: { commitment: mockDefaultIdentityCommitment },
|
||||
});
|
||||
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(2, defaultTabs[1].id, {
|
||||
type: EventName.REVEAL_COMMITMENT,
|
||||
payload: { commitment: mockDefaultIdentityCommitment },
|
||||
});
|
||||
});
|
||||
|
||||
test("should not reveal identity commitment if there is not connected identity properly", async () => {
|
||||
const [identityStorage, connectedIdentityStorage] = (SimpleStorage as jest.Mock).mock.instances as [
|
||||
MockStorage,
|
||||
MockStorage,
|
||||
];
|
||||
|
||||
identityStorage.get.mockReturnValue(undefined);
|
||||
connectedIdentityStorage.get.mockReturnValue(undefined);
|
||||
|
||||
await expect(zkIdentityService.revealConnectedIdentityCommitment()).rejects.toThrow(
|
||||
"No connected identity found",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("get identities", () => {
|
||||
beforeEach(async () => {
|
||||
await zkIdentityService.unlock();
|
||||
@@ -565,10 +271,7 @@ describe("background/services/zkIdentity", () => {
|
||||
test("should get identity properly", () => {
|
||||
const identity = zkIdentityService.getIdentity(mockDefaultIdentityCommitment);
|
||||
|
||||
expect(identity).toStrictEqual({
|
||||
commitment: mockDefaultIdentityCommitment,
|
||||
metadata: mockDefaultIdentity.metadata,
|
||||
});
|
||||
expect(identity?.metadata).toStrictEqual(mockDefaultIdentity.metadata);
|
||||
});
|
||||
|
||||
test("should return undefined if there is no such identity", () => {
|
||||
@@ -601,22 +304,6 @@ describe("background/services/zkIdentity", () => {
|
||||
expect(browser.windows.create).toBeCalledWith(defaultOptions);
|
||||
});
|
||||
|
||||
test("should request a connect identity modal properly", async () => {
|
||||
await zkIdentityService.connectIdentityRequest({ urlOrigin: "http://localhost:3000" });
|
||||
|
||||
expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });
|
||||
|
||||
const defaultOptions = {
|
||||
tabId: defaultPopupTab.id,
|
||||
type: "popup",
|
||||
focused: true,
|
||||
width: 385,
|
||||
height: 610,
|
||||
};
|
||||
|
||||
expect(browser.windows.create).toBeCalledWith(defaultOptions);
|
||||
});
|
||||
|
||||
test("should create a new identity with ethereum wallet properly", async () => {
|
||||
const identityMessageSignature = "0x000";
|
||||
const identityOptions: ICreateIdentityOptions = {
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { EventName } from "@cryptkeeperzk/providers";
|
||||
import {
|
||||
EWallet,
|
||||
type IIdentityMetadata,
|
||||
type ISetIdentityNameArgs,
|
||||
type INewIdentityRequest,
|
||||
type ConnectedIdentityMetadata,
|
||||
type ISetIdentityHostArgs,
|
||||
type IConnectIdentityArgs,
|
||||
type ICreateIdentityRequestArgs,
|
||||
type IConnectIdentityRequestArgs,
|
||||
type IZkMetadata,
|
||||
type IImportIdentityArgs,
|
||||
EWallet,
|
||||
IImportIdentityRequestArgs,
|
||||
type IImportIdentityRequestArgs,
|
||||
type IDeleteIdentityArgs,
|
||||
} from "@cryptkeeperzk/types";
|
||||
import { ZkIdentitySemaphore, createNewIdentity } from "@cryptkeeperzk/zk";
|
||||
import { bigintToHex } from "bigint-conversion";
|
||||
@@ -21,14 +18,12 @@ import browser from "webextension-polyfill";
|
||||
import BrowserUtils from "@src/background/controllers/browserUtils";
|
||||
import CryptoService, { ECryptMode } from "@src/background/services/crypto";
|
||||
import HistoryService from "@src/background/services/history";
|
||||
import LockerService from "@src/background/services/lock";
|
||||
import NotificationService from "@src/background/services/notification";
|
||||
import SimpleStorage from "@src/background/services/storage";
|
||||
import WalletService from "@src/background/services/wallet";
|
||||
import { Paths } from "@src/constants";
|
||||
import { OperationType } from "@src/types";
|
||||
import { setStatus } from "@src/ui/ducks/app";
|
||||
import { setIdentities, setConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { setIdentities } from "@src/ui/ducks/identities";
|
||||
import { ellipsify } from "@src/util/account";
|
||||
import pushMessage from "@src/util/pushMessage";
|
||||
|
||||
@@ -37,15 +32,12 @@ import type { BackupData, IBackupable } from "@src/background/services/backup";
|
||||
import BaseService from "../base";
|
||||
|
||||
const IDENTITY_KEY = "@@ID@@";
|
||||
const CONNECTED_IDENTITY_KEY = "@@CONNECTED-IDENTITY@@";
|
||||
|
||||
export default class ZkIdentityService extends BaseService implements IBackupable {
|
||||
private static INSTANCE?: ZkIdentityService;
|
||||
|
||||
private readonly identitiesStore: SimpleStorage;
|
||||
|
||||
private readonly connectedIdentityStore: SimpleStorage;
|
||||
|
||||
private readonly notificationService: NotificationService;
|
||||
|
||||
private readonly historyService: HistoryService;
|
||||
@@ -56,24 +48,17 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
|
||||
private readonly cryptoService: CryptoService;
|
||||
|
||||
private readonly lockService: LockerService;
|
||||
|
||||
private identities: Map<string, string>;
|
||||
|
||||
private connectedIdentity?: ZkIdentitySemaphore;
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
this.connectedIdentity = undefined;
|
||||
this.identities = new Map();
|
||||
this.identitiesStore = new SimpleStorage(IDENTITY_KEY);
|
||||
this.connectedIdentityStore = new SimpleStorage(CONNECTED_IDENTITY_KEY);
|
||||
this.notificationService = NotificationService.getInstance();
|
||||
this.historyService = HistoryService.getInstance();
|
||||
this.browserController = BrowserUtils.getInstance();
|
||||
this.walletService = WalletService.getInstance();
|
||||
this.cryptoService = CryptoService.getInstance();
|
||||
this.lockService = LockerService.getInstance();
|
||||
}
|
||||
|
||||
static getInstance = (): ZkIdentityService => {
|
||||
@@ -84,50 +69,6 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
return ZkIdentityService.INSTANCE;
|
||||
};
|
||||
|
||||
getConnectedIdentityCommitment = async (): Promise<string> => {
|
||||
const identity = await this.getConnectedIdentity();
|
||||
|
||||
return identity ? bigintToHex(identity.genIdentityCommitment()) : "";
|
||||
};
|
||||
|
||||
getConnectedIdentityData = async (_: unknown, meta?: IZkMetadata): Promise<ConnectedIdentityMetadata | undefined> => {
|
||||
const identity = await this.getConnectedIdentity();
|
||||
|
||||
if (!identity) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (meta?.urlOrigin && identity.metadata.urlOrigin !== meta.urlOrigin) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.getConnectedIdentityMetadata(identity.metadata);
|
||||
};
|
||||
|
||||
getConnectedIdentity = async (): Promise<ZkIdentitySemaphore | undefined> =>
|
||||
this.readConnectedIdentity(this.identities);
|
||||
|
||||
private readConnectedIdentity = async (identities: Map<string, string>) => {
|
||||
const connectedIdentityCommitmentCipher = await this.connectedIdentityStore.get<string>();
|
||||
|
||||
if (!connectedIdentityCommitmentCipher) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const connectedIdentityCommitment = this.cryptoService.decrypt(connectedIdentityCommitmentCipher, {
|
||||
mode: ECryptMode.MNEMONIC,
|
||||
});
|
||||
const identity = identities.get(connectedIdentityCommitment);
|
||||
|
||||
if (!identity) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.connectedIdentity = ZkIdentitySemaphore.genFromSerialized(identity);
|
||||
|
||||
return this.connectedIdentity;
|
||||
};
|
||||
|
||||
getIdentityCommitments = (): { commitments: string[] } => {
|
||||
const commitments = [...this.identities.keys()];
|
||||
|
||||
@@ -150,83 +91,18 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
});
|
||||
};
|
||||
|
||||
getIdentity = (commitment: string): { commitment: string; metadata: IIdentityMetadata } | undefined => {
|
||||
getIdentity = (commitment: string): ZkIdentitySemaphore | undefined => {
|
||||
const serializedIdentity = this.identities.get(commitment);
|
||||
|
||||
if (!serializedIdentity) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const identity = ZkIdentitySemaphore.genFromSerialized(serializedIdentity);
|
||||
|
||||
return {
|
||||
commitment,
|
||||
metadata: identity.metadata,
|
||||
};
|
||||
return ZkIdentitySemaphore.genFromSerialized(serializedIdentity);
|
||||
};
|
||||
|
||||
getNumOfIdentities = (): number => this.identities.size;
|
||||
|
||||
connectIdentity = async ({ urlOrigin, identityCommitment }: IConnectIdentityArgs): Promise<boolean> => {
|
||||
const result = await this.updateConnectedIdentity({ identities: this.identities, identityCommitment, urlOrigin });
|
||||
|
||||
if (result) {
|
||||
const status = await this.lockService.getStatus();
|
||||
await this.browserController
|
||||
.pushEvent(setStatus(status), { urlOrigin })
|
||||
.then(() => this.browserController.closePopup());
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
private updateConnectedIdentity = async ({
|
||||
identities,
|
||||
identityCommitment,
|
||||
urlOrigin,
|
||||
}: {
|
||||
identities: Map<string, string>;
|
||||
identityCommitment: string;
|
||||
urlOrigin?: string;
|
||||
}): Promise<boolean> => {
|
||||
const identity = identities.get(identityCommitment);
|
||||
|
||||
if (!identity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.connectedIdentity = ZkIdentitySemaphore.genFromSerialized(identity);
|
||||
this.connectedIdentity.updateMetadata({ urlOrigin });
|
||||
await this.writeConnectedIdentity(identityCommitment, this.connectedIdentity.metadata);
|
||||
|
||||
if (urlOrigin) {
|
||||
await this.setIdentityHost({ identityCommitment, urlOrigin });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
private writeConnectedIdentity = async (commitment: string, metadata?: IIdentityMetadata): Promise<void> => {
|
||||
const ciphertext = this.cryptoService.encrypt(commitment, { mode: ECryptMode.MNEMONIC });
|
||||
await this.connectedIdentityStore.set(ciphertext);
|
||||
const connectedMetadata = this.getConnectedIdentityMetadata(metadata);
|
||||
|
||||
await Promise.all([
|
||||
this.browserController.pushEvent(setConnectedIdentity(connectedMetadata), {
|
||||
urlOrigin: connectedMetadata?.urlOrigin,
|
||||
}),
|
||||
pushMessage(setConnectedIdentity(connectedMetadata)),
|
||||
]);
|
||||
};
|
||||
|
||||
private getConnectedIdentityMetadata(metadata?: IIdentityMetadata): ConnectedIdentityMetadata | undefined {
|
||||
if (!metadata) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return pick(metadata, ["name", "urlOrigin"]);
|
||||
}
|
||||
|
||||
setIdentityName = async ({ identityCommitment, name }: ISetIdentityNameArgs): Promise<boolean> => {
|
||||
const rawIdentity = this.identities.get(identityCommitment);
|
||||
|
||||
@@ -243,38 +119,9 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
return true;
|
||||
};
|
||||
|
||||
setIdentityHost = async ({ identityCommitment, urlOrigin }: ISetIdentityHostArgs): Promise<boolean> => {
|
||||
const rawIdentity = this.identities.get(identityCommitment);
|
||||
|
||||
if (!rawIdentity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const identity = ZkIdentitySemaphore.genFromSerialized(rawIdentity);
|
||||
identity.updateMetadata({ urlOrigin });
|
||||
this.identities.set(identityCommitment, identity.serialize());
|
||||
await this.writeIdentities(this.identities);
|
||||
await this.refresh();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
unlock = async (): Promise<boolean> => {
|
||||
await this.loadIdentities();
|
||||
|
||||
if (this.identities.size !== 0) {
|
||||
const identity = await this.readConnectedIdentity(this.identities);
|
||||
const identityCommitment = identity ? bigintToHex(identity.genIdentityCommitment()) : undefined;
|
||||
|
||||
if (identityCommitment) {
|
||||
await this.updateConnectedIdentity({
|
||||
identities: this.identities,
|
||||
identityCommitment,
|
||||
urlOrigin: identity?.metadata.urlOrigin,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.isUnlocked = true;
|
||||
this.onUnlocked();
|
||||
|
||||
@@ -298,23 +145,10 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
}
|
||||
};
|
||||
|
||||
private clearConnectedIdentity = async (): Promise<void> => {
|
||||
if (!this.connectedIdentity) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.connectedIdentity = undefined;
|
||||
await this.writeConnectedIdentity("");
|
||||
};
|
||||
|
||||
createIdentityRequest = async ({ urlOrigin }: ICreateIdentityRequestArgs): Promise<void> => {
|
||||
await this.browserController.openPopup({ params: { redirect: Paths.CREATE_IDENTITY, urlOrigin } });
|
||||
};
|
||||
|
||||
connectIdentityRequest = async ({ urlOrigin }: IConnectIdentityRequestArgs): Promise<void> => {
|
||||
await this.browserController.openPopup({ params: { redirect: Paths.CONNECT_IDENTITY, urlOrigin } });
|
||||
};
|
||||
|
||||
importRequest = async (
|
||||
{ trapdoor, nullifier }: IImportIdentityRequestArgs,
|
||||
{ urlOrigin = "" }: IZkMetadata,
|
||||
@@ -324,33 +158,10 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
});
|
||||
};
|
||||
|
||||
revealConnectedIdentityCommitmentRequest = async (): Promise<void> => {
|
||||
await this.browserController.openPopup({ params: { redirect: Paths.REVEAL_IDENTITY_COMMITMENT } });
|
||||
};
|
||||
|
||||
revealConnectedIdentityCommitment = async (): Promise<void> => {
|
||||
const connectedIdentity = await this.getConnectedIdentity();
|
||||
|
||||
if (!connectedIdentity) {
|
||||
throw new Error("No connected identity found");
|
||||
}
|
||||
|
||||
const commitment = bigintToHex(connectedIdentity.genIdentityCommitment());
|
||||
|
||||
await this.browserController.pushEvent(
|
||||
{ type: EventName.REVEAL_COMMITMENT, payload: { commitment } },
|
||||
{ urlOrigin: connectedIdentity.metadata.urlOrigin! },
|
||||
);
|
||||
|
||||
await this.historyService.trackOperation(OperationType.REVEAL_IDENTITY_COMMITMENT, {
|
||||
identity: { commitment, metadata: connectedIdentity.metadata },
|
||||
});
|
||||
};
|
||||
|
||||
import = async (args: IImportIdentityArgs): Promise<string> => {
|
||||
const identity = createNewIdentity({ ...args, groups: [], isDeterministic: false, isImported: true });
|
||||
|
||||
const status = await this.insertIdentity(identity);
|
||||
const status = await this.insertIdentity(identity, args.urlOrigin);
|
||||
|
||||
if (!status) {
|
||||
throw new Error("Identity is already imported");
|
||||
@@ -388,7 +199,7 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
}
|
||||
|
||||
const identity = createNewIdentity(config);
|
||||
const status = await this.insertIdentity(identity);
|
||||
const status = await this.insertIdentity(identity, urlOrigin);
|
||||
|
||||
if (!status) {
|
||||
throw new Error("Identity is already exist. Try to change nonce or identity data.");
|
||||
@@ -397,7 +208,7 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
return bigintToHex(identity.genIdentityCommitment());
|
||||
};
|
||||
|
||||
private insertIdentity = async (newIdentity: ZkIdentitySemaphore): Promise<boolean> => {
|
||||
private insertIdentity = async (newIdentity: ZkIdentitySemaphore, urlOrigin?: string): Promise<boolean> => {
|
||||
const identityCommitment = bigintToHex(newIdentity.genIdentityCommitment());
|
||||
|
||||
if (this.identities.has(identityCommitment)) {
|
||||
@@ -424,9 +235,9 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
await this.browserController.pushEvent(
|
||||
{
|
||||
type: newIdentity.metadata.isImported ? EventName.IMPORT_IDENTITY : EventName.CREATE_IDENTITY,
|
||||
payload: this.getConnectedIdentityMetadata(newIdentity.metadata),
|
||||
payload: pick(newIdentity.metadata, ["name"]),
|
||||
},
|
||||
{ urlOrigin: newIdentity.metadata.urlOrigin! },
|
||||
{ urlOrigin },
|
||||
);
|
||||
|
||||
return true;
|
||||
@@ -443,12 +254,11 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
await pushMessage(setIdentities(identities));
|
||||
};
|
||||
|
||||
deleteIdentity = async (payload: { identityCommitment: string }): Promise<boolean> => {
|
||||
const { identityCommitment } = payload;
|
||||
deleteIdentity = async ({ identityCommitment }: IDeleteIdentityArgs): Promise<IDeleteIdentityArgs> => {
|
||||
const identity = this.identities.get(identityCommitment);
|
||||
|
||||
if (!identity) {
|
||||
return false;
|
||||
throw new Error("CryptKeeper: no identity found");
|
||||
}
|
||||
|
||||
this.identities.delete(identityCommitment);
|
||||
@@ -462,7 +272,7 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
|
||||
await this.refresh();
|
||||
|
||||
return true;
|
||||
return { identityCommitment };
|
||||
};
|
||||
|
||||
deleteAllIdentities = async (): Promise<boolean> => {
|
||||
@@ -471,7 +281,7 @@ export default class ZkIdentityService extends BaseService implements IBackupabl
|
||||
}
|
||||
|
||||
this.identities.clear();
|
||||
await Promise.all([this.clearConnectedIdentity(), this.identitiesStore.clear(), pushMessage(setIdentities([]))]);
|
||||
await Promise.all([this.identitiesStore.clear(), pushMessage(setIdentities([]))]);
|
||||
await this.historyService.trackOperation(OperationType.DELETE_ALL_IDENTITIES, {});
|
||||
|
||||
await this.notificationService.create({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { IGroupData, IIdentityData, IMerkleProof } from "@cryptkeeperzk/types";
|
||||
import type { IGroupData, IIdentityConnection, IIdentityData, IMerkleProof } from "@cryptkeeperzk/types";
|
||||
|
||||
import { ZERO_ADDRESS } from "../const";
|
||||
|
||||
@@ -37,7 +37,7 @@ export const mockDefaultIdentitySecret =
|
||||
|
||||
export const mockDefaultIdentitySecretHex = "0x29149c6e74dad2c21c8405b4c7d415f26cbadce0fba1b8a58ce1247623bc28e8";
|
||||
|
||||
export const mockDefaultIdentity: IIdentityData & { secret?: string } = {
|
||||
export const mockDefaultIdentity: IIdentityData & { secret?: string; genIdentityCommitment?: () => string } = {
|
||||
commitment: mockDefaultIdentityCommitment,
|
||||
secret: "1234",
|
||||
metadata: {
|
||||
@@ -46,9 +46,15 @@ export const mockDefaultIdentity: IIdentityData & { secret?: string } = {
|
||||
groups: [],
|
||||
isDeterministic: true,
|
||||
nonce: 0,
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isImported: true,
|
||||
},
|
||||
genIdentityCommitment: () => mockDefaultIdentityCommitment,
|
||||
};
|
||||
|
||||
export const mockDefaultConnection: IIdentityConnection = {
|
||||
commitment: mockDefaultIdentityCommitment,
|
||||
name: "Account #1",
|
||||
urlOrigin: "http://localhost:3000",
|
||||
};
|
||||
|
||||
export const mockDefaultGroup: IGroupData = {
|
||||
|
||||
@@ -11,13 +11,11 @@ export enum RPCInternalAction {
|
||||
CREATE_IDENTITY = "rpc/identity/create",
|
||||
CREATE_IDENTITY_REQUEST = "rpc/identity/createRequest",
|
||||
IMPORT_IDENTITY = "rpc/identity/import",
|
||||
CONNECT_IDENTITY = "rpc/identity/connectIdentity",
|
||||
CONNECT = "rpc/connection/connect",
|
||||
GET_CONNECTIONS = "rpc/connection/get",
|
||||
SET_IDENTITY_NAME = "rpc/identity/setIdentityName",
|
||||
SET_IDENTITY_HOST = "rpc/identity/setIdentityHost",
|
||||
DELETE_IDENTITY = "rpc/identity/deleteIdentity",
|
||||
DELETE_ALL_IDENTITIES = "rpc/identity/deleteAllIdentities",
|
||||
GET_CONNECTED_IDENTITY_DATA = "rpc/identity/getConnectedIdentityData",
|
||||
GET_CONNECTED_IDENTITY_COMMITMENT = "rpc/identity/getConnectedIdentityCommitment",
|
||||
GET_IDENTITIES = "rpc/identity/getIdentities",
|
||||
JOIN_GROUP = "rpc/group/join",
|
||||
GENERATE_GROUP_MERKLE_PROOF = "rpc/groups/generateMerkleProof",
|
||||
@@ -68,7 +66,6 @@ export enum RPCInternalAction {
|
||||
GENERATE_RLN_PROOF_OFFSCREEN = "rpc/proofs/generate-rln-proof-offscreen",
|
||||
RLN_PROOF_RESULT = "rpc/proofs/rln-proof-result",
|
||||
PUSH_EVENT = "rpc/browser/tabs/pushEvent",
|
||||
JOIN_GROUP_REQUEST = "rpc/group/joinRequest",
|
||||
// DEV RPCS
|
||||
CLEAR_APPROVED_HOSTS = "rpc/hosts/clear",
|
||||
CLEAR_STORAGE = "rpc/browser/clear",
|
||||
|
||||
@@ -57,7 +57,7 @@ export class OffscreenController {
|
||||
}
|
||||
|
||||
const identity = ZkIdentitySemaphore.genFromSerialized(identitySerialized);
|
||||
const fullProof = await this.zkProofService.generateSemaphoreProof(identity, {
|
||||
const proof = await this.zkProofService.generateSemaphoreProof(identity, {
|
||||
externalNullifier,
|
||||
signal,
|
||||
merkleProofArtifacts,
|
||||
@@ -67,7 +67,7 @@ export class OffscreenController {
|
||||
zkeyFilePath,
|
||||
});
|
||||
|
||||
return fullProof;
|
||||
return proof;
|
||||
};
|
||||
|
||||
generateRlnProof = async ({
|
||||
@@ -88,7 +88,7 @@ export class OffscreenController {
|
||||
}
|
||||
|
||||
const identity = ZkIdentitySemaphore.genFromSerialized(identitySerialized);
|
||||
const rlnFullProof = await this.zkProofService.generateRLNProof(identity, {
|
||||
const proof = await this.zkProofService.generateRLNProof(identity, {
|
||||
rlnIdentifier,
|
||||
message,
|
||||
messageId,
|
||||
@@ -101,6 +101,6 @@ export class OffscreenController {
|
||||
merkleProofProvided,
|
||||
});
|
||||
|
||||
return rlnFullProof;
|
||||
return proof;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ export enum BackupableServices {
|
||||
APPROVAL = "approval",
|
||||
IDENTITY = "identity",
|
||||
VERIFIABLE_CREDENTIALS = "credentials",
|
||||
CONNECTIONS = "connections",
|
||||
}
|
||||
|
||||
export interface IUploadArgs {
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface IIdentityListProps {
|
||||
isShowAddNew: boolean;
|
||||
isShowMenu: boolean;
|
||||
identities: IIdentityData[];
|
||||
connectedOrigins: Record<string, string>;
|
||||
className?: string;
|
||||
selectedCommitment?: string;
|
||||
onSelect?: (identityCommitment: string) => void;
|
||||
@@ -26,6 +27,7 @@ export const IdentityList = ({
|
||||
isShowAddNew,
|
||||
isShowMenu,
|
||||
identities,
|
||||
connectedOrigins,
|
||||
className = "",
|
||||
selectedCommitment = undefined,
|
||||
onSelect = undefined,
|
||||
@@ -80,6 +82,7 @@ export const IdentityList = ({
|
||||
<IdentityItem
|
||||
key={commitment}
|
||||
commitment={commitment}
|
||||
connectedOrigin={connectedOrigins[commitment]}
|
||||
isShowMenu={isShowMenu}
|
||||
metadata={metadata}
|
||||
selected={selectedCommitment}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import classNames from "classnames";
|
||||
import Jazzicon, { jsNumberForAddress } from "react-jazzicon";
|
||||
|
||||
import { Icon } from "@src/ui/components/Icon";
|
||||
import { Input } from "@src/ui/components/Input";
|
||||
@@ -16,6 +17,7 @@ export interface IdentityItemProps {
|
||||
commitment: string;
|
||||
metadata: IIdentityMetadata;
|
||||
isShowMenu: boolean;
|
||||
connectedOrigin?: string;
|
||||
selected?: string;
|
||||
onDeleteIdentity: (commitment: string) => Promise<void>;
|
||||
onUpdateIdentityName: (commitment: string, name: string) => Promise<void>;
|
||||
@@ -27,6 +29,7 @@ export const IdentityItem = ({
|
||||
isShowMenu,
|
||||
selected = "",
|
||||
metadata,
|
||||
connectedOrigin = "",
|
||||
onDeleteIdentity,
|
||||
onUpdateIdentityName,
|
||||
onSelectIdentity = undefined,
|
||||
@@ -44,6 +47,7 @@ export const IdentityItem = ({
|
||||
} = useIdentityItem({
|
||||
commitment,
|
||||
metadata,
|
||||
connectedOrigin,
|
||||
onDelete: onDeleteIdentity,
|
||||
onUpdate: onUpdateIdentityName,
|
||||
onSelect: onSelectIdentity,
|
||||
@@ -75,15 +79,19 @@ export const IdentityItem = ({
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
className={classNames("identity-row__select-icon", {
|
||||
"identity-row__select-icon--selected": selected === commitment,
|
||||
"identity-row__select-icon--selectable": Boolean(onSelectIdentity),
|
||||
})}
|
||||
data-testid={`identity-select-${commitment}`}
|
||||
fontAwesome="fas fa-check"
|
||||
onClick={onSelect}
|
||||
/>
|
||||
{onSelectIdentity ? (
|
||||
<Icon
|
||||
className={classNames("identity-row__select-icon", {
|
||||
"identity-row__select-icon--selected": selected === commitment,
|
||||
"identity-row__select-icon--selectable": true,
|
||||
})}
|
||||
data-testid={`identity-select-${commitment}`}
|
||||
fontAwesome="fas fa-check"
|
||||
onClick={onSelect}
|
||||
/>
|
||||
) : (
|
||||
<Jazzicon diameter={32} paperStyles={{ marginRight: "1rem" }} seed={jsNumberForAddress(commitment)} />
|
||||
)}
|
||||
|
||||
<Box sx={{ display: "flex", flexDirection: "column", flexGrow: 1 }}>
|
||||
{isRenaming ? (
|
||||
@@ -129,7 +137,7 @@ export const IdentityItem = ({
|
||||
>
|
||||
{metadata.name}
|
||||
|
||||
{metadata.urlOrigin && (
|
||||
{connectedOrigin && (
|
||||
<Box
|
||||
data-testid="urlOrigin-icon"
|
||||
sx={{
|
||||
@@ -146,7 +154,7 @@ export const IdentityItem = ({
|
||||
}}
|
||||
onClick={onGoToHost}
|
||||
>
|
||||
<FontAwesomeIcon icon="link" title={metadata.urlOrigin} />
|
||||
<FontAwesomeIcon icon="link" title={connectedOrigin} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
@@ -168,7 +176,7 @@ export const IdentityItem = ({
|
||||
</Box>
|
||||
|
||||
{isShowMenu && (
|
||||
<Menu className="flex user-menu" items={selected !== commitment ? menuItems : [menuItems[0], menuItems[1]]}>
|
||||
<Menu className="flex user-menu" items={menuItems}>
|
||||
<Icon className="identity-row__menu-icon" fontAwesome="fas fa-ellipsis-h" />
|
||||
</Menu>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { redirectToNewTab, replaceUrlParams } from "@src/util/browser";
|
||||
export interface IUseIdentityItemArgs {
|
||||
commitment: string;
|
||||
metadata: IIdentityMetadata;
|
||||
connectedOrigin?: string;
|
||||
onDelete: (commitment: string) => Promise<void>;
|
||||
onUpdate: (commitment: string, name: string) => Promise<void>;
|
||||
onSelect?: (commitment: string) => void;
|
||||
@@ -33,6 +34,7 @@ interface IdentityFormFields {
|
||||
export const useIdentityItem = ({
|
||||
metadata,
|
||||
commitment,
|
||||
connectedOrigin,
|
||||
onDelete,
|
||||
onUpdate,
|
||||
onSelect,
|
||||
@@ -77,8 +79,8 @@ export const useIdentityItem = ({
|
||||
);
|
||||
|
||||
const onGoToHost = useCallback(() => {
|
||||
redirectToNewTab(metadata.urlOrigin!);
|
||||
}, [metadata.urlOrigin]);
|
||||
redirectToNewTab(connectedOrigin!);
|
||||
}, [connectedOrigin]);
|
||||
|
||||
const onGoToIdentity = useCallback(() => {
|
||||
navigate(replaceUrlParams(Paths.IDENTITY, { id: commitment }));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { act, render, screen, fireEvent } from "@testing-library/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { Paths } from "@src/constants";
|
||||
import { redirectToNewTab, replaceUrlParams } from "@src/util/browser";
|
||||
|
||||
@@ -20,6 +20,7 @@ describe("ui/components/IdentityList/Item", () => {
|
||||
isShowMenu: true,
|
||||
commitment: "1",
|
||||
selected: "0",
|
||||
connectedOrigin: mockDefaultConnection.urlOrigin,
|
||||
metadata: mockDefaultIdentity.metadata,
|
||||
onDeleteIdentity: jest.fn(),
|
||||
onSelectIdentity: jest.fn(),
|
||||
@@ -154,6 +155,6 @@ describe("ui/components/IdentityList/Item", () => {
|
||||
await act(() => Promise.resolve(icon.click()));
|
||||
|
||||
expect(redirectToNewTab).toBeCalledTimes(1);
|
||||
expect(redirectToNewTab).toBeCalledWith(defaultProps.metadata.urlOrigin);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultConnection.urlOrigin);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { act, render, screen, fireEvent } from "@testing-library/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { ZERO_ADDRESS } from "@src/config/const";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { Paths } from "@src/constants";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { deleteIdentity, setIdentityName, useIdentities } from "@src/ui/ducks/identities";
|
||||
@@ -40,7 +41,6 @@ describe("ui/components/IdentityList", () => {
|
||||
account: ZERO_ADDRESS,
|
||||
name: "Account #0",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
isImported: false,
|
||||
},
|
||||
@@ -61,6 +61,7 @@ describe("ui/components/IdentityList", () => {
|
||||
isShowMenu: true,
|
||||
identities: defaultIdentities,
|
||||
selectedCommitment: defaultIdentities[0].commitment,
|
||||
connectedOrigins: { 0: mockDefaultConnection.urlOrigin },
|
||||
onSelect: jest.fn(),
|
||||
};
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ describe("ui/components/ConnectionModal/ConnectionModal", () => {
|
||||
expect(title).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("should remove urlOrigin properly", async () => {
|
||||
test("should remove url origin properly", async () => {
|
||||
render(<PermissionModal {...defaultProps} />);
|
||||
|
||||
const button = await screen.findByText("Disconnect");
|
||||
|
||||
@@ -87,7 +87,7 @@ describe("ui/components/ConnectionModal/useConnectionModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should remove urlOrigin properly", async () => {
|
||||
test("should remove url origin properly", async () => {
|
||||
const { result } = renderHook(() => usePermissionModal(defaultArgs));
|
||||
await waitForData(result.current);
|
||||
|
||||
|
||||
95
packages/app/src/ui/ducks/__tests__/connections.test.tsx
Normal file
95
packages/app/src/ui/ducks/__tests__/connections.test.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { RPCInternalAction } from "@src/constants";
|
||||
import { store } from "@src/ui/store/configureAppStore";
|
||||
import postMessage from "@src/util/postMessage";
|
||||
|
||||
import {
|
||||
type IConnectionsState,
|
||||
fetchConnections,
|
||||
useConnections,
|
||||
useConnection,
|
||||
revealConnectedIdentityCommitment,
|
||||
connect,
|
||||
useConnectedOrigins,
|
||||
} from "../connections";
|
||||
|
||||
jest.unmock("@src/ui/ducks/hooks");
|
||||
|
||||
describe("ui/ducks/identities", () => {
|
||||
const defaultConnections: IConnectionsState["connections"] = {
|
||||
[mockDefaultConnection.urlOrigin]: mockDefaultConnection,
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should fetch connections properly", async () => {
|
||||
(postMessage as jest.Mock).mockResolvedValue(defaultConnections);
|
||||
|
||||
await Promise.resolve(store.dispatch(fetchConnections()));
|
||||
const { connections } = store.getState();
|
||||
const connectionsHookData = renderHook(() => useConnections(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
const connectionHookData = renderHook(
|
||||
() => useConnection(defaultConnections[mockDefaultConnection.urlOrigin].urlOrigin),
|
||||
{ wrapper: ({ children }) => <Provider store={store}>{children}</Provider> },
|
||||
);
|
||||
const emptyConnectionHookData = renderHook(() => useConnection(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
const connectedOriginsHookData = renderHook(() => useConnectedOrigins(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
|
||||
expect(connections.connections).toStrictEqual(defaultConnections);
|
||||
expect(connectionsHookData.result.current).toStrictEqual(defaultConnections);
|
||||
expect(connectionHookData.result.current).toStrictEqual(defaultConnections[mockDefaultConnection.urlOrigin]);
|
||||
expect(emptyConnectionHookData.result.current).toBeUndefined();
|
||||
expect(connectedOriginsHookData.result.current).toStrictEqual({
|
||||
[mockDefaultConnection.commitment]: mockDefaultConnection.urlOrigin,
|
||||
});
|
||||
});
|
||||
|
||||
test("should connect properly", async () => {
|
||||
(postMessage as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
await Promise.resolve(
|
||||
store.dispatch(
|
||||
connect({ urlOrigin: mockDefaultConnection.urlOrigin, commitment: mockDefaultConnection.commitment }),
|
||||
),
|
||||
);
|
||||
|
||||
expect(postMessage).toBeCalledTimes(1);
|
||||
expect(postMessage).toBeCalledWith({
|
||||
method: RPCInternalAction.CONNECT,
|
||||
payload: {
|
||||
commitment: mockDefaultConnection.commitment,
|
||||
},
|
||||
meta: {
|
||||
urlOrigin: mockDefaultConnection.urlOrigin,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should reveal connected identity commitment properly", async () => {
|
||||
(postMessage as jest.Mock).mockResolvedValue(mockDefaultConnection.commitment);
|
||||
|
||||
await Promise.resolve(store.dispatch(revealConnectedIdentityCommitment(mockDefaultConnection.urlOrigin)));
|
||||
|
||||
expect(postMessage).toBeCalledTimes(1);
|
||||
expect(postMessage).toBeCalledWith({
|
||||
method: RPCInternalAction.REVEAL_CONNECTED_IDENTITY_COMMITMENT,
|
||||
payload: {},
|
||||
meta: { urlOrigin: mockDefaultConnection.urlOrigin },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,10 +20,14 @@ describe("ui/ducks/groups", () => {
|
||||
const args = { groupId: "groupId", apiKey: "apiKey", inviteCode: "inviteCode" };
|
||||
(postMessage as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const result = await Promise.resolve(store.dispatch(joinGroup(args)));
|
||||
const result = await Promise.resolve(store.dispatch(joinGroup(args, "urlOrigin")));
|
||||
|
||||
expect(postMessage).toBeCalledTimes(1);
|
||||
expect(postMessage).toBeCalledWith({ method: RPCInternalAction.JOIN_GROUP, payload: args });
|
||||
expect(postMessage).toBeCalledWith({
|
||||
method: RPCInternalAction.JOIN_GROUP,
|
||||
payload: args,
|
||||
meta: { urlOrigin: "urlOrigin" },
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
@@ -31,10 +35,14 @@ describe("ui/ducks/groups", () => {
|
||||
const args = { groupId: "groupId" };
|
||||
(postMessage as jest.Mock).mockResolvedValue(defaultMerkleProof);
|
||||
|
||||
const result = await Promise.resolve(store.dispatch(generateGroupMerkleProof(args)));
|
||||
const result = await Promise.resolve(store.dispatch(generateGroupMerkleProof(args, "urlOrigin")));
|
||||
|
||||
expect(postMessage).toBeCalledTimes(1);
|
||||
expect(postMessage).toBeCalledWith({ method: RPCInternalAction.GENERATE_GROUP_MERKLE_PROOF, payload: args });
|
||||
expect(postMessage).toBeCalledWith({
|
||||
method: RPCInternalAction.GENERATE_GROUP_MERKLE_PROOF,
|
||||
payload: args,
|
||||
meta: { urlOrigin: "urlOrigin" },
|
||||
});
|
||||
expect(result).toStrictEqual(defaultMerkleProof);
|
||||
});
|
||||
|
||||
@@ -42,10 +50,14 @@ describe("ui/ducks/groups", () => {
|
||||
const args = { groupId: "groupId", apiKey: "apiKey", inviteCode: "inviteCode" };
|
||||
(postMessage as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const result = await Promise.resolve(store.dispatch(checkGroupMembership(args)));
|
||||
const result = await Promise.resolve(store.dispatch(checkGroupMembership(args, "urlOrigin")));
|
||||
|
||||
expect(postMessage).toBeCalledTimes(1);
|
||||
expect(postMessage).toBeCalledWith({ method: RPCInternalAction.CHECK_GROUP_MEMBERSHIP, payload: args });
|
||||
expect(postMessage).toBeCalledWith({
|
||||
method: RPCInternalAction.CHECK_GROUP_MEMBERSHIP,
|
||||
payload: args,
|
||||
meta: { urlOrigin: "urlOrigin" },
|
||||
});
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { EWallet, IIdentityMetadata } from "@cryptkeeperzk/types";
|
||||
import { EWallet } from "@cryptkeeperzk/types";
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
@@ -15,7 +15,6 @@ import postMessage from "@src/util/postMessage";
|
||||
import {
|
||||
createIdentityRequest,
|
||||
createIdentity,
|
||||
setConnectedIdentity,
|
||||
setIdentityName,
|
||||
deleteIdentity,
|
||||
deleteAllIdentities,
|
||||
@@ -23,10 +22,8 @@ import {
|
||||
IIdentitiesState,
|
||||
setIdentities,
|
||||
setIdentityRequestPending,
|
||||
connectIdentity,
|
||||
useIdentities,
|
||||
useIdentityRequestPending,
|
||||
useConnectedIdentity,
|
||||
fetchHistory,
|
||||
useIdentityOperations,
|
||||
setOperations,
|
||||
@@ -35,10 +32,7 @@ import {
|
||||
useHistorySettings,
|
||||
setSettings,
|
||||
enableHistory,
|
||||
useLinkedIdentities,
|
||||
useUnlinkedIdentities,
|
||||
useIdentity,
|
||||
revealConnectedIdentityCommitment,
|
||||
importIdentity,
|
||||
} from "../identities";
|
||||
|
||||
@@ -52,7 +46,6 @@ describe("ui/ducks/identities", () => {
|
||||
account: ZERO_ADDRESS,
|
||||
name: "Account #1",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
isImported: false,
|
||||
},
|
||||
@@ -80,19 +73,16 @@ describe("ui/ducks/identities", () => {
|
||||
|
||||
const defaultSettings: HistorySettings = { isEnabled: true };
|
||||
|
||||
const defaultConnectedIdentityMetadata: IIdentityMetadata = {
|
||||
...defaultIdentities[0].metadata,
|
||||
};
|
||||
beforeEach(() => {
|
||||
(postMessage as jest.Mock).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should fetch identities properly", async () => {
|
||||
(postMessage as jest.Mock)
|
||||
.mockResolvedValueOnce(defaultIdentities)
|
||||
.mockResolvedValueOnce(defaultConnectedIdentityMetadata)
|
||||
.mockResolvedValueOnce(defaultIdentities[0].commitment);
|
||||
(postMessage as jest.Mock).mockResolvedValue(defaultIdentities);
|
||||
|
||||
await Promise.resolve(store.dispatch(fetchIdentities()));
|
||||
const { identities } = store.getState();
|
||||
@@ -105,23 +95,11 @@ describe("ui/ducks/identities", () => {
|
||||
const emptyIdentityHookData = renderHook(() => useIdentity(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
const linkedIdentitiesHookData = renderHook(() => useLinkedIdentities("http://localhost:3000"), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
const unlinkedIdentitiesHookData = renderHook(() => useUnlinkedIdentities(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
const connectedIdentityHookData = renderHook(() => useConnectedIdentity(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
|
||||
expect(identities.identities).toStrictEqual(defaultIdentities);
|
||||
expect(identitiesHookData.result.current).toStrictEqual(defaultIdentities);
|
||||
expect(identityHookData.result.current).toStrictEqual(defaultIdentities[0]);
|
||||
expect(emptyIdentityHookData.result.current).toBeUndefined();
|
||||
expect(linkedIdentitiesHookData.result.current).toStrictEqual(defaultIdentities.slice(0, 1));
|
||||
expect(unlinkedIdentitiesHookData.result.current).toStrictEqual(defaultIdentities.slice(1));
|
||||
expect(connectedIdentityHookData.result.current).toStrictEqual(defaultIdentities[0]);
|
||||
});
|
||||
|
||||
test("should fetch history properly", async () => {
|
||||
@@ -176,13 +154,6 @@ describe("ui/ducks/identities", () => {
|
||||
expect(identities.operations).toStrictEqual(defaultOperations);
|
||||
});
|
||||
|
||||
test("should set connected identity properly", async () => {
|
||||
await Promise.resolve(store.dispatch(setConnectedIdentity(defaultConnectedIdentityMetadata)));
|
||||
const { identities } = store.getState();
|
||||
|
||||
expect(identities.connectedMetadata).toStrictEqual(defaultIdentities[0].metadata);
|
||||
});
|
||||
|
||||
test("should set identities properly", async () => {
|
||||
await Promise.resolve(store.dispatch(setIdentities(defaultIdentities)));
|
||||
const { identities } = store.getState();
|
||||
@@ -257,30 +228,6 @@ describe("ui/ducks/identities", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should reveal identity commitment properly", async () => {
|
||||
await Promise.resolve(store.dispatch(revealConnectedIdentityCommitment()));
|
||||
|
||||
expect(postMessage).toBeCalledTimes(1);
|
||||
expect(postMessage).toBeCalledWith({
|
||||
method: RPCInternalAction.REVEAL_CONNECTED_IDENTITY_COMMITMENT,
|
||||
});
|
||||
});
|
||||
|
||||
test("should call set connected identity action properly", async () => {
|
||||
await Promise.resolve(
|
||||
store.dispatch(connectIdentity({ identityCommitment: "1", urlOrigin: "http://localhost:3000" })),
|
||||
);
|
||||
|
||||
expect(postMessage).toBeCalledTimes(1);
|
||||
expect(postMessage).toBeCalledWith({
|
||||
method: RPCInternalAction.CONNECT_IDENTITY,
|
||||
payload: {
|
||||
identityCommitment: "1",
|
||||
urlOrigin: "http://localhost:3000",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should call set identity name action properly", async () => {
|
||||
await Promise.resolve(store.dispatch(setIdentityName("1", "name")));
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("ui/ducks/permissions", () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should fetch urlOrigin permissions properly", async () => {
|
||||
test("should fetch url origin permissions properly", async () => {
|
||||
(postMessage as jest.Mock).mockResolvedValue(defaultPermission);
|
||||
|
||||
await Promise.resolve(store.dispatch(fetchHostPermissions(defaultHost)));
|
||||
@@ -49,7 +49,7 @@ describe("ui/ducks/permissions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should set urlOrigin permission properly", async () => {
|
||||
test("should set url origin permission properly", async () => {
|
||||
(postMessage as jest.Mock).mockResolvedValue({ ...defaultPermission, canSkipApprove: false });
|
||||
|
||||
await Promise.resolve(store.dispatch(setHostPermissions({ urlOrigin: defaultHost, canSkipApprove: false })));
|
||||
@@ -72,7 +72,7 @@ describe("ui/ducks/permissions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should remove urlOrigin properly", async () => {
|
||||
test("should remove url origin properly", async () => {
|
||||
await Promise.resolve(store.dispatch(removeHost(defaultHost)));
|
||||
const { permissions } = store.getState();
|
||||
const { result } = renderHook(() => useHostPermission(defaultHost), {
|
||||
@@ -90,7 +90,7 @@ describe("ui/ducks/permissions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should check urlOrigin approval properly", async () => {
|
||||
test("should check url origin approval properly", async () => {
|
||||
(postMessage as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
const result = await store.dispatch(checkHostApproval(defaultHost));
|
||||
|
||||
85
packages/app/src/ui/ducks/connections.ts
Normal file
85
packages/app/src/ui/ducks/connections.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import deepEqual from "fast-deep-equal";
|
||||
|
||||
import { RPCInternalAction } from "@src/constants";
|
||||
import postMessage from "@src/util/postMessage";
|
||||
|
||||
import type { IConnectIdentityArgs, IIdentityConnection } from "@cryptkeeperzk/types";
|
||||
import type { TypedThunk } from "@src/ui/store/configureAppStore";
|
||||
|
||||
import { useAppSelector } from "./hooks";
|
||||
|
||||
export interface IConnectionsState {
|
||||
connections: Record<string, IIdentityConnection>;
|
||||
}
|
||||
|
||||
const initialState: IConnectionsState = {
|
||||
connections: {},
|
||||
};
|
||||
|
||||
const connectionsSlice = createSlice({
|
||||
name: "connections",
|
||||
initialState,
|
||||
reducers: {
|
||||
setConnections: (state: IConnectionsState, action: PayloadAction<Record<string, IIdentityConnection>>) => {
|
||||
state.connections = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setConnections } = connectionsSlice.actions;
|
||||
|
||||
export const connect =
|
||||
({ commitment, urlOrigin }: IConnectIdentityArgs) =>
|
||||
async (): Promise<boolean> =>
|
||||
postMessage({
|
||||
method: RPCInternalAction.CONNECT,
|
||||
payload: {
|
||||
commitment,
|
||||
},
|
||||
meta: {
|
||||
urlOrigin,
|
||||
},
|
||||
});
|
||||
|
||||
export const fetchConnections = (): TypedThunk<Promise<void>> => async (dispatch) => {
|
||||
const connections = await postMessage<Record<string, IIdentityConnection>>({
|
||||
method: RPCInternalAction.GET_CONNECTIONS,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
dispatch(setConnections(connections));
|
||||
};
|
||||
|
||||
export const revealConnectedIdentityCommitment =
|
||||
(urlOrigin: string): TypedThunk<Promise<void>> =>
|
||||
async () => {
|
||||
await postMessage({
|
||||
method: RPCInternalAction.REVEAL_CONNECTED_IDENTITY_COMMITMENT,
|
||||
payload: {},
|
||||
meta: { urlOrigin },
|
||||
});
|
||||
};
|
||||
|
||||
export const useConnections = (): Record<string, IIdentityConnection> =>
|
||||
useAppSelector((state) => state.connections.connections, deepEqual);
|
||||
|
||||
export const useConnectedOrigins = (): Record<string, string> =>
|
||||
useAppSelector(
|
||||
(state) =>
|
||||
Object.entries(state.connections.connections).reduce<Record<string, string>>(
|
||||
(acc, [urlOrigin, { commitment }]) => {
|
||||
acc[commitment] = urlOrigin;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
),
|
||||
deepEqual,
|
||||
);
|
||||
|
||||
export const useConnection = (urlOrigin?: string): IIdentityConnection | undefined =>
|
||||
useAppSelector((state) => (urlOrigin ? state.connections.connections[urlOrigin] : undefined), deepEqual);
|
||||
|
||||
export default connectionsSlice.reducer;
|
||||
@@ -10,25 +10,34 @@ import type {
|
||||
import type { TypedThunk } from "@src/ui/store/configureAppStore";
|
||||
|
||||
export const joinGroup =
|
||||
(payload: IJoinGroupMemberArgs): TypedThunk<Promise<boolean>> =>
|
||||
(payload: IJoinGroupMemberArgs, urlOrigin: string): TypedThunk<Promise<boolean>> =>
|
||||
async () =>
|
||||
postMessage({
|
||||
method: RPCInternalAction.JOIN_GROUP,
|
||||
payload,
|
||||
meta: {
|
||||
urlOrigin,
|
||||
},
|
||||
});
|
||||
|
||||
export const generateGroupMerkleProof =
|
||||
(payload: IGenerateGroupMerkleProofArgs): TypedThunk<Promise<IMerkleProof>> =>
|
||||
(payload: IGenerateGroupMerkleProofArgs, urlOrigin: string): TypedThunk<Promise<IMerkleProof>> =>
|
||||
async () =>
|
||||
postMessage({
|
||||
method: RPCInternalAction.GENERATE_GROUP_MERKLE_PROOF,
|
||||
payload,
|
||||
meta: {
|
||||
urlOrigin,
|
||||
},
|
||||
});
|
||||
|
||||
export const checkGroupMembership =
|
||||
(payload: ICheckGroupMembershipArgs): TypedThunk<Promise<boolean>> =>
|
||||
(payload: ICheckGroupMembershipArgs, urlOrigin: string): TypedThunk<Promise<boolean>> =>
|
||||
async () =>
|
||||
postMessage({
|
||||
method: RPCInternalAction.CHECK_GROUP_MEMBERSHIP,
|
||||
payload,
|
||||
meta: {
|
||||
urlOrigin,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,8 +10,6 @@ import type {
|
||||
ICreateIdentityUiArgs,
|
||||
IIdentityData,
|
||||
ICreateIdentityRequestArgs,
|
||||
ConnectedIdentityMetadata,
|
||||
IConnectIdentityArgs,
|
||||
IImportIdentityArgs,
|
||||
} from "@cryptkeeperzk/types";
|
||||
import type { TypedThunk } from "@src/ui/store/configureAppStore";
|
||||
@@ -22,8 +20,6 @@ export interface IIdentitiesState {
|
||||
identities: IIdentityData[];
|
||||
operations: Operation[];
|
||||
requestPending: boolean;
|
||||
connectedCommitment: string;
|
||||
connectedMetadata?: ConnectedIdentityMetadata;
|
||||
settings?: HistorySettings;
|
||||
}
|
||||
|
||||
@@ -32,22 +28,12 @@ const initialState: IIdentitiesState = {
|
||||
operations: [],
|
||||
settings: undefined,
|
||||
requestPending: false,
|
||||
connectedCommitment: "",
|
||||
connectedMetadata: undefined,
|
||||
};
|
||||
|
||||
const identitiesSlice = createSlice({
|
||||
name: "identities",
|
||||
initialState,
|
||||
reducers: {
|
||||
setConnectedIdentity: (state: IIdentitiesState, action: PayloadAction<ConnectedIdentityMetadata | undefined>) => {
|
||||
state.connectedMetadata = action.payload;
|
||||
},
|
||||
|
||||
setConnectedCommitment: (state: IIdentitiesState, action: PayloadAction<string>) => {
|
||||
state.connectedCommitment = action.payload;
|
||||
},
|
||||
|
||||
setIdentityRequestPending: (state: IIdentitiesState, action: PayloadAction<boolean>) => {
|
||||
state.requestPending = action.payload;
|
||||
},
|
||||
@@ -66,8 +52,7 @@ const identitiesSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { setConnectedIdentity, setIdentities, setIdentityRequestPending, setOperations, setSettings } =
|
||||
identitiesSlice.actions;
|
||||
export const { setIdentities, setIdentityRequestPending, setOperations, setSettings } = identitiesSlice.actions;
|
||||
|
||||
export const createIdentityRequest =
|
||||
({ urlOrigin }: ICreateIdentityRequestArgs) =>
|
||||
@@ -95,17 +80,6 @@ export const createIdentity =
|
||||
},
|
||||
});
|
||||
|
||||
export const connectIdentity =
|
||||
({ identityCommitment, urlOrigin }: IConnectIdentityArgs) =>
|
||||
async (): Promise<boolean> =>
|
||||
postMessage({
|
||||
method: RPCInternalAction.CONNECT_IDENTITY,
|
||||
payload: {
|
||||
identityCommitment,
|
||||
urlOrigin,
|
||||
},
|
||||
});
|
||||
|
||||
export const importIdentity = (payload: IImportIdentityArgs) => async (): Promise<string> =>
|
||||
postMessage({
|
||||
method: RPCInternalAction.IMPORT_IDENTITY,
|
||||
@@ -135,17 +109,9 @@ export const deleteAllIdentities = () => async (): Promise<boolean> =>
|
||||
});
|
||||
|
||||
export const fetchIdentities = (): TypedThunk<Promise<void>> => async (dispatch) => {
|
||||
const [identities, metadata, commitment] = await Promise.all([
|
||||
postMessage<IIdentityData[]>({ method: RPCInternalAction.GET_IDENTITIES }),
|
||||
postMessage<ConnectedIdentityMetadata | undefined>({
|
||||
method: RPCInternalAction.GET_CONNECTED_IDENTITY_DATA,
|
||||
}),
|
||||
postMessage<string>({ method: RPCInternalAction.GET_CONNECTED_IDENTITY_COMMITMENT }),
|
||||
]);
|
||||
const [identities] = await Promise.all([postMessage<IIdentityData[]>({ method: RPCInternalAction.GET_IDENTITIES })]);
|
||||
|
||||
dispatch(setIdentities(identities));
|
||||
dispatch(setConnectedIdentity(metadata));
|
||||
dispatch(identitiesSlice.actions.setConnectedCommitment(commitment));
|
||||
};
|
||||
|
||||
export const fetchHistory = (): TypedThunk<Promise<void>> => async (dispatch) => {
|
||||
@@ -178,10 +144,6 @@ export const enableHistory =
|
||||
dispatch(setSettings({ isEnabled }));
|
||||
};
|
||||
|
||||
export const revealConnectedIdentityCommitment = (): TypedThunk<Promise<void>> => async () => {
|
||||
await postMessage({ method: RPCInternalAction.REVEAL_CONNECTED_IDENTITY_COMMITMENT });
|
||||
};
|
||||
|
||||
export const useIdentities = (): IIdentityData[] => useAppSelector((state) => state.identities.identities, deepEqual);
|
||||
|
||||
export const useIdentity = (commitment?: string): IIdentityData | undefined =>
|
||||
@@ -189,21 +151,6 @@ export const useIdentity = (commitment?: string): IIdentityData | undefined =>
|
||||
commitment ? state.identities.identities.find((identity) => identity.commitment === commitment) : undefined,
|
||||
);
|
||||
|
||||
export const useLinkedIdentities = (urlOrigin: string): IIdentityData[] =>
|
||||
useAppSelector(
|
||||
(state) => state.identities.identities.filter((identity) => identity.metadata.urlOrigin === urlOrigin),
|
||||
deepEqual,
|
||||
);
|
||||
|
||||
export const useUnlinkedIdentities = (): IIdentityData[] =>
|
||||
useAppSelector((state) => state.identities.identities.filter((identity) => !identity.metadata.urlOrigin), deepEqual);
|
||||
|
||||
export const useConnectedIdentity = (): IIdentityData | undefined =>
|
||||
useAppSelector((state) => {
|
||||
const { identities, connectedCommitment } = state.identities;
|
||||
return identities.find(({ commitment }) => commitment === connectedCommitment);
|
||||
}, deepEqual);
|
||||
|
||||
export const useIdentityRequestPending = (): boolean =>
|
||||
useAppSelector((state) => state.identities.requestPending, deepEqual);
|
||||
|
||||
|
||||
@@ -12,8 +12,16 @@ import "./connectIdentity.scss";
|
||||
import { useConnectIdentity } from "./useConnectIdentity";
|
||||
|
||||
const ConnectIdentity = (): JSX.Element => {
|
||||
const { identities, urlOrigin, faviconUrl, selectedIdentityCommitment, onSelectIdentity, onReject, onConnect } =
|
||||
useConnectIdentity();
|
||||
const {
|
||||
identities,
|
||||
connectedOrigins,
|
||||
urlOrigin,
|
||||
faviconUrl,
|
||||
selectedIdentityCommitment,
|
||||
onSelectIdentity,
|
||||
onReject,
|
||||
onConnect,
|
||||
} = useConnectIdentity();
|
||||
|
||||
return (
|
||||
<FullModal data-testid="connect-identity-page" onClose={onReject}>
|
||||
@@ -44,6 +52,7 @@ const ConnectIdentity = (): JSX.Element => {
|
||||
<Box sx={{ position: "relative", width: "100%" }}>
|
||||
<IdentityList
|
||||
className="connect-identity-list"
|
||||
connectedOrigins={connectedOrigins}
|
||||
identities={identities}
|
||||
isShowAddNew={false}
|
||||
isShowMenu={false}
|
||||
|
||||
@@ -30,7 +30,6 @@ describe("ui/pages/ConnectIdentity", () => {
|
||||
account: ZERO_ADDRESS,
|
||||
name: "Account #1",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
isImported: false,
|
||||
},
|
||||
@@ -46,6 +45,9 @@ describe("ui/pages/ConnectIdentity", () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
connectedOrigins: {
|
||||
1234: "http://localhost:3000",
|
||||
},
|
||||
onSelectIdentity: jest.fn(),
|
||||
onTabChange: jest.fn(),
|
||||
onReject: jest.fn(),
|
||||
|
||||
@@ -8,10 +8,12 @@ import { SyntheticEvent } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { ZERO_ADDRESS } from "@src/config/const";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { connect, fetchConnections, useConnectedOrigins, useConnection } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { connectIdentity, fetchIdentities, useConnectedIdentity, useIdentities } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities, useIdentities } from "@src/ui/ducks/identities";
|
||||
|
||||
import { EConnectIdentityTabs, IUseConnectIdentityData, useConnectIdentity } from "../useConnectIdentity";
|
||||
|
||||
@@ -33,10 +35,15 @@ jest.mock("@src/ui/ducks/app", (): unknown => ({
|
||||
closePopup: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/connections", (): unknown => ({
|
||||
connect: jest.fn(),
|
||||
fetchConnections: jest.fn(),
|
||||
useConnection: jest.fn(),
|
||||
useConnectedOrigins: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/identities", (): unknown => ({
|
||||
fetchIdentities: jest.fn(),
|
||||
connectIdentity: jest.fn(),
|
||||
useConnectedIdentity: jest.fn(),
|
||||
useIdentities: jest.fn(),
|
||||
}));
|
||||
|
||||
@@ -80,7 +87,9 @@ describe("ui/pages/ConnectIdentity/useConnectIdentity", () => {
|
||||
|
||||
(useIdentities as jest.Mock).mockReturnValue(defaultIdentities);
|
||||
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(defaultIdentities[0]);
|
||||
(useConnection as jest.Mock).mockReturnValue(mockDefaultConnection);
|
||||
|
||||
(useConnectedOrigins as jest.Mock).mockReturnValue({});
|
||||
|
||||
window.location.href = `${oldHref}?urlOrigin=http://localhost:3000`;
|
||||
});
|
||||
@@ -94,10 +103,9 @@ describe("ui/pages/ConnectIdentity/useConnectIdentity", () => {
|
||||
const waitForData = async (current: IUseConnectIdentityData) => {
|
||||
await waitFor(() => current.faviconUrl !== "");
|
||||
await waitFor(() => {
|
||||
expect(mockDispatch).toBeCalledTimes(1);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(mockDispatch).toBeCalledTimes(2);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -109,6 +117,7 @@ describe("ui/pages/ConnectIdentity/useConnectIdentity", () => {
|
||||
expect(result.current.faviconUrl).toBe("http://localhost:3000/favicon.ico");
|
||||
expect(result.current.selectedTab).toBe(EConnectIdentityTabs.LINKED);
|
||||
expect(result.current.identities).toStrictEqual(defaultIdentities);
|
||||
expect(result.current.connectedOrigins).toStrictEqual({});
|
||||
});
|
||||
|
||||
test("should handle empty favicon properly", () => {
|
||||
@@ -125,8 +134,9 @@ describe("ui/pages/ConnectIdentity/useConnectIdentity", () => {
|
||||
|
||||
await act(() => Promise.resolve(result.current.onReject()));
|
||||
|
||||
expect(mockDispatch).toBeCalledTimes(2);
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledWith(Paths.HOME);
|
||||
@@ -139,10 +149,11 @@ describe("ui/pages/ConnectIdentity/useConnectIdentity", () => {
|
||||
await waitFor(() => result.current.selectedIdentityCommitment === "1");
|
||||
await act(() => Promise.resolve(result.current.onConnect()));
|
||||
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
expect(mockDispatch).toBeCalledTimes(4);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(connectIdentity).toBeCalledTimes(1);
|
||||
expect(connectIdentity).toBeCalledWith({ identityCommitment: "1", urlOrigin: "http://localhost:3000" });
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(connect).toBeCalledTimes(1);
|
||||
expect(connect).toBeCalledWith({ commitment: "1", urlOrigin: "http://localhost:3000" });
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledWith(Paths.HOME);
|
||||
|
||||
@@ -5,14 +5,16 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { connect, fetchConnections, useConnectedOrigins, useConnection } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { connectIdentity, fetchIdentities, useConnectedIdentity, useIdentities } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities, useIdentities } from "@src/ui/ducks/identities";
|
||||
|
||||
export interface IUseConnectIdentityData {
|
||||
urlOrigin: string;
|
||||
faviconUrl: string;
|
||||
selectedTab: EConnectIdentityTabs;
|
||||
identities: IIdentityData[];
|
||||
connectedOrigins: Record<string, string>;
|
||||
selectedIdentityCommitment?: string;
|
||||
onTabChange: (event: SyntheticEvent, value: EConnectIdentityTabs) => void;
|
||||
onSelectIdentity: (identityCommitment: string) => void;
|
||||
@@ -29,8 +31,9 @@ export const useConnectIdentity = (): IUseConnectIdentityData => {
|
||||
const { searchParams } = new URL(window.location.href.replace("#", ""));
|
||||
const urlOrigin = useMemo(() => searchParams.get("urlOrigin")!, [searchParams.toString()]);
|
||||
|
||||
const connectedIdentity = useConnectedIdentity();
|
||||
const connection = useConnection(urlOrigin);
|
||||
const identities = useIdentities();
|
||||
const connectedOrigins = useConnectedOrigins();
|
||||
|
||||
const [faviconUrl, setFaviconUrl] = useState("");
|
||||
const [selectedTab, setSelectedTab] = useState<EConnectIdentityTabs>(EConnectIdentityTabs.LINKED);
|
||||
@@ -59,7 +62,7 @@ export const useConnectIdentity = (): IUseConnectIdentityData => {
|
||||
}, [dispatch, navigate]);
|
||||
|
||||
const onConnect = useCallback(async () => {
|
||||
await dispatch(connectIdentity({ identityCommitment: selectedIdentityCommitment!, urlOrigin }));
|
||||
await dispatch(connect({ commitment: selectedIdentityCommitment!, urlOrigin }));
|
||||
await dispatch(closePopup()).then(() => {
|
||||
navigate(Paths.HOME);
|
||||
});
|
||||
@@ -67,13 +70,14 @@ export const useConnectIdentity = (): IUseConnectIdentityData => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchIdentities());
|
||||
dispatch(fetchConnections());
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (connectedIdentity?.commitment) {
|
||||
setSelectedIdentityCommitment(connectedIdentity.commitment);
|
||||
if (connection?.commitment) {
|
||||
setSelectedIdentityCommitment(connection.commitment);
|
||||
}
|
||||
}, [connectedIdentity?.commitment]);
|
||||
}, [connection?.commitment]);
|
||||
|
||||
useEffect(() => {
|
||||
getLinkPreview(urlOrigin)
|
||||
@@ -89,6 +93,7 @@ export const useConnectIdentity = (): IUseConnectIdentityData => {
|
||||
faviconUrl,
|
||||
selectedTab,
|
||||
identities,
|
||||
connectedOrigins,
|
||||
selectedIdentityCommitment,
|
||||
onTabChange,
|
||||
onReject,
|
||||
|
||||
@@ -106,7 +106,7 @@ describe("ui/pages/CreateIdentity", () => {
|
||||
(useSignatureOptions as jest.Mock).mockReturnValue({
|
||||
...mockSignatureOptions,
|
||||
options: [
|
||||
...mockSignatureOptions.options.filter(({ id }) => id === "eth"),
|
||||
...mockSignatureOptions.options.filter(({ id }) => id !== "eth"),
|
||||
{
|
||||
id: "eth",
|
||||
title: "Connect to MetaMask",
|
||||
|
||||
@@ -15,7 +15,7 @@ const GroupMerkleProof = (): JSX.Element => {
|
||||
isJoined,
|
||||
error,
|
||||
faviconUrl,
|
||||
connectedIdentity,
|
||||
connection,
|
||||
groupId,
|
||||
onGoBack,
|
||||
onGoToHost,
|
||||
@@ -23,7 +23,7 @@ const GroupMerkleProof = (): JSX.Element => {
|
||||
onGenerateMerkleProof,
|
||||
} = useGroupMerkleProof();
|
||||
|
||||
const isShowContent = isJoined && Boolean(connectedIdentity && groupId);
|
||||
const isShowContent = isJoined && Boolean(connection && groupId);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -51,7 +51,7 @@ const GroupMerkleProof = (): JSX.Element => {
|
||||
<Icon size={8} url={faviconUrl || logoSVG} />
|
||||
</Box>
|
||||
|
||||
{!connectedIdentity && (
|
||||
{!connection && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -120,7 +120,7 @@ const GroupMerkleProof = (): JSX.Element => {
|
||||
variant="h6"
|
||||
onClick={onGoToHost}
|
||||
>
|
||||
{connectedIdentity!.metadata.urlOrigin}
|
||||
{connection!.urlOrigin}
|
||||
</Typography>
|
||||
|
||||
<Typography fontWeight="bold" sx={{ display: "inline" }} variant="h6">
|
||||
@@ -159,7 +159,7 @@ const GroupMerkleProof = (): JSX.Element => {
|
||||
|
||||
<Button
|
||||
data-testid="generate-merkle-proof"
|
||||
disabled={!connectedIdentity || isSubmitting || !isJoined || !groupId}
|
||||
disabled={!connection || isSubmitting || !isJoined || !groupId}
|
||||
sx={{ ml: 1, width: "100%" }}
|
||||
variant="contained"
|
||||
onClick={onGenerateMerkleProof}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
|
||||
import GroupMerkleProof from "..";
|
||||
import { IUseGroupMerkleProofData, useGroupMerkleProof } from "../useGroupMerkleProof";
|
||||
@@ -22,7 +22,7 @@ describe("ui/pages/GroupMerkleProof", () => {
|
||||
error: "",
|
||||
faviconUrl: "favicon",
|
||||
groupId: "groupId",
|
||||
connectedIdentity: mockDefaultIdentity,
|
||||
connection: mockDefaultConnection,
|
||||
onGoBack: jest.fn(),
|
||||
onGoToHost: jest.fn(),
|
||||
onGoToGroup: jest.fn(),
|
||||
@@ -86,7 +86,7 @@ describe("ui/pages/GroupMerkleProof", () => {
|
||||
test("should render empty state properly", async () => {
|
||||
(useGroupMerkleProof as jest.Mock).mockReturnValue({
|
||||
...defaultHookData,
|
||||
connectedIdentity: undefined,
|
||||
connection: undefined,
|
||||
groupId: undefined,
|
||||
faviconUrl: "",
|
||||
});
|
||||
|
||||
@@ -7,12 +7,13 @@ import { getLinkPreview } from "link-preview-js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { getBandadaUrl } from "@src/config/env";
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { fetchConnections, useConnection } from "@src/ui/ducks/connections";
|
||||
import { checkGroupMembership, generateGroupMerkleProof } from "@src/ui/ducks/groups";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { fetchIdentities, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities } from "@src/ui/ducks/identities";
|
||||
import { rejectUserRequest } from "@src/ui/ducks/requests";
|
||||
import { useSearchParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
@@ -55,11 +56,15 @@ jest.mock("@src/ui/ducks/hooks", (): unknown => ({
|
||||
|
||||
jest.mock("@src/ui/ducks/identities", (): unknown => ({
|
||||
fetchIdentities: jest.fn(),
|
||||
useConnectedIdentity: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/connections", (): unknown => ({
|
||||
fetchConnections: jest.fn(),
|
||||
useConnection: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("ui/pages/GroupMerkleProof/useGroupMerkleProof", () => {
|
||||
const defaultFaviconsData = { favicons: [`${mockDefaultIdentity.metadata.urlOrigin}/favicon.ico`] };
|
||||
const defaultFaviconsData = { favicons: [`${mockDefaultConnection.urlOrigin}/favicon.ico`] };
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
const mockDispatch = jest.fn(() => Promise.resolve(false));
|
||||
@@ -67,7 +72,7 @@ describe("ui/pages/GroupMerkleProof/useGroupMerkleProof", () => {
|
||||
beforeEach(() => {
|
||||
(getLinkPreview as jest.Mock).mockResolvedValue(defaultFaviconsData);
|
||||
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(mockDefaultIdentity);
|
||||
(useConnection as jest.Mock).mockReturnValue(mockDefaultConnection);
|
||||
|
||||
(useNavigate as jest.Mock).mockReturnValue(mockNavigate);
|
||||
|
||||
@@ -94,7 +99,7 @@ describe("ui/pages/GroupMerkleProof/useGroupMerkleProof", () => {
|
||||
expect(result.current.error).toBe("");
|
||||
expect(result.current.faviconUrl).toBe(defaultFaviconsData.favicons[0]);
|
||||
expect(result.current.groupId).toBe("groupId");
|
||||
expect(result.current.connectedIdentity).toStrictEqual(mockDefaultIdentity);
|
||||
expect(result.current.connection).toStrictEqual(mockDefaultConnection);
|
||||
});
|
||||
|
||||
test("should go back properly", async () => {
|
||||
@@ -105,8 +110,9 @@ describe("ui/pages/GroupMerkleProof/useGroupMerkleProof", () => {
|
||||
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledWith(Paths.HOME);
|
||||
expect(mockDispatch).toBeCalledTimes(4);
|
||||
expect(mockDispatch).toBeCalledTimes(5);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(checkGroupMembership).toBeCalledTimes(1);
|
||||
expect(rejectUserRequest).toBeCalledTimes(1);
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
@@ -126,13 +132,13 @@ describe("ui/pages/GroupMerkleProof/useGroupMerkleProof", () => {
|
||||
});
|
||||
|
||||
test("should handle empty connected identity properly", async () => {
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(undefined);
|
||||
(useConnection as jest.Mock).mockReturnValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useGroupMerkleProof());
|
||||
await waitForData(result.current);
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.connectedIdentity).toBeUndefined();
|
||||
expect(result.current.connection).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should go to host properly", async () => {
|
||||
@@ -142,7 +148,7 @@ describe("ui/pages/GroupMerkleProof/useGroupMerkleProof", () => {
|
||||
await act(() => Promise.resolve(result.current.onGoToHost()));
|
||||
|
||||
expect(redirectToNewTab).toBeCalledTimes(1);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultIdentity.metadata.urlOrigin);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultConnection.urlOrigin);
|
||||
});
|
||||
|
||||
test("should go to group properly", async () => {
|
||||
@@ -162,8 +168,9 @@ describe("ui/pages/GroupMerkleProof/useGroupMerkleProof", () => {
|
||||
await act(() => Promise.resolve(result.current.onGenerateMerkleProof()));
|
||||
await waitFor(() => !result.current.isSubmitting);
|
||||
|
||||
expect(mockDispatch).toBeCalledTimes(4);
|
||||
expect(mockDispatch).toBeCalledTimes(5);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(checkGroupMembership).toBeCalledTimes(1);
|
||||
expect(generateGroupMerkleProof).toBeCalledTimes(1);
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
|
||||
@@ -5,15 +5,16 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { fetchConnections, useConnection } from "@src/ui/ducks/connections";
|
||||
import { checkGroupMembership, generateGroupMerkleProof } from "@src/ui/ducks/groups";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { fetchIdentities, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities } from "@src/ui/ducks/identities";
|
||||
import { rejectUserRequest } from "@src/ui/ducks/requests";
|
||||
import { useSearchParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
import { getBandadaGroupUrl } from "@src/util/groups";
|
||||
|
||||
import type { IIdentityData } from "@cryptkeeperzk/types";
|
||||
import type { IIdentityConnection } from "@cryptkeeperzk/types";
|
||||
|
||||
export interface IUseGroupMerkleProofData {
|
||||
isLoading: boolean;
|
||||
@@ -22,7 +23,7 @@ export interface IUseGroupMerkleProofData {
|
||||
error: string;
|
||||
faviconUrl: string;
|
||||
groupId?: string;
|
||||
connectedIdentity?: IIdentityData;
|
||||
connection?: IIdentityConnection;
|
||||
onGoBack: () => void;
|
||||
onGoToHost: () => void;
|
||||
onGoToGroup: () => void;
|
||||
@@ -40,13 +41,29 @@ export const useGroupMerkleProof = (): IUseGroupMerkleProofData => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const groupId = useSearchParam("groupId");
|
||||
const urlOrigin = useSearchParam("urlOrigin");
|
||||
|
||||
const connectedIdentity = useConnectedIdentity();
|
||||
const connection = useConnection(urlOrigin);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Promise.all([dispatch(fetchIdentities()), dispatch(checkGroupMembership({ groupId: groupId! }))])
|
||||
.then(([, isJoinedToGroup]) => {
|
||||
Promise.all([dispatch(fetchIdentities()), dispatch(fetchConnections())])
|
||||
.catch((err: Error) => {
|
||||
setError(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [dispatch, setLoading, setError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connection?.urlOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
dispatch(checkGroupMembership({ groupId: groupId! }, connection.urlOrigin))
|
||||
.then((isJoinedToGroup) => {
|
||||
setJoined(isJoinedToGroup);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
@@ -55,38 +72,27 @@ export const useGroupMerkleProof = (): IUseGroupMerkleProofData => {
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [groupId, dispatch, setLoading, setError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connectedIdentity?.metadata.urlOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
getLinkPreview(connectedIdentity.metadata.urlOrigin)
|
||||
getLinkPreview(connection.urlOrigin)
|
||||
.then((data) => {
|
||||
setFaviconUrl(data.favicons[0]);
|
||||
})
|
||||
.catch(() => {
|
||||
setFaviconUrl("");
|
||||
});
|
||||
}, [connectedIdentity?.metadata.urlOrigin, setFaviconUrl]);
|
||||
}, [groupId, connection?.urlOrigin, setJoined, setError, setLoading, dispatch, setFaviconUrl]);
|
||||
|
||||
const onGoBack = useCallback(() => {
|
||||
dispatch(
|
||||
rejectUserRequest(
|
||||
{ type: EventName.GROUP_MERKLE_PROOF, payload: { groupId } },
|
||||
connectedIdentity?.metadata.urlOrigin,
|
||||
),
|
||||
)
|
||||
dispatch(rejectUserRequest({ type: EventName.GROUP_MERKLE_PROOF, payload: { groupId } }, connection?.urlOrigin))
|
||||
.then(() => dispatch(closePopup()))
|
||||
.then(() => {
|
||||
navigate(Paths.HOME);
|
||||
});
|
||||
}, [groupId, connectedIdentity?.metadata.urlOrigin, dispatch, navigate]);
|
||||
}, [groupId, connection?.urlOrigin, dispatch, navigate]);
|
||||
|
||||
const onGoToHost = useCallback(() => {
|
||||
redirectToNewTab(connectedIdentity!.metadata.urlOrigin!);
|
||||
}, [connectedIdentity?.metadata.urlOrigin]);
|
||||
redirectToNewTab(connection!.urlOrigin);
|
||||
}, [connection?.urlOrigin]);
|
||||
|
||||
const onGoToGroup = useCallback(() => {
|
||||
redirectToNewTab(getBandadaGroupUrl(groupId!));
|
||||
@@ -94,7 +100,7 @@ export const useGroupMerkleProof = (): IUseGroupMerkleProofData => {
|
||||
|
||||
const onGenerateMerkleProof = useCallback(() => {
|
||||
setSubmitting(true);
|
||||
dispatch(generateGroupMerkleProof({ groupId: groupId! }))
|
||||
dispatch(generateGroupMerkleProof({ groupId: groupId! }, connection!.urlOrigin))
|
||||
.then(() => dispatch(closePopup()))
|
||||
.then(() => {
|
||||
navigate(Paths.HOME);
|
||||
@@ -105,7 +111,7 @@ export const useGroupMerkleProof = (): IUseGroupMerkleProofData => {
|
||||
.finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}, [groupId, dispatch, navigate, setError, setSubmitting]);
|
||||
}, [groupId, connection?.urlOrigin, dispatch, navigate, setError, setSubmitting]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
@@ -113,7 +119,7 @@ export const useGroupMerkleProof = (): IUseGroupMerkleProofData => {
|
||||
isJoined,
|
||||
error,
|
||||
faviconUrl,
|
||||
connectedIdentity,
|
||||
connection,
|
||||
groupId,
|
||||
onGoBack,
|
||||
onGoToHost,
|
||||
|
||||
@@ -9,7 +9,7 @@ import "./home.scss";
|
||||
import { useHome } from "./useHome";
|
||||
|
||||
const Home = (): JSX.Element => {
|
||||
const { identities, connectedIdentity, refreshConnectionStatus } = useHome();
|
||||
const { identities, connectedOrigins, refreshConnectionStatus } = useHome();
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col home" data-testid="home-page">
|
||||
@@ -19,12 +19,7 @@ const Home = (): JSX.Element => {
|
||||
<Info refreshConnectionStatus={refreshConnectionStatus} />
|
||||
|
||||
<TabList>
|
||||
<IdentityList
|
||||
isShowAddNew
|
||||
isShowMenu
|
||||
identities={identities}
|
||||
selectedCommitment={connectedIdentity?.commitment}
|
||||
/>
|
||||
<IdentityList isShowAddNew isShowMenu connectedOrigins={connectedOrigins} identities={identities} />
|
||||
|
||||
<ActivityList />
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Suspense } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { defaultWalletHookData } from "@src/config/mock/wallet";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { useCryptKeeperWallet, useEthWallet } from "@src/ui/hooks/wallet";
|
||||
|
||||
import Home from "..";
|
||||
@@ -36,6 +37,7 @@ describe("ui/pages/Home", () => {
|
||||
const defaultHookData: IUseHomeData = {
|
||||
identities: [],
|
||||
address: defaultWalletHookData.address,
|
||||
connectedOrigins: { [mockDefaultConnection.commitment]: mockDefaultConnection.urlOrigin },
|
||||
refreshConnectionStatus: jest.fn().mockResolvedValue(true),
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ import { act, renderHook } from "@testing-library/react";
|
||||
import { useRef } from "react";
|
||||
|
||||
import { defaultWalletHookData } from "@src/config/mock/wallet";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { fetchConnections, useConnectedOrigins } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { useIdentities, fetchIdentities, fetchHistory, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { useIdentities, fetchIdentities, fetchHistory } from "@src/ui/ducks/identities";
|
||||
import { checkHostApproval } from "@src/ui/ducks/permissions";
|
||||
import { useEthWallet } from "@src/ui/hooks/wallet";
|
||||
import { getLastActiveTabUrl } from "@src/util/browser";
|
||||
@@ -32,7 +34,11 @@ jest.mock("@src/ui/ducks/identities", (): unknown => ({
|
||||
fetchIdentities: jest.fn(),
|
||||
fetchHistory: jest.fn(),
|
||||
useIdentities: jest.fn(),
|
||||
useConnectedIdentity: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/connections", (): unknown => ({
|
||||
fetchConnections: jest.fn(),
|
||||
useConnectedOrigins: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/permissions", (): unknown => ({
|
||||
@@ -53,7 +59,6 @@ describe("ui/pages/Home/useHome", () => {
|
||||
account: defaultWalletHookData.address!,
|
||||
name: "Account #1",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
isImported: false,
|
||||
},
|
||||
@@ -64,13 +69,16 @@ describe("ui/pages/Home/useHome", () => {
|
||||
account: defaultWalletHookData.address!,
|
||||
name: "Account #2",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
isImported: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const defaultConnectedOrigins = {
|
||||
1: mockDefaultConnection.urlOrigin,
|
||||
};
|
||||
|
||||
const defaultUrl = new URL("http://localhost:3000");
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -84,7 +92,7 @@ describe("ui/pages/Home/useHome", () => {
|
||||
|
||||
(useIdentities as jest.Mock).mockReturnValue(defaultIdentities);
|
||||
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(defaultIdentities[0]);
|
||||
(useConnectedOrigins as jest.Mock).mockReturnValue(defaultConnectedOrigins);
|
||||
|
||||
(checkHostApproval as jest.Mock).mockReturnValue(true);
|
||||
});
|
||||
@@ -99,8 +107,9 @@ describe("ui/pages/Home/useHome", () => {
|
||||
expect(result.current.address).toBe(defaultWalletHookData.address);
|
||||
expect(result.current.identities).toStrictEqual(defaultIdentities);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(fetchHistory).toBeCalledTimes(1);
|
||||
expect(mockDispatch).toBeCalledTimes(2);
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
test("should refresh connection status properly", async () => {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import get from "lodash/get";
|
||||
|
||||
import "./activityListStyles.scss";
|
||||
import { ActivityItem } from "./Item";
|
||||
import { useActivityList } from "./useActivityList";
|
||||
|
||||
export const ActivityList = (): JSX.Element => {
|
||||
const { isLoading, operations, onDeleteHistoryOperation } = useActivityList();
|
||||
const { isLoading, operations, connectedOrigins, onDeleteHistoryOperation } = useActivityList();
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="activity-container flex flex-row items-center justify-center p-4">Loading...</div>;
|
||||
@@ -16,7 +18,12 @@ export const ActivityList = (): JSX.Element => {
|
||||
return (
|
||||
<div className="activity-content activity-container">
|
||||
{operations.map((operation) => (
|
||||
<ActivityItem key={operation.id} operation={operation} onDelete={onDeleteHistoryOperation} />
|
||||
<ActivityItem
|
||||
key={operation.id}
|
||||
operation={operation}
|
||||
urlOrigin={connectedOrigins[get(operation, "identity.commitment")!]}
|
||||
onDelete={onDeleteHistoryOperation}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ import "./activityListItemStyles.scss";
|
||||
|
||||
export interface IActivityItemProps {
|
||||
operation: Operation;
|
||||
urlOrigin?: string;
|
||||
onDelete: (id: string) => void;
|
||||
}
|
||||
|
||||
@@ -36,16 +37,14 @@ const OPERATIONS: Record<OperationType, string> = {
|
||||
[OperationType.JOIN_GROUP]: "Joined group",
|
||||
};
|
||||
|
||||
export const ActivityItem = ({ operation, onDelete }: IActivityItemProps): JSX.Element => {
|
||||
const metadata = operation.identity?.metadata;
|
||||
|
||||
export const ActivityItem = ({ operation, urlOrigin = "", onDelete }: IActivityItemProps): JSX.Element => {
|
||||
const handleDelete = useCallback(() => {
|
||||
onDelete(operation.id);
|
||||
}, [operation.id, onDelete]);
|
||||
|
||||
const onGoToHost = useCallback(() => {
|
||||
redirectToNewTab(metadata!.urlOrigin!);
|
||||
}, [metadata?.urlOrigin]);
|
||||
redirectToNewTab(urlOrigin);
|
||||
}, [urlOrigin]);
|
||||
|
||||
const onGoToGroup = useCallback(() => {
|
||||
redirectToNewTab(getBandadaGroupUrl(operation.group!.id!));
|
||||
@@ -57,11 +56,11 @@ export const ActivityItem = ({ operation, onDelete }: IActivityItemProps): JSX.E
|
||||
<div className="flex flex-row items-center text-lg font-semibold">
|
||||
{OPERATIONS[operation.type]}
|
||||
|
||||
{metadata?.urlOrigin && (
|
||||
{urlOrigin && (
|
||||
<span className="text-xs py-1 px-2 ml-2 rounded-full bg-gray-500 text-gray-800">
|
||||
<Tooltip title={metadata.urlOrigin}>
|
||||
<Tooltip title={urlOrigin}>
|
||||
<FontAwesomeIcon
|
||||
data-testid="urlOrigin"
|
||||
data-testid="url-origin"
|
||||
icon="link"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={onGoToHost}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { act, render, screen } from "@testing-library/react";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { Operation, OperationType } from "@src/types";
|
||||
|
||||
import { ActivityList } from "..";
|
||||
@@ -32,6 +32,7 @@ describe("ui/pages/Home/components/ActivityList", () => {
|
||||
|
||||
const defaultHookData: IUseActivityListData = {
|
||||
isLoading: false,
|
||||
connectedOrigins: { [mockDefaultIdentity.commitment]: mockDefaultConnection.urlOrigin },
|
||||
operations: defaultIdentityOperations,
|
||||
onDeleteHistoryOperation: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
|
||||
import { mockDefaultGroup, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultGroup, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { OperationType } from "@src/types";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
import { getBandadaGroupUrl } from "@src/util/groups";
|
||||
@@ -24,6 +24,7 @@ describe("ui/pages/Home/components/ActivityList/Item", () => {
|
||||
group: mockDefaultGroup,
|
||||
createdAt: new Date().toISOString(),
|
||||
},
|
||||
urlOrigin: mockDefaultConnection.urlOrigin,
|
||||
onDelete: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -55,18 +56,18 @@ describe("ui/pages/Home/components/ActivityList/Item", () => {
|
||||
expect(defaultProps.onDelete).toBeCalledWith("1");
|
||||
});
|
||||
|
||||
test("should go to connected urlOrigin properly", async () => {
|
||||
test("should go to connected url origin properly", async () => {
|
||||
render(<ActivityItem {...defaultProps} />);
|
||||
|
||||
const urlOrigin = await screen.findByTestId("urlOrigin");
|
||||
const urlOrigin = await screen.findByTestId("url-origin");
|
||||
await act(() => fireEvent.click(urlOrigin));
|
||||
|
||||
expect(redirectToNewTab).toBeCalledTimes(1);
|
||||
expect(redirectToNewTab).toBeCalledWith(defaultProps.operation.identity?.metadata.urlOrigin);
|
||||
expect(redirectToNewTab).toBeCalledWith(defaultProps.urlOrigin);
|
||||
});
|
||||
|
||||
test("should go to group properly", async () => {
|
||||
render(<ActivityItem {...defaultProps} />);
|
||||
render(<ActivityItem {...defaultProps} urlOrigin={undefined} />);
|
||||
|
||||
const group = await screen.findByTestId("group");
|
||||
await act(() => fireEvent.click(group));
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { HistorySettings, Operation, OperationType } from "@src/types";
|
||||
import { useConnectedOrigins } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import {
|
||||
deleteHistoryOperation,
|
||||
@@ -23,6 +24,10 @@ jest.mock("@src/ui/ducks/identities", (): unknown => ({
|
||||
useIdentityOperations: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/connections", (): unknown => ({
|
||||
useConnectedOrigins: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/hooks", (): unknown => ({
|
||||
useAppDispatch: jest.fn(),
|
||||
}));
|
||||
@@ -45,6 +50,10 @@ describe("ui/pages/Home/components/ActivityList/useActivityList", () => {
|
||||
},
|
||||
];
|
||||
|
||||
const defaultConnectedOrigins = {
|
||||
[mockDefaultIdentity.commitment]: mockDefaultConnection.urlOrigin,
|
||||
};
|
||||
|
||||
const defaultHistorySettings: HistorySettings = {
|
||||
isEnabled: true,
|
||||
};
|
||||
@@ -54,6 +63,8 @@ describe("ui/pages/Home/components/ActivityList/useActivityList", () => {
|
||||
|
||||
(useIdentityOperations as jest.Mock).mockReturnValue(defaultIdentityOperations);
|
||||
|
||||
(useConnectedOrigins as jest.Mock).mockReturnValue(defaultConnectedOrigins);
|
||||
|
||||
(useHistorySettings as jest.Mock).mockReturnValue(defaultHistorySettings);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { Operation } from "@src/types";
|
||||
import { useConnectedOrigins } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { deleteHistoryOperation, fetchHistory, useIdentityOperations } from "@src/ui/ducks/identities";
|
||||
|
||||
export interface IUseActivityListData {
|
||||
isLoading: boolean;
|
||||
operations: Operation[];
|
||||
connectedOrigins: Record<string, string>;
|
||||
onDeleteHistoryOperation: (id: string) => void;
|
||||
}
|
||||
|
||||
export const useActivityList = (): IUseActivityListData => {
|
||||
const dispatch = useAppDispatch();
|
||||
const operations = useIdentityOperations();
|
||||
const connectedOrigins = useConnectedOrigins();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const onDeleteHistoryOperation = useCallback(
|
||||
@@ -32,6 +35,7 @@ export const useActivityList = (): IUseActivityListData => {
|
||||
return {
|
||||
isLoading,
|
||||
operations,
|
||||
connectedOrigins,
|
||||
onDeleteHistoryOperation,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useCallback } from "react";
|
||||
|
||||
import { fetchConnections, useConnectedOrigins } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { fetchHistory, fetchIdentities, useIdentities, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { fetchHistory, fetchIdentities, useIdentities } from "@src/ui/ducks/identities";
|
||||
import { checkHostApproval } from "@src/ui/ducks/permissions";
|
||||
import { useEthWallet } from "@src/ui/hooks/wallet";
|
||||
import { getLastActiveTabUrl } from "@src/util/browser";
|
||||
@@ -10,7 +11,7 @@ import type { IIdentityData } from "@cryptkeeperzk/types";
|
||||
|
||||
export interface IUseHomeData {
|
||||
identities: IIdentityData[];
|
||||
connectedIdentity?: IIdentityData;
|
||||
connectedOrigins: Record<string, string>;
|
||||
address?: string;
|
||||
refreshConnectionStatus: () => Promise<boolean>;
|
||||
}
|
||||
@@ -18,7 +19,7 @@ export interface IUseHomeData {
|
||||
export const useHome = (): IUseHomeData => {
|
||||
const dispatch = useAppDispatch();
|
||||
const identities = useIdentities();
|
||||
const connectedIdentity = useConnectedIdentity();
|
||||
const connectedOrigins = useConnectedOrigins();
|
||||
|
||||
const { address } = useEthWallet();
|
||||
|
||||
@@ -34,13 +35,14 @@ export const useHome = (): IUseHomeData => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchIdentities());
|
||||
dispatch(fetchConnections());
|
||||
dispatch(fetchHistory());
|
||||
}, [dispatch]);
|
||||
|
||||
return {
|
||||
address,
|
||||
connectedIdentity,
|
||||
identities,
|
||||
connectedOrigins,
|
||||
refreshConnectionStatus,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -17,12 +17,12 @@ import { useIdentityPage } from "./useIdentityPage";
|
||||
const Identity = (): JSX.Element | null => {
|
||||
const {
|
||||
isLoading,
|
||||
isConnectedIdentity,
|
||||
isConfirmModalOpen,
|
||||
isUpdating,
|
||||
errors,
|
||||
commitment,
|
||||
metadata,
|
||||
urlOrigin,
|
||||
register,
|
||||
onGoBack,
|
||||
onConfirmDeleteIdentity,
|
||||
@@ -153,8 +153,8 @@ const Identity = (): JSX.Element | null => {
|
||||
|
||||
{renderRow(
|
||||
"Connected site",
|
||||
metadata.urlOrigin ? (
|
||||
<Tooltip title={metadata.urlOrigin}>
|
||||
urlOrigin ? (
|
||||
<Tooltip title={urlOrigin}>
|
||||
<FontAwesomeIcon
|
||||
data-testid="urlOrigin"
|
||||
icon="link"
|
||||
@@ -193,13 +193,7 @@ const Identity = (): JSX.Element | null => {
|
||||
{!isUpdating ? "Update" : "Confirm"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
color="error"
|
||||
disabled={isConnectedIdentity}
|
||||
sx={{ flex: 1, ml: 1 }}
|
||||
variant="contained"
|
||||
onClick={onConfirmDeleteIdentity}
|
||||
>
|
||||
<Button color="error" sx={{ flex: 1, ml: 1 }} variant="contained" onClick={onConfirmDeleteIdentity}>
|
||||
Delete
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { act, render, waitFor } from "@testing-library/react";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { defaultWalletHookData } from "@src/config/mock/wallet";
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { useCryptKeeperWallet, useEthWallet } from "@src/ui/hooks/wallet";
|
||||
|
||||
import Identity from "..";
|
||||
@@ -28,7 +28,6 @@ jest.mock("../useIdentityPage", (): unknown => ({
|
||||
describe("ui/pages/Identity", () => {
|
||||
const defaultHookData: IUseIdentityPageData = {
|
||||
isLoading: false,
|
||||
isConnectedIdentity: false,
|
||||
isConfirmModalOpen: false,
|
||||
isUpdating: false,
|
||||
errors: {},
|
||||
@@ -37,6 +36,7 @@ describe("ui/pages/Identity", () => {
|
||||
...mockDefaultIdentity.metadata,
|
||||
groups: [{ id: "1", name: "Group #1", description: "Description #1" }],
|
||||
},
|
||||
urlOrigin: mockDefaultConnection.urlOrigin,
|
||||
register: jest.fn(),
|
||||
onGoBack: jest.fn(),
|
||||
onConfirmDeleteIdentity: jest.fn(),
|
||||
@@ -124,10 +124,11 @@ describe("ui/pages/Identity", () => {
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
|
||||
test("should render properly without urlOrigin and groups", async () => {
|
||||
test("should render properly without url origin and groups", async () => {
|
||||
(useIdentityPage as jest.Mock).mockReturnValue({
|
||||
...defaultHookData,
|
||||
metadata: { ...defaultHookData.metadata, groups: [], urlOrigin: undefined },
|
||||
urlOrigin: undefined,
|
||||
metadata: { ...defaultHookData.metadata, groups: [] },
|
||||
});
|
||||
|
||||
const { container, findByText } = render(
|
||||
|
||||
@@ -5,16 +5,11 @@
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { Paths } from "@src/constants";
|
||||
import { fetchConnections, useConnectedOrigins, useConnection } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import {
|
||||
deleteIdentity,
|
||||
fetchIdentities,
|
||||
setIdentityName,
|
||||
useConnectedIdentity,
|
||||
useIdentity,
|
||||
} from "@src/ui/ducks/identities";
|
||||
import { deleteIdentity, fetchIdentities, setIdentityName, useIdentity } from "@src/ui/ducks/identities";
|
||||
import { useUrlParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
|
||||
@@ -36,16 +31,25 @@ jest.mock("@src/ui/ducks/identities", (): unknown => ({
|
||||
deleteIdentity: jest.fn(),
|
||||
fetchIdentities: jest.fn(),
|
||||
setIdentityName: jest.fn(),
|
||||
useConnectedIdentity: jest.fn(),
|
||||
useIdentity: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/connections", (): unknown => ({
|
||||
fetchConnections: jest.fn(),
|
||||
useConnection: jest.fn(),
|
||||
useConnectedOrigins: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("ui/pages/Identity/useIdentityPage", () => {
|
||||
const mockNavigate = jest.fn();
|
||||
const mockDispatch = jest.fn(() => Promise.resolve());
|
||||
|
||||
const defaultConnectedOrigins = {
|
||||
[mockDefaultConnection.commitment]: mockDefaultConnection.urlOrigin,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(mockDefaultIdentity);
|
||||
(useConnection as jest.Mock).mockReturnValue(mockDefaultConnection);
|
||||
|
||||
(useIdentity as jest.Mock).mockReturnValue(mockDefaultIdentity);
|
||||
|
||||
@@ -53,6 +57,8 @@ describe("ui/pages/Identity/useIdentityPage", () => {
|
||||
|
||||
(useUrlParam as jest.Mock).mockReturnValue(mockDefaultIdentity.commitment);
|
||||
|
||||
(useConnectedOrigins as jest.Mock).mockReturnValue(defaultConnectedOrigins);
|
||||
|
||||
(useAppDispatch as jest.Mock).mockReturnValue(mockDispatch);
|
||||
});
|
||||
|
||||
@@ -72,7 +78,6 @@ describe("ui/pages/Identity/useIdentityPage", () => {
|
||||
await waitForData(result.current);
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isConnectedIdentity).toBe(true);
|
||||
expect(result.current.isConfirmModalOpen).toBe(false);
|
||||
expect(result.current.errors).toStrictEqual({ root: undefined, name: undefined });
|
||||
expect(result.current.commitment).toBe(mockDefaultIdentity.commitment);
|
||||
@@ -98,7 +103,6 @@ describe("ui/pages/Identity/useIdentityPage", () => {
|
||||
await waitForData(result.current);
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isConnectedIdentity).toBe(false);
|
||||
expect(result.current.commitment).toBeUndefined();
|
||||
expect(result.current.metadata).toBeUndefined();
|
||||
expect(result.current.errors.root).toBe(error.message);
|
||||
@@ -110,8 +114,9 @@ describe("ui/pages/Identity/useIdentityPage", () => {
|
||||
|
||||
await act(() => Promise.resolve(result.current.onDeleteIdentity()));
|
||||
|
||||
expect(mockDispatch).toBeCalledTimes(2);
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(deleteIdentity).toBeCalledTimes(1);
|
||||
expect(deleteIdentity).toBeCalledWith(result.current.commitment);
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
@@ -149,8 +154,9 @@ describe("ui/pages/Identity/useIdentityPage", () => {
|
||||
await act(() => Promise.resolve(result.current.onConfirmUpdate()));
|
||||
|
||||
expect(result.current.isUpdating).toBe(false);
|
||||
expect(mockDispatch).toBeCalledTimes(2);
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(setIdentityName).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -185,6 +191,6 @@ describe("ui/pages/Identity/useIdentityPage", () => {
|
||||
await act(() => Promise.resolve(result.current.onGoToHost()));
|
||||
|
||||
expect(redirectToNewTab).toBeCalledTimes(1);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultIdentity.metadata.urlOrigin);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultConnection.urlOrigin);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
import { IIdentityMetadata } from "@cryptkeeperzk/types";
|
||||
import get from "lodash/get";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { UseFormRegister, useForm } from "react-hook-form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Paths } from "@src/constants";
|
||||
import { fetchConnections, useConnectedOrigins } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import {
|
||||
deleteIdentity,
|
||||
fetchIdentities,
|
||||
setIdentityName,
|
||||
useConnectedIdentity,
|
||||
useIdentity,
|
||||
} from "@src/ui/ducks/identities";
|
||||
import { deleteIdentity, fetchIdentities, setIdentityName, useIdentity } from "@src/ui/ducks/identities";
|
||||
import { useUrlParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
|
||||
export interface IUseIdentityPageData {
|
||||
isLoading: boolean;
|
||||
isConnectedIdentity: boolean;
|
||||
isConfirmModalOpen: boolean;
|
||||
isUpdating: boolean;
|
||||
errors: Partial<{ root: string; name: string }>;
|
||||
urlOrigin: string;
|
||||
commitment?: string;
|
||||
metadata?: IIdentityMetadata;
|
||||
register: UseFormRegister<FormFields>;
|
||||
@@ -45,7 +41,8 @@ export const useIdentityPage = (): IUseIdentityPageData => {
|
||||
|
||||
const commitment = useUrlParam("id");
|
||||
const identity = useIdentity(commitment);
|
||||
const connectedIdentity = useConnectedIdentity();
|
||||
const connectedOrigins = useConnectedOrigins();
|
||||
const urlOrigin = connectedOrigins[get(identity, "commitment")!];
|
||||
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
@@ -65,7 +62,7 @@ export const useIdentityPage = (): IUseIdentityPageData => {
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
dispatch(fetchIdentities())
|
||||
Promise.all([dispatch(fetchIdentities()), dispatch(fetchConnections())])
|
||||
.catch((err: Error) => {
|
||||
setError("root", { message: err.message });
|
||||
})
|
||||
@@ -104,8 +101,8 @@ export const useIdentityPage = (): IUseIdentityPageData => {
|
||||
}, [setUpdating]);
|
||||
|
||||
const onGoToHost = useCallback(() => {
|
||||
redirectToNewTab(identity!.metadata.urlOrigin!);
|
||||
}, [identity?.metadata.urlOrigin]);
|
||||
redirectToNewTab(urlOrigin);
|
||||
}, [urlOrigin]);
|
||||
|
||||
const onConfirmUpdate = useCallback(
|
||||
(data: FormFields) => {
|
||||
@@ -122,7 +119,6 @@ export const useIdentityPage = (): IUseIdentityPageData => {
|
||||
|
||||
return {
|
||||
isLoading: isLoading || isSubmitting,
|
||||
isConnectedIdentity: identity ? identity.commitment === connectedIdentity?.commitment : false,
|
||||
isConfirmModalOpen,
|
||||
isUpdating,
|
||||
errors: {
|
||||
@@ -131,6 +127,7 @@ export const useIdentityPage = (): IUseIdentityPageData => {
|
||||
},
|
||||
commitment: identity?.commitment,
|
||||
metadata: identity?.metadata,
|
||||
urlOrigin,
|
||||
register,
|
||||
onGoBack,
|
||||
onConfirmDeleteIdentity,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { render, waitFor } from "@testing-library/react";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { mockSignatureOptions } from "@src/config/mock/wallet";
|
||||
import { mockDefaultIdentity, mockDefaultIdentityCommitment, mockDefaultIdentitySecret } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection, mockDefaultIdentityCommitment, mockDefaultIdentitySecret } from "@src/config/mock/zk";
|
||||
import { useSignatureOptions } from "@src/ui/hooks/wallet";
|
||||
|
||||
import ImportIdentity from "..";
|
||||
@@ -28,7 +28,7 @@ describe("ui/pages/ImportIdentity", () => {
|
||||
const defaultHookData: IUseImportIdentityData = {
|
||||
isLoading: false,
|
||||
errors: {},
|
||||
urlOrigin: mockDefaultIdentity.metadata.urlOrigin,
|
||||
urlOrigin: mockDefaultConnection.urlOrigin,
|
||||
trapdoor: mockDefaultIdentitySecret,
|
||||
nullifier: mockDefaultIdentitySecret,
|
||||
secret: mockDefaultIdentitySecret,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@src/config/mock/file";
|
||||
import { defaultWalletHookData } from "@src/config/mock/wallet";
|
||||
import {
|
||||
mockDefaultIdentity,
|
||||
mockDefaultConnection,
|
||||
mockDefaultIdentityCommitment,
|
||||
mockDefaultIdentitySecret,
|
||||
mockDefaultNullifier,
|
||||
@@ -88,9 +88,9 @@ jest.mock("@src/ui/hooks/validation", (): unknown => ({
|
||||
}));
|
||||
|
||||
describe("ui/pages/ImportIdentity/useImportIdentity", () => {
|
||||
const defaultFaviconsData = { favicons: [`${mockDefaultIdentity.metadata.urlOrigin}/favicon.ico`] };
|
||||
const defaultFaviconsData = { favicons: [`${mockDefaultConnection.urlOrigin}/favicon.ico`] };
|
||||
const defaultUrlParams = {
|
||||
urlOrigin: mockDefaultIdentity.metadata.urlOrigin,
|
||||
urlOrigin: mockDefaultConnection.urlOrigin,
|
||||
trapdoor: mockDefaultTrapdoor,
|
||||
nullifier: mockDefaultNullifier,
|
||||
};
|
||||
@@ -176,7 +176,7 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => {
|
||||
await act(() => Promise.resolve(result.current.onGoToHost()));
|
||||
|
||||
expect(redirectToNewTab).toBeCalledTimes(1);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultIdentity.metadata.urlOrigin);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultConnection.urlOrigin);
|
||||
});
|
||||
|
||||
test("should drop object file properly", async () => {
|
||||
@@ -314,7 +314,7 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => {
|
||||
|
||||
test("should submit and go back properly", async () => {
|
||||
(useSearchParam as jest.Mock).mockImplementation((arg: string) =>
|
||||
arg === "urlOrigin" ? mockDefaultIdentity.metadata.urlOrigin : Paths.CONNECT_IDENTITY,
|
||||
arg === "urlOrigin" ? mockDefaultConnection.urlOrigin : Paths.CONNECT_IDENTITY,
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useImportIdentity());
|
||||
@@ -326,13 +326,13 @@ describe("ui/pages/ImportIdentity/useImportIdentity", () => {
|
||||
expect(importIdentity).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledWith(
|
||||
`${Paths.CONNECT_IDENTITY}?urlOrigin=${mockDefaultIdentity.metadata.urlOrigin}&back=${Paths.CONNECT_IDENTITY}`,
|
||||
`${Paths.CONNECT_IDENTITY}?urlOrigin=${mockDefaultConnection.urlOrigin}&back=${Paths.CONNECT_IDENTITY}`,
|
||||
);
|
||||
});
|
||||
|
||||
test("should submit and go home properly", async () => {
|
||||
(useSearchParam as jest.Mock).mockImplementation((arg: string) =>
|
||||
arg === "urlOrigin" ? mockDefaultIdentity.metadata.urlOrigin : Paths.CREATE_IDENTITY,
|
||||
arg === "urlOrigin" ? mockDefaultConnection.urlOrigin : Paths.CREATE_IDENTITY,
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useImportIdentity());
|
||||
|
||||
@@ -17,7 +17,7 @@ const JoinGroup = (): JSX.Element => {
|
||||
faviconUrl,
|
||||
apiKey,
|
||||
inviteCode,
|
||||
connectedIdentity,
|
||||
connection,
|
||||
groupId,
|
||||
onGoBack,
|
||||
onGoToHost,
|
||||
@@ -25,7 +25,7 @@ const JoinGroup = (): JSX.Element => {
|
||||
onJoin,
|
||||
} = useJoinGroup();
|
||||
|
||||
const isShowContent = !isJoined && Boolean(connectedIdentity && groupId);
|
||||
const isShowContent = !isJoined && Boolean(connection && groupId);
|
||||
const isShowInviteInfo = Boolean(apiKey || inviteCode);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -54,7 +54,7 @@ const JoinGroup = (): JSX.Element => {
|
||||
<Icon size={8} url={faviconUrl || logoSVG} />
|
||||
</Box>
|
||||
|
||||
{!connectedIdentity && (
|
||||
{!connection && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -119,7 +119,7 @@ const JoinGroup = (): JSX.Element => {
|
||||
variant="h6"
|
||||
onClick={onGoToHost}
|
||||
>
|
||||
{connectedIdentity!.metadata.urlOrigin}
|
||||
{connection!.urlOrigin}
|
||||
</Typography>
|
||||
|
||||
<Typography fontWeight="bold" sx={{ display: "inline" }} variant="h6">
|
||||
@@ -166,7 +166,7 @@ const JoinGroup = (): JSX.Element => {
|
||||
|
||||
<Button
|
||||
data-testid="join-group"
|
||||
disabled={!connectedIdentity || isSubmitting || isJoined || !groupId}
|
||||
disabled={!connection || isSubmitting || isJoined || !groupId}
|
||||
sx={{ ml: 1, width: "100%" }}
|
||||
variant="contained"
|
||||
onClick={onJoin}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
|
||||
import JoinGroup from "..";
|
||||
import { IUseJoinGroupData, useJoinGroup } from "../useJoinGroup";
|
||||
@@ -24,7 +24,7 @@ describe("ui/pages/JoinGroup", () => {
|
||||
groupId: "groupId",
|
||||
apiKey: "apiKey",
|
||||
inviteCode: "inviteCode",
|
||||
connectedIdentity: mockDefaultIdentity,
|
||||
connection: mockDefaultConnection,
|
||||
onGoBack: jest.fn(),
|
||||
onGoToHost: jest.fn(),
|
||||
onGoToGroup: jest.fn(),
|
||||
@@ -88,7 +88,7 @@ describe("ui/pages/JoinGroup", () => {
|
||||
test("should render empty state properly", async () => {
|
||||
(useJoinGroup as jest.Mock).mockReturnValue({
|
||||
...defaultHookData,
|
||||
connectedIdentity: undefined,
|
||||
connection: undefined,
|
||||
groupId: undefined,
|
||||
inviteCode: undefined,
|
||||
apiKey: undefined,
|
||||
|
||||
@@ -7,12 +7,13 @@ import { getLinkPreview } from "link-preview-js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { getBandadaUrl } from "@src/config/env";
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { fetchConnections, useConnection } from "@src/ui/ducks/connections";
|
||||
import { checkGroupMembership, joinGroup } from "@src/ui/ducks/groups";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { fetchIdentities, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities } from "@src/ui/ducks/identities";
|
||||
import { rejectUserRequest } from "@src/ui/ducks/requests";
|
||||
import { useSearchParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
@@ -55,19 +56,22 @@ jest.mock("@src/ui/ducks/hooks", (): unknown => ({
|
||||
|
||||
jest.mock("@src/ui/ducks/identities", (): unknown => ({
|
||||
fetchIdentities: jest.fn(),
|
||||
useConnectedIdentity: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/connections", (): unknown => ({
|
||||
fetchConnections: jest.fn(),
|
||||
useConnection: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("ui/pages/JoinGroup/useJoinGroup", () => {
|
||||
const defaultFaviconsData = { favicons: [`${mockDefaultIdentity.metadata.urlOrigin}/favicon.ico`] };
|
||||
const defaultFaviconsData = { favicons: [`${mockDefaultConnection.urlOrigin}/favicon.ico`] };
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
const mockDispatch = jest.fn(() => Promise.resolve(false));
|
||||
|
||||
beforeEach(() => {
|
||||
(getLinkPreview as jest.Mock).mockResolvedValue(defaultFaviconsData);
|
||||
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(mockDefaultIdentity);
|
||||
(useConnection as jest.Mock).mockReturnValue(mockDefaultConnection);
|
||||
|
||||
(useNavigate as jest.Mock).mockReturnValue(mockNavigate);
|
||||
|
||||
@@ -97,7 +101,7 @@ describe("ui/pages/JoinGroup/useJoinGroup", () => {
|
||||
expect(result.current.inviteCode).toBe("inviteCode");
|
||||
expect(result.current.faviconUrl).toBe(defaultFaviconsData.favicons[0]);
|
||||
expect(result.current.groupId).toBe("groupId");
|
||||
expect(result.current.connectedIdentity).toStrictEqual(mockDefaultIdentity);
|
||||
expect(result.current.connection).toStrictEqual(mockDefaultConnection);
|
||||
});
|
||||
|
||||
test("should go back properly", async () => {
|
||||
@@ -108,8 +112,9 @@ describe("ui/pages/JoinGroup/useJoinGroup", () => {
|
||||
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledWith(Paths.HOME);
|
||||
expect(mockDispatch).toBeCalledTimes(4);
|
||||
expect(mockDispatch).toBeCalledTimes(5);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(checkGroupMembership).toBeCalledTimes(1);
|
||||
expect(rejectUserRequest).toBeCalledTimes(1);
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
@@ -129,13 +134,13 @@ describe("ui/pages/JoinGroup/useJoinGroup", () => {
|
||||
});
|
||||
|
||||
test("should handle empty connected identity properly", async () => {
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(undefined);
|
||||
(useConnection as jest.Mock).mockReturnValue(undefined);
|
||||
|
||||
const { result } = renderHook(() => useJoinGroup());
|
||||
await waitForData(result.current);
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.connectedIdentity).toBeUndefined();
|
||||
expect(result.current.connection).toBeUndefined();
|
||||
});
|
||||
|
||||
test("should go to host properly", async () => {
|
||||
@@ -145,7 +150,7 @@ describe("ui/pages/JoinGroup/useJoinGroup", () => {
|
||||
await act(() => Promise.resolve(result.current.onGoToHost()));
|
||||
|
||||
expect(redirectToNewTab).toBeCalledTimes(1);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultIdentity.metadata.urlOrigin);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultConnection.urlOrigin);
|
||||
});
|
||||
|
||||
test("should go to group properly", async () => {
|
||||
@@ -165,8 +170,9 @@ describe("ui/pages/JoinGroup/useJoinGroup", () => {
|
||||
await act(() => Promise.resolve(result.current.onJoin()));
|
||||
await waitFor(() => !result.current.isSubmitting);
|
||||
|
||||
expect(mockDispatch).toBeCalledTimes(4);
|
||||
expect(mockDispatch).toBeCalledTimes(5);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(checkGroupMembership).toBeCalledTimes(1);
|
||||
expect(joinGroup).toBeCalledTimes(1);
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
|
||||
@@ -5,15 +5,16 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { fetchConnections, useConnection } from "@src/ui/ducks/connections";
|
||||
import { checkGroupMembership, joinGroup } from "@src/ui/ducks/groups";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { fetchIdentities, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities } from "@src/ui/ducks/identities";
|
||||
import { rejectUserRequest } from "@src/ui/ducks/requests";
|
||||
import { useSearchParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
import { getBandadaGroupUrl } from "@src/util/groups";
|
||||
|
||||
import type { IIdentityData } from "@cryptkeeperzk/types";
|
||||
import type { IIdentityConnection } from "@cryptkeeperzk/types";
|
||||
|
||||
export interface IUseJoinGroupData {
|
||||
isLoading: boolean;
|
||||
@@ -24,7 +25,7 @@ export interface IUseJoinGroupData {
|
||||
groupId?: string;
|
||||
apiKey?: string;
|
||||
inviteCode?: string;
|
||||
connectedIdentity?: IIdentityData;
|
||||
connection?: IIdentityConnection;
|
||||
onGoBack: () => void;
|
||||
onGoToHost: () => void;
|
||||
onGoToGroup: () => void;
|
||||
@@ -44,15 +45,13 @@ export const useJoinGroup = (): IUseJoinGroupData => {
|
||||
const groupId = useSearchParam("groupId");
|
||||
const apiKey = useSearchParam("apiKey");
|
||||
const inviteCode = useSearchParam("inviteCode");
|
||||
const urlOrigin = useSearchParam("urlOrigin");
|
||||
|
||||
const connectedIdentity = useConnectedIdentity();
|
||||
const connection = useConnection(urlOrigin);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
Promise.all([dispatch(fetchIdentities()), dispatch(checkGroupMembership({ groupId: groupId! }))])
|
||||
.then(([, isJoinedToGroup]) => {
|
||||
setJoined(isJoinedToGroup);
|
||||
})
|
||||
Promise.all([dispatch(fetchIdentities()), dispatch(fetchConnections())])
|
||||
.catch((err: Error) => {
|
||||
setError(err.message);
|
||||
})
|
||||
@@ -62,32 +61,41 @@ export const useJoinGroup = (): IUseJoinGroupData => {
|
||||
}, [groupId, dispatch, setLoading, setError]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connectedIdentity?.metadata.urlOrigin) {
|
||||
if (!connection?.urlOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
getLinkPreview(connectedIdentity.metadata.urlOrigin)
|
||||
dispatch(checkGroupMembership({ groupId: groupId! }, connection.urlOrigin))
|
||||
.then((isJoinedToGroup) => {
|
||||
setJoined(isJoinedToGroup);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
setError(err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
getLinkPreview(connection.urlOrigin)
|
||||
.then((data) => {
|
||||
setFaviconUrl(data.favicons[0]);
|
||||
})
|
||||
.catch(() => {
|
||||
setFaviconUrl("");
|
||||
});
|
||||
}, [connectedIdentity?.metadata.urlOrigin, setFaviconUrl]);
|
||||
}, [connection?.urlOrigin, groupId, dispatch, setLoading, setError, setJoined, setFaviconUrl]);
|
||||
|
||||
const onGoBack = useCallback(() => {
|
||||
dispatch(
|
||||
rejectUserRequest({ type: EventName.JOIN_GROUP, payload: { groupId } }, connectedIdentity?.metadata.urlOrigin),
|
||||
)
|
||||
dispatch(rejectUserRequest({ type: EventName.JOIN_GROUP, payload: { groupId } }, connection?.urlOrigin))
|
||||
.then(() => dispatch(closePopup()))
|
||||
.then(() => {
|
||||
navigate(Paths.HOME);
|
||||
});
|
||||
}, [groupId, connectedIdentity?.metadata.urlOrigin, dispatch, navigate]);
|
||||
}, [groupId, connection?.urlOrigin, dispatch, navigate]);
|
||||
|
||||
const onGoToHost = useCallback(() => {
|
||||
redirectToNewTab(connectedIdentity!.metadata.urlOrigin!);
|
||||
}, [connectedIdentity?.metadata.urlOrigin]);
|
||||
redirectToNewTab(connection!.urlOrigin);
|
||||
}, [connection?.urlOrigin]);
|
||||
|
||||
const onGoToGroup = useCallback(() => {
|
||||
redirectToNewTab(getBandadaGroupUrl(groupId!));
|
||||
@@ -95,7 +103,7 @@ export const useJoinGroup = (): IUseJoinGroupData => {
|
||||
|
||||
const onJoin = useCallback(() => {
|
||||
setSubmitting(true);
|
||||
dispatch(joinGroup({ groupId: groupId!, apiKey, inviteCode }))
|
||||
dispatch(joinGroup({ groupId: groupId!, apiKey, inviteCode }, connection!.urlOrigin))
|
||||
.then(() => dispatch(closePopup()))
|
||||
.then(() => {
|
||||
navigate(Paths.HOME);
|
||||
@@ -106,7 +114,7 @@ export const useJoinGroup = (): IUseJoinGroupData => {
|
||||
.finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}, [groupId, apiKey, inviteCode, dispatch, navigate, setError, setSubmitting]);
|
||||
}, [groupId, apiKey, inviteCode, connection?.urlOrigin, dispatch, navigate, setError, setSubmitting]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
@@ -114,7 +122,7 @@ export const useJoinGroup = (): IUseJoinGroupData => {
|
||||
isJoined,
|
||||
error,
|
||||
faviconUrl,
|
||||
connectedIdentity,
|
||||
connection,
|
||||
groupId,
|
||||
apiKey,
|
||||
inviteCode,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ellipsify } from "@src/util/account";
|
||||
import { useRevealIdentityCommitment } from "./useRevealIdentityCommitment";
|
||||
|
||||
const RevealIdentityCommitment = (): JSX.Element => {
|
||||
const { isLoading, error, connectedIdentity, onGoBack, onGoToHost, onReveal } = useRevealIdentityCommitment();
|
||||
const { isLoading, error, connection, onGoBack, onGoToHost, onReveal } = useRevealIdentityCommitment();
|
||||
|
||||
const renderRow = useCallback(
|
||||
(key: string, value?: ReactNode) => (
|
||||
@@ -54,7 +54,7 @@ const RevealIdentityCommitment = (): JSX.Element => {
|
||||
<FullModalHeader onClose={onGoBack}>Reveal identity commitment</FullModalHeader>
|
||||
|
||||
<FullModalContent>
|
||||
{!connectedIdentity && (
|
||||
{!connection && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -70,7 +70,7 @@ const RevealIdentityCommitment = (): JSX.Element => {
|
||||
)}
|
||||
|
||||
<Box>
|
||||
{connectedIdentity && (
|
||||
{connection && (
|
||||
<Box>
|
||||
<Typography component="div" fontWeight="bold" variant="h6">
|
||||
Reveal identity commitment to
|
||||
@@ -81,7 +81,7 @@ const RevealIdentityCommitment = (): JSX.Element => {
|
||||
variant="h6"
|
||||
onClick={onGoToHost}
|
||||
>
|
||||
{connectedIdentity.metadata.urlOrigin}
|
||||
{connection.urlOrigin}
|
||||
</Typography>
|
||||
</Typography>
|
||||
|
||||
@@ -90,32 +90,14 @@ const RevealIdentityCommitment = (): JSX.Element => {
|
||||
<Box>
|
||||
{renderRow(
|
||||
"Commitment",
|
||||
<Tooltip title={connectedIdentity.commitment}>
|
||||
<Tooltip title={connection.commitment}>
|
||||
<Typography component="span" variant="h6">
|
||||
{ellipsify(connectedIdentity.commitment)}
|
||||
{ellipsify(connection.commitment)}
|
||||
</Typography>
|
||||
</Tooltip>,
|
||||
)}
|
||||
|
||||
{renderRow("Name", connectedIdentity.metadata.name)}
|
||||
|
||||
{connectedIdentity.metadata.account &&
|
||||
renderRow(
|
||||
"Owner account",
|
||||
<Tooltip title={connectedIdentity.metadata.account}>
|
||||
<Typography component="span" variant="h6">
|
||||
{ellipsify(connectedIdentity.metadata.account)}
|
||||
</Typography>
|
||||
</Tooltip>,
|
||||
)}
|
||||
|
||||
{connectedIdentity.metadata.isImported &&
|
||||
renderRow(
|
||||
"Imported",
|
||||
<Typography component="span" variant="h6">
|
||||
Yes
|
||||
</Typography>,
|
||||
)}
|
||||
{renderRow("Name", connection.name)}
|
||||
</Box>
|
||||
|
||||
<Box component="hr" sx={{ my: 2 }} />
|
||||
@@ -143,7 +125,7 @@ const RevealIdentityCommitment = (): JSX.Element => {
|
||||
<Button
|
||||
color="error"
|
||||
data-testid="reveal-identity-commitment"
|
||||
disabled={!connectedIdentity}
|
||||
disabled={!connection}
|
||||
sx={{ ml: 1, width: "100%" }}
|
||||
variant="contained"
|
||||
onClick={onReveal}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
|
||||
import RevealIdentityCommitment from "..";
|
||||
import { IUseRevealIdentityCommitmentData, useRevealIdentityCommitment } from "../useRevealIdentityCommitment";
|
||||
@@ -18,7 +18,7 @@ describe("ui/pages/RevealIdentityCommitment", () => {
|
||||
const defaultHookData: IUseRevealIdentityCommitmentData = {
|
||||
isLoading: false,
|
||||
error: "",
|
||||
connectedIdentity: mockDefaultIdentity,
|
||||
connection: mockDefaultConnection,
|
||||
onGoBack: jest.fn(),
|
||||
onGoToHost: jest.fn(),
|
||||
onReveal: jest.fn(),
|
||||
@@ -79,7 +79,7 @@ describe("ui/pages/RevealIdentityCommitment", () => {
|
||||
});
|
||||
|
||||
test("should render empty state properly", async () => {
|
||||
(useRevealIdentityCommitment as jest.Mock).mockReturnValue({ ...defaultHookData, connectedIdentity: undefined });
|
||||
(useRevealIdentityCommitment as jest.Mock).mockReturnValue({ ...defaultHookData, connection: undefined });
|
||||
|
||||
const { container, findByText } = render(
|
||||
<Suspense>
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
import { act, renderHook, waitFor } from "@testing-library/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { mockDefaultIdentity } from "@src/config/mock/zk";
|
||||
import { mockDefaultConnection } from "@src/config/mock/zk";
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { fetchConnections, revealConnectedIdentityCommitment, useConnection } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { fetchIdentities, revealConnectedIdentityCommitment, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities } from "@src/ui/ducks/identities";
|
||||
import { rejectUserRequest } from "@src/ui/ducks/requests";
|
||||
import { useSearchParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
|
||||
import { IUseRevealIdentityCommitmentData, useRevealIdentityCommitment } from "../useRevealIdentityCommitment";
|
||||
@@ -23,6 +25,10 @@ jest.mock("@src/util/browser", (): unknown => ({
|
||||
redirectToNewTab: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/hooks/url", (): unknown => ({
|
||||
useSearchParam: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/app", (): unknown => ({
|
||||
closePopup: jest.fn(),
|
||||
}));
|
||||
@@ -33,8 +39,12 @@ jest.mock("@src/ui/ducks/hooks", (): unknown => ({
|
||||
|
||||
jest.mock("@src/ui/ducks/identities", (): unknown => ({
|
||||
fetchIdentities: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/connections", (): unknown => ({
|
||||
revealConnectedIdentityCommitment: jest.fn(),
|
||||
useConnectedIdentity: jest.fn(),
|
||||
fetchConnections: jest.fn(),
|
||||
useConnection: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("@src/ui/ducks/requests", (): unknown => ({
|
||||
@@ -46,11 +56,13 @@ describe("ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment", () =>
|
||||
const mockDispatch = jest.fn(() => Promise.resolve());
|
||||
|
||||
beforeEach(() => {
|
||||
(useConnectedIdentity as jest.Mock).mockReturnValue(mockDefaultIdentity);
|
||||
(useConnection as jest.Mock).mockReturnValue(mockDefaultConnection);
|
||||
|
||||
(useNavigate as jest.Mock).mockReturnValue(mockNavigate);
|
||||
|
||||
(useAppDispatch as jest.Mock).mockReturnValue(mockDispatch);
|
||||
|
||||
(useSearchParam as jest.Mock).mockReturnValue(mockDefaultConnection.urlOrigin);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -68,7 +80,7 @@ describe("ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment", () =>
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.error).toBe("");
|
||||
expect(result.current.connectedIdentity).toStrictEqual(mockDefaultIdentity);
|
||||
expect(result.current.connection).toStrictEqual(mockDefaultConnection);
|
||||
});
|
||||
|
||||
test("should go back properly", async () => {
|
||||
@@ -79,8 +91,9 @@ describe("ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment", () =>
|
||||
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledWith(Paths.HOME);
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
expect(mockDispatch).toBeCalledTimes(4);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(rejectUserRequest).toBeCalledTimes(1);
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
});
|
||||
@@ -103,7 +116,7 @@ describe("ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment", () =>
|
||||
await act(() => Promise.resolve(result.current.onGoToHost()));
|
||||
|
||||
expect(redirectToNewTab).toBeCalledTimes(1);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultIdentity.metadata.urlOrigin);
|
||||
expect(redirectToNewTab).toBeCalledWith(mockDefaultConnection.urlOrigin);
|
||||
});
|
||||
|
||||
test("should reveal connected identity commitment properly", async () => {
|
||||
@@ -112,8 +125,9 @@ describe("ui/pages/RevealIdentityCommitment/useRevealIdentityCommitment", () =>
|
||||
|
||||
await act(() => Promise.resolve(result.current.onReveal()));
|
||||
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
expect(mockDispatch).toBeCalledTimes(4);
|
||||
expect(fetchIdentities).toBeCalledTimes(1);
|
||||
expect(fetchConnections).toBeCalledTimes(1);
|
||||
expect(revealConnectedIdentityCommitment).toBeCalledTimes(1);
|
||||
expect(closePopup).toBeCalledTimes(1);
|
||||
expect(mockNavigate).toBeCalledTimes(1);
|
||||
|
||||
@@ -4,17 +4,19 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Paths } from "@src/constants";
|
||||
import { closePopup } from "@src/ui/ducks/app";
|
||||
import { fetchConnections, revealConnectedIdentityCommitment, useConnection } from "@src/ui/ducks/connections";
|
||||
import { useAppDispatch } from "@src/ui/ducks/hooks";
|
||||
import { fetchIdentities, revealConnectedIdentityCommitment, useConnectedIdentity } from "@src/ui/ducks/identities";
|
||||
import { fetchIdentities } from "@src/ui/ducks/identities";
|
||||
import { rejectUserRequest } from "@src/ui/ducks/requests";
|
||||
import { useSearchParam } from "@src/ui/hooks/url";
|
||||
import { redirectToNewTab } from "@src/util/browser";
|
||||
|
||||
import type { IIdentityData } from "@cryptkeeperzk/types";
|
||||
import type { IIdentityConnection } from "@cryptkeeperzk/types";
|
||||
|
||||
export interface IUseRevealIdentityCommitmentData {
|
||||
isLoading: boolean;
|
||||
error: string;
|
||||
connectedIdentity?: IIdentityData;
|
||||
connection?: IIdentityConnection;
|
||||
onGoBack: () => void;
|
||||
onGoToHost: () => void;
|
||||
onReveal: () => void;
|
||||
@@ -26,11 +28,12 @@ export const useRevealIdentityCommitment = (): IUseRevealIdentityCommitmentData
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const connectedIdentity = useConnectedIdentity();
|
||||
const urlOrigin = useSearchParam("urlOrigin");
|
||||
const connection = useConnection(urlOrigin);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
dispatch(fetchIdentities())
|
||||
Promise.all([dispatch(fetchIdentities()), dispatch(fetchConnections())])
|
||||
.catch((err: Error) => {
|
||||
setError(err.message);
|
||||
})
|
||||
@@ -40,19 +43,19 @@ export const useRevealIdentityCommitment = (): IUseRevealIdentityCommitmentData
|
||||
}, [dispatch, setLoading, setError]);
|
||||
|
||||
const onGoBack = useCallback(() => {
|
||||
dispatch(rejectUserRequest({ type: EventName.REVEAL_COMMITMENT }, connectedIdentity?.metadata.urlOrigin))
|
||||
dispatch(rejectUserRequest({ type: EventName.REVEAL_COMMITMENT }, connection?.urlOrigin))
|
||||
.then(() => dispatch(closePopup()))
|
||||
.then(() => {
|
||||
navigate(Paths.HOME);
|
||||
});
|
||||
}, [connectedIdentity?.metadata.urlOrigin, dispatch, navigate]);
|
||||
}, [connection?.urlOrigin, dispatch, navigate]);
|
||||
|
||||
const onGoToHost = useCallback(() => {
|
||||
redirectToNewTab(connectedIdentity!.metadata.urlOrigin!);
|
||||
}, [connectedIdentity?.metadata.urlOrigin]);
|
||||
redirectToNewTab(connection!.urlOrigin);
|
||||
}, [connection?.urlOrigin]);
|
||||
|
||||
const onReveal = useCallback(() => {
|
||||
dispatch(revealConnectedIdentityCommitment())
|
||||
dispatch(revealConnectedIdentityCommitment(connection!.urlOrigin))
|
||||
.then(() => dispatch(closePopup()))
|
||||
.then(() => {
|
||||
navigate(Paths.HOME);
|
||||
@@ -60,12 +63,12 @@ export const useRevealIdentityCommitment = (): IUseRevealIdentityCommitmentData
|
||||
.catch((err: Error) => {
|
||||
setError(err.message);
|
||||
});
|
||||
}, [dispatch, navigate, setError]);
|
||||
}, [connection?.urlOrigin, dispatch, navigate, setError]);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
connectedIdentity,
|
||||
connection,
|
||||
onGoBack,
|
||||
onGoToHost,
|
||||
onReveal,
|
||||
|
||||
@@ -4,6 +4,7 @@ import thunk from "redux-thunk";
|
||||
|
||||
import { isDebugMode } from "@src/config/env";
|
||||
import app from "@src/ui/ducks/app";
|
||||
import connections from "@src/ui/ducks/connections";
|
||||
import identities from "@src/ui/ducks/identities";
|
||||
import permissions from "@src/ui/ducks/permissions";
|
||||
import requests from "@src/ui/ducks/requests";
|
||||
@@ -15,6 +16,7 @@ const rootReducer = {
|
||||
app,
|
||||
permissions,
|
||||
verifiableCredentials,
|
||||
connections,
|
||||
};
|
||||
|
||||
const middlewares = isDebugMode() ? [thunk, createLogger({ collapsed: true })] : [thunk];
|
||||
|
||||
@@ -26,6 +26,16 @@ describe("util/postMessage", () => {
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test("should post message without meta properly", async () => {
|
||||
const result = await postMessage({
|
||||
method: RPCInternalAction.DUMMY_REQUEST,
|
||||
payload: {},
|
||||
error: false,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test("should throw error if there is an error in response", async () => {
|
||||
(browser.runtime.sendMessage as jest.Mock).mockResolvedValue([new Error("error"), null]);
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@ import browser from "webextension-polyfill";
|
||||
import type { IMessageAction } from "@cryptkeeperzk/types";
|
||||
|
||||
export default async function postMessage<T>(message: IMessageAction): Promise<T> {
|
||||
const [err, res] = (await browser.runtime.sendMessage(message)) as [string, T];
|
||||
const [err, res] = (await browser.runtime.sendMessage({ ...message, meta: message.meta ? message.meta : {} })) as [
|
||||
string,
|
||||
T,
|
||||
];
|
||||
|
||||
if (err) {
|
||||
throw new Error(err);
|
||||
|
||||
@@ -33,7 +33,6 @@ export const ActionBox = <T, U>({
|
||||
<Button
|
||||
data-testid={testId}
|
||||
sx={{ textTransform: "none", mb: 1 }}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
onClick={() => onClick(option as T)}
|
||||
>
|
||||
|
||||
@@ -5,16 +5,11 @@ import Typography from "@mui/material/Typography";
|
||||
import { useGlobalStyles } from "@src/styles";
|
||||
|
||||
interface IConnectedIdentityProps {
|
||||
identityCommitment?: string;
|
||||
identityName?: string;
|
||||
identityHost?: string;
|
||||
commitment?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export const ConnectedIdentity = ({
|
||||
identityCommitment = "",
|
||||
identityName = "",
|
||||
identityHost = "",
|
||||
}: IConnectedIdentityProps): JSX.Element => {
|
||||
export const ConnectedIdentity = ({ commitment = "", name = "" }: IConnectedIdentityProps): JSX.Element => {
|
||||
const classes = useGlobalStyles();
|
||||
|
||||
return (
|
||||
@@ -45,23 +40,15 @@ export const ConnectedIdentity = ({
|
||||
<Typography className="identity-name" color="text.primary" data-testid="connected-name">
|
||||
<strong>Name: </strong>
|
||||
|
||||
{identityName}
|
||||
{name}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{identityHost && (
|
||||
<Typography className="identity-name" color="text.primary" data-testid="connected-urlOrigin">
|
||||
<strong>Host: </strong>
|
||||
|
||||
{identityHost}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{identityCommitment ? (
|
||||
{commitment ? (
|
||||
<Typography className="identity-name" color="text.primary" data-testid="commitment">
|
||||
<strong>Commitment: </strong>
|
||||
|
||||
{identityCommitment}
|
||||
{commitment}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography className="identity-name" color="text.primary" data-testid="commitment">
|
||||
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
ConnectedIdentityMetadata,
|
||||
IVerifiablePresentation,
|
||||
IMerkleProof,
|
||||
IIdentityConnection,
|
||||
} from "@cryptkeeperzk/types";
|
||||
|
||||
interface IUseCryptKeeperData {
|
||||
@@ -50,21 +51,14 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
const [isLocked, setIsLocked] = useState(true);
|
||||
const [proof, setProof] = useState<ISemaphoreFullProof | IRLNFullProof | IMerkleProof>();
|
||||
const [connectedCommitment, setConnectedIdentityCommitment] = useState<string>();
|
||||
const [connectedIdentityMetadata, setConnectedIdentityMetadata] = useState<ConnectedIdentityMetadata>();
|
||||
const [connectedIdentityMetadata, setConnectedIdentityMetadata] = useState<IIdentityConnection>();
|
||||
const mockIdentityCommitments: string[] = genMockIdentityCommitments();
|
||||
|
||||
const connect = useCallback(
|
||||
async (isChangeIdentity = false) => {
|
||||
await client
|
||||
?.connect(isChangeIdentity)
|
||||
.then(() => {
|
||||
if (!connectedIdentityMetadata) {
|
||||
toast(`CryptKeeper connected successfully!`, { type: "success" });
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
toast(error.message, { type: "error" });
|
||||
});
|
||||
await client?.connect(isChangeIdentity).catch((error: Error) => {
|
||||
toast(error.message, { type: "error" });
|
||||
});
|
||||
},
|
||||
[client],
|
||||
);
|
||||
@@ -187,7 +181,7 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
})
|
||||
.then((connectedIdentity) => {
|
||||
if (connectedIdentity) {
|
||||
setConnectedIdentityMetadata(connectedIdentity as ConnectedIdentityMetadata);
|
||||
setConnectedIdentityMetadata(connectedIdentity as IIdentityConnection);
|
||||
setIsLocked(false);
|
||||
toast(`Getting Identity Metadata Successfully!`, { type: "success" });
|
||||
}
|
||||
@@ -233,18 +227,6 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
});
|
||||
}, [client]);
|
||||
|
||||
const onIdentityChanged = useCallback(
|
||||
(payload: unknown) => {
|
||||
const metadata = payload as ConnectedIdentityMetadata;
|
||||
setConnectedIdentityMetadata(metadata);
|
||||
|
||||
toast(`Identity has changed! ${metadata.name}`, {
|
||||
type: "success",
|
||||
});
|
||||
},
|
||||
[setConnectedIdentityMetadata],
|
||||
);
|
||||
|
||||
const onLogout = useCallback(() => {
|
||||
setConnectedIdentityMetadata(undefined);
|
||||
setIsLocked(true);
|
||||
@@ -316,6 +298,19 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
[setIsLocked, setConnectedIdentityMetadata, getConnectedIdentityMetadata],
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(payload: unknown) => {
|
||||
setConnectedIdentityMetadata(payload as IIdentityConnection);
|
||||
setIsLocked(false);
|
||||
},
|
||||
[setConnectedIdentityMetadata, setIsLocked],
|
||||
);
|
||||
|
||||
const onDisconnect = useCallback(() => {
|
||||
setConnectedIdentityMetadata(undefined);
|
||||
setIsLocked(true);
|
||||
}, [setIsLocked, setConnectedIdentityMetadata]);
|
||||
|
||||
// Initialize Injected CryptKeeper Provider Client
|
||||
useEffect(() => {
|
||||
const cryptkeeperInjectedProvider = initializeCryptKeeper();
|
||||
@@ -334,7 +329,6 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
}
|
||||
|
||||
client.on(EventName.LOGIN, onLogin);
|
||||
client.on(EventName.IDENTITY_CHANGED, onIdentityChanged);
|
||||
client.on(EventName.LOGOUT, onLogout);
|
||||
client.on(EventName.APPROVAL, onApproval);
|
||||
client.on(EventName.ADD_VERIFIABLE_CREDENTIAL, onAddVerifiableCredential);
|
||||
@@ -345,6 +339,8 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
client.on(EventName.GROUP_MERKLE_PROOF, onGroupMerkleProof);
|
||||
client.on(EventName.IMPORT_IDENTITY, onImportIdentity);
|
||||
client.on(EventName.CREATE_IDENTITY, onCreateIdentity);
|
||||
client.on(EventName.CONNECT, onConnect);
|
||||
client.on(EventName.DISCONNECT, onDisconnect);
|
||||
|
||||
getConnectedIdentityMetadata();
|
||||
|
||||
@@ -353,8 +349,8 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
};
|
||||
}, [
|
||||
client,
|
||||
getConnectedIdentityMetadata,
|
||||
onLogout,
|
||||
onIdentityChanged,
|
||||
onAddVerifiableCredential,
|
||||
onReject,
|
||||
onRevealCommitment,
|
||||
@@ -363,6 +359,8 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
|
||||
onImportIdentity,
|
||||
onCreateIdentity,
|
||||
onApproval,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
@@ -75,11 +75,7 @@ export const Main = (): JSX.Element => {
|
||||
<div>
|
||||
<hr />
|
||||
|
||||
<ConnectedIdentity
|
||||
identityCommitment={connectedCommitment}
|
||||
identityHost={connectedIdentityMetadata?.urlOrigin}
|
||||
identityName={connectedIdentityMetadata?.name}
|
||||
/>
|
||||
<ConnectedIdentity commitment={connectedCommitment} name={connectedIdentityMetadata?.name} />
|
||||
|
||||
<Connect isChangeIdentity connect={connect} title="Connect identity" />
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@ export const METAMASK_PASSWORD = process.env.METAMASK_PASSWORD || "123456Qq@";
|
||||
export const METAMASK_SEED_PHRASE =
|
||||
process.env.METAMASK_SEED_PHRASE || "test test test test test test test test test test test junk";
|
||||
export const NETWORK = process.env.NETWORK || "mainnet";
|
||||
export const DEMO_URL = process.env.DEMO_URL || "http://localhost:1234";
|
||||
|
||||
@@ -68,7 +68,7 @@ export default class CryptKeeper extends BasePage {
|
||||
await this.page.getByText("Continue", { exact: true }).click();
|
||||
|
||||
if (!mnemonic) {
|
||||
await this.page.getByText("Copy").click();
|
||||
await this.page.getByRole("button", { name: "Copy" }).click();
|
||||
}
|
||||
|
||||
if (mnemonic) {
|
||||
|
||||
@@ -6,22 +6,23 @@ export default class Groups extends BasePage {
|
||||
async joinGroupRequest(): Promise<Page> {
|
||||
const [popup] = await Promise.all([
|
||||
this.page.context().waitForEvent("page"),
|
||||
this.page.getByText("Join test group").click({ delay: 1000 }),
|
||||
this.page.getByText("Join test group").click(),
|
||||
]);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
async joinGroup(): Promise<void> {
|
||||
async joinGroup(): Promise<Page> {
|
||||
const cryptKeeper = await this.joinGroupRequest();
|
||||
await cryptKeeper.getByText("Accept", { exact: true }).click({ delay: 1000 });
|
||||
await cryptKeeper.close();
|
||||
await cryptKeeper.getByText("Accept", { exact: true }).click();
|
||||
|
||||
return cryptKeeper;
|
||||
}
|
||||
|
||||
async generateGroupMerkleProofRequest(): Promise<Page> {
|
||||
const [popup] = await Promise.all([
|
||||
this.page.context().waitForEvent("page"),
|
||||
this.page.getByText("Generate Group Merkle Proof").click({ delay: 1000 }),
|
||||
this.page.getByText("Generate Group Merkle Proof").click(),
|
||||
]);
|
||||
|
||||
return popup;
|
||||
@@ -30,6 +31,6 @@ export default class Groups extends BasePage {
|
||||
async generateGroupMerkleProof(): Promise<void> {
|
||||
const cryptKeeper = await this.generateGroupMerkleProofRequest();
|
||||
|
||||
await cryptKeeper.getByText("Accept", { exact: true }).click({ delay: 1000 });
|
||||
await cryptKeeper.getByText("Accept", { exact: true }).click();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ test.describe("groups", () => {
|
||||
const extension = new CryptKeeper(page);
|
||||
await extension.focus();
|
||||
|
||||
await extension.groups.joinGroup();
|
||||
const popup = await extension.groups.joinGroup();
|
||||
await expect(page.getByText(/User has joined the group./)).toBeVisible();
|
||||
await popup.close();
|
||||
|
||||
await extension.groups.generateGroupMerkleProof();
|
||||
await expect(page.getByText("Group Merkle Proof has been successfully generated!")).toBeVisible();
|
||||
@@ -39,7 +40,8 @@ test.describe("groups", () => {
|
||||
const extension = new CryptKeeper(page);
|
||||
await extension.focus();
|
||||
|
||||
await extension.groups.joinGroup();
|
||||
const cryptKeeper = await extension.groups.joinGroup();
|
||||
await cryptKeeper.close();
|
||||
const popup = await extension.groups.joinGroupRequest();
|
||||
await expect(popup.getByTestId("joined-text")).toBeVisible();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DEMO_URL } from "../constants";
|
||||
import { expect, test } from "../fixtures";
|
||||
import { connectWallet, createAccount } from "../helpers/account";
|
||||
|
||||
@@ -30,7 +31,9 @@ test.describe("reveal identity commitment", () => {
|
||||
}) => {
|
||||
const cryptKeeper = await context.newPage();
|
||||
await cryptKeeper.bringToFront();
|
||||
await cryptKeeper.goto(`chrome-extension://${cryptKeeperExtensionId}/popup.html#/reveal-identity-commitment`);
|
||||
await cryptKeeper.goto(
|
||||
`chrome-extension://${cryptKeeperExtensionId}/popup.html#/reveal-identity-commitment?urlOrigin=${DEMO_URL}`,
|
||||
);
|
||||
await cryptKeeper.getByTestId("reveal-identity-commitment").click();
|
||||
|
||||
await page.bringToFront();
|
||||
@@ -47,7 +50,9 @@ test.describe("reveal identity commitment", () => {
|
||||
|
||||
const cryptKeeper = await context.newPage();
|
||||
await cryptKeeper.bringToFront();
|
||||
await cryptKeeper.goto(`chrome-extension://${cryptKeeperExtensionId}/popup.html#/reveal-identity-commitment`);
|
||||
await cryptKeeper.goto(
|
||||
`chrome-extension://${cryptKeeperExtensionId}/popup.html#/reveal-identity-commitment?urlOrigin=${DEMO_URL}`,
|
||||
);
|
||||
await cryptKeeper.getByTestId("reveal-identity-commitment").click();
|
||||
|
||||
await page.bringToFront();
|
||||
|
||||
@@ -7,8 +7,7 @@ export enum RPCExternalAction {
|
||||
JOIN_GROUP = "rpc/injector/group/join",
|
||||
GENERATE_GROUP_MERKLE_PROOF = "rpc/injector/groups/generateGroupMerkleProof",
|
||||
IMPORT_IDENTITY = "rpc/injector/identity/import",
|
||||
// TODO: the following 3 RPC calls will be refactored in another PR
|
||||
REVEAL_CONNECTED_IDENTITY_COMMITMENT = "rpc/injector/identity/revealConnectedIdentityCommitment",
|
||||
GENERATE_VERIFIABLE_PRESENTATION = "rpc/injector/credentials/generateVerifiablePresentation",
|
||||
ADD_VERIFIABLE_CREDENTIAL = "rpc/injector/credentials/addVerifiableCredential",
|
||||
REVEAL_CONNECTED_IDENTITY_COMMITMENT = "rpc/injector/identity/revealConnectedIdentityCommitment",
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ export type EventHandler = (data: unknown) => void;
|
||||
* @enum {string}
|
||||
* @readonly
|
||||
* @property {string} LOGIN - "login"
|
||||
* @property {string} IDENTITY_CHANGED - "identityChanged"
|
||||
* @property {string} LOGOUT - "logout"
|
||||
* @property {string} APPROVAL - "approval"
|
||||
* @property {string} ADD_VERIFIABLE_CREDENTIAL - "addVerifiableCredential"
|
||||
@@ -24,13 +23,12 @@ export type EventHandler = (data: unknown) => void;
|
||||
* @property {string} GROUP_MERKLE_PROOF - "groupMerkleProof"
|
||||
* @property {string} IMPORT_IDENTITY - "importIdentity"
|
||||
* @property {string} CREATE_IDENTITY - "createIdentity"
|
||||
* @property {string} CONNECT_IDENTITY - "connectIdentity"
|
||||
* @property {string} DISCONNECT_IDENTITY - "disconnectIdentity"
|
||||
* @property {string} CONNECT - "connect"
|
||||
* @property {string} DISCONNECT - "disconnect"
|
||||
* @property {string} USER_REJECT - "userReject"
|
||||
*/
|
||||
export enum EventName {
|
||||
LOGIN = "login",
|
||||
IDENTITY_CHANGED = "identityChanged",
|
||||
LOGOUT = "logout",
|
||||
APPROVAL = "approval",
|
||||
ADD_VERIFIABLE_CREDENTIAL = "addVerifiableCredential",
|
||||
@@ -41,8 +39,8 @@ export enum EventName {
|
||||
GROUP_MERKLE_PROOF = "groupMerkleProof",
|
||||
IMPORT_IDENTITY = "importIdentity",
|
||||
CREATE_IDENTITY = "createIdentity",
|
||||
CONNECT_IDENTITY = "connectIdenity",
|
||||
DISCONNECT_IDENTITY = "disconnectIdentity",
|
||||
CONNECT = "connect",
|
||||
DISCONNECT = "disconnect",
|
||||
USER_REJECT = "userReject",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface IConnectionApprovalData {
|
||||
export interface IConnectionData {
|
||||
isApproved: boolean;
|
||||
isConnected: boolean;
|
||||
canSkipApprove: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,6 @@ export interface ICreateIdentityRequestArgs {
|
||||
urlOrigin: string;
|
||||
}
|
||||
|
||||
export interface IConnectIdentityRequestArgs {
|
||||
urlOrigin: string;
|
||||
}
|
||||
|
||||
export interface IImportIdentityRequestArgs {
|
||||
trapdoor: string;
|
||||
nullifier: string;
|
||||
@@ -47,10 +43,9 @@ export interface IIdentityMetadata {
|
||||
isImported: boolean;
|
||||
account?: string;
|
||||
nonce?: number;
|
||||
urlOrigin?: string;
|
||||
}
|
||||
|
||||
export type ConnectedIdentityMetadata = Pick<IIdentityMetadata, "name" | "urlOrigin">;
|
||||
export type ConnectedIdentityMetadata = Pick<IIdentityMetadata, "name">;
|
||||
|
||||
export interface IGroupData {
|
||||
id: string;
|
||||
@@ -71,13 +66,8 @@ export interface ISetIdentityNameArgs {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ISetIdentityHostArgs {
|
||||
identityCommitment: string;
|
||||
urlOrigin: string;
|
||||
}
|
||||
|
||||
export interface IConnectIdentityArgs {
|
||||
identityCommitment: string;
|
||||
commitment: string;
|
||||
urlOrigin: string;
|
||||
}
|
||||
|
||||
@@ -99,10 +89,20 @@ export interface ICreateIdentityArgs {
|
||||
nullifier?: string;
|
||||
}
|
||||
|
||||
export interface IIdenityConnection extends ConnectedIdentityMetadata {
|
||||
export interface IIdentityConnection {
|
||||
commitment: string;
|
||||
name: string;
|
||||
urlOrigin: string;
|
||||
}
|
||||
|
||||
export interface IConnectArgs {
|
||||
commitment: string;
|
||||
}
|
||||
|
||||
export interface IRevealConnectedIdentityCommitmentArgs {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface IDeleteIdentityArgs {
|
||||
identityCommitment: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type { IConnectionApprovalData, IConnectionOptions } from "./approval";
|
||||
export type { IConnectionData, IConnectionOptions } from "./approval";
|
||||
export type { IInjectedMessageData, IInjectedProviderRequest } from "./contentScript";
|
||||
export type {
|
||||
IRequestResolutionAction,
|
||||
@@ -10,21 +10,21 @@ export type {
|
||||
export type {
|
||||
ICreateIdentityOptions,
|
||||
ICreateIdentityRequestArgs,
|
||||
IConnectIdentityRequestArgs,
|
||||
ICreateIdentityArgs,
|
||||
INewIdentityRequest,
|
||||
IIdentityMetadata,
|
||||
IGroupData,
|
||||
IIdentityData,
|
||||
ISetIdentityNameArgs,
|
||||
ISetIdentityHostArgs,
|
||||
IConnectIdentityArgs,
|
||||
ISerializedIdentity,
|
||||
ConnectedIdentityMetadata,
|
||||
IImportIdentityArgs,
|
||||
IImportIdentityRequestArgs,
|
||||
IIdenityConnection,
|
||||
IIdentityConnection,
|
||||
IConnectArgs,
|
||||
IRevealConnectedIdentityCommitmentArgs,
|
||||
IDeleteIdentityArgs,
|
||||
} from "./identity";
|
||||
export { EWallet } from "./identity";
|
||||
export type {
|
||||
|
||||
@@ -9,7 +9,6 @@ describe("identity/factory", () => {
|
||||
account: "account",
|
||||
messageSignature: "signature",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
nonce: 0,
|
||||
isImported: false,
|
||||
@@ -26,7 +25,6 @@ describe("identity/factory", () => {
|
||||
nonce: undefined,
|
||||
name: "name",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
nullifier: "12578821460373135693013277026392552769801800051254682675996381598033497431909",
|
||||
trapdoor: "8599172605644748803815316525430713607475871751016594621440814664229873275229",
|
||||
isDeterministic: false,
|
||||
|
||||
@@ -4,18 +4,7 @@ import { ICreateIdentityArgs } from "@cryptkeeperzk/types";
|
||||
import { ZkIdentitySemaphore } from "../protocols";
|
||||
|
||||
export function createNewIdentity(config: ICreateIdentityArgs): ZkIdentitySemaphore {
|
||||
const {
|
||||
name,
|
||||
messageSignature,
|
||||
account,
|
||||
groups,
|
||||
urlOrigin,
|
||||
isDeterministic,
|
||||
isImported,
|
||||
nonce,
|
||||
trapdoor,
|
||||
nullifier,
|
||||
} = config;
|
||||
const { name, messageSignature, account, groups, isDeterministic, isImported, nonce, trapdoor, nullifier } = config;
|
||||
const serialized = trapdoor && nullifier ? JSON.stringify([trapdoor, nullifier]) : undefined;
|
||||
|
||||
const identity = new Identity(serialized || messageSignature);
|
||||
@@ -25,7 +14,6 @@ export function createNewIdentity(config: ICreateIdentityArgs): ZkIdentitySemaph
|
||||
name,
|
||||
groups,
|
||||
nonce,
|
||||
urlOrigin,
|
||||
isDeterministic,
|
||||
isImported,
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ describe("protocols/ZkIdentitySemaphore", () => {
|
||||
account: "account",
|
||||
name: "Identity #1",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
isImported: false,
|
||||
};
|
||||
|
||||
@@ -43,7 +43,6 @@ describe("background/services/protocols", () => {
|
||||
account: "account",
|
||||
name: "Identity #1",
|
||||
groups: [],
|
||||
urlOrigin: "http://localhost:3000",
|
||||
isDeterministic: true,
|
||||
isImported: false,
|
||||
};
|
||||
|
||||
@@ -53,7 +53,7 @@ describe("background/services/protocols/utils", () => {
|
||||
expect(merkleProof.siblings).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
test("should get merkle proof from remote urlOrigin properly", async () => {
|
||||
test("should get merkle proof from remote url origin properly", async () => {
|
||||
const fetchSpy = jest.spyOn(global, "fetch").mockResolvedValue({
|
||||
json: () => Promise.resolve({ data: { merkleProof: defaultDeserializedMerkleProof } }),
|
||||
} as Response);
|
||||
@@ -107,7 +107,7 @@ describe("background/services/protocols/utils", () => {
|
||||
).rejects.toThrowError("ZK: Cannot get MerkleProof");
|
||||
});
|
||||
|
||||
test("should get rln verification key proof from remote urlOrigin properly", async () => {
|
||||
test("should get rln verification key proof from remote url origin properly", async () => {
|
||||
const fetchSpy = jest.spyOn(global, "fetch").mockResolvedValue({
|
||||
json: () => Promise.resolve({ data: {} }),
|
||||
} as Response);
|
||||
|
||||
Reference in New Issue
Block a user