diff --git a/app/components/CardListV2.tsx b/app/components/CardListV2.tsx index 3a48fe049e..9ce49e6fe5 100644 --- a/app/components/CardListV2.tsx +++ b/app/components/CardListV2.tsx @@ -24,8 +24,7 @@ import { // --- Components import { LoadingCard } from "./LoadingCard"; -import { GenericOauthPlatform, PlatformProps } from "./GenericOauthPlatform"; -import { GenericEVMPlatform } from "./GenericEVMPlatform"; +import { GenericPlatform, PlatformProps } from "./GenericPlatform"; // --- Identity Providers import { SideBarContent } from "./SideBarContent"; @@ -165,7 +164,7 @@ export const CardList = ({ isLoading = false }: CardListProps): JSX.Element => { const platformProps = providers.get(currentPlatform.platform); if (platformProps) { return ( - + ); } } @@ -180,110 +179,6 @@ export const CardList = ({ isLoading = false }: CardListProps): JSX.Element => { verifyButton={undefined} /> ); - - // switch (currentPlatform?.platform) { - // case "Twitter": - // return ( - // - // ); - // case "GitPOAP": - // return ( - // - // ); - // case "Ens": - // return ; - // case "NFT": - // return ; - // case "Facebook": - // const facebook = new Facebook.FacebookPlatform(); - // return ; - // case "Github": - // return ( - // - // ); - // case "Gitcoin": - // const platformProps = providers.get("Github"); - // if (platformProps) { - // return ( - // - // ); - // } - // // case "Facebook": - // // return ; - // case "Snapshot": - // return ( - // - // ); - // // case "Google": - // // return ; - // // case "Linkedin": - // // return ; - // // case "ETH": - // // return ; - // // case "Discord": - // // return ; - // // case "POAP": - // // return ; - // // case "Ens": - // // return ; - // // case "Brightid": - // // return ; - // case "Poh": - // return ; - // // case "GTC": - // // return ; - // // case "GtcStaking": - // // return ; - // // case "NFT": - // // return ; - // case "ZkSync": - // return ( - // - // ); - // case "Lens": - // return ; - // // case "Lens": - // // return ; - // case "GnosisSafe": - // return ( - // - // ); - // default: - // return ( - // - // ); - // } }; useEffect(() => { diff --git a/app/components/GenericOauthPlatform.tsx b/app/components/GenericOauthPlatform.tsx deleted file mode 100644 index ad726f465a..0000000000 --- a/app/components/GenericOauthPlatform.tsx +++ /dev/null @@ -1,218 +0,0 @@ -// --- Methods -import React, { useContext, useEffect, useMemo, useState } from "react"; - -// --- Datadog -import { datadogLogs } from "@datadog/browser-logs"; - -import { debounce } from "ts-debounce"; -import { BroadcastChannel } from "broadcast-channel"; - -// --- Identity tools -import { - Stamp, - VerifiableCredential, - CredentialResponseBody, - VerifiableCredentialRecord, -} from "@gitcoin/passport-types"; -import { fetchVerifiableCredential } from "@gitcoin/passport-identity/dist/commonjs/src/credentials"; - -// --- Style Components -import { SideBarContent } from "./SideBarContent"; -import { DoneToastContent } from "./DoneToastContent"; -import { useToast } from "@chakra-ui/react"; - -// --- Context -import { CeramicContext } from "../context/ceramicContext"; -import { UserContext } from "../context/userContext"; - -// --- Types -import { PlatformGroupSpec } from "@gitcoin/passport-platforms/dist/commonjs/src/types"; -import { - Platform, - AccessTokenResult, - Proofs, - AppContext, - ProviderPayload, -} from "@gitcoin/passport-platforms/dist/commonjs/src/types"; -import { getPlatformSpec, PROVIDER_ID } from "@gitcoin/passport-platforms/dist/commonjs/src/platforms-config"; - -export type PlatformProps = { - // platformId: string; - platformgroupspec: PlatformGroupSpec[]; - platform: Platform; -}; - -function generateUID(length: number) { - return window - .btoa( - Array.from(window.crypto.getRandomValues(new Uint8Array(length * 2))) - .map((b) => String.fromCharCode(b)) - .join("") - ) - .replace(/[+/]/g, "") - .substring(0, length); -} - -export const GenericOauthPlatform = ({ platformgroupspec, platform }: PlatformProps): JSX.Element => { - const { address, signer } = useContext(UserContext); - const { handleAddStamps, allProvidersState } = useContext(CeramicContext); - const [isLoading, setLoading] = useState(false); - const [canSubmit, setCanSubmit] = useState(false); - - // find all providerIds - const providerIds = useMemo( - () => - platformgroupspec?.reduce((all, stamp) => { - return all.concat(stamp.providers?.map((provider) => provider.name as PROVIDER_ID)); - }, [] as PROVIDER_ID[]) || [], - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); - - // SelectedProviders will be passed in to the sidebar to be filled there... - const [verifiedProviders, setVerifiedProviders] = useState( - providerIds.filter((providerId) => { - return typeof allProvidersState[providerId]?.stamp?.credential !== "undefined"; - }) - ); - // SelectedProviders will be passed in to the sidebar to be filled there... - const [selectedProviders, setSelectedProviders] = useState([...verifiedProviders]); - - // any time we change selection state... - useEffect(() => { - if (selectedProviders.length !== verifiedProviders.length) { - setCanSubmit(true); - } - if (selectedProviders.length === 0) { - setCanSubmit(false); - } - }, [selectedProviders, verifiedProviders]); - - // --- Chakra functions - const toast = useToast(); - - async function fetchCredential(proofs: Proofs): Promise { - // fetch VCs for only the selectedProviders - const vcs = await fetchVerifiableCredential( - process.env.NEXT_PUBLIC_PASSPORT_IAM_URL || "", - { - type: platform.platformId, - types: selectedProviders, - version: "0.0.0", - address: address || "", - proofs, - }, - signer as { signMessage: (message: string) => Promise } - ); - await setVerifiedCredentialState(vcs); - } - - async function setVerifiedCredentialState(verified: VerifiableCredentialRecord) { - // because we provided a types array in the params we expect to receive a credentials array in the response... - const vcs = - verified.credentials - ?.map((cred: CredentialResponseBody): Stamp | undefined => { - if (!cred.error) { - // add each of the requested/received stamps to the passport... - return { - provider: cred.record?.type as PROVIDER_ID, - credential: cred.credential as VerifiableCredential, - }; - } - }) - .filter((v: Stamp | undefined) => v) || []; - // Add all the stamps to the passport at once - await handleAddStamps(vcs as Stamp[]); - // report success to datadog - datadogLogs.logger.info("Successfully saved Stamp", { platform: platform.platformId }); - // grab all providers who are verified from the verify response - const actualVerifiedProviders = providerIds.filter( - (providerId: string | undefined) => - !!vcs.find((vc: Stamp | undefined) => vc?.credential?.credentialSubject?.provider === providerId) - ); - // both verified and selected should look the same after save - setVerifiedProviders([...actualVerifiedProviders]); - setSelectedProviders([...actualVerifiedProviders]); - // reset can submit state - setCanSubmit(false); - - toast({ - duration: 5000, - isClosable: true, - render: (result: any) => ( - - ), - }); - } - - const state = `${platform.path}-` + generateUID(10); - - const waitForRedirect = (timeout?: number): Promise => { - const channel = new BroadcastChannel(`${platform.path}_oauth_channel`); - const waitForRedirect = new Promise((resolve, reject) => { - // Listener to watch for oauth redirect response on other windows (on the same host) - function listenForRedirect(e: { target: string; data: { code: string; state: string } }) { - // when receiving oauth response from a spawned child run fetchVerifiableCredential - if (e.target === platform.path) { - // pull data from message - const queryCode = e.data.code; - const queryState = e.data.state; - datadogLogs.logger.info("Saving Stamp", { platform: platform.platformId }); - try { - resolve({ code: queryCode, state: queryState }); - } catch (e) { - datadogLogs.logger.error("Error saving Stamp", { platform: platform.platformId }); - console.error(e); - reject(e); - } - } - } - // event handler will listen for messages from the child (debounced to avoid multiple submissions) - channel.onmessage = debounce(listenForRedirect, 300); - }).finally(() => { - channel.close(); - }); - return waitForRedirect; - }; - - async function initiateFetchCredential() { - try { - // fetch and store credential - setLoading(true); - const providerPayload = await platform.getProviderPayload({ state, window, screen, waitForRedirect }); - // TODO: use only one of Proofs or ProfiderPayload and drop the other - await fetchCredential(providerPayload as unknown as Proofs); - } catch (e) { - datadogLogs.logger.error("Error saving Stamp", { platform: platform.platformId }); - console.error(e); - } - setLoading(false); - } - - return ( - - Verify - - } - /> - ); -}; diff --git a/app/components/GenericEVMPlatform.tsx b/app/components/GenericPlatform.tsx similarity index 78% rename from app/components/GenericEVMPlatform.tsx rename to app/components/GenericPlatform.tsx index ccc70691d8..aa008779fb 100644 --- a/app/components/GenericEVMPlatform.tsx +++ b/app/components/GenericPlatform.tsx @@ -11,6 +11,7 @@ import { CredentialResponseBody, VerifiableCredentialRecord, } from "@gitcoin/passport-types"; +import { ProviderPayload } from "@gitcoin/passport-platforms/dist/commonjs/src/types"; import { fetchVerifiableCredential } from "@gitcoin/passport-identity/dist/commonjs/src/credentials"; // --- Style Components @@ -29,7 +30,10 @@ import { getPlatformSpec } from "@gitcoin/passport-platforms/dist/commonjs/platf // --- Helpers import { difference } from "../utils/helpers"; -type PlatformProps = { +import { debounce } from "ts-debounce"; +import { BroadcastChannel } from "broadcast-channel"; + +export type PlatformProps = { platFormGroupSpec: PlatformGroupSpec[]; platform: Platform; }; @@ -37,7 +41,18 @@ type PlatformProps = { const iamUrl = process.env.NEXT_PUBLIC_PASSPORT_IAM_URL || ""; const rpcUrl = process.env.NEXT_PUBLIC_RPC_URL; -export const GenericEVMPlatform = ({ platFormGroupSpec, platform }: PlatformProps): JSX.Element => { +function generateUID(length: number) { + return window + .btoa( + Array.from(window.crypto.getRandomValues(new Uint8Array(length * 2))) + .map((b) => String.fromCharCode(b)) + .join("") + ) + .replace(/[+/]/g, "") + .substring(0, length); +} + +export const GenericPlatform = ({ platFormGroupSpec, platform }: PlatformProps): JSX.Element => { const { address, signer } = useContext(UserContext); const { handleAddStamps, handleDeleteStamps, allProvidersState } = useContext(CeramicContext); const [isLoading, setLoading] = useState(false); @@ -74,12 +89,43 @@ export const GenericEVMPlatform = ({ platFormGroupSpec, platform }: PlatformProp } }, [selectedProviders, verifiedProviders]); + const waitForRedirect = (timeout?: number): Promise => { + const channel = new BroadcastChannel(`${platform.path}_oauth_channel`); + const waitForRedirect = new Promise((resolve, reject) => { + // Listener to watch for oauth redirect response on other windows (on the same host) + function listenForRedirect(e: { target: string; data: { code: string; state: string } }) { + // when receiving oauth response from a spawned child run fetchVerifiableCredential + if (e.target === platform.path) { + // pull data from message + const queryCode = e.data.code; + const queryState = e.data.state; + datadogLogs.logger.info("Saving Stamp", { platform: platform.platformId }); + try { + resolve({ code: queryCode, state: queryState }); + } catch (e) { + datadogLogs.logger.error("Error saving Stamp", { platform: platform.platformId }); + console.error(e); + reject(e); + } + } + } + // event handler will listen for messages from the child (debounced to avoid multiple submissions) + channel.onmessage = debounce(listenForRedirect, 300); + }).finally(() => { + channel.close(); + }); + return waitForRedirect; + }; + // fetch VCs from IAM server const handleFetchCredential = async (): Promise => { datadogLogs.logger.info("Saving Stamp", { platform: platform.platformId }); setLoading(true); setVerificationAttempted(true); try { + const state = `${platform.path}-` + generateUID(10); + const providerPayload = (await platform.getProviderPayload({ state, window, screen, waitForRedirect })) as {}; + const verified: VerifiableCredentialRecord = await fetchVerifiableCredential( iamUrl, { @@ -87,7 +133,7 @@ export const GenericEVMPlatform = ({ platFormGroupSpec, platform }: PlatformProp types: selectedProviders, version: "0.0.0", address: address || "", - proofs: {}, + proofs: providerPayload, rpcUrl, }, signer as { signMessage: (message: string) => Promise } diff --git a/platforms/src/Gitcoin/App-Bindings.ts b/platforms/src/Gitcoin/App-Bindings.ts index 174fb7d854..d83232b6fb 100644 --- a/platforms/src/Gitcoin/App-Bindings.ts +++ b/platforms/src/Gitcoin/App-Bindings.ts @@ -7,19 +7,37 @@ export class GitcoinPlatform implements Platform { clientId: string = null; redirectUri: string = null; - async getProviderPayload(appContext: AppContext): Promise { - return {}; - } - - constructor(options: PlatformOptions = {}) { this.clientId = options.clientId as string; this.redirectUri = options.redirectUri as string; } + async getProviderPayload(appContext: AppContext): Promise { + const authUrl: string = await this.getOAuthUrl(appContext.state); + const width = 600; + const height = 800; + const left = appContext.screen.width / 2 - width / 2; + const top = appContext.screen.height / 2 - height / 2; + + // Pass data to the page via props + appContext.window.open( + authUrl, + "_blank", + "toolbar=no, location=no, directories=no, status=no, menubar=no, resizable=no, copyhistory=no, width=" + + width + + ", height=" + + height + + ", top=" + + top + + ", left=" + + left + ); + + return appContext.waitForRedirect(); + } + async getOAuthUrl(state: string): Promise { const githubUrl = `https://github.com/login/oauth/authorize?client_id=${this.clientId}&redirect_uri=${this.redirectUri}&state=${state}`; - return githubUrl; } } diff --git a/platforms/src/Github/App-Bindings.ts b/platforms/src/Github/App-Bindings.ts index 8b2be9cec6..62169b7f75 100644 --- a/platforms/src/Github/App-Bindings.ts +++ b/platforms/src/Github/App-Bindings.ts @@ -13,9 +13,6 @@ export class GithubPlatform implements Platform { } async getProviderPayload(appContext: AppContext): Promise { - // TODO: open the BroadCastChannel - // TODO: register the event handler - onmessage - const authUrl: string = await this.getOAuthUrl(appContext.state); const width = 600; const height = 800; diff --git a/platforms/src/GnosisSafe/App-Bindings.ts b/platforms/src/GnosisSafe/App-Bindings.ts index a7003da562..15d8264133 100644 --- a/platforms/src/GnosisSafe/App-Bindings.ts +++ b/platforms/src/GnosisSafe/App-Bindings.ts @@ -2,16 +2,16 @@ import { AppContext, Platform, PlatformOptions, ProviderPayload } from "../types"; export class GnosisSafePlatform implements Platform { + platformId = "GnosisSafe"; + path = "GnosisSafe"; + clientId: string = null; + redirectUri: string = null; + getOAuthUrl(state: string): Promise { throw new Error("Method not implemented."); } + async getProviderPayload(appContext: AppContext): Promise { return {}; } - - - platformId = "GnosisSafe"; - path = "GnosisSafe"; - clientId: string = null; - redirectUri: string = null; } diff --git a/platforms/src/NFT/App-Bindings.ts b/platforms/src/NFT/App-Bindings.ts index f356370262..32879ebdd1 100644 --- a/platforms/src/NFT/App-Bindings.ts +++ b/platforms/src/NFT/App-Bindings.ts @@ -2,16 +2,16 @@ import { AppContext, Platform, PlatformOptions, ProviderPayload } from "../types"; export class NFTPlatform implements Platform { + platformId = "NFT"; + path = "NFT"; + clientId: string = null; + redirectUri: string = null; + async getProviderPayload(appContext: AppContext): Promise { return {}; } - getOAuthUrl(state: string): Promise { throw new Error("Method not implemented."); } - platformId = "NFT"; - path = "NFT"; - clientId: string = null; - redirectUri: string = null; -}; +}