{user ? (
@@ -65,13 +41,13 @@ export const GlobalSignIn = () => {
This Salesforce user account will be used by all Deskpro agents
diff --git a/src/pages/admin/callback/AdminCallbackPage.tsx b/src/pages/admin/callback/AdminCallbackPage.tsx
new file mode 100644
index 0000000..f904b6e
--- /dev/null
+++ b/src/pages/admin/callback/AdminCallbackPage.tsx
@@ -0,0 +1,36 @@
+import { CopyToClipboardInput, LoadingSpinner, OAuth2Result, useInitialisedDeskproAppClient, } from "@deskpro/app-sdk";
+import { P1 } from "@deskpro/deskpro-ui";
+import { useState } from "react";
+import type { FC } from "react";
+
+const AdminCallbackPage: FC = () => {
+ const [callbackUrl, setCallbackUrl] = useState
(null);
+
+ useInitialisedDeskproAppClient(async (client) => {
+ const oauth2 = await client.startOauth2Local(
+ ({ callbackUrl, state }) => `https://test.my.salesforce.com/services/oauth2/authorize?response_type=code&client_id=xx&redirect_uri=${callbackUrl}&state=${state}&scope=${"refresh_token api"}`,
+ /code=(?[0-9a-f]+)/,
+ async (): Promise => ({ data: { access_token: "", refresh_token: "" } })
+ );
+
+ const url = new URL(oauth2.authorizationUrl);
+ const redirectUri = url.searchParams.get("redirect_uri");
+
+ if (redirectUri) {
+ setCallbackUrl(redirectUri);
+ }
+ });
+
+ if (!callbackUrl) {
+ return ();
+ }
+
+ return (
+ <>
+
+ The callback URL will be required during your Salesforce app setup
+ >
+ );
+};
+
+export { AdminCallbackPage }
\ No newline at end of file
diff --git a/src/pages/admin/useGlobalSignIn.ts b/src/pages/admin/useGlobalSignIn.ts
index 8da8851..cf28601 100644
--- a/src/pages/admin/useGlobalSignIn.ts
+++ b/src/pages/admin/useGlobalSignIn.ts
@@ -1,186 +1,141 @@
-import {
- adminGenericProxyFetch,
- useDeskproAppClient,
- useDeskproAppEvents,
- useInitialisedDeskproAppClient
-} from "@deskpro/app-sdk";
-import { useEffect, useMemo, useState } from "react";
-import { Settings } from "../../types";
-import { v4 as uuidv4 } from "uuid";
-import { every, isEmpty } from "lodash";
-import { AuthTokens } from "../../api/types";
import { getMePreInstalled } from "../../api/preInstallationApi";
+import { IOAuth2, OAuth2Result, useDeskproAppClient, useDeskproLatestAppContext, useInitialisedDeskproAppClient } from "@deskpro/app-sdk";
+import { Settings } from "../../types";
+import { useCallback, useState } from "react";
+import getAccessAndRefreshTokens from "../../api/getAccessAndRefreshTokens";
+interface User {
+ name: string
+ email: string
+}
export const useGlobalSignIn = () => {
const { client } = useDeskproAppClient();
- const [ settings, setSettings ] = useState(null);
- const [ callbackUrl, setCallbackUrl ] = useState(null);
- const [ poll, setPoll ] = useState<(() => Promise<{ token: string }>)|null>(null);
- const [ isLoading, setIsLoading ] = useState(false);
- const [ isBlocking, setIsBlocking ] = useState(true);
- const [ accessCode, setAccessCode ] = useState(null);
- const [ user, setUser ] = useState<{ name: string; email: string; }|null>(null);
-
- const key = useMemo(() => uuidv4(), []);
+ const [user, setUser] = useState(null);
+ const [authUrl, setAuthUrl] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [isPolling, setIsPolling] = useState(false)
+ const [oauth2Context, setOAuth2Context] = useState(null)
+ const [error, setError] = useState(null);
+ const { context } = useDeskproLatestAppContext();
+
+ const settings = context?.settings
+
+ useInitialisedDeskproAppClient(async (client) => {
+ if (!settings || context?.settings.use_deskpro_saas === undefined) {
+ // Make sure settings have loaded.
+ return
+ }
- useDeskproAppEvents({
- onAdminSettingsChange: setSettings,
- }, []);
+ const clientId = settings.client_key;
+ const mode = settings.use_deskpro_saas ? "global" : "local";
- // Initialise OAuth flow
- useInitialisedDeskproAppClient((client) => {
- (async () => {
- const { callbackUrl, poll } = await client.oauth2().getAdminGenericCallbackUrl(
- key,
- /\?code=(?.+?)&/,
- /&state=(?.+)/
- );
-
- setCallbackUrl(callbackUrl);
- setPoll(() => poll);
- })();
- }, [key]);
-
- // Build auth flow entrypoint URL
- const oAuthUrl = useMemo(() => {
- if (!every([settings?.salesforce_instance_url, settings?.client_key])) {
- return null;
+ // Ensure the is a client id if in local mode
+ if (mode === "local" && !clientId) {
+ // Reset the authURL because there might be an authURL from when
+ // the user was in global mode
+ setAuthUrl(null)
+ return
}
- const url = new URL(`${settings?.salesforce_instance_url}/services/oauth2/authorize`);
-
- url.search = new URLSearchParams({
- response_type: "code",
- client_id: settings?.client_key as string,
- redirect_uri: callbackUrl as string,
- state: key,
- scope: "refresh_token api",
- }).toString();
-
- return url;
- }, [
- settings?.salesforce_instance_url,
- settings?.client_key,
- callbackUrl,
- key
- ]);
-
- // Exchange auth code for auth/refresh tokens
+ const oAuth2Response =
+ mode === "local"
+ // Local Version (custom/self-hosted app)
+ ? await client.startOauth2Local(
+ ({ state, callbackUrl }) => {
+ return `${settings?.salesforce_instance_url}/services/oauth2/authorize?response_type=code&client_id=${clientId}&redirect_uri=${callbackUrl}&state=${state}&scope=${"refresh_token api"}`;
+ },
+ /\?code=(?.+?)&/,
+ async (code: string): Promise => {
+ const url = new URL(oAuth2Response.authorizationUrl);
+ const redirectUri = url.searchParams.get("redirect_uri");
+ if (!redirectUri) throw new Error("Failed to get callback URL");
+
+ const data = await getAccessAndRefreshTokens({ settings, accessCode: code, callbackUrl: redirectUri, client })
+ return { data };
+ }
+ )
+ // Global Proxy Service
+ : await client.startOauth2Global("3MVG9k02hQhyUgQBDJFGjHpunit6Qn7nRoDm5DY06FG..mnbGEq316N2sOU4I4qZVculsUMYaTad8cY7.0gfV");
+
+ setAuthUrl(oAuth2Response.authorizationUrl);
+ setOAuth2Context(oAuth2Response);
+ }, [context, settings])
+
useInitialisedDeskproAppClient((client) => {
- const canRequestAccessToken = every([
- accessCode,
- callbackUrl,
- settings?.salesforce_instance_url,
- settings?.client_key,
- settings?.client_secret,
- ]);
-
- if (!canRequestAccessToken) {
- return;
+ if (!oauth2Context || !settings) {
+ return
}
- const url = new URL(`${settings?.salesforce_instance_url}/services/oauth2/token`);
-
- const requestOptions: RequestInit = {
- method: "POST",
- body: new URLSearchParams({
- grant_type: "authorization_code",
- code: accessCode as string,
- client_id: settings?.client_key as string,
- client_secret: settings?.client_secret as string,
- redirect_uri: callbackUrl as string,
- }),
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- };
-
- (async () => {
- const fetch = await adminGenericProxyFetch(client);
- const response = await fetch(url.toString(), requestOptions);
- const data = await response.json();
-
- const tokens: AuthTokens = {
- accessToken: data.access_token,
- refreshToken: data.refresh_token,
- };
-
- client.setAdminSetting(JSON.stringify(tokens));
-
- setIsLoading(false);
- })();
- }, [
- accessCode,
- callbackUrl,
- settings?.salesforce_instance_url,
- settings?.client_key,
- settings?.client_secret,
- ]);
-
- // Get current Salesforce user
- useInitialisedDeskproAppClient((client) => {
- (async () => {
- if (!isEmpty(settings?.global_access_token) && settings?.salesforce_instance_url) {
- setUser(await getMePreInstalled(client, settings));
+ const startPolling = async () => {
+
+ try {
+ const result = await oauth2Context.poll()
+
+ // Update the access/refresh tokens
+ const stringifiedTokens = JSON.stringify(result.data)
+ client.setAdminSetting(stringifiedTokens);
+
+ let currentUser: User | null = null
+ try {
+ currentUser = await getMePreInstalled(client, settings)
+ if (!currentUser) {
+ throw new Error()
+ }
+ } catch {
+ throw new Error("An error occurred while verifying the user")
+ }
+
+ setUser(currentUser)
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Unknown error')
+ } finally {
+ setIsLoading(false)
+ setIsPolling(false)
}
- })();
- }, [settings?.global_access_token, settings?.salesforce_instance_url]);
-
- // Set blocking flag
- useEffect(() => {
- if (!(callbackUrl && client && poll)) {
- setIsBlocking(true);
- } else if (settings?.global_access_token && !user) {
- setIsBlocking(true);
- } else {
- setIsBlocking(false);
}
- }, [
- callbackUrl,
- client,
- poll,
- user,
- settings?.global_access_token
- ]);
+
+ if (isPolling) {
+ void startPolling()
+ }
+ }, [isPolling, oauth2Context, settings])
const signOut = () => {
client?.setAdminSetting("");
setUser(null);
- setAccessCode(null);
};
- const signIn = () => {
- poll && (async () => {
- setIsLoading(true);
- setAccessCode((await poll()).token)
- })();
- };
+ const signIn = useCallback(() => {
+ setIsLoading(true)
+ setIsPolling(true)
+ window.open(authUrl ?? "", '_blank');
+ }, [setIsLoading, authUrl]);
// Only enable the sign-in button once we have all necessary settings
- let isDisabled = ! every([
- settings?.client_key,
- settings?.client_secret,
- settings?.salesforce_instance_url,
- ]);
+ let isDisabled = false
+
+ if (settings && settings.use_deskpro_saas === false && !settings.client_key || !settings?.client_secret || !settings.salesforce_instance_url) {
+ isDisabled = true
+ }
const isInstanceUrlInvalid = settings?.salesforce_instance_url
// eslint-disable-next-line no-useless-escape
? !/https:\/\/[a-zA-Z0-9\-]+\.(sandbox|develop\.)?my\.salesforce\.com$/.test(settings.salesforce_instance_url)
: false
- ;
if (settings?.salesforce_instance_url && isInstanceUrlInvalid) {
isDisabled = true;
}
- const cancelLoading = () => setIsLoading(false);
+ const cancelLoading = () => {
+ setIsLoading(false)
+ setIsPolling(false)
+ };
return {
- callbackUrl,
user,
- oAuthUrl,
isLoading,
- isBlocking,
+ error,
+ authUrl,
isDisabled,
isInstanceUrlInvalid,
cancelLoading,
diff --git a/src/types.ts b/src/types.ts
index fac7a6a..429f82e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,6 +3,7 @@ import { HomeLayout, ListLayout, ViewLayout } from "./screens/admin/types";
export interface Settings {
client_key?: string;
client_secret?: string;
+ use_deskpro_saas?: boolean,
salesforce_instance_url?: string;
global_access_token?: string;
mapping_contact?: string;