feat: demo host connection (#490)

* feat: demo host connection

- [x] Add proof output section
- [x] Support identity creation with host
- [x] Move manifest files to project root
- [x] Add license and privacy policy to dist
- [x] Rename selected identity to connected
- [x] Support search params for popup redirection
- [x] Update demo types

* fix: create identity request from popup

* fix: routing and demo minor fixes

* fix: push missing changes

---------

Co-authored-by: 0xmad <0xmad@users.noreply.github.com>
This commit is contained in:
Anton
2023-06-15 16:04:23 +03:00
committed by GitHub
parent a929cb37b2
commit a5056363c9
26 changed files with 303 additions and 222 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 0xisk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -2,7 +2,7 @@ import React, { useEffect } from "react";
import { createRoot } from "react-dom/client";
import { ToastContainer } from "react-toastify";
import { useCryptKeeper } from "./useCryptKeeper";
import { MerkleProofType, useCryptKeeper } from "./useCryptKeeper";
import "react-toastify/dist/ReactToastify.css";
@@ -24,16 +24,7 @@ function NoConnectedIdentityCommitment() {
}
function App() {
const {
client,
isLocked,
selectedIdentity,
MerkleProofType,
connect,
createIdentity,
getIdentityCommitment,
genSemaphoreProof,
} = useCryptKeeper();
const { client, isLocked, connectedIdentity, proof, connect, createIdentity, genSemaphoreProof } = useCryptKeeper();
useEffect(() => {
connect();
@@ -43,24 +34,63 @@ function App() {
return <NotConnected onClick={connect} />;
}
if (!selectedIdentity) {
if (!connectedIdentity) {
return <NoConnectedIdentityCommitment />;
}
return (
<div>
<hr />
<div>
<h2>Identity commitment for the connected identity:</h2>
<p>{connectedIdentity.commitment}</p>
</div>
<div>
<h2>Host name for the connected identity:</h2>
<p>{connectedIdentity.host}</p>
</div>
<hr />
<div>
<h2>Create a new secret Identity</h2>
<button data-testid="create-new-identity" onClick={createIdentity}>
Create
</button>
</div>
<hr />
<div>
<h2>Semaphore</h2>
<button onClick={() => genSemaphoreProof(MerkleProofType.STORAGE_ADDRESS)}>
Generate proof from Merkle proof storage address
</button>{" "}
</button>
<br />
<br />
<button onClick={() => genSemaphoreProof(MerkleProofType.ARTIFACTS)}>
Generate proof from Merkle proof artifacts
</button>
</div>
<hr />
<div>
<h2>Semaphore Proof output:</h2>
<div>
<pre>{JSON.stringify(proof, null, 2)}</pre>
</div>
</div>
{/* <div>
<h2>RLN</h2>
<button onClick={() => genRLNProof(MerkleProofType.STORAGE_ADDRESS)}>
@@ -73,29 +103,6 @@ function App() {
</button>
</div> */}
<hr />
<div>
<h2>Get Identity Commitment</h2>
<button onClick={getIdentityCommitment}>Get</button> <br />
<br />
</div>
<hr />
<div>
<h2>Create a new Identity</h2>
<button data-testid="create-new-identity" onClick={createIdentity}>
Create
</button>{" "}
<br />
<br />
</div>
<hr />
<div>
<h2>Identity commitment for connected identity:</h2>
<p>{selectedIdentity.commitment}</p>
</div>
<ToastContainer newestOnTop={true} />
</div>
);

View File

@@ -1,27 +1,35 @@
// TODO: temp until providers package isn't ready
export interface SelectedIdentity {
import type { FullProof } from "@semaphore-protocol/proof";
import type { RLNFullProof } from "rlnjs";
export interface ConnectedIdentity {
commitment: string;
web2Provider?: string;
host?: string;
}
export interface SemaphoreProof {
fullProof: FullProof;
}
export interface CryptKeeperInjectedProvider {
accounts: () => Promise<string[]>;
connect: () => Promise<CryptKeeperInjectedProvider>;
createIdentity: () => Promise<void>;
getConnectedIdentity: () => Promise<SelectedIdentity>;
createIdentity: (payload: { host: string }) => Promise<void>;
getConnectedIdentity: () => Promise<ConnectedIdentity>;
cleanListeners: () => void;
semaphoreProof(
externalNullifier: string,
signal: string,
merkleProofArtifactsOrStorageAddress: string | unknown,
merkleProof?: unknown,
): Promise<unknown>;
): Promise<SemaphoreProof>;
rlnProof(
externalNullifier: string,
signal: string,
merkleProofArtifactsOrStorageAddress: string | unknown,
rlnIdentifier: string,
): Promise<unknown>;
): Promise<RLNFullProof>;
on: (event: string, handler: (...args: unknown[]) => void) => void;
}

View File

@@ -1,13 +1,13 @@
/* eslint-disable no-console */
import { useState, useEffect, useCallback } from "react";
import { RLN } from "rlnjs";
import { RLN, RLNFullProof } from "rlnjs";
import { bigintToHex } from "bigint-conversion";
import { Identity } from "@semaphore-protocol/identity";
import { encodeBytes32String } from "ethers";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { CryptKeeperInjectedProvider, SelectedIdentity } from "./types";
import type { CryptKeeperInjectedProvider, ConnectedIdentity, SemaphoreProof } from "./types";
const SERVER_URL = "http://localhost:8090";
@@ -24,19 +24,19 @@ const genMockIdentityCommitments = (): string[] => {
return identityCommitments;
};
enum MerkleProofType {
export enum MerkleProofType {
STORAGE_ADDRESS,
ARTIFACTS,
}
interface IUseCryptKeeperData {
client?: CryptKeeperInjectedProvider;
isLocked: boolean;
selectedIdentity: SelectedIdentity;
MerkleProofType: typeof MerkleProofType;
connectedIdentity: ConnectedIdentity;
client?: CryptKeeperInjectedProvider;
proof?: SemaphoreProof | RLNFullProof;
connect: () => void;
createIdentity: () => unknown;
getIdentityCommitment: () => void;
getConnectedIdentity: () => void;
genSemaphoreProof: (proofType: MerkleProofType) => void;
genRLNProof: (proofType: MerkleProofType) => void;
}
@@ -46,9 +46,11 @@ const initializeClient = (): Promise<CryptKeeperInjectedProvider | undefined> =>
export const useCryptKeeper = (): IUseCryptKeeperData => {
const [client, setClient] = useState<CryptKeeperInjectedProvider>();
const [isLocked, setIsLocked] = useState(true);
const [selectedIdentity, setSelectedIdentity] = useState<SelectedIdentity>({
const [proof, setProof] = useState<SemaphoreProof | RLNFullProof>();
const [connectedIdentity, setConnectedIdentity] = useState<ConnectedIdentity>({
commitment: "",
web2Provider: "",
host: "",
});
const mockIdentityCommitments: string[] = genMockIdentityCommitments();
@@ -66,12 +68,13 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
const genSemaphoreProof = async (proofType: MerkleProofType = MerkleProofType.STORAGE_ADDRESS) => {
const externalNullifier = encodeBytes32String("voting-1");
const signal = encodeBytes32String("hello-world");
let storageAddressOrArtifacts: any = `${merkleStorageAddress}/Semaphore`;
if (!mockIdentityCommitments.includes(connectedIdentity.commitment)) {
mockIdentityCommitments.push(connectedIdentity.commitment);
}
if (proofType === MerkleProofType.ARTIFACTS) {
if (!mockIdentityCommitments.includes(selectedIdentity.commitment)) {
mockIdentityCommitments.push(selectedIdentity.commitment);
}
storageAddressOrArtifacts = {
leaves: mockIdentityCommitments,
depth: 20,
@@ -79,25 +82,24 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
};
}
let toastId;
try {
toastId = toast("Generating semaphore proof...", {
type: "info",
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: false,
});
const toastId = toast("Generating semaphore proof...", {
type: "info",
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: false,
});
const proof = await client?.semaphoreProof(externalNullifier, signal, storageAddressOrArtifacts);
console.log("Semaphore proof generated successfully!", proof);
toast("Semaphore proof generated successfully!", { type: "success" });
} catch (e) {
toast("Error while generating Semaphore proof!", { type: "error" });
console.error(e);
}
toast.dismiss(toastId);
await client
?.semaphoreProof(externalNullifier, signal, storageAddressOrArtifacts)
.then((proof) => {
setProof(proof);
toast("Semaphore proof generated successfully!", { type: "success" });
})
.catch((error) => {
toast("Error while generating Semaphore proof!", { type: "error" });
console.error(error);
})
.finally(() => toast.dismiss(toastId));
};
const genRLNProof = async (proofType: MerkleProofType = MerkleProofType.STORAGE_ADDRESS) => {
@@ -105,14 +107,13 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
const signal = encodeBytes32String("hello-world");
const rlnIdentifier = RLN._genIdentifier();
const rlnIdentifierHex = bigintToHex(rlnIdentifier);
let storageAddressOrArtifacts: any = `${merkleStorageAddress}/RLN`;
if (proofType === MerkleProofType.ARTIFACTS) {
if (!mockIdentityCommitments.includes(selectedIdentity.commitment)) {
mockIdentityCommitments.push(selectedIdentity.commitment);
}
if (!mockIdentityCommitments.includes(connectedIdentity.commitment)) {
mockIdentityCommitments.push(connectedIdentity.commitment);
}
if (proofType === MerkleProofType.ARTIFACTS) {
storageAddressOrArtifacts = {
leaves: mockIdentityCommitments,
depth: 15,
@@ -120,66 +121,68 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
};
}
try {
const toastId = toast("Generating RLN proof...", {
type: "info",
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: false,
});
const toastId = toast("Generating RLN proof...", {
type: "info",
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: false,
});
const proof = await client?.rlnProof(externalNullifier, signal, storageAddressOrArtifacts, rlnIdentifierHex);
console.log("RLN proof generated successfully!", proof);
toast("RLN proof generated successfully!", { type: "success" });
toast.dismiss(toastId);
} catch (e) {
toast("Error while generating RLN proof!", { type: "error" });
console.error(e);
}
await client
?.rlnProof(externalNullifier, signal, storageAddressOrArtifacts, rlnIdentifierHex)
.then((proof) => {
setProof(proof);
toast("RLN proof generated successfully!", { type: "success" });
})
.catch((error) => {
toast("Error while generating RLN proof!", { type: "error" });
console.error(error);
})
.finally(() => toast.dismiss(toastId));
};
const getIdentityCommitment = useCallback(async () => {
const getConnectedIdentity = useCallback(async () => {
const payload = await client?.getConnectedIdentity();
if (!payload) {
return;
}
setSelectedIdentity({
setConnectedIdentity({
commitment: payload.commitment,
web2Provider: payload.web2Provider,
host: payload.host,
});
toast(`Getting Identity Commitment successfully! ${payload.commitment}`, { type: "success" });
}, [client, setSelectedIdentity]);
}, [client, setConnectedIdentity]);
const createIdentity = useCallback(() => {
client?.createIdentity();
client?.createIdentity({ host: window.location.href });
}, [client]);
const onIdentityChanged = useCallback(
(payload: unknown) => {
const { commitment, web2Provider } = payload as SelectedIdentity;
const { commitment, web2Provider, host } = payload as ConnectedIdentity;
setSelectedIdentity({ commitment, web2Provider });
setConnectedIdentity({ commitment, web2Provider, host });
toast(`Identity has changed! ${commitment}`, { type: "success" });
},
[setSelectedIdentity],
[setConnectedIdentity],
);
const onLogin = useCallback(() => {
setIsLocked(false);
getIdentityCommitment();
}, [setIsLocked, getIdentityCommitment]);
}, [setIsLocked]);
const onLogout = useCallback(() => {
setSelectedIdentity({
setConnectedIdentity({
commitment: "",
web2Provider: "",
host: "",
});
setIsLocked(true);
}, [setSelectedIdentity, setIsLocked]);
}, [setConnectedIdentity, setIsLocked]);
useEffect(() => {
if (!client) {
@@ -190,17 +193,19 @@ export const useCryptKeeper = (): IUseCryptKeeperData => {
client?.on("identityChanged", onIdentityChanged);
client?.on("logout", onLogout);
getConnectedIdentity();
return () => client?.cleanListeners();
}, [client, onLogout, onIdentityChanged, onLogin]);
return {
client,
isLocked,
selectedIdentity,
MerkleProofType,
connectedIdentity,
proof,
connect,
createIdentity,
getIdentityCommitment,
getConnectedIdentity,
genSemaphoreProof,
genRLNProof,
};

View File

@@ -27,7 +27,7 @@
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
},
"permissions": ["scripting", "clipboardWrite", "activeTab", "storage", "notifications"],
"permissions": ["scripting", "clipboardWrite", "activeTab", "storage", "notifications", "unlimitedStorage"],
"host_permissions": ["http://*/", "https://*/"],
"web_accessible_resources": [
{

View File

@@ -26,7 +26,7 @@
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
},
"permissions": ["scripting", "clipboardWrite", "activeTab", "storage", "notifications"],
"permissions": ["scripting", "clipboardWrite", "activeTab", "storage", "notifications", "unlimitedStorage"],
"host_permissions": ["http://*/", "https://*/"],
"web_accessible_resources": [
{

View File

@@ -1,9 +1,9 @@
import log from "loglevel";
import { browser } from "webextension-polyfill-ts";
import { InjectedMessageData, ReduxAction, SelectedIdentity } from "@src/types";
import { InjectedMessageData, ReduxAction, ConnectedIdentity } from "@src/types";
import { setStatus } from "@src/ui/ducks/app";
import { setSelectedCommitment } from "@src/ui/ducks/identities";
import { setConnectedIdentity } from "@src/ui/ducks/identities";
function injectScript() {
const url = browser.runtime.getURL("js/injected.js");
@@ -33,11 +33,11 @@ function injectScript() {
browser.runtime.onMessage.addListener((action: ReduxAction) => {
switch (action.type) {
case setSelectedCommitment.type: {
case setConnectedIdentity.type: {
window.postMessage(
{
target: "injected-injectedscript",
payload: [null, action.payload as SelectedIdentity],
payload: [null, action.payload as ConnectedIdentity],
nonce: "identityChanged",
},
"*",

View File

@@ -47,7 +47,7 @@ export default class BrowserUtils {
const index = tabs.findIndex((tab) => tab.active && tab.highlighted);
const searchParams = params ? `?${new URLSearchParams(params).toString()}` : "";
const tab = await this.createTab({
url: `popup.html${searchParams}`,
url: `popup.html#/${searchParams}`,
active: index >= 0,
index: index >= 0 ? index : undefined,
});

View File

@@ -92,11 +92,7 @@ export default class CryptKeeperController {
this.lockService.ensure,
this.zkIdentityService.getConnectedIdentityData,
);
this.handler.add(
RPCAction.SET_CONNECTED_IDENTITY,
this.lockService.ensure,
this.zkIdentityService.setConnectedIdentity,
);
this.handler.add(RPCAction.CONNECT_IDENTITY, this.lockService.ensure, this.zkIdentityService.connectIdentity);
this.handler.add(RPCAction.SET_IDENTITY_NAME, this.lockService.ensure, this.zkIdentityService.setIdentityName);
this.handler.add(RPCAction.SET_IDENTITY_HOST, this.lockService.ensure, this.zkIdentityService.setIdentityHost);
this.handler.add(

View File

@@ -7,7 +7,7 @@ import ZkIdentityService from "@src/background/services/zkIdentity";
import { ZERO_ADDRESS } from "@src/config/const";
import { getEnabledFeatures } from "@src/config/features";
import { CreateIdentityOptions, EWallet, IdentityStrategy } from "@src/types";
import { setSelectedCommitment } from "@src/ui/ducks/identities";
import { setConnectedIdentity } from "@src/ui/ducks/identities";
import pushMessage from "@src/util/pushMessage";
import { createNewIdentity } from "../factory";
@@ -15,7 +15,10 @@ import { createNewIdentity } from "../factory";
const mockDefaultIdentityCommitment =
bigintToHex(15206603389158210388485662342360617949291660595274505642693885456541816400294n);
const mockDefaultIdentities = [
[mockDefaultIdentityCommitment, JSON.stringify({ secret: "1234", metadata: { identityStrategy: "interrep" } })],
[
mockDefaultIdentityCommitment,
JSON.stringify({ secret: "1234", metadata: { identityStrategy: "interrep", host: "http://localhost:3000" } }),
],
];
const mockSerializedDefaultIdentities = JSON.stringify(mockDefaultIdentities);
@@ -116,7 +119,7 @@ describe("background/services/zkIdentity", () => {
expect(result).toBe(true);
expect(pushMessage).toBeCalledTimes(1);
expect(pushMessage).toBeCalledWith(
setSelectedCommitment({
setConnectedIdentity({
commitment: mockDefaultIdentityCommitment,
}),
);
@@ -126,7 +129,7 @@ describe("background/services/zkIdentity", () => {
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(
index + 1,
defaultTabs[index].id,
setSelectedCommitment({
setConnectedIdentity({
commitment: mockDefaultIdentityCommitment,
}),
);
@@ -146,7 +149,7 @@ describe("background/services/zkIdentity", () => {
describe("set connected identity", () => {
test("should set connected identity properly", async () => {
const result = await zkIdentityService.setConnectedIdentity({
const result = await zkIdentityService.connectIdentity({
identityCommitment: mockDefaultIdentityCommitment,
host: "http://localhost:3000",
});
@@ -154,7 +157,7 @@ describe("background/services/zkIdentity", () => {
expect(result).toBe(true);
expect(pushMessage).toBeCalledTimes(1);
expect(pushMessage).toBeCalledWith(
setSelectedCommitment({
setConnectedIdentity({
commitment: mockDefaultIdentityCommitment,
host: "http://localhost:3000",
web2Provider: undefined,
@@ -166,7 +169,7 @@ describe("background/services/zkIdentity", () => {
expect(browser.tabs.sendMessage).toHaveBeenNthCalledWith(
index + 1,
defaultTabs[index].id,
setSelectedCommitment({
setConnectedIdentity({
commitment: mockDefaultIdentityCommitment,
host: "http://localhost:3000",
web2Provider: undefined,
@@ -180,7 +183,7 @@ describe("background/services/zkIdentity", () => {
instance.get.mockReturnValue(undefined);
});
const result = await zkIdentityService.setConnectedIdentity({
const result = await zkIdentityService.connectIdentity({
identityCommitment: mockDefaultIdentityCommitment,
host: "http://localhost:3000",
});
@@ -260,7 +263,7 @@ describe("background/services/zkIdentity", () => {
describe("delete all identities", () => {
test("should delete all identities properly", async () => {
const isIdentitySet = await zkIdentityService.setConnectedIdentity({
const isIdentitySet = await zkIdentityService.connectIdentity({
identityCommitment: mockDefaultIdentityCommitment,
host: "http://localhost:3000",
});
@@ -320,6 +323,7 @@ describe("background/services/zkIdentity", () => {
expect(data).toStrictEqual({
commitment: mockDefaultIdentityCommitment,
web2Provider: "",
host: "http://localhost:3000",
});
});
@@ -331,6 +335,7 @@ describe("background/services/zkIdentity", () => {
expect(data).toStrictEqual({
commitment: "",
web2Provider: "",
host: "",
});
});
@@ -379,7 +384,7 @@ describe("background/services/zkIdentity", () => {
describe("create", () => {
test("should be able to request a create identity modal", async () => {
await zkIdentityService.createIdentityRequest();
await zkIdentityService.createIdentityRequest({ host: "http://localhost:3000" });
expect(browser.tabs.query).toBeCalledWith({ lastFocusedWindow: true });

View File

@@ -17,11 +17,12 @@ import {
SetIdentityNameArgs,
NewIdentityRequest,
OperationType,
SelectedIdentity,
ConnectedIdentity,
SetIdentityHostArgs,
SetConnectedIdentityArgs,
ConnectIdentityArgs,
ICreateIdentityRequestArgs,
} from "@src/types";
import { setIdentities, setSelectedCommitment } from "@src/ui/ducks/identities";
import { setIdentities, setConnectedIdentity } from "@src/ui/ducks/identities";
import { ellipsify } from "@src/util/account";
import pushMessage from "@src/util/pushMessage";
@@ -70,12 +71,13 @@ export default class ZkIdentityService implements IBackupable {
return ZkIdentityService.INSTANCE;
};
getConnectedIdentityData = async (): Promise<SelectedIdentity> => {
getConnectedIdentityData = async (): Promise<ConnectedIdentity> => {
const identity = await this.getConnectedIdentity();
return {
commitment: identity ? bigintToHex(identity.genIdentityCommitment()) : "",
web2Provider: identity?.metadata.web2Provider || "",
host: identity?.metadata.host || "",
};
};
@@ -147,7 +149,7 @@ export default class ZkIdentityService implements IBackupable {
return identities.size;
};
setConnectedIdentity = async ({ host, identityCommitment }: SetConnectedIdentityArgs): Promise<boolean> => {
connectIdentity = async ({ host, identityCommitment }: ConnectIdentityArgs): Promise<boolean> => {
const identities = await this.getIdentitiesFromStore();
return this.updateConnectedIdentity({ identities, identityCommitment, host });
@@ -182,7 +184,7 @@ export default class ZkIdentityService implements IBackupable {
const [tabs] = await Promise.all([
browser.tabs.query({ active: true }),
pushMessage(
setSelectedCommitment({
setConnectedIdentity({
commitment,
web2Provider: metadata?.web2Provider,
host: metadata?.host,
@@ -195,7 +197,7 @@ export default class ZkIdentityService implements IBackupable {
browser.tabs
.sendMessage(
tab.id as number,
setSelectedCommitment({
setConnectedIdentity({
commitment,
web2Provider: metadata?.web2Provider,
host: metadata?.host,
@@ -271,8 +273,8 @@ export default class ZkIdentityService implements IBackupable {
await this.writeConnectedIdentity("");
};
createIdentityRequest = async (): Promise<void> => {
await this.browserController.openPopup({ params: { redirect: Paths.CREATE_IDENTITY } });
createIdentityRequest = async ({ host }: ICreateIdentityRequestArgs): Promise<void> => {
await this.browserController.openPopup({ params: { redirect: Paths.CREATE_IDENTITY, host } });
};
createIdentity = async ({

View File

@@ -6,7 +6,7 @@ export enum RPCAction {
SETUP_PASSWORD = "rpc/lock/setupPassword",
CREATE_IDENTITY = "rpc/identity/createIdentity",
CREATE_IDENTITY_REQ = "rpc/identity/createIdentityRequest",
SET_CONNECTED_IDENTITY = "rpc/identity/setConnectedIdentity",
CONNECT_IDENTITY = "rpc/identity/connectIdentity",
SET_IDENTITY_NAME = "rpc/identity/setIdentityName",
SET_IDENTITY_HOST = "rpc/identity/setIdentityHost",
DELETE_IDENTITY = "rpc/identity/deleteIdentity",

View File

@@ -14,8 +14,9 @@ import {
InjectedProviderRequest,
MerkleProofArtifacts,
RLNFullProof,
SelectedIdentity,
ConnectedIdentity,
SemaphoreProof,
ICreateIdentityRequestArgs,
} from "@src/types";
import { HostPermission } from "@src/ui/ducks/permissions";
@@ -161,10 +162,10 @@ export class CryptKeeperInjectedProvider extends EventEmitter {
});
}
async getConnectedIdentity(): Promise<SelectedIdentity> {
async getConnectedIdentity(): Promise<ConnectedIdentity> {
return this.post({
method: RPCAction.GET_CONNECTED_IDENTITY_DATA,
}) as Promise<SelectedIdentity>;
}) as Promise<ConnectedIdentity>;
}
async getHostPermissions(host: string): Promise<unknown> {
@@ -184,9 +185,12 @@ export class CryptKeeperInjectedProvider extends EventEmitter {
});
}
async createIdentity(): Promise<unknown> {
async createIdentity({ host }: ICreateIdentityRequestArgs): Promise<unknown> {
return this.post({
method: RPCAction.CREATE_IDENTITY_REQ,
payload: {
host,
},
});
}

View File

@@ -1,6 +1,6 @@
import type { CreateIdentityOptions, EWallet, GroupData, IdentityStrategy } from "../identity";
export interface SelectedIdentity {
export interface ConnectedIdentity {
commitment: string;
web2Provider?: string;
host?: string;

View File

@@ -10,6 +10,10 @@ export type CreateIdentityOptions = {
name?: string;
};
export interface ICreateIdentityRequestArgs {
host: string;
}
export type NewIdentityRequest = {
strategy: IdentityStrategy;
options: CreateIdentityOptions;
@@ -57,7 +61,7 @@ export interface SetIdentityHostArgs {
host: string;
}
export interface SetConnectedIdentityArgs {
export interface ConnectIdentityArgs {
identityCommitment: string;
host: string;
}

View File

@@ -43,7 +43,7 @@ export const IdentityList = ({
);
const onCreateIdentityRequest = useCallback(() => {
dispatch(createIdentityRequest());
dispatch(createIdentityRequest({ host: "" }));
}, [dispatch]);
return (

View File

@@ -7,7 +7,7 @@ import { Provider } from "react-redux";
import { ZERO_ADDRESS } from "@src/config/const";
import { RPCAction } from "@src/constants";
import { EWallet, HistorySettings, OperationType, SelectedIdentity } from "@src/types";
import { EWallet, HistorySettings, OperationType, ConnectedIdentity } from "@src/types";
import { store } from "@src/ui/store/configureAppStore";
import postMessage from "@src/util/postMessage";
@@ -22,10 +22,10 @@ import {
IdentitiesState,
setIdentities,
setIdentityRequestPending,
setSelectedCommitment,
connectIdentity,
useIdentities,
useIdentityRequestPending,
useSelectedIdentity,
useConnectedIdentity,
fetchHistory,
useIdentityOperations,
getHistory,
@@ -77,9 +77,10 @@ describe("ui/ducks/identities", () => {
const defaultSettings: HistorySettings = { isEnabled: true };
const defaultSelectedIdentity: SelectedIdentity = {
const defaultConnectedIdentity: ConnectedIdentity = {
commitment: defaultIdentities[0].commitment,
web2Provider: defaultIdentities[0].metadata.web2Provider,
host: defaultIdentities[0].metadata.host,
};
afterEach(() => {
@@ -87,7 +88,7 @@ describe("ui/ducks/identities", () => {
});
test("should fetch identities properly", async () => {
(postMessage as jest.Mock).mockResolvedValueOnce(defaultIdentities).mockResolvedValueOnce(defaultSelectedIdentity);
(postMessage as jest.Mock).mockResolvedValueOnce(defaultIdentities).mockResolvedValueOnce(defaultConnectedIdentity);
await Promise.resolve(store.dispatch(fetchIdentities()));
const { identities } = store.getState();
@@ -100,7 +101,7 @@ describe("ui/ducks/identities", () => {
const unlinkedIdentitiesHookData = renderHook(() => useUnlinkedIdentities(), {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
const selectedIdentityHookData = renderHook(() => useSelectedIdentity(), {
const connectedIdentityHookData = renderHook(() => useConnectedIdentity(), {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
@@ -108,7 +109,7 @@ describe("ui/ducks/identities", () => {
expect(identitiesHookData.result.current).toStrictEqual(defaultIdentities);
expect(linkedIdentitiesHookData.result.current).toStrictEqual(defaultIdentities.slice(0, 1));
expect(unlinkedIdentitiesHookData.result.current).toStrictEqual(defaultIdentities.slice(1));
expect(selectedIdentityHookData.result.current).toStrictEqual(defaultIdentities[0]);
expect(connectedIdentityHookData.result.current).toStrictEqual(defaultIdentities[0]);
});
test("should fetch history properly", async () => {
@@ -176,12 +177,13 @@ describe("ui/ducks/identities", () => {
expect(identities.operations).toStrictEqual(defaultOperations);
});
test("should set selected commitment properly", async () => {
await Promise.resolve(store.dispatch(setSelectedCommitment(defaultSelectedIdentity)));
test("should set connected identity properly", async () => {
await Promise.resolve(store.dispatch(setConnectedIdentity(defaultConnectedIdentity)));
const { identities } = store.getState();
expect(identities.selected.commitment).toBe("1");
expect(identities.selected.web2Provider).toBe("twitter");
expect(identities.connected.commitment).toBe("1");
expect(identities.connected.web2Provider).toBe("twitter");
expect(identities.connected.host).toBe("http://localhost:3000");
});
test("should set identities properly", async () => {
@@ -203,10 +205,13 @@ describe("ui/ducks/identities", () => {
});
test("should call create identity request action properly", async () => {
await Promise.resolve(store.dispatch(createIdentityRequest()));
await Promise.resolve(store.dispatch(createIdentityRequest({ host: "http://localhost:3000" })));
expect(postMessage).toBeCalledTimes(1);
expect(postMessage).toBeCalledWith({ method: RPCAction.CREATE_IDENTITY_REQ });
expect(postMessage).toBeCalledWith({
method: RPCAction.CREATE_IDENTITY_REQ,
payload: { host: "http://localhost:3000" },
});
});
test("should call create identity action properly", async () => {
@@ -238,13 +243,11 @@ describe("ui/ducks/identities", () => {
});
test("should call set connected identity action properly", async () => {
await Promise.resolve(
store.dispatch(setConnectedIdentity({ identityCommitment: "1", host: "http://localhost:3000" })),
);
await Promise.resolve(store.dispatch(connectIdentity({ identityCommitment: "1", host: "http://localhost:3000" })));
expect(postMessage).toBeCalledTimes(1);
expect(postMessage).toBeCalledWith({
method: RPCAction.SET_CONNECTED_IDENTITY,
method: RPCAction.CONNECT_IDENTITY,
payload: {
identityCommitment: "1",
host: "http://localhost:3000",

View File

@@ -8,8 +8,9 @@ import {
ICreateIdentityUiArgs,
IdentityData,
Operation,
SelectedIdentity,
SetConnectedIdentityArgs,
ConnectedIdentity,
ConnectIdentityArgs,
ICreateIdentityRequestArgs,
} from "@src/types";
import postMessage from "@src/util/postMessage";
@@ -21,7 +22,7 @@ export interface IdentitiesState {
identities: IdentityData[];
operations: Operation[];
requestPending: boolean;
selected: SelectedIdentity; // This aim to be a short-term solution to the integration with Zkitter
connected: ConnectedIdentity; // This aim to be a short-term solution to the integration with Zkitter
settings?: HistorySettings;
}
@@ -30,9 +31,10 @@ const initialState: IdentitiesState = {
operations: [],
settings: undefined,
requestPending: false,
selected: {
connected: {
commitment: "",
web2Provider: "",
host: "",
},
};
@@ -40,10 +42,11 @@ const identitiesSlice = createSlice({
name: "identities",
initialState,
reducers: {
setSelectedCommitment: (state: IdentitiesState, action: PayloadAction<SelectedIdentity>) => {
state.selected = {
setConnectedIdentity: (state: IdentitiesState, action: PayloadAction<ConnectedIdentity>) => {
state.connected = {
commitment: action.payload.commitment,
web2Provider: action.payload.web2Provider,
host: action.payload.host,
};
},
@@ -65,12 +68,19 @@ const identitiesSlice = createSlice({
},
});
export const { setSelectedCommitment, setIdentities, setIdentityRequestPending, setOperations, setSettings } =
export const { setConnectedIdentity, setIdentities, setIdentityRequestPending, setOperations, setSettings } =
identitiesSlice.actions;
export const createIdentityRequest = () => async (): Promise<void> => {
await postMessage({ method: RPCAction.CREATE_IDENTITY_REQ });
};
export const createIdentityRequest =
({ host }: ICreateIdentityRequestArgs) =>
async (): Promise<void> => {
await postMessage({
method: RPCAction.CREATE_IDENTITY_REQ,
payload: {
host,
},
});
};
export const createIdentity =
({ walletType, strategy, messageSignature, groups, host, options }: ICreateIdentityUiArgs) =>
@@ -87,11 +97,11 @@ export const createIdentity =
},
});
export const setConnectedIdentity =
({ identityCommitment, host }: SetConnectedIdentityArgs) =>
export const connectIdentity =
({ identityCommitment, host }: ConnectIdentityArgs) =>
async (): Promise<boolean> =>
postMessage({
method: RPCAction.SET_CONNECTED_IDENTITY,
method: RPCAction.CONNECT_IDENTITY,
payload: {
identityCommitment,
host,
@@ -122,14 +132,15 @@ export const deleteAllIdentities = () => async (): Promise<boolean> =>
export const fetchIdentities = (): TypedThunk => async (dispatch) => {
const data = await postMessage<IdentityData[]>({ method: RPCAction.GET_IDENTITIES });
const { commitment, web2Provider } = await postMessage<SelectedIdentity>({
const { commitment, web2Provider, host } = await postMessage<ConnectedIdentity>({
method: RPCAction.GET_CONNECTED_IDENTITY_DATA,
});
dispatch(setIdentities(data));
dispatch(
setSelectedCommitment({
setConnectedIdentity({
commitment,
web2Provider,
host,
}),
);
};
@@ -177,10 +188,10 @@ export const useLinkedIdentities = (host: string): IdentityData[] =>
export const useUnlinkedIdentities = (): IdentityData[] =>
useAppSelector((state) => state.identities.identities.filter((identity) => !identity.metadata.host), deepEqual);
export const useSelectedIdentity = (): IdentityData | undefined =>
export const useConnectedIdentity = (): IdentityData | undefined =>
useAppSelector((state) => {
const { identities, selected } = state.identities;
return identities.find(({ commitment }) => commitment === selected.commitment);
const { identities, connected } = state.identities;
return identities.find(({ commitment }) => commitment === connected.commitment);
}, deepEqual);
export const useIdentityRequestPending = (): boolean =>

View File

@@ -10,12 +10,7 @@ import { useNavigate } from "react-router-dom";
import { ZERO_ADDRESS } from "@src/config/const";
import { closePopup } from "@src/ui/ducks/app";
import { useAppDispatch } from "@src/ui/ducks/hooks";
import {
fetchIdentities,
setConnectedIdentity,
useLinkedIdentities,
useUnlinkedIdentities,
} from "@src/ui/ducks/identities";
import { connectIdentity, fetchIdentities, useLinkedIdentities, useUnlinkedIdentities } from "@src/ui/ducks/identities";
import { EConnectIdentityTabs, IUseConnectIdentityData, useConnectIdentity } from "../useConnectIdentity";
@@ -39,7 +34,7 @@ jest.mock("@src/ui/ducks/app", (): unknown => ({
jest.mock("@src/ui/ducks/identities", (): unknown => ({
fetchIdentities: jest.fn(),
setConnectedIdentity: jest.fn(),
connectIdentity: jest.fn(),
useLinkedIdentities: jest.fn(),
useUnlinkedIdentities: jest.fn(),
}));
@@ -147,8 +142,8 @@ describe("ui/pages/ConnectIdentity/useConnectIdentity", () => {
expect(mockDispatch).toBeCalledTimes(3);
expect(fetchIdentities).toBeCalledTimes(1);
expect(setConnectedIdentity).toBeCalledTimes(1);
expect(setConnectedIdentity).toBeCalledWith({ identityCommitment: "1", host: "http://localhost:3000" });
expect(connectIdentity).toBeCalledTimes(1);
expect(connectIdentity).toBeCalledWith({ identityCommitment: "1", host: "http://localhost:3000" });
expect(closePopup).toBeCalledTimes(1);
expect(mockNavigate).toBeCalledTimes(1);
expect(mockNavigate).toBeCalledWith(-1);

View File

@@ -4,12 +4,7 @@ import { useNavigate } from "react-router-dom";
import { closePopup } from "@src/ui/ducks/app";
import { useAppDispatch } from "@src/ui/ducks/hooks";
import {
fetchIdentities,
setConnectedIdentity,
useLinkedIdentities,
useUnlinkedIdentities,
} from "@src/ui/ducks/identities";
import { connectIdentity, fetchIdentities, useLinkedIdentities, useUnlinkedIdentities } from "@src/ui/ducks/identities";
import type { IdentityData } from "@src/types";
@@ -63,7 +58,7 @@ export const useConnectIdentity = (): IUseConnectIdentityData => {
}, [dispatch, navigate]);
const onConnect = useCallback(async () => {
await dispatch(setConnectedIdentity({ identityCommitment: selectedIdentityCommitment as string, host }));
await dispatch(connectIdentity({ identityCommitment: selectedIdentityCommitment as string, host }));
await dispatch(closePopup()).then(() => navigate(-1));
}, [selectedIdentityCommitment, host, dispatch]);

View File

@@ -8,7 +8,7 @@ import "./home.scss";
import { useHome } from "./useHome";
const Home = (): JSX.Element => {
const { identities, selectedIdentity, refreshConnectionStatus, onSelectIdentity } = useHome();
const { identities, connectedIdentity, refreshConnectionStatus, onSelectIdentity } = useHome();
return (
<div className="w-full h-full flex flex-col home" data-testid="home-page">
@@ -22,7 +22,7 @@ const Home = (): JSX.Element => {
isShowAddNew
isShowMenu
identities={identities}
selectedCommitment={selectedIdentity?.commitment}
selectedCommitment={connectedIdentity?.commitment}
onSelect={onSelectIdentity}
/>

View File

@@ -12,8 +12,8 @@ import {
useIdentities,
fetchIdentities,
fetchHistory,
useSelectedIdentity,
setConnectedIdentity,
useConnectedIdentity,
connectIdentity,
} from "@src/ui/ducks/identities";
import { checkHostApproval } from "@src/ui/ducks/permissions";
import { useEthWallet } from "@src/ui/hooks/wallet";
@@ -38,8 +38,8 @@ jest.mock("@src/ui/ducks/identities", (): unknown => ({
fetchIdentities: jest.fn(),
fetchHistory: jest.fn(),
useIdentities: jest.fn(),
useSelectedIdentity: jest.fn(),
setConnectedIdentity: jest.fn(),
useConnectedIdentity: jest.fn(),
connectIdentity: jest.fn(),
}));
jest.mock("@src/ui/ducks/permissions", (): unknown => ({
@@ -90,7 +90,7 @@ describe("ui/pages/Home/useHome", () => {
(useIdentities as jest.Mock).mockReturnValue(defaultIdentities);
(useSelectedIdentity as jest.Mock).mockReturnValue(defaultIdentities[0]);
(useConnectedIdentity as jest.Mock).mockReturnValue(defaultIdentities[0]);
(checkHostApproval as jest.Mock).mockReturnValue(true);
});
@@ -133,7 +133,7 @@ describe("ui/pages/Home/useHome", () => {
await act(async () => Promise.resolve(result.current.onSelectIdentity("1")));
expect(setConnectedIdentity).toBeCalledTimes(1);
expect(setConnectedIdentity).toBeCalledWith({ identityCommitment: "1", host: "" });
expect(connectIdentity).toBeCalledTimes(1);
expect(connectIdentity).toBeCalledWith({ identityCommitment: "1", host: "http://localhost:3000" });
});
});

View File

@@ -2,11 +2,11 @@ import { useEffect, useCallback } from "react";
import { useAppDispatch } from "@src/ui/ducks/hooks";
import {
connectIdentity,
fetchHistory,
fetchIdentities,
setConnectedIdentity,
useIdentities,
useSelectedIdentity,
useConnectedIdentity,
} from "@src/ui/ducks/identities";
import { checkHostApproval } from "@src/ui/ducks/permissions";
import { useEthWallet } from "@src/ui/hooks/wallet";
@@ -16,7 +16,7 @@ import type { IdentityData } from "@src/types";
export interface IUseHomeData {
identities: IdentityData[];
selectedIdentity?: IdentityData;
connectedIdentity?: IdentityData;
address?: string;
refreshConnectionStatus: () => Promise<boolean>;
onSelectIdentity: (identityCommitment: string) => void;
@@ -25,7 +25,7 @@ export interface IUseHomeData {
export const useHome = (): IUseHomeData => {
const dispatch = useAppDispatch();
const identities = useIdentities();
const selectedIdentity = useSelectedIdentity();
const connectedIdentity = useConnectedIdentity();
const { address } = useEthWallet();
@@ -41,9 +41,16 @@ export const useHome = (): IUseHomeData => {
const onSelectIdentity = useCallback(
(identityCommitment: string) => {
dispatch(setConnectedIdentity({ identityCommitment, host: "" }));
const identity = identities.find(({ commitment }) => commitment === identityCommitment);
dispatch(
connectIdentity({
identityCommitment,
host: identity?.metadata.host as string,
}),
);
},
[dispatch],
[identities, dispatch],
);
useEffect(() => {
@@ -53,7 +60,7 @@ export const useHome = (): IUseHomeData => {
return {
address,
selectedIdentity,
connectedIdentity,
identities,
refreshConnectionStatus,
onSelectIdentity,

View File

@@ -81,6 +81,8 @@ describe("ui/pages/Popup/usePopup", () => {
afterEach(() => {
jest.clearAllMocks();
window.location.href = oldHref;
});
test("should return initial data", async () => {
@@ -149,16 +151,14 @@ describe("ui/pages/Popup/usePopup", () => {
test("should redirect to create identity page", async () => {
(useAppStatus as jest.Mock).mockReturnValue({ isInitialized: true, isUnlocked: true, isMnemonicGenerated: true });
const url = `${window.location.href}?redirect=${Paths.CREATE_IDENTITY}`;
const url = `${window.location.href}?redirect=${Paths.CREATE_IDENTITY}&host=http://localhost:3000`;
window.location.href = url;
const { result } = renderHook(() => usePopup());
await waitForData(result.current);
expect(mockNavigate).toBeCalledTimes(1);
expect(mockNavigate).toBeCalledWith(Paths.CREATE_IDENTITY);
window.location.href = oldHref;
expect(mockNavigate).toBeCalledWith(`${Paths.CREATE_IDENTITY}?host=${encodeURIComponent("http://localhost:3000")}`);
});
test("should redirect to login page", async () => {

View File

@@ -1,5 +1,5 @@
import log from "loglevel";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Paths } from "@src/constants";
@@ -27,9 +27,9 @@ export const usePopup = (): IUsePopupData => {
const { isInitialized, isUnlocked, isMnemonicGenerated } = useAppStatus();
const isShowRequestModal = pendingRequests.length > 0;
const url = new URL(window.location.href);
const url = new URL(window.location.href.replace("#", ""));
const redirectParam = url.searchParams.get("redirect");
const redirect = useMemo(() => redirectParam && REDIRECT_PATHS[redirectParam], [redirectParam, window.location.href]);
const redirect = redirectParam && REDIRECT_PATHS[redirectParam];
const fetchData = useCallback(async () => {
await Promise.all([dispatch(fetchStatus()), dispatch(fetchPendingRequests())]);
@@ -53,9 +53,19 @@ export const usePopup = (): IUsePopupData => {
} else if (isShowRequestModal) {
navigate(Paths.REQUESTS);
} else if (redirect) {
navigate(redirect);
url.searchParams.delete("redirect");
navigate(`${redirect}?${url.searchParams.toString()}`);
}
}, [isLoading, isInitialized, isUnlocked, isShowRequestModal, isMnemonicGenerated, redirect, navigate]);
}, [
isLoading,
isInitialized,
isUnlocked,
isShowRequestModal,
isMnemonicGenerated,
redirect,
url.searchParams.toString(),
navigate,
]);
useEffect(() => {
setIsLoading(true);

View File

@@ -42,9 +42,17 @@ module.exports = {
patterns: [
{ from: path.resolve(__dirname, "./src/static/icons"), to: path.resolve(__dirname, "./dist/[name][ext]") },
{
from: path.resolve(__dirname, `./src/manifest.${TARGET}.json`),
from: path.resolve(__dirname, `./manifest.${TARGET}.json`),
to: path.resolve(__dirname, "./dist/manifest.json"),
},
{
from: path.resolve(__dirname, `./privacy_policy.md`),
to: path.resolve(__dirname, "./dist/privacy_policy.md"),
},
{
from: path.resolve(__dirname, `./LICENSE`),
to: path.resolve(__dirname, "./dist/LICENSE"),
},
{ from: path.resolve(__dirname, "./zkeyFiles"), to: path.resolve(__dirname, "./dist/js/zkeyFiles") },
],
}),