@hubspot/ui-extensions-dev-server 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/DevServerState.d.ts +1 -1
- package/dist/lib/ExtensionsWebSocket.js +26 -2
- package/dist/lib/__tests__/ExtensionsWebSocket.spec.js +42 -8
- package/dist/lib/__tests__/app-functions/context.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/context.spec.js +101 -0
- package/dist/lib/__tests__/app-functions/errorReporter.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/errorReporter.spec.js +102 -0
- package/dist/lib/__tests__/app-functions/executor_v20231.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/executor_v20231.spec.js +168 -0
- package/dist/lib/__tests__/app-functions/executor_v20232.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/executor_v20232.spec.js +190 -0
- package/dist/lib/__tests__/app-functions/fixtures/constants.d.ts +18 -0
- package/dist/lib/__tests__/app-functions/fixtures/constants.js +139 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-async-fails.cjs +8 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-async-fails.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-async-succeeds.cjs +8 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-async-succeeds.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-callback-on-promise-rejected.cjs +8 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-callback-on-promise-rejected.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-callback-on-promise-resolved.cjs +8 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-callback-on-promise-resolved.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-does-not-export-main.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-does-not-export-main.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-echos-input.cjs +8 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-echos-input.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-logs.cjs +10 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-logs.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-function.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-function.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-promise-rejected.cjs +7 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-promise-rejected.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-promise-resolved.cjs +7 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-promise-resolved.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-text.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-text.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-undefined.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-returns-undefined.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-throws-error.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-throws-error.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-times-out.cjs +10 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-times-out.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-undeclared.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-undeclared.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-uses-secret.cjs +14 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.1/app.functions/func-uses-secret.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-async-fails.cjs +5 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-async-fails.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-async-succeeds.cjs +5 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-async-succeeds.d.cts +3 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-calls-callback.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-calls-callback.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-does-not-export-main.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-does-not-export-main.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-echos-input.cjs +8 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-echos-input.d.cts +5 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-logs.cjs +10 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-logs.d.cts +3 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-function.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-function.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-implicitly.cjs +7 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-implicitly.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-promise-rejected.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-promise-rejected.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-promise-resolved.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-promise-resolved.d.cts +3 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-text.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-text.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-undefined.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-returns-undefined.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-throws-error.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-throws-error.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-times-out.cjs +12 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-times-out.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-undeclared.cjs +4 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-undeclared.d.cts +1 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-uses-secret.cjs +14 -0
- package/dist/lib/__tests__/app-functions/fixtures/v2023.2/app.functions/func-uses-secret.d.cts +4 -0
- package/dist/lib/__tests__/app-functions/secrets.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/secrets.spec.js +278 -0
- package/dist/lib/__tests__/app-functions/services/AppProxyService.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/services/AppProxyService.spec.js +667 -0
- package/dist/lib/__tests__/app-functions/services/PrivateAppUserTokenManager.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/services/PrivateAppUserTokenManager.spec.js +243 -0
- package/dist/lib/__tests__/app-functions/services/services_v20231.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/services/services_v20231.spec.js +319 -0
- package/dist/lib/__tests__/app-functions/services/services_v20232.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/services/services_v20232.spec.js +302 -0
- package/dist/lib/__tests__/app-functions/setup.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/setup.js +7 -0
- package/dist/lib/__tests__/app-functions/signing.spec.d.ts +1 -0
- package/dist/lib/__tests__/app-functions/signing.spec.js +460 -0
- package/dist/lib/__tests__/server.spec.js +24 -2
- package/dist/lib/app-functions/api/privateAppUserToken.d.ts +16 -0
- package/dist/lib/app-functions/api/privateAppUserToken.js +28 -0
- package/dist/lib/app-functions/config.d.ts +4 -0
- package/dist/lib/app-functions/config.js +48 -0
- package/dist/lib/app-functions/constants.d.ts +26 -0
- package/dist/lib/app-functions/constants.js +63 -0
- package/dist/lib/app-functions/context.d.ts +3 -0
- package/dist/lib/app-functions/context.js +65 -0
- package/dist/lib/app-functions/errorReporter.d.ts +22 -0
- package/dist/lib/app-functions/errorReporter.js +42 -0
- package/dist/lib/app-functions/errors.d.ts +44 -0
- package/dist/lib/app-functions/errors.js +82 -0
- package/dist/lib/app-functions/executor.d.ts +3 -0
- package/dist/lib/app-functions/executor.js +131 -0
- package/dist/lib/app-functions/index.d.ts +4 -0
- package/dist/lib/app-functions/index.js +4 -0
- package/dist/lib/app-functions/secrets.d.ts +5 -0
- package/dist/lib/app-functions/secrets.js +56 -0
- package/dist/lib/app-functions/services/AppFunctionExecutionService.d.ts +2 -0
- package/dist/lib/app-functions/services/AppFunctionExecutionService.js +55 -0
- package/dist/lib/app-functions/services/AppProxyService.d.ts +5 -0
- package/dist/lib/app-functions/services/AppProxyService.js +196 -0
- package/dist/lib/app-functions/services/PrivateAppUserTokenManager.d.ts +22 -0
- package/dist/lib/app-functions/services/PrivateAppUserTokenManager.js +185 -0
- package/dist/lib/app-functions/services/constants.d.ts +4 -0
- package/dist/lib/app-functions/services/constants.js +4 -0
- package/dist/lib/app-functions/services/index.d.ts +3 -0
- package/dist/lib/app-functions/services/index.js +3 -0
- package/dist/lib/app-functions/services/messages.d.ts +14 -0
- package/dist/lib/app-functions/services/messages.js +36 -0
- package/dist/lib/app-functions/signing.d.ts +29 -0
- package/dist/lib/app-functions/signing.js +51 -0
- package/dist/lib/app-functions/types.d.ts +172 -0
- package/dist/lib/app-functions/types.js +6 -0
- package/dist/lib/app-functions/utils.d.ts +15 -0
- package/dist/lib/app-functions/utils.js +28 -0
- package/dist/lib/server.js +15 -4
- package/dist/lib/types.d.ts +1 -1
- package/package.json +11 -7
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { AxiosError, HttpStatusCode } from 'axios';
|
|
2
|
+
export const APP_FUNCTIONS_DIRNAME = 'app.functions';
|
|
3
|
+
export const APP_FUNCTIONS_MANIFEST_FILENAME = 'serverless.json';
|
|
4
|
+
export const DEV_SERVER_DEFAULT_PORT = 6789;
|
|
5
|
+
export const EXECUTION_TIMEOUT_MS = 15_000;
|
|
6
|
+
export const PRIVATE_APP_ACCESS_TOKEN = 'PRIVATE_APP_ACCESS_TOKEN';
|
|
7
|
+
export const SECRETS_IN_CONTEXT = [
|
|
8
|
+
PRIVATE_APP_ACCESS_TOKEN,
|
|
9
|
+
'HS_ENVIRONMENT',
|
|
10
|
+
'HS_HUBLET',
|
|
11
|
+
'HS_SERVERLESS_FUNCTION_ID',
|
|
12
|
+
];
|
|
13
|
+
export const PLATFORM_VERSION = {
|
|
14
|
+
V20231: '2023.1',
|
|
15
|
+
V20232: '2023.2',
|
|
16
|
+
V20251: '2025.1',
|
|
17
|
+
V20252: '2025.2',
|
|
18
|
+
UNSTABLE: 'unstable',
|
|
19
|
+
};
|
|
20
|
+
export const defaultServerError = {
|
|
21
|
+
status: HttpStatusCode.InternalServerError,
|
|
22
|
+
message: 'Failure observed in UI Extensions Proxy Server. Contact Hubspot Support if the problem persists',
|
|
23
|
+
category: 'INTERNAL_SERVER_ERROR',
|
|
24
|
+
};
|
|
25
|
+
export const validationError = {
|
|
26
|
+
category: 'VALIDATION_ERROR',
|
|
27
|
+
status: HttpStatusCode.BadRequest,
|
|
28
|
+
};
|
|
29
|
+
// Axios errors types: https://github.com/axios/axios/blob/v1.x/README.md#error-types
|
|
30
|
+
export const axiosErrorMappings = {
|
|
31
|
+
[AxiosError.ERR_FR_TOO_MANY_REDIRECTS]: {
|
|
32
|
+
...defaultServerError,
|
|
33
|
+
message: 'Too many redirects',
|
|
34
|
+
},
|
|
35
|
+
[AxiosError.ERR_NETWORK]: {
|
|
36
|
+
...defaultServerError,
|
|
37
|
+
message: 'Network error',
|
|
38
|
+
},
|
|
39
|
+
[AxiosError.ERR_BAD_RESPONSE]: {
|
|
40
|
+
...validationError,
|
|
41
|
+
message: 'Invalid JSON Response Body',
|
|
42
|
+
},
|
|
43
|
+
[AxiosError.ERR_BAD_REQUEST]: {
|
|
44
|
+
...validationError,
|
|
45
|
+
message: 'Invalid JSON Request Body',
|
|
46
|
+
},
|
|
47
|
+
[AxiosError.ERR_INVALID_URL]: {
|
|
48
|
+
...validationError,
|
|
49
|
+
message: 'URL is malformed',
|
|
50
|
+
},
|
|
51
|
+
[AxiosError.ETIMEDOUT]: {
|
|
52
|
+
status: HttpStatusCode.GatewayTimeout,
|
|
53
|
+
category: 'GATEWAY_TIMEOUT',
|
|
54
|
+
message: 'Gateway took too long to respond',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
export const localProxyErrorMappings = {
|
|
58
|
+
BAD_GATEWAY: {
|
|
59
|
+
status: 200,
|
|
60
|
+
category: 'BAD_GATEWAY',
|
|
61
|
+
message: 'Error returned from Request Uri',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { FunctionContext, LocalExecutionInputs, ObjectQuery, ServiceConfiguration } from './types.ts';
|
|
2
|
+
export declare function fetchObjectContext(config: ServiceConfiguration, objectQuery?: ObjectQuery, accessToken?: string): Promise<FunctionContext>;
|
|
3
|
+
export declare function buildContext(config: ServiceConfiguration, localExecutionInputs: LocalExecutionInputs, secrets: FunctionContext['secrets']): Promise<FunctionContext>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Client } from '@hubspot/api-client';
|
|
2
|
+
import { isApiException, } from "./types.js";
|
|
3
|
+
import { filterMap } from "./utils.js";
|
|
4
|
+
export async function fetchObjectContext(config, objectQuery, accessToken) {
|
|
5
|
+
if (!objectQuery) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
const { logger, accountId } = config;
|
|
9
|
+
const { objectId, objectTypeId, objectPropertyNames } = objectQuery;
|
|
10
|
+
const propertyNames = objectPropertyNames.filter((field) => field);
|
|
11
|
+
if (propertyNames.length === 0) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
if (!accessToken) {
|
|
15
|
+
const errorMessage = `Cannot fetch CRM object properties without a HubSpot API access token. This is not a problem with your code. Please copy the access token for your app at ${config.hubspotWebsiteOrigin}/private-apps/${accountId} and save it in an '.env' file in the 'app.functions' folder. For more details, please see https://developers.hubspot.com/docs/guides/crm/private-apps/serverless-functions.`;
|
|
16
|
+
throw new Error(errorMessage);
|
|
17
|
+
}
|
|
18
|
+
let object;
|
|
19
|
+
try {
|
|
20
|
+
object = await getCrmObjectByHubSpotApiClient(accessToken, config.hubspotApiOrigin, objectTypeId, objectId, propertyNames);
|
|
21
|
+
logger.debug(`Successfully fetched CRM object properties with objectTypeId~${objectTypeId} objectId~${objectId} accountId~${accountId}:`, object?.properties);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (isApiException(err) && isMaybeWrongAccessToken(err)) {
|
|
25
|
+
logger.debug(`HubSpot API error:\n${err.message}`);
|
|
26
|
+
throw new Error(`Failed to fetch CRM object properties with objectTypeId~${objectTypeId} objectId~${objectId} accountId~${accountId}. Please check that the PRIVATE_APP_ACCESS_TOKEN specified in your '.env' file matches that of your app at ${config.hubspotWebsiteOrigin}/private-apps/${accountId}.`);
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Failed to fetch CRM object properties with objectTypeId~${objectTypeId} objectId~${objectId} accountId~${accountId}: ${err}`);
|
|
29
|
+
}
|
|
30
|
+
const propertiesToSend = filterMap(object?.properties ?? {}, objectPropertyNames);
|
|
31
|
+
return {
|
|
32
|
+
propertiesToSend,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function isMaybeWrongAccessToken(err) {
|
|
36
|
+
/**
|
|
37
|
+
* Our code controls the `objectTypeId` and `objectId` that are
|
|
38
|
+
* passed to the execution service from the record page where the
|
|
39
|
+
* extension is loaded. So when we get a 400 error for
|
|
40
|
+
* `objectTypeId` being invalid or a 404 error for
|
|
41
|
+
* `objectId` being not found, it is more likely that the
|
|
42
|
+
* access token is for the wrong portal. We also include 401 errors
|
|
43
|
+
* for this check as that's a clear indication of something wrong
|
|
44
|
+
* with the access token.
|
|
45
|
+
*/
|
|
46
|
+
return [400, 401, 404].includes(err.code);
|
|
47
|
+
}
|
|
48
|
+
async function getCrmObjectByHubSpotApiClient(accessToken, basePath, objectTypeId, objectId, propertyNames) {
|
|
49
|
+
const hubspotClient = new Client({
|
|
50
|
+
accessToken,
|
|
51
|
+
basePath,
|
|
52
|
+
});
|
|
53
|
+
return await hubspotClient.crm.objects.basicApi.getById(objectTypeId, String(objectId), propertyNames);
|
|
54
|
+
}
|
|
55
|
+
export async function buildContext(config, localExecutionInputs, secrets) {
|
|
56
|
+
const { objectQuery, parameters, event } = localExecutionInputs;
|
|
57
|
+
const objectContext = await fetchObjectContext(config, objectQuery, secrets?.PRIVATE_APP_ACCESS_TOKEN);
|
|
58
|
+
return {
|
|
59
|
+
propertiesToSend: {},
|
|
60
|
+
parameters,
|
|
61
|
+
event,
|
|
62
|
+
secrets,
|
|
63
|
+
...objectContext,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
type InternalServerErrorContext = {
|
|
2
|
+
errorType: 'internal_server_error';
|
|
3
|
+
functionName: string;
|
|
4
|
+
appId: number;
|
|
5
|
+
accountId: number;
|
|
6
|
+
};
|
|
7
|
+
type LocalProxyErrorContext = {
|
|
8
|
+
errorType: 'local_proxy_error';
|
|
9
|
+
accountId: number;
|
|
10
|
+
requestUri: string;
|
|
11
|
+
method: string;
|
|
12
|
+
correlationId: string;
|
|
13
|
+
};
|
|
14
|
+
type TokenManagerErrorContext = {
|
|
15
|
+
errorType: 'token_manager_error';
|
|
16
|
+
operation: 'get_private_app_user_token';
|
|
17
|
+
appId: number;
|
|
18
|
+
accountId: number;
|
|
19
|
+
};
|
|
20
|
+
export type ErrorContext = InternalServerErrorContext | LocalProxyErrorContext | TokenManagerErrorContext;
|
|
21
|
+
export declare function reportError(error: unknown, context?: ErrorContext): void;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as Sentry from '@sentry/node';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const version = JSON.parse(readFileSync(join(import.meta.dirname, '../../../package.json'), 'utf-8')).version;
|
|
5
|
+
const DSN_KEY = 'fbfd0619a2c1af3be058700da25fe9f1';
|
|
6
|
+
const LOGFETCH_DSN = `https://${DSN_KEY}@exceptions.hubspot.com/v2/1`;
|
|
7
|
+
Sentry.init({
|
|
8
|
+
dsn: LOGFETCH_DSN,
|
|
9
|
+
release: `@hubspot/app-functions-dev-server@${version}`,
|
|
10
|
+
initialScope: {
|
|
11
|
+
tags: {
|
|
12
|
+
devServerVersion: version,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Here we set up global error handlers to catch any uncaught exceptions or unhandled promise rejections.
|
|
18
|
+
* They will wait up to 2 seconds to flush the Sentry event queue before exiting the process.
|
|
19
|
+
*/
|
|
20
|
+
process.on('uncaughtException', (error) => {
|
|
21
|
+
Sentry.captureException(error);
|
|
22
|
+
Sentry.flush(2000)
|
|
23
|
+
.then(() => process.exit(1))
|
|
24
|
+
.catch(() => process.exit(1));
|
|
25
|
+
});
|
|
26
|
+
process.on('unhandledRejection', (reason) => {
|
|
27
|
+
Sentry.captureException(reason);
|
|
28
|
+
Sentry.flush(2000)
|
|
29
|
+
.then(() => process.exit(1))
|
|
30
|
+
.catch(() => process.exit(1));
|
|
31
|
+
});
|
|
32
|
+
export function reportError(error, context) {
|
|
33
|
+
Sentry.withScope((scope) => {
|
|
34
|
+
if (context) {
|
|
35
|
+
for (const [key, value] of Object.entries(context)) {
|
|
36
|
+
scope.setTag(key, String(value));
|
|
37
|
+
}
|
|
38
|
+
scope.setExtra('errorContext', context);
|
|
39
|
+
}
|
|
40
|
+
Sentry.captureException(error);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const Reason: {
|
|
2
|
+
readonly Timeout: "TIMEOUT";
|
|
3
|
+
readonly UncaughtError: "UNCAUGHT_ERROR";
|
|
4
|
+
readonly InvalidResponse: "INVALID_RESPONSE";
|
|
5
|
+
readonly InvalidFunction: "INVALID_FUNCTION";
|
|
6
|
+
readonly FunctionNotFound: "FUNCTION_NOT_FOUND";
|
|
7
|
+
};
|
|
8
|
+
type Reason = (typeof Reason)[keyof typeof Reason];
|
|
9
|
+
export declare class ExecutionError extends Error {
|
|
10
|
+
details: ErrorDetails;
|
|
11
|
+
reason?: Reason;
|
|
12
|
+
cause?: unknown;
|
|
13
|
+
constructor(details: ErrorDetails);
|
|
14
|
+
}
|
|
15
|
+
interface ErrorDetails {
|
|
16
|
+
reason: Reason;
|
|
17
|
+
functionName: string;
|
|
18
|
+
appId?: number;
|
|
19
|
+
elapsedTimeMs?: number;
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
}
|
|
22
|
+
export declare function buildBadRequestBody({ details }: ExecutionError): {
|
|
23
|
+
status: string;
|
|
24
|
+
message: string;
|
|
25
|
+
correlationId: string;
|
|
26
|
+
errors: {
|
|
27
|
+
subCategory: string;
|
|
28
|
+
message: string;
|
|
29
|
+
context: {
|
|
30
|
+
exception: string[];
|
|
31
|
+
logId: string[];
|
|
32
|
+
appFunctionLogId: string[];
|
|
33
|
+
serverlessFunction: string[];
|
|
34
|
+
};
|
|
35
|
+
}[];
|
|
36
|
+
category: string;
|
|
37
|
+
subCategory: string;
|
|
38
|
+
};
|
|
39
|
+
export declare function buildInternalErrorBody(): {
|
|
40
|
+
status: string;
|
|
41
|
+
message: string;
|
|
42
|
+
correlationId: string;
|
|
43
|
+
};
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { formatSeconds } from "./utils.js";
|
|
2
|
+
export const Reason = {
|
|
3
|
+
Timeout: 'TIMEOUT',
|
|
4
|
+
UncaughtError: 'UNCAUGHT_ERROR',
|
|
5
|
+
InvalidResponse: 'INVALID_RESPONSE',
|
|
6
|
+
InvalidFunction: 'INVALID_FUNCTION',
|
|
7
|
+
FunctionNotFound: 'FUNCTION_NOT_FOUND',
|
|
8
|
+
};
|
|
9
|
+
export class ExecutionError extends Error {
|
|
10
|
+
details;
|
|
11
|
+
reason;
|
|
12
|
+
cause;
|
|
13
|
+
constructor(details) {
|
|
14
|
+
super();
|
|
15
|
+
this.details = details;
|
|
16
|
+
this.reason = details.reason;
|
|
17
|
+
this.cause = details.cause;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const ERROR_BUILDERS = {
|
|
21
|
+
[Reason.Timeout]: buildServerlessFunctionError_timeout,
|
|
22
|
+
[Reason.UncaughtError]: buildServerlessFunctionError_uncaughtError,
|
|
23
|
+
[Reason.InvalidResponse]: buildServerlessFunctionError_nonJsonResponse,
|
|
24
|
+
[Reason.InvalidFunction]: buildServerlessFunctionError_mainNotFound,
|
|
25
|
+
[Reason.FunctionNotFound]: buildServerlessFunctionError_functionNotFound,
|
|
26
|
+
};
|
|
27
|
+
export function buildBadRequestBody({ details }) {
|
|
28
|
+
const error = ERROR_BUILDERS[details.reason]?.(details);
|
|
29
|
+
return {
|
|
30
|
+
status: 'error',
|
|
31
|
+
message: "There was a problem reaching the app's API. Contact the app developer for more information.",
|
|
32
|
+
correlationId: 'n/a',
|
|
33
|
+
errors: error ? [error] : [],
|
|
34
|
+
category: 'VALIDATION_ERROR',
|
|
35
|
+
subCategory: 'GenXExecutionErrorType.APP_ERROR',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function buildInternalErrorBody() {
|
|
39
|
+
return {
|
|
40
|
+
status: 'error',
|
|
41
|
+
message: 'internal error',
|
|
42
|
+
correlationId: 'n/a',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function buildServerlessFunctionError_timeout({ functionName, elapsedTimeMs, }) {
|
|
46
|
+
const timestamp = new Date().toJSON(); // Ex: '2023-07-21T13:20:39.657Z'
|
|
47
|
+
const functionId = 'n/a';
|
|
48
|
+
const elapsedTime = elapsedTimeMs
|
|
49
|
+
? formatSeconds(elapsedTimeMs, 2)
|
|
50
|
+
: 'n/a seconds';
|
|
51
|
+
const errorMessage = `${timestamp} ${functionId} Task timed out after ${elapsedTime}`;
|
|
52
|
+
return buildServerlessFunctionError(functionName, errorMessage);
|
|
53
|
+
}
|
|
54
|
+
function buildServerlessFunctionError_uncaughtError({ functionName, cause, }) {
|
|
55
|
+
const errorMessage = cause ? `${cause}` : '';
|
|
56
|
+
return buildServerlessFunctionError(functionName, errorMessage);
|
|
57
|
+
}
|
|
58
|
+
function buildServerlessFunctionError_nonJsonResponse({ functionName, }) {
|
|
59
|
+
const errorMessage = 'Wrong arguments';
|
|
60
|
+
return buildServerlessFunctionError(functionName, errorMessage);
|
|
61
|
+
}
|
|
62
|
+
function buildServerlessFunctionError_mainNotFound({ functionName, }) {
|
|
63
|
+
const errorMessage = 'customerPayload.main is not a function';
|
|
64
|
+
return buildServerlessFunctionError(functionName, errorMessage);
|
|
65
|
+
}
|
|
66
|
+
function buildServerlessFunctionError_functionNotFound({ functionName, appId = 0, }) {
|
|
67
|
+
// TODO improve server side error
|
|
68
|
+
const errorMessage = `com.hubspot.apicaller.exceptions.ApiFailure: Api[name=app-functions-executor-client, httpMethod=POST, subPath=/v1/app/${appId}/function/${functionName}] Failed to process your request. Error code was '400'. Error message was:\n{"status":"error","message":"The serverless function ${functionName} doesn't exist in this project. Go to the Activity tab for the project to check its deploy status.","correlationId":"bd3bc53f-db78-4c60-9713-b8a98a6303d4","context":{"SERVERLESS_FUNCTION":["${functionName}"]},"category":"VALIDATION_ERROR","subCategory":"AppFunctionsExecutorErrorType.SERVERLESS_FUNCTION_NOT_FOUND"}`;
|
|
69
|
+
return buildServerlessFunctionError(functionName, errorMessage);
|
|
70
|
+
}
|
|
71
|
+
function buildServerlessFunctionError(functionName, errorMessage) {
|
|
72
|
+
return {
|
|
73
|
+
subCategory: 'ServerlessActionExecutionError.SERVERLESS_FUNCTION_ERROR',
|
|
74
|
+
message: `The serverless function '${functionName}' failed to execute: ${errorMessage}.`,
|
|
75
|
+
context: {
|
|
76
|
+
exception: [errorMessage],
|
|
77
|
+
logId: ['n/a'],
|
|
78
|
+
appFunctionLogId: ['n/a'],
|
|
79
|
+
serverlessFunction: [functionName],
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { FunctionCallback, LocalExecutionInputs, ServiceConfiguration } from './types.ts';
|
|
2
|
+
import { PrivateAppUserTokenManager } from './services/PrivateAppUserTokenManager.ts';
|
|
3
|
+
export declare function executeFunction(config: ServiceConfiguration, functionName: string, localExecutionInputs: LocalExecutionInputs, callback: FunctionCallback, privateAppUserTokenManager?: PrivateAppUserTokenManager): Promise<void>;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { loadFunctionInfo } from "./config.js";
|
|
5
|
+
import { loadSecrets, setProperTokenInSecrets } from "./secrets.js";
|
|
6
|
+
import { buildContext } from "./context.js";
|
|
7
|
+
import { Reason, ExecutionError } from "./errors.js";
|
|
8
|
+
import { diffLists, formatSeconds, throwUnhandledPlatformVersionError, } from "./utils.js";
|
|
9
|
+
import { PLATFORM_VERSION, PRIVATE_APP_ACCESS_TOKEN } from "./constants.js";
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
export async function executeFunction(config, functionName, localExecutionInputs, callback, privateAppUserTokenManager) {
|
|
12
|
+
const { logger } = config;
|
|
13
|
+
const { appId } = localExecutionInputs;
|
|
14
|
+
const functionInfo = loadFunctionInfo(config, functionName, appId);
|
|
15
|
+
const functionPath = path.resolve(path.join(functionInfo.srcDir, functionInfo.file));
|
|
16
|
+
if (!fs.existsSync(functionPath)) {
|
|
17
|
+
logger.error(`Could not find file ${functionPath}.`);
|
|
18
|
+
throw new ExecutionError({
|
|
19
|
+
reason: Reason.FunctionNotFound,
|
|
20
|
+
functionName,
|
|
21
|
+
appId,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
delete require.cache[require.resolve(functionPath)];
|
|
25
|
+
const { main } = require(functionPath);
|
|
26
|
+
if (!main) {
|
|
27
|
+
logger.error(`Could not find "main" export in ${functionPath}.`);
|
|
28
|
+
throw new ExecutionError({
|
|
29
|
+
reason: Reason.InvalidFunction,
|
|
30
|
+
functionName,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const savedEnv = { ...process.env };
|
|
34
|
+
const initialSecrets = loadSecrets(functionInfo.srcDir);
|
|
35
|
+
// app token from the env file
|
|
36
|
+
const privateAppToken = process.env[PRIVATE_APP_ACCESS_TOKEN];
|
|
37
|
+
// fetch the user token from the local dev service
|
|
38
|
+
const privateAppUserToken = typeof appId === 'number'
|
|
39
|
+
? await privateAppUserTokenManager?.getPrivateAppUserToken(appId, {
|
|
40
|
+
privateAppToken,
|
|
41
|
+
})
|
|
42
|
+
: undefined;
|
|
43
|
+
const secrets = setProperTokenInSecrets(initialSecrets, logger, privateAppUserToken);
|
|
44
|
+
const { deleted: missingSecrets } = diffLists(
|
|
45
|
+
// secrets expected
|
|
46
|
+
functionInfo.secrets ?? [],
|
|
47
|
+
// secrets available
|
|
48
|
+
Object.keys(process.env));
|
|
49
|
+
if (missingSecrets.length > 0) {
|
|
50
|
+
logger.warn(`The following required secrets are missing: ${missingSecrets}. Please supply them as environment variables for local execution in a .env file.`);
|
|
51
|
+
}
|
|
52
|
+
let context;
|
|
53
|
+
try {
|
|
54
|
+
context = await buildContext(config, localExecutionInputs, secrets);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
process.env = savedEnv;
|
|
58
|
+
throw err;
|
|
59
|
+
}
|
|
60
|
+
// eslint-disable-next-line no-async-promise-executor, consistent-return
|
|
61
|
+
const rawResponse = await new Promise(async (resolve, reject) => {
|
|
62
|
+
// Start the timer to throw an error after 20s unless the timer
|
|
63
|
+
// is unref'd after the function completes (even with error)
|
|
64
|
+
const ms = config.functionTimeoutMs;
|
|
65
|
+
const timeout = setTimeout(() => {
|
|
66
|
+
process.env = savedEnv;
|
|
67
|
+
logger.error(`App function failed to callback within ${formatSeconds(ms, 2)}.`);
|
|
68
|
+
reject(new ExecutionError({
|
|
69
|
+
reason: Reason.Timeout,
|
|
70
|
+
functionName,
|
|
71
|
+
elapsedTimeMs: ms,
|
|
72
|
+
}));
|
|
73
|
+
}, ms);
|
|
74
|
+
// Catch any error thrown from the function, which may or may not be async
|
|
75
|
+
try {
|
|
76
|
+
switch (config.platformVersion) {
|
|
77
|
+
case PLATFORM_VERSION.V20231:
|
|
78
|
+
await main(context, (response) => {
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
process.env = savedEnv;
|
|
81
|
+
resolve(response);
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
case PLATFORM_VERSION.V20232:
|
|
85
|
+
case PLATFORM_VERSION.V20251: {
|
|
86
|
+
const response = await main(context);
|
|
87
|
+
clearTimeout(timeout);
|
|
88
|
+
process.env = savedEnv;
|
|
89
|
+
resolve(response);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case PLATFORM_VERSION.V20252:
|
|
93
|
+
case PLATFORM_VERSION.UNSTABLE: {
|
|
94
|
+
logger.error(`Serverless functions are not yet supported in platform version ${config.platformVersion}.`);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
default:
|
|
98
|
+
return throwUnhandledPlatformVersionError(config.platformVersion);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
process.env = savedEnv;
|
|
104
|
+
logger.error(`App function encountered an uncaught error.`, error);
|
|
105
|
+
reject(new ExecutionError({
|
|
106
|
+
reason: Reason.UncaughtError,
|
|
107
|
+
functionName,
|
|
108
|
+
cause: error,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
timeout.unref();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Confirm that the response is valid JSON
|
|
116
|
+
let jsonResponse = null;
|
|
117
|
+
try {
|
|
118
|
+
jsonResponse =
|
|
119
|
+
rawResponse === undefined
|
|
120
|
+
? null
|
|
121
|
+
: JSON.parse(JSON.stringify(rawResponse));
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
logger.error(`App function reponse is not valid JSON.`);
|
|
125
|
+
throw new ExecutionError({
|
|
126
|
+
reason: Reason.InvalidResponse,
|
|
127
|
+
functionName,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
callback(jsonResponse);
|
|
131
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FunctionContext, ProxyServiceConfig, PrivateAppUserToken } from './types.ts';
|
|
2
|
+
type Secrets = FunctionContext['secrets'];
|
|
3
|
+
export declare function setProperTokenInSecrets(secrets: Secrets, logger: ProxyServiceConfig['logger'], privateAppUserToken?: PrivateAppUserToken): Secrets;
|
|
4
|
+
export declare function loadSecrets(srcDir: string): Secrets;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// TODO: Replace dotenv.parse with `import { parseEnv } from 'node:util'` once the CLI is on Node 22+
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { PRIVATE_APP_ACCESS_TOKEN, SECRETS_IN_CONTEXT } from "./constants.js";
|
|
6
|
+
import { PrivateAppUserTokenManager } from "./services/PrivateAppUserTokenManager.js";
|
|
7
|
+
function getEnvPath(srcDir) {
|
|
8
|
+
const mode = process.env.NODE_ENV;
|
|
9
|
+
return (mode
|
|
10
|
+
? [`.env.${mode}.local`, '.env.local', `.env.${mode}`, '.env']
|
|
11
|
+
: ['.env.local', '.env'])
|
|
12
|
+
.map((fileName) => path.join(srcDir, fileName))
|
|
13
|
+
.find(fs.existsSync);
|
|
14
|
+
}
|
|
15
|
+
export function setProperTokenInSecrets(secrets, logger, privateAppUserToken) {
|
|
16
|
+
if (!privateAppUserToken)
|
|
17
|
+
return secrets;
|
|
18
|
+
const privateAppToken = secrets?.[PRIVATE_APP_ACCESS_TOKEN];
|
|
19
|
+
if (privateAppToken && privateAppUserToken) {
|
|
20
|
+
/*
|
|
21
|
+
* Make sure that the privateAppUserToken has all the scopes from the privateAppToken.
|
|
22
|
+
* If they do, suggest that they remove the privateAppToken from the environment file.
|
|
23
|
+
*/
|
|
24
|
+
if (PrivateAppUserTokenManager.doesUserTokenContainAppTokenScopes(privateAppUserToken)) {
|
|
25
|
+
logger.info('The private app user token was overridden by the PRIVATE_APP_ACCESS_TOKEN in the environment file. For local development, you no longer need a PRIVATE_APP_ACCESS_TOKEN in the environment file. You can safely remove it.');
|
|
26
|
+
}
|
|
27
|
+
// privateAppToken it's already in the secrets and it takes precedence. just return
|
|
28
|
+
return secrets;
|
|
29
|
+
}
|
|
30
|
+
// has only one of the tokens. Set the proper one in the secrets taking into account that privateAppToken takes precedence
|
|
31
|
+
const result = { ...secrets };
|
|
32
|
+
const theToken = privateAppToken ?? privateAppUserToken?.userTokenKey;
|
|
33
|
+
result[PRIVATE_APP_ACCESS_TOKEN] = theToken;
|
|
34
|
+
process.env[PRIVATE_APP_ACCESS_TOKEN] = theToken;
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export function loadSecrets(srcDir) {
|
|
38
|
+
const envPath = getEnvPath(srcDir);
|
|
39
|
+
if (envPath) {
|
|
40
|
+
try {
|
|
41
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
42
|
+
Object.assign(process.env, dotenv.parse(content));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// Silently ignore read errors to match dotenv's quiet behavior
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const secrets = {};
|
|
49
|
+
SECRETS_IN_CONTEXT.forEach((secretName) => {
|
|
50
|
+
const secretValue = process.env[secretName];
|
|
51
|
+
if (typeof secretValue === 'string') {
|
|
52
|
+
secrets[secretName] = secretValue;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
return secrets;
|
|
56
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { executeFunction } from "../executor.js";
|
|
4
|
+
import { buildServiceConfiguration } from "../config.js";
|
|
5
|
+
import { buildBadRequestBody, buildInternalErrorBody, ExecutionError, } from "../errors.js";
|
|
6
|
+
import { PrivateAppUserTokenManager } from "./PrivateAppUserTokenManager.js";
|
|
7
|
+
import { reportError } from "../errorReporter.js";
|
|
8
|
+
export const AppFunctionExecutionService = (partialConfig) => {
|
|
9
|
+
const config = setupConfig(partialConfig);
|
|
10
|
+
const { logger, accountId } = config;
|
|
11
|
+
const privateAppUserTokenManager = new PrivateAppUserTokenManager(accountId, logger);
|
|
12
|
+
privateAppUserTokenManager.init();
|
|
13
|
+
const app = express();
|
|
14
|
+
app.use(cors());
|
|
15
|
+
app.use(express.json());
|
|
16
|
+
app.post('/action/function/:appId', async (req, res) => {
|
|
17
|
+
const appId = Number(req.params.appId);
|
|
18
|
+
const request = req.body;
|
|
19
|
+
const { serverlessFunction: functionName, objectQuery, parameters, event, } = request;
|
|
20
|
+
const onSuccess = (response = null) => {
|
|
21
|
+
logger.debug(`${new Date().toISOString()} - App function "${functionName}" execution succeeded`);
|
|
22
|
+
res.status(200).json({ logId: 'n/a', response });
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
await executeFunction(config, functionName, { appId, objectQuery, parameters, event }, onSuccess, privateAppUserTokenManager);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof ExecutionError) {
|
|
29
|
+
const response = buildBadRequestBody(err);
|
|
30
|
+
logger.warn(`${new Date().toISOString()} - App function "${functionName}" execution failed`);
|
|
31
|
+
res.status(400).json(response);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
reportError(err, {
|
|
35
|
+
functionName,
|
|
36
|
+
appId,
|
|
37
|
+
accountId,
|
|
38
|
+
errorType: 'internal_server_error',
|
|
39
|
+
});
|
|
40
|
+
const response = buildInternalErrorBody();
|
|
41
|
+
logger.error(err);
|
|
42
|
+
logger.warn(`${new Date().toISOString()} - App function "${functionName}" execution failed due to server internal error`);
|
|
43
|
+
res.status(500).json(response);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return app;
|
|
48
|
+
};
|
|
49
|
+
function setupConfig(partialConfig) {
|
|
50
|
+
if (!partialConfig.app?.path) {
|
|
51
|
+
throw new Error('Cannot instantiate app function execution service: Path to the application directory is missing');
|
|
52
|
+
}
|
|
53
|
+
const { app: { path: appPath }, ...rest } = partialConfig;
|
|
54
|
+
return buildServiceConfiguration(appPath, rest);
|
|
55
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { AxiosError } from 'axios';
|
|
2
|
+
import { LocalDevUrlMapping, ProxyServiceConfig, ProxyServerError } from '../types.ts';
|
|
3
|
+
export declare const mapToLocalUrl: (localDevUrlMapping: LocalDevUrlMapping, requestUri: string, allowedUrls: string[], logger: ProxyServiceConfig["logger"]) => string;
|
|
4
|
+
export declare const extractErrorData: (e: unknown | AxiosError) => ProxyServerError;
|
|
5
|
+
export declare const AppProxyService: ({ localDevUrlMapping, logger, accountId, allowedUrls, }: ProxyServiceConfig) => import("express-serve-static-core").Express;
|