@twentyhq/call-recorder 1.0.1 → 1.0.2

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.
Files changed (237) hide show
  1. package/manifest.json +297 -0
  2. package/package.json +2 -2
  3. package/src/front-components/calendar-event-recording.front-component.mjs +269 -0
  4. package/src/front-components/calendar-event-recording.front-component.mjs.map +7 -0
  5. package/src/logic-functions/process-recall-webhook.mjs +1744 -0
  6. package/src/logic-functions/process-recall-webhook.mjs.map +7 -0
  7. package/src/logic-functions/recall-webhook.mjs +391 -0
  8. package/src/logic-functions/recall-webhook.mjs.map +7 -0
  9. package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs +1602 -0
  10. package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs.map +7 -0
  11. package/src/logic-functions/reconcile-stale-bot-state.mjs +2268 -0
  12. package/src/logic-functions/reconcile-stale-bot-state.mjs.map +7 -0
  13. package/.env.example +0 -5
  14. package/.nvmrc +0 -1
  15. package/.oxlintrc.json +0 -20
  16. package/AGENTS.md +0 -67
  17. package/CLAUDE.md +0 -67
  18. package/README.md +0 -24
  19. package/SETUP.md +0 -95
  20. package/src/__tests__/global-setup.ts +0 -100
  21. package/src/__tests__/schema.integration-test.ts +0 -104
  22. package/src/application-config.ts +0 -96
  23. package/src/constants/__tests__/call-recording-field-universal-identifiers.test.ts +0 -19
  24. package/src/constants/app-description.ts +0 -2
  25. package/src/constants/app-display-name.ts +0 -1
  26. package/src/constants/application-universal-identifier.ts +0 -2
  27. package/src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts +0 -2
  28. package/src/constants/calendar-event-record-page-layout-universal-identifier.ts +0 -2
  29. package/src/constants/calendar-event-recording-front-component-universal-identifier.ts +0 -2
  30. package/src/constants/calendar-event-recording-page-layout-tab-universal-identifier.ts +0 -2
  31. package/src/constants/calendar-event-recording-page-layout-widget-universal-identifier.ts +0 -2
  32. package/src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier.ts +0 -2
  33. package/src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier.ts +0 -2
  34. package/src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier.ts +0 -2
  35. package/src/constants/call-recorder-name-app-variable-universal-identifier.ts +0 -2
  36. package/src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier.ts +0 -2
  37. package/src/constants/call-recorder-preference-off-option-id.ts +0 -2
  38. package/src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier.ts +0 -2
  39. package/src/constants/call-recorder-preference-on-calendar-event-view-field-universal-identifier.ts +0 -2
  40. package/src/constants/call-recorder-preference-on-option-id.ts +0 -2
  41. package/src/constants/call-recorder-preference.ts +0 -4
  42. package/src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier.ts +0 -2
  43. package/src/constants/call-recording-audio-field-universal-identifier.ts +0 -2
  44. package/src/constants/call-recording-video-field-universal-identifier.ts +0 -2
  45. package/src/constants/default-role-universal-identifier.ts +0 -2
  46. package/src/constants/process-recall-webhook-logic-function-universal-identifier.ts +0 -2
  47. package/src/constants/recall-webhook-logic-function-universal-identifier.ts +0 -2
  48. package/src/constants/stale-bot-state-logic-function-universal-identifier.ts +0 -2
  49. package/src/default-role.ts +0 -69
  50. package/src/fields/call-recorder-failure-reason-on-call-recording.field.ts +0 -22
  51. package/src/fields/call-recorder-preference-on-calendar-event.field.ts +0 -41
  52. package/src/front-components/calendar-event-recording.front-component.tsx +0 -13
  53. package/src/front-components/components/CalendarEventRecording.tsx +0 -39
  54. package/src/front-components/components/CalendarEventRecordingBody.tsx +0 -96
  55. package/src/front-components/components/CalendarEventRecordingContent.tsx +0 -111
  56. package/src/front-components/components/RecordingTranscript.tsx +0 -92
  57. package/src/front-components/components/RecordingVideoPlayer.tsx +0 -52
  58. package/src/front-components/components/TranscriptEntryList.tsx +0 -61
  59. package/src/front-components/components/TranscriptEntryListItem.tsx +0 -115
  60. package/src/front-components/components/TranscriptErrorBox.tsx +0 -48
  61. package/src/front-components/components/TranscriptSpeakerAvatar.tsx +0 -141
  62. package/src/front-components/components/TranscriptSpeakerChip.tsx +0 -51
  63. package/src/front-components/constants/recording-theme-css-variables.ts +0 -40
  64. package/src/front-components/hooks/use-calendar-event-participants.ts +0 -172
  65. package/src/front-components/hooks/use-calendar-event-recording.ts +0 -155
  66. package/src/front-components/types/calendar-event-participant-by-speaker-name.type.ts +0 -6
  67. package/src/front-components/types/calendar-event-recording-participant.type.ts +0 -7
  68. package/src/front-components/types/transcript-entry.type.ts +0 -13
  69. package/src/front-components/utils/__tests__/find-active-transcript-entry-index.test.ts +0 -66
  70. package/src/front-components/utils/__tests__/format-transcript-timestamp.test.ts +0 -29
  71. package/src/front-components/utils/__tests__/get-speaker-name-match-keys.test.ts +0 -22
  72. package/src/front-components/utils/__tests__/parse-transcript-entries.test.ts +0 -162
  73. package/src/front-components/utils/build-calendar-event-participant-by-speaker-name.util.ts +0 -45
  74. package/src/front-components/utils/find-active-transcript-entry-index.util.ts +0 -77
  75. package/src/front-components/utils/format-transcript-timestamp.util.ts +0 -16
  76. package/src/front-components/utils/get-absolute-avatar-url.util.ts +0 -48
  77. package/src/front-components/utils/get-calendar-event-participant-for-speaker-name.util.ts +0 -24
  78. package/src/front-components/utils/get-speaker-name-match-keys.util.ts +0 -64
  79. package/src/front-components/utils/get-video-file-extension.util.ts +0 -23
  80. package/src/front-components/utils/parse-transcript-entries.util.ts +0 -85
  81. package/src/logic-functions/__tests__/process-recall-webhook.test.ts +0 -62
  82. package/src/logic-functions/__tests__/recall-webhook.test.ts +0 -180
  83. package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts +0 -2
  84. package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds.ts +0 -1
  85. package/src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts +0 -2
  86. package/src/logic-functions/constants/call-recorder-name-env-var-name.ts +0 -1
  87. package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts +0 -2
  88. package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds.ts +0 -1
  89. package/src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts +0 -2
  90. package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts +0 -2
  91. package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds.ts +0 -1
  92. package/src/logic-functions/constants/call-recording-micro-credits-per-hour.ts +0 -1
  93. package/src/logic-functions/constants/call-recording-request-status.ts +0 -5
  94. package/src/logic-functions/constants/call-recording-status.ts +0 -9
  95. package/src/logic-functions/constants/default-call-recorder-join-early-minutes.ts +0 -1
  96. package/src/logic-functions/constants/default-call-recorder-name.ts +0 -1
  97. package/src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts +0 -2
  98. package/src/logic-functions/constants/default-recall-region.ts +0 -1
  99. package/src/logic-functions/constants/milliseconds-per-minute.ts +0 -1
  100. package/src/logic-functions/constants/non-terminal-call-recording-statuses.ts +0 -8
  101. package/src/logic-functions/constants/recall-api-key-env-var-name.ts +0 -1
  102. package/src/logic-functions/constants/recall-api-max-attempts.ts +0 -1
  103. package/src/logic-functions/constants/recall-api-retry-delay-ms.ts +0 -1
  104. package/src/logic-functions/constants/recall-bot-automatic-leave.ts +0 -74
  105. package/src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts +0 -1
  106. package/src/logic-functions/constants/recall-bot-recording-config.ts +0 -34
  107. package/src/logic-functions/constants/recall-region-env-var-name.ts +0 -1
  108. package/src/logic-functions/constants/recall-webhook-secret-env-var-name.ts +0 -1
  109. package/src/logic-functions/constants/restricted-field-placeholder.ts +0 -3
  110. package/src/logic-functions/constants/stale-bot-state-cron-pattern.ts +0 -1
  111. package/src/logic-functions/constants/twenty-page-size.ts +0 -1
  112. package/src/logic-functions/data/__tests__/complete-call-recording-ingestion.test.ts +0 -55
  113. package/src/logic-functions/data/__tests__/fetch-all-nodes.test.ts +0 -43
  114. package/src/logic-functions/data/__tests__/get-current-workspace-id.test.ts +0 -38
  115. package/src/logic-functions/data/__tests__/strip-restricted-field-value.test.ts +0 -22
  116. package/src/logic-functions/data/complete-call-recording-ingestion.util.ts +0 -24
  117. package/src/logic-functions/data/create-call-recording.util.ts +0 -41
  118. package/src/logic-functions/data/fetch-all-nodes.util.ts +0 -44
  119. package/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +0 -80
  120. package/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +0 -20
  121. package/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +0 -19
  122. package/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +0 -17
  123. package/src/logic-functions/data/find-call-recordings-by-filter.util.ts +0 -102
  124. package/src/logic-functions/data/find-call-recordings-by-ids.util.ts +0 -17
  125. package/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +0 -14
  126. package/src/logic-functions/data/get-current-workspace-id.util.ts +0 -36
  127. package/src/logic-functions/data/strip-restricted-field-value.util.ts +0 -6
  128. package/src/logic-functions/data/update-call-recording.util.ts +0 -24
  129. package/src/logic-functions/domain/__tests__/build-call-recorder-policy-result.test.ts +0 -47
  130. package/src/logic-functions/domain/__tests__/compute-call-recording-charge.test.ts +0 -71
  131. package/src/logic-functions/domain/__tests__/compute-call-recording-id-for-meeting.test.ts +0 -37
  132. package/src/logic-functions/domain/__tests__/compute-real-meeting-key.test.ts +0 -88
  133. package/src/logic-functions/domain/__tests__/is-call-recording-ingestion-complete.test.ts +0 -59
  134. package/src/logic-functions/domain/__tests__/is-call-recording-status-downgrade.test.ts +0 -37
  135. package/src/logic-functions/domain/__tests__/resolve-call-recorder-policy-result.test.ts +0 -120
  136. package/src/logic-functions/domain/__tests__/should-complete-call-recording-ingestion.test.ts +0 -102
  137. package/src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts +0 -42
  138. package/src/logic-functions/domain/build-call-recorder-policy-result.util.ts +0 -53
  139. package/src/logic-functions/domain/build-failed-transcript-marker.util.ts +0 -13
  140. package/src/logic-functions/domain/build-pending-transcript-marker.util.ts +0 -13
  141. package/src/logic-functions/domain/build-recall-routing-metadata.util.ts +0 -12
  142. package/src/logic-functions/domain/build-transcript-failure-reason.util.ts +0 -7
  143. package/src/logic-functions/domain/compute-call-recording-charge.util.ts +0 -41
  144. package/src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts +0 -16
  145. package/src/logic-functions/domain/compute-real-meeting-key.util.ts +0 -48
  146. package/src/logic-functions/domain/compute-recall-bot-join-at.util.ts +0 -34
  147. package/src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts +0 -19
  148. package/src/logic-functions/domain/is-call-recording-status-downgrade.util.ts +0 -37
  149. package/src/logic-functions/domain/is-recall-recording-done-signal.util.ts +0 -13
  150. package/src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts +0 -26
  151. package/src/logic-functions/domain/parse-transcript-marker.util.ts +0 -29
  152. package/src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts +0 -72
  153. package/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +0 -32
  154. package/src/logic-functions/flows/__tests__/charge-completed-call-recording.test.ts +0 -45
  155. package/src/logic-functions/flows/__tests__/complete-and-charge-call-recording.test.ts +0 -61
  156. package/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +0 -727
  157. package/src/logic-functions/flows/__tests__/download-transcript.test.ts +0 -74
  158. package/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +0 -1301
  159. package/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.ts +0 -225
  160. package/src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts +0 -153
  161. package/src/logic-functions/flows/__tests__/reap-orphaned-call-recorders.test.ts +0 -425
  162. package/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +0 -1007
  163. package/src/logic-functions/flows/cancel-call-recording-request.util.ts +0 -46
  164. package/src/logic-functions/flows/charge-completed-call-recording.util.ts +0 -31
  165. package/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +0 -29
  166. package/src/logic-functions/flows/converge-diverged-call-recordings-result.type.ts +0 -8
  167. package/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +0 -447
  168. package/src/logic-functions/flows/download-transcript.util.ts +0 -67
  169. package/src/logic-functions/flows/ensure-call-recorder.util.ts +0 -73
  170. package/src/logic-functions/flows/handle-recall-webhook.util.ts +0 -672
  171. package/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +0 -82
  172. package/src/logic-functions/flows/ingest-call-recording-media.util.ts +0 -128
  173. package/src/logic-functions/flows/persist-call-recording-progress.util.ts +0 -58
  174. package/src/logic-functions/flows/reap-orphaned-call-recorders.util.ts +0 -183
  175. package/src/logic-functions/flows/reconcile-call-recorder.util.ts +0 -495
  176. package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +0 -11
  177. package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +0 -182
  178. package/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +0 -69
  179. package/src/logic-functions/process-recall-webhook.ts +0 -23
  180. package/src/logic-functions/recall-api/__tests__/extract-recall-bot-convergence.test.ts +0 -153
  181. package/src/logic-functions/recall-api/__tests__/extract-recall-media-urls.test.ts +0 -67
  182. package/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +0 -744
  183. package/src/logic-functions/recall-api/__tests__/verify-recall-webhook-signature.test.ts +0 -122
  184. package/src/logic-functions/recall-api/cancel-recall-bot.util.ts +0 -28
  185. package/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +0 -47
  186. package/src/logic-functions/recall-api/eject-recall-bot.util.ts +0 -28
  187. package/src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts +0 -149
  188. package/src/logic-functions/recall-api/extract-recall-bot-id.util.ts +0 -10
  189. package/src/logic-functions/recall-api/extract-recall-media-urls.util.ts +0 -30
  190. package/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts +0 -8
  191. package/src/logic-functions/recall-api/get-recall-api-config.util.ts +0 -59
  192. package/src/logic-functions/recall-api/get-recall-bot.util.ts +0 -42
  193. package/src/logic-functions/recall-api/get-recall-recording.util.ts +0 -31
  194. package/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts +0 -18
  195. package/src/logic-functions/recall-api/list-recall-transcripts.util.ts +0 -141
  196. package/src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts +0 -106
  197. package/src/logic-functions/recall-api/normalize-recall-timestamp.util.ts +0 -14
  198. package/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +0 -88
  199. package/src/logic-functions/recall-api/recall-bot-api-request.util.ts +0 -165
  200. package/src/logic-functions/recall-api/recall-transcript-summary.type.ts +0 -5
  201. package/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +0 -56
  202. package/src/logic-functions/recall-api/retrieve-recall-transcript.util.ts +0 -71
  203. package/src/logic-functions/recall-api/schedule-recall-bot.util.ts +0 -68
  204. package/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts +0 -109
  205. package/src/logic-functions/recall-webhook.ts +0 -90
  206. package/src/logic-functions/reconcile-call-recorder-calendar-event.ts +0 -178
  207. package/src/logic-functions/reconcile-stale-bot-state.ts +0 -106
  208. package/src/logic-functions/types/calendar-event-record.type.ts +0 -5
  209. package/src/logic-functions/types/call-recorder-policy-calendar-event-input.type.ts +0 -10
  210. package/src/logic-functions/types/call-recorder-policy-input.type.ts +0 -9
  211. package/src/logic-functions/types/call-recorder-policy-not-required-reason.type.ts +0 -5
  212. package/src/logic-functions/types/call-recorder-policy-required-reason.type.ts +0 -1
  213. package/src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type.ts +0 -9
  214. package/src/logic-functions/types/call-recorder-policy-result-for-meeting.type.ts +0 -6
  215. package/src/logic-functions/types/call-recorder-policy-result.type.ts +0 -12
  216. package/src/logic-functions/types/call-recorder-reconciliation-result.type.ts +0 -16
  217. package/src/logic-functions/types/call-recording-media-file.type.ts +0 -1
  218. package/src/logic-functions/types/call-recording-record.type.ts +0 -15
  219. package/src/logic-functions/types/call-recording-update-fields.type.ts +0 -20
  220. package/src/logic-functions/types/files-field-value.type.ts +0 -1
  221. package/src/logic-functions/types/meeting-recording.type.ts +0 -7
  222. package/src/logic-functions/types/recall-bot-operation-result.type.ts +0 -19
  223. package/src/logic-functions/types/recall-routing-metadata.type.ts +0 -4
  224. package/src/logic-functions/types/removed-call-recorder-occurrence.type.ts +0 -6
  225. package/src/logic-functions/types/transcript-marker.type.ts +0 -6
  226. package/src/logic-functions/utils/as-record.util.ts +0 -6
  227. package/src/logic-functions/utils/get-application-variable-value.util.ts +0 -3
  228. package/src/logic-functions/utils/get-record-at-path.util.ts +0 -10
  229. package/src/logic-functions/utils/get-string.util.ts +0 -4
  230. package/src/logic-functions/utils/get-unique-sorted-ids.util.ts +0 -8
  231. package/src/logic-functions/utils/is-non-empty-string.util.ts +0 -5
  232. package/src/page-layouts/calendar-event-recording-tab.ts +0 -33
  233. package/src/view-fields/call-recorder-preference-on-calendar-event.view-field.ts +0 -27
  234. package/tsconfig.json +0 -42
  235. package/tsconfig.spec.json +0 -9
  236. package/vitest.config.ts +0 -31
  237. package/vitest.unit.config.ts +0 -14
