Line data Source code
1 : import * as connection from '@ankoh/dashql-jsonschema/connection.js';
2 :
3 : import {
4 : SETUP_CANCELLED,
5 : SETUP_FAILED,
6 : SETUP_STARTED,
7 : GENERATED_PKCE_CHALLENGE,
8 : GENERATING_PKCE_CHALLENGE,
9 : RECEIVED_CORE_AUTH_CODE,
10 : RECEIVED_CORE_AUTH_TOKEN,
11 : RECEIVED_DATA_CLOUD_ACCESS_TOKEN,
12 : REQUESTING_CORE_AUTH_TOKEN,
13 : REQUESTING_DATA_CLOUD_ACCESS_TOKEN,
14 : SalesforceConnectionStateAction,
15 : SF_CHANNEL_READY,
16 : SF_CHANNEL_SETUP_STARTED,
17 : } from './salesforce_connection_state.js';
18 : import { generatePKCEChallenge } from '../../utils/pkce.js';
19 : import { sleep } from '../../utils/sleep.js';
20 : import { Dispatch } from '../../utils/variant.js';
21 : import { Logger, stringifyError } from '../../platform/logger/logger.js';
22 : import { SalesforceApiClientInterface, SalesforceDatabaseChannel } from './salesforce_api_client.js';
23 : import { SalesforceConnectorType, SalesforceSetupApi } from './salesforce_connection_setup.js';
24 : import { SalesforceConnectorConfig } from '../connector_configs.js';
25 : import { RESET_CONNECTION } from '../connection_state.js';
26 : import { HyperDatabaseChannelMock } from '../hyper/hyperdb_grpc_client_mock.js';
27 :
28 :
29 0 : export async function setupSalesforceConnection(updateState: Dispatch<SalesforceConnectionStateAction>, logger: Logger, params: connection.SalesforceConnectionParams, config: SalesforceConnectorConfig, apiClient: SalesforceApiClientInterface, abortSignal: AbortSignal): Promise<SalesforceDatabaseChannel> {
30 0 : try {
31 : // Start the authorization process
32 0 : updateState({
33 0 : type: SETUP_STARTED,
34 0 : value: params,
35 0 : });
36 0 : abortSignal.throwIfAborted()
37 :
38 : // Generate new PKCE challenge
39 0 : updateState({
40 0 : type: GENERATING_PKCE_CHALLENGE,
41 0 : value: null,
42 0 : });
43 0 : const pkceChallenge = await generatePKCEChallenge();
44 0 : abortSignal.throwIfAborted();
45 0 : updateState({
46 0 : type: GENERATED_PKCE_CHALLENGE,
47 0 : value: pkceChallenge,
48 0 : });
49 :
50 : // Wait 200ms before returning the OAuth code
51 0 : await sleep(200);
52 0 : abortSignal.throwIfAborted();
53 0 : const code = 'core-access-auth-code';
54 0 : updateState({
55 0 : type: RECEIVED_CORE_AUTH_CODE,
56 0 : value: {
57 0 : token: code,
58 0 : createdAt: new Date().toISOString()
59 0 : },
60 0 : });
61 :
62 : // Request the core access token
63 0 : updateState({
64 0 : type: REQUESTING_CORE_AUTH_TOKEN,
65 0 : value: null,
66 0 : });
67 :
68 0 : if (!config.auth?.oauthRedirect) {
69 0 : throw new Error(`Missing oauth redirect url`);
70 : }
71 0 : const coreAccessToken = await apiClient.getCoreAccessToken(
72 0 : config.auth,
73 0 : params,
74 0 : code,
75 0 : pkceChallenge.verifier,
76 0 : abortSignal,
77 0 : );
78 0 : updateState({
79 0 : type: RECEIVED_CORE_AUTH_TOKEN,
80 0 : value: coreAccessToken,
81 0 : });
82 0 : abortSignal.throwIfAborted();
83 :
84 : // Start the channel setup
85 0 : updateState({ type: REQUESTING_DATA_CLOUD_ACCESS_TOKEN, value: null });
86 0 : const token = await apiClient.getDataCloudAccessToken(coreAccessToken, abortSignal);
87 0 : abortSignal.throwIfAborted();
88 0 : updateState({ type: RECEIVED_DATA_CLOUD_ACCESS_TOKEN, value: token });
89 0 : const connParams: connection.HyperConnectionParams = {
90 0 : protocol: 'V3_HTTP',
91 0 : endpoint: token.instanceUrl ?? "",
92 0 : tls: {
93 0 : clientKeyPath: "",
94 0 : clientCertPath: "",
95 0 : caCertsPath: ""
96 0 : }
97 0 : };
98 0 : updateState({
99 0 : type: SF_CHANNEL_SETUP_STARTED,
100 0 : value: connParams,
101 0 : });
102 0 : abortSignal.throwIfAborted()
103 :
104 0 : const hyperChannel = new HyperDatabaseChannelMock();
105 0 : const sfChannel = new SalesforceDatabaseChannel(apiClient, coreAccessToken, token, hyperChannel);
106 0 : sleep(100);
107 :
108 : // Mark the channel as ready
109 0 : updateState({
110 0 : type: SF_CHANNEL_READY,
111 0 : value: sfChannel,
112 0 : });
113 0 : abortSignal.throwIfAborted();
114 :
115 0 : return sfChannel;
116 :
117 0 : } catch (error: any) {
118 0 : if (error.name === 'AbortError') {
119 0 : logger.warn("Oauth flow was aborted", {});
120 0 : updateState({
121 0 : type: SETUP_CANCELLED,
122 0 : value: error.message,
123 0 : });
124 0 : } else if (error instanceof Error) {
125 0 : logger.warn("Oauth flow failed", { "error": stringifyError(error) });
126 0 : updateState({
127 0 : type: SETUP_FAILED,
128 0 : value: {
129 0 : message: error.message
130 0 : },
131 0 : });
132 0 : }
133 :
134 : // Rethrow the error
135 0 : throw error;
136 0 : }
137 0 : }
138 :
139 0 : export function mockSalesforceAuthFlow(api: SalesforceApiClientInterface, config: SalesforceConnectorConfig, logger: Logger): (SalesforceSetupApi | null) {
140 0 : const setup = async (dispatch: Dispatch<SalesforceConnectionStateAction>, _connector: SalesforceConnectorType, params: connection.SalesforceConnectionParams, abort: AbortSignal) => {
141 0 : return setupSalesforceConnection(dispatch, logger, params, config, api, abort);
142 0 : };
143 0 : const reset = async (dispatch: Dispatch<SalesforceConnectionStateAction>) => {
144 0 : dispatch({
145 0 : type: RESET_CONNECTION,
146 0 : value: null,
147 0 : })
148 0 : };
149 0 : return { setup, reset: reset };
150 0 : };
|