@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.
- package/manifest.json +297 -0
- package/package.json +2 -2
- package/src/front-components/calendar-event-recording.front-component.mjs +269 -0
- package/src/front-components/calendar-event-recording.front-component.mjs.map +7 -0
- package/src/logic-functions/process-recall-webhook.mjs +1744 -0
- package/src/logic-functions/process-recall-webhook.mjs.map +7 -0
- package/src/logic-functions/recall-webhook.mjs +391 -0
- package/src/logic-functions/recall-webhook.mjs.map +7 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs +1602 -0
- package/src/logic-functions/reconcile-call-recorder-calendar-event.mjs.map +7 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs +2268 -0
- package/src/logic-functions/reconcile-stale-bot-state.mjs.map +7 -0
- package/.env.example +0 -5
- package/.nvmrc +0 -1
- package/.oxlintrc.json +0 -20
- package/AGENTS.md +0 -67
- package/CLAUDE.md +0 -67
- package/README.md +0 -24
- package/SETUP.md +0 -95
- package/src/__tests__/global-setup.ts +0 -100
- package/src/__tests__/schema.integration-test.ts +0 -104
- package/src/application-config.ts +0 -96
- package/src/constants/__tests__/call-recording-field-universal-identifiers.test.ts +0 -19
- package/src/constants/app-description.ts +0 -2
- package/src/constants/app-display-name.ts +0 -1
- package/src/constants/application-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-reconciliation-logic-function-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-record-page-layout-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-front-component-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-tab-universal-identifier.ts +0 -2
- package/src/constants/calendar-event-recording-page-layout-widget-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-everyone-left-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-failure-reason-on-call-recording-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-join-early-minutes-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-name-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-noone-joined-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-off-option-id.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-calendar-event-view-field-universal-identifier.ts +0 -2
- package/src/constants/call-recorder-preference-on-option-id.ts +0 -2
- package/src/constants/call-recorder-preference.ts +0 -4
- package/src/constants/call-recorder-waiting-room-timeout-seconds-app-variable-universal-identifier.ts +0 -2
- package/src/constants/call-recording-audio-field-universal-identifier.ts +0 -2
- package/src/constants/call-recording-video-field-universal-identifier.ts +0 -2
- package/src/constants/default-role-universal-identifier.ts +0 -2
- package/src/constants/process-recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/recall-webhook-logic-function-universal-identifier.ts +0 -2
- package/src/constants/stale-bot-state-logic-function-universal-identifier.ts +0 -2
- package/src/default-role.ts +0 -69
- package/src/fields/call-recorder-failure-reason-on-call-recording.field.ts +0 -22
- package/src/fields/call-recorder-preference-on-calendar-event.field.ts +0 -41
- package/src/front-components/calendar-event-recording.front-component.tsx +0 -13
- package/src/front-components/components/CalendarEventRecording.tsx +0 -39
- package/src/front-components/components/CalendarEventRecordingBody.tsx +0 -96
- package/src/front-components/components/CalendarEventRecordingContent.tsx +0 -111
- package/src/front-components/components/RecordingTranscript.tsx +0 -92
- package/src/front-components/components/RecordingVideoPlayer.tsx +0 -52
- package/src/front-components/components/TranscriptEntryList.tsx +0 -61
- package/src/front-components/components/TranscriptEntryListItem.tsx +0 -115
- package/src/front-components/components/TranscriptErrorBox.tsx +0 -48
- package/src/front-components/components/TranscriptSpeakerAvatar.tsx +0 -141
- package/src/front-components/components/TranscriptSpeakerChip.tsx +0 -51
- package/src/front-components/constants/recording-theme-css-variables.ts +0 -40
- package/src/front-components/hooks/use-calendar-event-participants.ts +0 -172
- package/src/front-components/hooks/use-calendar-event-recording.ts +0 -155
- package/src/front-components/types/calendar-event-participant-by-speaker-name.type.ts +0 -6
- package/src/front-components/types/calendar-event-recording-participant.type.ts +0 -7
- package/src/front-components/types/transcript-entry.type.ts +0 -13
- package/src/front-components/utils/__tests__/find-active-transcript-entry-index.test.ts +0 -66
- package/src/front-components/utils/__tests__/format-transcript-timestamp.test.ts +0 -29
- package/src/front-components/utils/__tests__/get-speaker-name-match-keys.test.ts +0 -22
- package/src/front-components/utils/__tests__/parse-transcript-entries.test.ts +0 -162
- package/src/front-components/utils/build-calendar-event-participant-by-speaker-name.util.ts +0 -45
- package/src/front-components/utils/find-active-transcript-entry-index.util.ts +0 -77
- package/src/front-components/utils/format-transcript-timestamp.util.ts +0 -16
- package/src/front-components/utils/get-absolute-avatar-url.util.ts +0 -48
- package/src/front-components/utils/get-calendar-event-participant-for-speaker-name.util.ts +0 -24
- package/src/front-components/utils/get-speaker-name-match-keys.util.ts +0 -64
- package/src/front-components/utils/get-video-file-extension.util.ts +0 -23
- package/src/front-components/utils/parse-transcript-entries.util.ts +0 -85
- package/src/logic-functions/__tests__/process-recall-webhook.test.ts +0 -62
- package/src/logic-functions/__tests__/recall-webhook.test.ts +0 -180
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-everyone-left-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-join-early-minutes-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-name-env-var-name.ts +0 -1
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-noone-joined-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recorder-recording-retention-hours-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds-env-var-name.ts +0 -2
- package/src/logic-functions/constants/call-recorder-waiting-room-timeout-seconds.ts +0 -1
- package/src/logic-functions/constants/call-recording-micro-credits-per-hour.ts +0 -1
- package/src/logic-functions/constants/call-recording-request-status.ts +0 -5
- package/src/logic-functions/constants/call-recording-status.ts +0 -9
- package/src/logic-functions/constants/default-call-recorder-join-early-minutes.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-name.ts +0 -1
- package/src/logic-functions/constants/default-call-recorder-recording-retention-hours.ts +0 -2
- package/src/logic-functions/constants/default-recall-region.ts +0 -1
- package/src/logic-functions/constants/milliseconds-per-minute.ts +0 -1
- package/src/logic-functions/constants/non-terminal-call-recording-statuses.ts +0 -8
- package/src/logic-functions/constants/recall-api-key-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-api-max-attempts.ts +0 -1
- package/src/logic-functions/constants/recall-api-retry-delay-ms.ts +0 -1
- package/src/logic-functions/constants/recall-bot-automatic-leave.ts +0 -74
- package/src/logic-functions/constants/recall-bot-everyone-left-min-activate-after-seconds.ts +0 -1
- package/src/logic-functions/constants/recall-bot-recording-config.ts +0 -34
- package/src/logic-functions/constants/recall-region-env-var-name.ts +0 -1
- package/src/logic-functions/constants/recall-webhook-secret-env-var-name.ts +0 -1
- package/src/logic-functions/constants/restricted-field-placeholder.ts +0 -3
- package/src/logic-functions/constants/stale-bot-state-cron-pattern.ts +0 -1
- package/src/logic-functions/constants/twenty-page-size.ts +0 -1
- package/src/logic-functions/data/__tests__/complete-call-recording-ingestion.test.ts +0 -55
- package/src/logic-functions/data/__tests__/fetch-all-nodes.test.ts +0 -43
- package/src/logic-functions/data/__tests__/get-current-workspace-id.test.ts +0 -38
- package/src/logic-functions/data/__tests__/strip-restricted-field-value.test.ts +0 -22
- package/src/logic-functions/data/complete-call-recording-ingestion.util.ts +0 -24
- package/src/logic-functions/data/create-call-recording.util.ts +0 -41
- package/src/logic-functions/data/fetch-all-nodes.util.ts +0 -44
- package/src/logic-functions/data/fetch-calendar-events-by-filter.util.ts +0 -80
- package/src/logic-functions/data/fetch-calendar-events-by-ids.util.ts +0 -20
- package/src/logic-functions/data/fetch-calendar-events-by-starts-at-values.util.ts +0 -19
- package/src/logic-functions/data/find-call-recordings-by-calendar-event-ids.util.ts +0 -17
- package/src/logic-functions/data/find-call-recordings-by-filter.util.ts +0 -102
- package/src/logic-functions/data/find-call-recordings-by-ids.util.ts +0 -17
- package/src/logic-functions/data/find-open-scheduled-call-recordings.util.ts +0 -14
- package/src/logic-functions/data/get-current-workspace-id.util.ts +0 -36
- package/src/logic-functions/data/strip-restricted-field-value.util.ts +0 -6
- package/src/logic-functions/data/update-call-recording.util.ts +0 -24
- package/src/logic-functions/domain/__tests__/build-call-recorder-policy-result.test.ts +0 -47
- package/src/logic-functions/domain/__tests__/compute-call-recording-charge.test.ts +0 -71
- package/src/logic-functions/domain/__tests__/compute-call-recording-id-for-meeting.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/compute-real-meeting-key.test.ts +0 -88
- package/src/logic-functions/domain/__tests__/is-call-recording-ingestion-complete.test.ts +0 -59
- package/src/logic-functions/domain/__tests__/is-call-recording-status-downgrade.test.ts +0 -37
- package/src/logic-functions/domain/__tests__/resolve-call-recorder-policy-result.test.ts +0 -120
- package/src/logic-functions/domain/__tests__/should-complete-call-recording-ingestion.test.ts +0 -102
- package/src/logic-functions/domain/aggregate-call-recorder-policy-results-by-meeting.util.ts +0 -42
- package/src/logic-functions/domain/build-call-recorder-policy-result.util.ts +0 -53
- package/src/logic-functions/domain/build-failed-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-pending-transcript-marker.util.ts +0 -13
- package/src/logic-functions/domain/build-recall-routing-metadata.util.ts +0 -12
- package/src/logic-functions/domain/build-transcript-failure-reason.util.ts +0 -7
- package/src/logic-functions/domain/compute-call-recording-charge.util.ts +0 -41
- package/src/logic-functions/domain/compute-call-recording-id-for-meeting.util.ts +0 -16
- package/src/logic-functions/domain/compute-real-meeting-key.util.ts +0 -48
- package/src/logic-functions/domain/compute-recall-bot-join-at.util.ts +0 -34
- package/src/logic-functions/domain/is-call-recording-ingestion-complete.util.ts +0 -19
- package/src/logic-functions/domain/is-call-recording-status-downgrade.util.ts +0 -37
- package/src/logic-functions/domain/is-recall-recording-done-signal.util.ts +0 -13
- package/src/logic-functions/domain/map-recall-status-code-to-call-recording-status.util.ts +0 -26
- package/src/logic-functions/domain/parse-transcript-marker.util.ts +0 -29
- package/src/logic-functions/domain/resolve-call-recorder-policy-result.util.ts +0 -72
- package/src/logic-functions/domain/should-complete-call-recording-ingestion.util.ts +0 -32
- package/src/logic-functions/flows/__tests__/charge-completed-call-recording.test.ts +0 -45
- package/src/logic-functions/flows/__tests__/complete-and-charge-call-recording.test.ts +0 -61
- package/src/logic-functions/flows/__tests__/converge-diverged-call-recordings.test.ts +0 -727
- package/src/logic-functions/flows/__tests__/download-transcript.test.ts +0 -74
- package/src/logic-functions/flows/__tests__/handle-recall-webhook.test.ts +0 -1301
- package/src/logic-functions/flows/__tests__/heal-call-recordings-missing-bot.test.ts +0 -225
- package/src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts +0 -153
- package/src/logic-functions/flows/__tests__/reap-orphaned-call-recorders.test.ts +0 -425
- package/src/logic-functions/flows/__tests__/reconcile-call-recorder.test.ts +0 -1007
- package/src/logic-functions/flows/cancel-call-recording-request.util.ts +0 -46
- package/src/logic-functions/flows/charge-completed-call-recording.util.ts +0 -31
- package/src/logic-functions/flows/complete-and-charge-call-recording.util.ts +0 -29
- package/src/logic-functions/flows/converge-diverged-call-recordings-result.type.ts +0 -8
- package/src/logic-functions/flows/converge-diverged-call-recordings.util.ts +0 -447
- package/src/logic-functions/flows/download-transcript.util.ts +0 -67
- package/src/logic-functions/flows/ensure-call-recorder.util.ts +0 -73
- package/src/logic-functions/flows/handle-recall-webhook.util.ts +0 -672
- package/src/logic-functions/flows/heal-call-recordings-missing-bot.util.ts +0 -82
- package/src/logic-functions/flows/ingest-call-recording-media.util.ts +0 -128
- package/src/logic-functions/flows/persist-call-recording-progress.util.ts +0 -58
- package/src/logic-functions/flows/reap-orphaned-call-recorders.util.ts +0 -183
- package/src/logic-functions/flows/reconcile-call-recorder.util.ts +0 -495
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact-result.type.ts +0 -11
- package/src/logic-functions/flows/reconcile-call-recording-transcript-artifact.util.ts +0 -182
- package/src/logic-functions/flows/reschedule-call-recording-bot.util.ts +0 -69
- package/src/logic-functions/process-recall-webhook.ts +0 -23
- package/src/logic-functions/recall-api/__tests__/extract-recall-bot-convergence.test.ts +0 -153
- package/src/logic-functions/recall-api/__tests__/extract-recall-media-urls.test.ts +0 -67
- package/src/logic-functions/recall-api/__tests__/recall-bot-api.test.ts +0 -744
- package/src/logic-functions/recall-api/__tests__/verify-recall-webhook-signature.test.ts +0 -122
- package/src/logic-functions/recall-api/cancel-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/create-async-recall-transcript.util.ts +0 -47
- package/src/logic-functions/recall-api/eject-recall-bot.util.ts +0 -28
- package/src/logic-functions/recall-api/extract-recall-bot-convergence.util.ts +0 -149
- package/src/logic-functions/recall-api/extract-recall-bot-id.util.ts +0 -10
- package/src/logic-functions/recall-api/extract-recall-media-urls.util.ts +0 -30
- package/src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util.ts +0 -8
- package/src/logic-functions/recall-api/get-recall-api-config.util.ts +0 -59
- package/src/logic-functions/recall-api/get-recall-bot.util.ts +0 -42
- package/src/logic-functions/recall-api/get-recall-recording.util.ts +0 -31
- package/src/logic-functions/recall-api/get-recall-webhook-bot-metadata.util.ts +0 -18
- package/src/logic-functions/recall-api/list-recall-transcripts.util.ts +0 -141
- package/src/logic-functions/recall-api/list-scheduled-recall-bots.util.ts +0 -106
- package/src/logic-functions/recall-api/normalize-recall-timestamp.util.ts +0 -14
- package/src/logic-functions/recall-api/parse-recall-webhook-event.util.ts +0 -88
- package/src/logic-functions/recall-api/recall-bot-api-request.util.ts +0 -165
- package/src/logic-functions/recall-api/recall-transcript-summary.type.ts +0 -5
- package/src/logic-functions/recall-api/reschedule-recall-bot.util.ts +0 -56
- package/src/logic-functions/recall-api/retrieve-recall-transcript.util.ts +0 -71
- package/src/logic-functions/recall-api/schedule-recall-bot.util.ts +0 -68
- package/src/logic-functions/recall-api/verify-recall-webhook-signature.util.ts +0 -109
- package/src/logic-functions/recall-webhook.ts +0 -90
- package/src/logic-functions/reconcile-call-recorder-calendar-event.ts +0 -178
- package/src/logic-functions/reconcile-stale-bot-state.ts +0 -106
- package/src/logic-functions/types/calendar-event-record.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-calendar-event-input.type.ts +0 -10
- package/src/logic-functions/types/call-recorder-policy-input.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-not-required-reason.type.ts +0 -5
- package/src/logic-functions/types/call-recorder-policy-required-reason.type.ts +0 -1
- package/src/logic-functions/types/call-recorder-policy-result-for-calendar-event.type.ts +0 -9
- package/src/logic-functions/types/call-recorder-policy-result-for-meeting.type.ts +0 -6
- package/src/logic-functions/types/call-recorder-policy-result.type.ts +0 -12
- package/src/logic-functions/types/call-recorder-reconciliation-result.type.ts +0 -16
- package/src/logic-functions/types/call-recording-media-file.type.ts +0 -1
- package/src/logic-functions/types/call-recording-record.type.ts +0 -15
- package/src/logic-functions/types/call-recording-update-fields.type.ts +0 -20
- package/src/logic-functions/types/files-field-value.type.ts +0 -1
- package/src/logic-functions/types/meeting-recording.type.ts +0 -7
- package/src/logic-functions/types/recall-bot-operation-result.type.ts +0 -19
- package/src/logic-functions/types/recall-routing-metadata.type.ts +0 -4
- package/src/logic-functions/types/removed-call-recorder-occurrence.type.ts +0 -6
- package/src/logic-functions/types/transcript-marker.type.ts +0 -6
- package/src/logic-functions/utils/as-record.util.ts +0 -6
- package/src/logic-functions/utils/get-application-variable-value.util.ts +0 -3
- package/src/logic-functions/utils/get-record-at-path.util.ts +0 -10
- package/src/logic-functions/utils/get-string.util.ts +0 -4
- package/src/logic-functions/utils/get-unique-sorted-ids.util.ts +0 -8
- package/src/logic-functions/utils/is-non-empty-string.util.ts +0 -5
- package/src/page-layouts/calendar-event-recording-tab.ts +0 -33
- package/src/view-fields/call-recorder-preference-on-calendar-event.view-field.ts +0 -27
- package/tsconfig.json +0 -42
- package/tsconfig.spec.json +0 -9
- package/vitest.config.ts +0 -31
- package/vitest.unit.config.ts +0 -14
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { isUndefined } from '@sniptt/guards';
|
|
2
|
-
|
|
3
|
-
import { getRecallBotAutomaticLeave } from 'src/logic-functions/constants/recall-bot-automatic-leave';
|
|
4
|
-
import { getRecallBotRecordingConfig } from 'src/logic-functions/constants/recall-bot-recording-config';
|
|
5
|
-
import { type RecallRoutingMetadata } from 'src/logic-functions/types/recall-routing-metadata.type';
|
|
6
|
-
import { type RecallBotScheduleResult } from 'src/logic-functions/types/recall-bot-operation-result.type';
|
|
7
|
-
import {
|
|
8
|
-
extractRecallBotId,
|
|
9
|
-
type RecallBotResponse,
|
|
10
|
-
} from 'src/logic-functions/recall-api/extract-recall-bot-id.util';
|
|
11
|
-
import { getRecallApiConfig } from 'src/logic-functions/recall-api/get-recall-api-config.util';
|
|
12
|
-
import { recallBotApiRequest } from 'src/logic-functions/recall-api/recall-bot-api-request.util';
|
|
13
|
-
|
|
14
|
-
export type ScheduleRecallBotArgs = {
|
|
15
|
-
meetingUrl: string;
|
|
16
|
-
joinAt: string;
|
|
17
|
-
metadata: RecallRoutingMetadata;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const scheduleRecallBot = async ({
|
|
21
|
-
meetingUrl,
|
|
22
|
-
joinAt,
|
|
23
|
-
metadata,
|
|
24
|
-
}: ScheduleRecallBotArgs): Promise<RecallBotScheduleResult> => {
|
|
25
|
-
const configResult = getRecallApiConfig();
|
|
26
|
-
|
|
27
|
-
if (!configResult.success) {
|
|
28
|
-
return { ok: false, status: null, errorMessage: configResult.error };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const automaticLeave = getRecallBotAutomaticLeave();
|
|
32
|
-
|
|
33
|
-
const result = await recallBotApiRequest<RecallBotResponse>({
|
|
34
|
-
config: configResult.config,
|
|
35
|
-
path: '/bot/',
|
|
36
|
-
method: 'POST',
|
|
37
|
-
body: {
|
|
38
|
-
meeting_url: meetingUrl,
|
|
39
|
-
join_at: joinAt,
|
|
40
|
-
bot_name: configResult.config.botName,
|
|
41
|
-
...(isUndefined(automaticLeave)
|
|
42
|
-
? {}
|
|
43
|
-
: { automatic_leave: automaticLeave }),
|
|
44
|
-
recording_config: getRecallBotRecordingConfig(),
|
|
45
|
-
metadata,
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (!result.ok) {
|
|
50
|
-
return result;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const externalBotId = extractRecallBotId(result.data);
|
|
54
|
-
|
|
55
|
-
if (isUndefined(externalBotId)) {
|
|
56
|
-
return {
|
|
57
|
-
ok: false,
|
|
58
|
-
status: null,
|
|
59
|
-
errorMessage:
|
|
60
|
-
'Recall API created a bot but the response did not include a bot id',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
ok: true,
|
|
66
|
-
externalBotId,
|
|
67
|
-
};
|
|
68
|
-
};
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
-
|
|
3
|
-
import { isUndefined } from '@sniptt/guards';
|
|
4
|
-
|
|
5
|
-
const RECALL_WEBHOOK_SECRET_PREFIX = 'whsec_';
|
|
6
|
-
const RECALL_WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS = 5 * 60;
|
|
7
|
-
|
|
8
|
-
export const verifyRecallWebhookSignature = ({
|
|
9
|
-
rawBody,
|
|
10
|
-
headers,
|
|
11
|
-
secret,
|
|
12
|
-
now = new Date(),
|
|
13
|
-
}: {
|
|
14
|
-
rawBody: string;
|
|
15
|
-
headers: Record<string, string | undefined>;
|
|
16
|
-
secret: string;
|
|
17
|
-
now?: Date;
|
|
18
|
-
}): { valid: true } | { valid: false; error: string } => {
|
|
19
|
-
if (!secret.startsWith(RECALL_WEBHOOK_SECRET_PREFIX)) {
|
|
20
|
-
return {
|
|
21
|
-
valid: false,
|
|
22
|
-
error: 'Webhook secret must start with whsec_',
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const webhookId = headers['webhook-id'] ?? headers['svix-id'];
|
|
27
|
-
const webhookTimestamp =
|
|
28
|
-
headers['webhook-timestamp'] ?? headers['svix-timestamp'];
|
|
29
|
-
const webhookSignature =
|
|
30
|
-
headers['webhook-signature'] ?? headers['svix-signature'];
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
isUndefined(webhookId) ||
|
|
34
|
-
isUndefined(webhookTimestamp) ||
|
|
35
|
-
isUndefined(webhookSignature)
|
|
36
|
-
) {
|
|
37
|
-
return {
|
|
38
|
-
valid: false,
|
|
39
|
-
error: 'Missing webhook signature headers',
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const webhookTimestampSeconds = Number(webhookTimestamp);
|
|
44
|
-
|
|
45
|
-
if (!Number.isInteger(webhookTimestampSeconds)) {
|
|
46
|
-
return {
|
|
47
|
-
valid: false,
|
|
48
|
-
error: 'Invalid webhook timestamp',
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const nowSeconds = Math.floor(now.getTime() / 1000);
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
Math.abs(nowSeconds - webhookTimestampSeconds) >
|
|
56
|
-
RECALL_WEBHOOK_TIMESTAMP_TOLERANCE_SECONDS
|
|
57
|
-
) {
|
|
58
|
-
return {
|
|
59
|
-
valid: false,
|
|
60
|
-
error: 'Webhook timestamp is outside of the allowed tolerance',
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const secretBytes = Buffer.from(
|
|
65
|
-
secret.slice(RECALL_WEBHOOK_SECRET_PREFIX.length),
|
|
66
|
-
'base64',
|
|
67
|
-
);
|
|
68
|
-
const expectedSignature = createHmac('sha256', secretBytes)
|
|
69
|
-
.update(`${webhookId}.${webhookTimestamp}.${rawBody}`)
|
|
70
|
-
.digest('base64');
|
|
71
|
-
const providedSignatures = webhookSignature
|
|
72
|
-
.split(' ')
|
|
73
|
-
.map((signaturePart) => signaturePart.trim())
|
|
74
|
-
.filter((signaturePart) => signaturePart !== '')
|
|
75
|
-
.flatMap((signaturePart) => {
|
|
76
|
-
if (signaturePart.startsWith('v1,') || signaturePart.startsWith('v1=')) {
|
|
77
|
-
return [signaturePart.slice(3).trim()];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return [];
|
|
81
|
-
})
|
|
82
|
-
.filter((signaturePart) => signaturePart !== '');
|
|
83
|
-
|
|
84
|
-
if (providedSignatures.length === 0) {
|
|
85
|
-
return {
|
|
86
|
-
valid: false,
|
|
87
|
-
error: 'Missing v1 signature',
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const expectedSignatureBuffer = Buffer.from(expectedSignature, 'base64');
|
|
92
|
-
|
|
93
|
-
for (const providedSignature of providedSignatures) {
|
|
94
|
-
const providedSignatureBuffer = Buffer.from(providedSignature, 'base64');
|
|
95
|
-
|
|
96
|
-
if (providedSignatureBuffer.length !== expectedSignatureBuffer.length) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (timingSafeEqual(providedSignatureBuffer, expectedSignatureBuffer)) {
|
|
101
|
-
return { valid: true };
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
valid: false,
|
|
107
|
-
error: 'Signature verification failed',
|
|
108
|
-
};
|
|
109
|
-
};
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { isNull, isUndefined } from '@sniptt/guards';
|
|
2
|
-
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
|
3
|
-
|
|
4
|
-
import { PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/process-recall-webhook-logic-function-universal-identifier';
|
|
5
|
-
import { RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/recall-webhook-logic-function-universal-identifier';
|
|
6
|
-
import { RECALL_WEBHOOK_SECRET_ENV_VAR_NAME } from 'src/logic-functions/constants/recall-webhook-secret-env-var-name';
|
|
7
|
-
import { extractTwentyWorkspaceIdFromRecallWebhook } from 'src/logic-functions/recall-api/extract-twenty-workspace-id-from-recall-webhook.util';
|
|
8
|
-
import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util';
|
|
9
|
-
import { verifyRecallWebhookSignature } from 'src/logic-functions/recall-api/verify-recall-webhook-signature.util';
|
|
10
|
-
import { getApplicationVariableValue } from 'src/logic-functions/utils/get-application-variable-value.util';
|
|
11
|
-
import { isNonEmptyString } from 'src/logic-functions/utils/is-non-empty-string.util';
|
|
12
|
-
|
|
13
|
-
type RecallWebhookResolverResult = {
|
|
14
|
-
workspaceId: string;
|
|
15
|
-
targetLogicFunctionUniversalIdentifier: string;
|
|
16
|
-
payload: RecallWebhookBody;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// A thrown error becomes a non-2xx, which makes Svix retry; a returned result dispatches to the target.
|
|
20
|
-
export const recallWebhookRouteHandler = (
|
|
21
|
-
routePayload: RoutePayload<RecallWebhookBody>,
|
|
22
|
-
): RecallWebhookResolverResult => {
|
|
23
|
-
const webhookSecret = getApplicationVariableValue(
|
|
24
|
-
RECALL_WEBHOOK_SECRET_ENV_VAR_NAME,
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
if (!isNonEmptyString(webhookSecret)) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
'RECALL_WEBHOOK_SECRET server variable is not set. A server admin must copy it from the Recall webhook endpoint settings and set it on the Call Recorder application registration.',
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const { rawBody } = routePayload;
|
|
34
|
-
|
|
35
|
-
if (isUndefined(rawBody)) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
'Raw request body was not forwarded by the server; cannot verify the webhook signature',
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const signatureCheck = verifyRecallWebhookSignature({
|
|
42
|
-
rawBody,
|
|
43
|
-
headers: routePayload.headers,
|
|
44
|
-
secret: webhookSecret,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
if (!signatureCheck.valid) {
|
|
48
|
-
throw new Error(`Invalid webhook signature: ${signatureCheck.error}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const body = routePayload.body;
|
|
52
|
-
|
|
53
|
-
if (isUndefined(body) || isNull(body)) {
|
|
54
|
-
throw new Error('Webhook payload was empty');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const workspaceId = extractTwentyWorkspaceIdFromRecallWebhook(body);
|
|
58
|
-
|
|
59
|
-
if (!isNonEmptyString(workspaceId)) {
|
|
60
|
-
throw new Error(
|
|
61
|
-
'Webhook payload is missing the Twenty workspace id in the Recall bot metadata',
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
workspaceId,
|
|
67
|
-
targetLogicFunctionUniversalIdentifier:
|
|
68
|
-
PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
69
|
-
payload: body,
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default defineLogicFunction({
|
|
74
|
-
universalIdentifier: RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
75
|
-
name: 'recall-webhook',
|
|
76
|
-
description:
|
|
77
|
-
'Verifies Recall.ai webhook signatures and resolves the target workspace for the matching CallRecording update.',
|
|
78
|
-
timeoutSeconds: 30,
|
|
79
|
-
handler: recallWebhookRouteHandler,
|
|
80
|
-
serverRouteTriggerSettings: {
|
|
81
|
-
forwardedRequestHeaders: [
|
|
82
|
-
'webhook-id',
|
|
83
|
-
'webhook-timestamp',
|
|
84
|
-
'webhook-signature',
|
|
85
|
-
'svix-id',
|
|
86
|
-
'svix-timestamp',
|
|
87
|
-
'svix-signature',
|
|
88
|
-
],
|
|
89
|
-
},
|
|
90
|
-
});
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { isUndefined } from '@sniptt/guards';
|
|
2
|
-
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
3
|
-
import {
|
|
4
|
-
defineLogicFunction,
|
|
5
|
-
type DatabaseEventPayload,
|
|
6
|
-
type ObjectRecordBaseEvent,
|
|
7
|
-
} from 'twenty-sdk/define';
|
|
8
|
-
|
|
9
|
-
import { CALENDAR_EVENT_RECONCILIATION_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/calendar-event-reconciliation-logic-function-universal-identifier';
|
|
10
|
-
import { type RemovedCallRecorderOccurrence } from 'src/logic-functions/types/removed-call-recorder-occurrence.type';
|
|
11
|
-
import { computeRealMeetingKey } from 'src/logic-functions/domain/compute-real-meeting-key.util';
|
|
12
|
-
import { getUniqueSortedIds } from 'src/logic-functions/utils/get-unique-sorted-ids.util';
|
|
13
|
-
import { reconcileCallRecorderForCalendarEventIds } from 'src/logic-functions/flows/reconcile-call-recorder.util';
|
|
14
|
-
|
|
15
|
-
const CALENDAR_EVENT_OBJECT_NAME = 'calendarEvent';
|
|
16
|
-
|
|
17
|
-
const CALL_RECORDER_RELEVANT_CALENDAR_EVENT_FIELDS = [
|
|
18
|
-
'title',
|
|
19
|
-
'callRecorderPreference',
|
|
20
|
-
'conferenceLink',
|
|
21
|
-
'startsAt',
|
|
22
|
-
'endsAt',
|
|
23
|
-
'isCanceled',
|
|
24
|
-
'iCalUid',
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const CALL_RECORDER_KEY_CALENDAR_EVENT_FIELDS = [
|
|
28
|
-
'conferenceLink',
|
|
29
|
-
'startsAt',
|
|
30
|
-
'iCalUid',
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
type CalendarEventForDatabaseEvent = {
|
|
34
|
-
id: string;
|
|
35
|
-
conferenceLink?: { primaryLinkUrl?: string | null } | null;
|
|
36
|
-
iCalUid?: string | null;
|
|
37
|
-
startsAt?: string | null;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type CalendarEventDatabaseEvent = DatabaseEventPayload<
|
|
41
|
-
ObjectRecordBaseEvent<CalendarEventForDatabaseEvent>
|
|
42
|
-
>;
|
|
43
|
-
|
|
44
|
-
type CalendarEventReconciliationPayload = {
|
|
45
|
-
calendarEventIds: string[];
|
|
46
|
-
removedOccurrences: RemovedCallRecorderOccurrence[];
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const handler = async (
|
|
50
|
-
event: CalendarEventDatabaseEvent,
|
|
51
|
-
): Promise<object | undefined> => {
|
|
52
|
-
const [objectName, action] = event.name.split('.');
|
|
53
|
-
|
|
54
|
-
if (objectName !== CALENDAR_EVENT_OBJECT_NAME) {
|
|
55
|
-
return { skipped: true, reason: 'not a calendar event' };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const reconciliationPayload = buildCalendarEventReconciliationPayload({
|
|
59
|
-
event,
|
|
60
|
-
action,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
reconciliationPayload.calendarEventIds.length === 0 &&
|
|
65
|
-
reconciliationPayload.removedOccurrences.length === 0
|
|
66
|
-
) {
|
|
67
|
-
return { skipped: true, reason: 'no relevant calendar event change' };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const client = new CoreApiClient();
|
|
71
|
-
const reconciliationResults = await reconcileCallRecorderForCalendarEventIds({
|
|
72
|
-
client,
|
|
73
|
-
calendarEventIds: reconciliationPayload.calendarEventIds,
|
|
74
|
-
removedOccurrences: reconciliationPayload.removedOccurrences,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
reconciled: true,
|
|
79
|
-
calendarEventIds: reconciliationPayload.calendarEventIds,
|
|
80
|
-
removedOccurrenceCount: reconciliationPayload.removedOccurrences.length,
|
|
81
|
-
reconciliationResults,
|
|
82
|
-
};
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const buildCalendarEventReconciliationPayload = ({
|
|
86
|
-
event,
|
|
87
|
-
action,
|
|
88
|
-
}: {
|
|
89
|
-
event: CalendarEventDatabaseEvent;
|
|
90
|
-
action: string | undefined;
|
|
91
|
-
}): CalendarEventReconciliationPayload => {
|
|
92
|
-
if (action === 'created') {
|
|
93
|
-
return {
|
|
94
|
-
calendarEventIds: getUniqueSortedIds([
|
|
95
|
-
event.recordId,
|
|
96
|
-
event.properties.after?.id,
|
|
97
|
-
]),
|
|
98
|
-
removedOccurrences: [],
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (action === 'updated') {
|
|
103
|
-
const updatedFields = event.properties.updatedFields ?? [];
|
|
104
|
-
|
|
105
|
-
if (!hasRelevantFieldChange(updatedFields)) {
|
|
106
|
-
return { calendarEventIds: [], removedOccurrences: [] };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const removedOccurrence = hasKeyFieldChange(updatedFields)
|
|
110
|
-
? buildRemovedOccurrence(event.properties.before)
|
|
111
|
-
: undefined;
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
calendarEventIds: getUniqueSortedIds([
|
|
115
|
-
event.recordId,
|
|
116
|
-
event.properties.after?.id,
|
|
117
|
-
]),
|
|
118
|
-
removedOccurrences: isUndefined(removedOccurrence)
|
|
119
|
-
? []
|
|
120
|
-
: [removedOccurrence],
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (action === 'deleted' || action === 'destroyed') {
|
|
125
|
-
const removedOccurrence = buildRemovedOccurrence(event.properties.before);
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
calendarEventIds: [],
|
|
129
|
-
removedOccurrences: isUndefined(removedOccurrence)
|
|
130
|
-
? []
|
|
131
|
-
: [removedOccurrence],
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return { calendarEventIds: [], removedOccurrences: [] };
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const hasRelevantFieldChange = (updatedFields: string[]): boolean =>
|
|
139
|
-
updatedFields.some((updatedField) =>
|
|
140
|
-
CALL_RECORDER_RELEVANT_CALENDAR_EVENT_FIELDS.includes(updatedField),
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const hasKeyFieldChange = (updatedFields: string[]): boolean =>
|
|
144
|
-
updatedFields.some((updatedField) =>
|
|
145
|
-
CALL_RECORDER_KEY_CALENDAR_EVENT_FIELDS.includes(updatedField),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const buildRemovedOccurrence = (
|
|
149
|
-
calendarEvent: CalendarEventForDatabaseEvent | undefined,
|
|
150
|
-
): RemovedCallRecorderOccurrence | undefined => {
|
|
151
|
-
if (isUndefined(calendarEvent)) {
|
|
152
|
-
return undefined;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
calendarEventId: calendarEvent.id,
|
|
157
|
-
realMeetingKey: computeRealMeetingKey({
|
|
158
|
-
calendarEventId: calendarEvent.id,
|
|
159
|
-
conferenceLinkUrl: calendarEvent.conferenceLink?.primaryLinkUrl,
|
|
160
|
-
iCalUid: calendarEvent.iCalUid ?? undefined,
|
|
161
|
-
startsAt: calendarEvent.startsAt ?? undefined,
|
|
162
|
-
}),
|
|
163
|
-
startsAt: calendarEvent.startsAt ?? undefined,
|
|
164
|
-
};
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
export default defineLogicFunction({
|
|
168
|
-
universalIdentifier:
|
|
169
|
-
CALENDAR_EVENT_RECONCILIATION_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
170
|
-
name: 'reconcile-call-recorder-calendar-event',
|
|
171
|
-
description:
|
|
172
|
-
'Reconciles app-managed Recall bot recording requests when calendar events change.',
|
|
173
|
-
timeoutSeconds: 60,
|
|
174
|
-
handler,
|
|
175
|
-
databaseEventTriggerSettings: {
|
|
176
|
-
eventName: `${CALENDAR_EVENT_OBJECT_NAME}.*`,
|
|
177
|
-
},
|
|
178
|
-
});
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
2
|
-
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
3
|
-
|
|
4
|
-
import { STALE_BOT_STATE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/stale-bot-state-logic-function-universal-identifier';
|
|
5
|
-
import { STALE_BOT_STATE_CRON_PATTERN } from 'src/logic-functions/constants/stale-bot-state-cron-pattern';
|
|
6
|
-
import { convergeDivergedCallRecordings } from 'src/logic-functions/flows/converge-diverged-call-recordings.util';
|
|
7
|
-
import { type ConvergeDivergedCallRecordingsResult } from 'src/logic-functions/flows/converge-diverged-call-recordings-result.type';
|
|
8
|
-
import {
|
|
9
|
-
healCallRecordingsMissingBot,
|
|
10
|
-
type HealCallRecordingsMissingBotResult,
|
|
11
|
-
} from 'src/logic-functions/flows/heal-call-recordings-missing-bot.util';
|
|
12
|
-
import {
|
|
13
|
-
reapOrphanedCallRecorders,
|
|
14
|
-
type ReapOrphanedCallRecordersResult,
|
|
15
|
-
} from 'src/logic-functions/flows/reap-orphaned-call-recorders.util';
|
|
16
|
-
|
|
17
|
-
// Every unwanted bot passes through this join_at window before it can attend.
|
|
18
|
-
const REAPER_JOIN_AT_LOOKBACK_HOURS = 4;
|
|
19
|
-
const REAPER_JOIN_AT_LOOKAHEAD_HOURS = 24;
|
|
20
|
-
|
|
21
|
-
type StepFailure = { error: string };
|
|
22
|
-
|
|
23
|
-
const reconcileStaleBotStateHandler = async (): Promise<object> => {
|
|
24
|
-
const now = new Date();
|
|
25
|
-
const client = new CoreApiClient();
|
|
26
|
-
|
|
27
|
-
const botlessHealResult = await healCallRecordingsMissingBotSafely(
|
|
28
|
-
client,
|
|
29
|
-
now,
|
|
30
|
-
);
|
|
31
|
-
const orphanedBotReapingResult =
|
|
32
|
-
await reapOrphanedCallRecordersInJoinAtWindow(client, now);
|
|
33
|
-
const statusConvergenceResult = await convergeDivergedCallRecordingsSafely(
|
|
34
|
-
client,
|
|
35
|
-
now,
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
botlessHealResult,
|
|
40
|
-
orphanedBotReapingResult,
|
|
41
|
-
statusConvergenceResult,
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const healCallRecordingsMissingBotSafely = async (
|
|
46
|
-
client: CoreApiClient,
|
|
47
|
-
now: Date,
|
|
48
|
-
): Promise<HealCallRecordingsMissingBotResult | StepFailure> => {
|
|
49
|
-
try {
|
|
50
|
-
return await healCallRecordingsMissingBot({ client, now });
|
|
51
|
-
} catch (error) {
|
|
52
|
-
return buildStepFailure('botless call recording healing', error);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const reapOrphanedCallRecordersInJoinAtWindow = async (
|
|
57
|
-
client: CoreApiClient,
|
|
58
|
-
now: Date,
|
|
59
|
-
): Promise<ReapOrphanedCallRecordersResult | StepFailure> => {
|
|
60
|
-
try {
|
|
61
|
-
return await reapOrphanedCallRecorders({
|
|
62
|
-
client,
|
|
63
|
-
joinAtAfter: new Date(
|
|
64
|
-
now.getTime() - REAPER_JOIN_AT_LOOKBACK_HOURS * 60 * 60 * 1000,
|
|
65
|
-
).toISOString(),
|
|
66
|
-
joinAtBefore: new Date(
|
|
67
|
-
now.getTime() + REAPER_JOIN_AT_LOOKAHEAD_HOURS * 60 * 60 * 1000,
|
|
68
|
-
).toISOString(),
|
|
69
|
-
});
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return buildStepFailure('orphaned bot reaping', error);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const convergeDivergedCallRecordingsSafely = async (
|
|
76
|
-
client: CoreApiClient,
|
|
77
|
-
now: Date,
|
|
78
|
-
): Promise<ConvergeDivergedCallRecordingsResult | StepFailure> => {
|
|
79
|
-
try {
|
|
80
|
-
return await convergeDivergedCallRecordings({ client, now });
|
|
81
|
-
} catch (error) {
|
|
82
|
-
return buildStepFailure('call recording status convergence', error);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const buildStepFailure = (stepLabel: string, error: unknown): StepFailure => {
|
|
87
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
88
|
-
|
|
89
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
90
|
-
console.error(`[call-recorder] ${stepLabel} failed: ${errorMessage}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { error: `${stepLabel} failed` };
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export default defineLogicFunction({
|
|
97
|
-
universalIdentifier: STALE_BOT_STATE_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
|
98
|
-
name: 'reconcile-stale-bot-state',
|
|
99
|
-
description:
|
|
100
|
-
'Converges call recordings with Recall on a schedule: pulls stale bot statuses and overdue transcripts, finishes failed cancellations, schedules bots for recordings still missing one, and reaps unclaimed bots. Reads calendar events only to heal already-decided recordings, never to discover meetings.',
|
|
101
|
-
timeoutSeconds: 250,
|
|
102
|
-
handler: reconcileStaleBotStateHandler,
|
|
103
|
-
cronTriggerSettings: {
|
|
104
|
-
pattern: STALE_BOT_STATE_CRON_PATTERN,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// Domain read shape: wire composites are flattened and absence is undefined.
|
|
2
|
-
export type CallRecorderPolicyCalendarEventInput = {
|
|
3
|
-
id: string;
|
|
4
|
-
isCanceled: boolean;
|
|
5
|
-
startsAt: string | undefined;
|
|
6
|
-
endsAt: string | undefined;
|
|
7
|
-
iCalUid: string | undefined;
|
|
8
|
-
conferenceLinkUrl: string | undefined;
|
|
9
|
-
callRecorderPreference: string | undefined;
|
|
10
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type CallRecorderPreference } from 'src/constants/call-recorder-preference';
|
|
2
|
-
|
|
3
|
-
export type CallRecorderPolicyInput = {
|
|
4
|
-
callRecorderPreference: CallRecorderPreference | undefined;
|
|
5
|
-
isCanceled: boolean;
|
|
6
|
-
startsAt: string | undefined;
|
|
7
|
-
endsAt: string | undefined;
|
|
8
|
-
conferenceLinkUrl: string | undefined;
|
|
9
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type CallRecorderPolicyRequiredReason = 'RECORDING_ENABLED';
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type CallRecorderPreference } from 'src/constants/call-recorder-preference';
|
|
2
|
-
import { type CallRecorderPolicyResult } from 'src/logic-functions/types/call-recorder-policy-result.type';
|
|
3
|
-
|
|
4
|
-
export type CallRecorderPolicyResultForCalendarEvent =
|
|
5
|
-
CallRecorderPolicyResult & {
|
|
6
|
-
calendarEventId: string;
|
|
7
|
-
callRecorderPreference: CallRecorderPreference | undefined;
|
|
8
|
-
realMeetingKey: string;
|
|
9
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type CallRecorderPolicyNotRequiredReason } from 'src/logic-functions/types/call-recorder-policy-not-required-reason.type';
|
|
2
|
-
import { type CallRecorderPolicyRequiredReason } from 'src/logic-functions/types/call-recorder-policy-required-reason.type';
|
|
3
|
-
|
|
4
|
-
export type CallRecorderPolicyResult =
|
|
5
|
-
| {
|
|
6
|
-
shouldRequestBot: true;
|
|
7
|
-
reason: CallRecorderPolicyRequiredReason;
|
|
8
|
-
}
|
|
9
|
-
| {
|
|
10
|
-
shouldRequestBot: false;
|
|
11
|
-
reason: CallRecorderPolicyNotRequiredReason;
|
|
12
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type CallRecorderReconciliationResult =
|
|
2
|
-
| {
|
|
3
|
-
action: 'CREATED' | 'UPDATED' | 'CANCELED';
|
|
4
|
-
realMeetingKey: string;
|
|
5
|
-
callRecordingId: string;
|
|
6
|
-
}
|
|
7
|
-
| {
|
|
8
|
-
action: 'SKIPPED';
|
|
9
|
-
realMeetingKey: string;
|
|
10
|
-
callRecordingId: string | null;
|
|
11
|
-
}
|
|
12
|
-
| {
|
|
13
|
-
action: 'FAILED';
|
|
14
|
-
realMeetingKey: string;
|
|
15
|
-
errorMessage: string;
|
|
16
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type CallRecordingMediaFile = { fileId: string; label: string };
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status';
|
|
2
|
-
|
|
3
|
-
// Domain read shape: absence is always undefined; null lives only on wire types.
|
|
4
|
-
export type CallRecordingRecord = {
|
|
5
|
-
id: string;
|
|
6
|
-
title?: string;
|
|
7
|
-
status?: string;
|
|
8
|
-
recordingRequestStatus?: CallRecordingRequestStatus;
|
|
9
|
-
startedAt?: string;
|
|
10
|
-
endedAt?: string;
|
|
11
|
-
calendarEventId?: string;
|
|
12
|
-
externalBotId?: string;
|
|
13
|
-
externalRecordingId?: string;
|
|
14
|
-
callRecorderFailureReason?: string;
|
|
15
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type CallRecordingRequestStatus } from 'src/logic-functions/constants/call-recording-request-status';
|
|
2
|
-
import { type CallRecordingStatus } from 'src/logic-functions/constants/call-recording-status';
|
|
3
|
-
import { type CallRecordingMediaFile } from 'src/logic-functions/types/call-recording-media-file.type';
|
|
4
|
-
|
|
5
|
-
export type CallRecordingUpdateFields = Partial<{
|
|
6
|
-
// null clears a previously synced title when the calendar title disappears.
|
|
7
|
-
title: string | null;
|
|
8
|
-
status: CallRecordingStatus;
|
|
9
|
-
recordingRequestStatus: CallRecordingRequestStatus;
|
|
10
|
-
startedAt: string;
|
|
11
|
-
endedAt: string;
|
|
12
|
-
calendarEventId: string;
|
|
13
|
-
// null clears stale app-owned state on cancel/eject or reschedule.
|
|
14
|
-
externalBotId: string | null;
|
|
15
|
-
externalRecordingId: string;
|
|
16
|
-
callRecorderFailureReason: string | null;
|
|
17
|
-
transcript: Record<string, unknown>;
|
|
18
|
-
audio: CallRecordingMediaFile[];
|
|
19
|
-
video: CallRecordingMediaFile[];
|
|
20
|
-
}>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type FilesFieldValue = { fileId: string }[];
|