@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,122 +0,0 @@
1
- import { createHmac } from 'crypto';
2
-
3
- import { describe, expect, it } from 'vitest';
4
-
5
- import { verifyRecallWebhookSignature } from 'src/logic-functions/recall-api/verify-recall-webhook-signature.util';
6
-
7
- const SECRET_BYTES = Buffer.from('test-secret-abc123');
8
- const SECRET = `whsec_${SECRET_BYTES.toString('base64')}`;
9
- const WEBHOOK_ID = 'msg_123';
10
- const WEBHOOK_TIMESTAMP = '1760000000';
11
- const NOW = new Date(Number(WEBHOOK_TIMESTAMP) * 1000);
12
-
13
- const sign = (body: string): string =>
14
- createHmac('sha256', SECRET_BYTES)
15
- .update(`${WEBHOOK_ID}.${WEBHOOK_TIMESTAMP}.${body}`)
16
- .digest('base64');
17
-
18
- describe('verifyRecallWebhookSignature', () => {
19
- it('accepts valid Recall webhook-* signature headers', () => {
20
- const body = JSON.stringify({ event: 'recording.done' });
21
-
22
- const result = verifyRecallWebhookSignature({
23
- rawBody: body,
24
- secret: SECRET,
25
- now: NOW,
26
- headers: {
27
- 'webhook-id': WEBHOOK_ID,
28
- 'webhook-timestamp': WEBHOOK_TIMESTAMP,
29
- 'webhook-signature': `v1,${sign(body)}`,
30
- },
31
- });
32
-
33
- expect(result).toEqual({ valid: true });
34
- });
35
-
36
- it('accepts valid svix-* signature headers', () => {
37
- const body = JSON.stringify({ event: 'recording.done' });
38
-
39
- const result = verifyRecallWebhookSignature({
40
- rawBody: body,
41
- secret: SECRET,
42
- now: NOW,
43
- headers: {
44
- 'svix-id': WEBHOOK_ID,
45
- 'svix-timestamp': WEBHOOK_TIMESTAMP,
46
- 'svix-signature': `v1,${sign(body)}`,
47
- },
48
- });
49
-
50
- expect(result).toEqual({ valid: true });
51
- });
52
-
53
- it('rejects deliveries whose timestamp is outside of the tolerance', () => {
54
- const body = JSON.stringify({ event: 'recording.done' });
55
-
56
- const result = verifyRecallWebhookSignature({
57
- rawBody: body,
58
- secret: SECRET,
59
- now: new Date(Number(WEBHOOK_TIMESTAMP) * 1000 + 6 * 60 * 1000),
60
- headers: {
61
- 'webhook-id': WEBHOOK_ID,
62
- 'webhook-timestamp': WEBHOOK_TIMESTAMP,
63
- 'webhook-signature': `v1,${sign(body)}`,
64
- },
65
- });
66
-
67
- expect(result).toEqual({
68
- valid: false,
69
- error: 'Webhook timestamp is outside of the allowed tolerance',
70
- });
71
- });
72
-
73
- it('rejects non-numeric timestamps', () => {
74
- const body = JSON.stringify({ event: 'recording.done' });
75
-
76
- const result = verifyRecallWebhookSignature({
77
- rawBody: body,
78
- secret: SECRET,
79
- now: NOW,
80
- headers: {
81
- 'webhook-id': WEBHOOK_ID,
82
- 'webhook-timestamp': 'not-a-timestamp',
83
- 'webhook-signature': `v1,${sign(body)}`,
84
- },
85
- });
86
-
87
- expect(result).toEqual({
88
- valid: false,
89
- error: 'Invalid webhook timestamp',
90
- });
91
- });
92
-
93
- it('rejects missing signature headers', () => {
94
- const result = verifyRecallWebhookSignature({
95
- rawBody: '{}',
96
- secret: SECRET,
97
- headers: {},
98
- });
99
-
100
- expect(result).toEqual({
101
- valid: false,
102
- error: 'Missing webhook signature headers',
103
- });
104
- });
105
-
106
- it('rejects signatures computed from a different body', () => {
107
- const body = JSON.stringify({ event: 'recording.done' });
108
-
109
- const result = verifyRecallWebhookSignature({
110
- rawBody: JSON.stringify({ event: 'recording.failed' }),
111
- secret: SECRET,
112
- now: NOW,
113
- headers: {
114
- 'webhook-id': WEBHOOK_ID,
115
- 'webhook-timestamp': WEBHOOK_TIMESTAMP,
116
- 'webhook-signature': `v1,${sign(body)}`,
117
- },
118
- });
119
-
120
- expect(result.valid).toBe(false);
121
- });
122
- });
@@ -1,28 +0,0 @@
1
- import { type RecallBotRemovalResult } from 'src/logic-functions/types/recall-bot-operation-result.type';
2
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
3
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
4
-
5
- export const cancelRecallBot = async ({
6
- externalBotId,
7
- }: {
8
- externalBotId: string;
9
- }): Promise<RecallBotRemovalResult> => {
10
- const configResult = getRecallApiConfig();
11
-
12
- if (!configResult.success) {
13
- return { ok: false, status: null, errorMessage: configResult.error };
14
- }
15
-
16
- const result = await recallBotApiRequest<undefined>({
17
- config: configResult.config,
18
- path: `/bot/${externalBotId}/`,
19
- method: 'DELETE',
20
- allowNotFound: true,
21
- });
22
-
23
- if (!result.ok) {
24
- return result;
25
- }
26
-
27
- return { ok: true };
28
- };
@@ -1,47 +0,0 @@
1
- import { isString } from '@sniptt/guards';
2
-
3
- import { type RecallBotOperationFailure } from 'src/logic-functions/types/recall-bot-operation-result.type';
4
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
5
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
6
-
7
- type CreateAsyncRecallTranscriptResult =
8
- | { ok: true; transcriptId: string }
9
- | RecallBotOperationFailure;
10
-
11
- export const createAsyncRecallTranscript = async ({
12
- externalRecordingId,
13
- }: {
14
- externalRecordingId: string;
15
- }): Promise<CreateAsyncRecallTranscriptResult> => {
16
- const configResult = getRecallApiConfig();
17
-
18
- if (!configResult.success) {
19
- return { ok: false, status: null, errorMessage: configResult.error };
20
- }
21
-
22
- const result = await recallBotApiRequest<{ id?: unknown }>({
23
- config: configResult.config,
24
- path: `/recording/${externalRecordingId}/create_transcript/`,
25
- method: 'POST',
26
- body: {
27
- provider: { recallai_async: { language_code: 'auto' } },
28
- diarization: { use_separate_streams_when_available: true },
29
- },
30
- maxAttempts: 1,
31
- });
32
-
33
- if (!result.ok) {
34
- return result;
35
- }
36
-
37
- if (!isString(result.data?.id)) {
38
- return {
39
- ok: false,
40
- status: null,
41
- errorMessage:
42
- 'Recall API created a transcript but the response did not include a transcript id',
43
- };
44
- }
45
-
46
- return { ok: true, transcriptId: result.data.id };
47
- };
@@ -1,28 +0,0 @@
1
- import { type RecallBotRemovalResult } from 'src/logic-functions/types/recall-bot-operation-result.type';
2
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
3
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
4
-
5
- export const ejectRecallBot = async ({
6
- externalBotId,
7
- }: {
8
- externalBotId: string;
9
- }): Promise<RecallBotRemovalResult> => {
10
- const configResult = getRecallApiConfig();
11
-
12
- if (!configResult.success) {
13
- return { ok: false, status: null, errorMessage: configResult.error };
14
- }
15
-
16
- const result = await recallBotApiRequest<undefined>({
17
- config: configResult.config,
18
- path: `/bot/${externalBotId}/leave_call/`,
19
- method: 'POST',
20
- allowNotFound: true,
21
- });
22
-
23
- if (!result.ok) {
24
- return result;
25
- }
26
-
27
- return { ok: true };
28
- };
@@ -1,149 +0,0 @@
1
- import { isArray, isUndefined } from '@sniptt/guards';
2
-
3
- import { CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status';
4
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
5
- import { getString } from 'src/logic-functions/utils/get-string.util';
6
- import { mapRecallStatusCodeToCallRecordingStatus } from 'src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util';
7
- import { normalizeRecallTimestamp } from 'src/logic-functions/recall-api/normalize-recall-timestamp.util';
8
-
9
- export type RecallBotConvergence = {
10
- status: CallRecordingStatus | undefined;
11
- failureReason: string | undefined;
12
- startedAt: string | undefined;
13
- endedAt: string | undefined;
14
- externalRecordingId: string | undefined;
15
- isRecallRecordingDone: boolean;
16
- };
17
-
18
- type RecallBotStatusChange = {
19
- code: string;
20
- createdAt: string | undefined;
21
- };
22
-
23
- // Derives the state a full webhook history would have produced from GET /bot.
24
- export const extractRecallBotConvergence = (
25
- bot: Record<string, unknown>,
26
- ): RecallBotConvergence => {
27
- const statusChanges = extractStatusChanges(bot);
28
- const latestStatusChange = getLatestStatusChange(statusChanges);
29
- const status = mapRecallStatusCodeToCallRecordingStatus(
30
- latestStatusChange?.code,
31
- );
32
- const recording = extractFirstRecording(bot);
33
-
34
- return {
35
- status,
36
- failureReason:
37
- status === CallRecordingStatus.FAILED
38
- ? latestStatusChange?.code
39
- : undefined,
40
- startedAt: normalizeRecallTimestamp(
41
- recording?.startedAt ??
42
- findStatusChangeTimestamp(statusChanges, 'in_call_recording'),
43
- ),
44
- endedAt: normalizeRecallTimestamp(
45
- recording?.completedAt ??
46
- findStatusChangeTimestamp(statusChanges, 'call_ended'),
47
- ),
48
- externalRecordingId: recording?.id,
49
- isRecallRecordingDone:
50
- !isUndefined(recording?.completedAt) ||
51
- statusChanges.some((statusChange) => statusChange.code === 'done'),
52
- };
53
- };
54
-
55
- const extractStatusChanges = (
56
- bot: Record<string, unknown>,
57
- ): RecallBotStatusChange[] => {
58
- if (!isArray(bot.status_changes)) {
59
- return [];
60
- }
61
-
62
- return bot.status_changes.flatMap((statusChange: unknown) => {
63
- const code = getString(asRecord(statusChange)?.code);
64
-
65
- if (isUndefined(code)) {
66
- return [];
67
- }
68
-
69
- return [{ code, createdAt: getString(asRecord(statusChange)?.created_at) }];
70
- });
71
- };
72
-
73
- const getLatestStatusChange = (
74
- statusChanges: RecallBotStatusChange[],
75
- ): RecallBotStatusChange | undefined =>
76
- statusChanges.reduce<RecallBotStatusChange | undefined>(
77
- (latestStatusChange, statusChange) => {
78
- if (isUndefined(latestStatusChange)) {
79
- return statusChange;
80
- }
81
-
82
- const statusChangeTime = getStatusChangeTime(statusChange);
83
- const latestStatusChangeTime = getStatusChangeTime(latestStatusChange);
84
-
85
- if (
86
- isUndefined(statusChangeTime) &&
87
- isUndefined(latestStatusChangeTime)
88
- ) {
89
- return statusChange;
90
- }
91
-
92
- if (isUndefined(statusChangeTime)) {
93
- return latestStatusChange;
94
- }
95
-
96
- if (isUndefined(latestStatusChangeTime)) {
97
- return statusChange;
98
- }
99
-
100
- return statusChangeTime >= latestStatusChangeTime
101
- ? statusChange
102
- : latestStatusChange;
103
- },
104
- undefined,
105
- );
106
-
107
- const getStatusChangeTime = (
108
- statusChange: RecallBotStatusChange,
109
- ): number | undefined => {
110
- const normalizedTimestamp = normalizeRecallTimestamp(statusChange.createdAt);
111
-
112
- if (isUndefined(normalizedTimestamp)) {
113
- return undefined;
114
- }
115
-
116
- return new Date(normalizedTimestamp).getTime();
117
- };
118
-
119
- const extractFirstRecording = (
120
- bot: Record<string, unknown>,
121
- ):
122
- | {
123
- id: string | undefined;
124
- startedAt: string | undefined;
125
- completedAt: string | undefined;
126
- }
127
- | undefined => {
128
- if (!isArray(bot.recordings)) {
129
- return undefined;
130
- }
131
-
132
- const recording = asRecord(bot.recordings[0]);
133
-
134
- if (isUndefined(recording)) {
135
- return undefined;
136
- }
137
-
138
- return {
139
- id: getString(recording.id),
140
- startedAt: getString(recording.started_at),
141
- completedAt: getString(recording.completed_at),
142
- };
143
- };
144
-
145
- const findStatusChangeTimestamp = (
146
- statusChanges: RecallBotStatusChange[],
147
- code: string,
148
- ): string | undefined =>
149
- statusChanges.find((statusChange) => statusChange.code === code)?.createdAt;
@@ -1,10 +0,0 @@
1
- import { getString } from 'src/logic-functions/utils/get-string.util';
2
-
3
- export type RecallBotResponse = {
4
- id?: unknown;
5
- bot_id?: unknown;
6
- };
7
-
8
- export const extractRecallBotId = (
9
- response: RecallBotResponse | undefined,
10
- ): string | undefined => getString(response?.id) ?? getString(response?.bot_id);
@@ -1,30 +0,0 @@
1
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
2
- import { getRecordAtPath } from 'src/logic-functions/utils/get-record-at-path.util';
3
- import { getString } from 'src/logic-functions/utils/get-string.util';
4
-
5
- export type RecallMediaUrls = {
6
- videoUrl: string | undefined;
7
- audioUrl: string | undefined;
8
- };
9
-
10
- // Pre-signed URLs expire within hours; always re-extract from a fresh GET /recording.
11
- export const extractRecallMediaUrls = (
12
- recording: Record<string, unknown>,
13
- ): RecallMediaUrls => {
14
- const mediaShortcuts = asRecord(recording.media_shortcuts);
15
-
16
- return {
17
- videoUrl: extractArtifactDownloadUrl(mediaShortcuts, 'video_mixed'),
18
- audioUrl: extractArtifactDownloadUrl(mediaShortcuts, 'audio_mixed'),
19
- };
20
- };
21
-
22
- // v1.11 exposes download_url flat on the artifact; older artifacts nest it under data.
23
- const extractArtifactDownloadUrl = (
24
- mediaShortcuts: Record<string, unknown> | undefined,
25
- artifactKey: string,
26
- ): string | undefined =>
27
- getString(getRecordAtPath(mediaShortcuts, [artifactKey, 'download_url'])) ??
28
- getString(
29
- getRecordAtPath(mediaShortcuts, [artifactKey, 'data', 'download_url']),
30
- );
@@ -1,8 +0,0 @@
1
- import { getRecallWebhookBotMetadata } from 'src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util';
2
- import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util';
3
- import { getString } from 'src/logic-functions/utils/get-string.util';
4
-
5
- export const extractTwentyWorkspaceIdFromRecallWebhook = (
6
- body: RecallWebhookBody,
7
- ): string | undefined =>
8
- getString(getRecallWebhookBotMetadata(body)?.twentyWorkspaceId);
@@ -1,59 +0,0 @@
1
- import { isUndefined } from '@sniptt/guards';
2
-
3
- import { CALL_RECORDER_NAME_ENV_VAR_NAME } from 'src/logic-functions/constants/call-recorder-name-env-var-name';
4
- import { DEFAULT_CALL_RECORDER_NAME } from 'src/logic-functions/constants/default-call-recorder-name';
5
- import { DEFAULT_RECALL_REGION } from 'src/logic-functions/constants/default-recall-region';
6
- import { RECALL_API_KEY_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-api-key-env-var-name';
7
- import { RECALL_REGION_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-region-env-var-name';
8
- import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util';
9
- import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
10
-
11
- export type RecallApiConfig = {
12
- apiKey: string;
13
- baseUrl: string;
14
- botName: string;
15
- };
16
-
17
- export const getRecallApiConfig = ():
18
- | {
19
- success: true;
20
- config: RecallApiConfig;
21
- }
22
- | {
23
- success: false;
24
- error: string;
25
- } => {
26
- const apiKey = normalizeOptionalString(
27
- getApplicationVariableValue(RECALL_API_KEY_ENV_VAR_NAME),
28
- );
29
-
30
- if (isUndefined(apiKey)) {
31
- return {
32
- success: false,
33
- error:
34
- 'RECALL_API_KEY server variable is not set. A server admin must set it on the Call Recorder application registration before scheduling bots.',
35
- };
36
- }
37
-
38
- const region =
39
- normalizeOptionalString(
40
- getApplicationVariableValue(RECALL_REGION_ENV_VAR_NAME),
41
- ) ?? DEFAULT_RECALL_REGION;
42
- const botName =
43
- normalizeOptionalString(
44
- getApplicationVariableValue(CALL_RECORDER_NAME_ENV_VAR_NAME),
45
- ) ?? DEFAULT_CALL_RECORDER_NAME;
46
-
47
- return {
48
- success: true,
49
- config: {
50
- apiKey,
51
- baseUrl: `https://${region}.recall.ai/api/v1`,
52
- botName,
53
- },
54
- };
55
- };
56
-
57
- const normalizeOptionalString = (
58
- value: string | undefined,
59
- ): string | undefined => (isNonEmptyString(value) ? value.trim() : undefined);
@@ -1,42 +0,0 @@
1
- import { type RecallBotOperationFailure } from 'src/logic-functions/types/recall-bot-operation-result.type';
2
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
3
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
4
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
5
-
6
- type GetRecallBotResult =
7
- | { ok: true; bot: Record<string, unknown> }
8
- | RecallBotOperationFailure;
9
-
10
- export const getRecallBot = async ({
11
- externalBotId,
12
- }: {
13
- externalBotId: string;
14
- }): Promise<GetRecallBotResult> => {
15
- const configResult = getRecallApiConfig();
16
-
17
- if (!configResult.success) {
18
- return { ok: false, status: null, errorMessage: configResult.error };
19
- }
20
-
21
- const result = await recallBotApiRequest<Record<string, unknown>>({
22
- config: configResult.config,
23
- path: `/bot/${externalBotId}/`,
24
- method: 'GET',
25
- });
26
-
27
- if (!result.ok) {
28
- return result;
29
- }
30
-
31
- const bot = asRecord(result.data);
32
-
33
- if (bot === undefined) {
34
- return {
35
- ok: false,
36
- status: result.status,
37
- errorMessage: 'Recall API returned an empty bot response',
38
- };
39
- }
40
-
41
- return { ok: true, bot };
42
- };
@@ -1,31 +0,0 @@
1
- import { type RecallBotOperationFailure } from 'src/logic-functions/types/recall-bot-operation-result.type';
2
- import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
3
- import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
4
-
5
- type GetRecallRecordingResult =
6
- | { ok: true; recording: Record<string, unknown> }
7
- | RecallBotOperationFailure;
8
-
9
- export const getRecallRecording = async ({
10
- externalRecordingId,
11
- }: {
12
- externalRecordingId: string;
13
- }): Promise<GetRecallRecordingResult> => {
14
- const configResult = getRecallApiConfig();
15
-
16
- if (!configResult.success) {
17
- return { ok: false, status: null, errorMessage: configResult.error };
18
- }
19
-
20
- const result = await recallBotApiRequest<Record<string, unknown>>({
21
- config: configResult.config,
22
- path: `/recording/${externalRecordingId}/`,
23
- method: 'GET',
24
- });
25
-
26
- if (!result.ok) {
27
- return result;
28
- }
29
-
30
- return { ok: true, recording: result.data ?? {} };
31
- };
@@ -1,18 +0,0 @@
1
- import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util';
2
- import { asRecord } from 'src/logic-functions/utils/as-record.util';
3
- import { getRecordAtPath } from 'src/logic-functions/utils/get-record-at-path.util';
4
-
5
- // Recall delivers bot metadata under several body shapes per event family; this is the single reader of all of them.
6
- export const getRecallWebhookBotMetadata = (
7
- body: RecallWebhookBody,
8
- ): Record<string, unknown> | undefined => {
9
- const data = asRecord(body.data);
10
- const bot = asRecord(body.bot);
11
-
12
- return (
13
- asRecord(bot?.metadata) ??
14
- asRecord(getRecordAtPath(data, ['bot', 'metadata'])) ??
15
- asRecord(getRecordAtPath(data, ['recording', 'metadata'])) ??
16
- asRecord(data?.metadata)
17
- );
18
- };