@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,82 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
- import { type CoreApiClient } from 'twenty-client-sdk/core';
3
-
4
- import { type CalendarEventRecord } from 'src/logic-functions/types/calendar-event-record.type';
5
- import { ensureCallRecorder } from 'src/logic-functions/flows/ensure-call-recorder.util';
6
- import { fetchCalendarEventsByIds } from 'src/logic-functions/data/fetch-calendar-events-by-ids.util';
7
- import { findOpenScheduledCallRecordings } from 'src/logic-functions/data/find-open-scheduled-call-recordings.util';
8
- import { getUniqueSortedIds } from 'src/logic-functions/utils/get-unique-sorted-ids.util';
9
-
10
- export type HealCallRecordingsMissingBotResult = {
11
- scheduledCallRecordingIds: string[];
12
- };
13
-
14
- // Closes the create-winner crash gap: a run that inserted the row but died before POSTing leaves a botless recording, and the cron is the single writer that re-POSTs it.
15
- export const healCallRecordingsMissingBot = async ({
16
- client,
17
- now,
18
- }: {
19
- client: CoreApiClient;
20
- now: Date;
21
- }): Promise<HealCallRecordingsMissingBotResult> => {
22
- const botlessCallRecordings = (
23
- await findOpenScheduledCallRecordings(client)
24
- ).filter((callRecording) => isUndefined(callRecording.externalBotId));
25
-
26
- if (botlessCallRecordings.length === 0) {
27
- return { scheduledCallRecordingIds: [] };
28
- }
29
-
30
- const calendarEventsById = new Map(
31
- (
32
- await fetchCalendarEventsByIds(
33
- client,
34
- getUniqueSortedIds(
35
- botlessCallRecordings.map(
36
- (callRecording) => callRecording.calendarEventId,
37
- ),
38
- ),
39
- )
40
- ).map((calendarEvent) => [calendarEvent.id, calendarEvent]),
41
- );
42
- const scheduledCallRecordingIds: string[] = [];
43
-
44
- for (const callRecording of botlessCallRecordings) {
45
- const calendarEvent = isUndefined(callRecording.calendarEventId)
46
- ? undefined
47
- : calendarEventsById.get(callRecording.calendarEventId);
48
-
49
- if (isUndefined(calendarEvent) || hasMeetingEnded({ calendarEvent, now })) {
50
- continue;
51
- }
52
-
53
- const didScheduleCallRecorder = await ensureCallRecorder(client, {
54
- callRecording,
55
- calendarEvent,
56
- });
57
-
58
- if (didScheduleCallRecorder) {
59
- scheduledCallRecordingIds.push(callRecording.id);
60
- }
61
- }
62
-
63
- return { scheduledCallRecordingIds };
64
- };
65
-
66
- const hasMeetingEnded = ({
67
- calendarEvent,
68
- now,
69
- }: {
70
- calendarEvent: CalendarEventRecord;
71
- now: Date;
72
- }): boolean => {
73
- const reference = calendarEvent.endsAt ?? calendarEvent.startsAt;
74
-
75
- if (isUndefined(reference)) {
76
- return false;
77
- }
78
-
79
- const referenceTime = new Date(reference).getTime();
80
-
81
- return !Number.isNaN(referenceTime) && referenceTime <= now.getTime();
82
- };
@@ -1,128 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
- import { MetadataApiClient } from 'twenty-client-sdk/metadata';
3
-
4
- import { CALL_RECORDING_AUDIO_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recording-audio-field-universal-identifier';
5
- import { CALL_RECORDING_VIDEO_FIELD_UNIVERSAL_IDENTIFIER } from 'src/constants/call-recording-video-field-universal-identifier';
6
- import { extractRecallMediaUrls } from 'src/logic-functions/recall-api/extract-recall-media-urls.util';
7
- import { getRecallRecording } from 'src/logic-functions/recall-api/get-recall-recording.util';
8
- import { type CallRecordingMediaFile } from 'src/logic-functions/types/call-recording-media-file.type';
9
- import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type';
10
-
11
- type CallRecordingMediaUpdateFields = Pick<
12
- CallRecordingUpdateFields,
13
- 'audio' | 'video'
14
- >;
15
-
16
- const MEDIA_DOWNLOAD_TIMEOUT_MS = 120_000;
17
-
18
- export const ingestCallRecordingMedia = async ({
19
- callRecordingId,
20
- externalRecordingId,
21
- hasAudio,
22
- hasVideo,
23
- }: {
24
- callRecordingId: string;
25
- externalRecordingId: string;
26
- hasAudio: boolean;
27
- hasVideo: boolean;
28
- }): Promise<CallRecordingMediaUpdateFields> => {
29
- if (hasAudio && hasVideo) {
30
- return {};
31
- }
32
-
33
- const recordingResult = await getRecallRecording({ externalRecordingId });
34
-
35
- if (!recordingResult.ok) {
36
- console.warn(
37
- `[call-recorder] failed to fetch Recall recording ${externalRecordingId} while ingesting media for call recording ${callRecordingId}: ${recordingResult.errorMessage}`,
38
- );
39
-
40
- return {};
41
- }
42
-
43
- const mediaUrls = extractRecallMediaUrls(recordingResult.recording);
44
- const metadataClient = new MetadataApiClient();
45
- const updateFields: CallRecordingMediaUpdateFields = {};
46
-
47
- if (!hasVideo && !isUndefined(mediaUrls.videoUrl)) {
48
- const video = await ingestMediaArtifact({
49
- callRecordingId,
50
- metadataClient,
51
- url: mediaUrls.videoUrl,
52
- fileName: 'video.mp4',
53
- fieldMetadataUniversalIdentifier:
54
- CALL_RECORDING_VIDEO_FIELD_UNIVERSAL_IDENTIFIER,
55
- });
56
-
57
- if (!isUndefined(video)) {
58
- updateFields.video = video;
59
- }
60
- }
61
-
62
- if (!hasAudio && !isUndefined(mediaUrls.audioUrl)) {
63
- const audio = await ingestMediaArtifact({
64
- callRecordingId,
65
- metadataClient,
66
- url: mediaUrls.audioUrl,
67
- fileName: 'audio.mp3',
68
- fieldMetadataUniversalIdentifier:
69
- CALL_RECORDING_AUDIO_FIELD_UNIVERSAL_IDENTIFIER,
70
- });
71
-
72
- if (!isUndefined(audio)) {
73
- updateFields.audio = audio;
74
- }
75
- }
76
-
77
- return updateFields;
78
- };
79
-
80
- const ingestMediaArtifact = async ({
81
- callRecordingId,
82
- metadataClient,
83
- url,
84
- fileName,
85
- fieldMetadataUniversalIdentifier,
86
- }: {
87
- callRecordingId: string;
88
- metadataClient: InstanceType<typeof MetadataApiClient>;
89
- url: string;
90
- fileName: string;
91
- fieldMetadataUniversalIdentifier: string;
92
- }): Promise<CallRecordingMediaFile[] | undefined> => {
93
- try {
94
- const { buffer, contentType } = await downloadMediaFile(url);
95
- const uploadedFile = await metadataClient.uploadFile(
96
- buffer,
97
- fileName,
98
- contentType,
99
- fieldMetadataUniversalIdentifier,
100
- );
101
-
102
- return [{ fileId: uploadedFile.id, label: fileName }];
103
- } catch (error) {
104
- console.warn(
105
- `[call-recorder] failed to ingest ${fileName} for call recording ${callRecordingId}: ${error instanceof Error ? error.message : String(error)}`,
106
- );
107
-
108
- return undefined;
109
- }
110
- };
111
-
112
- const downloadMediaFile = async (
113
- url: string,
114
- ): Promise<{ buffer: Buffer; contentType: string }> => {
115
- const response = await fetch(url, {
116
- signal: AbortSignal.timeout(MEDIA_DOWNLOAD_TIMEOUT_MS),
117
- });
118
-
119
- if (!response.ok) {
120
- throw new Error(`download failed with status ${response.status}`);
121
- }
122
-
123
- return {
124
- buffer: Buffer.from(await response.arrayBuffer()),
125
- contentType:
126
- response.headers.get('content-type') ?? 'application/octet-stream',
127
- };
128
- };
@@ -1,58 +0,0 @@
1
- import { type CoreApiClient } from 'twenty-client-sdk/core';
2
-
3
- import { type FilesFieldValue } from 'src/logic-functions/types/files-field-value.type';
4
- import { completeAndChargeCallRecording } from 'src/logic-functions/flows/complete-and-charge-call-recording.util';
5
- import { shouldCompleteCallRecordingIngestion } from 'src/logic-functions/domain/should-complete-call-recording-ingestion.util';
6
- import { updateCallRecording } from 'src/logic-functions/data/update-call-recording.util';
7
- import { type CallRecordingUpdateFields } from 'src/logic-functions/types/call-recording-update-fields.type';
8
-
9
- type PersistCallRecordingProgressCurrent = {
10
- status?: string;
11
- startedAt?: string;
12
- endedAt?: string;
13
- transcript?: unknown;
14
- audio?: FilesFieldValue;
15
- video?: FilesFieldValue;
16
- };
17
-
18
- export const persistCallRecordingProgress = async (
19
- client: CoreApiClient,
20
- {
21
- id,
22
- current,
23
- updateData,
24
- }: {
25
- id: string;
26
- current: PersistCallRecordingProgressCurrent;
27
- updateData: CallRecordingUpdateFields;
28
- },
29
- ): Promise<{ completesIngestion: boolean }> => {
30
- const completesIngestion = shouldCompleteCallRecordingIngestion({
31
- current,
32
- updateData,
33
- });
34
-
35
- if (!completesIngestion) {
36
- await updateCallRecording(client, { id, data: updateData });
37
-
38
- return { completesIngestion: false };
39
- }
40
-
41
- // Strip status so COMPLETED is written only by the atomic claim — its single winner bills once.
42
- const nonStatusUpdate: CallRecordingUpdateFields = { ...updateData };
43
-
44
- delete nonStatusUpdate.status;
45
- delete nonStatusUpdate.callRecorderFailureReason;
46
-
47
- if (Object.keys(nonStatusUpdate).length > 0) {
48
- await updateCallRecording(client, { id, data: nonStatusUpdate });
49
- }
50
-
51
- await completeAndChargeCallRecording(client, {
52
- id,
53
- startedAt: updateData.startedAt ?? current.startedAt,
54
- endedAt: updateData.endedAt ?? current.endedAt,
55
- });
56
-
57
- return { completesIngestion: true };
58
- };
@@ -1,183 +0,0 @@
1
- import { isNull, isUndefined } from '@sniptt/guards';
2
- import { type CoreApiClient } from 'twenty-client-sdk/core';
3
-
4
- import { CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status';
5
- import { type CallRecordingRecord } from 'src/logic-functions/types/call-recording-record.type';
6
- import { cancelRecallBot } from 'src/logic-functions/recall-api/cancel-recall-bot.util';
7
- import { ejectRecallBot } from 'src/logic-functions/recall-api/eject-recall-bot.util';
8
- import { findCallRecordingsByIds } from 'src/logic-functions/data/find-call-recordings-by-ids.util';
9
- import { getCurrentWorkspaceId } from 'src/logic-functions/data/get-current-workspace-id.util';
10
- import { getUniqueSortedIds } from 'src/logic-functions/utils/get-unique-sorted-ids.util';
11
- import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
12
- import {
13
- listScheduledRecallBots,
14
- type RecallScheduledBot,
15
- } from 'src/logic-functions/recall-api/list-scheduled-recall-bots.util';
16
-
17
- export type ReapOrphanedCallRecordersResult = {
18
- scannedBotCount: number;
19
- canceledExternalBotIds: string[];
20
- };
21
-
22
- // Bots no open CallRecording request claims would still join; cancel them on Recall.
23
- export const reapOrphanedCallRecorders = async ({
24
- client,
25
- joinAtAfter,
26
- joinAtBefore,
27
- }: {
28
- client: CoreApiClient;
29
- joinAtAfter: string;
30
- joinAtBefore: string;
31
- }): Promise<ReapOrphanedCallRecordersResult> => {
32
- const listResult = await listScheduledRecallBots({
33
- joinAtAfter,
34
- joinAtBefore,
35
- });
36
-
37
- if (!listResult.ok) {
38
- console.warn(
39
- `[call-recorder] failed to list Recall bots for orphan reaping: ${listResult.errorMessage}`,
40
- );
41
-
42
- return { scannedBotCount: 0, canceledExternalBotIds: [] };
43
- }
44
-
45
- const currentWorkspaceId = getCurrentWorkspaceId();
46
-
47
- if (isUndefined(currentWorkspaceId)) {
48
- console.warn(
49
- '[call-recorder] cannot reap orphaned Recall bots: workspace id unavailable',
50
- );
51
-
52
- return {
53
- scannedBotCount: listResult.bots.length,
54
- canceledExternalBotIds: [],
55
- };
56
- }
57
-
58
- const workspaceManagedBots = listResult.bots.filter((bot) =>
59
- isCurrentWorkspaceManagedBot({ bot, currentWorkspaceId }),
60
- );
61
-
62
- if (workspaceManagedBots.length === 0) {
63
- return {
64
- scannedBotCount: listResult.bots.length,
65
- canceledExternalBotIds: [],
66
- };
67
- }
68
-
69
- const callRecordings = await findCallRecordingsByIds(
70
- client,
71
- getUniqueSortedIds(
72
- workspaceManagedBots.map((bot) => getClaimedCallRecordingId(bot)),
73
- ),
74
- );
75
- const callRecordingsById = new Map(
76
- callRecordings.map((callRecording) => [callRecording.id, callRecording]),
77
- );
78
- const canceledExternalBotIds: string[] = [];
79
-
80
- for (const bot of workspaceManagedBots) {
81
- const claimedCallRecordingId = getClaimedCallRecordingId(bot);
82
- const callRecording = isUndefined(claimedCallRecordingId)
83
- ? undefined
84
- : callRecordingsById.get(claimedCallRecordingId);
85
-
86
- if (isBotClaimed({ bot, callRecording })) {
87
- continue;
88
- }
89
-
90
- console.warn(
91
- `[call-recorder] canceling orphaned Recall bot ${bot.id} (claimed callRecording: ${claimedCallRecordingId})`,
92
- );
93
-
94
- if (await cancelOrEjectRecallBot(bot.id)) {
95
- canceledExternalBotIds.push(bot.id);
96
- }
97
- }
98
-
99
- return {
100
- scannedBotCount: listResult.bots.length,
101
- canceledExternalBotIds,
102
- };
103
- };
104
-
105
- const getClaimedCallRecordingId = (
106
- bot: RecallScheduledBot,
107
- ): string | undefined => {
108
- const claimedCallRecordingId = bot.metadata.twentyCallRecordingId;
109
-
110
- return normalizeOptionalString(claimedCallRecordingId);
111
- };
112
-
113
- const getClaimedWorkspaceId = (bot: RecallScheduledBot): string | undefined => {
114
- const claimedWorkspaceId = bot.metadata.twentyWorkspaceId;
115
-
116
- return normalizeOptionalString(claimedWorkspaceId);
117
- };
118
-
119
- const isCurrentWorkspaceManagedBot = ({
120
- bot,
121
- currentWorkspaceId,
122
- }: {
123
- bot: RecallScheduledBot;
124
- currentWorkspaceId: string;
125
- }): boolean => {
126
- if (isUndefined(getClaimedCallRecordingId(bot))) {
127
- return false;
128
- }
129
-
130
- const claimedWorkspaceId = getClaimedWorkspaceId(bot);
131
-
132
- return claimedWorkspaceId === currentWorkspaceId;
133
- };
134
-
135
- const isBotClaimed = ({
136
- bot,
137
- callRecording,
138
- }: {
139
- bot: RecallScheduledBot;
140
- callRecording: CallRecordingRecord | undefined;
141
- }): boolean => {
142
- if (
143
- callRecording?.recordingRequestStatus !==
144
- CallRecordingRequestStatus.REQUESTED
145
- ) {
146
- return false;
147
- }
148
-
149
- if (callRecording.externalBotId === bot.id) {
150
- return true;
151
- }
152
-
153
- // An id-less REQUESTED recording may have a bot-id write-back in flight; spare its bot.
154
- return isUndefined(callRecording.externalBotId);
155
- };
156
-
157
- const cancelOrEjectRecallBot = async (
158
- externalBotId: string,
159
- ): Promise<boolean> => {
160
- const cancelResult = await cancelRecallBot({ externalBotId });
161
-
162
- if (cancelResult.ok) {
163
- return true;
164
- }
165
-
166
- // Deleting only works for not-yet-joined bots; eject the ones already in a call.
167
- if (!isNull(cancelResult.status)) {
168
- const ejectResult = await ejectRecallBot({ externalBotId });
169
-
170
- if (ejectResult.ok) {
171
- return true;
172
- }
173
- }
174
-
175
- console.warn(
176
- `[call-recorder] failed to cancel orphaned Recall bot ${externalBotId}: ${cancelResult.errorMessage}`,
177
- );
178
-
179
- return false;
180
- };
181
-
182
- const normalizeOptionalString = (value: unknown): string | undefined =>
183
- isNonEmptyString(value) ? value.trim() : undefined;