@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
@@ -0,0 +1,1602 @@
1
+ import { createRequire as __createRequire } from 'module';
2
+ const require = __createRequire(import.meta.url);
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e) {
13
+ throw mod = 0, e;
14
+ }
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // node_modules/@sniptt/guards/build/guards/primitives.js
34
+ var require_primitives = __commonJS({
35
+ "node_modules/@sniptt/guards/build/guards/primitives.js"(exports) {
36
+ "use strict";
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.isSymbol = exports.isBigInt = exports.isString = exports.isNumber = exports.isBoolean = exports.isUndefined = void 0;
39
+ var isUndefined17 = (term) => {
40
+ return typeof term === "undefined";
41
+ };
42
+ exports.isUndefined = isUndefined17;
43
+ var isBoolean = (term) => {
44
+ return typeof term === "boolean";
45
+ };
46
+ exports.isBoolean = isBoolean;
47
+ var isNumber = (term) => {
48
+ return typeof term === "number" && !Number.isNaN(term);
49
+ };
50
+ exports.isNumber = isNumber;
51
+ var isString5 = (term) => {
52
+ return typeof term === "string";
53
+ };
54
+ exports.isString = isString5;
55
+ var isBigInt = (term) => {
56
+ return typeof term === "bigint";
57
+ };
58
+ exports.isBigInt = isBigInt;
59
+ var isSymbol = (term) => {
60
+ return typeof term === "symbol";
61
+ };
62
+ exports.isSymbol = isSymbol;
63
+ }
64
+ });
65
+
66
+ // node_modules/@sniptt/guards/build/guards/structural.js
67
+ var require_structural = __commonJS({
68
+ "node_modules/@sniptt/guards/build/guards/structural.js"(exports) {
69
+ "use strict";
70
+ Object.defineProperty(exports, "__esModule", { value: true });
71
+ exports.isDate = exports.isWeakSet = exports.isWeakMap = exports.isSet = exports.isMap = exports.isArray = exports.isObject = exports.isFunction = exports.isNull = void 0;
72
+ var isNull = (term) => {
73
+ return term === null;
74
+ };
75
+ exports.isNull = isNull;
76
+ var isFunction = (term) => {
77
+ return typeof term === "function";
78
+ };
79
+ exports.isFunction = isFunction;
80
+ var isObject2 = (term) => {
81
+ return !exports.isNull(term) && typeof term === "object";
82
+ };
83
+ exports.isObject = isObject2;
84
+ var isArray2 = (term) => {
85
+ return Array.isArray(term);
86
+ };
87
+ exports.isArray = isArray2;
88
+ var isMap = (term) => {
89
+ return term instanceof Map;
90
+ };
91
+ exports.isMap = isMap;
92
+ var isSet = (term) => {
93
+ return term instanceof Set;
94
+ };
95
+ exports.isSet = isSet;
96
+ var isWeakMap = (term) => {
97
+ return term instanceof WeakMap;
98
+ };
99
+ exports.isWeakMap = isWeakMap;
100
+ var isWeakSet = (term) => {
101
+ return term instanceof WeakSet;
102
+ };
103
+ exports.isWeakSet = isWeakSet;
104
+ var isDate = (term) => {
105
+ return term instanceof Date;
106
+ };
107
+ exports.isDate = isDate;
108
+ }
109
+ });
110
+
111
+ // node_modules/@sniptt/guards/build/guards/convenience.js
112
+ var require_convenience = __commonJS({
113
+ "node_modules/@sniptt/guards/build/guards/convenience.js"(exports) {
114
+ "use strict";
115
+ Object.defineProperty(exports, "__esModule", { value: true });
116
+ exports.isNegativeInteger = exports.isNonNegativeInteger = exports.isPositiveInteger = exports.isInteger = exports.isNumberOrNaN = exports.isNonEmptyString = exports.isNonEmptyArray = exports.isObjectOrNull = void 0;
117
+ var primitives_1 = require_primitives();
118
+ var structural_1 = require_structural();
119
+ var isObjectOrNull = (term) => {
120
+ return typeof term === "object";
121
+ };
122
+ exports.isObjectOrNull = isObjectOrNull;
123
+ var isNonEmptyArray = (term) => {
124
+ return structural_1.isArray(term) && term.length > 0;
125
+ };
126
+ exports.isNonEmptyArray = isNonEmptyArray;
127
+ var isNonEmptyString2 = (term) => {
128
+ return primitives_1.isString(term) && term.length > 0;
129
+ };
130
+ exports.isNonEmptyString = isNonEmptyString2;
131
+ var isNumberOrNaN = (term) => {
132
+ return typeof term === "number";
133
+ };
134
+ exports.isNumberOrNaN = isNumberOrNaN;
135
+ var isInteger = (term) => {
136
+ return primitives_1.isNumber(term) && Number.isInteger(term);
137
+ };
138
+ exports.isInteger = isInteger;
139
+ var isPositiveInteger = (term) => {
140
+ return exports.isInteger(term) && term > 0;
141
+ };
142
+ exports.isPositiveInteger = isPositiveInteger;
143
+ var isNonNegativeInteger = (term) => {
144
+ return exports.isInteger(term) && term >= 0;
145
+ };
146
+ exports.isNonNegativeInteger = isNonNegativeInteger;
147
+ var isNegativeInteger = (term) => {
148
+ return exports.isInteger(term) && term < 0;
149
+ };
150
+ exports.isNegativeInteger = isNegativeInteger;
151
+ }
152
+ });
153
+
154
+ // node_modules/@sniptt/guards/build/index.js
155
+ var require_build = __commonJS({
156
+ "node_modules/@sniptt/guards/build/index.js"(exports) {
157
+ "use strict";
158
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
159
+ if (k2 === void 0) k2 = k;
160
+ Object.defineProperty(o, k2, { enumerable: true, get: function() {
161
+ return m[k];
162
+ } });
163
+ }) : (function(o, m, k, k2) {
164
+ if (k2 === void 0) k2 = k;
165
+ o[k2] = m[k];
166
+ }));
167
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
168
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
169
+ }) : function(o, v) {
170
+ o["default"] = v;
171
+ });
172
+ var __importStar = exports && exports.__importStar || function(mod) {
173
+ if (mod && mod.__esModule) return mod;
174
+ var result = {};
175
+ if (mod != null) {
176
+ for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
177
+ }
178
+ __setModuleDefault(result, mod);
179
+ return result;
180
+ };
181
+ var __exportStar = exports && exports.__exportStar || function(m, exports2) {
182
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
183
+ };
184
+ Object.defineProperty(exports, "__esModule", { value: true });
185
+ exports.structural = exports.primitives = exports.convenience = void 0;
186
+ exports.convenience = __importStar(require_convenience());
187
+ __exportStar(require_convenience(), exports);
188
+ exports.primitives = __importStar(require_primitives());
189
+ __exportStar(require_primitives(), exports);
190
+ exports.structural = __importStar(require_structural());
191
+ __exportStar(require_structural(), exports);
192
+ }
193
+ });
194
+
195
+ // src/logic-functions/reconcile-call-recorder-calendar-event.ts
196
+ var import_guards19 = __toESM(require_build());
197
+ import { CoreApiClient } from "twenty-client-sdk/core";
198
+
199
+ // twenty-sdk-define-stub:__twenty-sdk-define-stub__
200
+ var __defineFactoryStub = (config) => ({
201
+ success: true,
202
+ config,
203
+ errors: []
204
+ });
205
+ var __anyHandler = {
206
+ get(_target, prop) {
207
+ if (prop === "__esModule") return true;
208
+ if (prop === Symbol.toPrimitive) return () => "";
209
+ if (typeof prop === "symbol") return void 0;
210
+ return new Proxy(() => void 0, __anyHandler);
211
+ },
212
+ apply() {
213
+ return new Proxy(() => void 0, __anyHandler);
214
+ }
215
+ };
216
+ var __anyStub = new Proxy(() => void 0, __anyHandler);
217
+ var defineLogicFunction = __defineFactoryStub;
218
+
219
+ // src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts
220
+ var CALENDAR_EVENT_RECONCILIATION_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER = "1f28c477-6423-4911-85bf-2296ef112be9";
221
+
222
+ // src/logic-functions/domain/compute-real-meeting-key.util.ts
223
+ var import_guards2 = __toESM(require_build());
224
+
225
+ // src/logic-functions/utils/is-non-empty-string.util.ts
226
+ var import_guards = __toESM(require_build());
227
+ var isNonEmptyString = (value) => (0, import_guards.isString)(value) && value.trim() !== "";
228
+
229
+ // src/logic-functions/domain/compute-real-meeting-key.util.ts
230
+ var computeRealMeetingKey = ({
231
+ calendarEventId,
232
+ conferenceLinkUrl,
233
+ iCalUid,
234
+ startsAt
235
+ }) => {
236
+ const normalizedConferenceLink = normalizeConferenceLink(conferenceLinkUrl);
237
+ if (!(0, import_guards2.isUndefined)(normalizedConferenceLink)) {
238
+ return `link:${normalizedConferenceLink}:${startsAt ?? ""}`;
239
+ }
240
+ if (isNonEmptyString(iCalUid)) {
241
+ return `ical:${iCalUid}:${startsAt ?? ""}`;
242
+ }
243
+ return `event:${calendarEventId}`;
244
+ };
245
+ var normalizeConferenceLink = (conferenceLinkUrl) => {
246
+ if (!isNonEmptyString(conferenceLinkUrl)) {
247
+ return void 0;
248
+ }
249
+ const withoutProtocol = conferenceLinkUrl.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "");
250
+ const withoutQueryAndFragment = withoutProtocol.split(/[?#]/)[0];
251
+ const withoutTrailingSlash = withoutQueryAndFragment.replace(/\/+$/, "");
252
+ return withoutTrailingSlash === "" ? void 0 : withoutTrailingSlash;
253
+ };
254
+
255
+ // src/logic-functions/utils/get-unique-sorted-ids.util.ts
256
+ var import_guards3 = __toESM(require_build());
257
+ var getUniqueSortedIds = (ids) => [...new Set(ids.filter(import_guards3.isString))].sort(
258
+ (firstId, secondId) => firstId.localeCompare(secondId)
259
+ );
260
+
261
+ // src/logic-functions/flows/reconcile-call-recorder.util.ts
262
+ var import_guards18 = __toESM(require_build());
263
+
264
+ // src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts
265
+ var aggregateCallRecorderPolicyResultsByMeeting = (perCalendarEventPolicyResults) => {
266
+ const meetingPolicyResultsByMeetingKey = /* @__PURE__ */ new Map();
267
+ for (const {
268
+ calendarEventId,
269
+ realMeetingKey,
270
+ shouldRequestBot
271
+ } of perCalendarEventPolicyResults) {
272
+ const meetingPolicyResult = meetingPolicyResultsByMeetingKey.get(
273
+ realMeetingKey
274
+ ) ?? {
275
+ realMeetingKey,
276
+ shouldRequestBot: false,
277
+ calendarEventIds: [],
278
+ requestingCalendarEventIds: []
279
+ };
280
+ meetingPolicyResult.calendarEventIds.push(calendarEventId);
281
+ if (shouldRequestBot) {
282
+ meetingPolicyResult.shouldRequestBot = true;
283
+ meetingPolicyResult.requestingCalendarEventIds.push(calendarEventId);
284
+ }
285
+ meetingPolicyResultsByMeetingKey.set(realMeetingKey, meetingPolicyResult);
286
+ }
287
+ return [...meetingPolicyResultsByMeetingKey.values()];
288
+ };
289
+
290
+ // src/constants/call-recorder-preference.ts
291
+ var CallRecorderPreference = /* @__PURE__ */ ((CallRecorderPreference2) => {
292
+ CallRecorderPreference2["ON"] = "ON";
293
+ CallRecorderPreference2["OFF"] = "OFF";
294
+ return CallRecorderPreference2;
295
+ })(CallRecorderPreference || {});
296
+
297
+ // src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts
298
+ var resolveCallRecorderPolicyResult = ({
299
+ input,
300
+ now
301
+ }) => {
302
+ if (input.isCanceled) {
303
+ return botNotRequired("EVENT_CANCELED");
304
+ }
305
+ if (input.callRecorderPreference === "OFF" /* OFF */) {
306
+ return botNotRequired("PREFERENCE_OFF");
307
+ }
308
+ if (!isNonEmptyString(input.conferenceLinkUrl)) {
309
+ return botNotRequired("MISSING_CONFERENCE_LINK");
310
+ }
311
+ if (!isCalendarEventInFuture({
312
+ startsAt: input.startsAt,
313
+ endsAt: input.endsAt,
314
+ now
315
+ })) {
316
+ return botNotRequired("EVENT_NOT_UPCOMING");
317
+ }
318
+ return botRequired("RECORDING_ENABLED");
319
+ };
320
+ var isCalendarEventInFuture = ({
321
+ startsAt,
322
+ endsAt,
323
+ now
324
+ }) => {
325
+ const reference = endsAt ?? startsAt;
326
+ if (!isNonEmptyString(reference)) {
327
+ return false;
328
+ }
329
+ const referenceTime = new Date(reference).getTime();
330
+ if (Number.isNaN(referenceTime)) {
331
+ return false;
332
+ }
333
+ return referenceTime > now.getTime();
334
+ };
335
+ var botRequired = (reason) => ({ shouldRequestBot: true, reason });
336
+ var botNotRequired = (reason) => ({ shouldRequestBot: false, reason });
337
+
338
+ // src/logic-functions/domain/build-call-recorder-policy-result.util.ts
339
+ var buildCallRecorderPolicyResult = (calendarEvent, now) => {
340
+ const realMeetingKey = computeRealMeetingKey({
341
+ calendarEventId: calendarEvent.id,
342
+ conferenceLinkUrl: calendarEvent.conferenceLinkUrl,
343
+ iCalUid: calendarEvent.iCalUid,
344
+ startsAt: calendarEvent.startsAt
345
+ });
346
+ const callRecorderPreference = normalizeCallRecorderPreference(
347
+ calendarEvent.callRecorderPreference
348
+ );
349
+ const policyResult = resolveCallRecorderPolicyResult({
350
+ input: {
351
+ callRecorderPreference,
352
+ isCanceled: calendarEvent.isCanceled,
353
+ startsAt: calendarEvent.startsAt,
354
+ endsAt: calendarEvent.endsAt,
355
+ conferenceLinkUrl: calendarEvent.conferenceLinkUrl
356
+ },
357
+ now
358
+ });
359
+ return {
360
+ calendarEventId: calendarEvent.id,
361
+ callRecorderPreference,
362
+ realMeetingKey,
363
+ ...policyResult
364
+ };
365
+ };
366
+ var normalizeCallRecorderPreference = (callRecorderPreference) => isCallRecorderPreference(callRecorderPreference) ? callRecorderPreference : void 0;
367
+ var isCallRecorderPreference = (callRecorderPreference) => Object.values(CallRecorderPreference).some(
368
+ (preference) => preference === callRecorderPreference
369
+ );
370
+
371
+ // src/logic-functions/flows/cancel-call-recording-request.util.ts
372
+ var import_guards6 = __toESM(require_build());
373
+
374
+ // src/logic-functions/recall-api/get-recall-api-config.util.ts
375
+ var import_guards4 = __toESM(require_build());
376
+
377
+ // src/logic-functions/constants/call-recorder-name-env-var-name.ts
378
+ var CALL_RECORDER_NAME_ENV_VAR_NAME = "CALL_RECORDER_NAME";
379
+
380
+ // src/logic-functions/constants/default-call-recorder-name.ts
381
+ var DEFAULT_CALL_RECORDER_NAME = "Twenty.com";
382
+
383
+ // src/logic-functions/constants/default-recall-region.ts
384
+ var DEFAULT_RECALL_REGION = "eu-central-1";
385
+
386
+ // src/logic-functions/constants/recall-api-key-env-var-name.ts
387
+ var RECALL_API_KEY_ENV_VAR_NAME = "RECALL_API_KEY";
388
+
389
+ // src/logic-functions/constants/recall-region-env-var-name.ts
390
+ var RECALL_REGION_ENV_VAR_NAME = "RECALL_REGION";
391
+
392
+ // src/logic-functions/utils/get-application-variable-value.util.ts
393
+ var getApplicationVariableValue = (key) => process.env[key];
394
+
395
+ // src/logic-functions/recall-api/get-recall-api-config.util.ts
396
+ var getRecallApiConfig = () => {
397
+ const apiKey = normalizeOptionalString(
398
+ getApplicationVariableValue(RECALL_API_KEY_ENV_VAR_NAME)
399
+ );
400
+ if ((0, import_guards4.isUndefined)(apiKey)) {
401
+ return {
402
+ success: false,
403
+ error: "RECALL_API_KEY server variable is not set. A server admin must set it on the Call Recorder application registration before scheduling bots."
404
+ };
405
+ }
406
+ const region = normalizeOptionalString(
407
+ getApplicationVariableValue(RECALL_REGION_ENV_VAR_NAME)
408
+ ) ?? DEFAULT_RECALL_REGION;
409
+ const botName = normalizeOptionalString(
410
+ getApplicationVariableValue(CALL_RECORDER_NAME_ENV_VAR_NAME)
411
+ ) ?? DEFAULT_CALL_RECORDER_NAME;
412
+ return {
413
+ success: true,
414
+ config: {
415
+ apiKey,
416
+ baseUrl: `https://${region}.recall.ai/api/v1`,
417
+ botName
418
+ }
419
+ };
420
+ };
421
+ var normalizeOptionalString = (value) => isNonEmptyString(value) ? value.trim() : void 0;
422
+
423
+ // src/logic-functions/recall-api/recall-bot-api-request.util.ts
424
+ var import_guards5 = __toESM(require_build());
425
+
426
+ // src/logic-functions/constants/recall-api-max-attempts.ts
427
+ var RECALL_API_MAX_ATTEMPTS = 3;
428
+
429
+ // src/logic-functions/constants/recall-api-retry-delay-ms.ts
430
+ var RECALL_API_RETRY_DELAY_MS = 500;
431
+
432
+ // src/logic-functions/recall-api/recall-bot-api-request.util.ts
433
+ var recallBotApiRequest = async (requestArgs) => {
434
+ const maxAttempts = requestArgs.maxAttempts ?? RECALL_API_MAX_ATTEMPTS;
435
+ for (let attemptNumber = 1; ; attemptNumber++) {
436
+ const { result, isRetryable } = await performRecallBotApiRequestAttempt(requestArgs);
437
+ if (!isRetryable || attemptNumber >= maxAttempts) {
438
+ return result;
439
+ }
440
+ await sleep(RECALL_API_RETRY_DELAY_MS * attemptNumber);
441
+ }
442
+ };
443
+ var performRecallBotApiRequestAttempt = async ({
444
+ config,
445
+ path,
446
+ method,
447
+ body,
448
+ allowNotFound = false
449
+ }) => {
450
+ let response;
451
+ try {
452
+ response = await fetch(`${config.baseUrl}${path}`, {
453
+ method,
454
+ headers: {
455
+ Authorization: buildRecallApiAuthorizationHeader(config.apiKey),
456
+ ...(0, import_guards5.isUndefined)(body) ? {} : { "Content-Type": "application/json" }
457
+ },
458
+ ...(0, import_guards5.isUndefined)(body) ? {} : { body: JSON.stringify(body) }
459
+ });
460
+ } catch (error) {
461
+ return {
462
+ isRetryable: true,
463
+ result: {
464
+ ok: false,
465
+ status: null,
466
+ errorMessage: `Recall API request failed: ${error instanceof Error ? error.message : String(error)}`
467
+ }
468
+ };
469
+ }
470
+ if (allowNotFound && response.status === 404) {
471
+ return {
472
+ isRetryable: false,
473
+ result: {
474
+ ok: true,
475
+ status: response.status,
476
+ data: void 0
477
+ }
478
+ };
479
+ }
480
+ if (response.status === 204) {
481
+ return {
482
+ isRetryable: false,
483
+ result: {
484
+ ok: true,
485
+ status: response.status,
486
+ data: void 0
487
+ }
488
+ };
489
+ }
490
+ if (!response.ok) {
491
+ return {
492
+ isRetryable: isRetryableRecallApiStatus(response.status),
493
+ result: {
494
+ ok: false,
495
+ status: response.status,
496
+ errorMessage: await extractRecallApiErrorMessage(response)
497
+ }
498
+ };
499
+ }
500
+ try {
501
+ return {
502
+ isRetryable: false,
503
+ result: {
504
+ ok: true,
505
+ status: response.status,
506
+ data: await response.json()
507
+ }
508
+ };
509
+ } catch (error) {
510
+ return {
511
+ isRetryable: false,
512
+ result: {
513
+ ok: false,
514
+ status: response.status,
515
+ errorMessage: `Recall API returned a non-JSON response: ${error instanceof Error ? error.message : String(error)}`
516
+ }
517
+ };
518
+ }
519
+ };
520
+ var isRetryableRecallApiStatus = (status) => status === 429 || status >= 500;
521
+ var sleep = (delayMs) => new Promise((resolve) => {
522
+ setTimeout(resolve, delayMs);
523
+ });
524
+ var buildRecallApiAuthorizationHeader = (apiKey) => {
525
+ const trimmedApiKey = apiKey.trim();
526
+ return trimmedApiKey.toLowerCase().startsWith("token ") ? trimmedApiKey : `Token ${trimmedApiKey}`;
527
+ };
528
+ var extractRecallApiErrorMessage = async (response) => {
529
+ const fallback = `Recall API responded with HTTP ${response.status}`;
530
+ try {
531
+ const body = await response.json();
532
+ return `${fallback}: ${JSON.stringify(body)}`;
533
+ } catch {
534
+ return fallback;
535
+ }
536
+ };
537
+
538
+ // src/logic-functions/recall-api/cancel-recall-bot.util.ts
539
+ var cancelRecallBot = async ({
540
+ externalBotId
541
+ }) => {
542
+ const configResult = getRecallApiConfig();
543
+ if (!configResult.success) {
544
+ return { ok: false, status: null, errorMessage: configResult.error };
545
+ }
546
+ const result = await recallBotApiRequest({
547
+ config: configResult.config,
548
+ path: `/bot/${externalBotId}/`,
549
+ method: "DELETE",
550
+ allowNotFound: true
551
+ });
552
+ if (!result.ok) {
553
+ return result;
554
+ }
555
+ return { ok: true };
556
+ };
557
+
558
+ // src/logic-functions/data/update-call-recording.util.ts
559
+ var updateCallRecording = async (client, {
560
+ id,
561
+ data
562
+ }) => {
563
+ await client.mutation({
564
+ updateCallRecording: {
565
+ __args: {
566
+ id,
567
+ data
568
+ },
569
+ id: true
570
+ }
571
+ });
572
+ };
573
+
574
+ // src/logic-functions/flows/cancel-call-recording-request.util.ts
575
+ var cancelCallRecordingRequest = async ({
576
+ client,
577
+ callRecording
578
+ }) => {
579
+ await updateCallRecording(client, {
580
+ id: callRecording.id,
581
+ data: {
582
+ recordingRequestStatus: "CANCELED" /* CANCELED */
583
+ }
584
+ });
585
+ if ((0, import_guards6.isUndefined)(callRecording.externalBotId)) {
586
+ return;
587
+ }
588
+ const cancelResult = await cancelRecallBot({
589
+ externalBotId: callRecording.externalBotId
590
+ });
591
+ if (!cancelResult.ok) {
592
+ console.warn(
593
+ `[call-recorder] failed to cancel Recall bot for callRecording ${callRecording.id}, leaving it for the stale-state cron: ${cancelResult.errorMessage}`
594
+ );
595
+ return;
596
+ }
597
+ await updateCallRecording(client, {
598
+ id: callRecording.id,
599
+ data: {
600
+ externalBotId: null
601
+ }
602
+ });
603
+ };
604
+
605
+ // src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts
606
+ import { createHash } from "crypto";
607
+ var computeCallRecordingIdForMeeting = (realMeetingKey) => {
608
+ const bytes = createHash("sha256").update(realMeetingKey).digest();
609
+ bytes[6] = bytes[6] & 15 | 64;
610
+ bytes[8] = bytes[8] & 63 | 128;
611
+ const hex = bytes.subarray(0, 16).toString("hex");
612
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
613
+ };
614
+
615
+ // src/logic-functions/data/create-call-recording.util.ts
616
+ var import_guards7 = __toESM(require_build());
617
+ var createCallRecording = async (client, {
618
+ id,
619
+ data
620
+ }) => {
621
+ const mutationResult = await client.mutation({
622
+ createCallRecording: {
623
+ __args: {
624
+ data: { id, ...data }
625
+ },
626
+ id: true
627
+ }
628
+ });
629
+ const createdCallRecordingId = mutationResult.createCallRecording?.id;
630
+ if ((0, import_guards7.isUndefined)(createdCallRecordingId)) {
631
+ throw new Error(
632
+ "createCallRecording mutation did not return a call recording id"
633
+ );
634
+ }
635
+ return createdCallRecordingId;
636
+ };
637
+
638
+ // src/logic-functions/flows/ensure-call-recorder.util.ts
639
+ var import_guards14 = __toESM(require_build());
640
+
641
+ // src/logic-functions/domain/build-recall-routing-metadata.util.ts
642
+ var buildRecallRoutingMetadata = ({
643
+ callRecordingId,
644
+ workspaceId
645
+ }) => ({
646
+ twentyWorkspaceId: workspaceId,
647
+ twentyCallRecordingId: callRecordingId
648
+ });
649
+
650
+ // src/logic-functions/constants/default-call-recorder-join-early-minutes.ts
651
+ var DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES = 1;
652
+
653
+ // src/logic-functions/constants/milliseconds-per-minute.ts
654
+ var MILLISECONDS_PER_MINUTE = 6e4;
655
+
656
+ // src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts
657
+ var CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME = "CALL_RECORDER_JOIN_EARLY_MINUTES";
658
+
659
+ // src/logic-functions/domain/compute-recall-bot-join-at.util.ts
660
+ var computeRecallBotJoinAt = (meetingStartsAt) => {
661
+ const meetingStartTimeInMilliseconds = new Date(meetingStartsAt).getTime();
662
+ if (Number.isNaN(meetingStartTimeInMilliseconds)) {
663
+ return meetingStartsAt;
664
+ }
665
+ return new Date(
666
+ meetingStartTimeInMilliseconds - getRecallBotJoinEarlyMinutes() * MILLISECONDS_PER_MINUTE
667
+ ).toISOString();
668
+ };
669
+ var getRecallBotJoinEarlyMinutes = () => {
670
+ const rawValue = getApplicationVariableValue(
671
+ CALL_RECORDER_JOIN_EARLY_MINUTES_ENV_VAR_NAME
672
+ );
673
+ if (!isNonEmptyString(rawValue)) {
674
+ return DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES;
675
+ }
676
+ const joinEarlyMinutes = Number(rawValue.trim());
677
+ return Number.isInteger(joinEarlyMinutes) && joinEarlyMinutes >= 0 ? joinEarlyMinutes : DEFAULT_CALL_RECORDER_JOIN_EARLY_MINUTES;
678
+ };
679
+
680
+ // src/logic-functions/data/find-call-recordings-by-filter.util.ts
681
+ var import_guards9 = __toESM(require_build());
682
+
683
+ // src/logic-functions/constants/twenty-page-size.ts
684
+ var TWENTY_PAGE_SIZE = 100;
685
+
686
+ // src/logic-functions/data/fetch-all-nodes.util.ts
687
+ var import_guards8 = __toESM(require_build());
688
+ var fetchAllNodes = async (fetchPage) => {
689
+ const nodes = [];
690
+ let hasNextPage = true;
691
+ let afterCursor;
692
+ while (hasNextPage) {
693
+ const connection = await fetchPage(afterCursor);
694
+ if ((0, import_guards8.isUndefined)(connection)) {
695
+ throw new Error("Pagination query returned no connection");
696
+ }
697
+ for (const edge of connection.edges ?? []) {
698
+ nodes.push(edge.node);
699
+ }
700
+ hasNextPage = connection.pageInfo?.hasNextPage === true;
701
+ const endCursor = connection.pageInfo?.endCursor;
702
+ if (hasNextPage && !(0, import_guards8.isString)(endCursor)) {
703
+ throw new Error(
704
+ "Inconsistent pagination state: hasNextPage is true without an endCursor"
705
+ );
706
+ }
707
+ afterCursor = (0, import_guards8.isString)(endCursor) ? endCursor : void 0;
708
+ }
709
+ return nodes;
710
+ };
711
+
712
+ // src/logic-functions/data/find-call-recordings-by-filter.util.ts
713
+ var findCallRecordingsByFilter = async (client, filter) => {
714
+ const callRecordingNodes = await fetchAllNodes(
715
+ async (afterCursor) => {
716
+ const queryResult = await client.query({
717
+ callRecordings: {
718
+ __args: {
719
+ filter,
720
+ first: TWENTY_PAGE_SIZE,
721
+ ...(0, import_guards9.isUndefined)(afterCursor) ? {} : { after: afterCursor }
722
+ },
723
+ pageInfo: {
724
+ hasNextPage: true,
725
+ endCursor: true
726
+ },
727
+ edges: {
728
+ node: {
729
+ id: true,
730
+ title: true,
731
+ status: true,
732
+ recordingRequestStatus: true,
733
+ startedAt: true,
734
+ endedAt: true,
735
+ calendarEventId: true,
736
+ externalBotId: true,
737
+ externalRecordingId: true,
738
+ callRecorderFailureReason: true
739
+ }
740
+ }
741
+ }
742
+ });
743
+ return queryResult.callRecordings;
744
+ }
745
+ );
746
+ return callRecordingNodes.map((callRecording) => ({
747
+ id: callRecording.id,
748
+ title: callRecording.title ?? void 0,
749
+ status: callRecording.status ?? void 0,
750
+ recordingRequestStatus: normalizeCallRecordingRequestStatus(
751
+ callRecording.recordingRequestStatus
752
+ ),
753
+ startedAt: callRecording.startedAt ?? void 0,
754
+ endedAt: callRecording.endedAt ?? void 0,
755
+ calendarEventId: callRecording.calendarEventId ?? void 0,
756
+ externalBotId: normalizeOptionalString2(callRecording.externalBotId),
757
+ externalRecordingId: normalizeOptionalString2(
758
+ callRecording.externalRecordingId
759
+ ),
760
+ callRecorderFailureReason: normalizeOptionalString2(
761
+ callRecording.callRecorderFailureReason
762
+ )
763
+ }));
764
+ };
765
+ var normalizeOptionalString2 = (value) => isNonEmptyString(value) ? value : void 0;
766
+ var normalizeCallRecordingRequestStatus = (recordingRequestStatus) => {
767
+ if (recordingRequestStatus === "REQUESTED" /* REQUESTED */) {
768
+ return recordingRequestStatus;
769
+ }
770
+ if (recordingRequestStatus === "CANCELED" /* CANCELED */) {
771
+ return recordingRequestStatus;
772
+ }
773
+ return void 0;
774
+ };
775
+
776
+ // src/logic-functions/data/find-call-recordings-by-ids.util.ts
777
+ var findCallRecordingsByIds = async (client, callRecordingIds) => {
778
+ if (callRecordingIds.length === 0) {
779
+ return [];
780
+ }
781
+ return findCallRecordingsByFilter(client, {
782
+ id: { in: callRecordingIds }
783
+ });
784
+ };
785
+
786
+ // src/logic-functions/data/get-current-workspace-id.util.ts
787
+ var import_guards11 = __toESM(require_build());
788
+
789
+ // src/logic-functions/utils/as-record.util.ts
790
+ var import_guards10 = __toESM(require_build());
791
+ var asRecord = (value) => (0, import_guards10.isObject)(value) && !(0, import_guards10.isArray)(value) ? value : void 0;
792
+
793
+ // src/logic-functions/utils/get-string.util.ts
794
+ var getString = (value) => isNonEmptyString(value) ? value : void 0;
795
+
796
+ // src/logic-functions/data/get-current-workspace-id.util.ts
797
+ var APP_ACCESS_TOKEN_ENV_VAR_NAME = "TWENTY_APP_ACCESS_TOKEN";
798
+ var getCurrentWorkspaceId = () => {
799
+ const accessToken = getString(process.env[APP_ACCESS_TOKEN_ENV_VAR_NAME]);
800
+ if ((0, import_guards11.isUndefined)(accessToken)) {
801
+ return void 0;
802
+ }
803
+ return getWorkspaceIdFromAccessToken(accessToken);
804
+ };
805
+ var getWorkspaceIdFromAccessToken = (accessToken) => {
806
+ const encodedPayload = accessToken.split(".")[1];
807
+ if ((0, import_guards11.isUndefined)(encodedPayload)) {
808
+ return void 0;
809
+ }
810
+ try {
811
+ const payload = asRecord(
812
+ JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"))
813
+ );
814
+ return getString(payload?.workspaceId);
815
+ } catch {
816
+ return void 0;
817
+ }
818
+ };
819
+
820
+ // src/logic-functions/recall-api/schedule-recall-bot.util.ts
821
+ var import_guards13 = __toESM(require_build());
822
+
823
+ // src/logic-functions/constants/recall-bot-automatic-leave.ts
824
+ var import_guards12 = __toESM(require_build());
825
+
826
+ // src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts
827
+ var CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME = "CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS";
828
+
829
+ // src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts
830
+ var CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME = "CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS";
831
+
832
+ // src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts
833
+ var CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME = "CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS";
834
+
835
+ // src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts
836
+ var RECALL_BOT_EVERYONE_LEFT_MIN_ACTIVATE_AFTER_SECONDS = 1;
837
+
838
+ // src/logic-functions/constants/recall-bot-automatic-leave.ts
839
+ var getRecallBotAutomaticLeave = () => {
840
+ const waitingRoomTimeoutSeconds = getOptionalPositiveIntegerVariable(
841
+ CALL_RECORDER_WAITING_ROOM_TIMEOUT_SECONDS_ENV_VAR_NAME
842
+ );
843
+ const nooneJoinedTimeoutSeconds = getOptionalPositiveIntegerVariable(
844
+ CALL_RECORDER_NOONE_JOINED_TIMEOUT_SECONDS_ENV_VAR_NAME
845
+ );
846
+ const everyoneLeftTimeoutSeconds = getOptionalPositiveIntegerVariable(
847
+ CALL_RECORDER_EVERYONE_LEFT_TIMEOUT_SECONDS_ENV_VAR_NAME
848
+ );
849
+ const automaticLeave = {};
850
+ if (!(0, import_guards12.isUndefined)(waitingRoomTimeoutSeconds)) {
851
+ automaticLeave.waiting_room_timeout = waitingRoomTimeoutSeconds;
852
+ }
853
+ if (!(0, import_guards12.isUndefined)(nooneJoinedTimeoutSeconds)) {
854
+ automaticLeave.noone_joined_timeout = nooneJoinedTimeoutSeconds;
855
+ }
856
+ if (!(0, import_guards12.isUndefined)(everyoneLeftTimeoutSeconds)) {
857
+ automaticLeave.everyone_left_timeout = {
858
+ timeout: everyoneLeftTimeoutSeconds,
859
+ activate_after: RECALL_BOT_EVERYONE_LEFT_MIN_ACTIVATE_AFTER_SECONDS
860
+ };
861
+ }
862
+ return Object.keys(automaticLeave).length === 0 ? void 0 : automaticLeave;
863
+ };
864
+ var getOptionalPositiveIntegerVariable = (variableName) => {
865
+ const rawValue = normalizeOptionalString3(
866
+ getApplicationVariableValue(variableName)
867
+ );
868
+ if ((0, import_guards12.isUndefined)(rawValue)) {
869
+ return void 0;
870
+ }
871
+ const timeoutSeconds = Number(rawValue);
872
+ if (!Number.isInteger(timeoutSeconds) || timeoutSeconds <= 0) {
873
+ return void 0;
874
+ }
875
+ return timeoutSeconds;
876
+ };
877
+ var normalizeOptionalString3 = (value) => isNonEmptyString(value) ? value.trim() : void 0;
878
+
879
+ // src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts
880
+ var CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME = "CALL_RECORDER_RECORDING_RETENTION_HOURS";
881
+
882
+ // src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts
883
+ var DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS = 166;
884
+
885
+ // src/logic-functions/constants/recall-bot-recording-config.ts
886
+ var getRecallBotRecordingConfig = () => {
887
+ const configuredRecordingRetentionHours = getApplicationVariableValue(
888
+ CALL_RECORDER_RECORDING_RETENTION_HOURS_ENV_VAR_NAME
889
+ );
890
+ const recordingRetentionHours = isNonEmptyString(
891
+ configuredRecordingRetentionHours
892
+ ) ? Number(configuredRecordingRetentionHours.trim()) : NaN;
893
+ const resolvedRecordingRetentionHours = Number.isInteger(recordingRetentionHours) && recordingRetentionHours > 0 ? recordingRetentionHours : DEFAULT_CALL_RECORDER_RECORDING_RETENTION_HOURS;
894
+ return {
895
+ video_mixed_mp4: {},
896
+ audio_mixed_mp3: {},
897
+ retention: { type: "timed", hours: resolvedRecordingRetentionHours }
898
+ };
899
+ };
900
+
901
+ // src/logic-functions/recall-api/extract-recall-bot-id.util.ts
902
+ var extractRecallBotId = (response) => getString(response?.id) ?? getString(response?.bot_id);
903
+
904
+ // src/logic-functions/recall-api/schedule-recall-bot.util.ts
905
+ var scheduleRecallBot = async ({
906
+ meetingUrl,
907
+ joinAt,
908
+ metadata
909
+ }) => {
910
+ const configResult = getRecallApiConfig();
911
+ if (!configResult.success) {
912
+ return { ok: false, status: null, errorMessage: configResult.error };
913
+ }
914
+ const automaticLeave = getRecallBotAutomaticLeave();
915
+ const result = await recallBotApiRequest({
916
+ config: configResult.config,
917
+ path: "/bot/",
918
+ method: "POST",
919
+ body: {
920
+ meeting_url: meetingUrl,
921
+ join_at: joinAt,
922
+ bot_name: configResult.config.botName,
923
+ ...(0, import_guards13.isUndefined)(automaticLeave) ? {} : { automatic_leave: automaticLeave },
924
+ recording_config: getRecallBotRecordingConfig(),
925
+ metadata
926
+ }
927
+ });
928
+ if (!result.ok) {
929
+ return result;
930
+ }
931
+ const externalBotId = extractRecallBotId(result.data);
932
+ if ((0, import_guards13.isUndefined)(externalBotId)) {
933
+ return {
934
+ ok: false,
935
+ status: null,
936
+ errorMessage: "Recall API created a bot but the response did not include a bot id"
937
+ };
938
+ }
939
+ return {
940
+ ok: true,
941
+ externalBotId
942
+ };
943
+ };
944
+
945
+ // src/logic-functions/flows/ensure-call-recorder.util.ts
946
+ var ensureCallRecorder = async (client, { callRecording, calendarEvent }) => {
947
+ const meetingUrl = calendarEvent.conferenceLinkUrl;
948
+ const meetingStartsAt = calendarEvent.startsAt;
949
+ if ((0, import_guards14.isUndefined)(meetingUrl) || (0, import_guards14.isUndefined)(meetingStartsAt)) {
950
+ return false;
951
+ }
952
+ const joinAt = computeRecallBotJoinAt(meetingStartsAt);
953
+ const freshCallRecording = (await findCallRecordingsByIds(client, [callRecording.id]))[0];
954
+ if ((0, import_guards14.isUndefined)(freshCallRecording) || freshCallRecording.recordingRequestStatus !== "REQUESTED" /* REQUESTED */ || !(0, import_guards14.isUndefined)(freshCallRecording.externalBotId)) {
955
+ return false;
956
+ }
957
+ const workspaceId = getCurrentWorkspaceId();
958
+ if ((0, import_guards14.isUndefined)(workspaceId)) {
959
+ console.error(
960
+ `[call-recorder] cannot schedule Recall bot for callRecording ${callRecording.id}: workspace id unavailable, the shared webhook could not be routed back`
961
+ );
962
+ return false;
963
+ }
964
+ const scheduleResult = await scheduleRecallBot({
965
+ meetingUrl,
966
+ joinAt,
967
+ metadata: buildRecallRoutingMetadata({
968
+ callRecordingId: callRecording.id,
969
+ workspaceId
970
+ })
971
+ });
972
+ if (!scheduleResult.ok) {
973
+ console.warn(
974
+ `[call-recorder] failed to schedule Recall bot for callRecording ${callRecording.id}: ${scheduleResult.errorMessage}`
975
+ );
976
+ return false;
977
+ }
978
+ await updateCallRecording(client, {
979
+ id: callRecording.id,
980
+ data: { externalBotId: scheduleResult.externalBotId }
981
+ });
982
+ return true;
983
+ };
984
+
985
+ // src/logic-functions/data/fetch-calendar-events-by-filter.util.ts
986
+ var import_guards15 = __toESM(require_build());
987
+
988
+ // src/logic-functions/constants/restricted-field-placeholder.ts
989
+ var RESTRICTED_FIELD_PLACEHOLDER = "FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED";
990
+
991
+ // src/logic-functions/data/strip-restricted-field-value.util.ts
992
+ var stripRestrictedFieldValue = (value) => value === RESTRICTED_FIELD_PLACEHOLDER ? void 0 : value;
993
+
994
+ // src/logic-functions/data/fetch-calendar-events-by-filter.util.ts
995
+ var fetchCalendarEventsByFilter = async (client, filter) => {
996
+ const calendarEventNodes = await fetchAllNodes(
997
+ async (afterCursor) => {
998
+ const queryResult = await client.query({
999
+ calendarEvents: {
1000
+ __args: {
1001
+ filter,
1002
+ first: TWENTY_PAGE_SIZE,
1003
+ ...(0, import_guards15.isUndefined)(afterCursor) ? {} : { after: afterCursor }
1004
+ },
1005
+ pageInfo: {
1006
+ hasNextPage: true,
1007
+ endCursor: true
1008
+ },
1009
+ edges: {
1010
+ node: {
1011
+ id: true,
1012
+ title: true,
1013
+ isCanceled: true,
1014
+ startsAt: true,
1015
+ endsAt: true,
1016
+ iCalUid: true,
1017
+ conferenceLink: {
1018
+ primaryLinkUrl: true
1019
+ },
1020
+ callRecorderPreference: true
1021
+ }
1022
+ }
1023
+ }
1024
+ });
1025
+ return queryResult.calendarEvents;
1026
+ }
1027
+ );
1028
+ return calendarEventNodes.map((calendarEvent) => ({
1029
+ id: calendarEvent.id,
1030
+ title: stripRestrictedFieldValue(calendarEvent.title ?? void 0),
1031
+ isCanceled: calendarEvent.isCanceled ?? false,
1032
+ startsAt: calendarEvent.startsAt ?? void 0,
1033
+ endsAt: calendarEvent.endsAt ?? void 0,
1034
+ iCalUid: calendarEvent.iCalUid ?? void 0,
1035
+ conferenceLinkUrl: isNonEmptyString(
1036
+ calendarEvent.conferenceLink?.primaryLinkUrl
1037
+ ) ? calendarEvent.conferenceLink.primaryLinkUrl : void 0,
1038
+ callRecorderPreference: (0, import_guards15.isString)(calendarEvent.callRecorderPreference) ? calendarEvent.callRecorderPreference : void 0
1039
+ }));
1040
+ };
1041
+
1042
+ // src/logic-functions/data/fetch-calendar-events-by-ids.util.ts
1043
+ var fetchCalendarEventsByIds = async (client, calendarEventIds) => {
1044
+ const uniqueCalendarEventIds = getUniqueSortedIds(calendarEventIds);
1045
+ if (uniqueCalendarEventIds.length === 0) {
1046
+ return [];
1047
+ }
1048
+ return fetchCalendarEventsByFilter(client, {
1049
+ id: { in: uniqueCalendarEventIds }
1050
+ });
1051
+ };
1052
+
1053
+ // src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts
1054
+ var fetchCalendarEventsByStartsAtValues = async (client, startsAtValues) => {
1055
+ const uniqueStartsAtValues = [...new Set(startsAtValues)].sort();
1056
+ if (uniqueStartsAtValues.length === 0) {
1057
+ return [];
1058
+ }
1059
+ return fetchCalendarEventsByFilter(client, {
1060
+ startsAt: { in: uniqueStartsAtValues }
1061
+ });
1062
+ };
1063
+
1064
+ // src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts
1065
+ var findCallRecordingsByCalendarEventIds = async (client, calendarEventIds) => {
1066
+ if (calendarEventIds.length === 0) {
1067
+ return [];
1068
+ }
1069
+ return findCallRecordingsByFilter(client, {
1070
+ calendarEventId: { in: calendarEventIds }
1071
+ });
1072
+ };
1073
+
1074
+ // src/logic-functions/flows/reschedule-call-recording-bot.util.ts
1075
+ var import_guards17 = __toESM(require_build());
1076
+
1077
+ // src/logic-functions/recall-api/reschedule-recall-bot.util.ts
1078
+ var import_guards16 = __toESM(require_build());
1079
+ var rescheduleRecallBot = async ({
1080
+ externalBotId,
1081
+ meetingUrl,
1082
+ joinAt,
1083
+ metadata
1084
+ }) => {
1085
+ const configResult = getRecallApiConfig();
1086
+ if (!configResult.success) {
1087
+ return { ok: false, status: null, errorMessage: configResult.error };
1088
+ }
1089
+ const automaticLeave = getRecallBotAutomaticLeave();
1090
+ const result = await recallBotApiRequest({
1091
+ config: configResult.config,
1092
+ path: `/bot/${externalBotId}/`,
1093
+ method: "PATCH",
1094
+ body: {
1095
+ meeting_url: meetingUrl,
1096
+ join_at: joinAt,
1097
+ bot_name: configResult.config.botName,
1098
+ ...(0, import_guards16.isUndefined)(automaticLeave) ? {} : { automatic_leave: automaticLeave },
1099
+ recording_config: getRecallBotRecordingConfig(),
1100
+ metadata
1101
+ }
1102
+ });
1103
+ if (!result.ok) {
1104
+ return result;
1105
+ }
1106
+ return {
1107
+ ok: true,
1108
+ externalBotId: extractRecallBotId(result.data) ?? externalBotId
1109
+ };
1110
+ };
1111
+
1112
+ // src/logic-functions/flows/reschedule-call-recording-bot.util.ts
1113
+ var RECALL_BOT_NOT_FOUND_STATUS = 404;
1114
+ var rescheduleCallRecordingBot = async (client, { callRecording, calendarEvent }) => {
1115
+ const externalBotId = callRecording.externalBotId;
1116
+ if ((0, import_guards17.isUndefined)(externalBotId)) {
1117
+ return;
1118
+ }
1119
+ const meetingUrl = calendarEvent.conferenceLinkUrl;
1120
+ const meetingStartsAt = calendarEvent.startsAt;
1121
+ if ((0, import_guards17.isUndefined)(meetingUrl) || (0, import_guards17.isUndefined)(meetingStartsAt)) {
1122
+ return;
1123
+ }
1124
+ const joinAt = computeRecallBotJoinAt(meetingStartsAt);
1125
+ const workspaceId = getCurrentWorkspaceId();
1126
+ if ((0, import_guards17.isUndefined)(workspaceId)) {
1127
+ console.warn(
1128
+ `[call-recorder] cannot reschedule Recall bot for callRecording ${callRecording.id}: workspace id unavailable`
1129
+ );
1130
+ return;
1131
+ }
1132
+ const rescheduleResult = await rescheduleRecallBot({
1133
+ externalBotId,
1134
+ meetingUrl,
1135
+ joinAt,
1136
+ metadata: buildRecallRoutingMetadata({
1137
+ callRecordingId: callRecording.id,
1138
+ workspaceId
1139
+ })
1140
+ });
1141
+ if (rescheduleResult.ok) {
1142
+ return;
1143
+ }
1144
+ if (rescheduleResult.status === RECALL_BOT_NOT_FOUND_STATUS) {
1145
+ await updateCallRecording(client, {
1146
+ id: callRecording.id,
1147
+ data: { externalBotId: null }
1148
+ });
1149
+ return;
1150
+ }
1151
+ console.warn(
1152
+ `[call-recorder] failed to update Recall bot for callRecording ${callRecording.id}: ${rescheduleResult.errorMessage}`
1153
+ );
1154
+ };
1155
+
1156
+ // src/logic-functions/flows/reconcile-call-recorder.util.ts
1157
+ var reconcileCallRecorderForCalendarEventIds = async ({
1158
+ client,
1159
+ calendarEventIds,
1160
+ removedOccurrences = [],
1161
+ now = /* @__PURE__ */ new Date()
1162
+ }) => {
1163
+ const meetingPolicyResults = await resolveCallRecorderPolicyResultsForMeetings({
1164
+ client,
1165
+ calendarEventIds,
1166
+ removedOccurrences,
1167
+ now
1168
+ });
1169
+ return reconcileCallRecorderForMeetingOccurrences({
1170
+ client,
1171
+ meetingPolicyResults,
1172
+ removedOccurrences
1173
+ });
1174
+ };
1175
+ var resolveCallRecorderPolicyResultsForMeetings = async ({
1176
+ client,
1177
+ calendarEventIds,
1178
+ removedOccurrences = [],
1179
+ now = /* @__PURE__ */ new Date()
1180
+ }) => {
1181
+ const changedCalendarEvents = await fetchCalendarEventsByIds(
1182
+ client,
1183
+ getUniqueSortedIds(calendarEventIds)
1184
+ );
1185
+ const affectedMeetingKeys = /* @__PURE__ */ new Set();
1186
+ const occurrenceStartsAtAnchors = /* @__PURE__ */ new Set();
1187
+ const changedCalendarEventPolicyResults = changedCalendarEvents.map(
1188
+ (calendarEvent) => buildCallRecorderPolicyResult(calendarEvent, now)
1189
+ );
1190
+ for (const policyResult of changedCalendarEventPolicyResults) {
1191
+ affectedMeetingKeys.add(policyResult.realMeetingKey);
1192
+ }
1193
+ for (const calendarEvent of changedCalendarEvents) {
1194
+ if (!(0, import_guards18.isUndefined)(calendarEvent.startsAt)) {
1195
+ occurrenceStartsAtAnchors.add(calendarEvent.startsAt);
1196
+ }
1197
+ }
1198
+ for (const removedOccurrence of removedOccurrences) {
1199
+ affectedMeetingKeys.add(removedOccurrence.realMeetingKey);
1200
+ if (!(0, import_guards18.isUndefined)(removedOccurrence.startsAt)) {
1201
+ occurrenceStartsAtAnchors.add(removedOccurrence.startsAt);
1202
+ }
1203
+ }
1204
+ if (affectedMeetingKeys.size === 0) {
1205
+ return [];
1206
+ }
1207
+ const occurrenceSiblingEvents = await fetchCalendarEventsByStartsAtValues(
1208
+ client,
1209
+ [...occurrenceStartsAtAnchors]
1210
+ );
1211
+ const policyResultsByCalendarEventId = new Map(
1212
+ changedCalendarEventPolicyResults.map((policyResult) => [
1213
+ policyResult.calendarEventId,
1214
+ policyResult
1215
+ ])
1216
+ );
1217
+ for (const calendarEvent of occurrenceSiblingEvents) {
1218
+ if (policyResultsByCalendarEventId.has(calendarEvent.id)) {
1219
+ continue;
1220
+ }
1221
+ policyResultsByCalendarEventId.set(
1222
+ calendarEvent.id,
1223
+ buildCallRecorderPolicyResult(calendarEvent, now)
1224
+ );
1225
+ }
1226
+ const perCalendarEventPolicyResults = [
1227
+ ...policyResultsByCalendarEventId.values()
1228
+ ].filter(
1229
+ (policyResult) => affectedMeetingKeys.has(policyResult.realMeetingKey)
1230
+ ).map((policyResult) => ({
1231
+ calendarEventId: policyResult.calendarEventId,
1232
+ realMeetingKey: policyResult.realMeetingKey,
1233
+ shouldRequestBot: policyResult.shouldRequestBot
1234
+ }));
1235
+ const meetingPolicyResults = aggregateCallRecorderPolicyResultsByMeeting(
1236
+ perCalendarEventPolicyResults
1237
+ );
1238
+ const meetingKeysWithPolicyResult = new Set(
1239
+ meetingPolicyResults.map(
1240
+ (meetingPolicyResult) => meetingPolicyResult.realMeetingKey
1241
+ )
1242
+ );
1243
+ for (const meetingKey of [...affectedMeetingKeys].sort()) {
1244
+ if (meetingKeysWithPolicyResult.has(meetingKey)) {
1245
+ continue;
1246
+ }
1247
+ meetingPolicyResults.push({
1248
+ realMeetingKey: meetingKey,
1249
+ shouldRequestBot: false,
1250
+ calendarEventIds: [],
1251
+ requestingCalendarEventIds: []
1252
+ });
1253
+ }
1254
+ return meetingPolicyResults;
1255
+ };
1256
+ var reconcileCallRecorderForMeetingOccurrences = async ({
1257
+ client,
1258
+ meetingPolicyResults,
1259
+ removedOccurrences = []
1260
+ }) => {
1261
+ const removedCalendarEventIdsByMeetingKey = buildRemovedCalendarEventIdsByMeetingKey(removedOccurrences);
1262
+ const reconciliationResults = [];
1263
+ const orderedMeetingPolicyResults = [
1264
+ ...meetingPolicyResults.filter(
1265
+ (meetingPolicyResult) => !meetingPolicyResult.shouldRequestBot
1266
+ ),
1267
+ ...meetingPolicyResults.filter(
1268
+ (meetingPolicyResult) => meetingPolicyResult.shouldRequestBot
1269
+ )
1270
+ ];
1271
+ for (const meetingPolicyResult of orderedMeetingPolicyResults) {
1272
+ const removedCalendarEventIds = removedCalendarEventIdsByMeetingKey.get(
1273
+ meetingPolicyResult.realMeetingKey
1274
+ ) ?? [];
1275
+ try {
1276
+ reconciliationResults.push(
1277
+ meetingPolicyResult.shouldRequestBot ? await reconcileActiveMeeting({
1278
+ client,
1279
+ meetingPolicyResult,
1280
+ removedCalendarEventIds
1281
+ }) : await reconcileCanceledMeeting({
1282
+ client,
1283
+ meetingPolicyResult,
1284
+ removedCalendarEventIds
1285
+ })
1286
+ );
1287
+ } catch (error) {
1288
+ const errorMessage = error instanceof Error ? error.message : String(error);
1289
+ console.error(
1290
+ `[call-recorder] reconciliation failed for meeting ${meetingPolicyResult.realMeetingKey}: ${errorMessage}`
1291
+ );
1292
+ reconciliationResults.push({
1293
+ action: "FAILED",
1294
+ realMeetingKey: meetingPolicyResult.realMeetingKey,
1295
+ errorMessage
1296
+ });
1297
+ }
1298
+ }
1299
+ return reconciliationResults;
1300
+ };
1301
+ var reconcileActiveMeeting = async ({
1302
+ client,
1303
+ meetingPolicyResult,
1304
+ removedCalendarEventIds
1305
+ }) => {
1306
+ const representativeCalendarEventId = getUniqueSortedIds(
1307
+ meetingPolicyResult.requestingCalendarEventIds
1308
+ )[0];
1309
+ if ((0, import_guards18.isUndefined)(representativeCalendarEventId)) {
1310
+ return buildSkippedResult(meetingPolicyResult.realMeetingKey);
1311
+ }
1312
+ const representativeCalendarEvent = (await fetchCalendarEventsByIds(client, [representativeCalendarEventId]))[0];
1313
+ if ((0, import_guards18.isUndefined)(representativeCalendarEvent)) {
1314
+ return buildSkippedResult(meetingPolicyResult.realMeetingKey);
1315
+ }
1316
+ const callRecordingId = computeCallRecordingIdForMeeting(
1317
+ meetingPolicyResult.realMeetingKey
1318
+ );
1319
+ const existingCallRecording = (await findCallRecordingsByIds(client, [callRecordingId]))[0];
1320
+ if (!(0, import_guards18.isUndefined)(existingCallRecording)) {
1321
+ return updatePolicyManagedCallRecording({
1322
+ client,
1323
+ existingCallRecording,
1324
+ representativeCalendarEvent,
1325
+ realMeetingKey: meetingPolicyResult.realMeetingKey
1326
+ });
1327
+ }
1328
+ const manualOpenCallRecording = await findManualOpenCallRecording({
1329
+ client,
1330
+ meetingPolicyResult,
1331
+ removedCalendarEventIds
1332
+ });
1333
+ if (!(0, import_guards18.isUndefined)(manualOpenCallRecording)) {
1334
+ return {
1335
+ action: "SKIPPED",
1336
+ realMeetingKey: meetingPolicyResult.realMeetingKey,
1337
+ callRecordingId: manualOpenCallRecording.id
1338
+ };
1339
+ }
1340
+ return createPolicyManagedCallRecording({
1341
+ client,
1342
+ callRecordingId,
1343
+ representativeCalendarEvent,
1344
+ realMeetingKey: meetingPolicyResult.realMeetingKey
1345
+ });
1346
+ };
1347
+ var updatePolicyManagedCallRecording = async ({
1348
+ client,
1349
+ existingCallRecording,
1350
+ representativeCalendarEvent,
1351
+ realMeetingKey
1352
+ }) => {
1353
+ await updateCallRecording(client, {
1354
+ id: existingCallRecording.id,
1355
+ data: buildPolicyManagedCallRecordingUpdateFields({
1356
+ existingCallRecording,
1357
+ calendarEvent: representativeCalendarEvent
1358
+ })
1359
+ });
1360
+ await rescheduleCallRecordingBot(client, {
1361
+ callRecording: existingCallRecording,
1362
+ calendarEvent: representativeCalendarEvent
1363
+ });
1364
+ return {
1365
+ action: "UPDATED",
1366
+ realMeetingKey,
1367
+ callRecordingId: existingCallRecording.id
1368
+ };
1369
+ };
1370
+ var createPolicyManagedCallRecording = async ({
1371
+ client,
1372
+ callRecordingId,
1373
+ representativeCalendarEvent,
1374
+ realMeetingKey
1375
+ }) => {
1376
+ const scheduledFields = buildScheduledCallRecordingFields(
1377
+ representativeCalendarEvent
1378
+ );
1379
+ try {
1380
+ await createCallRecording(client, {
1381
+ id: callRecordingId,
1382
+ data: scheduledFields
1383
+ });
1384
+ } catch (error) {
1385
+ const concurrentlyCreatedCallRecording = (await findCallRecordingsByIds(client, [callRecordingId]))[0];
1386
+ if ((0, import_guards18.isUndefined)(concurrentlyCreatedCallRecording)) {
1387
+ throw error;
1388
+ }
1389
+ return updatePolicyManagedCallRecording({
1390
+ client,
1391
+ existingCallRecording: concurrentlyCreatedCallRecording,
1392
+ representativeCalendarEvent,
1393
+ realMeetingKey
1394
+ });
1395
+ }
1396
+ await ensureCallRecorder(client, {
1397
+ callRecording: {
1398
+ id: callRecordingId,
1399
+ ...scheduledFields,
1400
+ title: scheduledFields.title ?? void 0
1401
+ },
1402
+ calendarEvent: representativeCalendarEvent
1403
+ });
1404
+ return {
1405
+ action: "CREATED",
1406
+ realMeetingKey,
1407
+ callRecordingId
1408
+ };
1409
+ };
1410
+ var findManualOpenCallRecording = async ({
1411
+ client,
1412
+ meetingPolicyResult,
1413
+ removedCalendarEventIds
1414
+ }) => {
1415
+ const calendarEventIds = getUniqueSortedIds([
1416
+ ...meetingPolicyResult.calendarEventIds,
1417
+ ...meetingPolicyResult.requestingCalendarEventIds,
1418
+ ...removedCalendarEventIds
1419
+ ]);
1420
+ const callRecordings = await findCallRecordingsByCalendarEventIds(
1421
+ client,
1422
+ calendarEventIds
1423
+ );
1424
+ return [...callRecordings].sort(
1425
+ (firstCallRecording, secondCallRecording) => firstCallRecording.id.localeCompare(secondCallRecording.id)
1426
+ ).find(
1427
+ (callRecording) => callRecording.status !== "COMPLETED" /* COMPLETED */ && (0, import_guards18.isUndefined)(callRecording.recordingRequestStatus)
1428
+ );
1429
+ };
1430
+ var reconcileCanceledMeeting = async ({
1431
+ client,
1432
+ meetingPolicyResult,
1433
+ removedCalendarEventIds
1434
+ }) => {
1435
+ const calendarEventIds = getUniqueSortedIds([
1436
+ ...meetingPolicyResult.calendarEventIds,
1437
+ ...removedCalendarEventIds
1438
+ ]);
1439
+ const cancellableCallRecordings = (await findCallRecordingsByCalendarEventIds(client, calendarEventIds)).filter(
1440
+ (callRecording) => callRecording.status === "SCHEDULED" /* SCHEDULED */ && callRecording.recordingRequestStatus === "REQUESTED" /* REQUESTED */
1441
+ );
1442
+ if (cancellableCallRecordings.length === 0) {
1443
+ return buildSkippedResult(meetingPolicyResult.realMeetingKey);
1444
+ }
1445
+ for (const callRecording of cancellableCallRecordings) {
1446
+ await cancelCallRecordingRequest({
1447
+ client,
1448
+ callRecording
1449
+ });
1450
+ }
1451
+ return {
1452
+ action: "CANCELED",
1453
+ realMeetingKey: meetingPolicyResult.realMeetingKey,
1454
+ callRecordingId: cancellableCallRecordings[0].id
1455
+ };
1456
+ };
1457
+ var buildCalendarDrivenCallRecordingFields = (calendarEvent) => ({
1458
+ // Wire null clears a stale title when the calendar title is gone or restricted.
1459
+ title: calendarEvent.title ?? null,
1460
+ recordingRequestStatus: "REQUESTED" /* REQUESTED */,
1461
+ calendarEventId: calendarEvent.id
1462
+ });
1463
+ var buildScheduledCallRecordingFields = (calendarEvent) => ({
1464
+ ...buildCalendarDrivenCallRecordingFields(calendarEvent),
1465
+ status: "SCHEDULED" /* SCHEDULED */
1466
+ });
1467
+ var buildPolicyManagedCallRecordingUpdateFields = ({
1468
+ existingCallRecording,
1469
+ calendarEvent
1470
+ }) => canResetCallRecordingStatusToScheduled(existingCallRecording.status) ? {
1471
+ ...buildScheduledCallRecordingFields(calendarEvent),
1472
+ ...(0, import_guards18.isUndefined)(existingCallRecording.callRecorderFailureReason) ? {} : { callRecorderFailureReason: null }
1473
+ } : buildCalendarDrivenCallRecordingFields(calendarEvent);
1474
+ var canResetCallRecordingStatusToScheduled = (status) => status === "SCHEDULED" /* SCHEDULED */ || status === "FAILED" /* FAILED */;
1475
+ var buildRemovedCalendarEventIdsByMeetingKey = (removedOccurrences) => {
1476
+ const calendarEventIdsByMeetingKey = /* @__PURE__ */ new Map();
1477
+ for (const removedOccurrence of removedOccurrences) {
1478
+ calendarEventIdsByMeetingKey.set(removedOccurrence.realMeetingKey, [
1479
+ ...calendarEventIdsByMeetingKey.get(removedOccurrence.realMeetingKey) ?? [],
1480
+ removedOccurrence.calendarEventId
1481
+ ]);
1482
+ }
1483
+ return calendarEventIdsByMeetingKey;
1484
+ };
1485
+ var buildSkippedResult = (realMeetingKey) => ({
1486
+ action: "SKIPPED",
1487
+ realMeetingKey,
1488
+ callRecordingId: null
1489
+ });
1490
+
1491
+ // src/logic-functions/reconcile-call-recorder-calendar-event.ts
1492
+ var CALENDAR_EVENT_OBJECT_NAME = "calendarEvent";
1493
+ var CALL_RECORDER_RELEVANT_CALENDAR_EVENT_FIELDS = [
1494
+ "title",
1495
+ "callRecorderPreference",
1496
+ "conferenceLink",
1497
+ "startsAt",
1498
+ "endsAt",
1499
+ "isCanceled",
1500
+ "iCalUid"
1501
+ ];
1502
+ var CALL_RECORDER_KEY_CALENDAR_EVENT_FIELDS = [
1503
+ "conferenceLink",
1504
+ "startsAt",
1505
+ "iCalUid"
1506
+ ];
1507
+ var handler = async (event) => {
1508
+ const [objectName, action] = event.name.split(".");
1509
+ if (objectName !== CALENDAR_EVENT_OBJECT_NAME) {
1510
+ return { skipped: true, reason: "not a calendar event" };
1511
+ }
1512
+ const reconciliationPayload = buildCalendarEventReconciliationPayload({
1513
+ event,
1514
+ action
1515
+ });
1516
+ if (reconciliationPayload.calendarEventIds.length === 0 && reconciliationPayload.removedOccurrences.length === 0) {
1517
+ return { skipped: true, reason: "no relevant calendar event change" };
1518
+ }
1519
+ const client = new CoreApiClient();
1520
+ const reconciliationResults = await reconcileCallRecorderForCalendarEventIds({
1521
+ client,
1522
+ calendarEventIds: reconciliationPayload.calendarEventIds,
1523
+ removedOccurrences: reconciliationPayload.removedOccurrences
1524
+ });
1525
+ return {
1526
+ reconciled: true,
1527
+ calendarEventIds: reconciliationPayload.calendarEventIds,
1528
+ removedOccurrenceCount: reconciliationPayload.removedOccurrences.length,
1529
+ reconciliationResults
1530
+ };
1531
+ };
1532
+ var buildCalendarEventReconciliationPayload = ({
1533
+ event,
1534
+ action
1535
+ }) => {
1536
+ if (action === "created") {
1537
+ return {
1538
+ calendarEventIds: getUniqueSortedIds([
1539
+ event.recordId,
1540
+ event.properties.after?.id
1541
+ ]),
1542
+ removedOccurrences: []
1543
+ };
1544
+ }
1545
+ if (action === "updated") {
1546
+ const updatedFields = event.properties.updatedFields ?? [];
1547
+ if (!hasRelevantFieldChange(updatedFields)) {
1548
+ return { calendarEventIds: [], removedOccurrences: [] };
1549
+ }
1550
+ const removedOccurrence = hasKeyFieldChange(updatedFields) ? buildRemovedOccurrence(event.properties.before) : void 0;
1551
+ return {
1552
+ calendarEventIds: getUniqueSortedIds([
1553
+ event.recordId,
1554
+ event.properties.after?.id
1555
+ ]),
1556
+ removedOccurrences: (0, import_guards19.isUndefined)(removedOccurrence) ? [] : [removedOccurrence]
1557
+ };
1558
+ }
1559
+ if (action === "deleted" || action === "destroyed") {
1560
+ const removedOccurrence = buildRemovedOccurrence(event.properties.before);
1561
+ return {
1562
+ calendarEventIds: [],
1563
+ removedOccurrences: (0, import_guards19.isUndefined)(removedOccurrence) ? [] : [removedOccurrence]
1564
+ };
1565
+ }
1566
+ return { calendarEventIds: [], removedOccurrences: [] };
1567
+ };
1568
+ var hasRelevantFieldChange = (updatedFields) => updatedFields.some(
1569
+ (updatedField) => CALL_RECORDER_RELEVANT_CALENDAR_EVENT_FIELDS.includes(updatedField)
1570
+ );
1571
+ var hasKeyFieldChange = (updatedFields) => updatedFields.some(
1572
+ (updatedField) => CALL_RECORDER_KEY_CALENDAR_EVENT_FIELDS.includes(updatedField)
1573
+ );
1574
+ var buildRemovedOccurrence = (calendarEvent) => {
1575
+ if ((0, import_guards19.isUndefined)(calendarEvent)) {
1576
+ return void 0;
1577
+ }
1578
+ return {
1579
+ calendarEventId: calendarEvent.id,
1580
+ realMeetingKey: computeRealMeetingKey({
1581
+ calendarEventId: calendarEvent.id,
1582
+ conferenceLinkUrl: calendarEvent.conferenceLink?.primaryLinkUrl,
1583
+ iCalUid: calendarEvent.iCalUid ?? void 0,
1584
+ startsAt: calendarEvent.startsAt ?? void 0
1585
+ }),
1586
+ startsAt: calendarEvent.startsAt ?? void 0
1587
+ };
1588
+ };
1589
+ var reconcile_call_recorder_calendar_event_default = defineLogicFunction({
1590
+ universalIdentifier: CALENDAR_EVENT_RECONCILIATION_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
1591
+ name: "reconcile-call-recorder-calendar-event",
1592
+ description: "Reconciles app-managed Recall bot recording requests when calendar events change.",
1593
+ timeoutSeconds: 60,
1594
+ handler,
1595
+ databaseEventTriggerSettings: {
1596
+ eventName: `${CALENDAR_EVENT_OBJECT_NAME}.*`
1597
+ }
1598
+ });
1599
+ export {
1600
+ reconcile_call_recorder_calendar_event_default as default
1601
+ };
1602
+ //# sourceMappingURL=reconcile-call-recorder-calendar-event.mjs.map