wrapApiHandlerWithSentryVercelCrons.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. var {
  2. _optionalChain
  3. } = require('@sentry/utils');
  4. Object.defineProperty(exports, '__esModule', { value: true });
  5. const core = require('@sentry/core');
  6. /**
  7. * Wraps a function with Sentry crons instrumentation by automaticaly sending check-ins for the given Vercel crons config.
  8. */
  9. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  10. function wrapApiHandlerWithSentryVercelCrons(
  11. handler,
  12. vercelCronsConfig,
  13. ) {
  14. return new Proxy(handler, {
  15. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  16. apply: (originalFunction, thisArg, args) => {
  17. return core.runWithAsyncContext(() => {
  18. if (!args || !args[0]) {
  19. return originalFunction.apply(thisArg, args);
  20. }
  21. core.addTracingExtensions();
  22. const [req] = args ;
  23. let maybePromiseResult;
  24. const cronsKey = 'nextUrl' in req ? req.nextUrl.pathname : req.url;
  25. const userAgentHeader = 'nextUrl' in req ? req.headers.get('user-agent') : req.headers['user-agent'];
  26. if (
  27. !vercelCronsConfig || // do nothing if vercel crons config is missing
  28. !_optionalChain([userAgentHeader, 'optionalAccess', _ => _.includes, 'call', _2 => _2('vercel-cron')]) // do nothing if endpoint is not called from vercel crons
  29. ) {
  30. return originalFunction.apply(thisArg, args);
  31. }
  32. const vercelCron = vercelCronsConfig.find(vercelCron => vercelCron.path === cronsKey);
  33. if (!vercelCron || !vercelCron.path || !vercelCron.schedule) {
  34. return originalFunction.apply(thisArg, args);
  35. }
  36. const monitorSlug = vercelCron.path;
  37. const checkInId = core.captureCheckIn(
  38. {
  39. monitorSlug,
  40. status: 'in_progress',
  41. },
  42. {
  43. maxRuntime: 60 * 12, // (minutes) so 12 hours - just a very high arbitrary number since we don't know the actual duration of the users cron job
  44. schedule: {
  45. type: 'crontab',
  46. value: vercelCron.schedule,
  47. },
  48. },
  49. );
  50. const startTime = Date.now() / 1000;
  51. const handleErrorCase = () => {
  52. core.captureCheckIn({
  53. checkInId,
  54. monitorSlug,
  55. status: 'error',
  56. duration: Date.now() / 1000 - startTime,
  57. });
  58. };
  59. try {
  60. maybePromiseResult = originalFunction.apply(thisArg, args);
  61. } catch (e) {
  62. handleErrorCase();
  63. throw e;
  64. }
  65. if (typeof maybePromiseResult === 'object' && maybePromiseResult !== null && 'then' in maybePromiseResult) {
  66. Promise.resolve(maybePromiseResult).then(
  67. () => {
  68. core.captureCheckIn({
  69. checkInId,
  70. monitorSlug,
  71. status: 'ok',
  72. duration: Date.now() / 1000 - startTime,
  73. });
  74. },
  75. () => {
  76. handleErrorCase();
  77. },
  78. );
  79. // It is very important that we return the original promise here, because Next.js attaches various properties
  80. // to that promise and will throw if they are not on the returned value.
  81. return maybePromiseResult;
  82. } else {
  83. core.captureCheckIn({
  84. checkInId,
  85. monitorSlug,
  86. status: 'ok',
  87. duration: Date.now() / 1000 - startTime,
  88. });
  89. return maybePromiseResult;
  90. }
  91. });
  92. },
  93. });
  94. }
  95. exports.wrapApiHandlerWithSentryVercelCrons = wrapApiHandlerWithSentryVercelCrons;
  96. //# sourceMappingURL=wrapApiHandlerWithSentryVercelCrons.js.map