UNPKG

@saleor/app-sdk

Version:
928 lines (888 loc) 29.9 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _chunkI3JXSADOjs = require('./chunk-I3JXSADO.js'); var _chunkEJ6YJ4BDjs = require('./chunk-EJ6YJ4BD.js'); var _chunkODMQWUGYjs = require('./chunk-ODMQWUGY.js'); var _chunkGZLV5NDPjs = require('./chunk-GZLV5NDP.js'); var _chunkVEHOYPBTjs = require('./chunk-VEHOYPBT.js'); var _chunkKM2345JLjs = require('./chunk-KM2345JL.js'); var _chunkDE4A7PETjs = require('./chunk-DE4A7PET.js'); // src/auth/fetch-remote-jwks.ts var _api = require('@opentelemetry/api'); var _semanticconventions = require('@opentelemetry/semantic-conventions'); var fetchRemoteJwks = async (saleorApiUrl) => { const tracer = _chunkKM2345JLjs.getOtelTracer.call(void 0, ); return tracer.startActiveSpan( "fetchRemoteJwks", { kind: _api.SpanKind.CLIENT, attributes: { saleorApiUrl, [_semanticconventions.SemanticAttributes.PEER_SERVICE]: _chunkKM2345JLjs.OTEL_CORE_SERVICE_NAME } }, async (span) => { try { const jwksResponse = await fetch(_chunkVEHOYPBTjs.getJwksUrlFromSaleorApiUrl.call(void 0, saleorApiUrl)); const jwksText = await jwksResponse.text(); span.setStatus({ code: _api.SpanStatusCode.OK }); return jwksText; } catch (err) { span.setStatus({ code: _api.SpanStatusCode.ERROR }); throw err; } finally { span.end(); } } ); }; // src/get-app-id.ts var debug = _chunkDE4A7PETjs.createDebug.call(void 0, "getAppId"); var getAppId = async ({ saleorApiUrl, token }) => { try { const response = await fetch(saleorApiUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ query: ` { app{ id } } ` }) }); if (response.status !== 200) { debug(`Could not get the app ID: Saleor API has response code ${response.status}`); return void 0; } const body = await response.json(); const appId = _optionalChain([body, 'access', _ => _.data, 'optionalAccess', _2 => _2.app, 'optionalAccess', _3 => _3.id]); return appId; } catch (e) { debug("Could not get the app ID: %O", e); return void 0; } }; // src/handlers/shared/saleor-request-processor.ts var SaleorRequestProcessor = class { constructor(adapter) { this.adapter = adapter; this.toStringOrUndefined = (value) => value ? value.toString() : void 0; } withMethod(methods) { if (!methods.includes(this.adapter.method)) { return { body: "Method not allowed", bodyType: "string", status: 405 }; } return null; } withSaleorApiUrlPresent() { const { saleorApiUrl } = this.getSaleorHeaders(); if (!saleorApiUrl) { return { body: "Missing saleor-api-url header", bodyType: "string", status: 400 }; } return null; } getSaleorHeaders() { return { authorizationBearer: this.toStringOrUndefined( this.adapter.getHeader(_chunkODMQWUGYjs.SALEOR_AUTHORIZATION_BEARER_HEADER) ), signature: this.toStringOrUndefined(this.adapter.getHeader(_chunkODMQWUGYjs.SALEOR_SIGNATURE_HEADER)), event: this.toStringOrUndefined(this.adapter.getHeader(_chunkODMQWUGYjs.SALEOR_EVENT_HEADER)), saleorApiUrl: this.toStringOrUndefined(this.adapter.getHeader(_chunkODMQWUGYjs.SALEOR_API_URL_HEADER)), /** * Schema version must remain string. Since format is "x.x" like "3.20" for javascript it's a floating numer - so it's 3.2 * Saleor version 3.20 != 3.2. * Semver must be compared as strings */ schemaVersion: this.toStringOrUndefined(this.adapter.getHeader(_chunkODMQWUGYjs.SALEOR_SCHEMA_VERSION_HEADER)) }; } }; // src/handlers/shared/validate-allow-saleor-urls.ts var validateAllowSaleorUrls = (saleorApiUrl, allowedUrls) => { if (!allowedUrls || allowedUrls.length === 0) { return true; } for (const urlOrFn of allowedUrls) { if (typeof urlOrFn === "string" && urlOrFn === saleorApiUrl) { return true; } if (typeof urlOrFn === "function" && urlOrFn(saleorApiUrl)) { return true; } } return false; }; // src/handlers/actions/register-action-handler.ts var debug2 = _chunkDE4A7PETjs.createDebug.call(void 0, "createAppRegisterHandler"); var RegisterCallbackError = class extends Error { constructor(errorParams) { super(errorParams.message); this.status = 500; if (errorParams.status) { this.status = errorParams.status; } } }; var createRegisterHandlerResponseBody = (success, error, statusCode) => ({ status: _nullishCoalesce(statusCode, () => ( (success ? 200 : 500))), body: { success, error }, bodyType: "json" }); var RegisterActionHandler = class { constructor(adapter) { this.adapter = adapter; this.requestProcessor = new SaleorRequestProcessor(this.adapter); this.createCallbackError = (params) => { throw new RegisterCallbackError(params); }; } runPreChecks() { const checksToRun = [ this.requestProcessor.withMethod(["POST"]), this.requestProcessor.withSaleorApiUrlPresent() ]; for (const check of checksToRun) { if (check) { return check; } } return null; } async handleAction(config) { debug2("Request received"); const precheckResult = this.runPreChecks(); if (precheckResult) { return precheckResult; } const saleorApiUrl = this.adapter.getHeader(_chunkODMQWUGYjs.SALEOR_API_URL_HEADER); const authTokenResult = await this.parseRequestBody(); if (!authTokenResult.success) { return authTokenResult.response; } const { authToken } = authTokenResult; const handleOnRequestResult = await this.handleOnRequestStartCallback(config.onRequestStart, { authToken, saleorApiUrl }); if (handleOnRequestResult) { return handleOnRequestResult; } const saleorApiUrlValidationResult = this.handleSaleorApiUrlValidation({ saleorApiUrl, allowedSaleorUrls: config.allowedSaleorUrls }); if (saleorApiUrlValidationResult) { return saleorApiUrlValidationResult; } const aplCheckResult = await this.checkAplIsConfigured(config.apl); if (aplCheckResult) { return aplCheckResult; } const getAppIdResult = await this.getAppIdAndHandleMissingAppId({ saleorApiUrl, token: authToken }); if (!getAppIdResult.success) { return getAppIdResult.responseBody; } const { appId } = getAppIdResult; const getJwksResult = await this.getJwksAndHandleMissingJwks({ saleorApiUrl }); if (!getJwksResult.success) { return getJwksResult.responseBody; } const { jwks } = getJwksResult; const authData = { token: authToken, saleorApiUrl, appId, jwks }; const onRequestVerifiedErrorResponse = await this.handleOnRequestVerifiedCallback( config.onRequestVerified, authData ); if (onRequestVerifiedErrorResponse) { return onRequestVerifiedErrorResponse; } const aplSaveResponse = await this.saveAplAuthData({ apl: config.apl, authData, onAplSetFailed: config.onAplSetFailed, onAuthAplSaved: config.onAuthAplSaved }); return aplSaveResponse; } async parseRequestBody() { let body; try { body = await this.adapter.getBody(); } catch (err) { return { success: false, response: { status: 400, body: "Invalid request json.", bodyType: "string" } }; } const authToken = _optionalChain([body, 'optionalAccess', _4 => _4.auth_token]); if (!authToken) { debug2("Found missing authToken param"); return { success: false, response: { status: 400, body: "Missing auth token.", bodyType: "string" } }; } return { success: true, authToken }; } async handleOnRequestStartCallback(onRequestStart, { authToken, saleorApiUrl }) { if (onRequestStart) { debug2('Calling "onRequestStart" hook'); try { await onRequestStart(this.adapter.request, { authToken, saleorApiUrl, respondWithError: this.createCallbackError }); } catch (e) { debug2('"onRequestStart" hook thrown error: %o', e); return this.handleHookError(e); } } return null; } handleSaleorApiUrlValidation({ saleorApiUrl, allowedSaleorUrls }) { if (!validateAllowSaleorUrls(saleorApiUrl, allowedSaleorUrls)) { debug2( "Validation of URL %s against allowSaleorUrls param resolves to false, throwing", saleorApiUrl ); return createRegisterHandlerResponseBody( false, { code: "SALEOR_URL_PROHIBITED", message: "This app expects to be installed only in allowed Saleor instances" }, 403 ); } return null; } async checkAplIsConfigured(apl) { if (!apl.isConfigured) { return null; } const { configured: aplConfigured } = await apl.isConfigured(); if (!aplConfigured) { debug2("The APL has not been configured"); return createRegisterHandlerResponseBody( false, { code: "APL_NOT_CONFIGURED", message: "APL_NOT_CONFIGURED. App is configured properly. Check APL docs for help." }, 503 ); } return null; } async getAppIdAndHandleMissingAppId({ saleorApiUrl, token }) { const appId = await getAppId({ saleorApiUrl, token }); if (!appId) { const responseBody = createRegisterHandlerResponseBody( false, { code: "UNKNOWN_APP_ID", message: `The auth data given during registration request could not be used to fetch app ID. This usually means that App could not connect to Saleor during installation. Saleor URL that App tried to connect: ${saleorApiUrl}` }, 401 ); return { success: false, responseBody }; } return { success: true, appId }; } async getJwksAndHandleMissingJwks({ saleorApiUrl }) { try { const jwks = await fetchRemoteJwks(saleorApiUrl); if (jwks) { return { success: true, jwks }; } } catch (err) { } const responseBody = createRegisterHandlerResponseBody( false, { code: "JWKS_NOT_AVAILABLE", message: "Can't fetch the remote JWKS." }, 401 ); return { success: false, responseBody }; } async handleOnRequestVerifiedCallback(onRequestVerified, authData) { if (onRequestVerified) { debug2('Calling "onRequestVerified" hook'); try { await onRequestVerified(this.adapter.request, { authData, respondWithError: this.createCallbackError }); } catch (e) { debug2('"onRequestVerified" hook thrown error: %o', e); return this.handleHookError(e); } } return null; } async saveAplAuthData({ apl, onAplSetFailed, onAuthAplSaved, authData }) { try { await apl.set(authData); if (onAuthAplSaved) { debug2('Calling "onAuthAplSaved" hook'); try { await onAuthAplSaved(this.adapter.request, { authData, respondWithError: this.createCallbackError }); } catch (e) { debug2('"onAuthAplSaved" hook thrown error: %o', e); return this.handleHookError(e); } } } catch (aplError) { debug2("There was an error during saving the auth data"); if (onAplSetFailed) { debug2('Calling "onAuthAplFailed" hook'); try { await onAplSetFailed(this.adapter.request, { authData, error: aplError, respondWithError: this.createCallbackError }); } catch (hookError) { debug2('"onAuthAplFailed" hook thrown error: %o', hookError); return this.handleHookError(hookError); } } return createRegisterHandlerResponseBody(false, { message: "Registration failed: could not save the auth data." }); } debug2("Register complete"); return createRegisterHandlerResponseBody(true); } /** Callbacks declared by users in configuration can throw an error * It is caught here and converted into a response */ handleHookError(e) { if (e instanceof RegisterCallbackError) { return createRegisterHandlerResponseBody( false, { code: "REGISTER_HANDLER_HOOK_ERROR", message: e.message }, e.status ); } return { status: 500, body: "Error during app installation", bodyType: "string" }; } }; // src/handlers/actions/manifest-action-handler.ts var debug3 = _chunkDE4A7PETjs.createDebug.call(void 0, "create-manifest-handler"); var ManifestActionHandler = class { constructor(adapter) { this.adapter = adapter; this.requestProcessor = new SaleorRequestProcessor(this.adapter); } async handleAction(options) { const { schemaVersion } = this.requestProcessor.getSaleorHeaders(); const parsedSchemaVersion = _nullishCoalesce(_chunkGZLV5NDPjs.parseSchemaVersion.call(void 0, schemaVersion), () => ( void 0)); const baseURL = this.adapter.getBaseUrl(); debug3('Received request with schema version "%s" and base URL "%s"', schemaVersion, baseURL); const invalidMethodResponse = this.requestProcessor.withMethod(["GET"]); if (invalidMethodResponse) { return invalidMethodResponse; } try { const manifest = await options.manifestFactory({ appBaseUrl: baseURL, request: this.adapter.request, schemaVersion: parsedSchemaVersion }); debug3("Executed manifest file"); return { status: 200, bodyType: "json", body: manifest }; } catch (e) { debug3("Error while resolving manifest: %O", e); return { status: 500, bodyType: "string", body: "Error resolving manifest file." }; } } }; // src/handlers/shared/protected-action-validator.ts var ProtectedActionValidator = class { constructor(adapter) { this.adapter = adapter; this.debug = _chunkDE4A7PETjs.createDebug.call(void 0, "ProtectedActionValidator"); this.tracer = _chunkKM2345JLjs.getOtelTracer.call(void 0, ); this.requestProcessor = new SaleorRequestProcessor(this.adapter); } /** Validates received request if it's legitimate webhook request from Saleor * returns ActionHandlerResult if request is invalid and must be terminated early * */ async validateRequest(config) { return this.tracer.startActiveSpan( "processSaleorProtectedHandler", { kind: _api.SpanKind.INTERNAL, attributes: { requiredPermissions: config.requiredPermissions } }, async (span) => { this.debug("Request processing started"); const { saleorApiUrl, authorizationBearer: token } = this.requestProcessor.getSaleorHeaders(); const baseUrl = this.adapter.getBaseUrl(); span.setAttribute("saleorApiUrl", _nullishCoalesce(saleorApiUrl, () => ( ""))); if (!baseUrl) { span.setStatus({ code: _api.SpanStatusCode.ERROR, message: "Missing host header" }).end(); this.debug("Missing host header"); return { result: "failure", value: { bodyType: "string", status: 400, body: "Validation error: Missing host header" } }; } if (!saleorApiUrl) { span.setStatus({ code: _api.SpanStatusCode.ERROR, message: "Missing saleor-api-url header" }).end(); this.debug("Missing saleor-api-url header"); return { result: "failure", value: { bodyType: "string", status: 400, body: "Validation error: Missing saleor-api-url header" } }; } if (!token) { span.setStatus({ code: _api.SpanStatusCode.ERROR, message: "Missing authorization-bearer header" }).end(); this.debug("Missing authorization-bearer header"); return { result: "failure", value: { bodyType: "string", status: 400, body: "Validation error: Missing authorization-bearer header" } }; } const authData = await config.apl.get(saleorApiUrl); if (!authData) { span.setStatus({ code: _api.SpanStatusCode.ERROR, message: "APL didn't found auth data for API URL" }).end(); this.debug("APL didn't found auth data for API URL %s", saleorApiUrl); return { result: "failure", value: { bodyType: "string", status: 401, body: `Validation error: Can't find auth data for saleorApiUrl ${saleorApiUrl}. Please register the application` } }; } try { await _chunkVEHOYPBTjs.verifyJWT.call(void 0, { appId: authData.appId, token, saleorApiUrl, requiredPermissions: config.requiredPermissions }); } catch (e) { span.setStatus({ code: _api.SpanStatusCode.ERROR, message: "JWT verification failed" }).end(); return { result: "failure", value: { bodyType: "string", status: 401, body: "Validation error: JWT verification failed" } }; } try { const userJwtPayload = _chunkEJ6YJ4BDjs.extractUserFromJwt.call(void 0, token); span.end(); return { result: "ok", value: { baseUrl, authData, user: userJwtPayload } }; } catch (err) { span.setStatus({ code: _api.SpanStatusCode.ERROR, message: "Error parsing user from JWT" }); span.end(); return { result: "failure", value: { bodyType: "string", status: 500, body: "Unexpected error: parsing user from JWT" } }; } } ); } }; // src/gql-ast-to-string.ts var _graphql = require('graphql'); var gqlAstToString = (ast) => _graphql.print.call(void 0, ast).replaceAll(/\n*/g, "").replaceAll(/\s{2,}/g, " ").trim(); // src/handlers/shared/saleor-webhook-validator.ts var SaleorWebhookValidator = class { constructor(params) { this.verifySignatureWithJwks = _chunkVEHOYPBTjs.verifySignatureWithJwks.bind(this); this.debug = _chunkDE4A7PETjs.createDebug.call(void 0, "processProtectedHandler"); this.tracer = _chunkKM2345JLjs.getOtelTracer.call(void 0, ); if (_optionalChain([params, 'optionalAccess', _5 => _5.verifySignatureFn])) { this.verifySignatureWithJwks = params.verifySignatureFn; } } async validateRequest(config) { try { const context = await this.validateRequestOrThrowError(config); return { result: "ok", context }; } catch (err) { return { result: "failure", error: err }; } } async validateRequestOrThrowError({ allowedEvent, apl, adapter, requestProcessor }) { return this.tracer.startActiveSpan( "processSaleorWebhook", { kind: _api.SpanKind.INTERNAL, attributes: { allowedEvent } }, async (span) => { try { this.debug("Request processing started"); if (adapter.method !== "POST") { this.debug("Wrong HTTP method"); throw new (0, _chunkI3JXSADOjs.WebhookError)("Wrong request method, only POST allowed", "WRONG_METHOD"); } const { event, signature, saleorApiUrl } = requestProcessor.getSaleorHeaders(); const baseUrl = adapter.getBaseUrl(); if (!baseUrl) { this.debug("Missing host header"); throw new (0, _chunkI3JXSADOjs.WebhookError)("Missing host header", "MISSING_HOST_HEADER"); } if (!saleorApiUrl) { this.debug("Missing saleor-api-url header"); throw new (0, _chunkI3JXSADOjs.WebhookError)("Missing saleor-api-url header", "MISSING_API_URL_HEADER"); } if (!event) { this.debug("Missing saleor-event header"); throw new (0, _chunkI3JXSADOjs.WebhookError)("Missing saleor-event header", "MISSING_EVENT_HEADER"); } const expected = allowedEvent.toLowerCase(); if (event !== expected) { this.debug(`Wrong incoming request event: ${event}. Expected: ${expected}`); throw new (0, _chunkI3JXSADOjs.WebhookError)( `Wrong incoming request event: ${event}. Expected: ${expected}`, "WRONG_EVENT" ); } if (!signature) { this.debug("No signature"); throw new (0, _chunkI3JXSADOjs.WebhookError)("Missing saleor-signature header", "MISSING_SIGNATURE_HEADER"); } const rawBody = await adapter.getRawBody(); if (!rawBody) { this.debug("Missing request body"); throw new (0, _chunkI3JXSADOjs.WebhookError)("Missing request body", "MISSING_REQUEST_BODY"); } let parsedBody; try { parsedBody = JSON.parse(rawBody); } catch (e2) { this.debug("Request body cannot be parsed"); throw new (0, _chunkI3JXSADOjs.WebhookError)("Request body can't be parsed", "CANT_BE_PARSED"); } let parsedSchemaVersion = null; try { parsedSchemaVersion = _chunkGZLV5NDPjs.parseSchemaVersion.call(void 0, parsedBody.version); } catch (e3) { this.debug("Schema version cannot be parsed"); } const authData = await apl.get(saleorApiUrl); if (!authData) { this.debug("APL didn't found auth data for %s", saleorApiUrl); throw new (0, _chunkI3JXSADOjs.WebhookError)( `Can't find auth data for ${saleorApiUrl}. Please register the application`, "NOT_REGISTERED" ); } try { this.debug("Will verify signature with JWKS saved in AuthData"); if (!authData.jwks) { throw new Error("JWKS not found in AuthData"); } await this.verifySignatureWithJwks(authData.jwks, signature, rawBody); } catch (e4) { this.debug("Request signature check failed. Refresh the JWKS cache and check again"); const newJwks = await fetchRemoteJwks(authData.saleorApiUrl).catch((e) => { this.debug(e); throw new (0, _chunkI3JXSADOjs.WebhookError)( "Fetching remote JWKS failed", "SIGNATURE_VERIFICATION_FAILED" ); }); this.debug("Fetched refreshed JWKS"); try { this.debug( "Second attempt to validate the signature JWKS, using fresh tokens from the API" ); await this.verifySignatureWithJwks(newJwks, signature, rawBody); this.debug("Verification successful - update JWKS in the AuthData"); await apl.set({ ...authData, jwks: newJwks }); } catch (e5) { this.debug("Second attempt also ended with validation error. Reject the webhook"); throw new (0, _chunkI3JXSADOjs.WebhookError)( "Request signature check failed", "SIGNATURE_VERIFICATION_FAILED" ); } } span.setStatus({ code: _api.SpanStatusCode.OK }); return { baseUrl, event, payload: parsedBody, authData, schemaVersion: parsedSchemaVersion }; } catch (err) { const message = _nullishCoalesce(_optionalChain([err, 'optionalAccess', _6 => _6.message]), () => ( "Unknown error")); span.setStatus({ code: _api.SpanStatusCode.ERROR, message }); throw err; } finally { span.end(); } } ); } }; // src/handlers/shared/generic-saleor-webhook.ts var debug4 = _chunkDE4A7PETjs.createDebug.call(void 0, "SaleorWebhook"); var GenericSaleorWebhook = class { constructor(configuration) { const { name, webhookPath, event, query, apl, isActive = true } = configuration; this.name = name || `${event} webhook`; this.query = query; this.webhookPath = webhookPath; this.event = event; this.isActive = isActive; this.apl = apl; this.onError = configuration.onError; this.formatErrorResponse = configuration.formatErrorResponse; this.verifySignatureFn = _nullishCoalesce(configuration.verifySignatureFn, () => ( _chunkVEHOYPBTjs.verifySignatureWithJwks)); this.webhookValidator = new SaleorWebhookValidator({ verifySignatureFn: this.verifySignatureFn }); } /** Gets webhook absolute URL based on baseUrl of app * baseUrl is passed usually from manifest * baseUrl can include it's own pathname (e.g. http://aws-lambda.com/prod -> has /prod pathname) * that should be included in full webhook URL, e.g. http://my-webhook.com/prod/api/webhook/order-created */ getTargetUrl(baseUrl) { const parsedBaseUrl = new URL(baseUrl); const normalizedWebhookPath = this.webhookPath.replace(/^\//, ""); const fullPath = `${parsedBaseUrl.pathname}/${normalizedWebhookPath}`.replace("//", "/"); return new URL(fullPath, baseUrl).href; } /** * Returns synchronous event manifest for this webhook. * * @param baseUrl Base URL used by your application * @returns WebhookManifest */ getWebhookManifest(baseUrl) { const manifestBase = { query: typeof this.query === "string" ? this.query : gqlAstToString(this.query), name: this.name, targetUrl: this.getTargetUrl(baseUrl), isActive: this.isActive }; switch (this.eventType) { case "async": return { ...manifestBase, asyncEvents: [this.event] }; case "sync": return { ...manifestBase, syncEvents: [this.event] }; default: { throw new Error("Class extended incorrectly"); } } } async prepareRequest(adapter) { const requestProcessor = new SaleorRequestProcessor(adapter); const validationResult = await this.webhookValidator.validateRequest({ allowedEvent: this.event, apl: this.apl, adapter, requestProcessor }); if (validationResult.result === "ok") { return { result: "callHandler", context: validationResult.context }; } const { error } = validationResult; debug4(`Unexpected error during processing the webhook ${this.name}`); if (error instanceof _chunkI3JXSADOjs.WebhookError) { debug4(`Validation error: ${error.message}`); if (this.onError) { this.onError(error, adapter.request); } if (this.formatErrorResponse) { const { code, body } = await this.formatErrorResponse(error, adapter.request); return { result: "sendResponse", response: adapter.send({ status: code, body, bodyType: "string" }) }; } return { result: "sendResponse", response: adapter.send({ bodyType: "json", body: { error: { type: error.errorType, message: error.message } }, status: _chunkI3JXSADOjs.WebhookErrorCodeMap[error.errorType] || 400 }) }; } debug4("Unexpected error: %O", error); if (this.onError) { this.onError(error, adapter.request); } if (this.formatErrorResponse) { const { code, body } = await this.formatErrorResponse(error, adapter.request); return { result: "sendResponse", response: adapter.send({ status: code, body, bodyType: "string" }) }; } return { result: "sendResponse", response: adapter.send({ status: 500, body: "Unexpected error while handling request", bodyType: "string" }) }; } }; exports.RegisterActionHandler = RegisterActionHandler; exports.ManifestActionHandler = ManifestActionHandler; exports.ProtectedActionValidator = ProtectedActionValidator; exports.GenericSaleorWebhook = GenericSaleorWebhook;