@arbidocs/react 0.5.83 → 0.5.85
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/README.md +56 -0
- package/dist/artifacts/index.cjs +9 -9
- package/dist/artifacts/index.js +3 -3
- package/dist/calls/livekit/index.cjs +48 -48
- package/dist/calls/livekit/index.js +3 -3
- package/dist/{chunk-FEPBB7VJ.cjs → chunk-2Y2WOIHH.cjs} +31 -31
- package/dist/{chunk-FEPBB7VJ.cjs.map → chunk-2Y2WOIHH.cjs.map} +1 -1
- package/dist/{chunk-RZWWSAQG.js → chunk-47YVW3J4.js} +3 -3
- package/dist/{chunk-RZWWSAQG.js.map → chunk-47YVW3J4.js.map} +1 -1
- package/dist/{chunk-VH234EOF.cjs → chunk-E7VD5WNY.cjs} +25 -25
- package/dist/{chunk-VH234EOF.cjs.map → chunk-E7VD5WNY.cjs.map} +1 -1
- package/dist/{chunk-B3C25GWG.js → chunk-JT7PN7JH.js} +4 -4
- package/dist/{chunk-B3C25GWG.js.map → chunk-JT7PN7JH.js.map} +1 -1
- package/dist/{chunk-JLJLPC5X.cjs → chunk-LUDIYWT7.cjs} +22 -22
- package/dist/{chunk-JLJLPC5X.cjs.map → chunk-LUDIYWT7.cjs.map} +1 -1
- package/dist/{chunk-FOB5QVGI.cjs → chunk-Q6U7ALNV.cjs} +16 -16
- package/dist/{chunk-FOB5QVGI.cjs.map → chunk-Q6U7ALNV.cjs.map} +1 -1
- package/dist/{chunk-UKLLE4FC.cjs → chunk-TAVS54EN.cjs} +4 -4
- package/dist/{chunk-UKLLE4FC.cjs.map → chunk-TAVS54EN.cjs.map} +1 -1
- package/dist/{chunk-26WAJNXN.js → chunk-VGAA7AFS.js} +3 -3
- package/dist/{chunk-26WAJNXN.js.map → chunk-VGAA7AFS.js.map} +1 -1
- package/dist/{chunk-AEG5ZMKH.cjs → chunk-VGMMK5OM.cjs} +2 -2
- package/dist/chunk-VGMMK5OM.cjs.map +1 -0
- package/dist/{chunk-EJIO2KAF.js → chunk-VGO77X5U.js} +2 -2
- package/dist/chunk-VGO77X5U.js.map +1 -0
- package/dist/{chunk-BB32GHDD.js → chunk-WQY3RQ6G.js} +4 -4
- package/dist/{chunk-BB32GHDD.js.map → chunk-WQY3RQ6G.js.map} +1 -1
- package/dist/{chunk-7R4SVVQE.js → chunk-XPLD4LFC.js} +3 -3
- package/dist/{chunk-7R4SVVQE.js.map → chunk-XPLD4LFC.js.map} +1 -1
- package/dist/document-tools/core.cjs +99 -99
- package/dist/document-tools/core.js +3 -3
- package/dist/document-tools/index.cjs +101 -101
- package/dist/document-tools/index.js +4 -4
- package/dist/index.cjs +2666 -1078
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +327 -28
- package/dist/index.d.ts +327 -28
- package/dist/index.js +1804 -242
- package/dist/index.js.map +1 -1
- package/dist/viewers/index.cjs +2 -2
- package/dist/viewers/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-AEG5ZMKH.cjs.map +0 -1
- package/dist/chunk-EJIO2KAF.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { AudioViewer, ImageViewer, VideoViewer } from './chunk-HDCRPPBW.js';
|
|
2
|
-
import { useUserSettings, invalidateConversationLists, patchConversation, invalidateConversationDetails, removeConversationFromLists, addConversationToList, findConversation, isConversationShared, patchConversationList, useFeature, useAccountClient, useUpdateUserSettings, myChatId, upsertChat, appendToThread, dropFromThread, deleteChatMessage, removeChat, getOpenChat, cachedChat, applyChatEvent, applyCallEvent } from './chunk-
|
|
3
|
-
export { AUDIENCE_CHOICES, CALL_CAPTION_TOPIC, CALL_CHAT_MAX_CHARS, CALL_CHAT_TOPIC, CALL_MODERATION_TOPIC, CALL_RECORDING_TOPIC, CALL_TRANSCRIPTION_TOPIC, CALL_WEBINAR_TOPIC, CAPTION_MAX_CHARS, COMPOSITE_AUDIO_BITS_PER_SECOND, COMPOSITE_VIDEO_BITS_PER_SECOND, DEFAULT_JOIN_CHOICES, EMPTY_CALL_SESSION, EXPORT_SAMPLE_RATE, HISTORY_LIMIT, LIVE_SEGMENTER, MAX_AVATAR_FILE_BYTES, MUTED_DEDUPE_MS, PLAYBACK_LOOKAHEAD_MS, READ_ONLY_MEETING_TEXT, RECORDING_CHUNK_MS, RECORDING_SEGMENTER, SEGMENT_FRAME_MS, SEGMENT_ID_PATTERN, SEGMENT_PRE_ROLL_MS, SHORT_PIECE_MERGE_GAP_MS, SPEAKER_AUDIO_BITS_PER_SECOND, TrackUploader, UPLOAD_ATTEMPTS, UPLOAD_BACKOFF_MS, acceptRing, addConversationToList, appendToThread, applyAgentDmStatus, applyCallEvent, applyChatEvent, applyChatMessage, applyChatRead, applyChatUpdated, arbiAccountClient, buildMeetingChatBody, cachedChat, cachedChats, callLink, callMemberKeys, callSession, chatByExtId, chatHasLiveCall, chatLiveCallId, chatTranscriptRef, clearMeetingChatCache, clearTrackUploaders, clockTime, compositeFiles, compositeTrack, conversationKeyForCall, createLocalFeatureStore, createMutedNotifier, createTrackCutter, cutAtPauses, declineRing, decodeCallChatPacket, decodeCaptionPacket, decodeMono16k, decryptPart, encodeCallChatPacket, encodeCaptionPacket, encodeWav, encryptPart, ensureMeetingChat, entitlementRefusal, extensionFor, fetchPart, fileSafe, findConversation, floatToWav, formatTranscriptMarkdown, getOpenChat, hasRecordingTranscript, invalidateCall, invalidateConversationDetails, invalidateConversationLists, isConversationListKey, isConversationShared, isRecordingOver, isRecordingSegmentId, isRetryable, isWorkspaceGuestOf, joinFromPreJoin, knownMeetingChat, loadMeetingHistory, markRejoining, meetingChatRefFrom, meetingRefForCall, mergeChatLines, mutedNoticeFromEvent, mutedNoticeFromPacket, mutedNoticeText, myChatId, newPacketId, newSegmentId, newTrackKey, nextSeqAfter, onCallEvent, openCall, openQuestion, openRecording, parseWebinarPacket, partKey, partOffsetMs, partsDue, patchConversation, patchConversationList, persistMeetingMessage, persistTranscriptSegment, playheadAt, postRecordingDocsToThread, postTranscriptToThread, readLocalFeature, recordingFiles, recordingWindowsOf, rejoinAsAudience, removeChat, removeConversationFromLists, resetCallSession, resolveMeetingChat, saveRecordingFilesToThread, schedulePart, sealQuestionText, serverCallsExperimental, setOpenChat, sortTranscriptMessages, sortedParts, speakerTracks, speakerWav, splitIntoFiles, standalone, startChatCall, startContactsCall, startDmCall, startThreadCall, startsMediaFile, storeTranscriptSegment, stripSpeakerPrefix, supersedeLiveTranscript, supersededTranscriptLines, threadHasLiveCall, threadLiveCallId, threadTranscriptLines, threadTranscriptRef, threadTranscriptText, timelineDurationMs, timelineOf, toProfileView, trackUploaderFor, transcribeRecording, transcriptPersistRole, transcriptSegmentOf, unwrapTrackKey, upsertChat, useAccountClient, useAvatarCrop, useCall, useCallInviteLinks, useCallRecording, useCallRecordings, useCallSession, useCallTranscriptionAvailable, useCallsConfig, useCallsEnabled, useCanHostCalls, useEntitlements, useFeature, useFeatureSwitch, useInvalidateEntitlements, useIsEntitled, useMeetingReadOnly, useNameEditor, useOpenCallMeetingChat, useProfile, useRecordingActions, useRecordingWindows, useRevokeCallInviteLink, useSaveToThread, useServerCallsEnabled, useServerCallsExperimental, useUpdateProfile, useUpdateUserSettings, useUserSettings, wrapTrackKey, writeSamples } from './chunk-
|
|
4
|
-
import { replayableTagMutations, inWorkspace, primeOnClient, patchDocuments, setCachedDocument, adjustTagCounts, patchCachedDocument, FINISHED_DOCUMENT_STATUSES, refreshDocuments, getCachedDocuments, useArbiWsStore, IN_FLIGHT_DOCUMENT_STATUSES, useCapturingWorkspace, QUEUED_WRITE_RETRY, isWorkspaceUnavailable, primeOnArbi, forgetDocuments, useDocument, useSimilarDocuments, SIMILARITY_THRESHOLD, QUEUED_WRITE_BLOCKED_MESSAGE, isBlockedOnWorkspace, useDocuments, finishEntityExtraction } from './chunk-
|
|
5
|
-
export { DISMISSED_TASK_TTL_MS, DOCUMENTS_FIRST_PAGE_MEASURE, DOCUMENTS_FIRST_PAGE_SIZE, DOCUMENTS_PAGE_SIZE, FINISHED_DOCUMENT_STATUSES, IN_FLIGHT_DOCUMENT_STATUSES, SIMILARITY_THRESHOLD, adjustTagCounts, dropTagFromCachedDocuments, finishEntityExtraction, forgetDocuments, getCachedDocuments, invalidateTagData, invalidateTagTables, isDocumentsLoading, isRowNewer, isStaleTaskUpdate, patchCachedDocument, patchDocuments, patchWorkspaceTags, refreshDocuments, resolveDocumentStatus, setCachedDocument, settledByRows, startEntityExtraction, stopDocumentsLoading, subscribeDocumentRows, upsertDocuments, useArbiWsStore, useCreateTag, useDeleteTag, useDocument, useDocuments, useDocumentsById, useDocumentsLoading, useEntityExtractionDocIds, useExtractEntities, useInvalidateTagData, useIsCreatingTag, useRefreshDocuments, useSetTagValues, useSimilarDocuments, useTagKindSchema, useUpdateTag, useUpdateTagKindSchema } from './chunk-
|
|
6
|
-
import { useArbiSessionState, useArbiSession, useWorkspaces, useConversations, useConfig, useArbiSessionStore, useAvailableModels, useParsedDocument, useTags, selectDraft, useConversationThreads, createArbiSessionStore, useTransientView, useProjects, useAgents, useAssistantQuery, useActivity, queuedMessages, MIN_QUEUED_MESSAGE_VISIBILITY_MS } from './chunk-
|
|
7
|
-
export { DEFAULT_DOCUMENT_VIEWER_STATE, DOCUMENT_VIEWER_PANEL, EMPTY_CHAT_STATE, EMPTY_DOCUMENT_STATE, EMPTY_SESSION_STATE, MAX_THREAD_TABS, MIN_QUEUED_MESSAGE_VISIBILITY_MS, PROJECT_CREATION_DENIED_REASON, WORKSPACE_LAST_OPENED_KEY, WORKSPACE_STORAGE_KEY, agentControl, browserWorkspaceStorage, clearSealedDrafts, closePanelIn, composeArbiSessionStore, conversationKey, createArbiChatStore, createArbiDocumentSessionStore, createArbiLayoutStore, createArbiSessionStore, createArbiWorkspaceStore, createDebouncedWriter, createSealedDraftStorage, createStoragePersist, createUiStateStorage, defaultLayoutState, dmTabId, flushOnPageExit, isPanelOpen, isUsageMetered, layoutActionsOf, nextViewingDocument, openPanelIn, panelPlace, parseSavedChat, parseSavedDocuments, parseSavedLayout, queuedMessages, readWorkspaceLastOpened, restoreLayout, restoreSavedDocuments, savedChatOf, savedDocumentsOf, savedLayoutOf, selectDraft, selectIsGenerating, selectViewingDocumentId, threadTabId, useActivity, useAgents, useArbiSession, useArbiSessionState, useArbiSessionStore, useAssistantQuery, useAvailableModels, useBoxRole, useBuiltinCommands, useCanCreateProjects, useConfig, useConfigs, useConversationThreads, useConversations, useCurrentUser, useDeleteAgents, useDocumentVersionList, useFirmPool, useHealth, useMessageDetails, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessionPageTab, useSessionView, useSignedInSession, useTags, useThumbnails, useTopUpFirmPool, useTransientView, useUserUsageToday, useWorkspaceActivity, useWorkspaces } from './chunk-
|
|
8
|
-
import { fetchDocumentFile, downloadBlob, fetchMarkupPdf, fetchDocumentPdf } from './chunk-
|
|
9
|
-
import { arbiMutationKeys, arbiQueryKeys, mailQueryKeys, connectorQueryKeys, calendarQueryKeys } from './chunk-TK7ERJPE.js';
|
|
2
|
+
import { useUserSettings, invalidateConversationLists, patchConversation, invalidateConversationDetails, removeConversationFromLists, addConversationToList, findConversation, isConversationShared, patchConversationList, useFeature, useAccountClient, useUpdateUserSettings, useIsEntitled, myChatId, upsertChat, appendToThread, dropFromThread, deleteChatMessage, removeChat, getOpenChat, cachedChat, applyChatEvent, applyCallEvent } from './chunk-WQY3RQ6G.js';
|
|
3
|
+
export { AUDIENCE_CHOICES, CALL_CAPTION_TOPIC, CALL_CHAT_MAX_CHARS, CALL_CHAT_TOPIC, CALL_MODERATION_TOPIC, CALL_RECORDING_TOPIC, CALL_TRANSCRIPTION_TOPIC, CALL_WEBINAR_TOPIC, CAPTION_MAX_CHARS, COMPOSITE_AUDIO_BITS_PER_SECOND, COMPOSITE_VIDEO_BITS_PER_SECOND, DEFAULT_JOIN_CHOICES, EMPTY_CALL_SESSION, EXPORT_SAMPLE_RATE, HISTORY_LIMIT, LIVE_SEGMENTER, MAX_AVATAR_FILE_BYTES, MUTED_DEDUPE_MS, PLAYBACK_LOOKAHEAD_MS, READ_ONLY_MEETING_TEXT, RECORDING_CHUNK_MS, RECORDING_SEGMENTER, SEGMENT_FRAME_MS, SEGMENT_ID_PATTERN, SEGMENT_PRE_ROLL_MS, SHORT_PIECE_MERGE_GAP_MS, SPEAKER_AUDIO_BITS_PER_SECOND, TrackUploader, UPLOAD_ATTEMPTS, UPLOAD_BACKOFF_MS, acceptRing, addConversationToList, appendToThread, applyAgentDmStatus, applyCallEvent, applyChatEvent, applyChatMessage, applyChatRead, applyChatUpdated, arbiAccountClient, buildMeetingChatBody, cachedChat, cachedChats, callLink, callMemberKeys, callSession, chatByExtId, chatHasLiveCall, chatLiveCallId, chatTranscriptRef, clearMeetingChatCache, clearTrackUploaders, clockTime, compositeFiles, compositeTrack, conversationKeyForCall, createLocalFeatureStore, createMutedNotifier, createTrackCutter, cutAtPauses, declineRing, decodeCallChatPacket, decodeCaptionPacket, decodeMono16k, decryptPart, encodeCallChatPacket, encodeCaptionPacket, encodeWav, encryptPart, ensureMeetingChat, entitlementRefusal, extensionFor, fetchPart, fileSafe, findConversation, floatToWav, formatTranscriptMarkdown, getOpenChat, hasRecordingTranscript, invalidateCall, invalidateConversationDetails, invalidateConversationLists, isConversationListKey, isConversationShared, isRecordingOver, isRecordingSegmentId, isRetryable, isWorkspaceGuestOf, joinFromPreJoin, knownMeetingChat, loadMeetingHistory, markRejoining, meetingChatRefFrom, meetingRefForCall, mergeChatLines, mutedNoticeFromEvent, mutedNoticeFromPacket, mutedNoticeText, myChatId, newPacketId, newSegmentId, newTrackKey, nextSeqAfter, onCallEvent, openCall, openQuestion, openRecording, parseWebinarPacket, partKey, partOffsetMs, partsDue, patchConversation, patchConversationList, persistMeetingMessage, persistTranscriptSegment, playheadAt, postRecordingDocsToThread, postTranscriptToThread, readLocalFeature, recordingFiles, recordingWindowsOf, rejoinAsAudience, removeChat, removeConversationFromLists, resetCallSession, resolveMeetingChat, saveRecordingFilesToThread, schedulePart, sealQuestionText, serverCallsExperimental, setOpenChat, sortTranscriptMessages, sortedParts, speakerTracks, speakerWav, splitIntoFiles, standalone, startChatCall, startContactsCall, startDmCall, startThreadCall, startsMediaFile, storeTranscriptSegment, stripSpeakerPrefix, supersedeLiveTranscript, supersededTranscriptLines, threadHasLiveCall, threadLiveCallId, threadTranscriptLines, threadTranscriptRef, threadTranscriptText, timelineDurationMs, timelineOf, toProfileView, trackUploaderFor, transcribeRecording, transcriptPersistRole, transcriptSegmentOf, unwrapTrackKey, upsertChat, useAccountClient, useAvatarCrop, useCall, useCallInviteLinks, useCallRecording, useCallRecordings, useCallSession, useCallTranscriptionAvailable, useCallsConfig, useCallsEnabled, useCanHostCalls, useEntitlements, useFeature, useFeatureSwitch, useInvalidateEntitlements, useIsEntitled, useMeetingReadOnly, useNameEditor, useOpenCallMeetingChat, useProfile, useRecordingActions, useRecordingWindows, useRevokeCallInviteLink, useSaveToThread, useServerCallsEnabled, useServerCallsExperimental, useUpdateProfile, useUpdateUserSettings, useUserSettings, wrapTrackKey, writeSamples } from './chunk-WQY3RQ6G.js';
|
|
4
|
+
import { replayableTagMutations, inWorkspace, primeOnClient, patchDocuments, setCachedDocument, adjustTagCounts, patchCachedDocument, FINISHED_DOCUMENT_STATUSES, refreshDocuments, getCachedDocuments, useArbiWsStore, IN_FLIGHT_DOCUMENT_STATUSES, useCapturingWorkspace, QUEUED_WRITE_RETRY, isWorkspaceUnavailable, primeOnArbi, forgetDocuments, useDocument, useSimilarDocuments, SIMILARITY_THRESHOLD, QUEUED_WRITE_BLOCKED_MESSAGE, isBlockedOnWorkspace, useDocuments, finishEntityExtraction } from './chunk-VGAA7AFS.js';
|
|
5
|
+
export { DISMISSED_TASK_TTL_MS, DOCUMENTS_FIRST_PAGE_MEASURE, DOCUMENTS_FIRST_PAGE_SIZE, DOCUMENTS_PAGE_SIZE, FINISHED_DOCUMENT_STATUSES, IN_FLIGHT_DOCUMENT_STATUSES, SIMILARITY_THRESHOLD, adjustTagCounts, dropTagFromCachedDocuments, finishEntityExtraction, forgetDocuments, getCachedDocuments, invalidateTagData, invalidateTagTables, isDocumentsLoading, isRowNewer, isStaleTaskUpdate, patchCachedDocument, patchDocuments, patchWorkspaceTags, refreshDocuments, resolveDocumentStatus, setCachedDocument, settledByRows, startEntityExtraction, stopDocumentsLoading, subscribeDocumentRows, upsertDocuments, useArbiWsStore, useCreateTag, useDeleteTag, useDocument, useDocuments, useDocumentsById, useDocumentsLoading, useEntityExtractionDocIds, useExtractEntities, useInvalidateTagData, useIsCreatingTag, useRefreshDocuments, useSetTagValues, useSimilarDocuments, useTagKindSchema, useUpdateTag, useUpdateTagKindSchema } from './chunk-VGAA7AFS.js';
|
|
6
|
+
import { useArbiSessionState, useArbiSession, useWorkspaces, useConversations, useConfig, useArbiSessionStore, useAvailableModels, useParsedDocument, useTags, selectDraft, useConversationThreads, createArbiSessionStore, useTransientView, useProjects, useAgents, useAssistantQuery, useActivity, queuedMessages, MIN_QUEUED_MESSAGE_VISIBILITY_MS } from './chunk-XPLD4LFC.js';
|
|
7
|
+
export { DEFAULT_DOCUMENT_VIEWER_STATE, DOCUMENT_VIEWER_PANEL, EMPTY_CHAT_STATE, EMPTY_DOCUMENT_STATE, EMPTY_SESSION_STATE, MAX_THREAD_TABS, MIN_QUEUED_MESSAGE_VISIBILITY_MS, PROJECT_CREATION_DENIED_REASON, WORKSPACE_LAST_OPENED_KEY, WORKSPACE_STORAGE_KEY, agentControl, browserWorkspaceStorage, clearSealedDrafts, closePanelIn, composeArbiSessionStore, conversationKey, createArbiChatStore, createArbiDocumentSessionStore, createArbiLayoutStore, createArbiSessionStore, createArbiWorkspaceStore, createDebouncedWriter, createSealedDraftStorage, createStoragePersist, createUiStateStorage, defaultLayoutState, dmTabId, flushOnPageExit, isPanelOpen, isUsageMetered, layoutActionsOf, nextViewingDocument, openPanelIn, panelPlace, parseSavedChat, parseSavedDocuments, parseSavedLayout, queuedMessages, readWorkspaceLastOpened, restoreLayout, restoreSavedDocuments, savedChatOf, savedDocumentsOf, savedLayoutOf, selectDraft, selectIsGenerating, selectViewingDocumentId, threadTabId, useActivity, useAgents, useArbiSession, useArbiSessionState, useArbiSessionStore, useAssistantQuery, useAvailableModels, useBoxRole, useBuiltinCommands, useCanCreateProjects, useConfig, useConfigs, useConversationThreads, useConversations, useCurrentUser, useDeleteAgents, useDocumentVersionList, useFirmPool, useHealth, useMessageDetails, useParsedDocument, useProjectInvoices, useProjectUsage, useProjects, useProjectsUsage, useSessionPageTab, useSessionView, useSignedInSession, useTags, useThumbnails, useTopUpFirmPool, useTransientView, useUserUsageToday, useWorkspaceActivity, useWorkspaces } from './chunk-XPLD4LFC.js';
|
|
8
|
+
import { fetchDocumentFile, downloadBlob, fetchMarkupPdf, fetchDocumentPdf } from './chunk-47YVW3J4.js';
|
|
9
|
+
import { arbiMutationKeys, arbiQueryKeys, mailQueryKeys, isUnderQueryKey, connectorQueryKeys, calendarQueryKeys } from './chunk-TK7ERJPE.js';
|
|
10
10
|
export { arbiMutationKeys, arbiQueryKeys, calendarQueryKeys, callQueryKeys, connectorQueryKeys, isUnderQueryKey, mailQueryKeys } from './chunk-TK7ERJPE.js';
|
|
11
|
-
import { useArbi, useArbiHost } from './chunk-
|
|
12
|
-
export { useArbi, useArbiHost, useDeploymentDomain } from './chunk-
|
|
11
|
+
import { useArbi, useArbiHost } from './chunk-VGO77X5U.js';
|
|
12
|
+
export { useArbi, useArbiHost, useDeploymentDomain } from './chunk-VGO77X5U.js';
|
|
13
13
|
import { ArbiHost, linkMentions, extractMentions, UNBOUND, bindingOfRow, readLegacyThreadDocuments, forgetLegacyThreadDocuments, toMessageMentions, takeDocumentForTurn, hasMessageBody, createSearchSession, clearSearchSession } from './chunk-CSPAEVZS.js';
|
|
14
14
|
export { LEGACY_THREAD_DOCUMENTS_KEY, MENTION_HREF_PREFIX, NotificationBell, Toaster, UNBOUND, bindingOfRow, clearSearchSession, createSearchSession, extractMentions, filterMentionCandidates, findMentionQuery, forgetLegacyThreadDocuments, hasMessageBody, insertMention, latestThreadOfDocument, linkMentions, mentionFromHref, openDocumentId, opensInWord, persistSearchSession, readLegacyThreadDocuments, requestSearch, savedDocumentIdFor, sharedSearchSession, takeDocumentForTurn, takePendingSearch, toMessageMentions, useOpenDocumentId, useRegisterOpenDocument, useTurnDocumentId } from './chunk-CSPAEVZS.js';
|
|
15
15
|
import { notify, recordChatArtifact } from './chunk-G4Z3IP4J.js';
|
|
@@ -41,7 +41,7 @@ export { ACCOUNT_VENDOR, Arbi, ArbiError, DEFAULT_AI_USD_PER_GBP, DRIVE_SYNC_VEN
|
|
|
41
41
|
import { Link, Trash2, CheckCheck, Check, MoreHorizontal, SquarePen, Pencil, Download, X, Mic, Pause, Play, Square, Loader2, RotateCcw, Sparkles, Camera, Circle, AlertTriangle, ShieldCheck, Copy, FileText, GitCompareArrows, Search, CornerDownLeft, Send, Tags, Info, Activity, HardDrive, Gauge, Gift, PartyPopper, UserPlus, UserCheck, Reply, PenLine, AtSign, MessageCircleMore, FileX2, FileCheck2, FolderPlus, SquareArrowOutUpRight, Paperclip, ArrowRight, Image as Image$1, AppWindow } from 'lucide-react';
|
|
42
42
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
43
43
|
import { create } from 'zustand';
|
|
44
|
-
import { getUserEncryptionKeypair, sealedBoxDecrypt, bytesToBase64, isMessageType, decryptMessage, SETTINGS_WRITE_BREAKER_CODE, passwordChangeErrorMessage, createRecoveryFile, isAbortError, base64ToBytes, sealedBoxEncrypt } from '@arbidocs/client';
|
|
44
|
+
import { createOfflineSealer, getUserEncryptionKeypair, sealedBoxDecrypt, bytesToBase64, isMessageType, decryptMessage, SETTINGS_WRITE_BREAKER_CODE, passwordChangeErrorMessage, createRecoveryFile, logger, isAbortError, base64ToBytes, sealedBoxEncrypt } from '@arbidocs/client';
|
|
45
45
|
export { isMessageType } from '@arbidocs/client';
|
|
46
46
|
import { get, del, set } from 'idb-keyval';
|
|
47
47
|
import { diffWords } from 'diff';
|
|
@@ -162,7 +162,7 @@ function useStartTaskQuery() {
|
|
|
162
162
|
const arbi = useArbi();
|
|
163
163
|
const queryClient = useQueryClient();
|
|
164
164
|
const chatModel = useArbiSessionState((s) => s.chatModel);
|
|
165
|
-
const
|
|
165
|
+
const post = useMutation({
|
|
166
166
|
mutationFn: (query) => {
|
|
167
167
|
const selectedConfig = chatModel || queryClient.getQueriesData({ queryKey: arbiQueryKeys.userSettings() }).map(([, settings]) => settings?.last_config).find(Boolean);
|
|
168
168
|
return arbi.assistant.submit(query.input, {
|
|
@@ -185,8 +185,8 @@ function useStartTaskQuery() {
|
|
|
185
185
|
};
|
|
186
186
|
const lookUpThread = async (messageId, attempts, known) => {
|
|
187
187
|
let conversationId;
|
|
188
|
-
for (let
|
|
189
|
-
if (
|
|
188
|
+
for (let attempt2 = 0; attempt2 < attempts && !conversationId; attempt2 += 1) {
|
|
189
|
+
if (attempt2 > 0) await new Promise((r) => setTimeout(r, THREAD_LOOKUP_DELAY_MS));
|
|
190
190
|
try {
|
|
191
191
|
conversationId = await threadOf(messageId, known);
|
|
192
192
|
} catch (error) {
|
|
@@ -199,11 +199,11 @@ function useStartTaskQuery() {
|
|
|
199
199
|
return { conversationId };
|
|
200
200
|
};
|
|
201
201
|
return {
|
|
202
|
-
isPending:
|
|
202
|
+
isPending: post.isPending,
|
|
203
203
|
/** Look again for the thread of a run whose start threw {@link TaskThreadPendingError}. */
|
|
204
204
|
findThread: (messageId) => lookUpThread(messageId, 1),
|
|
205
205
|
start: async (query) => {
|
|
206
|
-
const accepted = await
|
|
206
|
+
const accepted = await post.mutateAsync(query);
|
|
207
207
|
const found = await lookUpThread(
|
|
208
208
|
accepted.id,
|
|
209
209
|
THREAD_LOOKUP_ATTEMPTS,
|
|
@@ -1806,9 +1806,9 @@ function useWorkspaceProcessing(workspaceId) {
|
|
|
1806
1806
|
}, []);
|
|
1807
1807
|
useEffect(() => {
|
|
1808
1808
|
if (!workspaceId) return;
|
|
1809
|
-
const
|
|
1809
|
+
const store3 = useArbiWsStore.getState();
|
|
1810
1810
|
const documents2 = getCachedDocuments(queryClient, workspaceId) ?? [];
|
|
1811
|
-
|
|
1811
|
+
store3.settleTasks(documents2);
|
|
1812
1812
|
const listed = new Set(documents2.map((d) => d.external_id));
|
|
1813
1813
|
const now = Date.now();
|
|
1814
1814
|
for (const task of Object.values(useArbiWsStore.getState().tasks)) {
|
|
@@ -1818,7 +1818,7 @@ function useWorkspaceProcessing(workspaceId) {
|
|
|
1818
1818
|
const age = since == null ? null : now - since;
|
|
1819
1819
|
const stale = age != null && age > STALE_TASK_MS;
|
|
1820
1820
|
const orphan = documents2.length > 0 && !listed.has(task.doc_ext_id) && age != null && age > ORPHAN_TASK_MS;
|
|
1821
|
-
if (stale || orphan)
|
|
1821
|
+
if (stale || orphan) store3.clearTask(task.doc_ext_id);
|
|
1822
1822
|
}
|
|
1823
1823
|
}, [workspaceId, queryClient]);
|
|
1824
1824
|
useEffect(() => {
|
|
@@ -1863,25 +1863,25 @@ function useWorkspaceProcessing(workspaceId) {
|
|
|
1863
1863
|
}, [workspaceId, snapshot]);
|
|
1864
1864
|
}
|
|
1865
1865
|
function applyOptimisticDocumentEdit(qc, updates) {
|
|
1866
|
-
const byId = new Map(updates.map((
|
|
1866
|
+
const byId = new Map(updates.map((update2) => [update2.external_id, update2]));
|
|
1867
1867
|
const previous = [];
|
|
1868
1868
|
patchDocuments(qc, (old) => {
|
|
1869
1869
|
let changed = false;
|
|
1870
1870
|
const next = old.map((doc) => {
|
|
1871
|
-
const
|
|
1872
|
-
if (!
|
|
1871
|
+
const update2 = byId.get(doc.external_id);
|
|
1872
|
+
if (!update2) return doc;
|
|
1873
1873
|
changed = true;
|
|
1874
1874
|
previous.push(doc);
|
|
1875
|
-
return { ...doc, ...
|
|
1875
|
+
return { ...doc, ...update2 };
|
|
1876
1876
|
});
|
|
1877
1877
|
return changed ? next : old;
|
|
1878
1878
|
});
|
|
1879
|
-
const previousOwn = updates.map((
|
|
1879
|
+
const previousOwn = updates.map((update2) => {
|
|
1880
1880
|
const own = qc.getQueryData(
|
|
1881
|
-
arbiQueryKeys.document(
|
|
1881
|
+
arbiQueryKeys.document(update2.external_id)
|
|
1882
1882
|
);
|
|
1883
|
-
if (own) setCachedDocument(qc, { ...own, ...
|
|
1884
|
-
return [
|
|
1883
|
+
if (own) setCachedDocument(qc, { ...own, ...update2 });
|
|
1884
|
+
return [update2.external_id, own];
|
|
1885
1885
|
});
|
|
1886
1886
|
return { optimistic: true, previous, previousOwn };
|
|
1887
1887
|
}
|
|
@@ -2211,11 +2211,11 @@ function useUpdateDocTag() {
|
|
|
2211
2211
|
);
|
|
2212
2212
|
}
|
|
2213
2213
|
function queueProcessing(documents2) {
|
|
2214
|
-
const
|
|
2215
|
-
const previous = documents2.map(({ docId }) => [docId,
|
|
2214
|
+
const store3 = useArbiWsStore.getState();
|
|
2215
|
+
const previous = documents2.map(({ docId }) => [docId, store3.tasks[docId]]);
|
|
2216
2216
|
const now = Date.now();
|
|
2217
2217
|
for (const doc of documents2)
|
|
2218
|
-
|
|
2218
|
+
store3.putTask({
|
|
2219
2219
|
doc_ext_id: doc.docId,
|
|
2220
2220
|
workspace_ext_id: doc.workspaceId,
|
|
2221
2221
|
file_name: doc.fileName,
|
|
@@ -3992,14 +3992,14 @@ function watchPendingDocIds(queryClient) {
|
|
|
3992
3992
|
if (watched.has(queryClient)) return;
|
|
3993
3993
|
watched.add(queryClient);
|
|
3994
3994
|
const cache = queryClient.getMutationCache();
|
|
3995
|
-
const
|
|
3995
|
+
const update2 = () => {
|
|
3996
3996
|
const ids = new Set(
|
|
3997
3997
|
cache.findAll({ predicate: PENDING_SYNC_PREDICATE }).flatMap((m) => docIdsFromVariables(kindOf(m.options.mutationKey), m.state.variables))
|
|
3998
3998
|
);
|
|
3999
3999
|
if (!sameSet(usePendingDocIds.getState().ids, ids)) usePendingDocIds.setState({ ids });
|
|
4000
4000
|
};
|
|
4001
|
-
|
|
4002
|
-
cache.subscribe(
|
|
4001
|
+
update2();
|
|
4002
|
+
cache.subscribe(update2);
|
|
4003
4003
|
}
|
|
4004
4004
|
function useDocumentPendingSync(docId) {
|
|
4005
4005
|
const queryClient = useQueryClient();
|
|
@@ -4038,23 +4038,23 @@ function promisify(request) {
|
|
|
4038
4038
|
request.onerror = () => reject(request.error);
|
|
4039
4039
|
});
|
|
4040
4040
|
}
|
|
4041
|
-
async function withStore(mode,
|
|
4041
|
+
async function withStore(mode, run2) {
|
|
4042
4042
|
const db = await openDB();
|
|
4043
|
-
const
|
|
4044
|
-
return promisify(
|
|
4043
|
+
const store3 = db.transaction(STORE, mode).objectStore(STORE);
|
|
4044
|
+
return promisify(run2(store3));
|
|
4045
4045
|
}
|
|
4046
4046
|
async function putOfflineDocument(record) {
|
|
4047
|
-
await withStore("readwrite", (
|
|
4047
|
+
await withStore("readwrite", (store3) => store3.put(record));
|
|
4048
4048
|
}
|
|
4049
4049
|
async function getOfflineDocument(docExtId) {
|
|
4050
|
-
const record = await withStore("readonly", (
|
|
4050
|
+
const record = await withStore("readonly", (store3) => store3.get(docExtId));
|
|
4051
4051
|
return record;
|
|
4052
4052
|
}
|
|
4053
4053
|
async function deleteOfflineDocument(docExtId) {
|
|
4054
|
-
await withStore("readwrite", (
|
|
4054
|
+
await withStore("readwrite", (store3) => store3.delete(docExtId));
|
|
4055
4055
|
}
|
|
4056
4056
|
async function listOfflineDocumentIds() {
|
|
4057
|
-
const keys = await withStore("readonly", (
|
|
4057
|
+
const keys = await withStore("readonly", (store3) => store3.getAllKeys());
|
|
4058
4058
|
return keys;
|
|
4059
4059
|
}
|
|
4060
4060
|
async function requestPersistentStorage() {
|
|
@@ -4141,78 +4141,15 @@ function useRemoveDocumentsOffline() {
|
|
|
4141
4141
|
});
|
|
4142
4142
|
}
|
|
4143
4143
|
|
|
4144
|
-
// src/chat/sibling-tabs.ts
|
|
4145
|
-
var SIBLING_TABS_CHANNEL = "arbi:conversation-messages";
|
|
4146
|
-
var channel = null;
|
|
4147
|
-
var listeners = 0;
|
|
4148
|
-
function post(message) {
|
|
4149
|
-
if (typeof BroadcastChannel === "undefined") return;
|
|
4150
|
-
if (channel) {
|
|
4151
|
-
channel.postMessage(message);
|
|
4152
|
-
return;
|
|
4153
|
-
}
|
|
4154
|
-
const once = new BroadcastChannel(SIBLING_TABS_CHANNEL);
|
|
4155
|
-
once.postMessage(message);
|
|
4156
|
-
once.close();
|
|
4157
|
-
}
|
|
4158
|
-
function announceToSiblingTabs(userId, conversationId, messageId) {
|
|
4159
|
-
if (!userId || !messageId || !conversationId || conversationId === "new") return;
|
|
4160
|
-
const message = {
|
|
4161
|
-
type: "conversation.message_posted",
|
|
4162
|
-
conversation_ext_id: conversationId,
|
|
4163
|
-
message_ext_id: messageId,
|
|
4164
|
-
user_ext_id: userId
|
|
4165
|
-
};
|
|
4166
|
-
try {
|
|
4167
|
-
post(message);
|
|
4168
|
-
} catch {
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4171
|
-
function listenToSiblingTabs(me, onMessage) {
|
|
4172
|
-
if (typeof BroadcastChannel === "undefined") return () => {
|
|
4173
|
-
};
|
|
4174
|
-
channel ??= new BroadcastChannel(SIBLING_TABS_CHANNEL);
|
|
4175
|
-
const ch = channel;
|
|
4176
|
-
listeners += 1;
|
|
4177
|
-
const listener = (event) => {
|
|
4178
|
-
const data = event.data;
|
|
4179
|
-
if (data?.type !== "conversation.message_posted" || !data.conversation_ext_id || !data.message_ext_id)
|
|
4180
|
-
return;
|
|
4181
|
-
const user = me();
|
|
4182
|
-
if (!user || data.user_ext_id !== user) return;
|
|
4183
|
-
onMessage(data);
|
|
4184
|
-
};
|
|
4185
|
-
ch.addEventListener("message", listener);
|
|
4186
|
-
let listening = true;
|
|
4187
|
-
return () => {
|
|
4188
|
-
if (!listening) return;
|
|
4189
|
-
listening = false;
|
|
4190
|
-
ch.removeEventListener("message", listener);
|
|
4191
|
-
listeners -= 1;
|
|
4192
|
-
if (listeners === 0 && channel === ch) {
|
|
4193
|
-
ch.close();
|
|
4194
|
-
channel = null;
|
|
4195
|
-
}
|
|
4196
|
-
};
|
|
4197
|
-
}
|
|
4198
|
-
|
|
4199
4144
|
// src/chat/stream-handlers.ts
|
|
4200
4145
|
var MIN_NARRATION_DWELL_MS = 4e3;
|
|
4201
4146
|
var FIRST_TOKEN_MEASURE = "arbi:send-message:first-token";
|
|
4202
|
-
function announceTurnEnded(userId, streamState) {
|
|
4203
|
-
if (streamState.endAnnounced) return;
|
|
4204
|
-
const messageId = streamState.assistantId ?? streamState.userMessageId;
|
|
4205
|
-
if (!messageId) return;
|
|
4206
|
-
streamState.endAnnounced = true;
|
|
4207
|
-
announceToSiblingTabs(userId, streamState.conversationId, messageId);
|
|
4208
|
-
}
|
|
4209
4147
|
function createStreamHandlers(ctx, params) {
|
|
4210
4148
|
const { queryClient, session, host } = ctx;
|
|
4211
4149
|
const { streamState, parentMessageId, parentId } = params;
|
|
4212
4150
|
let turnUserMessageId = null;
|
|
4213
4151
|
let queuedEntryRetired = false;
|
|
4214
4152
|
const isViewingConversation = () => session.getState().conversationId === streamState.conversationId;
|
|
4215
|
-
const tellOtherTabs = (messageId) => announceToSiblingTabs(ctx.userId(), streamState.conversationId, messageId);
|
|
4216
4153
|
const retireQueuedEntry = () => {
|
|
4217
4154
|
const clientId = streamState.queuedClientId;
|
|
4218
4155
|
if (!clientId || queuedEntryRetired) return;
|
|
@@ -4277,7 +4214,6 @@ function createStreamHandlers(ctx, params) {
|
|
|
4277
4214
|
}
|
|
4278
4215
|
turnUserMessageId = userMessage.external_id;
|
|
4279
4216
|
streamState.userMessageId = userMessage.external_id;
|
|
4280
|
-
tellOtherTabs(userMessage.external_id);
|
|
4281
4217
|
if (streamState.queuedContinuation) {
|
|
4282
4218
|
retireQueuedEntry();
|
|
4283
4219
|
const viewedLeaf = session.getState().cursor;
|
|
@@ -4362,7 +4298,6 @@ function createStreamHandlers(ctx, params) {
|
|
|
4362
4298
|
}
|
|
4363
4299
|
void invalidateConversationLists(queryClient);
|
|
4364
4300
|
session.setCursor(streamState.assistantId);
|
|
4365
|
-
tellOtherTabs(streamState.assistantId);
|
|
4366
4301
|
};
|
|
4367
4302
|
let firstTokenSeen = false;
|
|
4368
4303
|
const markFirstToken = () => {
|
|
@@ -4520,7 +4455,6 @@ function createStreamHandlers(ctx, params) {
|
|
|
4520
4455
|
if (streamState.assistantId) {
|
|
4521
4456
|
markMessageAsFailed(queryClient, streamState.conversationId, streamState.assistantId);
|
|
4522
4457
|
}
|
|
4523
|
-
announceTurnEnded(ctx.userId(), streamState);
|
|
4524
4458
|
};
|
|
4525
4459
|
const handleComplete = () => {
|
|
4526
4460
|
console.debug("[SSE] Stream complete");
|
|
@@ -4531,7 +4465,6 @@ function createStreamHandlers(ctx, params) {
|
|
|
4531
4465
|
});
|
|
4532
4466
|
}
|
|
4533
4467
|
flushStreamingTokens(queryClient);
|
|
4534
|
-
announceTurnEnded(ctx.userId(), streamState);
|
|
4535
4468
|
if (streamState.assistantId) {
|
|
4536
4469
|
session.clearAgentControl(streamState.assistantId);
|
|
4537
4470
|
}
|
|
@@ -4959,7 +4892,6 @@ function useConversationActions({
|
|
|
4959
4892
|
}
|
|
4960
4893
|
});
|
|
4961
4894
|
flushStreamingTokens(queryClient);
|
|
4962
|
-
announceTurnEnded(signedInUserId(arbi) ?? "", streamState);
|
|
4963
4895
|
if (queuedContinuation && !streamState.hasStreamStarted) {
|
|
4964
4896
|
if (streamState.queuedFailed) failQueuedContinuation(queryClient, session, streamState);
|
|
4965
4897
|
return;
|
|
@@ -5029,7 +4961,6 @@ function useConversationActions({
|
|
|
5029
4961
|
}
|
|
5030
4962
|
if (streamState.isStopped() || isAbort) {
|
|
5031
4963
|
console.debug("Message sending was aborted by user");
|
|
5032
|
-
announceTurnEnded(signedInUserId(arbi) ?? "", streamState);
|
|
5033
4964
|
clearLoadingState(queryClient, streamState.conversationId);
|
|
5034
4965
|
void queryClient.invalidateQueries({
|
|
5035
4966
|
queryKey: getThreadsQueryKey(streamState.conversationId)
|
|
@@ -5498,7 +5429,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5498
5429
|
if (!conversationId || !responseId) return;
|
|
5499
5430
|
let disposed = false;
|
|
5500
5431
|
let controller = new AbortController();
|
|
5501
|
-
let
|
|
5432
|
+
let retryTimer2;
|
|
5502
5433
|
let reconnects = 0;
|
|
5503
5434
|
const runStartedAt = session.getState().messageTimers[`${conversationId}:${responseId}`]?.startTime;
|
|
5504
5435
|
session.setConversationGenerating(conversationId, true, null);
|
|
@@ -5523,7 +5454,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5523
5454
|
if (isMessageRunning(queryClient, conversationId, responseId) && reconnects < MAX_RECONNECTS) {
|
|
5524
5455
|
reconnects += 1;
|
|
5525
5456
|
markLoading();
|
|
5526
|
-
|
|
5457
|
+
retryTimer2 = setTimeout(connect, RECONNECT_DELAY_MS);
|
|
5527
5458
|
return;
|
|
5528
5459
|
}
|
|
5529
5460
|
settle(false);
|
|
@@ -5609,7 +5540,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5609
5540
|
const stop = () => {
|
|
5610
5541
|
if (disposed) return;
|
|
5611
5542
|
disposed = true;
|
|
5612
|
-
clearTimeout(
|
|
5543
|
+
clearTimeout(retryTimer2);
|
|
5613
5544
|
window.removeEventListener("online", reconnectWhenOnline);
|
|
5614
5545
|
controller.abort();
|
|
5615
5546
|
settle(true);
|
|
@@ -5618,7 +5549,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5618
5549
|
return () => {
|
|
5619
5550
|
unregister();
|
|
5620
5551
|
disposed = true;
|
|
5621
|
-
clearTimeout(
|
|
5552
|
+
clearTimeout(retryTimer2);
|
|
5622
5553
|
window.removeEventListener("online", reconnectWhenOnline);
|
|
5623
5554
|
controller.abort();
|
|
5624
5555
|
settle(false);
|
|
@@ -6420,20 +6351,20 @@ function useConversationSettings() {
|
|
|
6420
6351
|
useEffect(() => {
|
|
6421
6352
|
if (isFullConfig(activeConfig)) session.setChatModelName(resolveConfigModel(activeConfig));
|
|
6422
6353
|
}, [activeConfig, configToLoad]);
|
|
6423
|
-
const
|
|
6424
|
-
const pending =
|
|
6354
|
+
const update2 = useUpdateConversation();
|
|
6355
|
+
const pending = update2.isPending ? update2.variables?.patch : void 0;
|
|
6425
6356
|
const saveTitle = useCallback(() => {
|
|
6426
6357
|
const trimmed = title.trim();
|
|
6427
6358
|
if (!conversationId || !isOwner || !trimmed || trimmed.length > TITLE_MAX) return;
|
|
6428
6359
|
if (trimmed === conversation?.title) return;
|
|
6429
|
-
|
|
6430
|
-
}, [conversationId, isOwner, title, conversation?.title,
|
|
6360
|
+
update2.mutate({ conversationId, patch: { title: trimmed } });
|
|
6361
|
+
}, [conversationId, isOwner, title, conversation?.title, update2]);
|
|
6431
6362
|
const setShared = useCallback(
|
|
6432
6363
|
(shared) => {
|
|
6433
6364
|
if (!conversationId || !isOwner || isShared === shared) return;
|
|
6434
|
-
|
|
6365
|
+
update2.mutate({ conversationId, patch: { shared } });
|
|
6435
6366
|
},
|
|
6436
|
-
[conversationId, isOwner, isShared,
|
|
6367
|
+
[conversationId, isOwner, isShared, update2]
|
|
6437
6368
|
);
|
|
6438
6369
|
const share = useCallback(() => setShared(true), [setShared]);
|
|
6439
6370
|
const unshare = useCallback(() => setShared(false), [setShared]);
|
|
@@ -6832,12 +6763,69 @@ function useInstallSkillPack(pack) {
|
|
|
6832
6763
|
}
|
|
6833
6764
|
});
|
|
6834
6765
|
}
|
|
6766
|
+
var WORK_OFFLINE_VIEW = "app.workOffline";
|
|
6767
|
+
var isWorkingOffline = (session) => session.getState().views.transient[WORK_OFFLINE_VIEW] === true;
|
|
6768
|
+
var setWorkOfflineIn = (session, on) => session.setTransientView(WORK_OFFLINE_VIEW, on || void 0);
|
|
6769
|
+
function useWorkOffline() {
|
|
6770
|
+
const session = useArbiSessionStore();
|
|
6771
|
+
const on = useArbiSessionState((s) => s.views.transient[WORK_OFFLINE_VIEW] === true);
|
|
6772
|
+
const set2 = useCallback((next) => setWorkOfflineIn(session, next), [session]);
|
|
6773
|
+
return [on, set2];
|
|
6774
|
+
}
|
|
6775
|
+
function computeOnline(wsStatus, isAuthenticated, workOffline, wsEverConnected) {
|
|
6776
|
+
if (workOffline) return false;
|
|
6777
|
+
const navOnline = typeof navigator === "undefined" ? true : navigator.onLine;
|
|
6778
|
+
const wsIsEvidence = isAuthenticated && wsEverConnected;
|
|
6779
|
+
const wsOnline = !wsIsEvidence || wsStatus !== "disconnected" && wsStatus !== "error";
|
|
6780
|
+
return navOnline && wsOnline;
|
|
6781
|
+
}
|
|
6782
|
+
function useOnlineSync() {
|
|
6783
|
+
const session = useArbiSessionStore();
|
|
6784
|
+
const wsStatus = useArbiWsStore((s) => s.status);
|
|
6785
|
+
const signedIn2 = useArbiSession().status === "signed-in";
|
|
6786
|
+
const [workOffline] = useWorkOffline();
|
|
6787
|
+
const wsEverConnected = useRef(false);
|
|
6788
|
+
if (wsStatus === "connected") wsEverConnected.current = true;
|
|
6789
|
+
const signedInRef = useRef(signedIn2);
|
|
6790
|
+
signedInRef.current = signedIn2;
|
|
6791
|
+
useEffect(() => {
|
|
6792
|
+
onlineManager.setOnline(computeOnline(wsStatus, signedIn2, workOffline, wsEverConnected.current));
|
|
6793
|
+
}, [wsStatus, signedIn2, workOffline]);
|
|
6794
|
+
useEffect(() => {
|
|
6795
|
+
const update2 = () => onlineManager.setOnline(
|
|
6796
|
+
computeOnline(
|
|
6797
|
+
useArbiWsStore.getState().status,
|
|
6798
|
+
signedInRef.current,
|
|
6799
|
+
isWorkingOffline(session),
|
|
6800
|
+
wsEverConnected.current
|
|
6801
|
+
)
|
|
6802
|
+
);
|
|
6803
|
+
window.addEventListener("online", update2);
|
|
6804
|
+
window.addEventListener("offline", update2);
|
|
6805
|
+
return () => {
|
|
6806
|
+
window.removeEventListener("online", update2);
|
|
6807
|
+
window.removeEventListener("offline", update2);
|
|
6808
|
+
};
|
|
6809
|
+
}, [session]);
|
|
6810
|
+
}
|
|
6811
|
+
function useReconnect() {
|
|
6812
|
+
const session = useArbiSessionStore();
|
|
6813
|
+
return useCallback(() => {
|
|
6814
|
+
if (isWorkingOffline(session)) {
|
|
6815
|
+
setWorkOfflineIn(session, false);
|
|
6816
|
+
return "resumed";
|
|
6817
|
+
}
|
|
6818
|
+
if (typeof navigator !== "undefined" && navigator.onLine === false) return "no-network";
|
|
6819
|
+
window.location.reload();
|
|
6820
|
+
return "reloading";
|
|
6821
|
+
}, [session]);
|
|
6822
|
+
}
|
|
6835
6823
|
|
|
6836
6824
|
// src/websocket/reconnect.ts
|
|
6837
6825
|
var INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
6838
6826
|
var MAX_RECONNECT_DELAY_MS = 3e4;
|
|
6839
|
-
function nextReconnectDelay(
|
|
6840
|
-
return Math.min(INITIAL_RECONNECT_DELAY_MS * 2 ** Math.max(0,
|
|
6827
|
+
function nextReconnectDelay(attempt2) {
|
|
6828
|
+
return Math.min(INITIAL_RECONNECT_DELAY_MS * 2 ** Math.max(0, attempt2), MAX_RECONNECT_DELAY_MS);
|
|
6841
6829
|
}
|
|
6842
6830
|
function resolveSocketBaseUrl(configured, origin) {
|
|
6843
6831
|
return /^https?:\/\//.test(configured) || !origin ? configured : origin;
|
|
@@ -7122,13 +7110,13 @@ function useUpdateMailMessage() {
|
|
|
7122
7110
|
mutationFn: ({
|
|
7123
7111
|
accountId,
|
|
7124
7112
|
messageId,
|
|
7125
|
-
update
|
|
7113
|
+
update: update2
|
|
7126
7114
|
}) => mailJson(
|
|
7127
7115
|
client.fetch,
|
|
7128
7116
|
`${accountPath(accountId)}/messages/${encodeURIComponent(messageId)}`,
|
|
7129
7117
|
{
|
|
7130
7118
|
method: "PATCH",
|
|
7131
|
-
body: JSON.stringify(
|
|
7119
|
+
body: JSON.stringify(update2)
|
|
7132
7120
|
}
|
|
7133
7121
|
)
|
|
7134
7122
|
});
|
|
@@ -7298,6 +7286,26 @@ async function applyLiveUpdate(queryClient, message) {
|
|
|
7298
7286
|
return false;
|
|
7299
7287
|
}
|
|
7300
7288
|
}
|
|
7289
|
+
function applyRunFinished(queryClient, arbi, session, message, onQuotaExceeded) {
|
|
7290
|
+
const { generating } = session.getState();
|
|
7291
|
+
const streaming = Object.entries(generating).find(
|
|
7292
|
+
([, generation]) => generation.streamingAssistantId === message.response_id
|
|
7293
|
+
)?.[0];
|
|
7294
|
+
const conversationId = streaming ?? message.conversation_ext_id ?? null;
|
|
7295
|
+
if (!conversationId) return;
|
|
7296
|
+
session.setConversationGenerating(conversationId, false, null);
|
|
7297
|
+
if (message.doc_ext_ids?.length) {
|
|
7298
|
+
refreshDocuments(queryClient, arbi, message.doc_ext_ids).catch(
|
|
7299
|
+
(error) => logger.error("[WebSocket] Failed to refresh task outputs:", error)
|
|
7300
|
+
);
|
|
7301
|
+
}
|
|
7302
|
+
if (message.user_message_id) session.removeQueuedMessageByServerId(message.user_message_id);
|
|
7303
|
+
void applyConversationEvent(queryClient, { ...message, conversation_ext_id: conversationId });
|
|
7304
|
+
if (message.status === "failed") {
|
|
7305
|
+
const quota = cachedQuotaUsedUp(queryClient, session.getState().workspaceId);
|
|
7306
|
+
if (quota) onQuotaExceeded?.(quota);
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
7301
7309
|
|
|
7302
7310
|
// src/notifications/live.ts
|
|
7303
7311
|
function isNotificationRow(message) {
|
|
@@ -7337,6 +7345,7 @@ var EVICTED_CLOSE_CODE = 1008;
|
|
|
7337
7345
|
function ArbiWebSocketProvider({
|
|
7338
7346
|
enabled = true,
|
|
7339
7347
|
onAuthenticated,
|
|
7348
|
+
handlers,
|
|
7340
7349
|
children
|
|
7341
7350
|
}) {
|
|
7342
7351
|
const arbi = useArbi();
|
|
@@ -7351,9 +7360,18 @@ function ArbiWebSocketProvider({
|
|
|
7351
7360
|
const setEvicted = useArbiWsStore((s) => s.setEvicted);
|
|
7352
7361
|
const onAuthenticatedRef = useRef(onAuthenticated);
|
|
7353
7362
|
onAuthenticatedRef.current = onAuthenticated;
|
|
7363
|
+
const handlersRef = useRef(handlers);
|
|
7364
|
+
handlersRef.current = handlers;
|
|
7365
|
+
const session = useArbiSessionStore();
|
|
7366
|
+
const sessionRef = useRef(session);
|
|
7367
|
+
sessionRef.current = session;
|
|
7368
|
+
const host = useArbiHost();
|
|
7369
|
+
const hostRef = useRef(host);
|
|
7370
|
+
hostRef.current = host;
|
|
7354
7371
|
const acceptRings = useFeature("calls");
|
|
7355
7372
|
const acceptRingsRef = useRef(acceptRings);
|
|
7356
7373
|
acceptRingsRef.current = acceptRings;
|
|
7374
|
+
useOnlineSync();
|
|
7357
7375
|
const context = useMemo(
|
|
7358
7376
|
() => ({
|
|
7359
7377
|
subscribe: (fn) => {
|
|
@@ -7382,6 +7400,16 @@ function ArbiWebSocketProvider({
|
|
|
7382
7400
|
} else if (isMessageType(msg, "task_update")) {
|
|
7383
7401
|
if (applyTaskUpdate(msg)) applyTaskEvent(queryClient, arbiRef.current, msg, me());
|
|
7384
7402
|
} else if (isMessageType(msg, "presence_update")) applyPresence(msg);
|
|
7403
|
+
else if (isMessageType(msg, "response_complete"))
|
|
7404
|
+
applyRunFinished(
|
|
7405
|
+
queryClient,
|
|
7406
|
+
arbiRef.current,
|
|
7407
|
+
sessionRef.current,
|
|
7408
|
+
msg,
|
|
7409
|
+
(quota) => hostRef.current.chat?.onQuotaExceeded?.(quota)
|
|
7410
|
+
);
|
|
7411
|
+
else if (type === "error")
|
|
7412
|
+
logger.error("[WebSocket] Server error:", msg.message);
|
|
7385
7413
|
else if (isMessageType(msg, "batch_complete")) {
|
|
7386
7414
|
if (msg.batch_type === "entity_extract") {
|
|
7387
7415
|
finishEntityExtraction(msg.doc_ext_ids ?? []);
|
|
@@ -7398,6 +7426,11 @@ function ArbiWebSocketProvider({
|
|
|
7398
7426
|
applyCallEvent(queryClient, msg, { me: me(), acceptRings: acceptRingsRef.current });
|
|
7399
7427
|
else if (isNotificationRow(msg)) applyNotificationRow(queryClient, msg, me());
|
|
7400
7428
|
else void applyLiveUpdate(queryClient, msg);
|
|
7429
|
+
try {
|
|
7430
|
+
handlersRef.current?.[type]?.(msg);
|
|
7431
|
+
} catch (error) {
|
|
7432
|
+
logger.error(`[WebSocket] The host's ${type} handler failed:`, error);
|
|
7433
|
+
}
|
|
7401
7434
|
for (const fn of subscribersRef.current) {
|
|
7402
7435
|
try {
|
|
7403
7436
|
fn(msg);
|
|
@@ -7405,15 +7438,15 @@ function ArbiWebSocketProvider({
|
|
|
7405
7438
|
}
|
|
7406
7439
|
}
|
|
7407
7440
|
};
|
|
7408
|
-
let
|
|
7409
|
-
let
|
|
7441
|
+
let retryTimer2 = null;
|
|
7442
|
+
let attempt2 = 0;
|
|
7410
7443
|
const scheduleRestart = () => {
|
|
7411
|
-
if (cancelled || evicted ||
|
|
7444
|
+
if (cancelled || evicted || retryTimer2) return;
|
|
7412
7445
|
setStatus("disconnected");
|
|
7413
|
-
const delay = nextReconnectDelay(
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7446
|
+
const delay = nextReconnectDelay(attempt2);
|
|
7447
|
+
attempt2 += 1;
|
|
7448
|
+
retryTimer2 = setTimeout(() => {
|
|
7449
|
+
retryTimer2 = null;
|
|
7417
7450
|
void start();
|
|
7418
7451
|
}, delay);
|
|
7419
7452
|
};
|
|
@@ -7444,7 +7477,7 @@ function ArbiWebSocketProvider({
|
|
|
7444
7477
|
onMessage,
|
|
7445
7478
|
onReconnecting: () => setStatus("reconnecting"),
|
|
7446
7479
|
onReconnected: () => {
|
|
7447
|
-
|
|
7480
|
+
attempt2 = 0;
|
|
7448
7481
|
setStatus("connected");
|
|
7449
7482
|
},
|
|
7450
7483
|
// Evicted (a newer session of the same user, or the server took this connection over for a
|
|
@@ -7466,7 +7499,7 @@ function ArbiWebSocketProvider({
|
|
|
7466
7499
|
return;
|
|
7467
7500
|
}
|
|
7468
7501
|
conn = c;
|
|
7469
|
-
|
|
7502
|
+
attempt2 = 0;
|
|
7470
7503
|
setStatus("connected");
|
|
7471
7504
|
} catch {
|
|
7472
7505
|
if (!info.signal.aborted) scheduleRestart();
|
|
@@ -7476,18 +7509,11 @@ function ArbiWebSocketProvider({
|
|
|
7476
7509
|
void start();
|
|
7477
7510
|
return () => {
|
|
7478
7511
|
cancelled = true;
|
|
7479
|
-
if (
|
|
7512
|
+
if (retryTimer2) clearTimeout(retryTimer2);
|
|
7480
7513
|
conn?.close();
|
|
7481
7514
|
reset();
|
|
7482
7515
|
};
|
|
7483
7516
|
}, [enabled, queryClient, setStatus, setEvicted, applyTaskUpdate, applyPresence, reset]);
|
|
7484
|
-
useEffect(() => {
|
|
7485
|
-
if (!enabled) return;
|
|
7486
|
-
return listenToSiblingTabs(
|
|
7487
|
-
() => signedInId(arbiRef.current),
|
|
7488
|
-
(message) => void applyConversationEvent(queryClient, message)
|
|
7489
|
-
);
|
|
7490
|
-
}, [enabled, queryClient]);
|
|
7491
7517
|
return /* @__PURE__ */ jsx(ArbiWsContext.Provider, { value: context, children });
|
|
7492
7518
|
}
|
|
7493
7519
|
function useArbiWebSocket(onMessage) {
|
|
@@ -7558,10 +7584,10 @@ function createWorkspaceSwitch({
|
|
|
7558
7584
|
queryClient
|
|
7559
7585
|
}) {
|
|
7560
7586
|
const switchWorkspace = (target) => {
|
|
7561
|
-
const
|
|
7562
|
-
switching.add(
|
|
7563
|
-
void
|
|
7564
|
-
return
|
|
7587
|
+
const run2 = runSwitch(target);
|
|
7588
|
+
switching.add(run2);
|
|
7589
|
+
void run2.finally(() => switching.delete(run2));
|
|
7590
|
+
return run2;
|
|
7565
7591
|
};
|
|
7566
7592
|
const runSwitch = async (target) => {
|
|
7567
7593
|
const { workspaceId: previousId, workspaceStatus } = session.getState();
|
|
@@ -8228,7 +8254,7 @@ function useFactsSearch(options = {}) {
|
|
|
8228
8254
|
const workspaceId = options.workspaceId ?? (arbi.hasWorkspace ? arbi.getWorkspaceId() : null);
|
|
8229
8255
|
const [kept, setKept] = useTransientView(FACTS_SEARCH_VIEW, EMPTY2);
|
|
8230
8256
|
const searchFacts = useSearchFacts(workspaceId);
|
|
8231
|
-
const
|
|
8257
|
+
const run2 = useRef(0);
|
|
8232
8258
|
const foreign = !!kept.workspaceId && !!workspaceId && kept.workspaceId !== workspaceId;
|
|
8233
8259
|
const state = foreign ? EMPTY2 : kept;
|
|
8234
8260
|
const patch = useCallback(
|
|
@@ -8243,18 +8269,18 @@ function useFactsSearch(options = {}) {
|
|
|
8243
8269
|
const search = useCallback(async () => {
|
|
8244
8270
|
const q = query.trim();
|
|
8245
8271
|
if (!q) return;
|
|
8246
|
-
const mine = ++
|
|
8272
|
+
const mine = ++run2.current;
|
|
8247
8273
|
try {
|
|
8248
8274
|
const res = await searchFacts.mutateAsync({ query: q, search_mode: mode, limit });
|
|
8249
|
-
if (
|
|
8275
|
+
if (run2.current !== mine) return;
|
|
8250
8276
|
patch({ hits: res.facts, lastQuery: q });
|
|
8251
8277
|
} catch (error) {
|
|
8252
|
-
if (
|
|
8278
|
+
if (run2.current !== mine) return;
|
|
8253
8279
|
throw error;
|
|
8254
8280
|
}
|
|
8255
8281
|
}, [query, searchFacts, mode, limit, patch]);
|
|
8256
8282
|
const clear = useCallback(() => {
|
|
8257
|
-
|
|
8283
|
+
run2.current++;
|
|
8258
8284
|
patch({ query: "", hits: null });
|
|
8259
8285
|
}, [patch]);
|
|
8260
8286
|
return {
|
|
@@ -8472,26 +8498,26 @@ var DEFAULT_LIMIT = 50;
|
|
|
8472
8498
|
var pad = (n) => String(n).padStart(2, "0");
|
|
8473
8499
|
function asDay(value) {
|
|
8474
8500
|
if (typeof value !== "string" || value.length < 10) return null;
|
|
8475
|
-
const
|
|
8476
|
-
return Number.isNaN(
|
|
8501
|
+
const day2 = /* @__PURE__ */ new Date(`${value.slice(0, 10)}T00:00:00Z`);
|
|
8502
|
+
return Number.isNaN(day2.getTime()) ? null : day2;
|
|
8477
8503
|
}
|
|
8478
|
-
function isoWeek(
|
|
8479
|
-
const d = new Date(
|
|
8504
|
+
function isoWeek(day2) {
|
|
8505
|
+
const d = new Date(day2);
|
|
8480
8506
|
const weekday = d.getUTCDay() || 7;
|
|
8481
8507
|
d.setUTCDate(d.getUTCDate() + 4 - weekday);
|
|
8482
8508
|
const yearStart = Date.UTC(d.getUTCFullYear(), 0, 1);
|
|
8483
8509
|
return [d.getUTCFullYear(), Math.ceil(((d.getTime() - yearStart) / 864e5 + 1) / 7)];
|
|
8484
8510
|
}
|
|
8485
|
-
function bucketOf(value,
|
|
8486
|
-
const
|
|
8487
|
-
if (!
|
|
8488
|
-
const y =
|
|
8489
|
-
const m =
|
|
8490
|
-
switch (
|
|
8511
|
+
function bucketOf(value, bucket2) {
|
|
8512
|
+
const day2 = asDay(value);
|
|
8513
|
+
if (!day2) return null;
|
|
8514
|
+
const y = day2.getUTCFullYear();
|
|
8515
|
+
const m = day2.getUTCMonth() + 1;
|
|
8516
|
+
switch (bucket2) {
|
|
8491
8517
|
case "day":
|
|
8492
|
-
return `${y}-${pad(m)}-${pad(
|
|
8518
|
+
return `${y}-${pad(m)}-${pad(day2.getUTCDate())}`;
|
|
8493
8519
|
case "week": {
|
|
8494
|
-
const [wy, w] = isoWeek(
|
|
8520
|
+
const [wy, w] = isoWeek(day2);
|
|
8495
8521
|
return `${wy}-W${pad(w)}`;
|
|
8496
8522
|
}
|
|
8497
8523
|
case "quarter":
|
|
@@ -8502,12 +8528,12 @@ function bucketOf(value, bucket) {
|
|
|
8502
8528
|
return `${y}-${pad(m)}`;
|
|
8503
8529
|
}
|
|
8504
8530
|
}
|
|
8505
|
-
function bucketStart(key,
|
|
8531
|
+
function bucketStart(key, bucket2) {
|
|
8506
8532
|
const parts = key.match(/^(\d{4})(?:-(W|Q)?(\d{1,2}))?(?:-(\d{2}))?$/);
|
|
8507
8533
|
if (!parts) return null;
|
|
8508
8534
|
const year = Number(parts[1]);
|
|
8509
8535
|
const n = Number(parts[3] ?? 1);
|
|
8510
|
-
switch (
|
|
8536
|
+
switch (bucket2) {
|
|
8511
8537
|
case "day":
|
|
8512
8538
|
return asDay(key);
|
|
8513
8539
|
case "week": {
|
|
@@ -8524,32 +8550,32 @@ function bucketStart(key, bucket) {
|
|
|
8524
8550
|
return new Date(Date.UTC(year, n - 1, 1));
|
|
8525
8551
|
}
|
|
8526
8552
|
}
|
|
8527
|
-
function nextBucket(
|
|
8528
|
-
const d = new Date(
|
|
8529
|
-
if (
|
|
8530
|
-
else if (
|
|
8531
|
-
else if (
|
|
8532
|
-
else if (
|
|
8553
|
+
function nextBucket(day2, bucket2) {
|
|
8554
|
+
const d = new Date(day2);
|
|
8555
|
+
if (bucket2 === "day") d.setUTCDate(d.getUTCDate() + 1);
|
|
8556
|
+
else if (bucket2 === "week") d.setUTCDate(d.getUTCDate() + 7);
|
|
8557
|
+
else if (bucket2 === "month") d.setUTCMonth(d.getUTCMonth() + 1);
|
|
8558
|
+
else if (bucket2 === "quarter") d.setUTCMonth(d.getUTCMonth() + 3);
|
|
8533
8559
|
else d.setUTCFullYear(d.getUTCFullYear() + 1);
|
|
8534
8560
|
return d;
|
|
8535
8561
|
}
|
|
8536
|
-
function bucketRange(first, last,
|
|
8537
|
-
const start = bucketStart(first,
|
|
8538
|
-
const end = bucketStart(last,
|
|
8562
|
+
function bucketRange(first, last, bucket2, max = 500) {
|
|
8563
|
+
const start = bucketStart(first, bucket2);
|
|
8564
|
+
const end = bucketStart(last, bucket2);
|
|
8539
8565
|
if (!start || !end || start > end) return null;
|
|
8540
8566
|
const keys = [];
|
|
8541
|
-
for (let d = start; d <= end && keys.length <= max; d = nextBucket(d,
|
|
8542
|
-
keys.push(bucketOf(d.toISOString(),
|
|
8567
|
+
for (let d = start; d <= end && keys.length <= max; d = nextBucket(d, bucket2)) {
|
|
8568
|
+
keys.push(bucketOf(d.toISOString(), bucket2));
|
|
8543
8569
|
}
|
|
8544
8570
|
return keys.length > max ? null : keys;
|
|
8545
8571
|
}
|
|
8546
8572
|
var isEmpty = (value) => value === null || value === void 0 || value === "" || Array.isArray(value) && !value.length;
|
|
8547
|
-
function groupKeys(value, field,
|
|
8573
|
+
function groupKeys(value, field, bucket2) {
|
|
8548
8574
|
if (isEmpty(value)) return [""];
|
|
8549
8575
|
if (Array.isArray(value)) {
|
|
8550
|
-
return [...new Set(value.map((v) => groupKeys(v, field,
|
|
8576
|
+
return [...new Set(value.map((v) => groupKeys(v, field, bucket2)[0]))];
|
|
8551
8577
|
}
|
|
8552
|
-
if (field.type === "date") return [bucketOf(value,
|
|
8578
|
+
if (field.type === "date") return [bucketOf(value, bucket2) ?? ""];
|
|
8553
8579
|
if (typeof value === "boolean") return [value ? "Yes" : "No"];
|
|
8554
8580
|
return [String(value)];
|
|
8555
8581
|
}
|
|
@@ -8644,7 +8670,7 @@ function aggregateDataset(dataset, query) {
|
|
|
8644
8670
|
const split = query.splitBy ? byId.get(query.splitBy) : void 0;
|
|
8645
8671
|
const op = query.measure?.op ?? "count";
|
|
8646
8672
|
const valueField = query.measure?.field ? byId.get(query.measure.field) : void 0;
|
|
8647
|
-
const
|
|
8673
|
+
const bucket2 = query.bucket ?? "month";
|
|
8648
8674
|
const fields = aggregateFields(dataset.fields, query);
|
|
8649
8675
|
if (op !== "count" && valueField?.type !== "number") {
|
|
8650
8676
|
throw new Error(`${op} needs a number field`);
|
|
@@ -8652,8 +8678,8 @@ function aggregateDataset(dataset, query) {
|
|
|
8652
8678
|
const rows = filterRows(dataset.rows, dataset.fields, query.filters);
|
|
8653
8679
|
const cells = /* @__PURE__ */ new Map();
|
|
8654
8680
|
for (const row of rows) {
|
|
8655
|
-
const groups = group ? groupKeys(row[group.id], group,
|
|
8656
|
-
const splits = split ? groupKeys(row[split.id], split,
|
|
8681
|
+
const groups = group ? groupKeys(row[group.id], group, bucket2) : ["All"];
|
|
8682
|
+
const splits = split ? groupKeys(row[split.id], split, bucket2) : [""];
|
|
8657
8683
|
const value = valueField ? row[valueField.id] ?? null : 1;
|
|
8658
8684
|
for (const g of groups) {
|
|
8659
8685
|
const bySplit = cells.get(g) ?? /* @__PURE__ */ new Map();
|
|
@@ -8699,12 +8725,12 @@ function aggregateDataset(dataset, query) {
|
|
|
8699
8725
|
}
|
|
8700
8726
|
return { fields, rows: out, totalRows: rows.length, truncated: keys.length > limit };
|
|
8701
8727
|
}
|
|
8702
|
-
function fillTimeGaps(dataset,
|
|
8728
|
+
function fillTimeGaps(dataset, bucket2) {
|
|
8703
8729
|
const keyId = AGGREGATE_FIELDS.key;
|
|
8704
8730
|
const keys = [...new Set(dataset.rows.map((r) => String(r[keyId] ?? "")))].filter(Boolean);
|
|
8705
8731
|
if (keys.length < 2) return dataset;
|
|
8706
8732
|
const sorted = [...keys].sort();
|
|
8707
|
-
const range = bucketRange(sorted[0], sorted[sorted.length - 1],
|
|
8733
|
+
const range = bucketRange(sorted[0], sorted[sorted.length - 1], bucket2);
|
|
8708
8734
|
if (!range || range.length === keys.length) return dataset;
|
|
8709
8735
|
const hasSeries = dataset.fields.some((f) => f.id === AGGREGATE_FIELDS.series);
|
|
8710
8736
|
const seriesLabels = hasSeries ? [...new Set(dataset.rows.map((r) => String(r[AGGREGATE_FIELDS.series])))] : [null];
|
|
@@ -9246,8 +9272,8 @@ var DEFAULT_USAGE_SOURCE_LABELS = {
|
|
|
9246
9272
|
var monthsBackOf = (params) => String(params.period ?? "0") === "1" ? 1 : 0;
|
|
9247
9273
|
function usageRows(usage, project, modelLabel = (g) => MODEL_GROUP_LABELS[g] ?? g) {
|
|
9248
9274
|
return (usage.daily ?? []).flatMap(
|
|
9249
|
-
(
|
|
9250
|
-
day:
|
|
9275
|
+
(day2) => Object.entries(day2.model_groups ?? {}).map(([group, mg]) => ({
|
|
9276
|
+
day: day2.date,
|
|
9251
9277
|
project,
|
|
9252
9278
|
model: modelLabel(group),
|
|
9253
9279
|
spend: mg.spend ?? 0,
|
|
@@ -9511,18 +9537,18 @@ function useDashboardStore() {
|
|
|
9511
9537
|
const { dashboards } = useArbiHost();
|
|
9512
9538
|
return (dashboards?.useStore ?? useDatabaseDashboardStore)();
|
|
9513
9539
|
}
|
|
9514
|
-
function useSavedDashboards(
|
|
9540
|
+
function useSavedDashboards(store3) {
|
|
9515
9541
|
return useQuery({
|
|
9516
|
-
queryKey: arbiQueryKeys.dashboards(
|
|
9517
|
-
queryFn: () =>
|
|
9518
|
-
enabled:
|
|
9542
|
+
queryKey: arbiQueryKeys.dashboards(store3.scope ?? ""),
|
|
9543
|
+
queryFn: () => store3.list(),
|
|
9544
|
+
enabled: store3.scope !== null
|
|
9519
9545
|
});
|
|
9520
9546
|
}
|
|
9521
|
-
function useSaveDashboard(
|
|
9547
|
+
function useSaveDashboard(store3) {
|
|
9522
9548
|
const queryClient = useQueryClient();
|
|
9523
|
-
const key = arbiQueryKeys.dashboards(
|
|
9549
|
+
const key = arbiQueryKeys.dashboards(store3.scope ?? "");
|
|
9524
9550
|
return useMutation({
|
|
9525
|
-
mutationFn: (dashboard) =>
|
|
9551
|
+
mutationFn: (dashboard) => store3.save(dashboard),
|
|
9526
9552
|
onSuccess: (saved) => {
|
|
9527
9553
|
queryClient.setQueryData(
|
|
9528
9554
|
key,
|
|
@@ -9532,11 +9558,11 @@ function useSaveDashboard(store) {
|
|
|
9532
9558
|
}
|
|
9533
9559
|
});
|
|
9534
9560
|
}
|
|
9535
|
-
function useDeleteDashboard(
|
|
9561
|
+
function useDeleteDashboard(store3) {
|
|
9536
9562
|
const queryClient = useQueryClient();
|
|
9537
|
-
const key = arbiQueryKeys.dashboards(
|
|
9563
|
+
const key = arbiQueryKeys.dashboards(store3.scope ?? "");
|
|
9538
9564
|
return useMutation({
|
|
9539
|
-
mutationFn: (id) =>
|
|
9565
|
+
mutationFn: (id) => store3.remove(id),
|
|
9540
9566
|
onSuccess: (_void, id) => {
|
|
9541
9567
|
queryClient.setQueryData(key, (old = []) => old.filter((d) => d.id !== id));
|
|
9542
9568
|
void queryClient.invalidateQueries({ queryKey: arbiQueryKeys.databaseRows() });
|
|
@@ -9639,8 +9665,8 @@ function isClientError(error) {
|
|
|
9639
9665
|
}
|
|
9640
9666
|
return false;
|
|
9641
9667
|
}
|
|
9642
|
-
function retryDelay(
|
|
9643
|
-
return Math.min(1e3 * 2 **
|
|
9668
|
+
function retryDelay(attempt2) {
|
|
9669
|
+
return Math.min(1e3 * 2 ** attempt2, 3e4) + Math.random() * 500;
|
|
9644
9670
|
}
|
|
9645
9671
|
function shouldRetryQuery(failureCount, error) {
|
|
9646
9672
|
return failureCount < 2 && !isClientError(error) && !isAbortError(error);
|
|
@@ -9681,6 +9707,14 @@ function createArbiQueryClient({
|
|
|
9681
9707
|
});
|
|
9682
9708
|
return { queryClient, persistOptions: persist ? createArbiQueryPersistOptions() : null };
|
|
9683
9709
|
}
|
|
9710
|
+
|
|
9711
|
+
// src/offline-store/network.ts
|
|
9712
|
+
function isNetworkFailure(error) {
|
|
9713
|
+
return error instanceof TypeError && /fetch|network|load failed|internet/i.test(error.message);
|
|
9714
|
+
}
|
|
9715
|
+
function isBrowserOffline() {
|
|
9716
|
+
return typeof navigator !== "undefined" && navigator.onLine === false;
|
|
9717
|
+
}
|
|
9684
9718
|
function useFilteredProjects(filters, options) {
|
|
9685
9719
|
const arbi = useArbi();
|
|
9686
9720
|
return useQuery({
|
|
@@ -10898,7 +10932,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10898
10932
|
useEffect(() => {
|
|
10899
10933
|
if (status && lastVerified.current === void 0) lastVerified.current = status.verified;
|
|
10900
10934
|
}, [status]);
|
|
10901
|
-
const
|
|
10935
|
+
const run2 = useCallback(
|
|
10902
10936
|
async (kind, fallback, fn) => {
|
|
10903
10937
|
setBusy(kind);
|
|
10904
10938
|
setProblem(null);
|
|
@@ -10919,7 +10953,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10919
10953
|
const startVerifyRef = useRef(async () => {
|
|
10920
10954
|
});
|
|
10921
10955
|
const refresh = useCallback(async () => {
|
|
10922
|
-
const next = await
|
|
10956
|
+
const next = await run2(
|
|
10923
10957
|
"refresh",
|
|
10924
10958
|
"Could not read the verification result",
|
|
10925
10959
|
() => apiRef.current.refresh()
|
|
@@ -10931,7 +10965,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10931
10965
|
const nextStep = next.state === "declined" ? "result" : identityStepFor(next);
|
|
10932
10966
|
if (nextStep === "verify" && isOpenRef.current) void startVerifyRef.current();
|
|
10933
10967
|
else setStep(nextStep);
|
|
10934
|
-
}, [
|
|
10968
|
+
}, [run2, apply]);
|
|
10935
10969
|
const refreshedOnLoad = useRef(false);
|
|
10936
10970
|
useEffect(() => {
|
|
10937
10971
|
if (refreshedOnLoad.current || !status) return;
|
|
@@ -10941,7 +10975,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10941
10975
|
const startVerify = useCallback(async () => {
|
|
10942
10976
|
setStep("verify");
|
|
10943
10977
|
setEmbedFailed(false);
|
|
10944
|
-
const url = await
|
|
10978
|
+
const url = await run2(
|
|
10945
10979
|
"start",
|
|
10946
10980
|
"Could not start identity verification",
|
|
10947
10981
|
() => apiRef.current.startSession()
|
|
@@ -10951,7 +10985,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10951
10985
|
return;
|
|
10952
10986
|
}
|
|
10953
10987
|
setStep("pay");
|
|
10954
|
-
}, [
|
|
10988
|
+
}, [run2]);
|
|
10955
10989
|
startVerifyRef.current = startVerify;
|
|
10956
10990
|
useEffect(() => {
|
|
10957
10991
|
if (!isOpen || step !== "verify" || !sessionUrl || !container || embedFailed) return;
|
|
@@ -11006,7 +11040,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
11006
11040
|
}, []);
|
|
11007
11041
|
closeRef.current = close;
|
|
11008
11042
|
const pay = useCallback(async () => {
|
|
11009
|
-
const result = await
|
|
11043
|
+
const result = await run2(
|
|
11010
11044
|
"pay",
|
|
11011
11045
|
"Payment could not be completed",
|
|
11012
11046
|
() => apiRef.current.purchase()
|
|
@@ -11019,10 +11053,10 @@ function useIdentityVerificationFlow(api, {
|
|
|
11019
11053
|
setPaymentUrl(null);
|
|
11020
11054
|
void query.refetch();
|
|
11021
11055
|
void startVerify();
|
|
11022
|
-
}, [
|
|
11056
|
+
}, [run2, query, startVerify]);
|
|
11023
11057
|
const acceptName = useCallback(async () => {
|
|
11024
11058
|
const offered = status;
|
|
11025
|
-
const next = await
|
|
11059
|
+
const next = await run2(
|
|
11026
11060
|
"accept",
|
|
11027
11061
|
"Could not change your name",
|
|
11028
11062
|
() => apiRef.current.acceptName()
|
|
@@ -11035,15 +11069,15 @@ function useIdentityVerificationFlow(api, {
|
|
|
11035
11069
|
familyName: offered.document_family_name ?? null
|
|
11036
11070
|
});
|
|
11037
11071
|
}
|
|
11038
|
-
}, [
|
|
11072
|
+
}, [run2, apply, status, onNameAccepted]);
|
|
11039
11073
|
const remove = useCallback(async () => {
|
|
11040
|
-
const next = await
|
|
11074
|
+
const next = await run2(
|
|
11041
11075
|
"remove",
|
|
11042
11076
|
"Could not remove the verification",
|
|
11043
11077
|
() => apiRef.current.remove()
|
|
11044
11078
|
);
|
|
11045
11079
|
if (next) apply(next);
|
|
11046
|
-
}, [
|
|
11080
|
+
}, [run2, apply]);
|
|
11047
11081
|
const price = useMemo(() => status ? formatPrice(status) : null, [status, formatPrice]);
|
|
11048
11082
|
return {
|
|
11049
11083
|
status,
|
|
@@ -11098,6 +11132,1191 @@ async function downloadRecoveryFile(params) {
|
|
|
11098
11132
|
});
|
|
11099
11133
|
downloadBlob(new Blob([file.html], { type: "text/html" }), file.filename);
|
|
11100
11134
|
}
|
|
11135
|
+
var OfflineMailLockedError = class extends Error {
|
|
11136
|
+
constructor() {
|
|
11137
|
+
super("Sign in to open the mail saved on this device");
|
|
11138
|
+
this.name = "OfflineMailLockedError";
|
|
11139
|
+
}
|
|
11140
|
+
};
|
|
11141
|
+
var sealer = createOfflineSealer("arbi-offline-mail-v1", {
|
|
11142
|
+
locked: () => new OfflineMailLockedError()
|
|
11143
|
+
});
|
|
11144
|
+
var forgetOfflineMailKeys = sealer.forget;
|
|
11145
|
+
var sealBytes = sealer.sealBytes;
|
|
11146
|
+
var openBytes = sealer.openBytes;
|
|
11147
|
+
var sealJson = sealer.sealJson;
|
|
11148
|
+
var openJson = sealer.openJson;
|
|
11149
|
+
var blind = sealer.blind;
|
|
11150
|
+
|
|
11151
|
+
// src/offline-store/store.ts
|
|
11152
|
+
var DB_VERSION2 = 1;
|
|
11153
|
+
var RECORDS = "records";
|
|
11154
|
+
var META = "meta";
|
|
11155
|
+
function promisify2(request) {
|
|
11156
|
+
return new Promise((resolve, reject) => {
|
|
11157
|
+
request.onsuccess = () => resolve(request.result);
|
|
11158
|
+
request.onerror = () => reject(request.error);
|
|
11159
|
+
});
|
|
11160
|
+
}
|
|
11161
|
+
function createRecordStore(dbName) {
|
|
11162
|
+
let dbPromise = null;
|
|
11163
|
+
function openDB2() {
|
|
11164
|
+
if (dbPromise) return dbPromise;
|
|
11165
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
11166
|
+
const request = indexedDB.open(dbName, DB_VERSION2);
|
|
11167
|
+
request.onerror = () => {
|
|
11168
|
+
dbPromise = null;
|
|
11169
|
+
reject(request.error);
|
|
11170
|
+
};
|
|
11171
|
+
request.onsuccess = () => {
|
|
11172
|
+
const db = request.result;
|
|
11173
|
+
db.onversionchange = () => {
|
|
11174
|
+
db.close();
|
|
11175
|
+
dbPromise = null;
|
|
11176
|
+
};
|
|
11177
|
+
resolve(db);
|
|
11178
|
+
};
|
|
11179
|
+
request.onupgradeneeded = () => {
|
|
11180
|
+
const db = request.result;
|
|
11181
|
+
if (!db.objectStoreNames.contains(RECORDS)) {
|
|
11182
|
+
const records = db.createObjectStore(RECORDS, { keyPath: "key" });
|
|
11183
|
+
records.createIndex("bucket", "bucket");
|
|
11184
|
+
records.createIndex("kind", "kind");
|
|
11185
|
+
}
|
|
11186
|
+
if (!db.objectStoreNames.contains(META)) db.createObjectStore(META, { keyPath: "key" });
|
|
11187
|
+
};
|
|
11188
|
+
});
|
|
11189
|
+
return dbPromise;
|
|
11190
|
+
}
|
|
11191
|
+
async function run2(store3, mode, fn) {
|
|
11192
|
+
const db = await openDB2();
|
|
11193
|
+
return promisify2(fn(db.transaction(store3, mode).objectStore(store3)));
|
|
11194
|
+
}
|
|
11195
|
+
async function scan(pick) {
|
|
11196
|
+
const db = await openDB2();
|
|
11197
|
+
const store3 = db.transaction(RECORDS, "readonly").objectStore(RECORDS);
|
|
11198
|
+
return new Promise((resolve, reject) => {
|
|
11199
|
+
const out = [];
|
|
11200
|
+
const cursor = store3.openCursor();
|
|
11201
|
+
cursor.onerror = () => reject(cursor.error);
|
|
11202
|
+
cursor.onsuccess = () => {
|
|
11203
|
+
const c = cursor.result;
|
|
11204
|
+
if (!c) return resolve(out);
|
|
11205
|
+
out.push(pick(c.value));
|
|
11206
|
+
c.continue();
|
|
11207
|
+
};
|
|
11208
|
+
});
|
|
11209
|
+
}
|
|
11210
|
+
const self = {
|
|
11211
|
+
dbName,
|
|
11212
|
+
async putRecord(record) {
|
|
11213
|
+
await run2(RECORDS, "readwrite", (s) => s.put(record));
|
|
11214
|
+
},
|
|
11215
|
+
async getRecord(key) {
|
|
11216
|
+
return await run2(RECORDS, "readonly", (s) => s.get(key));
|
|
11217
|
+
},
|
|
11218
|
+
async hasRecord(key) {
|
|
11219
|
+
return await run2(RECORDS, "readonly", (s) => s.count(key)) > 0;
|
|
11220
|
+
},
|
|
11221
|
+
async deleteRecords(keys) {
|
|
11222
|
+
if (keys.length === 0) return;
|
|
11223
|
+
const db = await openDB2();
|
|
11224
|
+
const tx = db.transaction(RECORDS, "readwrite");
|
|
11225
|
+
const store3 = tx.objectStore(RECORDS);
|
|
11226
|
+
for (const key of keys) store3.delete(key);
|
|
11227
|
+
await new Promise((resolve, reject) => {
|
|
11228
|
+
tx.oncomplete = () => resolve();
|
|
11229
|
+
tx.onerror = () => reject(tx.error);
|
|
11230
|
+
tx.onabort = () => reject(tx.error);
|
|
11231
|
+
});
|
|
11232
|
+
},
|
|
11233
|
+
async recordsInBucket(bucket2) {
|
|
11234
|
+
return await run2(
|
|
11235
|
+
RECORDS,
|
|
11236
|
+
"readonly",
|
|
11237
|
+
(s) => s.index("bucket").getAll(bucket2)
|
|
11238
|
+
);
|
|
11239
|
+
},
|
|
11240
|
+
async recordsOfKind(kind) {
|
|
11241
|
+
return await run2(
|
|
11242
|
+
RECORDS,
|
|
11243
|
+
"readonly",
|
|
11244
|
+
(s) => s.index("kind").getAll(kind)
|
|
11245
|
+
);
|
|
11246
|
+
},
|
|
11247
|
+
allRecordSizes() {
|
|
11248
|
+
return scan(({ key, kind, size }) => ({ key, kind, size }));
|
|
11249
|
+
},
|
|
11250
|
+
allRecordAccess() {
|
|
11251
|
+
return scan(({ key, kind, lastAccess }) => ({ key, kind, lastAccess }));
|
|
11252
|
+
},
|
|
11253
|
+
async touchRecord(key) {
|
|
11254
|
+
const record = await self.getRecord(key);
|
|
11255
|
+
if (record) await self.putRecord({ ...record, lastAccess: Date.now() });
|
|
11256
|
+
},
|
|
11257
|
+
async getMeta(key) {
|
|
11258
|
+
const row = await run2(META, "readonly", (s) => s.get(key));
|
|
11259
|
+
return row?.value;
|
|
11260
|
+
},
|
|
11261
|
+
async setMeta(key, value) {
|
|
11262
|
+
await run2(META, "readwrite", (s) => s.put({ key, value }));
|
|
11263
|
+
},
|
|
11264
|
+
async deleteDatabase() {
|
|
11265
|
+
const db = await dbPromise?.catch(() => null);
|
|
11266
|
+
db?.close();
|
|
11267
|
+
dbPromise = null;
|
|
11268
|
+
await new Promise((resolve, reject) => {
|
|
11269
|
+
const request = indexedDB.deleteDatabase(dbName);
|
|
11270
|
+
request.onsuccess = () => resolve();
|
|
11271
|
+
request.onerror = () => reject(request.error);
|
|
11272
|
+
request.onblocked = () => {
|
|
11273
|
+
};
|
|
11274
|
+
});
|
|
11275
|
+
}
|
|
11276
|
+
};
|
|
11277
|
+
return self;
|
|
11278
|
+
}
|
|
11279
|
+
|
|
11280
|
+
// src/mail/offline/store.ts
|
|
11281
|
+
var OFFLINE_MAIL_DB = "arbi-offline-mail";
|
|
11282
|
+
var store = createRecordStore(OFFLINE_MAIL_DB);
|
|
11283
|
+
var putRecord = store.putRecord;
|
|
11284
|
+
var getRecord = store.getRecord;
|
|
11285
|
+
var hasRecord = store.hasRecord;
|
|
11286
|
+
var deleteRecords = store.deleteRecords;
|
|
11287
|
+
var recordsInBucket = store.recordsInBucket;
|
|
11288
|
+
var recordsOfKind = store.recordsOfKind;
|
|
11289
|
+
var allRecordSizes = store.allRecordSizes;
|
|
11290
|
+
var touchRecord = store.touchRecord;
|
|
11291
|
+
var getMeta = store.getMeta;
|
|
11292
|
+
var setMeta = store.setMeta;
|
|
11293
|
+
var deleteOfflineMailDatabase = store.deleteDatabase;
|
|
11294
|
+
|
|
11295
|
+
// src/mail/offline/mailbox.ts
|
|
11296
|
+
var keyOf = {
|
|
11297
|
+
accounts: () => blind("accounts"),
|
|
11298
|
+
policy: () => blind("policy"),
|
|
11299
|
+
folders: (accountId) => blind("folders", accountId),
|
|
11300
|
+
header: (accountId, folderId, messageId) => blind("header", accountId, folderId, messageId),
|
|
11301
|
+
body: (accountId, messageId) => blind("body", accountId, messageId),
|
|
11302
|
+
attachment: (accountId, messageId, attachmentId) => blind("attachment", accountId, messageId, attachmentId)
|
|
11303
|
+
};
|
|
11304
|
+
var bucketOf2 = {
|
|
11305
|
+
global: () => blind("bucket", "global"),
|
|
11306
|
+
folder: (accountId, folderId) => blind("bucket", "folder", accountId, folderId),
|
|
11307
|
+
account: (accountId) => blind("bucket", "account", accountId)
|
|
11308
|
+
};
|
|
11309
|
+
async function put(kind, key, bucket2, data, sortAt = Date.now()) {
|
|
11310
|
+
const now = Date.now();
|
|
11311
|
+
await putRecord({ key, kind, bucket: bucket2, sortAt, size: data.byteLength, lastAccess: now, data });
|
|
11312
|
+
}
|
|
11313
|
+
async function readJson(record) {
|
|
11314
|
+
if (!record) return void 0;
|
|
11315
|
+
try {
|
|
11316
|
+
return await openJson(record.data);
|
|
11317
|
+
} catch {
|
|
11318
|
+
return void 0;
|
|
11319
|
+
}
|
|
11320
|
+
}
|
|
11321
|
+
var receivedAt = (m) => m.received_at ? Date.parse(m.received_at) || 0 : 0;
|
|
11322
|
+
async function saveAccounts(accounts) {
|
|
11323
|
+
await put("accounts", await keyOf.accounts(), await bucketOf2.global(), await sealJson(accounts));
|
|
11324
|
+
}
|
|
11325
|
+
async function loadAccounts() {
|
|
11326
|
+
return readJson(await getRecord(await keyOf.accounts()));
|
|
11327
|
+
}
|
|
11328
|
+
async function savePolicy(policy) {
|
|
11329
|
+
await put("policy", await keyOf.policy(), await bucketOf2.global(), await sealJson(policy));
|
|
11330
|
+
}
|
|
11331
|
+
async function loadPolicy() {
|
|
11332
|
+
return readJson(await getRecord(await keyOf.policy()));
|
|
11333
|
+
}
|
|
11334
|
+
async function saveFolders(accountId, folders) {
|
|
11335
|
+
await put(
|
|
11336
|
+
"folders",
|
|
11337
|
+
await keyOf.folders(accountId),
|
|
11338
|
+
await bucketOf2.account(accountId),
|
|
11339
|
+
await sealJson(folders)
|
|
11340
|
+
);
|
|
11341
|
+
}
|
|
11342
|
+
async function loadFolders(accountId) {
|
|
11343
|
+
return readJson(await getRecord(await keyOf.folders(accountId)));
|
|
11344
|
+
}
|
|
11345
|
+
function toHeader(message) {
|
|
11346
|
+
return {
|
|
11347
|
+
...message,
|
|
11348
|
+
text: "",
|
|
11349
|
+
html: void 0,
|
|
11350
|
+
delivery: void 0,
|
|
11351
|
+
attachments: message.attachments.map((a) => ({ ...a, content_base64: null }))
|
|
11352
|
+
};
|
|
11353
|
+
}
|
|
11354
|
+
async function saveHeader(accountId, folderId, message) {
|
|
11355
|
+
await put(
|
|
11356
|
+
"header",
|
|
11357
|
+
await keyOf.header(accountId, folderId, message.id),
|
|
11358
|
+
await bucketOf2.folder(accountId, folderId),
|
|
11359
|
+
await sealJson(toHeader(message)),
|
|
11360
|
+
receivedAt(message)
|
|
11361
|
+
);
|
|
11362
|
+
}
|
|
11363
|
+
async function loadHeaders(accountId, folderId) {
|
|
11364
|
+
const records = await recordsInBucket(await bucketOf2.folder(accountId, folderId));
|
|
11365
|
+
const out = [];
|
|
11366
|
+
for (const record of records) {
|
|
11367
|
+
if (record.kind !== "header") continue;
|
|
11368
|
+
const message = await readJson(record);
|
|
11369
|
+
if (message) out.push({ key: record.key, sortAt: record.sortAt, message });
|
|
11370
|
+
}
|
|
11371
|
+
return out.sort((a, b) => b.sortAt - a.sortAt);
|
|
11372
|
+
}
|
|
11373
|
+
async function hasFolder(accountId) {
|
|
11374
|
+
return hasRecord(await keyOf.folders(accountId));
|
|
11375
|
+
}
|
|
11376
|
+
async function saveBody(accountId, message) {
|
|
11377
|
+
const stripped = {
|
|
11378
|
+
...message,
|
|
11379
|
+
attachments: message.attachments.map((a) => ({ ...a, content_base64: null }))
|
|
11380
|
+
};
|
|
11381
|
+
await put(
|
|
11382
|
+
"body",
|
|
11383
|
+
await keyOf.body(accountId, message.id),
|
|
11384
|
+
await bucketOf2.account(accountId),
|
|
11385
|
+
await sealJson(stripped),
|
|
11386
|
+
receivedAt(message)
|
|
11387
|
+
);
|
|
11388
|
+
}
|
|
11389
|
+
async function hasBody(accountId, messageId) {
|
|
11390
|
+
return hasRecord(await keyOf.body(accountId, messageId));
|
|
11391
|
+
}
|
|
11392
|
+
async function loadBody(accountId, messageId) {
|
|
11393
|
+
return readJson(await getRecord(await keyOf.body(accountId, messageId)));
|
|
11394
|
+
}
|
|
11395
|
+
async function saveAttachment(accountId, messageId, attachmentId, bytes) {
|
|
11396
|
+
await put(
|
|
11397
|
+
"attachment",
|
|
11398
|
+
await keyOf.attachment(accountId, messageId, attachmentId),
|
|
11399
|
+
await bucketOf2.account(accountId),
|
|
11400
|
+
await sealBytes(bytes)
|
|
11401
|
+
);
|
|
11402
|
+
}
|
|
11403
|
+
async function hasAttachment(accountId, messageId, attachmentId) {
|
|
11404
|
+
return hasRecord(await keyOf.attachment(accountId, messageId, attachmentId));
|
|
11405
|
+
}
|
|
11406
|
+
async function loadAttachment(accountId, messageId, attachmentId) {
|
|
11407
|
+
const key = await keyOf.attachment(accountId, messageId, attachmentId);
|
|
11408
|
+
const record = await getRecord(key);
|
|
11409
|
+
if (!record) return void 0;
|
|
11410
|
+
try {
|
|
11411
|
+
const bytes = await openBytes(record.data);
|
|
11412
|
+
void touchRecord(key);
|
|
11413
|
+
return bytes;
|
|
11414
|
+
} catch {
|
|
11415
|
+
return void 0;
|
|
11416
|
+
}
|
|
11417
|
+
}
|
|
11418
|
+
async function removeKeys(keys) {
|
|
11419
|
+
await deleteRecords(keys);
|
|
11420
|
+
}
|
|
11421
|
+
function bodyText(body) {
|
|
11422
|
+
const html = body.text ? "" : (body.html ?? "").replace(/<[^>]*>/g, " ");
|
|
11423
|
+
return `${body.text} ${html} ${body.attachments.map((a) => a.name).join(" ")}`;
|
|
11424
|
+
}
|
|
11425
|
+
var folded = (value) => value.toLocaleLowerCase().normalize("NFKD");
|
|
11426
|
+
async function searchFolder(accountId, folderId, query) {
|
|
11427
|
+
const words = folded(query).split(/\s+/).filter(Boolean);
|
|
11428
|
+
const headers = await loadHeaders(accountId, folderId);
|
|
11429
|
+
if (words.length === 0) return headers.map((h) => h.message);
|
|
11430
|
+
const out = [];
|
|
11431
|
+
for (const { message } of headers) {
|
|
11432
|
+
const people = [...message.sender, ...message.to, ...message.cc].map((p) => `${p.name ?? ""} ${p.email}`).join(" ");
|
|
11433
|
+
let haystack = folded(`${message.subject} ${people} ${message.preview}`);
|
|
11434
|
+
if (!words.every((w) => haystack.includes(w))) {
|
|
11435
|
+
const body = await loadBody(accountId, message.id);
|
|
11436
|
+
if (body) haystack += " " + folded(bodyText(body));
|
|
11437
|
+
}
|
|
11438
|
+
if (words.every((w) => haystack.includes(w))) out.push(message);
|
|
11439
|
+
}
|
|
11440
|
+
return out;
|
|
11441
|
+
}
|
|
11442
|
+
|
|
11443
|
+
// src/mail/offline/settings.ts
|
|
11444
|
+
var DEFAULT_OFFLINE_MAIL_SETTINGS = {
|
|
11445
|
+
enabled: false,
|
|
11446
|
+
scope: "90d",
|
|
11447
|
+
attachmentCapMb: 25,
|
|
11448
|
+
quotaMb: 1024
|
|
11449
|
+
};
|
|
11450
|
+
var SCOPE_DAYS = {
|
|
11451
|
+
"30d": 30,
|
|
11452
|
+
"90d": 90,
|
|
11453
|
+
"365d": 365,
|
|
11454
|
+
all: null
|
|
11455
|
+
};
|
|
11456
|
+
var STORAGE_PREFIX = "arbi-offline-mail-settings:";
|
|
11457
|
+
function getOfflineMailSettings(userExtId) {
|
|
11458
|
+
if (!userExtId) return DEFAULT_OFFLINE_MAIL_SETTINGS;
|
|
11459
|
+
const key = STORAGE_PREFIX + userExtId;
|
|
11460
|
+
try {
|
|
11461
|
+
const raw = localStorage.getItem(key);
|
|
11462
|
+
return raw ? { ...DEFAULT_OFFLINE_MAIL_SETTINGS, ...JSON.parse(raw) } : DEFAULT_OFFLINE_MAIL_SETTINGS;
|
|
11463
|
+
} catch {
|
|
11464
|
+
return DEFAULT_OFFLINE_MAIL_SETTINGS;
|
|
11465
|
+
}
|
|
11466
|
+
}
|
|
11467
|
+
function setOfflineMailSettings(userExtId, patch) {
|
|
11468
|
+
const next = { ...getOfflineMailSettings(userExtId), ...patch };
|
|
11469
|
+
if (userExtId) {
|
|
11470
|
+
try {
|
|
11471
|
+
localStorage.setItem(STORAGE_PREFIX + userExtId, JSON.stringify(next));
|
|
11472
|
+
} catch {
|
|
11473
|
+
}
|
|
11474
|
+
}
|
|
11475
|
+
notifyOfflineMailChange("settings");
|
|
11476
|
+
return next;
|
|
11477
|
+
}
|
|
11478
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
11479
|
+
var CHANNEL = "arbi-offline-mail";
|
|
11480
|
+
var channel = null;
|
|
11481
|
+
function getChannel() {
|
|
11482
|
+
if (channel || typeof BroadcastChannel === "undefined") return channel;
|
|
11483
|
+
channel = new BroadcastChannel(CHANNEL);
|
|
11484
|
+
channel.onmessage = (event) => {
|
|
11485
|
+
for (const listener of listeners) listener(event.data);
|
|
11486
|
+
};
|
|
11487
|
+
return channel;
|
|
11488
|
+
}
|
|
11489
|
+
function notifyOfflineMailChange(change) {
|
|
11490
|
+
for (const listener of listeners) listener(change);
|
|
11491
|
+
try {
|
|
11492
|
+
getChannel()?.postMessage(change);
|
|
11493
|
+
} catch {
|
|
11494
|
+
}
|
|
11495
|
+
}
|
|
11496
|
+
function onOfflineMailChange(listener) {
|
|
11497
|
+
getChannel();
|
|
11498
|
+
listeners.add(listener);
|
|
11499
|
+
return () => listeners.delete(listener);
|
|
11500
|
+
}
|
|
11501
|
+
|
|
11502
|
+
// src/mail/offline/drafts.ts
|
|
11503
|
+
var keyFor = (id) => blind("draft", id);
|
|
11504
|
+
async function saveLocalDraft(accountId, compose, id = crypto.randomUUID()) {
|
|
11505
|
+
const draft = { id, accountId, compose, updatedAt: Date.now() };
|
|
11506
|
+
await putRecord({
|
|
11507
|
+
key: await keyFor(id),
|
|
11508
|
+
kind: "draft",
|
|
11509
|
+
bucket: await blind("bucket", "drafts", accountId),
|
|
11510
|
+
sortAt: draft.updatedAt,
|
|
11511
|
+
size: 0,
|
|
11512
|
+
lastAccess: draft.updatedAt,
|
|
11513
|
+
data: await sealJson(draft)
|
|
11514
|
+
});
|
|
11515
|
+
notifyOfflineMailChange("drafts");
|
|
11516
|
+
return draft;
|
|
11517
|
+
}
|
|
11518
|
+
async function listLocalDrafts(accountId) {
|
|
11519
|
+
const drafts = [];
|
|
11520
|
+
for (const record of await recordsOfKind("draft")) {
|
|
11521
|
+
try {
|
|
11522
|
+
const draft = await openJson(record.data);
|
|
11523
|
+
if (!accountId || draft.accountId === accountId) drafts.push(draft);
|
|
11524
|
+
} catch {
|
|
11525
|
+
}
|
|
11526
|
+
}
|
|
11527
|
+
return drafts.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
11528
|
+
}
|
|
11529
|
+
async function deleteLocalDraft(id) {
|
|
11530
|
+
await deleteRecords([await keyFor(id)]);
|
|
11531
|
+
notifyOfflineMailChange("drafts");
|
|
11532
|
+
}
|
|
11533
|
+
async function syncLocalDrafts(save) {
|
|
11534
|
+
let uploaded = 0;
|
|
11535
|
+
for (const draft of await listLocalDrafts()) {
|
|
11536
|
+
if (draft.error) continue;
|
|
11537
|
+
try {
|
|
11538
|
+
await save(draft);
|
|
11539
|
+
await deleteLocalDraft(draft.id);
|
|
11540
|
+
uploaded++;
|
|
11541
|
+
} catch (error) {
|
|
11542
|
+
const status = error.status;
|
|
11543
|
+
if (isNetworkFailure(error) || status === void 0 || status === 401 || status >= 500) break;
|
|
11544
|
+
await putRecord({
|
|
11545
|
+
key: await keyFor(draft.id),
|
|
11546
|
+
kind: "draft",
|
|
11547
|
+
bucket: await blind("bucket", "drafts", draft.accountId),
|
|
11548
|
+
sortAt: draft.updatedAt,
|
|
11549
|
+
size: 0,
|
|
11550
|
+
lastAccess: Date.now(),
|
|
11551
|
+
data: await sealJson({
|
|
11552
|
+
...draft,
|
|
11553
|
+
error: error instanceof Error ? error.message : "The server refused this draft"
|
|
11554
|
+
})
|
|
11555
|
+
});
|
|
11556
|
+
notifyOfflineMailChange("drafts");
|
|
11557
|
+
}
|
|
11558
|
+
}
|
|
11559
|
+
return uploaded;
|
|
11560
|
+
}
|
|
11561
|
+
|
|
11562
|
+
// src/mail/offline/outbox.ts
|
|
11563
|
+
var bucket = () => blind("bucket", "outbox");
|
|
11564
|
+
var keyFor2 = (id) => blind("outbox", id);
|
|
11565
|
+
function outboxProblem(compose) {
|
|
11566
|
+
const missing = compose.attachments.find((a) => a.content_base64 == null);
|
|
11567
|
+
if (missing)
|
|
11568
|
+
return `${missing.name} isn't saved on this device, so it can't be sent offline. Remove it or send once you're back online.`;
|
|
11569
|
+
return null;
|
|
11570
|
+
}
|
|
11571
|
+
var seqCounter = 0;
|
|
11572
|
+
async function write(item) {
|
|
11573
|
+
await putRecord({
|
|
11574
|
+
key: await keyFor2(item.id),
|
|
11575
|
+
kind: "outbox",
|
|
11576
|
+
bucket: await bucket(),
|
|
11577
|
+
sortAt: item.seq,
|
|
11578
|
+
size: 0,
|
|
11579
|
+
lastAccess: Date.now(),
|
|
11580
|
+
data: await sealJson(item)
|
|
11581
|
+
});
|
|
11582
|
+
}
|
|
11583
|
+
async function listOutbox() {
|
|
11584
|
+
const records = await recordsInBucket(await bucket());
|
|
11585
|
+
const items = [];
|
|
11586
|
+
for (const record of records) {
|
|
11587
|
+
try {
|
|
11588
|
+
items.push(await openJson(record.data));
|
|
11589
|
+
} catch {
|
|
11590
|
+
}
|
|
11591
|
+
}
|
|
11592
|
+
return items.sort((a, b) => a.seq - b.seq);
|
|
11593
|
+
}
|
|
11594
|
+
async function enqueueOutbox(input) {
|
|
11595
|
+
const problem = outboxProblem(input.compose);
|
|
11596
|
+
if (problem) throw new Error(problem);
|
|
11597
|
+
const now = Date.now();
|
|
11598
|
+
seqCounter = Math.max(seqCounter + 1, now * 1e3);
|
|
11599
|
+
const item = {
|
|
11600
|
+
id: crypto.randomUUID(),
|
|
11601
|
+
requestId: input.requestId,
|
|
11602
|
+
account: input.account,
|
|
11603
|
+
compose: input.compose,
|
|
11604
|
+
secure: input.secure,
|
|
11605
|
+
securePayload: input.securePayload,
|
|
11606
|
+
status: "queued",
|
|
11607
|
+
attempts: 0,
|
|
11608
|
+
createdAt: now,
|
|
11609
|
+
seq: seqCounter
|
|
11610
|
+
};
|
|
11611
|
+
await write(item);
|
|
11612
|
+
notifyOfflineMailChange("outbox");
|
|
11613
|
+
return item;
|
|
11614
|
+
}
|
|
11615
|
+
async function update(item, patch) {
|
|
11616
|
+
const next = { ...item, ...patch };
|
|
11617
|
+
await write(next);
|
|
11618
|
+
notifyOfflineMailChange("outbox");
|
|
11619
|
+
return next;
|
|
11620
|
+
}
|
|
11621
|
+
async function deleteOutboxItem(id) {
|
|
11622
|
+
await deleteRecords([await keyFor2(id)]);
|
|
11623
|
+
notifyOfflineMailChange("outbox");
|
|
11624
|
+
}
|
|
11625
|
+
async function retryOutboxItem(id) {
|
|
11626
|
+
const item = (await listOutbox()).find((i) => i.id === id);
|
|
11627
|
+
if (item && item.status === "failed")
|
|
11628
|
+
await update(item, { status: "queued", error: void 0, inDoubt: item.inDoubt });
|
|
11629
|
+
}
|
|
11630
|
+
var IN_DOUBT_MESSAGE = "The mail server didn't confirm this message. It may already have been sent: check Sent before sending it again.";
|
|
11631
|
+
function classify(error) {
|
|
11632
|
+
if (isNetworkFailure(error)) return { kind: "offline" };
|
|
11633
|
+
const status = error?.status;
|
|
11634
|
+
const detail = error instanceof Error ? error.message : "The message could not be sent";
|
|
11635
|
+
if (status === void 0) return { kind: "failed", error: detail, inDoubt: false };
|
|
11636
|
+
if (status === 409) return { kind: "failed", error: IN_DOUBT_MESSAGE, inDoubt: true };
|
|
11637
|
+
if (status >= 500) return { kind: "failed", error: IN_DOUBT_MESSAGE, inDoubt: true };
|
|
11638
|
+
return { kind: "failed", error: detail, inDoubt: false };
|
|
11639
|
+
}
|
|
11640
|
+
async function prepared(item, sender) {
|
|
11641
|
+
if (!item.secure || item.securePayload !== void 0) return item;
|
|
11642
|
+
return update(item, { securePayload: await sender.prepareSecure(item) });
|
|
11643
|
+
}
|
|
11644
|
+
async function attempt(item, sender) {
|
|
11645
|
+
try {
|
|
11646
|
+
const result = await sender.send(item);
|
|
11647
|
+
return { kind: "sent", serverId: result?.id };
|
|
11648
|
+
} catch (error) {
|
|
11649
|
+
const outcome = classify(error);
|
|
11650
|
+
if (error.status === 401) {
|
|
11651
|
+
try {
|
|
11652
|
+
await sender.unlock(item.account);
|
|
11653
|
+
const result = await sender.send(item);
|
|
11654
|
+
return { kind: "sent", serverId: result?.id };
|
|
11655
|
+
} catch (retryError) {
|
|
11656
|
+
return classify(retryError);
|
|
11657
|
+
}
|
|
11658
|
+
}
|
|
11659
|
+
return outcome;
|
|
11660
|
+
}
|
|
11661
|
+
}
|
|
11662
|
+
async function flushOutboxOnce(sender, isOnline) {
|
|
11663
|
+
const result = { sent: 0, failed: 0, interrupted: false };
|
|
11664
|
+
const unlocked2 = /* @__PURE__ */ new Set();
|
|
11665
|
+
for (; ; ) {
|
|
11666
|
+
if (!isOnline()) {
|
|
11667
|
+
result.interrupted = true;
|
|
11668
|
+
break;
|
|
11669
|
+
}
|
|
11670
|
+
const next = (await listOutbox()).find((i) => i.status === "queued" || i.status === "sending");
|
|
11671
|
+
if (!next) break;
|
|
11672
|
+
let item = await update(next, { status: "sending", attempts: next.attempts + 1 });
|
|
11673
|
+
try {
|
|
11674
|
+
item = await prepared(item, sender);
|
|
11675
|
+
} catch (error) {
|
|
11676
|
+
const message = error instanceof Error ? error.message : "Could not encrypt the message";
|
|
11677
|
+
await update(item, { status: "failed", error: message });
|
|
11678
|
+
result.failed++;
|
|
11679
|
+
continue;
|
|
11680
|
+
}
|
|
11681
|
+
if (!unlocked2.has(item.account.id)) {
|
|
11682
|
+
await sender.unlock(item.account).catch(() => {
|
|
11683
|
+
});
|
|
11684
|
+
unlocked2.add(item.account.id);
|
|
11685
|
+
}
|
|
11686
|
+
const outcome = await attempt(item, sender);
|
|
11687
|
+
if (outcome.kind === "sent") {
|
|
11688
|
+
item = await update(item, {
|
|
11689
|
+
status: "sent",
|
|
11690
|
+
sentAt: Date.now(),
|
|
11691
|
+
error: void 0,
|
|
11692
|
+
inDoubt: false
|
|
11693
|
+
});
|
|
11694
|
+
result.sent++;
|
|
11695
|
+
} else if (outcome.kind === "offline") {
|
|
11696
|
+
await update(item, { status: "queued" });
|
|
11697
|
+
result.interrupted = true;
|
|
11698
|
+
break;
|
|
11699
|
+
} else {
|
|
11700
|
+
await update(item, {
|
|
11701
|
+
status: "failed",
|
|
11702
|
+
error: outcome.error,
|
|
11703
|
+
inDoubt: item.inDoubt || outcome.inDoubt
|
|
11704
|
+
});
|
|
11705
|
+
result.failed++;
|
|
11706
|
+
}
|
|
11707
|
+
}
|
|
11708
|
+
return result;
|
|
11709
|
+
}
|
|
11710
|
+
async function purgeSentOutbox(olderThanMs = 0) {
|
|
11711
|
+
const cutoff = Date.now() - olderThanMs;
|
|
11712
|
+
const done = (await listOutbox()).filter((i) => i.status === "sent" && (i.sentAt ?? 0) <= cutoff);
|
|
11713
|
+
if (done.length === 0) return;
|
|
11714
|
+
await deleteRecords(await Promise.all(done.map((i) => keyFor2(i.id))));
|
|
11715
|
+
notifyOfflineMailChange("outbox");
|
|
11716
|
+
}
|
|
11717
|
+
var STATUS_KEY = "status";
|
|
11718
|
+
var FULL_SYNC_EVERY_MS = 60 * 60 * 1e3;
|
|
11719
|
+
var MAX_PAGES = 200;
|
|
11720
|
+
async function getOfflineMailStatus() {
|
|
11721
|
+
return await getMeta(STATUS_KEY) ?? { messages: 0, bytes: 0, skipped: [] };
|
|
11722
|
+
}
|
|
11723
|
+
var MB = 1024 * 1024;
|
|
11724
|
+
var time = (m) => m.received_at ? Date.parse(m.received_at) || 0 : 0;
|
|
11725
|
+
var sameHeader = (a, b) => JSON.stringify(toHeader(a)) === JSON.stringify(toHeader(b));
|
|
11726
|
+
var Budget = class {
|
|
11727
|
+
constructor(used, quota) {
|
|
11728
|
+
this.used = used;
|
|
11729
|
+
this.quota = quota;
|
|
11730
|
+
}
|
|
11731
|
+
used;
|
|
11732
|
+
quota;
|
|
11733
|
+
get full() {
|
|
11734
|
+
return this.used >= this.quota;
|
|
11735
|
+
}
|
|
11736
|
+
take(bytes) {
|
|
11737
|
+
this.used += bytes;
|
|
11738
|
+
}
|
|
11739
|
+
};
|
|
11740
|
+
async function saveMessageContent(transport, account, header, settings, budget) {
|
|
11741
|
+
const message = await transport.message(account, header.id);
|
|
11742
|
+
await saveBody(account.id, message);
|
|
11743
|
+
budget.take((message.text?.length ?? 0) + (message.html?.length ?? 0));
|
|
11744
|
+
let attachmentBytes = 0;
|
|
11745
|
+
for (const attachment of message.attachments) {
|
|
11746
|
+
if (budget.full) return;
|
|
11747
|
+
if (attachmentBytes + attachment.size > settings.attachmentCapMb * MB) continue;
|
|
11748
|
+
if (await hasAttachment(account.id, message.id, attachment.id)) continue;
|
|
11749
|
+
const bytes = attachment.content_base64 ? Uint8Array.from(atob(attachment.content_base64), (c) => c.charCodeAt(0)) : await transport.attachment(account, message.id, attachment.id);
|
|
11750
|
+
await saveAttachment(account.id, message.id, attachment.id, bytes);
|
|
11751
|
+
attachmentBytes += bytes.byteLength;
|
|
11752
|
+
budget.take(bytes.byteLength);
|
|
11753
|
+
}
|
|
11754
|
+
}
|
|
11755
|
+
async function syncFolder(transport, account, folder, settings, windowStart, full, budget) {
|
|
11756
|
+
const saved = new Map(
|
|
11757
|
+
(await loadHeaders(account.id, folder.id)).map((h) => [h.message.id, h])
|
|
11758
|
+
);
|
|
11759
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11760
|
+
let oldestSeen = Infinity;
|
|
11761
|
+
let cursor = null;
|
|
11762
|
+
let reachedEnd = false;
|
|
11763
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
11764
|
+
const result = await transport.messages(account, folder.id, cursor);
|
|
11765
|
+
let anythingNew = false;
|
|
11766
|
+
let outOfScope = false;
|
|
11767
|
+
for (const message of result.messages) {
|
|
11768
|
+
const at = time(message);
|
|
11769
|
+
if (at && at < windowStart) {
|
|
11770
|
+
outOfScope = true;
|
|
11771
|
+
break;
|
|
11772
|
+
}
|
|
11773
|
+
seen.add(message.id);
|
|
11774
|
+
if (at) oldestSeen = Math.min(oldestSeen, at);
|
|
11775
|
+
const previous = saved.get(message.id)?.message;
|
|
11776
|
+
if (!previous || !sameHeader(previous, message)) {
|
|
11777
|
+
await saveHeader(account.id, folder.id, message);
|
|
11778
|
+
anythingNew = true;
|
|
11779
|
+
}
|
|
11780
|
+
if (!budget.full && !await hasBody(account.id, message.id)) {
|
|
11781
|
+
try {
|
|
11782
|
+
await saveMessageContent(transport, account, message, settings, budget);
|
|
11783
|
+
anythingNew = true;
|
|
11784
|
+
} catch (error) {
|
|
11785
|
+
logger.warn("[offline-mail] could not save a message body", error);
|
|
11786
|
+
}
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
cursor = result.next_cursor ?? null;
|
|
11790
|
+
if (outOfScope || !cursor) {
|
|
11791
|
+
reachedEnd = true;
|
|
11792
|
+
break;
|
|
11793
|
+
}
|
|
11794
|
+
if (!full && !anythingNew) break;
|
|
11795
|
+
}
|
|
11796
|
+
const stale = [...saved.values()].filter(
|
|
11797
|
+
(h) => !seen.has(h.message.id) && (reachedEnd || h.sortAt >= oldestSeen)
|
|
11798
|
+
);
|
|
11799
|
+
await removeKeys(stale.map((h) => h.key));
|
|
11800
|
+
}
|
|
11801
|
+
async function collectGarbage(accounts) {
|
|
11802
|
+
for (const account of accounts) {
|
|
11803
|
+
const folders = await loadFolders(account.id) ?? [];
|
|
11804
|
+
const expected = /* @__PURE__ */ new Set([await keyOf.folders(account.id)]);
|
|
11805
|
+
for (const folder of folders) {
|
|
11806
|
+
for (const { message } of await loadHeaders(account.id, folder.id)) {
|
|
11807
|
+
expected.add(await keyOf.body(account.id, message.id));
|
|
11808
|
+
for (const attachment of message.attachments)
|
|
11809
|
+
expected.add(await keyOf.attachment(account.id, message.id, attachment.id));
|
|
11810
|
+
}
|
|
11811
|
+
}
|
|
11812
|
+
const orphans = (await recordsInBucket(await bucketOf2.account(account.id))).filter((r) => (r.kind === "body" || r.kind === "attachment") && !expected.has(r.key)).map((r) => r.key);
|
|
11813
|
+
await removeKeys(orphans);
|
|
11814
|
+
}
|
|
11815
|
+
}
|
|
11816
|
+
async function forgetAccount(accountId) {
|
|
11817
|
+
for (const folder of await loadFolders(accountId) ?? []) {
|
|
11818
|
+
const records2 = await recordsInBucket(await bucketOf2.folder(accountId, folder.id));
|
|
11819
|
+
await removeKeys(records2.map((r) => r.key));
|
|
11820
|
+
}
|
|
11821
|
+
const records = await recordsInBucket(await bucketOf2.account(accountId));
|
|
11822
|
+
await removeKeys(records.filter((r) => r.kind !== "draft").map((r) => r.key));
|
|
11823
|
+
}
|
|
11824
|
+
async function enforceQuota(quotaBytes) {
|
|
11825
|
+
let used = (await allRecordSizes()).reduce((n, r) => n + r.size, 0);
|
|
11826
|
+
if (used <= quotaBytes) return used;
|
|
11827
|
+
for (const kind of ["attachment", "body"]) {
|
|
11828
|
+
const records = (await recordsOfKind(kind)).sort(
|
|
11829
|
+
(a, b) => kind === "attachment" ? a.lastAccess - b.lastAccess : a.sortAt - b.sortAt
|
|
11830
|
+
);
|
|
11831
|
+
const evict = [];
|
|
11832
|
+
for (const record of records) {
|
|
11833
|
+
if (used <= quotaBytes) break;
|
|
11834
|
+
evict.push(record.key);
|
|
11835
|
+
used -= record.size;
|
|
11836
|
+
}
|
|
11837
|
+
await removeKeys(evict);
|
|
11838
|
+
if (used <= quotaBytes) break;
|
|
11839
|
+
}
|
|
11840
|
+
return used;
|
|
11841
|
+
}
|
|
11842
|
+
async function syncMailbox(transport, settings, options = {}) {
|
|
11843
|
+
const now = options.now ?? Date.now();
|
|
11844
|
+
const previous = await getOfflineMailStatus();
|
|
11845
|
+
const full = options.full || !previous.lastFullSyncAt || now - previous.lastFullSyncAt > FULL_SYNC_EVERY_MS;
|
|
11846
|
+
const days = SCOPE_DAYS[settings.scope];
|
|
11847
|
+
const windowStart = days === null ? 0 : now - days * 24 * 60 * 60 * 1e3;
|
|
11848
|
+
const quota = settings.quotaMb * MB;
|
|
11849
|
+
const skipped = [];
|
|
11850
|
+
let error;
|
|
11851
|
+
try {
|
|
11852
|
+
await savePolicy(await transport.policy());
|
|
11853
|
+
const accounts = await transport.accounts();
|
|
11854
|
+
const before = await loadAccounts() ?? [];
|
|
11855
|
+
await saveAccounts(accounts);
|
|
11856
|
+
for (const gone of before.filter((b) => !accounts.some((a) => a.id === b.id)))
|
|
11857
|
+
await forgetAccount(gone.id);
|
|
11858
|
+
const budget = new Budget(
|
|
11859
|
+
(await allRecordSizes()).reduce((n, r) => n + r.size, 0),
|
|
11860
|
+
quota
|
|
11861
|
+
);
|
|
11862
|
+
for (const account of accounts) {
|
|
11863
|
+
if (account.needs_reconsent) {
|
|
11864
|
+
skipped.push(account.email);
|
|
11865
|
+
continue;
|
|
11866
|
+
}
|
|
11867
|
+
try {
|
|
11868
|
+
await transport.unlock(account);
|
|
11869
|
+
const folders = await transport.folders(account);
|
|
11870
|
+
const oldFolders = await loadFolders(account.id) ?? [];
|
|
11871
|
+
await saveFolders(account.id, folders);
|
|
11872
|
+
for (const gone of oldFolders.filter((o) => !folders.some((f) => f.id === o.id))) {
|
|
11873
|
+
const records = await recordsInBucket(await bucketOf2.folder(account.id, gone.id));
|
|
11874
|
+
await removeKeys(records.map((r) => r.key));
|
|
11875
|
+
}
|
|
11876
|
+
for (const folder of folders)
|
|
11877
|
+
await syncFolder(transport, account, folder, settings, windowStart, full, budget);
|
|
11878
|
+
notifyOfflineMailChange("mailbox");
|
|
11879
|
+
} catch (accountError) {
|
|
11880
|
+
if (accountError.status === void 0) throw accountError;
|
|
11881
|
+
logger.warn("[offline-mail] account sync failed", accountError);
|
|
11882
|
+
skipped.push(account.email);
|
|
11883
|
+
}
|
|
11884
|
+
}
|
|
11885
|
+
if (full) await collectGarbage(accounts);
|
|
11886
|
+
} catch (syncError) {
|
|
11887
|
+
error = syncError.status === void 0 ? "The connection dropped during sync. It will continue when you are back online." : syncError instanceof Error ? syncError.message : "Sync failed";
|
|
11888
|
+
}
|
|
11889
|
+
const bytes = await enforceQuota(quota);
|
|
11890
|
+
const messages = (await recordsOfKind("header")).length;
|
|
11891
|
+
const status = {
|
|
11892
|
+
lastSyncedAt: error ? previous.lastSyncedAt : now,
|
|
11893
|
+
lastFullSyncAt: !error && full ? now : previous.lastFullSyncAt,
|
|
11894
|
+
messages,
|
|
11895
|
+
bytes,
|
|
11896
|
+
error,
|
|
11897
|
+
skipped
|
|
11898
|
+
};
|
|
11899
|
+
await setMeta(STATUS_KEY, status);
|
|
11900
|
+
notifyOfflineMailChange("status");
|
|
11901
|
+
return status;
|
|
11902
|
+
}
|
|
11903
|
+
|
|
11904
|
+
// src/mail/offline/live.ts
|
|
11905
|
+
var OUTBOX_FOLDER = "outbox";
|
|
11906
|
+
function sessionUser(arbi) {
|
|
11907
|
+
return arbi.session?.getState().user?.external_id ?? null;
|
|
11908
|
+
}
|
|
11909
|
+
function hasLiveSession(arbi) {
|
|
11910
|
+
return !!arbi.session?.getState().serverSessionKey;
|
|
11911
|
+
}
|
|
11912
|
+
function canReachMail(arbi) {
|
|
11913
|
+
return onlineManager.isOnline() && hasLiveSession(arbi);
|
|
11914
|
+
}
|
|
11915
|
+
var unlocked = { nativeUnlocked: true };
|
|
11916
|
+
function mailCall(arbi, path, init = {}) {
|
|
11917
|
+
return mailJson((p, i) => arbi.mail.fetch(p, i, unlocked), path, init);
|
|
11918
|
+
}
|
|
11919
|
+
function liveTransport(arbi) {
|
|
11920
|
+
const unlock = async (account) => {
|
|
11921
|
+
if (!hasLiveSession(arbi))
|
|
11922
|
+
throw Object.assign(new Error("Sign in to unlock your mailbox"), { status: 401 });
|
|
11923
|
+
await arbi.mail.unlockAccount(account);
|
|
11924
|
+
};
|
|
11925
|
+
return {
|
|
11926
|
+
policy: () => mailCall(arbi, "/policy"),
|
|
11927
|
+
accounts: () => mailCall(arbi, "/accounts"),
|
|
11928
|
+
unlock,
|
|
11929
|
+
folders: (account) => mailCall(arbi, `${accountPath(account.id)}/folders`),
|
|
11930
|
+
messages: (account, folderId, cursor) => mailCall(
|
|
11931
|
+
arbi,
|
|
11932
|
+
`${accountPath(account.id)}/messages?${new URLSearchParams({
|
|
11933
|
+
folder_id: folderId,
|
|
11934
|
+
q: "",
|
|
11935
|
+
limit: "100",
|
|
11936
|
+
...cursor ? { cursor } : {}
|
|
11937
|
+
})}`
|
|
11938
|
+
),
|
|
11939
|
+
message: async (account, messageId) => {
|
|
11940
|
+
const path = `${accountPath(account.id)}/messages/${encodeURIComponent(messageId)}`;
|
|
11941
|
+
try {
|
|
11942
|
+
return await mailCall(arbi, path);
|
|
11943
|
+
} catch (error) {
|
|
11944
|
+
if (error.status !== 401 || account.provider !== "native")
|
|
11945
|
+
throw error;
|
|
11946
|
+
if (hasLiveSession(arbi)) await arbi.mail.unlockNative();
|
|
11947
|
+
return mailCall(arbi, path);
|
|
11948
|
+
}
|
|
11949
|
+
},
|
|
11950
|
+
attachment: async (account, messageId, attachmentId) => {
|
|
11951
|
+
const response = await arbi.mail.fetch(
|
|
11952
|
+
`${accountPath(account.id)}/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
11953
|
+
{},
|
|
11954
|
+
unlocked
|
|
11955
|
+
);
|
|
11956
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
11957
|
+
}
|
|
11958
|
+
};
|
|
11959
|
+
}
|
|
11960
|
+
function liveSender(arbi) {
|
|
11961
|
+
const transport = liveTransport(arbi);
|
|
11962
|
+
return {
|
|
11963
|
+
prepareSecure: async (item) => {
|
|
11964
|
+
if (item.account.provider !== "native")
|
|
11965
|
+
throw new Error("Choose your native inbox to send securely");
|
|
11966
|
+
return arbi.secureMail.prepare(item.compose, item.account, item.requestId);
|
|
11967
|
+
},
|
|
11968
|
+
unlock: async (account) => {
|
|
11969
|
+
if (account.provider !== "native") await transport.unlock(account);
|
|
11970
|
+
},
|
|
11971
|
+
send: (item) => item.secure ? mailJson((p, i) => arbi.mail.fetch(p, i), "/secure/send", {
|
|
11972
|
+
method: "POST",
|
|
11973
|
+
body: JSON.stringify(item.securePayload)
|
|
11974
|
+
}) : mailJson(
|
|
11975
|
+
(p, i) => arbi.mail.fetch(p, i),
|
|
11976
|
+
`${accountPath(item.account.id)}/send`,
|
|
11977
|
+
{
|
|
11978
|
+
method: "POST",
|
|
11979
|
+
body: JSON.stringify({ ...item.compose, request_id: item.requestId })
|
|
11980
|
+
}
|
|
11981
|
+
)
|
|
11982
|
+
};
|
|
11983
|
+
}
|
|
11984
|
+
var localLocks = /* @__PURE__ */ new Set();
|
|
11985
|
+
async function exclusive(name, fn) {
|
|
11986
|
+
if (typeof navigator !== "undefined" && navigator.locks?.request) {
|
|
11987
|
+
return navigator.locks.request(name, { ifAvailable: true }, async (lock) => {
|
|
11988
|
+
if (!lock) return false;
|
|
11989
|
+
await fn();
|
|
11990
|
+
return true;
|
|
11991
|
+
});
|
|
11992
|
+
}
|
|
11993
|
+
if (localLocks.has(name)) return false;
|
|
11994
|
+
localLocks.add(name);
|
|
11995
|
+
try {
|
|
11996
|
+
await fn();
|
|
11997
|
+
return true;
|
|
11998
|
+
} finally {
|
|
11999
|
+
localLocks.delete(name);
|
|
12000
|
+
}
|
|
12001
|
+
}
|
|
12002
|
+
var retryTimer = null;
|
|
12003
|
+
async function flushOutbox(context) {
|
|
12004
|
+
const { arbi, queryClient, openOutbox } = context;
|
|
12005
|
+
if (!canReachMail(arbi)) return;
|
|
12006
|
+
const ran = await exclusive("arbi-mail-outbox", async () => {
|
|
12007
|
+
const result = await flushOutboxOnce(liveSender(arbi), () => onlineManager.isOnline());
|
|
12008
|
+
if (result.sent > 0) void queryClient.invalidateQueries({ queryKey: mailQueryKeys.all() });
|
|
12009
|
+
const remaining = (await listOutbox()).filter((i) => i.status !== "sent");
|
|
12010
|
+
if (result.failed > 0) {
|
|
12011
|
+
notify.error(
|
|
12012
|
+
result.failed === 1 ? "A message in your Outbox couldn't be sent" : `${result.failed} messages in your Outbox couldn't be sent`,
|
|
12013
|
+
{
|
|
12014
|
+
description: "Open the Outbox to see why, then retry, edit or delete it.",
|
|
12015
|
+
...openOutbox && { action: { label: "Open Outbox", onClick: openOutbox } }
|
|
12016
|
+
}
|
|
12017
|
+
);
|
|
12018
|
+
} else if (result.sent > 0 && remaining.length === 0) {
|
|
12019
|
+
notify.success(
|
|
12020
|
+
result.sent === 1 ? "Your Outbox message was sent" : `${result.sent} Outbox messages sent`
|
|
12021
|
+
);
|
|
12022
|
+
}
|
|
12023
|
+
setTimeout(() => void purgeSentOutbox(3e4), 31e3);
|
|
12024
|
+
});
|
|
12025
|
+
if (!ran) {
|
|
12026
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
12027
|
+
retryTimer = setTimeout(() => void flushOutbox(context), 3e3);
|
|
12028
|
+
}
|
|
12029
|
+
}
|
|
12030
|
+
async function uploadOfflineDrafts({
|
|
12031
|
+
arbi,
|
|
12032
|
+
queryClient
|
|
12033
|
+
}) {
|
|
12034
|
+
if (!canReachMail(arbi)) return;
|
|
12035
|
+
await exclusive("arbi-mail-drafts", async () => {
|
|
12036
|
+
const uploaded = await syncLocalDrafts(
|
|
12037
|
+
({ accountId, compose }) => mailJson(
|
|
12038
|
+
(p, i) => arbi.mail.fetch(p, i),
|
|
12039
|
+
`${accountPath(accountId)}/drafts`,
|
|
12040
|
+
{
|
|
12041
|
+
method: "POST",
|
|
12042
|
+
body: JSON.stringify(compose)
|
|
12043
|
+
}
|
|
12044
|
+
)
|
|
12045
|
+
);
|
|
12046
|
+
if (uploaded > 0) {
|
|
12047
|
+
void queryClient.invalidateQueries({ queryKey: mailQueryKeys.allMessages() });
|
|
12048
|
+
notify.success(
|
|
12049
|
+
uploaded === 1 ? "Your offline draft was saved to Drafts" : `${uploaded} offline drafts were saved to Drafts`
|
|
12050
|
+
);
|
|
12051
|
+
}
|
|
12052
|
+
});
|
|
12053
|
+
}
|
|
12054
|
+
var syncing = false;
|
|
12055
|
+
function isMailSyncRunning() {
|
|
12056
|
+
return syncing;
|
|
12057
|
+
}
|
|
12058
|
+
async function syncOfflineMailbox({ arbi }, options = {}) {
|
|
12059
|
+
const settings = getOfflineMailSettings(sessionUser(arbi));
|
|
12060
|
+
if (!settings.enabled || !canReachMail(arbi)) return;
|
|
12061
|
+
await exclusive("arbi-mail-sync", async () => {
|
|
12062
|
+
syncing = true;
|
|
12063
|
+
notifyOfflineMailChange("status");
|
|
12064
|
+
try {
|
|
12065
|
+
await syncMailbox(liveTransport(arbi), settings, options);
|
|
12066
|
+
} catch (error) {
|
|
12067
|
+
logger.error("[offline-mail] sync failed", error);
|
|
12068
|
+
} finally {
|
|
12069
|
+
syncing = false;
|
|
12070
|
+
notifyOfflineMailChange("status");
|
|
12071
|
+
}
|
|
12072
|
+
});
|
|
12073
|
+
}
|
|
12074
|
+
|
|
12075
|
+
// src/mail/offline/reads.ts
|
|
12076
|
+
var NOT_SAVED_HINT = 'Turn on "Keep my mailbox available offline" in Mail settings to read mail without a connection.';
|
|
12077
|
+
async function onlineFirst(online, offline, unavailable) {
|
|
12078
|
+
if (onlineManager.isOnline()) {
|
|
12079
|
+
try {
|
|
12080
|
+
return await online();
|
|
12081
|
+
} catch (error) {
|
|
12082
|
+
if (!isNetworkFailure(error)) throw error;
|
|
12083
|
+
}
|
|
12084
|
+
}
|
|
12085
|
+
const saved = await offline().catch(() => void 0);
|
|
12086
|
+
if (saved !== void 0) return saved;
|
|
12087
|
+
throw new MailOfflineUnavailableError(unavailable);
|
|
12088
|
+
}
|
|
12089
|
+
function offlineMailReads(getArbi, { keepsMail = () => true } = {}) {
|
|
12090
|
+
const call = (path) => mailJson((p, i) => getArbi().mail.fetch(p, i), path);
|
|
12091
|
+
const storeEnabled = () => getOfflineMailSettings(sessionUser(getArbi())).enabled;
|
|
12092
|
+
const remember = (write2) => {
|
|
12093
|
+
if (!keepsMail()) return;
|
|
12094
|
+
write2().catch((error) => logger.warn("[offline-mail] could not save for offline use", error));
|
|
12095
|
+
};
|
|
12096
|
+
return {
|
|
12097
|
+
getPolicy: () => onlineFirst(
|
|
12098
|
+
async () => {
|
|
12099
|
+
const policy = await call("/policy");
|
|
12100
|
+
remember(() => savePolicy(policy));
|
|
12101
|
+
return policy;
|
|
12102
|
+
},
|
|
12103
|
+
loadPolicy,
|
|
12104
|
+
"You're offline. Mail opens here once you've used it online on this device."
|
|
12105
|
+
),
|
|
12106
|
+
listAccounts: () => onlineFirst(
|
|
12107
|
+
async () => {
|
|
12108
|
+
const accounts = await call("/accounts");
|
|
12109
|
+
remember(() => saveAccounts(accounts));
|
|
12110
|
+
return accounts;
|
|
12111
|
+
},
|
|
12112
|
+
loadAccounts,
|
|
12113
|
+
"You're offline. Your mail accounts appear here once you've opened Mail online on this device."
|
|
12114
|
+
),
|
|
12115
|
+
listFolders: (account) => onlineFirst(
|
|
12116
|
+
async () => {
|
|
12117
|
+
const folders = await call(`${accountPath(account.id)}/folders`);
|
|
12118
|
+
if (storeEnabled()) remember(() => saveFolders(account.id, folders));
|
|
12119
|
+
return folders;
|
|
12120
|
+
},
|
|
12121
|
+
() => loadFolders(account.id),
|
|
12122
|
+
`You're offline and this mailbox isn't saved on this device. ${NOT_SAVED_HINT}`
|
|
12123
|
+
),
|
|
12124
|
+
listMessages: (account, folderId, query, cursor) => onlineFirst(
|
|
12125
|
+
async () => {
|
|
12126
|
+
const page = await call(
|
|
12127
|
+
`${accountPath(account.id)}/messages?${new URLSearchParams({ folder_id: folderId, q: query, ...cursor ? { cursor } : {} })}`
|
|
12128
|
+
);
|
|
12129
|
+
if (storeEnabled() && !query)
|
|
12130
|
+
remember(async () => {
|
|
12131
|
+
for (const message of page.messages) await saveHeader(account.id, folderId, message);
|
|
12132
|
+
});
|
|
12133
|
+
return page;
|
|
12134
|
+
},
|
|
12135
|
+
async () => {
|
|
12136
|
+
if (!await hasFolder(account.id)) return void 0;
|
|
12137
|
+
return { messages: await searchFolder(account.id, folderId, query), fromDevice: true };
|
|
12138
|
+
},
|
|
12139
|
+
`You're offline and this folder isn't saved on this device. ${NOT_SAVED_HINT}`
|
|
12140
|
+
),
|
|
12141
|
+
getMessage: (account, messageId) => onlineFirst(
|
|
12142
|
+
async () => {
|
|
12143
|
+
const message = await call(
|
|
12144
|
+
`${accountPath(account.id)}/messages/${encodeURIComponent(messageId)}`
|
|
12145
|
+
);
|
|
12146
|
+
if (storeEnabled()) remember(() => saveBody(account.id, message));
|
|
12147
|
+
return message;
|
|
12148
|
+
},
|
|
12149
|
+
() => loadBody(account.id, messageId),
|
|
12150
|
+
storeEnabled() ? "You're offline and this message hasn't been saved on this device yet." : `You're offline. ${NOT_SAVED_HINT}`
|
|
12151
|
+
)
|
|
12152
|
+
};
|
|
12153
|
+
}
|
|
12154
|
+
|
|
12155
|
+
// src/mail/offline/wipe.ts
|
|
12156
|
+
var OWNER_KEY2 = "owner";
|
|
12157
|
+
async function wipeOfflineMail() {
|
|
12158
|
+
forgetOfflineMailKeys();
|
|
12159
|
+
await deleteOfflineMailDatabase();
|
|
12160
|
+
notifyOfflineMailChange("wiped");
|
|
12161
|
+
}
|
|
12162
|
+
async function claimOfflineMail(userExtId) {
|
|
12163
|
+
const owner = await getMeta(OWNER_KEY2);
|
|
12164
|
+
if (owner && owner !== userExtId) await wipeOfflineMail();
|
|
12165
|
+
await setMeta(OWNER_KEY2, userExtId);
|
|
12166
|
+
}
|
|
12167
|
+
|
|
12168
|
+
// src/mail/offline/runtime.ts
|
|
12169
|
+
var SYNC_INTERVAL_MS = 5 * 60 * 1e3;
|
|
12170
|
+
var NEW_MAIL_DEBOUNCE_MS = 5e3;
|
|
12171
|
+
function useOfflineMailRuntime({ openOutbox } = {}) {
|
|
12172
|
+
const { user, status, serverSessionKey } = useArbiSession();
|
|
12173
|
+
const userExtId = user?.external_id;
|
|
12174
|
+
const authenticated = status === "signed-in";
|
|
12175
|
+
const mailEnabled = useFeature("mail");
|
|
12176
|
+
const entitled = useIsEntitled("mail");
|
|
12177
|
+
const hasSession = !!serverSessionKey;
|
|
12178
|
+
const active = authenticated && mailEnabled && entitled && !!userExtId;
|
|
12179
|
+
const queryClient = useQueryClient();
|
|
12180
|
+
const arbi = useArbi();
|
|
12181
|
+
const latest = useRef({ arbi, queryClient, openOutbox });
|
|
12182
|
+
latest.current = { arbi, queryClient, openOutbox };
|
|
12183
|
+
useEffect(() => {
|
|
12184
|
+
if (!active || !userExtId) return;
|
|
12185
|
+
claimOfflineMail(userExtId).catch(
|
|
12186
|
+
(error) => logger.warn("[offline-mail] could not bind the store", error)
|
|
12187
|
+
);
|
|
12188
|
+
}, [active, userExtId]);
|
|
12189
|
+
useEffect(() => {
|
|
12190
|
+
if (!active || !hasSession) return;
|
|
12191
|
+
const context = () => latest.current;
|
|
12192
|
+
const catchUp = async () => {
|
|
12193
|
+
await flushOutbox(context());
|
|
12194
|
+
await uploadOfflineDrafts(context());
|
|
12195
|
+
await syncOfflineMailbox(context());
|
|
12196
|
+
};
|
|
12197
|
+
const run2 = () => {
|
|
12198
|
+
catchUp().catch((error) => logger.warn("[offline-mail] catch-up failed", error));
|
|
12199
|
+
};
|
|
12200
|
+
run2();
|
|
12201
|
+
let wasOnline = onlineManager.isOnline();
|
|
12202
|
+
const offOnline = onlineManager.subscribe((online) => {
|
|
12203
|
+
if (online && !wasOnline) run2();
|
|
12204
|
+
wasOnline = online;
|
|
12205
|
+
});
|
|
12206
|
+
const interval = setInterval(() => {
|
|
12207
|
+
if (document.visibilityState === "visible") void syncOfflineMailbox(context());
|
|
12208
|
+
}, SYNC_INTERVAL_MS);
|
|
12209
|
+
const enabled = () => getOfflineMailSettings(sessionUser(context().arbi)).enabled;
|
|
12210
|
+
let newMailTimer = null;
|
|
12211
|
+
const offCache = queryClient.getQueryCache().subscribe((event) => {
|
|
12212
|
+
if (event.type !== "updated" || event.action.type !== "invalidate") return;
|
|
12213
|
+
if (!isUnderQueryKey(event.query.queryKey, mailQueryKeys.all()) || !enabled()) return;
|
|
12214
|
+
if (newMailTimer) clearTimeout(newMailTimer);
|
|
12215
|
+
newMailTimer = setTimeout(() => void syncOfflineMailbox(context()), NEW_MAIL_DEBOUNCE_MS);
|
|
12216
|
+
});
|
|
12217
|
+
const offSettings = onOfflineMailChange((change) => {
|
|
12218
|
+
if (change === "settings" && enabled()) void syncOfflineMailbox(context(), { full: true });
|
|
12219
|
+
if (change === "outbox") void flushOutbox(context());
|
|
12220
|
+
if (change === "drafts") void uploadOfflineDrafts(context());
|
|
12221
|
+
});
|
|
12222
|
+
return () => {
|
|
12223
|
+
offOnline();
|
|
12224
|
+
clearInterval(interval);
|
|
12225
|
+
if (newMailTimer) clearTimeout(newMailTimer);
|
|
12226
|
+
offCache();
|
|
12227
|
+
offSettings();
|
|
12228
|
+
};
|
|
12229
|
+
}, [active, hasSession, queryClient]);
|
|
12230
|
+
}
|
|
12231
|
+
function useStoreValue(load, initial, changes) {
|
|
12232
|
+
const [value, setValue] = useState(initial);
|
|
12233
|
+
const key = changes.join(",");
|
|
12234
|
+
useEffect(() => {
|
|
12235
|
+
let alive = true;
|
|
12236
|
+
const read = () => load().then((next) => alive && setValue(next)).catch((error) => logger.warn("[offline-mail] read failed", error));
|
|
12237
|
+
void read();
|
|
12238
|
+
const off = onOfflineMailChange((change) => {
|
|
12239
|
+
if (change === "wiped") setValue(initial);
|
|
12240
|
+
if (change === "wiped" || key.split(",").includes(change)) void read();
|
|
12241
|
+
});
|
|
12242
|
+
return () => {
|
|
12243
|
+
alive = false;
|
|
12244
|
+
off();
|
|
12245
|
+
};
|
|
12246
|
+
}, [key, load, initial]);
|
|
12247
|
+
return value;
|
|
12248
|
+
}
|
|
12249
|
+
function useOfflineMailSettings() {
|
|
12250
|
+
const userExtId = useArbiSession().user?.external_id;
|
|
12251
|
+
const subscribe = useCallback(
|
|
12252
|
+
(onChange) => onOfflineMailChange((change) => {
|
|
12253
|
+
if (change === "settings") onChange();
|
|
12254
|
+
}),
|
|
12255
|
+
[]
|
|
12256
|
+
);
|
|
12257
|
+
const raw = useSyncExternalStore(
|
|
12258
|
+
subscribe,
|
|
12259
|
+
() => JSON.stringify([userExtId, getOfflineMailSettings(userExtId)])
|
|
12260
|
+
);
|
|
12261
|
+
const settings = useMemo(() => JSON.parse(raw)[1], [raw]);
|
|
12262
|
+
const update2 = useCallback(
|
|
12263
|
+
(patch) => void setOfflineMailSettings(userExtId, patch),
|
|
12264
|
+
[userExtId]
|
|
12265
|
+
);
|
|
12266
|
+
return [settings, update2];
|
|
12267
|
+
}
|
|
12268
|
+
var EMPTY_STATUS = { messages: 0, bytes: 0, skipped: [] };
|
|
12269
|
+
function useOfflineMailStatus() {
|
|
12270
|
+
const status = useStoreValue(getOfflineMailStatus, EMPTY_STATUS, ["status", "mailbox"]);
|
|
12271
|
+
const [syncing2, setSyncing] = useState(isMailSyncRunning);
|
|
12272
|
+
useEffect(
|
|
12273
|
+
() => onOfflineMailChange((change) => {
|
|
12274
|
+
if (change === "status") setSyncing(isMailSyncRunning());
|
|
12275
|
+
}),
|
|
12276
|
+
[]
|
|
12277
|
+
);
|
|
12278
|
+
return { ...status, syncing: syncing2 };
|
|
12279
|
+
}
|
|
12280
|
+
var NO_ITEMS = [];
|
|
12281
|
+
function useOutbox(enabled = true) {
|
|
12282
|
+
const load = useCallback(() => enabled ? listOutbox() : Promise.resolve(NO_ITEMS), [enabled]);
|
|
12283
|
+
return useStoreValue(load, NO_ITEMS, ["outbox"]);
|
|
12284
|
+
}
|
|
12285
|
+
var NO_DRAFTS = [];
|
|
12286
|
+
function useLocalDrafts(accountId) {
|
|
12287
|
+
const load = useCallback(
|
|
12288
|
+
() => accountId ? listLocalDrafts(accountId) : Promise.resolve(NO_DRAFTS),
|
|
12289
|
+
[accountId]
|
|
12290
|
+
);
|
|
12291
|
+
return useStoreValue(load, NO_DRAFTS, ["drafts"]);
|
|
12292
|
+
}
|
|
12293
|
+
function useFlushOutbox() {
|
|
12294
|
+
const arbi = useArbi();
|
|
12295
|
+
const queryClient = useQueryClient();
|
|
12296
|
+
return useCallback(() => flushOutbox({ arbi, queryClient }), [arbi, queryClient]);
|
|
12297
|
+
}
|
|
12298
|
+
function useSyncOfflineMailbox() {
|
|
12299
|
+
const arbi = useArbi();
|
|
12300
|
+
return useCallback((options) => syncOfflineMailbox({ arbi }, options), [arbi]);
|
|
12301
|
+
}
|
|
12302
|
+
|
|
12303
|
+
// src/mail/offline/confirm-sign-out.ts
|
|
12304
|
+
async function confirmLosingOfflineMail(confirm) {
|
|
12305
|
+
const [outbox, drafts] = await Promise.all([
|
|
12306
|
+
listOutbox().catch(() => []),
|
|
12307
|
+
listLocalDrafts().catch(() => [])
|
|
12308
|
+
]);
|
|
12309
|
+
const unsent = outbox.filter((item) => item.status !== "sent").length;
|
|
12310
|
+
if (unsent === 0 && drafts.length === 0) return true;
|
|
12311
|
+
const parts = [
|
|
12312
|
+
unsent > 0 ? `${unsent} unsent ${unsent === 1 ? "message" : "messages"} in your Outbox` : "",
|
|
12313
|
+
drafts.length > 0 ? `${drafts.length} ${drafts.length === 1 ? "draft" : "drafts"} saved only on this device` : ""
|
|
12314
|
+
].filter(Boolean);
|
|
12315
|
+
return confirm({
|
|
12316
|
+
message: `You have ${parts.join(" and ")}. Signing out deletes ${unsent + drafts.length === 1 ? "it" : "them"} from this device. Reconnect first to send and save them.`,
|
|
12317
|
+
title: "Sign out and delete unsent mail?"
|
|
12318
|
+
});
|
|
12319
|
+
}
|
|
11101
12320
|
function useConnectorMutation(mutationFn) {
|
|
11102
12321
|
const qc = useQueryClient();
|
|
11103
12322
|
return useMutation({
|
|
@@ -11565,9 +12784,9 @@ function useDeleteCalendar() {
|
|
|
11565
12784
|
function useCreateEvent() {
|
|
11566
12785
|
const arbi = useArbi();
|
|
11567
12786
|
const qc = useQueryClient();
|
|
11568
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12787
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11569
12788
|
return useMutation({
|
|
11570
|
-
mutationFn: (body) =>
|
|
12789
|
+
mutationFn: (body) => write2({ kind: "create", body }, () => arbi.calendar.createEvent(body)),
|
|
11571
12790
|
networkMode,
|
|
11572
12791
|
onSuccess: () => {
|
|
11573
12792
|
void invalidateCalendarEvents(qc);
|
|
@@ -11577,9 +12796,9 @@ function useCreateEvent() {
|
|
|
11577
12796
|
function useUpdateEvent() {
|
|
11578
12797
|
const arbi = useArbi();
|
|
11579
12798
|
const qc = useQueryClient();
|
|
11580
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12799
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11581
12800
|
return useMutation({
|
|
11582
|
-
mutationFn: ({ target, body }) =>
|
|
12801
|
+
mutationFn: ({ target, body }) => write2({ kind: "update", target, body }, () => arbi.calendar.updateEvent(target, body)),
|
|
11583
12802
|
networkMode,
|
|
11584
12803
|
onSuccess: () => {
|
|
11585
12804
|
void invalidateCalendarEvents(qc);
|
|
@@ -11589,9 +12808,9 @@ function useUpdateEvent() {
|
|
|
11589
12808
|
function useDeleteEvent() {
|
|
11590
12809
|
const arbi = useArbi();
|
|
11591
12810
|
const qc = useQueryClient();
|
|
11592
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12811
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11593
12812
|
return useMutation({
|
|
11594
|
-
mutationFn: (target) =>
|
|
12813
|
+
mutationFn: (target) => write2({ kind: "delete", target }, () => arbi.calendar.deleteEvent(target)),
|
|
11595
12814
|
networkMode,
|
|
11596
12815
|
onSuccess: () => {
|
|
11597
12816
|
void invalidateCalendarEvents(qc);
|
|
@@ -11611,9 +12830,9 @@ function useMoveEvent() {
|
|
|
11611
12830
|
function useRespondToEvent() {
|
|
11612
12831
|
const arbi = useArbi();
|
|
11613
12832
|
const qc = useQueryClient();
|
|
11614
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12833
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11615
12834
|
return useMutation({
|
|
11616
|
-
mutationFn: (params) =>
|
|
12835
|
+
mutationFn: (params) => write2({ kind: "respond", params }, () => arbi.calendar.respond(params)),
|
|
11617
12836
|
networkMode,
|
|
11618
12837
|
onSuccess: () => {
|
|
11619
12838
|
void invalidateCalendarEvents(qc);
|
|
@@ -11853,7 +13072,7 @@ function useSyncAccountsOnOpen(enabled = true) {
|
|
|
11853
13072
|
const arbi = useArbi();
|
|
11854
13073
|
const { data: accounts } = useCalendarAccounts(enabled);
|
|
11855
13074
|
const qc = useQueryClient();
|
|
11856
|
-
const [
|
|
13075
|
+
const [syncing2, setSyncing] = useState(false);
|
|
11857
13076
|
useEffect(() => {
|
|
11858
13077
|
if (!enabled || !accounts?.length) return;
|
|
11859
13078
|
const due = accounts.filter((a) => !syncedThisSession.has(a.id));
|
|
@@ -11865,7 +13084,7 @@ function useSyncAccountsOnOpen(enabled = true) {
|
|
|
11865
13084
|
void invalidateAccounts(qc);
|
|
11866
13085
|
});
|
|
11867
13086
|
}, [enabled, accounts, arbi, qc]);
|
|
11868
|
-
return { syncing };
|
|
13087
|
+
return { syncing: syncing2 };
|
|
11869
13088
|
}
|
|
11870
13089
|
function useBookingPages(enabled = true) {
|
|
11871
13090
|
const arbi = useArbi();
|
|
@@ -11893,6 +13112,349 @@ function useDeleteBookingPage() {
|
|
|
11893
13112
|
onSuccess: () => qc.invalidateQueries({ queryKey: calendarQueryKeys.bookingPages() })
|
|
11894
13113
|
});
|
|
11895
13114
|
}
|
|
13115
|
+
|
|
13116
|
+
// src/offline-store/wipe.ts
|
|
13117
|
+
async function wipeStore(sealer3, store3) {
|
|
13118
|
+
sealer3.forget();
|
|
13119
|
+
await store3.deleteDatabase();
|
|
13120
|
+
}
|
|
13121
|
+
|
|
13122
|
+
// src/calendar/offline/outbox.ts
|
|
13123
|
+
var OFFLINE_CALENDAR_DB = "arbi-offline-calendar";
|
|
13124
|
+
var CACHE_LIMIT = 200;
|
|
13125
|
+
var OfflineCalendarLockedError = class extends Error {
|
|
13126
|
+
constructor() {
|
|
13127
|
+
super("Sign in to open the calendar saved on this device");
|
|
13128
|
+
this.name = "OfflineCalendarLockedError";
|
|
13129
|
+
}
|
|
13130
|
+
};
|
|
13131
|
+
var store2 = createRecordStore(OFFLINE_CALENDAR_DB);
|
|
13132
|
+
var sealer2 = createOfflineSealer("arbi-offline-calendar-v1", {
|
|
13133
|
+
locked: () => new OfflineCalendarLockedError()
|
|
13134
|
+
});
|
|
13135
|
+
function isCalendarOffline() {
|
|
13136
|
+
return isBrowserOffline();
|
|
13137
|
+
}
|
|
13138
|
+
function isCalendarNetworkError(error) {
|
|
13139
|
+
if (isNetworkFailure(error)) return true;
|
|
13140
|
+
const status = error?.status;
|
|
13141
|
+
return typeof status !== "number" && isBrowserOffline();
|
|
13142
|
+
}
|
|
13143
|
+
var cacheBucket = () => sealer2.blind("bucket", "cache");
|
|
13144
|
+
var cacheKey = (key) => sealer2.blind("cache", key);
|
|
13145
|
+
async function writeCalendarCache(key, data) {
|
|
13146
|
+
const sealed = await sealer2.sealJson({ key, data });
|
|
13147
|
+
const now = Date.now();
|
|
13148
|
+
await store2.putRecord({
|
|
13149
|
+
key: await cacheKey(key),
|
|
13150
|
+
kind: "cache",
|
|
13151
|
+
bucket: await cacheBucket(),
|
|
13152
|
+
sortAt: now,
|
|
13153
|
+
size: sealed.length,
|
|
13154
|
+
lastAccess: now,
|
|
13155
|
+
data: sealed
|
|
13156
|
+
});
|
|
13157
|
+
await evictCache();
|
|
13158
|
+
}
|
|
13159
|
+
async function readCalendarCache(key) {
|
|
13160
|
+
try {
|
|
13161
|
+
const blindKey = await cacheKey(key);
|
|
13162
|
+
const record = await store2.getRecord(blindKey);
|
|
13163
|
+
if (!record) return null;
|
|
13164
|
+
const opened = await sealer2.openJson(record.data);
|
|
13165
|
+
if (opened.key !== key) return null;
|
|
13166
|
+
await store2.putRecord({ ...record, lastAccess: Date.now() });
|
|
13167
|
+
return opened.data;
|
|
13168
|
+
} catch {
|
|
13169
|
+
return null;
|
|
13170
|
+
}
|
|
13171
|
+
}
|
|
13172
|
+
async function evictCache() {
|
|
13173
|
+
const rows = (await store2.allRecordAccess()).filter((r) => r.kind === "cache");
|
|
13174
|
+
if (rows.length <= CACHE_LIMIT) return;
|
|
13175
|
+
rows.sort((a, b) => a.lastAccess - b.lastAccess);
|
|
13176
|
+
await store2.deleteRecords(rows.slice(0, rows.length - CACHE_LIMIT).map((r) => r.key));
|
|
13177
|
+
}
|
|
13178
|
+
var outboxBucket = () => sealer2.blind("bucket", "outbox");
|
|
13179
|
+
var outboxKey = (requestId) => sealer2.blind("outbox", requestId);
|
|
13180
|
+
var seqCounter2 = 0;
|
|
13181
|
+
async function writeItem(item) {
|
|
13182
|
+
const sealed = await sealer2.sealJson(item);
|
|
13183
|
+
await store2.putRecord({
|
|
13184
|
+
key: await outboxKey(item.requestId),
|
|
13185
|
+
kind: "outbox",
|
|
13186
|
+
bucket: await outboxBucket(),
|
|
13187
|
+
sortAt: item.seq,
|
|
13188
|
+
size: sealed.length,
|
|
13189
|
+
lastAccess: Date.now(),
|
|
13190
|
+
data: sealed
|
|
13191
|
+
});
|
|
13192
|
+
}
|
|
13193
|
+
async function readItems() {
|
|
13194
|
+
const records = await store2.recordsInBucket(await outboxBucket());
|
|
13195
|
+
const items = [];
|
|
13196
|
+
for (const record of records) {
|
|
13197
|
+
try {
|
|
13198
|
+
items.push(await sealer2.openJson(record.data));
|
|
13199
|
+
} catch {
|
|
13200
|
+
}
|
|
13201
|
+
}
|
|
13202
|
+
return items.sort((a, b) => a.seq - b.seq);
|
|
13203
|
+
}
|
|
13204
|
+
function newRequestId() {
|
|
13205
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
13206
|
+
}
|
|
13207
|
+
async function enqueueCalendarWrite(op) {
|
|
13208
|
+
const now = Date.now();
|
|
13209
|
+
seqCounter2 = Math.max(seqCounter2 + 1, now * 1e3);
|
|
13210
|
+
const item = {
|
|
13211
|
+
requestId: newRequestId(),
|
|
13212
|
+
op,
|
|
13213
|
+
state: "queued",
|
|
13214
|
+
attempts: 0,
|
|
13215
|
+
createdAt: now,
|
|
13216
|
+
seq: seqCounter2
|
|
13217
|
+
};
|
|
13218
|
+
await writeItem(item);
|
|
13219
|
+
notify2("enqueued");
|
|
13220
|
+
return { requestId: item.requestId };
|
|
13221
|
+
}
|
|
13222
|
+
async function listCalendarOutbox() {
|
|
13223
|
+
try {
|
|
13224
|
+
return (await readItems()).map(({ requestId, op, state, inDoubt, error }) => ({
|
|
13225
|
+
requestId,
|
|
13226
|
+
op,
|
|
13227
|
+
state,
|
|
13228
|
+
inDoubt,
|
|
13229
|
+
error
|
|
13230
|
+
}));
|
|
13231
|
+
} catch {
|
|
13232
|
+
return [];
|
|
13233
|
+
}
|
|
13234
|
+
}
|
|
13235
|
+
function classify2(error) {
|
|
13236
|
+
if (isCalendarNetworkError(error)) return "offline";
|
|
13237
|
+
const status = error?.status;
|
|
13238
|
+
if (typeof status === "number" && status >= 500) return "retry";
|
|
13239
|
+
return "failed";
|
|
13240
|
+
}
|
|
13241
|
+
function run(exec, item) {
|
|
13242
|
+
const op = item.op;
|
|
13243
|
+
switch (op.kind) {
|
|
13244
|
+
case "create":
|
|
13245
|
+
return exec.create(op.body, item.requestId);
|
|
13246
|
+
case "update":
|
|
13247
|
+
return exec.update(op.target, op.body, item.requestId);
|
|
13248
|
+
case "delete":
|
|
13249
|
+
return exec.delete(op.target, item.requestId);
|
|
13250
|
+
case "respond":
|
|
13251
|
+
return exec.respond(op.params, item.requestId);
|
|
13252
|
+
}
|
|
13253
|
+
}
|
|
13254
|
+
var replaying = null;
|
|
13255
|
+
function replayCalendarOutbox(exec) {
|
|
13256
|
+
if (replaying) return replaying;
|
|
13257
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
13258
|
+
const run2 = locks?.request ? locks.request(
|
|
13259
|
+
"arbi-calendar-outbox-replay",
|
|
13260
|
+
() => replayOnce(exec)
|
|
13261
|
+
) : replayOnce(exec);
|
|
13262
|
+
const current = run2.finally(() => {
|
|
13263
|
+
replaying = null;
|
|
13264
|
+
});
|
|
13265
|
+
replaying = current;
|
|
13266
|
+
return current;
|
|
13267
|
+
}
|
|
13268
|
+
async function replayOnce(exec) {
|
|
13269
|
+
const result = { sent: 0, failed: 0, inDoubt: 0 };
|
|
13270
|
+
for (const queued of await readItems()) {
|
|
13271
|
+
if (queued.state !== "queued" && queued.state !== "sending") continue;
|
|
13272
|
+
if (isCalendarOffline()) break;
|
|
13273
|
+
let item = {
|
|
13274
|
+
...queued,
|
|
13275
|
+
inDoubt: queued.inDoubt || queued.state === "sending"
|
|
13276
|
+
};
|
|
13277
|
+
if (item.op.kind === "create" && item.inDoubt && exec.findCreated) {
|
|
13278
|
+
let found = false;
|
|
13279
|
+
try {
|
|
13280
|
+
found = await exec.findCreated(item.requestId, item.op.body);
|
|
13281
|
+
} catch (error) {
|
|
13282
|
+
if (classify2(error) === "offline") break;
|
|
13283
|
+
}
|
|
13284
|
+
if (found) {
|
|
13285
|
+
await store2.deleteRecords([await outboxKey(item.requestId)]);
|
|
13286
|
+
result.sent++;
|
|
13287
|
+
continue;
|
|
13288
|
+
}
|
|
13289
|
+
}
|
|
13290
|
+
item = { ...item, state: "sending", attempts: item.attempts + 1 };
|
|
13291
|
+
await writeItem(item);
|
|
13292
|
+
let outcome;
|
|
13293
|
+
let message;
|
|
13294
|
+
try {
|
|
13295
|
+
await run(exec, item);
|
|
13296
|
+
outcome = "sent";
|
|
13297
|
+
} catch (error) {
|
|
13298
|
+
outcome = classify2(error);
|
|
13299
|
+
message = error instanceof Error ? error.message : "The change could not be saved";
|
|
13300
|
+
}
|
|
13301
|
+
if (outcome === "sent") {
|
|
13302
|
+
await store2.deleteRecords([await outboxKey(item.requestId)]);
|
|
13303
|
+
result.sent++;
|
|
13304
|
+
continue;
|
|
13305
|
+
}
|
|
13306
|
+
if (outcome === "failed") {
|
|
13307
|
+
await writeItem({ ...item, state: "failed", error: message });
|
|
13308
|
+
result.failed++;
|
|
13309
|
+
continue;
|
|
13310
|
+
}
|
|
13311
|
+
const inDoubt = item.inDoubt || item.op.kind === "create";
|
|
13312
|
+
await writeItem({ ...item, state: "queued", inDoubt, error: message });
|
|
13313
|
+
if (inDoubt) result.inDoubt++;
|
|
13314
|
+
if (outcome === "offline") break;
|
|
13315
|
+
}
|
|
13316
|
+
notify2();
|
|
13317
|
+
return result;
|
|
13318
|
+
}
|
|
13319
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
13320
|
+
function notify2(change = "changed") {
|
|
13321
|
+
for (const l of listeners2) l(change);
|
|
13322
|
+
}
|
|
13323
|
+
function onCalendarOutboxChange(listener) {
|
|
13324
|
+
listeners2.add(listener);
|
|
13325
|
+
return () => listeners2.delete(listener);
|
|
13326
|
+
}
|
|
13327
|
+
async function wipeCalendarOffline() {
|
|
13328
|
+
await wipeStore(sealer2, store2);
|
|
13329
|
+
notify2();
|
|
13330
|
+
}
|
|
13331
|
+
async function claimCalendarOffline(userExtId) {
|
|
13332
|
+
const owner = await store2.getMeta("owner");
|
|
13333
|
+
if (owner && owner !== userExtId) await wipeCalendarOffline();
|
|
13334
|
+
await store2.setMeta("owner", userExtId);
|
|
13335
|
+
}
|
|
13336
|
+
|
|
13337
|
+
// src/calendar/offline/support.ts
|
|
13338
|
+
var calendarOfflineSupport = {
|
|
13339
|
+
async read(key, fetch2) {
|
|
13340
|
+
const cacheKey2 = JSON.stringify(key);
|
|
13341
|
+
if (!isCalendarOffline()) {
|
|
13342
|
+
try {
|
|
13343
|
+
const data = await fetch2();
|
|
13344
|
+
void writeCalendarCache(cacheKey2, data).catch(() => {
|
|
13345
|
+
});
|
|
13346
|
+
return data;
|
|
13347
|
+
} catch (err) {
|
|
13348
|
+
if (!isCalendarNetworkError(err)) throw err;
|
|
13349
|
+
}
|
|
13350
|
+
}
|
|
13351
|
+
const cached = await readCalendarCache(cacheKey2).catch(
|
|
13352
|
+
() => null
|
|
13353
|
+
);
|
|
13354
|
+
if (cached != null) return cached;
|
|
13355
|
+
throw new ArbiApiError("You are offline and this view is not saved on this device");
|
|
13356
|
+
},
|
|
13357
|
+
async write(op, send) {
|
|
13358
|
+
if (!isCalendarOffline()) return send();
|
|
13359
|
+
const { requestId } = await enqueueCalendarWrite(op);
|
|
13360
|
+
return { queued: true, requestId };
|
|
13361
|
+
},
|
|
13362
|
+
pending: listCalendarOutbox,
|
|
13363
|
+
subscribe: (listener) => onCalendarOutboxChange(() => listener())
|
|
13364
|
+
};
|
|
13365
|
+
var day = (date, offset) => {
|
|
13366
|
+
const t = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
13367
|
+
t.setUTCDate(t.getUTCDate() + offset);
|
|
13368
|
+
return t.toISOString();
|
|
13369
|
+
};
|
|
13370
|
+
var calendarOutboxExecutor = (arbi) => ({
|
|
13371
|
+
create: (body) => arbi.calendar.createEvent(body),
|
|
13372
|
+
update: (target, body) => arbi.calendar.updateEvent(target, body),
|
|
13373
|
+
delete: (target) => arbi.calendar.deleteEvent(target),
|
|
13374
|
+
respond: (params) => arbi.calendar.respond(params),
|
|
13375
|
+
/** No server dedupe: an in-doubt create counts as made if an event with its title and start exists. */
|
|
13376
|
+
async findCreated(_requestId, body) {
|
|
13377
|
+
const { title, start } = body;
|
|
13378
|
+
const date = (start?.date_time ?? start?.date ?? "").slice(0, 10);
|
|
13379
|
+
if (!title || !/^\d{4}-\d{2}-\d{2}$/.test(date)) return false;
|
|
13380
|
+
const events = await arbi.calendar.listEvents({
|
|
13381
|
+
start: day(date, -1),
|
|
13382
|
+
end: day(date, 2)
|
|
13383
|
+
});
|
|
13384
|
+
return events.some(
|
|
13385
|
+
(e) => e.title === title && (start?.date_time ? (e.start.date_time ?? "").slice(0, 16) === start.date_time.slice(0, 16) : e.start.date === start?.date)
|
|
13386
|
+
);
|
|
13387
|
+
}
|
|
13388
|
+
});
|
|
13389
|
+
var SYNC_CHANNEL = "arbi-calendar-outbox";
|
|
13390
|
+
function refreshCalendarQueries(queryClient) {
|
|
13391
|
+
void queryClient.invalidateQueries({ queryKey: calendarQueryKeys.all() });
|
|
13392
|
+
}
|
|
13393
|
+
async function replay(arbi, queryClient) {
|
|
13394
|
+
const result = await replayCalendarOutbox(calendarOutboxExecutor(arbi));
|
|
13395
|
+
if (result.sent || result.failed) {
|
|
13396
|
+
refreshCalendarQueries(queryClient);
|
|
13397
|
+
try {
|
|
13398
|
+
const channel2 = new BroadcastChannel(SYNC_CHANNEL);
|
|
13399
|
+
channel2.postMessage("synced");
|
|
13400
|
+
channel2.close();
|
|
13401
|
+
} catch {
|
|
13402
|
+
}
|
|
13403
|
+
}
|
|
13404
|
+
if (result.failed)
|
|
13405
|
+
notify.error(
|
|
13406
|
+
result.failed === 1 ? "A calendar change made offline could not be saved" : `${result.failed} calendar changes made offline could not be saved`
|
|
13407
|
+
);
|
|
13408
|
+
}
|
|
13409
|
+
function useCalendarOfflineRuntime() {
|
|
13410
|
+
const { status, serverSessionKey, user } = useArbiSession();
|
|
13411
|
+
const userExtId = user?.external_id;
|
|
13412
|
+
const authenticated = status === "signed-in";
|
|
13413
|
+
const hasSession = !!serverSessionKey;
|
|
13414
|
+
const arbi = useArbi();
|
|
13415
|
+
const ready = authenticated && hasSession && arbi.isLoggedIn;
|
|
13416
|
+
const queryClient = useQueryClient();
|
|
13417
|
+
useEffect(() => {
|
|
13418
|
+
if (!authenticated || !userExtId) return;
|
|
13419
|
+
claimCalendarOffline(userExtId).catch(
|
|
13420
|
+
(error) => logger.warn("[offline-calendar] could not bind the store", error)
|
|
13421
|
+
);
|
|
13422
|
+
}, [authenticated, userExtId]);
|
|
13423
|
+
useEffect(() => {
|
|
13424
|
+
if (!ready) return;
|
|
13425
|
+
const run2 = () => {
|
|
13426
|
+
if (!onlineManager.isOnline()) return;
|
|
13427
|
+
replay(arbi, queryClient).catch(
|
|
13428
|
+
(error) => logger.warn("[offline-calendar] replay failed", error)
|
|
13429
|
+
);
|
|
13430
|
+
};
|
|
13431
|
+
run2();
|
|
13432
|
+
let wasOnline = onlineManager.isOnline();
|
|
13433
|
+
const offOnline = onlineManager.subscribe((online) => {
|
|
13434
|
+
if (online && !wasOnline) run2();
|
|
13435
|
+
wasOnline = online;
|
|
13436
|
+
});
|
|
13437
|
+
let queued = null;
|
|
13438
|
+
const offOutbox = onCalendarOutboxChange((change) => {
|
|
13439
|
+
if (change !== "enqueued") return;
|
|
13440
|
+
if (queued) clearTimeout(queued);
|
|
13441
|
+
queued = setTimeout(run2, 2e3);
|
|
13442
|
+
});
|
|
13443
|
+
let channel2 = null;
|
|
13444
|
+
try {
|
|
13445
|
+
channel2 = new BroadcastChannel(SYNC_CHANNEL);
|
|
13446
|
+
channel2.onmessage = () => refreshCalendarQueries(queryClient);
|
|
13447
|
+
} catch {
|
|
13448
|
+
channel2 = null;
|
|
13449
|
+
}
|
|
13450
|
+
return () => {
|
|
13451
|
+
channel2?.close();
|
|
13452
|
+
offOnline();
|
|
13453
|
+
offOutbox();
|
|
13454
|
+
if (queued) clearTimeout(queued);
|
|
13455
|
+
};
|
|
13456
|
+
}, [ready, arbi, queryClient]);
|
|
13457
|
+
}
|
|
11896
13458
|
function useWorkspaceDocs(workspaceId) {
|
|
11897
13459
|
const { data: docs = [], isLoading, isError } = useDocuments(workspaceId);
|
|
11898
13460
|
const docIds = useMemo(
|
|
@@ -11921,7 +13483,7 @@ function useAiTask() {
|
|
|
11921
13483
|
artifactsRef.current = /* @__PURE__ */ new Map();
|
|
11922
13484
|
bufferRef.current = "";
|
|
11923
13485
|
}, []);
|
|
11924
|
-
const
|
|
13486
|
+
const run2 = useCallback(
|
|
11925
13487
|
async (question, docIds, opts) => {
|
|
11926
13488
|
setStatus("running");
|
|
11927
13489
|
setText("");
|
|
@@ -11989,7 +13551,7 @@ function useAiTask() {
|
|
|
11989
13551
|
error,
|
|
11990
13552
|
responseId,
|
|
11991
13553
|
artifacts,
|
|
11992
|
-
run,
|
|
13554
|
+
run: run2,
|
|
11993
13555
|
reset,
|
|
11994
13556
|
isRunning: status === "running",
|
|
11995
13557
|
isDone: status === "done"
|
|
@@ -12427,13 +13989,13 @@ function useSemanticSearch(docIds = [], options = {}) {
|
|
|
12427
13989
|
clearSearchSession(void 0, session);
|
|
12428
13990
|
return;
|
|
12429
13991
|
}
|
|
12430
|
-
const
|
|
13992
|
+
const run2 = session.getState().run + 1;
|
|
12431
13993
|
const searched = workspaceId ?? session.getState().workspaceId ?? void 0;
|
|
12432
13994
|
session.setState((s) => ({
|
|
12433
13995
|
query: trimmed,
|
|
12434
13996
|
status: "running",
|
|
12435
13997
|
error: null,
|
|
12436
|
-
run,
|
|
13998
|
+
run: run2,
|
|
12437
13999
|
workspaceId: workspaceId ?? s.workspaceId
|
|
12438
14000
|
}));
|
|
12439
14001
|
try {
|
|
@@ -12443,10 +14005,10 @@ function useSemanticSearch(docIds = [], options = {}) {
|
|
|
12443
14005
|
...model ? { model } : {},
|
|
12444
14006
|
...searched ? { workspaceId: searched } : {}
|
|
12445
14007
|
});
|
|
12446
|
-
if (session.getState().run !==
|
|
14008
|
+
if (session.getState().run !== run2) return;
|
|
12447
14009
|
session.setState({ results: searchChunks(result), searched: true, status: "done" });
|
|
12448
14010
|
} catch (error) {
|
|
12449
|
-
if (session.getState().run !==
|
|
14011
|
+
if (session.getState().run !== run2) return;
|
|
12450
14012
|
session.setState({
|
|
12451
14013
|
status: "error",
|
|
12452
14014
|
error: error instanceof Error ? error.message : "Search failed"
|
|
@@ -12649,7 +14211,7 @@ var CitationRendererComponent = ({
|
|
|
12649
14211
|
useCitationState,
|
|
12650
14212
|
onCloseCitation,
|
|
12651
14213
|
onDismissCitation,
|
|
12652
|
-
notify:
|
|
14214
|
+
notify: notify3,
|
|
12653
14215
|
docTitles
|
|
12654
14216
|
}) => {
|
|
12655
14217
|
const chunkMap = useMemo(() => {
|
|
@@ -12693,7 +14255,7 @@ var CitationRendererComponent = ({
|
|
|
12693
14255
|
useCitationState,
|
|
12694
14256
|
onCloseCitation,
|
|
12695
14257
|
onDismissCitation,
|
|
12696
|
-
notify:
|
|
14258
|
+
notify: notify3
|
|
12697
14259
|
}),
|
|
12698
14260
|
[
|
|
12699
14261
|
citations,
|
|
@@ -12706,7 +14268,7 @@ var CitationRendererComponent = ({
|
|
|
12706
14268
|
useCitationState,
|
|
12707
14269
|
onCloseCitation,
|
|
12708
14270
|
onDismissCitation,
|
|
12709
|
-
|
|
14271
|
+
notify3
|
|
12710
14272
|
]
|
|
12711
14273
|
);
|
|
12712
14274
|
return /* @__PURE__ */ jsx(CitationAnchorContext.Provider, { value: anchorContext, children: /* @__PURE__ */ jsx("div", { className: cn("citation-container", className), "data-testid": "citation-renderer", children: renderMarkdown({
|
|
@@ -12765,7 +14327,7 @@ var CitationPopover = ({
|
|
|
12765
14327
|
useCitationState,
|
|
12766
14328
|
onCloseCitation,
|
|
12767
14329
|
onDismissCitation,
|
|
12768
|
-
notify:
|
|
14330
|
+
notify: notify3,
|
|
12769
14331
|
children
|
|
12770
14332
|
}) => {
|
|
12771
14333
|
const { activeCitationKey, viewingPassageId, isOnConversationPage } = useCitationState();
|
|
@@ -12839,11 +14401,11 @@ var CitationPopover = ({
|
|
|
12839
14401
|
const source = [firstTitle, firstPage != null ? `p.${firstPage}` : null].filter(Boolean).join(", ");
|
|
12840
14402
|
try {
|
|
12841
14403
|
await navigator.clipboard.writeText(source ? `\u201C${firstText}\u201D (${source})` : firstText);
|
|
12842
|
-
|
|
14404
|
+
notify3?.("Citation copied", "success");
|
|
12843
14405
|
} catch {
|
|
12844
|
-
|
|
14406
|
+
notify3?.("Could not copy the citation", "error");
|
|
12845
14407
|
}
|
|
12846
|
-
}, [firstText, firstTitle, firstPage,
|
|
14408
|
+
}, [firstText, firstTitle, firstPage, notify3]);
|
|
12847
14409
|
const items = isStreaming || chunks.length === 0 ? [] : [
|
|
12848
14410
|
{
|
|
12849
14411
|
id: "open",
|
|
@@ -14397,7 +15959,7 @@ function useChatPost() {
|
|
|
14397
15959
|
const arbi = useArbi();
|
|
14398
15960
|
const queryClient = useQueryClient();
|
|
14399
15961
|
return useCallback(
|
|
14400
|
-
async (target,
|
|
15962
|
+
async (target, post) => {
|
|
14401
15963
|
const me = myChatId(arbi);
|
|
14402
15964
|
if (!me) throw new Error("No active session");
|
|
14403
15965
|
let chat = target.chat;
|
|
@@ -14410,7 +15972,7 @@ function useChatPost() {
|
|
|
14410
15972
|
(old) => old ?? { messages: [], hasMore: false }
|
|
14411
15973
|
);
|
|
14412
15974
|
}
|
|
14413
|
-
const files =
|
|
15975
|
+
const files = post.files ?? [];
|
|
14414
15976
|
const optimisticId = `optimistic-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
14415
15977
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14416
15978
|
const optimistic = {
|
|
@@ -14418,11 +15980,11 @@ function useChatPost() {
|
|
|
14418
15980
|
chat_ext_id: chat.external_id,
|
|
14419
15981
|
sender_ext_id: me,
|
|
14420
15982
|
kind: "text",
|
|
14421
|
-
content:
|
|
15983
|
+
content: post.text,
|
|
14422
15984
|
key_version: chat.key_version,
|
|
14423
15985
|
deleted: false,
|
|
14424
|
-
context_only: !!
|
|
14425
|
-
workspace_ext_id:
|
|
15986
|
+
context_only: !!post.contextOnly,
|
|
15987
|
+
workspace_ext_id: post.workspaceExtId ?? null,
|
|
14426
15988
|
created_at: now,
|
|
14427
15989
|
updated_at: now,
|
|
14428
15990
|
optimistic: true
|
|
@@ -14442,10 +16004,10 @@ function useChatPost() {
|
|
|
14442
16004
|
)
|
|
14443
16005
|
);
|
|
14444
16006
|
}
|
|
14445
|
-
const message = await arbi.chats.send(chat,
|
|
16007
|
+
const message = await arbi.chats.send(chat, post.text, {
|
|
14446
16008
|
attachments,
|
|
14447
|
-
...
|
|
14448
|
-
contextOnly: !!
|
|
16009
|
+
...post.workspaceExtId ? { workspaceExtId: post.workspaceExtId } : {},
|
|
16010
|
+
contextOnly: !!post.contextOnly
|
|
14449
16011
|
});
|
|
14450
16012
|
appendToThread(queryClient, chat.external_id, message, optimisticId);
|
|
14451
16013
|
upsertChat(queryClient, {
|
|
@@ -14556,12 +16118,12 @@ function annotateThread(messages, accessors, runWindowMs = RUN_WINDOW_MS) {
|
|
|
14556
16118
|
return messages.map((message, i) => {
|
|
14557
16119
|
const next = messages[i + 1];
|
|
14558
16120
|
const prev = messages[i - 1];
|
|
14559
|
-
const
|
|
14560
|
-
const continuesIntoNext = !!next && senderId(next) === senderId(message) && dayKey(createdAt(next)) ===
|
|
16121
|
+
const day2 = dayKey(createdAt(message));
|
|
16122
|
+
const continuesIntoNext = !!next && senderId(next) === senderId(message) && dayKey(createdAt(next)) === day2 && Date.parse(createdAt(next)) - Date.parse(createdAt(message)) < runWindowMs;
|
|
14561
16123
|
return {
|
|
14562
16124
|
message,
|
|
14563
16125
|
isLastOfRun: !continuesIntoNext,
|
|
14564
|
-
isFirstOfDay: !prev || dayKey(createdAt(prev)) !==
|
|
16126
|
+
isFirstOfDay: !prev || dayKey(createdAt(prev)) !== day2
|
|
14565
16127
|
};
|
|
14566
16128
|
});
|
|
14567
16129
|
}
|
|
@@ -15006,6 +16568,6 @@ function useRateSupportTicket() {
|
|
|
15006
16568
|
});
|
|
15007
16569
|
}
|
|
15008
16570
|
|
|
15009
|
-
export { AGENT_CONTROL_COMMANDS, AGGREGATE_FIELDS, ALLOWED_PORTAL_ORIGINS, ARCHIVE_POLL_MS, ASSISTANT_MENTION_LABEL, AUTO_CHAT_MODEL, ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, ArbiHost2 as ArbiHost, ArbiWebSocketProvider, ArtifactCanvas, BOOT_GATE_TIMEOUT_MS, BOX_SEAT_REFUSED, BOX_SEAT_REFUSED_ACTION, BOX_SEAT_REFUSED_TITLE, BUILTIN_DATA_SOURCES, CONVERSATION_SWITCH_MEASURE, ChatBubble, CitationRenderer, CommandPalette, CommandPaletteProvider, ConversationList, ConversationScope, DATABASE_RELATION_PICK_LIMIT, DATABASE_ROWS_PAGE, DEFAULT_ASPECT_OPTIONS, DEFAULT_CONVERSATIONS_SOURCE_LABELS, DEFAULT_DATABASE_SOURCE_LABELS, DEFAULT_DOCUMENTS_SOURCE_LABELS, DEFAULT_PASSWORD_CHANGE_FORM_MESSAGES, DEFAULT_PROJECTS_SOURCE_LABELS, DEFAULT_USAGE_SOURCE_LABELS, DEFAULT_WORKSPACES_SOURCE_LABELS, DateSeparator, DmAvatar, DmComposer, DocQaPanel, DocSearch, DocumentCardMenu, DocumentReview, DraftTitleControls, EMPTY_GROUP_LABEL, FACTS_SEARCH_VIEW, FIRST_TOKEN_MEASURE, FileIcon, GitHubIcon, ImageCropModal, ImageCropper, LIVE_UPDATE_TYPES, LinkedInIcon, MAIL_DRAFT_REPLACED_EVENT, MAX_DASHBOARD_SPEC_CHARS, MAX_SERIES, MIN_NARRATION_DWELL_MS, MODEL_GROUP_LABELS, MailOfflineUnavailableError, MessageArtifactCard, PENDING_SYNC_PREDICATE, POLL_EVERY_MS, PhotoCapture, RUN_WINDOW_MS, SIGNATURE_NOTIFICATION_TYPES, SLOW_AFTER_MS, STILL_WORKING, StorageActionError, TRANSFER_POLL_MS, TaskThreadPendingError, TypingIndicator, UPLOAD_ACCEPT, UPLOAD_MAX_FILE_SIZE, VideoCapture, VoiceNoteRecorder, WORK_NOTIFICATION_LABELS, __resetPendingFinalCommitsForTests, __resetStreamingTokenBufferForTests, accountPath, addAssistantMessageToCache, addUserMessageToCache, adoptNewChatDocument, aggregateDataset, aliasTakenMessage, allConversationThreads, annotateThread, announceMailDraftReplaced, announceTurnEnded, appendTokenToMessage, applyConversationEvent, applyFinalContextToMessage, applyLiveUpdate, applyNotificationRow, applyOptimisticDocumentEdit, applyOutputItemToMessage, applyTaskEvent, arbiMailClient, archiveName, archivedWorkspaceMessage, assistantLabels, bindThreadDocument, boundDocumentOf, bucketOf, bucketRange, buildDocumentMetadataRows, buildMentionCandidates, buildPendingFirstTurn, buildPendingSyncItem, buildWorkspaceArchive, cacheThreadBinding, cachedAssistantMode, cachedMentionCandidates, cachedQuotaUsedUp, calendarNotificationMeta, callNotificationMeta, captureFrameAsBlob, claimOfflineQueue, clearLoadingState, clearUserInputRequest, commitFinalMessage, completeQueuedUserMessageByContent, connectOAuthCalendarAccount, connectorErrorStatus, connectorReturnTo, createArbiQueryClient, createConversationsSource, createDataSourceRegistry, createDatabaseSource, createDocumentsSource, createMailClient, createMemoryDashboardStore, createProjectsSource, createSkillPack, createStreamHandlers, createUsageSource, createWorkspaceSwitch, createWorkspacesSource, cropImage, dayKey, defaultMailAlias, defineRowsSource, deploymentUserMeta, describeActivity, describeCalendarNotification, describeServerNotification, discardStreamingTokensForMessage, disconnectAttachedRun, documentActivityMeta, documentActivityPhrase, documentDisplayName, docxMentionMeta, downloadRecoveryFile, ensureAssistantMessageLoading, extractZipFile, fallbackFileName, fileAttachment, filesFromDrop, fillTimeGaps, filterRows, flushStreamingTokens, forgetUploadedDocumentFolder, formatConfigValidationError, formatIdentityPrice, getInterjectionText, getNotificationTypeMeta, getOfflineDocument, getOrphanedLastMessage, getRelatedLabels, getRelatedTotal, getRunStartedAt, getThreadsQueryKey, getWorkspaceActionText, getWorkspaceKind, getWorkspaceSentPhrase, hasPendingFinalCommit, hasVideoTrack, identityStepFor, inputRequestTitle, invalidateCalendarEvents, invalidateCalendars, invalidateConversationActivity, isActivityKey, isAliasTakenError, isArchiveMoving, isCommentToPeople, isDeploymentAdmin, isDeploymentUserNotification, isFullConfig, isMessageRunning, isNotificationRow, isOrphanedUserMessage, isQueuedCalendarWrite, isReplayableArbiMutation, isSignatureNotificationType, isTableViewOf, isTransferFinished, isTransferResumable, isWorkspaceArchivedError, isWorkspaceNotificationType, isZipFile, loadOfflineDocument, mailDraftIdFromContent, mailJson, markMessageAsFailed, markNarrationSettled, migrateLegacyThreadDocuments, mountDiditVerification, nextAliasSuggestion, notificationUserName, openAccessRequest, parseActivityContent, parseDashboardSpec, parseDeleteAccountBlockers, pendingChatModelSave, peopleDirectory, personOfUser, pickRecentWorkspaces, prepareUploadFiles, queryOfChart, recordsOf, redlineStats, refuseChatWhenOffline, registerArbiMutationDefaults, registerAttachedRun, removeSettledNarration, requestPersistentStorage, resetCalendarAccountSyncState, resetMessageContent, resolveActiveWorkspace, resolveConfigModel, resolveReplyTarget, rollbackOptimisticDocumentEdit, safePortalUrl, serializeDashboardSpec, serverNotificationBellEntry, setMessageContent, setUserInputRequest, signatureNotificationPhrase, signatureRequestMeta, subscribeDocumentReads, supportsHouseTemplates, textToArtifact, threadBinding, threadEndingAtContains, titleFromQuestion, toChatCitations, toNotificationPreferences, toRedlineMarkdown, toShellWorkspace, updateMessageWithMetadata, updateUserMessageInCache, updateUserMessageStateInCache, uploadFolderOf, uploadedDocumentFolder, useActOnStep, useActiveConfigId, useAddChatMembers, useAddDatabaseRows, useAddHolidayCalendar, useAddProjectAddonBlock, useAddWorkspaceMembers, useAddonQuote, useAgentChats, useAgentConfig, useAgentDmStatus, useAgentModels, useAgentRunControl, useAgentSession, useAgentWorkspaces, useAiTask, useAnswerAgentQuestion, useApplyChatModel, useArbiConnected, useArbiShellWorkspaces, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArchiveProgressPolling, useArchiveWorkspace, useAskAgain, useAssignDocTags, useAssistantName, useAuthorizeLimits, useAutoSendDraft, useBookingPages, useBootTimeout, useBranchNavigation, useCalDavProfiles, useCalendarAccounts, useCalendarEvent, useCalendarEventByUid, useCalendarEvents, useCalendarInvitations, useCalendarOutbox, useCalendarOverlays, useCalendarSettings, useCalendarShares, useCalendarSupport, useCalendars, useCameraStream, useCanEditShared, useCancelDriveSync, useCancelMailboxSync, useCancelManyPendingSync, useCancelProjectSubscription, useCancelWorkspaceTransfer, useChangeProjectPlan, useChargeWatch, useChatArrivals, useChatPost, useChatSummaries, useChatThread, useChatUnreadTotal, useChats, useClaimNativeAlias, useClaimSecureMail, useCloseAgentSession, useCommandPalette, useConfigSchema, useConfirmEmailChange, useConnectCalDavAccount, useConnectMailAccount, useConnectOAuthAccount, useConnectRunStream, useConnectorAutoSync, useConnectorCatalog, useConnectorVendorVisibility, useContacts, useConversation, useConversationActions, useConversationActivity, useConversationData, useConversationDocuments, useConversationIsShared, useConversationItems, useConversationPendingAuth, useConversationSearch, useConversationSettings, useConversationTurn, useConversationUsage, useConversationWorkspace, useCreateAgent, useCreateCalendar, useCreateContact, useCreateDatabase, useCreateEvent, useCreateFact, useCreateFacts, useCreateGroupChat, useCreateProject, useCreateProjectCheckout, useCreateRoom, useCreateSupportTicket, useCreateWork, useCreateWorkflow, useCreateWorkspace, useCurrentSessionId, useCurrentWorkspace, useDashboardStore, useDataSources, useDatabase, useDatabaseAggregate, useDatabaseDashboardStore, useDatabaseRows, useDatabases, useDecideCounter, useDeleteAccount, useDeleteAccountFlow, useDeleteBookingPage, useDeleteCalendar, useDeleteChatMessage, useDeleteConfig, useDeleteContact, useDeleteConversation, useDeleteDashboard, useDeleteDatabase, useDeleteDatabaseRows, useDeleteDocuments, useDeleteEvent, useDeleteFact, useDeleteMessage, useDeleteNotifications, useDeleteProject, useDeleteSkill, useDeleteWorkflow, useDeleteWorkspaces, useDirectModelUsage, useDisconnectCalendarAccount, useDisconnectConnector, useDisconnectMailAccount, useDisconnectSlack, useDismissedTips, useDocumentAutoAdd, useDocumentHostActions, useDocumentPendingSync, useDocumentThumbnails, useDocumentsSupport, useDownloadDocuments, useDriveFolderChildren, useDriveFolderDocCount, useDriveFolders, useDriveSyncEstimate, useDriveSyncProgress, useEmailChange, useEnableDriveSync, useEnableMailboxSync, useExportDatabase, useFacts, useFactsSearch, useFetchCalendarEvent, useFilteredProjects, useFinaliseTranslation, useFindTime, useFollowThread, useFreeBusy, useGenerateAgentAvatar, useGenerateDocTags, useGrantAgentAccess, useHolidayCountries, useHouseTemplateCandidates, useIdentityVerification, useIdentityVerificationFlow, useImportCalendar, useImportContacts, useImportDatabase, useImportDatabaseRows, useInstallSkillPack, useInvalidateAfterTransfer, useInvalidateProjects, useInvalidateStorage, useInviteContacts, useIsSlow, useIsWorkspaceOwner, useKeepDocumentsOffline, useLinkContact, useLinkMentions, useMailAccounts, useMailAttachmentText, useMailCache, useMailClient, useMailDraftCompose, useMailDraftEditor, useMailDraftReplaced, useMailFolders, useMailMessage, useMailMessages, useMailOnline, useMailPolicy, useMailProjects, useMailUnlock, useMailboxFolders, useMailboxSyncProgress, useMarkChatRead, useMarkDocumentShared, useMarkNotificationsRead, useMcpTools, useMentionCandidates, useMessageTimer, useModelConnections, useMoveEvent, useMoveWorkspaceToProject, useMyConnectors, useNotificationPreferences, useNotifications, useNotifyConversation, useOfflineDocumentIds, useOpenSelectedWorkspace, useOpenViewerDocumentId, usePanelDataset, usePassageQuotes, usePasswordChange, usePasswordChangeForm, usePendingSyncCounts, usePendingSyncItems, usePeople, usePeopleSearch, usePlanQuote, usePlanWork, usePlanWorkspaceTransfer, usePreviewProjectPlanChange, useProcessingTask, useProcessingTasks, useProducts, useProjectBillingPortal, useProjectConversationUsage, useProjectEvents, usePublishCalendar, useRateSupportTicket, useRefreshCalendar, useRefreshConnectorsOnRunEnd, useRefreshProject, useRelatedDocuments, useRemoveChatMember, useRemoveDocTags, useRemoveDocumentsOffline, useRemoveModelConnection, useRemoveWorkspaceMembers, useRenameChat, useRenameFolder, useRequestEmailChange, useRequestWorkspaceAccess, useRespondToEvent, useResumeCalendarAccountConnect, useResumeWorkspaceTransfer, useRevokeAgentAccess, useRooms, useRunConversation, useRunWorkflow, useSaveAgentConfig, useSaveBookingPage, useSaveCalendarSettings, useSaveConfig, useSaveConfigField, useSaveDashboard, useSaveMailDraft, useSaveModelConnection, useSaveSkill, useSavedDashboards, useSearchContacts, useSearchDatabaseRows, useSearchFacts, useSemanticSearch, useSendBackStep, useSendMail, useSessions, useSetDeploymentHouseTemplate, useSetSkillVisibility, useSetWorkspaceHouseTemplate, useSetWorkspaceMemberRoles, useShareCalendar, useShellWorkspaces, useSignOut, useSkillSource, useSkills, useSlackConnection, useStartAgentSession, useStartCalendarAccountOAuth, useStartTaskQuery, useStartWorkspaceTransfer, useStorageActions, useStorageItemNames, useStorageItems, useStorageProjects, useStorageSnapshots, useStreamHandlers, useSubscribeCalendar, useSupportTicket, useSupportTickets, useSwitchWorkspace, useSyncAccountsOnOpen, useSyncCalendarAccount, useTerminateSession, useThinkingContent, useThreadDocument, useTriggerDriveSync, useTriggerMailboxSync, useUnarchiveWorkspace, useUnlockModelConnections, useUnreadByPeer, useUnshareCalendar, useUpdateCalendar, useUpdateCalendarAccount, useUpdateContact, useUpdateConversation, useUpdateDatabase, useUpdateDatabaseRow, useUpdateDocTag, useUpdateDocuments, useUpdateEvent, useUpdateFact, useUpdateMailMessage, useUpdateProjectAddonSettings, useUpdateProjectFields, useUpdateProjectNextPeriodAddons, useUpdateWorkflow, useUpdateWorkspace, useUploadDocuments, useUploadFiles, useUploadFromUrls, useUploadSkillFiles, useUploadUrls, useVideoRecorder, useWorkItems, useWorkNotifications, useWorkflowRuns, useWorkflows, useWorkspaceArchive, useWorkspaceDocs, useWorkspaceExportDownload, useWorkspaceNotificationLevel, useWorkspaceProcessing, useWorkspaceRole, useWorkspaceTransfer, useWorkspaceTransfers, useWorkspaceTrash, useWorkspaceUsers, userDisplayName, userInitials, viewsOfTable, waitForResponseId, wipeOfflineDocuments, withRelativePath, workflowStepOf, workspaceHouseTemplateId };
|
|
16571
|
+
export { AGENT_CONTROL_COMMANDS, AGGREGATE_FIELDS, ALLOWED_PORTAL_ORIGINS, ARCHIVE_POLL_MS, ASSISTANT_MENTION_LABEL, AUTO_CHAT_MODEL, ActivityFeed, AiActionCard, AiAnswer, AppleIcon, ArbiBotIcon, ArbiHost2 as ArbiHost, ArbiWebSocketProvider, ArtifactCanvas, BOOT_GATE_TIMEOUT_MS, BOX_SEAT_REFUSED, BOX_SEAT_REFUSED_ACTION, BOX_SEAT_REFUSED_TITLE, BUILTIN_DATA_SOURCES, CONVERSATION_SWITCH_MEASURE, ChatBubble, CitationRenderer, CommandPalette, CommandPaletteProvider, ConversationList, ConversationScope, DATABASE_RELATION_PICK_LIMIT, DATABASE_ROWS_PAGE, DEFAULT_ASPECT_OPTIONS, DEFAULT_CONVERSATIONS_SOURCE_LABELS, DEFAULT_DATABASE_SOURCE_LABELS, DEFAULT_DOCUMENTS_SOURCE_LABELS, DEFAULT_PASSWORD_CHANGE_FORM_MESSAGES, DEFAULT_PROJECTS_SOURCE_LABELS, DEFAULT_USAGE_SOURCE_LABELS, DEFAULT_WORKSPACES_SOURCE_LABELS, DateSeparator, DmAvatar, DmComposer, DocQaPanel, DocSearch, DocumentCardMenu, DocumentReview, DraftTitleControls, EMPTY_GROUP_LABEL, FACTS_SEARCH_VIEW, FIRST_TOKEN_MEASURE, FileIcon, GitHubIcon, ImageCropModal, ImageCropper, LIVE_UPDATE_TYPES, LinkedInIcon, MAIL_DRAFT_REPLACED_EVENT, MAX_DASHBOARD_SPEC_CHARS, MAX_SERIES, MIN_NARRATION_DWELL_MS, MODEL_GROUP_LABELS, MailOfflineUnavailableError, MessageArtifactCard, OUTBOX_FOLDER, PENDING_SYNC_PREDICATE, POLL_EVERY_MS, PhotoCapture, RUN_WINDOW_MS, SIGNATURE_NOTIFICATION_TYPES, SLOW_AFTER_MS, STILL_WORKING, StorageActionError, TRANSFER_POLL_MS, TaskThreadPendingError, TypingIndicator, UPLOAD_ACCEPT, UPLOAD_MAX_FILE_SIZE, VideoCapture, VoiceNoteRecorder, WORK_NOTIFICATION_LABELS, WORK_OFFLINE_VIEW, __resetPendingFinalCommitsForTests, __resetStreamingTokenBufferForTests, accountPath, addAssistantMessageToCache, addUserMessageToCache, adoptNewChatDocument, aggregateDataset, aliasTakenMessage, allConversationThreads, annotateThread, announceMailDraftReplaced, appendTokenToMessage, applyConversationEvent, applyFinalContextToMessage, applyLiveUpdate, applyNotificationRow, applyOptimisticDocumentEdit, applyOutputItemToMessage, applyTaskEvent, arbiMailClient, archiveName, archivedWorkspaceMessage, assistantLabels, bindThreadDocument, boundDocumentOf, bucketOf, bucketRange, buildDocumentMetadataRows, buildMentionCandidates, buildPendingFirstTurn, buildPendingSyncItem, buildWorkspaceArchive, cacheThreadBinding, cachedAssistantMode, cachedMentionCandidates, cachedQuotaUsedUp, calendarNotificationMeta, calendarOfflineSupport, callNotificationMeta, captureFrameAsBlob, claimOfflineQueue, clearLoadingState, clearUserInputRequest, commitFinalMessage, completeQueuedUserMessageByContent, computeOnline, confirmLosingOfflineMail, connectOAuthCalendarAccount, connectorErrorStatus, connectorReturnTo, createArbiQueryClient, createConversationsSource, createDataSourceRegistry, createDatabaseSource, createDocumentsSource, createMailClient, createMemoryDashboardStore, createProjectsSource, createSkillPack, createStreamHandlers, createUsageSource, createWorkspaceSwitch, createWorkspacesSource, cropImage, dayKey, defaultMailAlias, defineRowsSource, deleteLocalDraft, deleteOutboxItem, deploymentUserMeta, describeActivity, describeCalendarNotification, describeServerNotification, discardStreamingTokensForMessage, disconnectAttachedRun, documentActivityMeta, documentActivityPhrase, documentDisplayName, docxMentionMeta, downloadRecoveryFile, enqueueOutbox, ensureAssistantMessageLoading, extractZipFile, fallbackFileName, fileAttachment, filesFromDrop, fillTimeGaps, filterRows, flushStreamingTokens, forgetUploadedDocumentFolder, formatConfigValidationError, formatIdentityPrice, getInterjectionText, getNotificationTypeMeta, getOfflineDocument, getOrphanedLastMessage, getRelatedLabels, getRelatedTotal, getRunStartedAt, getThreadsQueryKey, getWorkspaceActionText, getWorkspaceKind, getWorkspaceSentPhrase, hasPendingFinalCommit, hasVideoTrack, identityStepFor, inputRequestTitle, invalidateCalendarEvents, invalidateCalendars, invalidateConversationActivity, isActivityKey, isAliasTakenError, isArchiveMoving, isCommentToPeople, isDeploymentAdmin, isDeploymentUserNotification, isFullConfig, isMessageRunning, isNetworkFailure, isNotificationRow, isOrphanedUserMessage, isQueuedCalendarWrite, isReplayableArbiMutation, isSignatureNotificationType, isTableViewOf, isTransferFinished, isTransferResumable, isWorkingOffline, isWorkspaceArchivedError, isWorkspaceNotificationType, isZipFile, loadOfflineDocument, loadAttachment as loadOfflineMailAttachment, mailDraftIdFromContent, mailJson, markMessageAsFailed, markNarrationSettled, migrateLegacyThreadDocuments, mountDiditVerification, nextAliasSuggestion, notificationUserName, offlineMailReads, openAccessRequest, parseActivityContent, parseDashboardSpec, parseDeleteAccountBlockers, pendingChatModelSave, peopleDirectory, personOfUser, pickRecentWorkspaces, prepareUploadFiles, queryOfChart, recordsOf, redlineStats, refuseChatWhenOffline, registerArbiMutationDefaults, registerAttachedRun, removeSettledNarration, requestPersistentStorage, resetCalendarAccountSyncState, resetMessageContent, resolveActiveWorkspace, resolveConfigModel, resolveReplyTarget, retryOutboxItem, rollbackOptimisticDocumentEdit, safePortalUrl, saveLocalDraft, serializeDashboardSpec, serverNotificationBellEntry, setMessageContent, setUserInputRequest, setWorkOfflineIn, signatureNotificationPhrase, signatureRequestMeta, subscribeDocumentReads, supportsHouseTemplates, textToArtifact, threadBinding, threadEndingAtContains, titleFromQuestion, toChatCitations, toNotificationPreferences, toRedlineMarkdown, toShellWorkspace, updateMessageWithMetadata, updateUserMessageInCache, updateUserMessageStateInCache, uploadFolderOf, uploadedDocumentFolder, useActOnStep, useActiveConfigId, useAddChatMembers, useAddDatabaseRows, useAddHolidayCalendar, useAddProjectAddonBlock, useAddWorkspaceMembers, useAddonQuote, useAgentChats, useAgentConfig, useAgentDmStatus, useAgentModels, useAgentRunControl, useAgentSession, useAgentWorkspaces, useAiTask, useAnswerAgentQuestion, useApplyChatModel, useArbiConnected, useArbiShellWorkspaces, useArbiWebSocket, useArbiWsContext, useArbiWsMessage, useArbiWsStatus, useArchiveProgressPolling, useArchiveWorkspace, useAskAgain, useAssignDocTags, useAssistantName, useAuthorizeLimits, useAutoSendDraft, useBookingPages, useBootTimeout, useBranchNavigation, useCalDavProfiles, useCalendarAccounts, useCalendarEvent, useCalendarEventByUid, useCalendarEvents, useCalendarInvitations, useCalendarOfflineRuntime, useCalendarOutbox, useCalendarOverlays, useCalendarSettings, useCalendarShares, useCalendarSupport, useCalendars, useCameraStream, useCanEditShared, useCancelDriveSync, useCancelMailboxSync, useCancelManyPendingSync, useCancelProjectSubscription, useCancelWorkspaceTransfer, useChangeProjectPlan, useChargeWatch, useChatArrivals, useChatPost, useChatSummaries, useChatThread, useChatUnreadTotal, useChats, useClaimNativeAlias, useClaimSecureMail, useCloseAgentSession, useCommandPalette, useConfigSchema, useConfirmEmailChange, useConnectCalDavAccount, useConnectMailAccount, useConnectOAuthAccount, useConnectRunStream, useConnectorAutoSync, useConnectorCatalog, useConnectorVendorVisibility, useContacts, useConversation, useConversationActions, useConversationActivity, useConversationData, useConversationDocuments, useConversationIsShared, useConversationItems, useConversationPendingAuth, useConversationSearch, useConversationSettings, useConversationTurn, useConversationUsage, useConversationWorkspace, useCreateAgent, useCreateCalendar, useCreateContact, useCreateDatabase, useCreateEvent, useCreateFact, useCreateFacts, useCreateGroupChat, useCreateProject, useCreateProjectCheckout, useCreateRoom, useCreateSupportTicket, useCreateWork, useCreateWorkflow, useCreateWorkspace, useCurrentSessionId, useCurrentWorkspace, useDashboardStore, useDataSources, useDatabase, useDatabaseAggregate, useDatabaseDashboardStore, useDatabaseRows, useDatabases, useDecideCounter, useDeleteAccount, useDeleteAccountFlow, useDeleteBookingPage, useDeleteCalendar, useDeleteChatMessage, useDeleteConfig, useDeleteContact, useDeleteConversation, useDeleteDashboard, useDeleteDatabase, useDeleteDatabaseRows, useDeleteDocuments, useDeleteEvent, useDeleteFact, useDeleteMessage, useDeleteNotifications, useDeleteProject, useDeleteSkill, useDeleteWorkflow, useDeleteWorkspaces, useDirectModelUsage, useDisconnectCalendarAccount, useDisconnectConnector, useDisconnectMailAccount, useDisconnectSlack, useDismissedTips, useDocumentAutoAdd, useDocumentHostActions, useDocumentPendingSync, useDocumentThumbnails, useDocumentsSupport, useDownloadDocuments, useDriveFolderChildren, useDriveFolderDocCount, useDriveFolders, useDriveSyncEstimate, useDriveSyncProgress, useEmailChange, useEnableDriveSync, useEnableMailboxSync, useExportDatabase, useFacts, useFactsSearch, useFetchCalendarEvent, useFilteredProjects, useFinaliseTranslation, useFindTime, useFlushOutbox, useFollowThread, useFreeBusy, useGenerateAgentAvatar, useGenerateDocTags, useGrantAgentAccess, useHolidayCountries, useHouseTemplateCandidates, useIdentityVerification, useIdentityVerificationFlow, useImportCalendar, useImportContacts, useImportDatabase, useImportDatabaseRows, useInstallSkillPack, useInvalidateAfterTransfer, useInvalidateProjects, useInvalidateStorage, useInviteContacts, useIsSlow, useIsWorkspaceOwner, useKeepDocumentsOffline, useLinkContact, useLinkMentions, useLocalDrafts, useMailAccounts, useMailAttachmentText, useMailCache, useMailClient, useMailDraftCompose, useMailDraftEditor, useMailDraftReplaced, useMailFolders, useMailMessage, useMailMessages, useMailOnline, useMailPolicy, useMailProjects, useMailUnlock, useMailboxFolders, useMailboxSyncProgress, useMarkChatRead, useMarkDocumentShared, useMarkNotificationsRead, useMcpTools, useMentionCandidates, useMessageTimer, useModelConnections, useMoveEvent, useMoveWorkspaceToProject, useMyConnectors, useNotificationPreferences, useNotifications, useNotifyConversation, useOfflineDocumentIds, useOfflineMailRuntime, useOfflineMailSettings, useOfflineMailStatus, useOpenSelectedWorkspace, useOpenViewerDocumentId, useOutbox, usePanelDataset, usePassageQuotes, usePasswordChange, usePasswordChangeForm, usePendingSyncCounts, usePendingSyncItems, usePeople, usePeopleSearch, usePlanQuote, usePlanWork, usePlanWorkspaceTransfer, usePreviewProjectPlanChange, useProcessingTask, useProcessingTasks, useProducts, useProjectBillingPortal, useProjectConversationUsage, useProjectEvents, usePublishCalendar, useRateSupportTicket, useReconnect, useRefreshCalendar, useRefreshConnectorsOnRunEnd, useRefreshProject, useRelatedDocuments, useRemoveChatMember, useRemoveDocTags, useRemoveDocumentsOffline, useRemoveModelConnection, useRemoveWorkspaceMembers, useRenameChat, useRenameFolder, useRequestEmailChange, useRequestWorkspaceAccess, useRespondToEvent, useResumeCalendarAccountConnect, useResumeWorkspaceTransfer, useRevokeAgentAccess, useRooms, useRunConversation, useRunWorkflow, useSaveAgentConfig, useSaveBookingPage, useSaveCalendarSettings, useSaveConfig, useSaveConfigField, useSaveDashboard, useSaveMailDraft, useSaveModelConnection, useSaveSkill, useSavedDashboards, useSearchContacts, useSearchDatabaseRows, useSearchFacts, useSemanticSearch, useSendBackStep, useSendMail, useSessions, useSetDeploymentHouseTemplate, useSetSkillVisibility, useSetWorkspaceHouseTemplate, useSetWorkspaceMemberRoles, useShareCalendar, useShellWorkspaces, useSignOut, useSkillSource, useSkills, useSlackConnection, useStartAgentSession, useStartCalendarAccountOAuth, useStartTaskQuery, useStartWorkspaceTransfer, useStorageActions, useStorageItemNames, useStorageItems, useStorageProjects, useStorageSnapshots, useStreamHandlers, useSubscribeCalendar, useSupportTicket, useSupportTickets, useSwitchWorkspace, useSyncAccountsOnOpen, useSyncCalendarAccount, useSyncOfflineMailbox, useTerminateSession, useThinkingContent, useThreadDocument, useTriggerDriveSync, useTriggerMailboxSync, useUnarchiveWorkspace, useUnlockModelConnections, useUnreadByPeer, useUnshareCalendar, useUpdateCalendar, useUpdateCalendarAccount, useUpdateContact, useUpdateConversation, useUpdateDatabase, useUpdateDatabaseRow, useUpdateDocTag, useUpdateDocuments, useUpdateEvent, useUpdateFact, useUpdateMailMessage, useUpdateProjectAddonSettings, useUpdateProjectFields, useUpdateProjectNextPeriodAddons, useUpdateWorkflow, useUpdateWorkspace, useUploadDocuments, useUploadFiles, useUploadFromUrls, useUploadSkillFiles, useUploadUrls, useVideoRecorder, useWorkItems, useWorkNotifications, useWorkOffline, useWorkflowRuns, useWorkflows, useWorkspaceArchive, useWorkspaceDocs, useWorkspaceExportDownload, useWorkspaceNotificationLevel, useWorkspaceProcessing, useWorkspaceRole, useWorkspaceTransfer, useWorkspaceTransfers, useWorkspaceTrash, useWorkspaceUsers, userDisplayName, userInitials, viewsOfTable, waitForResponseId, wipeCalendarOffline, wipeOfflineDocuments, wipeOfflineMail, withRelativePath, workflowStepOf, workspaceHouseTemplateId };
|
|
15010
16572
|
//# sourceMappingURL=index.js.map
|
|
15011
16573
|
//# sourceMappingURL=index.js.map
|