Skip to content

Commit

Permalink
Fix SMTP timeout by trycatching usecase (#1442)
Browse files Browse the repository at this point in the history
* Fix SMTP timeout by trycatching usecase

* Improve message
  • Loading branch information
lkostrowski committed Jun 19, 2024
1 parent 94515db commit 5fbf043
Show file tree
Hide file tree
Showing 11 changed files with 498 additions and 395 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-moose-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"smtp": patch
---

Fixed error with webhooks timing out. Now root UseCase operation is wrapped with try/catch block, so if unhandled error occurs, response will be returned. Previously response was hanging until lambda was terminated.
114 changes: 61 additions & 53 deletions apps/smtp/src/pages/api/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,75 +6,83 @@ import { createInstrumentedGraphqlClient } from "../../lib/create-instrumented-g
import { createLogger } from "../../logger";
import { fetchSaleorVersion } from "../../modules/feature-flag-service/fetch-saleor-version";
import { REQUIRED_SALEOR_VERSION, saleorApp } from "../../saleor-app";
import { loggerContext } from "../../logger-context";
import { wrapWithLoggerContext } from "@saleor/apps-logger/node";

const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN;

/**
* Required endpoint, called by Saleor to install app.
* It will exchange tokens with app, so saleorApp.apl will contain token
*/
export default withOtel(
createAppRegisterHandler({
apl: saleorApp.apl,
allowedSaleorUrls: [
(url) => {
if (allowedUrlsPattern) {
const regex = new RegExp(allowedUrlsPattern);
export default wrapWithLoggerContext(
withOtel(
createAppRegisterHandler({
apl: saleorApp.apl,
allowedSaleorUrls: [
(url) => {
if (allowedUrlsPattern) {
const regex = new RegExp(allowedUrlsPattern);

return regex.test(url);
}
return regex.test(url);
}

return true;
},
],
async onRequestVerified(req, { authData: { token, saleorApiUrl }, respondWithError }) {
const logger = createLogger("onRequestVerified");
return true;
},
],
async onRequestVerified(req, { authData: { token, saleorApiUrl }, respondWithError }) {
const logger = createLogger("onRequestVerified");

let saleorVersion: string;
let saleorVersion: string;

try {
const client = createInstrumentedGraphqlClient({
saleorApiUrl: saleorApiUrl,
token: token,
});
try {
const client = createInstrumentedGraphqlClient({
saleorApiUrl: saleorApiUrl,
token: token,
});

saleorVersion = await fetchSaleorVersion(client);
} catch (e: unknown) {
const message = (e as Error)?.message ?? "Unknown error";
saleorVersion = await fetchSaleorVersion(client);
} catch (e: unknown) {
const message = (e as Error)?.message ?? "Unknown error";

logger.debug(
{ message, saleorApiUrl },
"Error during fetching saleor version in onRequestVerified handler",
);
logger.debug(
{ message, saleorApiUrl },
"Error during fetching saleor version in onRequestVerified handler",
);

throw respondWithError({
message: "Couldn't communicate with Saleor API",
status: 400,
});
}
throw respondWithError({
message: "Couldn't communicate with Saleor API",
status: 400,
});
}

if (!saleorVersion) {
logger.warn({ saleorApiUrl }, "No version returned from Saleor API");
throw respondWithError({
message: "Saleor version couldn't be fetched from the API",
status: 400,
});
}
if (!saleorVersion) {
logger.warn({ saleorApiUrl }, "No version returned from Saleor API");
throw respondWithError({
message: "Saleor version couldn't be fetched from the API",
status: 400,
});
}

const isVersionValid = new SaleorVersionCompatibilityValidator(
REQUIRED_SALEOR_VERSION,
).isValid(saleorVersion);
const isVersionValid = new SaleorVersionCompatibilityValidator(
REQUIRED_SALEOR_VERSION,
).isValid(saleorVersion);

if (!isVersionValid) {
logger.info({ saleorApiUrl }, "Rejecting installation due to incompatible Saleor version");
throw respondWithError({
message: `Saleor version (${saleorVersion}) is not compatible with this app version (${REQUIRED_SALEOR_VERSION})`,
status: 400,
});
}
if (!isVersionValid) {
logger.info(
{ saleorApiUrl },
"Rejecting installation due to incompatible Saleor version",
);
throw respondWithError({
message: `Saleor version (${saleorVersion}) is not compatible with this app version (${REQUIRED_SALEOR_VERSION})`,
status: 400,
});
}

logger.info("Saleor version validated successfully");
},
}),
"api/register",
logger.info("Saleor version validated successfully");
},
}),
"api/register",
),
loggerContext,
);
86 changes: 48 additions & 38 deletions apps/smtp/src/pages/api/webhooks/gift-card-sent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,44 +115,54 @@ const handler: NextWebhookApiHandler<GiftCardSentWebhookPayloadFragment> = async

const useCase = useCaseFactory.createFromAuthData(authData);

return useCase
.sendEventMessages({
channelSlug: channel,
event: "GIFT_CARD_SENT",
payload,
recipientEmail,
})
.then((result) =>
result.match(
(r) => {
logger.info("Successfully sent email(s)");

return res.status(200).json({ message: "The event has been handled" });
},
(err) => {
const errorInstance = err[0];

if (errorInstance instanceof SendEventMessagesUseCase.ServerError) {
logger.error("Failed to send email(s) [server error]", { error: err });

return res.status(500).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.ClientError) {
logger.info("Failed to send email(s) [client error]", { error: err });

return res.status(400).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.NoOpError) {
logger.info("Sending emails aborted [no op]", { error: err });

return res.status(200).json({ message: "The event has been handled [no op]" });
}

logger.error("Failed to send email(s) [unhandled error]", { error: err });
captureException(new Error("Unhandled useCase error", { cause: err }));

return res.status(500).json({ message: "Failed to send email [unhandled]" });
},
),
);
try {
return useCase
.sendEventMessages({
channelSlug: channel,
event: "GIFT_CARD_SENT",
payload,
recipientEmail,
})
.then((result) =>
result.match(
(r) => {
logger.info("Successfully sent email(s)");

return res.status(200).json({ message: "The event has been handled" });
},
(err) => {
const errorInstance = err[0];

if (errorInstance instanceof SendEventMessagesUseCase.ServerError) {
logger.error("Failed to send email(s) [server error]", { error: err });

return res.status(500).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.ClientError) {
logger.info("Failed to send email(s) [client error]", { error: err });

return res.status(400).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.NoOpError) {
logger.info("Sending emails aborted [no op]", { error: err });

return res.status(200).json({ message: "The event has been handled [no op]" });
}

logger.error("Failed to send email(s) [unhandled error]", { error: err });
captureException(new Error("Unhandled useCase error", { cause: err }));

return res.status(500).json({ message: "Failed to send email [unhandled]" });
},
),
);
} catch (e) {
logger.error("Unhandled error from useCase", {
error: e,
});

captureException(e);

return res.status(500).json({ message: "Failed to execute webhook" });
}
};

export default wrapWithLoggerContext(
Expand Down
86 changes: 48 additions & 38 deletions apps/smtp/src/pages/api/webhooks/invoice-sent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,44 +93,54 @@ const handler: NextWebhookApiHandler<InvoiceSentWebhookPayloadFragment> = async

const useCase = useCaseFactory.createFromAuthData(authData);

return useCase
.sendEventMessages({
channelSlug: channel,
event: "INVOICE_SENT",
payload: { order: payload.order },
recipientEmail,
})
.then((result) =>
result.match(
(r) => {
logger.info("Successfully sent email(s)");

return res.status(200).json({ message: "The event has been handled" });
},
(err) => {
const errorInstance = err[0];

if (errorInstance instanceof SendEventMessagesUseCase.ServerError) {
logger.error("Failed to send email(s) [server error]", { error: err });

return res.status(500).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.ClientError) {
logger.info("Failed to send email(s) [client error]", { error: err });

return res.status(400).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.NoOpError) {
logger.info("Sending emails aborted [no op]", { error: err });

return res.status(200).json({ message: "The event has been handled [no op]" });
}

logger.error("Failed to send email(s) [unhandled error]", { error: err });
captureException(new Error("Unhandled useCase error", { cause: err }));

return res.status(500).json({ message: "Failed to send email [unhandled]" });
},
),
);
try {
return useCase
.sendEventMessages({
channelSlug: channel,
event: "INVOICE_SENT",
payload: { order: payload.order },
recipientEmail,
})
.then((result) =>
result.match(
(r) => {
logger.info("Successfully sent email(s)");

return res.status(200).json({ message: "The event has been handled" });
},
(err) => {
const errorInstance = err[0];

if (errorInstance instanceof SendEventMessagesUseCase.ServerError) {
logger.error("Failed to send email(s) [server error]", { error: err });

return res.status(500).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.ClientError) {
logger.info("Failed to send email(s) [client error]", { error: err });

return res.status(400).json({ message: "Failed to send email" });
} else if (errorInstance instanceof SendEventMessagesUseCase.NoOpError) {
logger.info("Sending emails aborted [no op]", { error: err });

return res.status(200).json({ message: "The event has been handled [no op]" });
}

logger.error("Failed to send email(s) [unhandled error]", { error: err });
captureException(new Error("Unhandled useCase error", { cause: err }));

return res.status(500).json({ message: "Failed to send email [unhandled]" });
},
),
);
} catch (e) {
logger.error("Unhandled error from useCase", {
error: e,
});

captureException(e);

return res.status(500).json({ message: "Failed to execute webhook" });
}
};

export default wrapWithLoggerContext(
Expand Down
Loading

0 comments on commit 5fbf043

Please sign in to comment.