From 1e173f0cc8c58bd29a2d2ad72e5d7747bd7b3441 Mon Sep 17 00:00:00 2001 From: Rob Knight Date: Thu, 26 Sep 2024 13:01:30 +0200 Subject: [PATCH] Embedded Zapps in Zupass (#1899) This adds functionality for Zapps to be embedded in Zupass. This can be enabled by configuring the `EMBEDDED_ZAPPS` environment variable for `passport-client`. This is a stringified JavaScript object, of the form: ```json { "Zapp name": "http://zapp-url.com" } ``` A folder will appear in Zupass using the given name, and clicking on the folder will take the user to the Zapp. Zapps will require some minimal changes to be able to support embedding, which are shown in the `test-zapp` example code. --- apps/passport-client/.env.example | 7 +- apps/passport-client/build.ts | 5 + .../EmbeddedAddSubscription.tsx | 99 ------------------- .../EmbeddedGPCProofScreen.tsx | 2 + .../EmbeddedScreens/EmbeddedScreen.tsx | 12 +-- .../screens/HomeScreen/HomeScreen.tsx | 14 +++ .../screens/ZappScreens/ZappScreen.tsx | 36 +++++++ apps/passport-client/pages/index.tsx | 4 +- apps/passport-client/src/appConfig.ts | 19 +++- apps/passport-client/src/embedded.ts | 9 +- .../passport-client/src/zapp/useZappServer.ts | 25 ++++- examples/test-zapp/package.json | 2 +- examples/test-zapp/src/apis/GPC.tsx | 2 +- examples/test-zapp/src/apis/PODSection.tsx | 4 +- .../test-zapp/src/hooks/useParcnetClient.tsx | 26 ++++- turbo.json | 1 + yarn.lock | 21 ++-- 17 files changed, 142 insertions(+), 146 deletions(-) delete mode 100644 apps/passport-client/components/screens/EmbeddedScreens/EmbeddedAddSubscription.tsx create mode 100644 apps/passport-client/components/screens/ZappScreens/ZappScreen.tsx diff --git a/apps/passport-client/.env.example b/apps/passport-client/.env.example index 4b31eba68d..5576d0b7c3 100644 --- a/apps/passport-client/.env.example +++ b/apps/passport-client/.env.example @@ -46,4 +46,9 @@ GPC_ARTIFACTS_CONFIG_OVERRIDE= # If Zapp POD signing is to be restricted, set this to "true". ZAPP_RESTRICT_ORIGINS=true # If Zapp POD signing is to be restricted, set this to the allowed origins as a JSON-stringified array. -ZAPP_ALLOWED_SIGNER_ORIGINS='["http://example.com","http://localhost:3200"]' \ No newline at end of file +ZAPP_ALLOWED_SIGNER_ORIGINS='["http://example.com","http://localhost:3200"]' + +# To configure Zapps, set this to a JSON object with folder names as keys and URLs as values. +# These will appear as folders in Zupass. +#EMBEDDED_ZAPPS={"Pondcrypto": "http://localhost:3200", "Meerkat": "http://localhost:3201"} +EMBEDDED_ZAPPS= diff --git a/apps/passport-client/build.ts b/apps/passport-client/build.ts index 573c8c85c8..4f5ed65c09 100644 --- a/apps/passport-client/build.ts +++ b/apps/passport-client/build.ts @@ -78,6 +78,11 @@ const define = { process.env.ZAPP_ALLOWED_SIGNER_ORIGINS ) } + : {}), + ...(process.env.EMBEDDED_ZAPPS !== undefined + ? { + "process.env.EMBEDDED_ZAPPS": JSON.stringify(process.env.EMBEDDED_ZAPPS) + } : {}) }; diff --git a/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedAddSubscription.tsx b/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedAddSubscription.tsx deleted file mode 100644 index c9db3fedd1..0000000000 --- a/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedAddSubscription.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { Feed } from "@pcd/passport-interface"; -import { ReactNode, useEffect, useState } from "react"; -import { useSubscriptions } from "../../../src/appHooks"; -import { SubscriptionInfoRow } from "../AddSubscriptionScreen"; - -enum FeedFetchStates { - Idle, - Fetching, - Fetched -} - -type FeedFetch = - | { - state: FeedFetchStates.Idle; - } - | { - state: FeedFetchStates.Fetching; - } - | { - state: FeedFetchStates.Fetched; - success: true; - feeds: Feed[]; - providerUrl: string; - providerName: string; - } - | { - state: FeedFetchStates.Fetched; - success: false; - error: string; - }; - -export function EmbeddedAddSubscription({ - feedUrl, - feedId -}: { - feedUrl: string; - feedId: string; -}): ReactNode { - const [feedFetch, setFeedFetch] = useState({ - state: FeedFetchStates.Idle - }); - const { value: subs } = useSubscriptions(); - useEffect(() => { - if (feedFetch.state === FeedFetchStates.Fetching) { - return; - } - - setFeedFetch({ state: FeedFetchStates.Fetching }); - - subs - .listFeeds(feedUrl) - .then((response) => { - setFeedFetch({ - state: FeedFetchStates.Fetched, - success: true, - feeds: response.feeds, - providerName: response.providerName, - providerUrl: response.providerUrl - }); - }) - .catch((e) => { - console.log(`error fetching subscription infos ${e}`); - setFeedFetch({ - state: FeedFetchStates.Fetched, - success: false, - error: - "Unable to fetch subscriptions. Check that the URL is correct, or try again later." - }); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (feedFetch.state === FeedFetchStates.Idle) { - return
Fetching feeds...
; - } else if (feedFetch.state === FeedFetchStates.Fetching) { - return
Fetching feeds...
; - } else if (feedFetch.state === FeedFetchStates.Fetched && feedFetch.success) { - const feed = feedFetch.feeds.find((f) => f.id === feedId); - if (!feed) { - return
Feed not found
; - } - return ( - - ); - } else if ( - feedFetch.state === FeedFetchStates.Fetched && - !feedFetch.success - ) { - return
{feedFetch.error}
; - } -} diff --git a/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedGPCProofScreen.tsx b/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedGPCProofScreen.tsx index a491ae1690..183a921ab8 100644 --- a/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedGPCProofScreen.tsx +++ b/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedGPCProofScreen.tsx @@ -9,6 +9,7 @@ import { v3tov4Identity } from "@pcd/semaphore-identity-pcd"; import { Fragment, ReactNode, useMemo, useState } from "react"; import styled from "styled-components"; import { useIdentityV3, usePCDsInFolder } from "../../../src/appHooks"; +import { useSyncE2EEStorage } from "../../../src/useSyncE2EEStorage"; import { ZAPP_POD_SPECIAL_FOLDER_NAME } from "../../../src/zapp/ZappServer"; import { H2 } from "../../core"; import { AppContainer } from "../../shared/AppContainer"; @@ -21,6 +22,7 @@ export function EmbeddedGPCProofScreen({ proofRequestSchema: PodspecProofRequest; callback: (result: ProveResult) => void; }): ReactNode { + useSyncE2EEStorage(); const prs = useMemo(() => { return p.proofRequest(proofRequestSchema); }, [proofRequestSchema]); diff --git a/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedScreen.tsx b/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedScreen.tsx index 58d4303fe7..7e9c484c31 100644 --- a/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedScreen.tsx +++ b/apps/passport-client/components/screens/EmbeddedScreens/EmbeddedScreen.tsx @@ -6,7 +6,6 @@ import { useEmbeddedScreenState } from "../../../src/appHooks"; import { EmbeddedScreenType } from "../../../src/embedded"; import { useSyncE2EEStorage } from "../../../src/useSyncE2EEStorage"; import { GenericProveScreen } from "../ProveScreen/GenericProveScreen"; -import { EmbeddedAddSubscription } from "./EmbeddedAddSubscription"; import { EmbeddedGPCProofScreen } from "./EmbeddedGPCProofScreen"; /** @@ -14,7 +13,6 @@ import { EmbeddedGPCProofScreen } from "./EmbeddedGPCProofScreen"; */ export function EmbeddedScreen(): ReactNode { const embeddedScreen = useEmbeddedScreenState(); - useSyncE2EEStorage(); if (!embeddedScreen) { return null; } @@ -25,15 +23,6 @@ export function EmbeddedScreen(): ReactNode { callback={embeddedScreen.screen.callback} /> ); - } else if ( - embeddedScreen.screen?.type === EmbeddedScreenType.EmbeddedAddSubscription - ) { - return ( - - ); } else if ( embeddedScreen.screen?.type === EmbeddedScreenType.EmbeddedGPCProof ) { @@ -56,6 +45,7 @@ function EmbeddedGetRequest({ request: PCDGetRequest; callback: (serialized: SerializedPCD) => void; }): ReactNode { + useSyncE2EEStorage(); const onProve = useCallback( ( pcd: PCD, diff --git a/apps/passport-client/components/screens/HomeScreen/HomeScreen.tsx b/apps/passport-client/components/screens/HomeScreen/HomeScreen.tsx index 1a9088755f..9329220d08 100644 --- a/apps/passport-client/components/screens/HomeScreen/HomeScreen.tsx +++ b/apps/passport-client/components/screens/HomeScreen/HomeScreen.tsx @@ -13,6 +13,7 @@ import React, { } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import styled, { CSSProperties } from "styled-components"; +import { appConfig } from "../../../src/appConfig"; import { useDispatch, useFolders, @@ -38,6 +39,7 @@ import { EdgeCityHome } from "../EdgeCityScreens/EdgeCityHome"; import { FrogCryptoHomeSection } from "../FrogScreens/FrogCryptoHomeSection"; import { FrogFolder } from "../FrogScreens/FrogFolder"; import { ProtocolWorldsHome } from "../ProtocolWorldsScreens/ProtocolWorldsHome"; +import { ZappScreen } from "../ZappScreens/ZappScreen"; import { FolderCard, FolderDetails, @@ -127,6 +129,8 @@ export function HomeScreenImpl(): JSX.Element | null { const isFrogCrypto = isFrogCryptoFolder(browsingFolder); const isEdgeCity = isEdgeCityFolder(browsingFolder); const isProtocolWorlds = isProtocolWorldsFolder(browsingFolder); + const isZappFolder = !!appConfig.embeddedZapps[browsingFolder]; + const shouldShowFrogCrypto = useMemo(() => { const folders = pcdCollection.value.getAllFolderNames(); const goodFolders = [ @@ -211,6 +215,14 @@ export function HomeScreenImpl(): JSX.Element | null { onFolderClick={onFolderClick} /> )} + {isRoot && + Object.keys(appConfig.embeddedZapps).map((folder) => ( + + ))} )} @@ -220,6 +232,8 @@ export function HomeScreenImpl(): JSX.Element | null { ) : isEdgeCity ? ( + ) : isZappFolder ? ( + ) : ( <> {!(foldersInFolder.length === 0 && isRoot) && } diff --git a/apps/passport-client/components/screens/ZappScreens/ZappScreen.tsx b/apps/passport-client/components/screens/ZappScreens/ZappScreen.tsx new file mode 100644 index 0000000000..bc8967b3f7 --- /dev/null +++ b/apps/passport-client/components/screens/ZappScreens/ZappScreen.tsx @@ -0,0 +1,36 @@ +import { ReactNode } from "react"; +import { useDispatch, useEmbeddedScreenState } from "../../../src/appHooks"; +import { ListenMode, useZappServer } from "../../../src/zapp/useZappServer"; +import { AdhocModal } from "../../modals/AdhocModal"; +import { EmbeddedScreen } from "../EmbeddedScreens/EmbeddedScreen"; + +export function ZappScreen({ url }: { url: string }): ReactNode { + return ( + <> + +