@@ -1,672 +0,0 @@
1
- import { isNonEmptyArray, isNull, isUndefined } from '@sniptt/guards';
2
- import { type CoreApiClient } from 'twenty-client-sdk/core';
3
-
4
- import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status';
5
- import { type FilesFieldValue } from 'src/logic-functions/types/files-field-value.type';
6
- import { buildFailedTranscriptMarker } from 'src/logic-functions/domain/build-failed-transcript-marker.util';
7
- import { buildTranscriptFailureReason } from 'src/logic-functions/domain/build-transcript-failure-reason.util';
8
- import { downloadTranscript } from 'src/logic-functions/flows/download-transcript.util';
9
- import { extractRecallBotConvergence } from 'src/logic-functions/recall-api/extract-recall-bot-convergence.util';
10
- import { getRecallBot } from 'src/logic-functions/recall-api/get-recall-bot.util';
11
- import { getString } from 'src/logic-functions/utils/get-string.util';
12
- import { ingestCallRecordingMedia } from 'src/logic-functions/flows/ingest-call-recording-media.util';
13
- import { isCallRecordingStatusDowngrade } from 'src/logic-functions/domain/is-call-recording-status-downgrade.util';
14
- import { isRecallRecordingDoneSignal } from 'src/logic-functions/domain/is-recall-recording-done-signal.util';
15
- import { mapRecallStatusCodeToCallRecordingStatus } from 'src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util';
16
- import {
17
- parseRecallWebhookEvent,
18
- type RecallWebhookBody,
19
- type RecallWebhookEvent,
20
- } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util';
21
- import { parseTranscriptMarker } from 'src/logic-functions/domain/parse-transcript-marker.util';
22
- import { persistCallRecordingProgress } from 'src/logic-functions/flows/persist-call-recording-progress.util';
23
- import { reconcileCallRecordingTranscriptArtifact } from 'src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util';
24
- import { updateCallRecording } from 'src/logic-functions/data/update-call-recording.util';
25
- import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type';
26
-
27
- type MatchedCallRecording = {
28
- id: string;
29
- status?: string;
30
- startedAt?: string;
31
- endedAt?: string;
32
- externalRecordingId?: string;
33
- transcript?: unknown;
34
- audio?: FilesFieldValue;
35
- video?: FilesFieldValue;
36
- };
37
-
38
- type ExternalRecordingIdResolution = {
39
- externalRecordingId: string | undefined;
40
- providerLookupFailed: boolean;
41
- };
42
-
43
- type RecallWebhookHandlerResult =
44
- | {
45
- status: 'updated';
46
- callRecordingId: string;
47
- event: string;
48
- callRecordingStatus: string;
49
- }
50
- | {
51
- status: 'updated';
52
- callRecordingId: string;
53
- event: string;
54
- transcriptOutcome: 'FILLED' | 'FAILED';
55
- }
56
- | {
57
- status: 'skipped';
58
- event: string | null;
59
- reason: string;
60
- };
61
-
62
- export const handleRecallWebhook = async ({
63
- client,
64
- body,
65
- }: {
66
- client: CoreApiClient;
67
- body: RecallWebhookBody;
68
- }): Promise<RecallWebhookHandlerResult> => {
69
- const webhookEvent = parseRecallWebhookEvent(body);
70
-
71
- if (isUndefined(webhookEvent)) {
72
- return {
73
- status: 'skipped',
74
- event: null,
75
- reason: 'missing event type',
76
- };
77
- }
78
-
79
- const { event } = webhookEvent;
80
-
81
- if (event === 'transcript.done' || event === 'transcript.failed') {
82
- return handleRecallTranscriptEvent({ client, webhookEvent, event });
83
- }
84
-
85
- return handleRecallStatusEvent({ client, webhookEvent });
86
- };
87
-
88
- const handleRecallStatusEvent = async ({
89
- client,
90
- webhookEvent,
91
- }: {
92
- client: CoreApiClient;
93
- webhookEvent: RecallWebhookEvent;
94
- }): Promise<RecallWebhookHandlerResult> => {
95
- const { event, statusCode } = webhookEvent;
96
- const callRecordingStatus = mapRecallEventToCallRecordingStatus({
97
- event,
98
- statusCode,
99
- });
100
-
101
- if (isUndefined(callRecordingStatus)) {
102
- return {
103
- status: 'skipped',
104
- event,
105
- reason: `unsupported Recall event status ${statusCode ?? event}`,
106
- };
107
- }
108
-
109
- const callRecording = await findMatchingCallRecording({
110
- client,
111
- webhookEvent,
112
- });
113
-
114
- if (isUndefined(callRecording)) {
115
- return {
116
- status: 'skipped',
117
- event,
118
- reason: 'no matching call recording',
119
- };
120
- }
121
-
122
- if (
123
- isCallRecordingStatusDowngrade({
124
- fromStatus: callRecording.status,
125
- toStatus: callRecordingStatus,
126
- })
127
- ) {
128
- return {
129
- status: 'skipped',
130
- event,
131
- reason: `stale status event (${callRecording.status} -> ${callRecordingStatus})`,
132
- };
133
- }
134
-
135
- const updateData: CallRecordingUpdateFields = {
136
- ...(isUndefined(webhookEvent.externalBotId)
137
- ? {}
138
- : { externalBotId: webhookEvent.externalBotId }),
139
- ...buildExternalRecordingIdUpdate(webhookEvent),
140
- ...buildCallRecordingStatusUpdate({
141
- reason: getRecallWebhookFailureReason(webhookEvent),
142
- status: callRecordingStatus,
143
- }),
144
- ...buildRecordingTimestampsUpdate({ webhookEvent, callRecording }),
145
- };
146
-
147
- if (isRecallRecordingDoneSignal({ event, statusCode })) {
148
- const externalRecordingIdResolution = await resolveExternalRecordingId({
149
- callRecording,
150
- webhookEvent,
151
- });
152
-
153
- Object.assign(
154
- updateData,
155
- await buildTranscriptArtifactUpdate({
156
- callRecording,
157
- externalRecordingId: externalRecordingIdResolution.externalRecordingId,
158
- }),
159
- );
160
-
161
- Object.assign(
162
- updateData,
163
- await buildMediaIngestionUpdate({
164
- callRecording,
165
- externalRecordingId: externalRecordingIdResolution.externalRecordingId,
166
- }),
167
- );
168
-
169
- const terminalArtifactGateFailureUpdate =
170
- buildTerminalArtifactGateFailureUpdate({
171
- callRecording,
172
- providerLookupFailed:
173
- externalRecordingIdResolution.providerLookupFailed,
174
- updateData,
175
- webhookEvent,
176
- });
177
-
178
- if (!isUndefined(terminalArtifactGateFailureUpdate)) {
179
- Object.assign(updateData, terminalArtifactGateFailureUpdate);
180
- }
181
- }
182
-
183
- const { completesIngestion } = await persistCallRecordingProgress(client, {
184
- id: callRecording.id,
185
- current: callRecording,
186
- updateData,
187
- });
188
-
189
- return {
190
- status: 'updated',
191
- event,
192
- callRecordingId: callRecording.id,
193
- callRecordingStatus: completesIngestion
194
- ? CallRecordingStatus.COMPLETED
195
- : (updateData.status ?? callRecordingStatus),
196
- };
197
- };
198
-
199
- const findMatchingCallRecording = async ({
200
- client,
201
- webhookEvent,
202
- }: {
203
- client: CoreApiClient;
204
- webhookEvent: RecallWebhookEvent;
205
- }): Promise<MatchedCallRecording | undefined> => {
206
- if (!isUndefined(webhookEvent.callRecordingIdFromMetadata)) {
207
- return findCallRecordingByFilter(client, {
208
- id: { eq: webhookEvent.callRecordingIdFromMetadata },
209
- });
210
- }
211
-
212
- if (isUndefined(webhookEvent.externalBotId)) {
213
- return undefined;
214
- }
215
-
216
- return findCallRecordingByFilter(client, {
217
- externalBotId: { eq: webhookEvent.externalBotId },
218
- });
219
- };
220
-
221
- const findCallRecordingByFilter = async (
222
- client: CoreApiClient,
223
- filter: Record<string, unknown>,
224
- ): Promise<MatchedCallRecording | undefined> => {
225
- const queryResult = await client.query({
226
- callRecordings: {
227
- __args: {
228
- filter,
229
- first: 1,
230
- },
231
- edges: {
232
- node: {
233
- id: true,
234
- status: true,
235
- startedAt: true,
236
- endedAt: true,
237
- externalRecordingId: true,
238
- transcript: true,
239
- audio: { fileId: true },
240
- video: { fileId: true },
241
- },
242
- },
243
- },
244
- });
245
-
246
- const node = queryResult.callRecordings?.edges?.[0]?.node;
247
-
248
- if (isUndefined(node) || isNull(node)) {
249
- return undefined;
250
- }
251
-
252
- return {
253
- id: node.id,
254
- status: getString(node.status),
255
- startedAt: getString(node.startedAt),
256
- endedAt: getString(node.endedAt),
257
- externalRecordingId: getString(node.externalRecordingId),
258
- transcript: node.transcript ?? undefined,
259
- audio: node.audio ?? undefined,
260
- video: node.video ?? undefined,
261
- };
262
- };
263
-
264
- const mapRecallEventToCallRecordingStatus = ({
265
- event,
266
- statusCode,
267
- }: {
268
- event: string;
269
- statusCode: string | undefined;
270
- }): CallRecordingStatus | undefined => {
271
- if (event === 'recording.done') {
272
- return CallRecordingStatus.PROCESSING;
273
- }
274
-
275
- if (event === 'recording.failed') {
276
- return CallRecordingStatus.FAILED;
277
- }
278
-
279
- return mapRecallStatusCodeToCallRecordingStatus(statusCode);
280
- };
281
-
282
- const buildRecordingTimestampsUpdate = ({
283
- webhookEvent,
284
- callRecording,
285
- }: {
286
- webhookEvent: RecallWebhookEvent;
287
- callRecording: MatchedCallRecording;
288
- }): { startedAt?: string; endedAt?: string } => {
289
- const { event, statusCode, statusTimestamp } = webhookEvent;
290
-
291
- const impliesRecordingStarted = statusCode === 'in_call_recording';
292
- const impliesRecordingEnded =
293
- event === 'recording.done' ||
294
- statusCode === 'call_ended' ||
295
- statusCode === 'done';
296
-
297
- const startedAt =
298
- webhookEvent.recordingStartedAt ??
299
- (impliesRecordingStarted ? statusTimestamp : undefined);
300
- const endedAt =
301
- webhookEvent.recordingEndedAt ??
302
- (impliesRecordingEnded ? statusTimestamp : undefined);
303
-
304
- return {
305
- ...(!isUndefined(startedAt) && isUndefined(callRecording.startedAt)
306
- ? { startedAt }
307
- : {}),
308
- ...(!isUndefined(endedAt) && isUndefined(callRecording.endedAt)
309
- ? { endedAt }
310
- : {}),
311
- };
312
- };
313
-
314
- const buildExternalRecordingIdUpdate = (
315
- webhookEvent: RecallWebhookEvent,
316
- ): { externalRecordingId?: string } =>
317
- isUndefined(webhookEvent.externalRecordingId)
318
- ? {}
319
- : { externalRecordingId: webhookEvent.externalRecordingId };
320
-
321
- type NonFailedCallRecordingStatus = Exclude<
322
- CallRecordingStatus,
323
- CallRecordingStatus.FAILED
324
- >;
325
-
326
- type CallRecordingStatusUpdate =
327
- | {
328
- status: NonFailedCallRecordingStatus;
329
- }
330
- | {
331
- status: CallRecordingStatus.FAILED;
332
- callRecorderFailureReason: string;
333
- };
334
-
335
- type TerminalArtifactGateFailureUpdate = {
336
- status: CallRecordingStatus.FAILED;
337
- callRecorderFailureReason: string;
338
- };
339
-
340
- const buildCallRecordingStatusUpdate = ({
341
- reason,
342
- status,
343
- }: {
344
- reason: string;
345
- status: CallRecordingStatus;
346
- }): CallRecordingStatusUpdate => {
347
- if (status === CallRecordingStatus.FAILED) {
348
- return { status, callRecorderFailureReason: reason };
349
- }
350
-
351
- return { status };
352
- };
353
-
354
- const buildTerminalArtifactGateFailureUpdate = ({
355
- callRecording,
356
- providerLookupFailed,
357
- updateData,
358
- webhookEvent,
359
- }: {
360
- callRecording: MatchedCallRecording;
361
- providerLookupFailed: boolean;
362
- updateData: CallRecordingUpdateFields;
363
- webhookEvent: RecallWebhookEvent;
364
- }): TerminalArtifactGateFailureUpdate | undefined => {
365
- if (updateData.status === CallRecordingStatus.FAILED) {
366
- return isUndefined(updateData.callRecorderFailureReason)
367
- ? {
368
- status: CallRecordingStatus.FAILED,
369
- callRecorderFailureReason:
370
- getRecallWebhookFailureReason(webhookEvent),
371
- }
372
- : undefined;
373
- }
374
-
375
- if (
376
- providerLookupFailed ||
377
- hasRecordingArtifactPath({ callRecording, updateData })
378
- ) {
379
- return undefined;
380
- }
381
-
382
- return {
383
- status: CallRecordingStatus.FAILED,
384
- callRecorderFailureReason: 'recording_artifacts_unavailable',
385
- };
386
- };
387
-
388
- const getRecallWebhookFailureReason = ({
389
- event,
390
- statusCode,
391
- }: RecallWebhookEvent): string => statusCode ?? event;
392
-
393
- const hasRecordingArtifactPath = ({
394
- callRecording,
395
- updateData,
396
- }: {
397
- callRecording: MatchedCallRecording;
398
- updateData: CallRecordingUpdateFields;
399
- }): boolean => {
400
- return (
401
- !isUndefined(
402
- updateData.externalRecordingId ?? callRecording.externalRecordingId,
403
- ) ||
404
- isNonEmptyArray(updateData.audio ?? callRecording.audio) ||
405
- isNonEmptyArray(updateData.video ?? callRecording.video) ||
406
- hasReachableTranscript(updateData.transcript ?? callRecording.transcript)
407
- );
408
- };
409
-
410
- const hasReachableTranscript = (transcript: unknown): boolean => {
411
- if (isNull(transcript) || isUndefined(transcript)) {
412
- return false;
413
- }
414
-
415
- const marker = parseTranscriptMarker(transcript);
416
-
417
- return isUndefined(marker) || marker.status === 'PENDING';
418
- };
419
-
420
- const isTranscriptUnset = (callRecording: MatchedCallRecording): boolean =>
421
- isUndefined(callRecording.transcript);
422
-
423
- const buildMediaIngestionUpdate = async ({
424
- callRecording,
425
- externalRecordingId,
426
- }: {
427
- callRecording: MatchedCallRecording;
428
- externalRecordingId: string | undefined;
429
- }): Promise<Pick<CallRecordingUpdateFields, 'audio' | 'video'>> => {
430
- const hasAudio = isNonEmptyArray(callRecording.audio);
431
- const hasVideo = isNonEmptyArray(callRecording.video);
432
-
433
- if (hasAudio && hasVideo) {
434
- return {};
435
- }
436
-
437
- if (isUndefined(externalRecordingId)) {
438
- console.warn(
439
- `[call-recorder] cannot ingest media for call recording ${callRecording.id}: no Recall recording id available`,
440
- );
441
-
442
- return {};
443
- }
444
-
445
- return ingestCallRecordingMedia({
446
- callRecordingId: callRecording.id,
447
- externalRecordingId,
448
- hasAudio,
449
- hasVideo,
450
- });
451
- };
452
-
453
- const buildTranscriptArtifactUpdate = async ({
454
- callRecording,
455
- externalRecordingId,
456
- }: {
457
- callRecording: MatchedCallRecording;
458
- externalRecordingId: string | undefined;
459
- }): Promise<CallRecordingUpdateFields> => {
460
- if (isUndefined(externalRecordingId)) {
461
- console.warn(
462
- `[call-recorder] cannot reconcile transcript for call recording ${callRecording.id}: no Recall recording id available`,
463
- );
464
-
465
- return {};
466
- }
467
-
468
- const transcriptArtifactResult =
469
- await reconcileCallRecordingTranscriptArtifact({
470
- callRecordingId: callRecording.id,
471
- currentStatus: callRecording.status,
472
- externalRecordingId,
473
- requestedAt: new Date().toISOString(),
474
- transcript: callRecording.transcript,
475
- });
476
-
477
- return {
478
- ...(isUndefined(callRecording.externalRecordingId)
479
- ? { externalRecordingId }
480
- : {}),
481
- ...transcriptArtifactResult.updateData,
482
- };
483
- };
484
-
485
- const resolveExternalRecordingId = async ({
486
- callRecording,
487
- webhookEvent,
488
- }: {
489
- callRecording: MatchedCallRecording;
490
- webhookEvent: RecallWebhookEvent;
491
- }): Promise<ExternalRecordingIdResolution> => {
492
- const externalRecordingId =
493
- webhookEvent.externalRecordingId ?? callRecording.externalRecordingId;
494
-
495
- if (!isUndefined(externalRecordingId)) {
496
- return { externalRecordingId, providerLookupFailed: false };
497
- }
498
-
499
- if (isUndefined(webhookEvent.externalBotId)) {
500
- return { externalRecordingId: undefined, providerLookupFailed: false };
501
- }
502
-
503
- return fetchExternalRecordingIdFromRecallBot(webhookEvent.externalBotId);
504
- };
505
-
506
- const fetchExternalRecordingIdFromRecallBot = async (
507
- externalBotId: string,
508
- ): Promise<ExternalRecordingIdResolution> => {
509
- const botResult = await getRecallBot({ externalBotId });
510
-
511
- if (!botResult.ok) {
512
- console.warn(
513
- `[call-recorder] failed to fetch Recall bot ${externalBotId} while resolving a recording id: ${botResult.errorMessage}`,
514
- );
515
-
516
- return { externalRecordingId: undefined, providerLookupFailed: true };
517
- }
518
-
519
- return {
520
- externalRecordingId: extractRecallBotConvergence(botResult.bot)
521
- .externalRecordingId,
522
- providerLookupFailed: false,
523
- };
524
- };
525
-
526
- const handleRecallTranscriptEvent = async ({
527
- client,
528
- webhookEvent,
529
- event,
530
- }: {
531
- client: CoreApiClient;
532
- webhookEvent: RecallWebhookEvent;
533
- event: 'transcript.done' | 'transcript.failed';
534
- }): Promise<RecallWebhookHandlerResult> => {
535
- const callRecording = await findMatchingCallRecording({
536
- client,
537
- webhookEvent,
538
- });
539
-
540
- if (isUndefined(callRecording)) {
541
- return {
542
- status: 'skipped',
543
- event,
544
- reason: 'no matching call recording',
545
- };
546
- }
547
-
548
- const { transcriptId } = webhookEvent;
549
-
550
- if (event === 'transcript.failed') {
551
- return applyTranscriptFailure({
552
- client,
553
- callRecording,
554
- event,
555
- transcriptId,
556
- subCode: webhookEvent.transcriptFailureSubCode ?? null,
557
- });
558
- }
559
-
560
- if (isUndefined(transcriptId)) {
561
- return {
562
- status: 'skipped',
563
- event,
564
- reason: 'missing transcript id',
565
- };
566
- }
567
-
568
- const downloadResult = await downloadTranscript({ transcriptId });
569
-
570
- switch (downloadResult.outcome) {
571
- case 'filled': {
572
- const updateData: CallRecordingUpdateFields = {
573
- transcript: downloadResult.content as Record<string, unknown>,
574
- ...(isUndefined(callRecording.externalRecordingId)
575
- ? buildExternalRecordingIdUpdate(webhookEvent)
576
- : {}),
577
- };
578
-
579
- await persistCallRecordingProgress(client, {
580
- id: callRecording.id,
581
- current: callRecording,
582
- updateData,
583
- });
584
-
585
- return {
586
- status: 'updated',
587
- event,
588
- callRecordingId: callRecording.id,
589
- transcriptOutcome: 'FILLED',
590
- };
591
- }
592
- case 'failed':
593
- return applyTranscriptFailure({
594
- client,
595
- callRecording,
596
- event,
597
- transcriptId,
598
- subCode: downloadResult.subCode,
599
- });
600
- case 'pending':
601
- case 'error': {
602
- // 200-acked either way, Svix never redelivers; the cron re-check retries this.
603
- const reason =
604
- downloadResult.outcome === 'pending'
605
- ? 'transcript not downloadable yet'
606
- : downloadResult.errorMessage;
607
-
608
- console.warn(
609
- `[call-recorder] could not fill transcript for call recording ${callRecording.id}: ${reason}`,
610
- );
611
-
612
- return {
613
- status: 'skipped',
614
- event,
615
- reason,
616
- };
617
- }
618
- }
619
- };
620
-
621
- const applyTranscriptFailure = async ({
622
- client,
623
- callRecording,
624
- event,
625
- transcriptId,
626
- subCode,
627
- }: {
628
- client: CoreApiClient;
629
- callRecording: MatchedCallRecording;
630
- event: string;
631
- transcriptId: string | undefined;
632
- subCode: string | null;
633
- }): Promise<RecallWebhookHandlerResult> => {
634
- const existingMarker = parseTranscriptMarker(callRecording.transcript);
635
-
636
- if (!isTranscriptUnset(callRecording) && isUndefined(existingMarker)) {
637
- return {
638
- status: 'skipped',
639
- event,
640
- reason: 'transcript already filled',
641
- };
642
- }
643
-
644
- console.warn(
645
- `[call-recorder] transcript failed for call recording ${callRecording.id}${isNull(subCode) ? '' : ` (${subCode})`}`,
646
- );
647
-
648
- await updateCallRecording(client, {
649
- id: callRecording.id,
650
- data: {
651
- transcript: buildFailedTranscriptMarker({
652
- recallTranscriptId:
653
- transcriptId ?? existingMarker?.recallTranscriptId ?? null,
654
- subCode,
655
- }),
656
- callRecorderFailureReason: buildTranscriptFailureReason(subCode),
657
- ...(isCallRecordingStatusDowngrade({
658
- fromStatus: callRecording.status,
659
- toStatus: CallRecordingStatus.FAILED,
660
- })
661
- ? {}
662
- : { status: CallRecordingStatus.FAILED }),
663
- },
664
- });
665
-
666
- return {
667
- status: 'updated',
668
- event,
669
- callRecordingId: callRecording.id,
670
- transcriptOutcome: 'FAILED',
671
- };
672
- };