Skip to content

Commit

Permalink
feat: salesforce sdk restructured
Browse files Browse the repository at this point in the history
  • Loading branch information
shrouti1507 committed Dec 23, 2024
1 parent 7002989 commit 424cb67
Show file tree
Hide file tree
Showing 10 changed files with 448 additions and 191 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { HttpClient } from "../utils/httpClient";
import { SalesforceAuth, AuthProvider, SalesforceRecord, SalesforceResponse, QueryResponse, OAuthCredentials, SalesforceDestinationConfig } from "../types/salesforceTypes";
import { TokenProvider } from "../auth/tokenProvider";
import { OAuthProvider } from "../auth/oauthProvider";

export class SalesforceClient {
private httpClient: HttpClient;

constructor(auth: SalesforceAuth) {
let authProvider: AuthProvider;
let instanceUrl: string;

if ("accessToken" in auth) {
authProvider = new OAuthProvider(auth as OAuthCredentials );
instanceUrl = auth.instanceUrl;
} else {
authProvider = new TokenProvider(auth as SalesforceDestinationConfig);
instanceUrl = auth.instanceUrl;
}

this.httpClient = new HttpClient(instanceUrl, authProvider);
}

async create(objectType: string, record: SalesforceRecord, salesforceId?: string): Promise<SalesforceResponse> {
let targetEndpoint = `/services/data/v50.0/sobjects/${objectType}`;
if (salesforceId) {
targetEndpoint += `/${salesforceId}?_HttpMethod=PATCH`;
}
return this.httpClient.post<SalesforceResponse>(targetEndpoint, record);
}

async search(objectType: string, identifierValue: string, identifierType: string): Promise<QueryResponse<SalesforceRecord>> {
return this.httpClient.get<QueryResponse<SalesforceRecord>>(`/services/data/v50.0/parameterizedSearch/?q=${identifierValue}&sobject=${objectType}&in=${identifierType}&${objectType}.fields=id,${identifierType}`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* AuthProvider interface acts as a contract for authentication mechanisms.
* Each implementation of this interface will define how to retrieve the access token.
*/
export interface AuthProvider {
/**
* Retrieves the access token required for authenticating API calls.
* @returns A Promise that resolves to the access token as a string.
*/
getAccessToken(): Promise<string>;

getAuthenticationHeader(token: string): any;

areCredentialsSet(): boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { AuthProvider } from './authContext';
import { OAuthCredentials } from '../types/salesforceTypes';

/**
* OAuthProvider is an implementation of AuthProvider that retrieves an access token using OAuth.
*/
export class OAuthProvider implements AuthProvider {
private readonly credentials: OAuthCredentials;

constructor(credentials: OAuthCredentials) {
if (!credentials.token || !credentials.instanceUrl) {
throw new Error('OAuth credentials are incomplete.');
}
this.credentials = credentials;
}

async getAccessToken(): Promise<string> {
return this.credentials.token;
}

getAuthenticationHeader(token: string): any {
return {
Authorization: `Bearer ${token}`,
};
}

areCredentialsSet(): boolean {
return !!(this.credentials && this.credentials.token && this.credentials.instanceUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import axios from 'axios';
import { AuthProvider } from './authContext';
import {
SalesforceDestinationConfig,
SF_TOKEN_REQUEST_URL,
SF_TOKEN_REQUEST_URL_SANDBOX,
} from '../types/salesforceTypes';

/**
* TokenProvider is an implementation of AuthProvider that uses a pre-existing access token.
*/
export class TokenProvider implements AuthProvider {
private credentials!: SalesforceDestinationConfig;

// Setter method for credentials to validate before setting
setCredentials(credentials: SalesforceDestinationConfig): void {
if (!credentials.consumerKey || !credentials.password || !credentials.consumerSecret) {
throw new Error('Access token is required for TokenProvider.');
}
this.credentials = credentials;
}

constructor(credentials: SalesforceDestinationConfig) {
this.setCredentials(credentials); // Use the setter method
}

async getAccessToken(): Promise<string> {
let SF_TOKEN_URL;
if (this.credentials.sandbox) {
SF_TOKEN_URL = SF_TOKEN_REQUEST_URL_SANDBOX;
} else {
SF_TOKEN_URL = SF_TOKEN_REQUEST_URL;
}

try {
const authUrl = `${SF_TOKEN_URL}?username=${
this.credentials.userName
}&password=${encodeURIComponent(this.credentials.password)}${encodeURIComponent(
this.credentials.initialAccessToken,
)}&client_id=${this.credentials.consumerKey}&client_secret=${
this.credentials.consumerSecret
}&grant_type=password`;
const response = await axios.post(authUrl);

if (response.data && response.data.access_token) {
this.credentials.instanceUrl = response.data.instance_url;
return response.data.access_token;
}
throw new Error('Failed to retrieve access token.');
} catch (error: unknown) {
if (error instanceof Error) {
throw new Error(`Error fetching access token: ${error.message}`);
} else {
throw new Error('Error fetching access token: Unknown error occurred.');
}
}
}

getAuthenticationHeader(token: string): any {
return {
Authorization: token,
};
}

areCredentialsSet(): boolean {
return !!(
this.credentials &&
this.credentials.consumerKey &&
this.credentials.password &&
this.credentials.consumerSecret &&
this.credentials.instanceUrl
);
}
}
22 changes: 22 additions & 0 deletions src/v0/destinations/salesforce/salesforce-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { AuthProvider } from './auth/authContext';
import { TokenProvider } from './auth/tokenProvider';
import { OAuthProvider } from './auth/oauthProvider';
import {
SalesforceDestinationConfig,
OAuthCredentials,
LEGACY,
OAUTH,
} from './types/salesforceTypes';

export function createAuthProvider(
authType: 'legacy' | 'oauth',
metadata: SalesforceDestinationConfig | OAuthCredentials,
): AuthProvider {
if (authType === LEGACY) {
return new TokenProvider(metadata as SalesforceDestinationConfig);
}
if (authType === OAUTH) {
return new OAuthProvider(metadata as OAuthCredentials);
}
throw new Error(`Unsupported auth type: ${authType}`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export interface OAuthCredentials {
token: string;
instanceUrl: string;
}

export interface SalesforceDestinationConfig {
initialAccessToken: string;
consumerKey: string;
consumerSecret: string;
userName: string;
password: string;
sandbox: true;
instanceUrl: string;
}

export type SalesforceAuth = SalesforceDestinationConfig | OAuthCredentials;

export interface AuthProvider {
getAccessToken(): Promise<string>;
getAuthenticationHeader(token: string): any;
}

export interface SalesforceResponse {
id: string;
success: boolean;
errors?: string[];
}

export interface SalesforceRecord {
Id: string;
Name: string;
}

export interface QueryResponse<T> {
totalSize: number;
done: boolean;
records: T[];
}

export const SF_TOKEN_REQUEST_URL = 'https://login.salesforce.com/services/oauth2/token';
export const SF_TOKEN_REQUEST_URL_SANDBOX = 'https://test.salesforce.com/services/oauth2/token';

export const LEGACY = 'legacy';
export const OAUTH = 'oauth';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import axios, { AxiosInstance } from 'axios';
import { AuthProvider } from '../types/salesforceTypes';

export class HttpClient {
private client: AxiosInstance;

private authProvider: AuthProvider;

constructor(instanceUrl: string, authProvider: AuthProvider) {
this.authProvider = authProvider;
this.client = axios.create({
baseURL: instanceUrl,
headers: {
'Content-Type': 'application/json',
},
});
}

private async addAuthHeader(): Promise<void> {
const token = await this.authProvider.getAccessToken();
this.client.defaults.headers = this.authProvider.getAuthenticationHeader(token);
}

async get<T>(url: string): Promise<T> {
// for getting the access token we are not requiring to send any headers
const response = await this.client.get<T>(url);
return response.data;
}

async post<T>(url: string, data: any): Promise<T> {
await this.addAuthHeader();
const response = await this.client.post<T>(url, data);
return response.data;
}
}
Loading

0 comments on commit 424cb67

Please sign in to comment.