@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,141 +0,0 @@
1
- import { isArray, isUndefined } from '@sniptt/guards';
2
-
3
- import { type RecallBotOperationFailure } from 'src/logic-functions/types/recall-bot-operation-result.type';
4
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
5
- import { getString } from 'src/logic-functions/utils/get-string.util';
6
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
7
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
8
- import { type RecallTranscriptSummary } from 'src/logic-functions/recall-api/recall-transcript-summary.type';
9
-
10
- type ListRecallTranscriptsResult =
11
- | { ok: true; transcripts: RecallTranscriptSummary[] }
12
- | RecallBotOperationFailure;
13
-
14
- type RecallTranscriptListResponse = {
15
- next?: unknown;
16
- results?: unknown;
17
- };
18
-
19
- const RECALL_TRANSCRIPT_LIST_MAX_PAGES = 10;
20
-
21
- export const listRecallTranscripts = async ({
22
- externalRecordingId,
23
- }: {
24
- externalRecordingId: string;
25
- }): Promise<ListRecallTranscriptsResult> => {
26
- const configResult = getRecallApiConfig();
27
-
28
- if (!configResult.success) {
29
- return { ok: false, status: null, errorMessage: configResult.error };
30
- }
31
-
32
- const transcripts: RecallTranscriptSummary[] = [];
33
- let path: string | undefined = buildListRecallTranscriptsPath({
34
- externalRecordingId,
35
- });
36
-
37
- for (
38
- let pageIndex = 0;
39
- !isUndefined(path) && pageIndex < RECALL_TRANSCRIPT_LIST_MAX_PAGES;
40
- pageIndex++
41
- ) {
42
- const result = await recallBotApiRequest<RecallTranscriptListResponse>({
43
- config: configResult.config,
44
- path,
45
- method: 'GET',
46
- });
47
-
48
- if (!result.ok) {
49
- return result;
50
- }
51
-
52
- const pageTranscripts = extractRecallTranscriptSummaries(result.data);
53
-
54
- if (isUndefined(pageTranscripts)) {
55
- return {
56
- ok: false,
57
- status: result.status,
58
- errorMessage: 'Recall API returned malformed transcript list',
59
- };
60
- }
61
-
62
- transcripts.push(...pageTranscripts);
63
- path = extractNextPath(result.data, configResult.config.baseUrl);
64
- }
65
-
66
- if (!isUndefined(path)) {
67
- return {
68
- ok: false,
69
- status: null,
70
- errorMessage: `Recall transcript list exceeded ${RECALL_TRANSCRIPT_LIST_MAX_PAGES} pages`,
71
- };
72
- }
73
-
74
- return { ok: true, transcripts };
75
- };
76
-
77
- const buildListRecallTranscriptsPath = ({
78
- externalRecordingId,
79
- }: {
80
- externalRecordingId: string;
81
- }): string => {
82
- const searchParams = new URLSearchParams({
83
- recording_id: externalRecordingId,
84
- });
85
-
86
- return `/transcript/?${searchParams.toString()}`;
87
- };
88
-
89
- const extractRecallTranscriptSummaries = (
90
- response: RecallTranscriptListResponse | undefined,
91
- ): RecallTranscriptSummary[] | undefined => {
92
- if (!isArray(response?.results)) {
93
- return undefined;
94
- }
95
-
96
- const transcripts: RecallTranscriptSummary[] = [];
97
-
98
- for (const result of response.results) {
99
- const transcript = extractRecallTranscriptSummary(result);
100
-
101
- if (isUndefined(transcript)) {
102
- return undefined;
103
- }
104
-
105
- transcripts.push(transcript);
106
- }
107
-
108
- return transcripts;
109
- };
110
-
111
- const extractRecallTranscriptSummary = (
112
- transcript: unknown,
113
- ): RecallTranscriptSummary | undefined => {
114
- const transcriptRecord = asRecord(transcript);
115
- const transcriptId = getString(transcriptRecord?.id);
116
-
117
- if (isUndefined(transcriptRecord) || isUndefined(transcriptId)) {
118
- return undefined;
119
- }
120
-
121
- const status = asRecord(transcriptRecord.status);
122
-
123
- return {
124
- id: transcriptId,
125
- statusCode: getString(status?.code),
126
- statusSubCode: getString(status?.sub_code),
127
- };
128
- };
129
-
130
- const extractNextPath = (
131
- response: RecallTranscriptListResponse | undefined,
132
- baseUrl: string,
133
- ): string | undefined => {
134
- const nextPage = getString(response?.next);
135
-
136
- if (isUndefined(nextPage) || !nextPage.startsWith(baseUrl)) {
137
- return undefined;
138
- }
139
-
140
- return nextPage.slice(baseUrl.length);
141
- };
@@ -1,106 +0,0 @@
1
- import { isString, isUndefined } from '@sniptt/guards';
2
-
3
- import { type RecallBotOperationFailure } from 'src/logic-functions/types/recall-bot-operation-result.type';
4
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
5
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
6
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
7
-
8
- export type RecallScheduledBot = {
9
- id: string;
10
- metadata: Record<string, unknown>;
11
- };
12
-
13
- type RecallBotListResponse = {
14
- next?: unknown;
15
- results?: unknown;
16
- };
17
-
18
- type ListScheduledRecallBotsResult =
19
- | { ok: true; bots: RecallScheduledBot[] }
20
- | RecallBotOperationFailure;
21
-
22
- const RECALL_BOT_LIST_MAX_PAGES = 10;
23
-
24
- export const listScheduledRecallBots = async ({
25
- joinAtAfter,
26
- joinAtBefore,
27
- }: {
28
- joinAtAfter: string;
29
- joinAtBefore: string;
30
- }): Promise<ListScheduledRecallBotsResult> => {
31
- const configResult = getRecallApiConfig();
32
-
33
- if (!configResult.success) {
34
- return { ok: false, status: null, errorMessage: configResult.error };
35
- }
36
-
37
- const bots: RecallScheduledBot[] = [];
38
- let path: string | undefined = `/bot/?join_at_after=${encodeURIComponent(
39
- joinAtAfter,
40
- )}&join_at_before=${encodeURIComponent(joinAtBefore)}`;
41
-
42
- for (
43
- let pageIndex = 0;
44
- !isUndefined(path) && pageIndex < RECALL_BOT_LIST_MAX_PAGES;
45
- pageIndex++
46
- ) {
47
- const result = await recallBotApiRequest<RecallBotListResponse>({
48
- config: configResult.config,
49
- path,
50
- method: 'GET',
51
- });
52
-
53
- if (!result.ok) {
54
- return result;
55
- }
56
-
57
- bots.push(...extractRecallBots(result.data));
58
- path = extractNextPath(result.data, configResult.config.baseUrl);
59
- }
60
-
61
- if (!isUndefined(path)) {
62
- return {
63
- ok: false,
64
- status: null,
65
- errorMessage: `Recall bot list exceeded ${RECALL_BOT_LIST_MAX_PAGES} pages`,
66
- };
67
- }
68
-
69
- return { ok: true, bots };
70
- };
71
-
72
- const extractRecallBots = (
73
- response: RecallBotListResponse | undefined,
74
- ): RecallScheduledBot[] => {
75
- if (!Array.isArray(response?.results)) {
76
- return [];
77
- }
78
-
79
- return response.results.flatMap((candidate: unknown) => {
80
- const bot = asRecord(candidate);
81
-
82
- if (isUndefined(bot) || !isString(bot.id)) {
83
- return [];
84
- }
85
-
86
- return [
87
- {
88
- id: bot.id,
89
- metadata: asRecord(bot.metadata) ?? {},
90
- },
91
- ];
92
- });
93
- };
94
-
95
- const extractNextPath = (
96
- response: RecallBotListResponse | undefined,
97
- baseUrl: string,
98
- ): string | undefined => {
99
- const next = response?.next;
100
-
101
- if (!isString(next) || !next.startsWith(baseUrl)) {
102
- return undefined;
103
- }
104
-
105
- return next.slice(baseUrl.length);
106
- };
@@ -1,14 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- // Twenty rejects Recall's microsecond precision; truncate to millisecond ISO.
4
- export const normalizeRecallTimestamp = (
5
- value: string | undefined,
6
- ): string | undefined => {
7
- if (isUndefined(value)) {
8
- return undefined;
9
- }
10
-
11
- const parsed = new Date(value);
12
-
13
- return Number.isNaN(parsed.getTime()) ? undefined : parsed.toISOString();
14
- };
@@ -1,88 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { getRecallWebhookBotMetadata } from 'src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util';
4
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
5
- import { getRecordAtPath } from 'src/logic-functions/utils/get-record-at-path.util';
6
- import { getString } from 'src/logic-functions/utils/get-string.util';
7
- import { normalizeRecallTimestamp } from 'src/logic-functions/recall-api/normalize-recall-timestamp.util';
8
-
9
- export type RecallWebhookBody = {
10
- event?: unknown;
11
- type?: unknown;
12
- data?: unknown;
13
- bot?: unknown;
14
- };
15
-
16
- export type RecallWebhookEvent = {
17
- event: string;
18
- statusCode: string | undefined;
19
- statusTimestamp: string | undefined;
20
- externalBotId: string | undefined;
21
- externalRecordingId: string | undefined;
22
- callRecordingIdFromMetadata: string | undefined;
23
- recordingStartedAt: string | undefined;
24
- recordingEndedAt: string | undefined;
25
- transcriptId: string | undefined;
26
- transcriptFailureSubCode: string | undefined;
27
- };
28
-
29
- // The only reader of raw webhook payloads; Recall delivers several body shapes per event family.
30
- export const parseRecallWebhookEvent = (
31
- body: RecallWebhookBody,
32
- ): RecallWebhookEvent | undefined => {
33
- const event = getString(body.event) ?? getString(body.type);
34
-
35
- if (isUndefined(event)) {
36
- return undefined;
37
- }
38
-
39
- const data = asRecord(body.data);
40
- const bot = asRecord(body.bot);
41
-
42
- return {
43
- event,
44
- statusCode:
45
- getString(getRecordAtPath(data, ['status', 'code'])) ??
46
- getString(getRecordAtPath(data, ['data', 'code'])) ??
47
- getString(getRecordAtPath(bot, ['status', 'code'])) ??
48
- getStatusCodeFromEventName(event),
49
- statusTimestamp: normalizeRecallTimestamp(
50
- getString(getRecordAtPath(data, ['status', 'created_at'])) ??
51
- getString(getRecordAtPath(data, ['data', 'updated_at'])) ??
52
- getString(getRecordAtPath(bot, ['status', 'created_at'])),
53
- ),
54
- externalBotId:
55
- getString(data?.bot_id) ??
56
- getString(getRecordAtPath(data, ['bot', 'id'])) ??
57
- getString(getRecordAtPath(data, ['recording', 'bot_id'])) ??
58
- getString(getRecordAtPath(data, ['recording', 'bot', 'id'])) ??
59
- getString(bot?.id),
60
- externalRecordingId:
61
- getString(getRecordAtPath(data, ['status', 'recording_id'])) ??
62
- getString(getRecordAtPath(data, ['recording', 'id'])) ??
63
- getString(data?.recording_id),
64
- callRecordingIdFromMetadata: getString(
65
- getRecallWebhookBotMetadata(body)?.twentyCallRecordingId,
66
- ),
67
- recordingStartedAt: normalizeRecallTimestamp(
68
- getString(getRecordAtPath(data, ['recording', 'started_at'])),
69
- ),
70
- recordingEndedAt: normalizeRecallTimestamp(
71
- getString(getRecordAtPath(data, ['recording', 'completed_at'])),
72
- ),
73
- transcriptId: getString(getRecordAtPath(data, ['transcript', 'id'])),
74
- transcriptFailureSubCode: getString(
75
- getRecordAtPath(data, ['status', 'sub_code']),
76
- ),
77
- };
78
- };
79
-
80
- const getStatusCodeFromEventName = (event: string): string | undefined => {
81
- if (!event.startsWith('bot.')) {
82
- return undefined;
83
- }
84
-
85
- const statusCode = event.slice('bot.'.length);
86
-
87
- return statusCode === 'status_change' ? undefined : statusCode;
88
- };
@@ -1,165 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { RECALL_API_MAX_ATTEMPTS } from 'src/logic-functions/constants/recall-api-max-attempts';
4
- import { RECALL_API_RETRY_DELAY_MS } from 'src/logic-functions/constants/recall-api-retry-delay-ms';
5
- import { type RecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
6
-
7
- type RecallBotApiRequestArgs = {
8
- config: RecallApiConfig;
9
- path: string;
10
- method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
11
- body?: unknown;
12
- allowNotFound?: boolean;
13
- maxAttempts?: number;
14
- };
15
-
16
- type RecallBotApiRequestResult<TData> =
17
- | {
18
- ok: true;
19
- status: number;
20
- data: TData;
21
- }
22
- | {
23
- ok: false;
24
- status: number | null;
25
- errorMessage: string;
26
- };
27
-
28
- // Bot creates tolerate retries because duplicates stay unclaimed and get reaped.
29
- // Callers that cannot retry idempotently can lower maxAttempts.
30
- export const recallBotApiRequest = async <TData>(
31
- requestArgs: RecallBotApiRequestArgs,
32
- ): Promise<RecallBotApiRequestResult<TData>> => {
33
- const maxAttempts = requestArgs.maxAttempts ?? RECALL_API_MAX_ATTEMPTS;
34
-
35
- for (let attemptNumber = 1; ; attemptNumber++) {
36
- const { result, isRetryable } =
37
- await performRecallBotApiRequestAttempt<TData>(requestArgs);
38
-
39
- if (!isRetryable || attemptNumber >= maxAttempts) {
40
- return result;
41
- }
42
-
43
- await sleep(RECALL_API_RETRY_DELAY_MS * attemptNumber);
44
- }
45
- };
46
-
47
- const performRecallBotApiRequestAttempt = async <TData>({
48
- config,
49
- path,
50
- method,
51
- body,
52
- allowNotFound = false,
53
- }: RecallBotApiRequestArgs): Promise<{
54
- result: RecallBotApiRequestResult<TData>;
55
- isRetryable: boolean;
56
- }> => {
57
- let response: Response;
58
-
59
- try {
60
- response = await fetch(`${config.baseUrl}${path}`, {
61
- method,
62
- headers: {
63
- Authorization: buildRecallApiAuthorizationHeader(config.apiKey),
64
- ...(isUndefined(body) ? {} : { 'Content-Type': 'application/json' }),
65
- },
66
- ...(isUndefined(body) ? {} : { body: JSON.stringify(body) }),
67
- });
68
- } catch (error) {
69
- return {
70
- isRetryable: true,
71
- result: {
72
- ok: false,
73
- status: null,
74
- errorMessage: `Recall API request failed: ${
75
- error instanceof Error ? error.message : String(error)
76
- }`,
77
- },
78
- };
79
- }
80
-
81
- if (allowNotFound && response.status === 404) {
82
- return {
83
- isRetryable: false,
84
- result: {
85
- ok: true,
86
- status: response.status,
87
- data: undefined as TData,
88
- },
89
- };
90
- }
91
-
92
- if (response.status === 204) {
93
- return {
94
- isRetryable: false,
95
- result: {
96
- ok: true,
97
- status: response.status,
98
- data: undefined as TData,
99
- },
100
- };
101
- }
102
-
103
- if (!response.ok) {
104
- return {
105
- isRetryable: isRetryableRecallApiStatus(response.status),
106
- result: {
107
- ok: false,
108
- status: response.status,
109
- errorMessage: await extractRecallApiErrorMessage(response),
110
- },
111
- };
112
- }
113
-
114
- try {
115
- return {
116
- isRetryable: false,
117
- result: {
118
- ok: true,
119
- status: response.status,
120
- data: (await response.json()) as TData,
121
- },
122
- };
123
- } catch (error) {
124
- return {
125
- isRetryable: false,
126
- result: {
127
- ok: false,
128
- status: response.status,
129
- errorMessage: `Recall API returned a non-JSON response: ${
130
- error instanceof Error ? error.message : String(error)
131
- }`,
132
- },
133
- };
134
- }
135
- };
136
-
137
- const isRetryableRecallApiStatus = (status: number): boolean =>
138
- status === 429 || status >= 500;
139
-
140
- const sleep = (delayMs: number): Promise<void> =>
141
- new Promise((resolve) => {
142
- setTimeout(resolve, delayMs);
143
- });
144
-
145
- const buildRecallApiAuthorizationHeader = (apiKey: string): string => {
146
- const trimmedApiKey = apiKey.trim();
147
-
148
- return trimmedApiKey.toLowerCase().startsWith('token ')
149
- ? trimmedApiKey
150
- : `Token ${trimmedApiKey}`;
151
- };
152
-
153
- const extractRecallApiErrorMessage = async (
154
- response: Response,
155
- ): Promise<string> => {
156
- const fallback = `Recall API responded with HTTP ${response.status}`;
157
-
158
- try {
159
- const body = (await response.json()) as unknown;
160
-
161
- return `${fallback}: ${JSON.stringify(body)}`;
162
- } catch {
163
- return fallback;
164
- }
165
- };
@@ -1,5 +0,0 @@
1
- export type RecallTranscriptSummary = {
2
- id: string;
3
- statusCode: string | undefined;
4
- statusSubCode: string | undefined;
5
- };
@@ -1,56 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { getRecallBotAutomaticLeave } from 'src/logic-functions/constants/recall-bot-automatic-leave';
4
- import { getRecallBotRecordingConfig } from 'src/logic-functions/constants/recall-bot-recording-config';
5
- import { type RecallBotScheduleResult } from 'src/logic-functions/types/recall-bot-operation-result.type';
6
- import {
7
- extractRecallBotId,
8
- type RecallBotResponse,
9
- } from 'src/logic-functions/recall-api/extract-recall-bot-id.util';
10
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
11
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
12
- import { type ScheduleRecallBotArgs } from 'src/logic-functions/recall-api/schedule-recall-bot.util';
13
-
14
- type RescheduleRecallBotArgs = ScheduleRecallBotArgs & {
15
- externalBotId: string;
16
- };
17
-
18
- export const rescheduleRecallBot = async ({
19
- externalBotId,
20
- meetingUrl,
21
- joinAt,
22
- metadata,
23
- }: RescheduleRecallBotArgs): Promise<RecallBotScheduleResult> => {
24
- const configResult = getRecallApiConfig();
25
-
26
- if (!configResult.success) {
27
- return { ok: false, status: null, errorMessage: configResult.error };
28
- }
29
-
30
- const automaticLeave = getRecallBotAutomaticLeave();
31
-
32
- const result = await recallBotApiRequest<RecallBotResponse>({
33
- config: configResult.config,
34
- path: `/bot/${externalBotId}/`,
35
- method: 'PATCH',
36
- body: {
37
- meeting_url: meetingUrl,
38
- join_at: joinAt,
39
- bot_name: configResult.config.botName,
40
- ...(isUndefined(automaticLeave)
41
- ? {}
42
- : { automatic_leave: automaticLeave }),
43
- recording_config: getRecallBotRecordingConfig(),
44
- metadata,
45
- },
46
- });
47
-
48
- if (!result.ok) {
49
- return result;
50
- }
51
-
52
- return {
53
- ok: true,
54
- externalBotId: extractRecallBotId(result.data) ?? externalBotId,
55
- };
56
- };
@@ -1,71 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { type RecallBotOperationFailure } from 'src/logic-functions/types/recall-bot-operation-result.type';
4
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
5
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
6
- import { getString } from 'src/logic-functions/utils/get-string.util';
7
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
8
-
9
- export type RecallTranscriptDetails = {
10
- downloadUrl: string | undefined;
11
- statusCode: string | undefined;
12
- statusSubCode: string | undefined;
13
- };
14
-
15
- type RetrieveRecallTranscriptResult =
16
- | { ok: true; transcript: RecallTranscriptDetails }
17
- | RecallBotOperationFailure;
18
-
19
- export const retrieveRecallTranscript = async ({
20
- transcriptId,
21
- }: {
22
- transcriptId: string;
23
- }): Promise<RetrieveRecallTranscriptResult> => {
24
- const configResult = getRecallApiConfig();
25
-
26
- if (!configResult.success) {
27
- return { ok: false, status: null, errorMessage: configResult.error };
28
- }
29
-
30
- const result = await recallBotApiRequest<Record<string, unknown>>({
31
- config: configResult.config,
32
- path: `/transcript/${transcriptId}/`,
33
- method: 'GET',
34
- });
35
-
36
- if (!result.ok) {
37
- return result;
38
- }
39
-
40
- const transcript = extractRecallTranscriptDetails(result.data);
41
-
42
- if (isMalformedRecallTranscriptDetails(transcript)) {
43
- return {
44
- ok: false,
45
- status: result.status,
46
- errorMessage: 'Recall API returned malformed transcript details',
47
- };
48
- }
49
-
50
- return { ok: true, transcript };
51
- };
52
-
53
- const extractRecallTranscriptDetails = (
54
- response: Record<string, unknown> | undefined,
55
- ): RecallTranscriptDetails => {
56
- const data = asRecord(response?.data);
57
- const status = asRecord(response?.status);
58
-
59
- return {
60
- downloadUrl: getString(data?.download_url),
61
- statusCode: getString(status?.code),
62
- statusSubCode: getString(status?.sub_code),
63
- };
64
- };
65
-
66
- const isMalformedRecallTranscriptDetails = ({
67
- downloadUrl,
68
- statusCode,
69
- }: RecallTranscriptDetails): boolean =>
70
- (isUndefined(downloadUrl) && isUndefined(statusCode)) ||
71
- (isUndefined(downloadUrl) && statusCode === 'done');