@arbidocs/react 0.5.84 → 0.5.86
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 +31 -0
- package/dist/index.cjs +1736 -148
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +272 -1
- package/dist/index.d.ts +272 -1
- package/dist/index.js +1718 -152
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -176,8 +176,8 @@ function useStartTaskQuery() {
|
|
|
176
176
|
};
|
|
177
177
|
const lookUpThread = async (messageId, attempts, known) => {
|
|
178
178
|
let conversationId;
|
|
179
|
-
for (let
|
|
180
|
-
if (
|
|
179
|
+
for (let attempt2 = 0; attempt2 < attempts && !conversationId; attempt2 += 1) {
|
|
180
|
+
if (attempt2 > 0) await new Promise((r) => setTimeout(r, THREAD_LOOKUP_DELAY_MS));
|
|
181
181
|
try {
|
|
182
182
|
conversationId = await threadOf(messageId, known);
|
|
183
183
|
} catch (error) {
|
|
@@ -1797,9 +1797,9 @@ function useWorkspaceProcessing(workspaceId) {
|
|
|
1797
1797
|
}, []);
|
|
1798
1798
|
React.useEffect(() => {
|
|
1799
1799
|
if (!workspaceId) return;
|
|
1800
|
-
const
|
|
1800
|
+
const store3 = chunkQ6U7ALNV_cjs.useArbiWsStore.getState();
|
|
1801
1801
|
const documents2 = chunkQ6U7ALNV_cjs.getCachedDocuments(queryClient, workspaceId) ?? [];
|
|
1802
|
-
|
|
1802
|
+
store3.settleTasks(documents2);
|
|
1803
1803
|
const listed = new Set(documents2.map((d) => d.external_id));
|
|
1804
1804
|
const now = Date.now();
|
|
1805
1805
|
for (const task of Object.values(chunkQ6U7ALNV_cjs.useArbiWsStore.getState().tasks)) {
|
|
@@ -1809,7 +1809,7 @@ function useWorkspaceProcessing(workspaceId) {
|
|
|
1809
1809
|
const age = since == null ? null : now - since;
|
|
1810
1810
|
const stale = age != null && age > STALE_TASK_MS;
|
|
1811
1811
|
const orphan = documents2.length > 0 && !listed.has(task.doc_ext_id) && age != null && age > ORPHAN_TASK_MS;
|
|
1812
|
-
if (stale || orphan)
|
|
1812
|
+
if (stale || orphan) store3.clearTask(task.doc_ext_id);
|
|
1813
1813
|
}
|
|
1814
1814
|
}, [workspaceId, queryClient]);
|
|
1815
1815
|
React.useEffect(() => {
|
|
@@ -1854,25 +1854,25 @@ function useWorkspaceProcessing(workspaceId) {
|
|
|
1854
1854
|
}, [workspaceId, snapshot]);
|
|
1855
1855
|
}
|
|
1856
1856
|
function applyOptimisticDocumentEdit(qc, updates) {
|
|
1857
|
-
const byId = new Map(updates.map((
|
|
1857
|
+
const byId = new Map(updates.map((update2) => [update2.external_id, update2]));
|
|
1858
1858
|
const previous = [];
|
|
1859
1859
|
chunkQ6U7ALNV_cjs.patchDocuments(qc, (old) => {
|
|
1860
1860
|
let changed = false;
|
|
1861
1861
|
const next = old.map((doc) => {
|
|
1862
|
-
const
|
|
1863
|
-
if (!
|
|
1862
|
+
const update2 = byId.get(doc.external_id);
|
|
1863
|
+
if (!update2) return doc;
|
|
1864
1864
|
changed = true;
|
|
1865
1865
|
previous.push(doc);
|
|
1866
|
-
return { ...doc, ...
|
|
1866
|
+
return { ...doc, ...update2 };
|
|
1867
1867
|
});
|
|
1868
1868
|
return changed ? next : old;
|
|
1869
1869
|
});
|
|
1870
|
-
const previousOwn = updates.map((
|
|
1870
|
+
const previousOwn = updates.map((update2) => {
|
|
1871
1871
|
const own = qc.getQueryData(
|
|
1872
|
-
chunkU4PWVSCT_cjs.arbiQueryKeys.document(
|
|
1872
|
+
chunkU4PWVSCT_cjs.arbiQueryKeys.document(update2.external_id)
|
|
1873
1873
|
);
|
|
1874
|
-
if (own) chunkQ6U7ALNV_cjs.setCachedDocument(qc, { ...own, ...
|
|
1875
|
-
return [
|
|
1874
|
+
if (own) chunkQ6U7ALNV_cjs.setCachedDocument(qc, { ...own, ...update2 });
|
|
1875
|
+
return [update2.external_id, own];
|
|
1876
1876
|
});
|
|
1877
1877
|
return { optimistic: true, previous, previousOwn };
|
|
1878
1878
|
}
|
|
@@ -2202,11 +2202,11 @@ function useUpdateDocTag() {
|
|
|
2202
2202
|
);
|
|
2203
2203
|
}
|
|
2204
2204
|
function queueProcessing(documents2) {
|
|
2205
|
-
const
|
|
2206
|
-
const previous = documents2.map(({ docId }) => [docId,
|
|
2205
|
+
const store3 = chunkQ6U7ALNV_cjs.useArbiWsStore.getState();
|
|
2206
|
+
const previous = documents2.map(({ docId }) => [docId, store3.tasks[docId]]);
|
|
2207
2207
|
const now = Date.now();
|
|
2208
2208
|
for (const doc of documents2)
|
|
2209
|
-
|
|
2209
|
+
store3.putTask({
|
|
2210
2210
|
doc_ext_id: doc.docId,
|
|
2211
2211
|
workspace_ext_id: doc.workspaceId,
|
|
2212
2212
|
file_name: doc.fileName,
|
|
@@ -3983,14 +3983,14 @@ function watchPendingDocIds(queryClient) {
|
|
|
3983
3983
|
if (watched.has(queryClient)) return;
|
|
3984
3984
|
watched.add(queryClient);
|
|
3985
3985
|
const cache = queryClient.getMutationCache();
|
|
3986
|
-
const
|
|
3986
|
+
const update2 = () => {
|
|
3987
3987
|
const ids = new Set(
|
|
3988
3988
|
cache.findAll({ predicate: PENDING_SYNC_PREDICATE }).flatMap((m) => docIdsFromVariables(kindOf(m.options.mutationKey), m.state.variables))
|
|
3989
3989
|
);
|
|
3990
3990
|
if (!sameSet(usePendingDocIds.getState().ids, ids)) usePendingDocIds.setState({ ids });
|
|
3991
3991
|
};
|
|
3992
|
-
|
|
3993
|
-
cache.subscribe(
|
|
3992
|
+
update2();
|
|
3993
|
+
cache.subscribe(update2);
|
|
3994
3994
|
}
|
|
3995
3995
|
function useDocumentPendingSync(docId) {
|
|
3996
3996
|
const queryClient = reactQuery.useQueryClient();
|
|
@@ -4029,23 +4029,23 @@ function promisify(request) {
|
|
|
4029
4029
|
request.onerror = () => reject(request.error);
|
|
4030
4030
|
});
|
|
4031
4031
|
}
|
|
4032
|
-
async function withStore(mode,
|
|
4032
|
+
async function withStore(mode, run2) {
|
|
4033
4033
|
const db = await openDB();
|
|
4034
|
-
const
|
|
4035
|
-
return promisify(
|
|
4034
|
+
const store3 = db.transaction(STORE, mode).objectStore(STORE);
|
|
4035
|
+
return promisify(run2(store3));
|
|
4036
4036
|
}
|
|
4037
4037
|
async function putOfflineDocument(record) {
|
|
4038
|
-
await withStore("readwrite", (
|
|
4038
|
+
await withStore("readwrite", (store3) => store3.put(record));
|
|
4039
4039
|
}
|
|
4040
4040
|
async function getOfflineDocument(docExtId) {
|
|
4041
|
-
const record = await withStore("readonly", (
|
|
4041
|
+
const record = await withStore("readonly", (store3) => store3.get(docExtId));
|
|
4042
4042
|
return record;
|
|
4043
4043
|
}
|
|
4044
4044
|
async function deleteOfflineDocument(docExtId) {
|
|
4045
|
-
await withStore("readwrite", (
|
|
4045
|
+
await withStore("readwrite", (store3) => store3.delete(docExtId));
|
|
4046
4046
|
}
|
|
4047
4047
|
async function listOfflineDocumentIds() {
|
|
4048
|
-
const keys = await withStore("readonly", (
|
|
4048
|
+
const keys = await withStore("readonly", (store3) => store3.getAllKeys());
|
|
4049
4049
|
return keys;
|
|
4050
4050
|
}
|
|
4051
4051
|
async function requestPersistentStorage() {
|
|
@@ -5420,7 +5420,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5420
5420
|
if (!conversationId || !responseId) return;
|
|
5421
5421
|
let disposed = false;
|
|
5422
5422
|
let controller = new AbortController();
|
|
5423
|
-
let
|
|
5423
|
+
let retryTimer2;
|
|
5424
5424
|
let reconnects = 0;
|
|
5425
5425
|
const runStartedAt = session.getState().messageTimers[`${conversationId}:${responseId}`]?.startTime;
|
|
5426
5426
|
session.setConversationGenerating(conversationId, true, null);
|
|
@@ -5445,7 +5445,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5445
5445
|
if (isMessageRunning(queryClient, conversationId, responseId) && reconnects < MAX_RECONNECTS) {
|
|
5446
5446
|
reconnects += 1;
|
|
5447
5447
|
markLoading();
|
|
5448
|
-
|
|
5448
|
+
retryTimer2 = setTimeout(connect, RECONNECT_DELAY_MS);
|
|
5449
5449
|
return;
|
|
5450
5450
|
}
|
|
5451
5451
|
settle(false);
|
|
@@ -5531,7 +5531,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5531
5531
|
const stop = () => {
|
|
5532
5532
|
if (disposed) return;
|
|
5533
5533
|
disposed = true;
|
|
5534
|
-
clearTimeout(
|
|
5534
|
+
clearTimeout(retryTimer2);
|
|
5535
5535
|
window.removeEventListener("online", reconnectWhenOnline);
|
|
5536
5536
|
controller.abort();
|
|
5537
5537
|
settle(true);
|
|
@@ -5540,7 +5540,7 @@ function useAttachRun(conversationId, responseId) {
|
|
|
5540
5540
|
return () => {
|
|
5541
5541
|
unregister();
|
|
5542
5542
|
disposed = true;
|
|
5543
|
-
clearTimeout(
|
|
5543
|
+
clearTimeout(retryTimer2);
|
|
5544
5544
|
window.removeEventListener("online", reconnectWhenOnline);
|
|
5545
5545
|
controller.abort();
|
|
5546
5546
|
settle(false);
|
|
@@ -6342,20 +6342,20 @@ function useConversationSettings() {
|
|
|
6342
6342
|
React.useEffect(() => {
|
|
6343
6343
|
if (isFullConfig(activeConfig)) session.setChatModelName(resolveConfigModel(activeConfig));
|
|
6344
6344
|
}, [activeConfig, configToLoad]);
|
|
6345
|
-
const
|
|
6346
|
-
const pending =
|
|
6345
|
+
const update2 = useUpdateConversation();
|
|
6346
|
+
const pending = update2.isPending ? update2.variables?.patch : void 0;
|
|
6347
6347
|
const saveTitle = React.useCallback(() => {
|
|
6348
6348
|
const trimmed = title.trim();
|
|
6349
6349
|
if (!conversationId || !isOwner || !trimmed || trimmed.length > TITLE_MAX) return;
|
|
6350
6350
|
if (trimmed === conversation?.title) return;
|
|
6351
|
-
|
|
6352
|
-
}, [conversationId, isOwner, title, conversation?.title,
|
|
6351
|
+
update2.mutate({ conversationId, patch: { title: trimmed } });
|
|
6352
|
+
}, [conversationId, isOwner, title, conversation?.title, update2]);
|
|
6353
6353
|
const setShared = React.useCallback(
|
|
6354
6354
|
(shared) => {
|
|
6355
6355
|
if (!conversationId || !isOwner || isShared === shared) return;
|
|
6356
|
-
|
|
6356
|
+
update2.mutate({ conversationId, patch: { shared } });
|
|
6357
6357
|
},
|
|
6358
|
-
[conversationId, isOwner, isShared,
|
|
6358
|
+
[conversationId, isOwner, isShared, update2]
|
|
6359
6359
|
);
|
|
6360
6360
|
const share = React.useCallback(() => setShared(true), [setShared]);
|
|
6361
6361
|
const unshare = React.useCallback(() => setShared(false), [setShared]);
|
|
@@ -6783,7 +6783,7 @@ function useOnlineSync() {
|
|
|
6783
6783
|
reactQuery.onlineManager.setOnline(computeOnline(wsStatus, signedIn2, workOffline, wsEverConnected.current));
|
|
6784
6784
|
}, [wsStatus, signedIn2, workOffline]);
|
|
6785
6785
|
React.useEffect(() => {
|
|
6786
|
-
const
|
|
6786
|
+
const update2 = () => reactQuery.onlineManager.setOnline(
|
|
6787
6787
|
computeOnline(
|
|
6788
6788
|
chunkQ6U7ALNV_cjs.useArbiWsStore.getState().status,
|
|
6789
6789
|
signedInRef.current,
|
|
@@ -6791,11 +6791,11 @@ function useOnlineSync() {
|
|
|
6791
6791
|
wsEverConnected.current
|
|
6792
6792
|
)
|
|
6793
6793
|
);
|
|
6794
|
-
window.addEventListener("online",
|
|
6795
|
-
window.addEventListener("offline",
|
|
6794
|
+
window.addEventListener("online", update2);
|
|
6795
|
+
window.addEventListener("offline", update2);
|
|
6796
6796
|
return () => {
|
|
6797
|
-
window.removeEventListener("online",
|
|
6798
|
-
window.removeEventListener("offline",
|
|
6797
|
+
window.removeEventListener("online", update2);
|
|
6798
|
+
window.removeEventListener("offline", update2);
|
|
6799
6799
|
};
|
|
6800
6800
|
}, [session]);
|
|
6801
6801
|
}
|
|
@@ -6815,8 +6815,8 @@ function useReconnect() {
|
|
|
6815
6815
|
// src/websocket/reconnect.ts
|
|
6816
6816
|
var INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
6817
6817
|
var MAX_RECONNECT_DELAY_MS = 3e4;
|
|
6818
|
-
function nextReconnectDelay(
|
|
6819
|
-
return Math.min(INITIAL_RECONNECT_DELAY_MS * 2 ** Math.max(0,
|
|
6818
|
+
function nextReconnectDelay(attempt2) {
|
|
6819
|
+
return Math.min(INITIAL_RECONNECT_DELAY_MS * 2 ** Math.max(0, attempt2), MAX_RECONNECT_DELAY_MS);
|
|
6820
6820
|
}
|
|
6821
6821
|
function resolveSocketBaseUrl(configured, origin) {
|
|
6822
6822
|
return /^https?:\/\//.test(configured) || !origin ? configured : origin;
|
|
@@ -7101,13 +7101,13 @@ function useUpdateMailMessage() {
|
|
|
7101
7101
|
mutationFn: ({
|
|
7102
7102
|
accountId,
|
|
7103
7103
|
messageId,
|
|
7104
|
-
update
|
|
7104
|
+
update: update2
|
|
7105
7105
|
}) => mailJson(
|
|
7106
7106
|
client.fetch,
|
|
7107
7107
|
`${accountPath(accountId)}/messages/${encodeURIComponent(messageId)}`,
|
|
7108
7108
|
{
|
|
7109
7109
|
method: "PATCH",
|
|
7110
|
-
body: JSON.stringify(
|
|
7110
|
+
body: JSON.stringify(update2)
|
|
7111
7111
|
}
|
|
7112
7112
|
)
|
|
7113
7113
|
});
|
|
@@ -7429,15 +7429,15 @@ function ArbiWebSocketProvider({
|
|
|
7429
7429
|
}
|
|
7430
7430
|
}
|
|
7431
7431
|
};
|
|
7432
|
-
let
|
|
7433
|
-
let
|
|
7432
|
+
let retryTimer2 = null;
|
|
7433
|
+
let attempt2 = 0;
|
|
7434
7434
|
const scheduleRestart = () => {
|
|
7435
|
-
if (cancelled || evicted ||
|
|
7435
|
+
if (cancelled || evicted || retryTimer2) return;
|
|
7436
7436
|
setStatus("disconnected");
|
|
7437
|
-
const delay = nextReconnectDelay(
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7437
|
+
const delay = nextReconnectDelay(attempt2);
|
|
7438
|
+
attempt2 += 1;
|
|
7439
|
+
retryTimer2 = setTimeout(() => {
|
|
7440
|
+
retryTimer2 = null;
|
|
7441
7441
|
void start();
|
|
7442
7442
|
}, delay);
|
|
7443
7443
|
};
|
|
@@ -7468,7 +7468,7 @@ function ArbiWebSocketProvider({
|
|
|
7468
7468
|
onMessage,
|
|
7469
7469
|
onReconnecting: () => setStatus("reconnecting"),
|
|
7470
7470
|
onReconnected: () => {
|
|
7471
|
-
|
|
7471
|
+
attempt2 = 0;
|
|
7472
7472
|
setStatus("connected");
|
|
7473
7473
|
},
|
|
7474
7474
|
// Evicted (a newer session of the same user, or the server took this connection over for a
|
|
@@ -7490,7 +7490,7 @@ function ArbiWebSocketProvider({
|
|
|
7490
7490
|
return;
|
|
7491
7491
|
}
|
|
7492
7492
|
conn = c;
|
|
7493
|
-
|
|
7493
|
+
attempt2 = 0;
|
|
7494
7494
|
setStatus("connected");
|
|
7495
7495
|
} catch {
|
|
7496
7496
|
if (!info.signal.aborted) scheduleRestart();
|
|
@@ -7500,7 +7500,7 @@ function ArbiWebSocketProvider({
|
|
|
7500
7500
|
void start();
|
|
7501
7501
|
return () => {
|
|
7502
7502
|
cancelled = true;
|
|
7503
|
-
if (
|
|
7503
|
+
if (retryTimer2) clearTimeout(retryTimer2);
|
|
7504
7504
|
conn?.close();
|
|
7505
7505
|
reset();
|
|
7506
7506
|
};
|
|
@@ -7575,10 +7575,10 @@ function createWorkspaceSwitch({
|
|
|
7575
7575
|
queryClient
|
|
7576
7576
|
}) {
|
|
7577
7577
|
const switchWorkspace = (target) => {
|
|
7578
|
-
const
|
|
7579
|
-
switching.add(
|
|
7580
|
-
void
|
|
7581
|
-
return
|
|
7578
|
+
const run2 = runSwitch(target);
|
|
7579
|
+
switching.add(run2);
|
|
7580
|
+
void run2.finally(() => switching.delete(run2));
|
|
7581
|
+
return run2;
|
|
7582
7582
|
};
|
|
7583
7583
|
const runSwitch = async (target) => {
|
|
7584
7584
|
const { workspaceId: previousId, workspaceStatus } = session.getState();
|
|
@@ -8245,7 +8245,7 @@ function useFactsSearch(options = {}) {
|
|
|
8245
8245
|
const workspaceId = options.workspaceId ?? (arbi.hasWorkspace ? arbi.getWorkspaceId() : null);
|
|
8246
8246
|
const [kept, setKept] = chunk2Y2WOIHH_cjs.useTransientView(FACTS_SEARCH_VIEW, EMPTY2);
|
|
8247
8247
|
const searchFacts = useSearchFacts(workspaceId);
|
|
8248
|
-
const
|
|
8248
|
+
const run2 = React.useRef(0);
|
|
8249
8249
|
const foreign = !!kept.workspaceId && !!workspaceId && kept.workspaceId !== workspaceId;
|
|
8250
8250
|
const state = foreign ? EMPTY2 : kept;
|
|
8251
8251
|
const patch = React.useCallback(
|
|
@@ -8260,18 +8260,18 @@ function useFactsSearch(options = {}) {
|
|
|
8260
8260
|
const search = React.useCallback(async () => {
|
|
8261
8261
|
const q = query.trim();
|
|
8262
8262
|
if (!q) return;
|
|
8263
|
-
const mine = ++
|
|
8263
|
+
const mine = ++run2.current;
|
|
8264
8264
|
try {
|
|
8265
8265
|
const res = await searchFacts.mutateAsync({ query: q, search_mode: mode, limit });
|
|
8266
|
-
if (
|
|
8266
|
+
if (run2.current !== mine) return;
|
|
8267
8267
|
patch({ hits: res.facts, lastQuery: q });
|
|
8268
8268
|
} catch (error) {
|
|
8269
|
-
if (
|
|
8269
|
+
if (run2.current !== mine) return;
|
|
8270
8270
|
throw error;
|
|
8271
8271
|
}
|
|
8272
8272
|
}, [query, searchFacts, mode, limit, patch]);
|
|
8273
8273
|
const clear = React.useCallback(() => {
|
|
8274
|
-
|
|
8274
|
+
run2.current++;
|
|
8275
8275
|
patch({ query: "", hits: null });
|
|
8276
8276
|
}, [patch]);
|
|
8277
8277
|
return {
|
|
@@ -8489,26 +8489,26 @@ var DEFAULT_LIMIT = 50;
|
|
|
8489
8489
|
var pad = (n) => String(n).padStart(2, "0");
|
|
8490
8490
|
function asDay(value) {
|
|
8491
8491
|
if (typeof value !== "string" || value.length < 10) return null;
|
|
8492
|
-
const
|
|
8493
|
-
return Number.isNaN(
|
|
8492
|
+
const day2 = /* @__PURE__ */ new Date(`${value.slice(0, 10)}T00:00:00Z`);
|
|
8493
|
+
return Number.isNaN(day2.getTime()) ? null : day2;
|
|
8494
8494
|
}
|
|
8495
|
-
function isoWeek(
|
|
8496
|
-
const d = new Date(
|
|
8495
|
+
function isoWeek(day2) {
|
|
8496
|
+
const d = new Date(day2);
|
|
8497
8497
|
const weekday = d.getUTCDay() || 7;
|
|
8498
8498
|
d.setUTCDate(d.getUTCDate() + 4 - weekday);
|
|
8499
8499
|
const yearStart = Date.UTC(d.getUTCFullYear(), 0, 1);
|
|
8500
8500
|
return [d.getUTCFullYear(), Math.ceil(((d.getTime() - yearStart) / 864e5 + 1) / 7)];
|
|
8501
8501
|
}
|
|
8502
|
-
function bucketOf(value,
|
|
8503
|
-
const
|
|
8504
|
-
if (!
|
|
8505
|
-
const y =
|
|
8506
|
-
const m =
|
|
8507
|
-
switch (
|
|
8502
|
+
function bucketOf(value, bucket2) {
|
|
8503
|
+
const day2 = asDay(value);
|
|
8504
|
+
if (!day2) return null;
|
|
8505
|
+
const y = day2.getUTCFullYear();
|
|
8506
|
+
const m = day2.getUTCMonth() + 1;
|
|
8507
|
+
switch (bucket2) {
|
|
8508
8508
|
case "day":
|
|
8509
|
-
return `${y}-${pad(m)}-${pad(
|
|
8509
|
+
return `${y}-${pad(m)}-${pad(day2.getUTCDate())}`;
|
|
8510
8510
|
case "week": {
|
|
8511
|
-
const [wy, w] = isoWeek(
|
|
8511
|
+
const [wy, w] = isoWeek(day2);
|
|
8512
8512
|
return `${wy}-W${pad(w)}`;
|
|
8513
8513
|
}
|
|
8514
8514
|
case "quarter":
|
|
@@ -8519,12 +8519,12 @@ function bucketOf(value, bucket) {
|
|
|
8519
8519
|
return `${y}-${pad(m)}`;
|
|
8520
8520
|
}
|
|
8521
8521
|
}
|
|
8522
|
-
function bucketStart(key,
|
|
8522
|
+
function bucketStart(key, bucket2) {
|
|
8523
8523
|
const parts = key.match(/^(\d{4})(?:-(W|Q)?(\d{1,2}))?(?:-(\d{2}))?$/);
|
|
8524
8524
|
if (!parts) return null;
|
|
8525
8525
|
const year = Number(parts[1]);
|
|
8526
8526
|
const n = Number(parts[3] ?? 1);
|
|
8527
|
-
switch (
|
|
8527
|
+
switch (bucket2) {
|
|
8528
8528
|
case "day":
|
|
8529
8529
|
return asDay(key);
|
|
8530
8530
|
case "week": {
|
|
@@ -8541,32 +8541,32 @@ function bucketStart(key, bucket) {
|
|
|
8541
8541
|
return new Date(Date.UTC(year, n - 1, 1));
|
|
8542
8542
|
}
|
|
8543
8543
|
}
|
|
8544
|
-
function nextBucket(
|
|
8545
|
-
const d = new Date(
|
|
8546
|
-
if (
|
|
8547
|
-
else if (
|
|
8548
|
-
else if (
|
|
8549
|
-
else if (
|
|
8544
|
+
function nextBucket(day2, bucket2) {
|
|
8545
|
+
const d = new Date(day2);
|
|
8546
|
+
if (bucket2 === "day") d.setUTCDate(d.getUTCDate() + 1);
|
|
8547
|
+
else if (bucket2 === "week") d.setUTCDate(d.getUTCDate() + 7);
|
|
8548
|
+
else if (bucket2 === "month") d.setUTCMonth(d.getUTCMonth() + 1);
|
|
8549
|
+
else if (bucket2 === "quarter") d.setUTCMonth(d.getUTCMonth() + 3);
|
|
8550
8550
|
else d.setUTCFullYear(d.getUTCFullYear() + 1);
|
|
8551
8551
|
return d;
|
|
8552
8552
|
}
|
|
8553
|
-
function bucketRange(first, last,
|
|
8554
|
-
const start = bucketStart(first,
|
|
8555
|
-
const end = bucketStart(last,
|
|
8553
|
+
function bucketRange(first, last, bucket2, max = 500) {
|
|
8554
|
+
const start = bucketStart(first, bucket2);
|
|
8555
|
+
const end = bucketStart(last, bucket2);
|
|
8556
8556
|
if (!start || !end || start > end) return null;
|
|
8557
8557
|
const keys = [];
|
|
8558
|
-
for (let d = start; d <= end && keys.length <= max; d = nextBucket(d,
|
|
8559
|
-
keys.push(bucketOf(d.toISOString(),
|
|
8558
|
+
for (let d = start; d <= end && keys.length <= max; d = nextBucket(d, bucket2)) {
|
|
8559
|
+
keys.push(bucketOf(d.toISOString(), bucket2));
|
|
8560
8560
|
}
|
|
8561
8561
|
return keys.length > max ? null : keys;
|
|
8562
8562
|
}
|
|
8563
8563
|
var isEmpty = (value) => value === null || value === void 0 || value === "" || Array.isArray(value) && !value.length;
|
|
8564
|
-
function groupKeys(value, field,
|
|
8564
|
+
function groupKeys(value, field, bucket2) {
|
|
8565
8565
|
if (isEmpty(value)) return [""];
|
|
8566
8566
|
if (Array.isArray(value)) {
|
|
8567
|
-
return [...new Set(value.map((v) => groupKeys(v, field,
|
|
8567
|
+
return [...new Set(value.map((v) => groupKeys(v, field, bucket2)[0]))];
|
|
8568
8568
|
}
|
|
8569
|
-
if (field.type === "date") return [bucketOf(value,
|
|
8569
|
+
if (field.type === "date") return [bucketOf(value, bucket2) ?? ""];
|
|
8570
8570
|
if (typeof value === "boolean") return [value ? "Yes" : "No"];
|
|
8571
8571
|
return [String(value)];
|
|
8572
8572
|
}
|
|
@@ -8661,7 +8661,7 @@ function aggregateDataset(dataset, query) {
|
|
|
8661
8661
|
const split = query.splitBy ? byId.get(query.splitBy) : void 0;
|
|
8662
8662
|
const op = query.measure?.op ?? "count";
|
|
8663
8663
|
const valueField = query.measure?.field ? byId.get(query.measure.field) : void 0;
|
|
8664
|
-
const
|
|
8664
|
+
const bucket2 = query.bucket ?? "month";
|
|
8665
8665
|
const fields = aggregateFields(dataset.fields, query);
|
|
8666
8666
|
if (op !== "count" && valueField?.type !== "number") {
|
|
8667
8667
|
throw new Error(`${op} needs a number field`);
|
|
@@ -8669,8 +8669,8 @@ function aggregateDataset(dataset, query) {
|
|
|
8669
8669
|
const rows = filterRows(dataset.rows, dataset.fields, query.filters);
|
|
8670
8670
|
const cells = /* @__PURE__ */ new Map();
|
|
8671
8671
|
for (const row of rows) {
|
|
8672
|
-
const groups = group ? groupKeys(row[group.id], group,
|
|
8673
|
-
const splits = split ? groupKeys(row[split.id], split,
|
|
8672
|
+
const groups = group ? groupKeys(row[group.id], group, bucket2) : ["All"];
|
|
8673
|
+
const splits = split ? groupKeys(row[split.id], split, bucket2) : [""];
|
|
8674
8674
|
const value = valueField ? row[valueField.id] ?? null : 1;
|
|
8675
8675
|
for (const g of groups) {
|
|
8676
8676
|
const bySplit = cells.get(g) ?? /* @__PURE__ */ new Map();
|
|
@@ -8716,12 +8716,12 @@ function aggregateDataset(dataset, query) {
|
|
|
8716
8716
|
}
|
|
8717
8717
|
return { fields, rows: out, totalRows: rows.length, truncated: keys.length > limit };
|
|
8718
8718
|
}
|
|
8719
|
-
function fillTimeGaps(dataset,
|
|
8719
|
+
function fillTimeGaps(dataset, bucket2) {
|
|
8720
8720
|
const keyId = AGGREGATE_FIELDS.key;
|
|
8721
8721
|
const keys = [...new Set(dataset.rows.map((r) => String(r[keyId] ?? "")))].filter(Boolean);
|
|
8722
8722
|
if (keys.length < 2) return dataset;
|
|
8723
8723
|
const sorted = [...keys].sort();
|
|
8724
|
-
const range = bucketRange(sorted[0], sorted[sorted.length - 1],
|
|
8724
|
+
const range = bucketRange(sorted[0], sorted[sorted.length - 1], bucket2);
|
|
8725
8725
|
if (!range || range.length === keys.length) return dataset;
|
|
8726
8726
|
const hasSeries = dataset.fields.some((f) => f.id === AGGREGATE_FIELDS.series);
|
|
8727
8727
|
const seriesLabels = hasSeries ? [...new Set(dataset.rows.map((r) => String(r[AGGREGATE_FIELDS.series])))] : [null];
|
|
@@ -9263,8 +9263,8 @@ var DEFAULT_USAGE_SOURCE_LABELS = {
|
|
|
9263
9263
|
var monthsBackOf = (params) => String(params.period ?? "0") === "1" ? 1 : 0;
|
|
9264
9264
|
function usageRows(usage, project, modelLabel = (g) => MODEL_GROUP_LABELS[g] ?? g) {
|
|
9265
9265
|
return (usage.daily ?? []).flatMap(
|
|
9266
|
-
(
|
|
9267
|
-
day:
|
|
9266
|
+
(day2) => Object.entries(day2.model_groups ?? {}).map(([group, mg]) => ({
|
|
9267
|
+
day: day2.date,
|
|
9268
9268
|
project,
|
|
9269
9269
|
model: modelLabel(group),
|
|
9270
9270
|
spend: mg.spend ?? 0,
|
|
@@ -9528,18 +9528,18 @@ function useDashboardStore() {
|
|
|
9528
9528
|
const { dashboards } = chunkVGMMK5OM_cjs.useArbiHost();
|
|
9529
9529
|
return (dashboards?.useStore ?? useDatabaseDashboardStore)();
|
|
9530
9530
|
}
|
|
9531
|
-
function useSavedDashboards(
|
|
9531
|
+
function useSavedDashboards(store3) {
|
|
9532
9532
|
return reactQuery.useQuery({
|
|
9533
|
-
queryKey: chunkU4PWVSCT_cjs.arbiQueryKeys.dashboards(
|
|
9534
|
-
queryFn: () =>
|
|
9535
|
-
enabled:
|
|
9533
|
+
queryKey: chunkU4PWVSCT_cjs.arbiQueryKeys.dashboards(store3.scope ?? ""),
|
|
9534
|
+
queryFn: () => store3.list(),
|
|
9535
|
+
enabled: store3.scope !== null
|
|
9536
9536
|
});
|
|
9537
9537
|
}
|
|
9538
|
-
function useSaveDashboard(
|
|
9538
|
+
function useSaveDashboard(store3) {
|
|
9539
9539
|
const queryClient = reactQuery.useQueryClient();
|
|
9540
|
-
const key = chunkU4PWVSCT_cjs.arbiQueryKeys.dashboards(
|
|
9540
|
+
const key = chunkU4PWVSCT_cjs.arbiQueryKeys.dashboards(store3.scope ?? "");
|
|
9541
9541
|
return reactQuery.useMutation({
|
|
9542
|
-
mutationFn: (dashboard) =>
|
|
9542
|
+
mutationFn: (dashboard) => store3.save(dashboard),
|
|
9543
9543
|
onSuccess: (saved) => {
|
|
9544
9544
|
queryClient.setQueryData(
|
|
9545
9545
|
key,
|
|
@@ -9549,11 +9549,11 @@ function useSaveDashboard(store) {
|
|
|
9549
9549
|
}
|
|
9550
9550
|
});
|
|
9551
9551
|
}
|
|
9552
|
-
function useDeleteDashboard(
|
|
9552
|
+
function useDeleteDashboard(store3) {
|
|
9553
9553
|
const queryClient = reactQuery.useQueryClient();
|
|
9554
|
-
const key = chunkU4PWVSCT_cjs.arbiQueryKeys.dashboards(
|
|
9554
|
+
const key = chunkU4PWVSCT_cjs.arbiQueryKeys.dashboards(store3.scope ?? "");
|
|
9555
9555
|
return reactQuery.useMutation({
|
|
9556
|
-
mutationFn: (id) =>
|
|
9556
|
+
mutationFn: (id) => store3.remove(id),
|
|
9557
9557
|
onSuccess: (_void, id) => {
|
|
9558
9558
|
queryClient.setQueryData(key, (old = []) => old.filter((d) => d.id !== id));
|
|
9559
9559
|
void queryClient.invalidateQueries({ queryKey: chunkU4PWVSCT_cjs.arbiQueryKeys.databaseRows() });
|
|
@@ -9656,8 +9656,8 @@ function isClientError(error) {
|
|
|
9656
9656
|
}
|
|
9657
9657
|
return false;
|
|
9658
9658
|
}
|
|
9659
|
-
function retryDelay(
|
|
9660
|
-
return Math.min(1e3 * 2 **
|
|
9659
|
+
function retryDelay(attempt2) {
|
|
9660
|
+
return Math.min(1e3 * 2 ** attempt2, 3e4) + Math.random() * 500;
|
|
9661
9661
|
}
|
|
9662
9662
|
function shouldRetryQuery(failureCount, error) {
|
|
9663
9663
|
return failureCount < 2 && !isClientError(error) && !client.isAbortError(error);
|
|
@@ -9698,6 +9698,14 @@ function createArbiQueryClient({
|
|
|
9698
9698
|
});
|
|
9699
9699
|
return { queryClient, persistOptions: persist ? createArbiQueryPersistOptions() : null };
|
|
9700
9700
|
}
|
|
9701
|
+
|
|
9702
|
+
// src/offline-store/network.ts
|
|
9703
|
+
function isNetworkFailure(error) {
|
|
9704
|
+
return error instanceof TypeError && /fetch|network|load failed|internet/i.test(error.message);
|
|
9705
|
+
}
|
|
9706
|
+
function isBrowserOffline() {
|
|
9707
|
+
return typeof navigator !== "undefined" && navigator.onLine === false;
|
|
9708
|
+
}
|
|
9701
9709
|
function useFilteredProjects(filters, options) {
|
|
9702
9710
|
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
9703
9711
|
return reactQuery.useQuery({
|
|
@@ -10915,7 +10923,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10915
10923
|
React.useEffect(() => {
|
|
10916
10924
|
if (status && lastVerified.current === void 0) lastVerified.current = status.verified;
|
|
10917
10925
|
}, [status]);
|
|
10918
|
-
const
|
|
10926
|
+
const run2 = React.useCallback(
|
|
10919
10927
|
async (kind, fallback, fn) => {
|
|
10920
10928
|
setBusy(kind);
|
|
10921
10929
|
setProblem(null);
|
|
@@ -10936,7 +10944,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10936
10944
|
const startVerifyRef = React.useRef(async () => {
|
|
10937
10945
|
});
|
|
10938
10946
|
const refresh = React.useCallback(async () => {
|
|
10939
|
-
const next = await
|
|
10947
|
+
const next = await run2(
|
|
10940
10948
|
"refresh",
|
|
10941
10949
|
"Could not read the verification result",
|
|
10942
10950
|
() => apiRef.current.refresh()
|
|
@@ -10948,7 +10956,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10948
10956
|
const nextStep = next.state === "declined" ? "result" : identityStepFor(next);
|
|
10949
10957
|
if (nextStep === "verify" && isOpenRef.current) void startVerifyRef.current();
|
|
10950
10958
|
else setStep(nextStep);
|
|
10951
|
-
}, [
|
|
10959
|
+
}, [run2, apply]);
|
|
10952
10960
|
const refreshedOnLoad = React.useRef(false);
|
|
10953
10961
|
React.useEffect(() => {
|
|
10954
10962
|
if (refreshedOnLoad.current || !status) return;
|
|
@@ -10958,7 +10966,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10958
10966
|
const startVerify = React.useCallback(async () => {
|
|
10959
10967
|
setStep("verify");
|
|
10960
10968
|
setEmbedFailed(false);
|
|
10961
|
-
const url = await
|
|
10969
|
+
const url = await run2(
|
|
10962
10970
|
"start",
|
|
10963
10971
|
"Could not start identity verification",
|
|
10964
10972
|
() => apiRef.current.startSession()
|
|
@@ -10968,7 +10976,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
10968
10976
|
return;
|
|
10969
10977
|
}
|
|
10970
10978
|
setStep("pay");
|
|
10971
|
-
}, [
|
|
10979
|
+
}, [run2]);
|
|
10972
10980
|
startVerifyRef.current = startVerify;
|
|
10973
10981
|
React.useEffect(() => {
|
|
10974
10982
|
if (!isOpen || step !== "verify" || !sessionUrl || !container || embedFailed) return;
|
|
@@ -11023,7 +11031,7 @@ function useIdentityVerificationFlow(api, {
|
|
|
11023
11031
|
}, []);
|
|
11024
11032
|
closeRef.current = close;
|
|
11025
11033
|
const pay = React.useCallback(async () => {
|
|
11026
|
-
const result = await
|
|
11034
|
+
const result = await run2(
|
|
11027
11035
|
"pay",
|
|
11028
11036
|
"Payment could not be completed",
|
|
11029
11037
|
() => apiRef.current.purchase()
|
|
@@ -11036,10 +11044,10 @@ function useIdentityVerificationFlow(api, {
|
|
|
11036
11044
|
setPaymentUrl(null);
|
|
11037
11045
|
void query.refetch();
|
|
11038
11046
|
void startVerify();
|
|
11039
|
-
}, [
|
|
11047
|
+
}, [run2, query, startVerify]);
|
|
11040
11048
|
const acceptName = React.useCallback(async () => {
|
|
11041
11049
|
const offered = status;
|
|
11042
|
-
const next = await
|
|
11050
|
+
const next = await run2(
|
|
11043
11051
|
"accept",
|
|
11044
11052
|
"Could not change your name",
|
|
11045
11053
|
() => apiRef.current.acceptName()
|
|
@@ -11052,15 +11060,15 @@ function useIdentityVerificationFlow(api, {
|
|
|
11052
11060
|
familyName: offered.document_family_name ?? null
|
|
11053
11061
|
});
|
|
11054
11062
|
}
|
|
11055
|
-
}, [
|
|
11063
|
+
}, [run2, apply, status, onNameAccepted]);
|
|
11056
11064
|
const remove = React.useCallback(async () => {
|
|
11057
|
-
const next = await
|
|
11065
|
+
const next = await run2(
|
|
11058
11066
|
"remove",
|
|
11059
11067
|
"Could not remove the verification",
|
|
11060
11068
|
() => apiRef.current.remove()
|
|
11061
11069
|
);
|
|
11062
11070
|
if (next) apply(next);
|
|
11063
|
-
}, [
|
|
11071
|
+
}, [run2, apply]);
|
|
11064
11072
|
const price = React.useMemo(() => status ? formatPrice(status) : null, [status, formatPrice]);
|
|
11065
11073
|
return {
|
|
11066
11074
|
status,
|
|
@@ -11115,6 +11123,1221 @@ async function downloadRecoveryFile(params) {
|
|
|
11115
11123
|
});
|
|
11116
11124
|
chunkTAVS54EN_cjs.downloadBlob(new Blob([file.html], { type: "text/html" }), file.filename);
|
|
11117
11125
|
}
|
|
11126
|
+
async function documentMailAttachment(arbi, doc, maxBytes, version) {
|
|
11127
|
+
const older = version && version !== (doc.current_version ?? 1) ? version : null;
|
|
11128
|
+
if (!reactQuery.onlineManager.isOnline() && !older) return offlineDocumentAttachment(doc, maxBytes);
|
|
11129
|
+
const failed = (cause) => new Error(`Could not download ${chunkPXWKJI4K_cjs.getDocumentTitle(doc, "the document")}`, { cause });
|
|
11130
|
+
const response = await arbi.documents.download(doc.external_id, older ?? void 0).catch((error) => {
|
|
11131
|
+
throw failed(error);
|
|
11132
|
+
});
|
|
11133
|
+
if (!response.ok) throw failed();
|
|
11134
|
+
const blob = await response.blob();
|
|
11135
|
+
if (blob.size > maxBytes) throw new Error("Document exceeds the email attachment limit");
|
|
11136
|
+
const fileName = older ? doc.versions?.find((v) => v.n === older)?.file_name ?? doc.file_name : doc.file_name;
|
|
11137
|
+
return fileAttachment(
|
|
11138
|
+
new File([blob], fileName || chunkPXWKJI4K_cjs.getDocumentTitle(doc, "document"), { type: blob.type })
|
|
11139
|
+
);
|
|
11140
|
+
}
|
|
11141
|
+
async function offlineDocumentAttachment(doc, maxBytes) {
|
|
11142
|
+
const title = chunkPXWKJI4K_cjs.getDocumentTitle(doc, "This document");
|
|
11143
|
+
const record = await getOfflineDocument(doc.external_id);
|
|
11144
|
+
const isPdf = /\.pdf$/i.test(doc.file_name ?? "");
|
|
11145
|
+
if (!record || record.kind === "pdf" && !isPdf)
|
|
11146
|
+
throw new Error(
|
|
11147
|
+
`${title} isn't available offline. Make it available offline first, or attach it when you're back online.`
|
|
11148
|
+
);
|
|
11149
|
+
const blob = await loadOfflineDocument(doc.external_id);
|
|
11150
|
+
if (!blob) throw new Error(`${title} isn't available offline.`);
|
|
11151
|
+
if (blob.size > maxBytes) throw new Error("Document exceeds the email attachment limit");
|
|
11152
|
+
return fileAttachment(
|
|
11153
|
+
new File([blob], doc.file_name || chunkPXWKJI4K_cjs.getDocumentTitle(doc, "document"), { type: blob.type })
|
|
11154
|
+
);
|
|
11155
|
+
}
|
|
11156
|
+
var OfflineMailLockedError = class extends Error {
|
|
11157
|
+
constructor() {
|
|
11158
|
+
super("Sign in to open the mail saved on this device");
|
|
11159
|
+
this.name = "OfflineMailLockedError";
|
|
11160
|
+
}
|
|
11161
|
+
};
|
|
11162
|
+
var sealer = client.createOfflineSealer("arbi-offline-mail-v1", {
|
|
11163
|
+
locked: () => new OfflineMailLockedError()
|
|
11164
|
+
});
|
|
11165
|
+
var forgetOfflineMailKeys = sealer.forget;
|
|
11166
|
+
var sealBytes = sealer.sealBytes;
|
|
11167
|
+
var openBytes = sealer.openBytes;
|
|
11168
|
+
var sealJson = sealer.sealJson;
|
|
11169
|
+
var openJson = sealer.openJson;
|
|
11170
|
+
var blind = sealer.blind;
|
|
11171
|
+
|
|
11172
|
+
// src/offline-store/store.ts
|
|
11173
|
+
var DB_VERSION2 = 1;
|
|
11174
|
+
var RECORDS = "records";
|
|
11175
|
+
var META = "meta";
|
|
11176
|
+
function promisify2(request) {
|
|
11177
|
+
return new Promise((resolve, reject) => {
|
|
11178
|
+
request.onsuccess = () => resolve(request.result);
|
|
11179
|
+
request.onerror = () => reject(request.error);
|
|
11180
|
+
});
|
|
11181
|
+
}
|
|
11182
|
+
function createRecordStore(dbName) {
|
|
11183
|
+
let dbPromise = null;
|
|
11184
|
+
function openDB2() {
|
|
11185
|
+
if (dbPromise) return dbPromise;
|
|
11186
|
+
dbPromise = new Promise((resolve, reject) => {
|
|
11187
|
+
const request = indexedDB.open(dbName, DB_VERSION2);
|
|
11188
|
+
request.onerror = () => {
|
|
11189
|
+
dbPromise = null;
|
|
11190
|
+
reject(request.error);
|
|
11191
|
+
};
|
|
11192
|
+
request.onsuccess = () => {
|
|
11193
|
+
const db = request.result;
|
|
11194
|
+
db.onversionchange = () => {
|
|
11195
|
+
db.close();
|
|
11196
|
+
dbPromise = null;
|
|
11197
|
+
};
|
|
11198
|
+
resolve(db);
|
|
11199
|
+
};
|
|
11200
|
+
request.onupgradeneeded = () => {
|
|
11201
|
+
const db = request.result;
|
|
11202
|
+
if (!db.objectStoreNames.contains(RECORDS)) {
|
|
11203
|
+
const records = db.createObjectStore(RECORDS, { keyPath: "key" });
|
|
11204
|
+
records.createIndex("bucket", "bucket");
|
|
11205
|
+
records.createIndex("kind", "kind");
|
|
11206
|
+
}
|
|
11207
|
+
if (!db.objectStoreNames.contains(META)) db.createObjectStore(META, { keyPath: "key" });
|
|
11208
|
+
};
|
|
11209
|
+
});
|
|
11210
|
+
return dbPromise;
|
|
11211
|
+
}
|
|
11212
|
+
async function run2(store3, mode, fn) {
|
|
11213
|
+
const db = await openDB2();
|
|
11214
|
+
return promisify2(fn(db.transaction(store3, mode).objectStore(store3)));
|
|
11215
|
+
}
|
|
11216
|
+
async function scan(pick) {
|
|
11217
|
+
const db = await openDB2();
|
|
11218
|
+
const store3 = db.transaction(RECORDS, "readonly").objectStore(RECORDS);
|
|
11219
|
+
return new Promise((resolve, reject) => {
|
|
11220
|
+
const out = [];
|
|
11221
|
+
const cursor = store3.openCursor();
|
|
11222
|
+
cursor.onerror = () => reject(cursor.error);
|
|
11223
|
+
cursor.onsuccess = () => {
|
|
11224
|
+
const c = cursor.result;
|
|
11225
|
+
if (!c) return resolve(out);
|
|
11226
|
+
out.push(pick(c.value));
|
|
11227
|
+
c.continue();
|
|
11228
|
+
};
|
|
11229
|
+
});
|
|
11230
|
+
}
|
|
11231
|
+
const self = {
|
|
11232
|
+
dbName,
|
|
11233
|
+
async putRecord(record) {
|
|
11234
|
+
await run2(RECORDS, "readwrite", (s) => s.put(record));
|
|
11235
|
+
},
|
|
11236
|
+
async getRecord(key) {
|
|
11237
|
+
return await run2(RECORDS, "readonly", (s) => s.get(key));
|
|
11238
|
+
},
|
|
11239
|
+
async hasRecord(key) {
|
|
11240
|
+
return await run2(RECORDS, "readonly", (s) => s.count(key)) > 0;
|
|
11241
|
+
},
|
|
11242
|
+
async deleteRecords(keys) {
|
|
11243
|
+
if (keys.length === 0) return;
|
|
11244
|
+
const db = await openDB2();
|
|
11245
|
+
const tx = db.transaction(RECORDS, "readwrite");
|
|
11246
|
+
const store3 = tx.objectStore(RECORDS);
|
|
11247
|
+
for (const key of keys) store3.delete(key);
|
|
11248
|
+
await new Promise((resolve, reject) => {
|
|
11249
|
+
tx.oncomplete = () => resolve();
|
|
11250
|
+
tx.onerror = () => reject(tx.error);
|
|
11251
|
+
tx.onabort = () => reject(tx.error);
|
|
11252
|
+
});
|
|
11253
|
+
},
|
|
11254
|
+
async recordsInBucket(bucket2) {
|
|
11255
|
+
return await run2(
|
|
11256
|
+
RECORDS,
|
|
11257
|
+
"readonly",
|
|
11258
|
+
(s) => s.index("bucket").getAll(bucket2)
|
|
11259
|
+
);
|
|
11260
|
+
},
|
|
11261
|
+
async recordsOfKind(kind) {
|
|
11262
|
+
return await run2(
|
|
11263
|
+
RECORDS,
|
|
11264
|
+
"readonly",
|
|
11265
|
+
(s) => s.index("kind").getAll(kind)
|
|
11266
|
+
);
|
|
11267
|
+
},
|
|
11268
|
+
allRecordSizes() {
|
|
11269
|
+
return scan(({ key, kind, size }) => ({ key, kind, size }));
|
|
11270
|
+
},
|
|
11271
|
+
allRecordAccess() {
|
|
11272
|
+
return scan(({ key, kind, lastAccess }) => ({ key, kind, lastAccess }));
|
|
11273
|
+
},
|
|
11274
|
+
async touchRecord(key) {
|
|
11275
|
+
const record = await self.getRecord(key);
|
|
11276
|
+
if (record) await self.putRecord({ ...record, lastAccess: Date.now() });
|
|
11277
|
+
},
|
|
11278
|
+
async getMeta(key) {
|
|
11279
|
+
const row = await run2(META, "readonly", (s) => s.get(key));
|
|
11280
|
+
return row?.value;
|
|
11281
|
+
},
|
|
11282
|
+
async setMeta(key, value) {
|
|
11283
|
+
await run2(META, "readwrite", (s) => s.put({ key, value }));
|
|
11284
|
+
},
|
|
11285
|
+
async deleteDatabase() {
|
|
11286
|
+
const db = await dbPromise?.catch(() => null);
|
|
11287
|
+
db?.close();
|
|
11288
|
+
dbPromise = null;
|
|
11289
|
+
await new Promise((resolve, reject) => {
|
|
11290
|
+
const request = indexedDB.deleteDatabase(dbName);
|
|
11291
|
+
request.onsuccess = () => resolve();
|
|
11292
|
+
request.onerror = () => reject(request.error);
|
|
11293
|
+
request.onblocked = () => {
|
|
11294
|
+
};
|
|
11295
|
+
});
|
|
11296
|
+
}
|
|
11297
|
+
};
|
|
11298
|
+
return self;
|
|
11299
|
+
}
|
|
11300
|
+
|
|
11301
|
+
// src/mail/offline/store.ts
|
|
11302
|
+
var OFFLINE_MAIL_DB = "arbi-offline-mail";
|
|
11303
|
+
var store = createRecordStore(OFFLINE_MAIL_DB);
|
|
11304
|
+
var putRecord = store.putRecord;
|
|
11305
|
+
var getRecord = store.getRecord;
|
|
11306
|
+
var hasRecord = store.hasRecord;
|
|
11307
|
+
var deleteRecords = store.deleteRecords;
|
|
11308
|
+
var recordsInBucket = store.recordsInBucket;
|
|
11309
|
+
var recordsOfKind = store.recordsOfKind;
|
|
11310
|
+
var allRecordSizes = store.allRecordSizes;
|
|
11311
|
+
var touchRecord = store.touchRecord;
|
|
11312
|
+
var getMeta = store.getMeta;
|
|
11313
|
+
var setMeta = store.setMeta;
|
|
11314
|
+
var deleteOfflineMailDatabase = store.deleteDatabase;
|
|
11315
|
+
|
|
11316
|
+
// src/mail/offline/mailbox.ts
|
|
11317
|
+
var keyOf = {
|
|
11318
|
+
accounts: () => blind("accounts"),
|
|
11319
|
+
policy: () => blind("policy"),
|
|
11320
|
+
folders: (accountId) => blind("folders", accountId),
|
|
11321
|
+
header: (accountId, folderId, messageId) => blind("header", accountId, folderId, messageId),
|
|
11322
|
+
body: (accountId, messageId) => blind("body", accountId, messageId),
|
|
11323
|
+
attachment: (accountId, messageId, attachmentId) => blind("attachment", accountId, messageId, attachmentId)
|
|
11324
|
+
};
|
|
11325
|
+
var bucketOf2 = {
|
|
11326
|
+
global: () => blind("bucket", "global"),
|
|
11327
|
+
folder: (accountId, folderId) => blind("bucket", "folder", accountId, folderId),
|
|
11328
|
+
account: (accountId) => blind("bucket", "account", accountId)
|
|
11329
|
+
};
|
|
11330
|
+
async function put(kind, key, bucket2, data, sortAt = Date.now()) {
|
|
11331
|
+
const now = Date.now();
|
|
11332
|
+
await putRecord({ key, kind, bucket: bucket2, sortAt, size: data.byteLength, lastAccess: now, data });
|
|
11333
|
+
}
|
|
11334
|
+
async function readJson(record) {
|
|
11335
|
+
if (!record) return void 0;
|
|
11336
|
+
try {
|
|
11337
|
+
return await openJson(record.data);
|
|
11338
|
+
} catch {
|
|
11339
|
+
return void 0;
|
|
11340
|
+
}
|
|
11341
|
+
}
|
|
11342
|
+
var receivedAt = (m) => m.received_at ? Date.parse(m.received_at) || 0 : 0;
|
|
11343
|
+
async function saveAccounts(accounts) {
|
|
11344
|
+
await put("accounts", await keyOf.accounts(), await bucketOf2.global(), await sealJson(accounts));
|
|
11345
|
+
}
|
|
11346
|
+
async function loadAccounts() {
|
|
11347
|
+
return readJson(await getRecord(await keyOf.accounts()));
|
|
11348
|
+
}
|
|
11349
|
+
async function savePolicy(policy) {
|
|
11350
|
+
await put("policy", await keyOf.policy(), await bucketOf2.global(), await sealJson(policy));
|
|
11351
|
+
}
|
|
11352
|
+
async function loadPolicy() {
|
|
11353
|
+
return readJson(await getRecord(await keyOf.policy()));
|
|
11354
|
+
}
|
|
11355
|
+
async function saveFolders(accountId, folders) {
|
|
11356
|
+
await put(
|
|
11357
|
+
"folders",
|
|
11358
|
+
await keyOf.folders(accountId),
|
|
11359
|
+
await bucketOf2.account(accountId),
|
|
11360
|
+
await sealJson(folders)
|
|
11361
|
+
);
|
|
11362
|
+
}
|
|
11363
|
+
async function loadFolders(accountId) {
|
|
11364
|
+
return readJson(await getRecord(await keyOf.folders(accountId)));
|
|
11365
|
+
}
|
|
11366
|
+
function toHeader(message) {
|
|
11367
|
+
return {
|
|
11368
|
+
...message,
|
|
11369
|
+
text: "",
|
|
11370
|
+
html: void 0,
|
|
11371
|
+
delivery: void 0,
|
|
11372
|
+
attachments: message.attachments.map((a) => ({ ...a, content_base64: null }))
|
|
11373
|
+
};
|
|
11374
|
+
}
|
|
11375
|
+
async function saveHeader(accountId, folderId, message) {
|
|
11376
|
+
await put(
|
|
11377
|
+
"header",
|
|
11378
|
+
await keyOf.header(accountId, folderId, message.id),
|
|
11379
|
+
await bucketOf2.folder(accountId, folderId),
|
|
11380
|
+
await sealJson(toHeader(message)),
|
|
11381
|
+
receivedAt(message)
|
|
11382
|
+
);
|
|
11383
|
+
}
|
|
11384
|
+
async function loadHeaders(accountId, folderId) {
|
|
11385
|
+
const records = await recordsInBucket(await bucketOf2.folder(accountId, folderId));
|
|
11386
|
+
const out = [];
|
|
11387
|
+
for (const record of records) {
|
|
11388
|
+
if (record.kind !== "header") continue;
|
|
11389
|
+
const message = await readJson(record);
|
|
11390
|
+
if (message) out.push({ key: record.key, sortAt: record.sortAt, message });
|
|
11391
|
+
}
|
|
11392
|
+
return out.sort((a, b) => b.sortAt - a.sortAt);
|
|
11393
|
+
}
|
|
11394
|
+
async function hasFolder(accountId) {
|
|
11395
|
+
return hasRecord(await keyOf.folders(accountId));
|
|
11396
|
+
}
|
|
11397
|
+
async function saveBody(accountId, message) {
|
|
11398
|
+
const stripped = {
|
|
11399
|
+
...message,
|
|
11400
|
+
attachments: message.attachments.map((a) => ({ ...a, content_base64: null }))
|
|
11401
|
+
};
|
|
11402
|
+
await put(
|
|
11403
|
+
"body",
|
|
11404
|
+
await keyOf.body(accountId, message.id),
|
|
11405
|
+
await bucketOf2.account(accountId),
|
|
11406
|
+
await sealJson(stripped),
|
|
11407
|
+
receivedAt(message)
|
|
11408
|
+
);
|
|
11409
|
+
}
|
|
11410
|
+
async function hasBody(accountId, messageId) {
|
|
11411
|
+
return hasRecord(await keyOf.body(accountId, messageId));
|
|
11412
|
+
}
|
|
11413
|
+
async function loadBody(accountId, messageId) {
|
|
11414
|
+
return readJson(await getRecord(await keyOf.body(accountId, messageId)));
|
|
11415
|
+
}
|
|
11416
|
+
async function saveAttachment(accountId, messageId, attachmentId, bytes) {
|
|
11417
|
+
await put(
|
|
11418
|
+
"attachment",
|
|
11419
|
+
await keyOf.attachment(accountId, messageId, attachmentId),
|
|
11420
|
+
await bucketOf2.account(accountId),
|
|
11421
|
+
await sealBytes(bytes)
|
|
11422
|
+
);
|
|
11423
|
+
}
|
|
11424
|
+
async function hasAttachment(accountId, messageId, attachmentId) {
|
|
11425
|
+
return hasRecord(await keyOf.attachment(accountId, messageId, attachmentId));
|
|
11426
|
+
}
|
|
11427
|
+
async function loadAttachment(accountId, messageId, attachmentId) {
|
|
11428
|
+
const key = await keyOf.attachment(accountId, messageId, attachmentId);
|
|
11429
|
+
const record = await getRecord(key);
|
|
11430
|
+
if (!record) return void 0;
|
|
11431
|
+
try {
|
|
11432
|
+
const bytes = await openBytes(record.data);
|
|
11433
|
+
void touchRecord(key);
|
|
11434
|
+
return bytes;
|
|
11435
|
+
} catch {
|
|
11436
|
+
return void 0;
|
|
11437
|
+
}
|
|
11438
|
+
}
|
|
11439
|
+
async function removeKeys(keys) {
|
|
11440
|
+
await deleteRecords(keys);
|
|
11441
|
+
}
|
|
11442
|
+
function bodyText(body) {
|
|
11443
|
+
const html = body.text ? "" : (body.html ?? "").replace(/<[^>]*>/g, " ");
|
|
11444
|
+
return `${body.text} ${html} ${body.attachments.map((a) => a.name).join(" ")}`;
|
|
11445
|
+
}
|
|
11446
|
+
var folded = (value) => value.toLocaleLowerCase().normalize("NFKD");
|
|
11447
|
+
async function searchFolder(accountId, folderId, query) {
|
|
11448
|
+
const words = folded(query).split(/\s+/).filter(Boolean);
|
|
11449
|
+
const headers = await loadHeaders(accountId, folderId);
|
|
11450
|
+
if (words.length === 0) return headers.map((h) => h.message);
|
|
11451
|
+
const out = [];
|
|
11452
|
+
for (const { message } of headers) {
|
|
11453
|
+
const people = [...message.sender, ...message.to, ...message.cc].map((p) => `${p.name ?? ""} ${p.email}`).join(" ");
|
|
11454
|
+
let haystack = folded(`${message.subject} ${people} ${message.preview}`);
|
|
11455
|
+
if (!words.every((w) => haystack.includes(w))) {
|
|
11456
|
+
const body = await loadBody(accountId, message.id);
|
|
11457
|
+
if (body) haystack += " " + folded(bodyText(body));
|
|
11458
|
+
}
|
|
11459
|
+
if (words.every((w) => haystack.includes(w))) out.push(message);
|
|
11460
|
+
}
|
|
11461
|
+
return out;
|
|
11462
|
+
}
|
|
11463
|
+
|
|
11464
|
+
// src/mail/offline/settings.ts
|
|
11465
|
+
var DEFAULT_OFFLINE_MAIL_SETTINGS = {
|
|
11466
|
+
enabled: false,
|
|
11467
|
+
scope: "90d",
|
|
11468
|
+
attachmentCapMb: 25,
|
|
11469
|
+
quotaMb: 1024
|
|
11470
|
+
};
|
|
11471
|
+
var SCOPE_DAYS = {
|
|
11472
|
+
"30d": 30,
|
|
11473
|
+
"90d": 90,
|
|
11474
|
+
"365d": 365,
|
|
11475
|
+
all: null
|
|
11476
|
+
};
|
|
11477
|
+
var STORAGE_PREFIX = "arbi-offline-mail-settings:";
|
|
11478
|
+
function getOfflineMailSettings(userExtId) {
|
|
11479
|
+
if (!userExtId) return DEFAULT_OFFLINE_MAIL_SETTINGS;
|
|
11480
|
+
const key = STORAGE_PREFIX + userExtId;
|
|
11481
|
+
try {
|
|
11482
|
+
const raw = localStorage.getItem(key);
|
|
11483
|
+
return raw ? { ...DEFAULT_OFFLINE_MAIL_SETTINGS, ...JSON.parse(raw) } : DEFAULT_OFFLINE_MAIL_SETTINGS;
|
|
11484
|
+
} catch {
|
|
11485
|
+
return DEFAULT_OFFLINE_MAIL_SETTINGS;
|
|
11486
|
+
}
|
|
11487
|
+
}
|
|
11488
|
+
function setOfflineMailSettings(userExtId, patch) {
|
|
11489
|
+
const next = { ...getOfflineMailSettings(userExtId), ...patch };
|
|
11490
|
+
if (userExtId) {
|
|
11491
|
+
try {
|
|
11492
|
+
localStorage.setItem(STORAGE_PREFIX + userExtId, JSON.stringify(next));
|
|
11493
|
+
} catch {
|
|
11494
|
+
}
|
|
11495
|
+
}
|
|
11496
|
+
notifyOfflineMailChange("settings");
|
|
11497
|
+
return next;
|
|
11498
|
+
}
|
|
11499
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
11500
|
+
var CHANNEL = "arbi-offline-mail";
|
|
11501
|
+
var channel = null;
|
|
11502
|
+
function getChannel() {
|
|
11503
|
+
if (channel || typeof BroadcastChannel === "undefined") return channel;
|
|
11504
|
+
channel = new BroadcastChannel(CHANNEL);
|
|
11505
|
+
channel.onmessage = (event) => {
|
|
11506
|
+
for (const listener of listeners) listener(event.data);
|
|
11507
|
+
};
|
|
11508
|
+
return channel;
|
|
11509
|
+
}
|
|
11510
|
+
function notifyOfflineMailChange(change) {
|
|
11511
|
+
for (const listener of listeners) listener(change);
|
|
11512
|
+
try {
|
|
11513
|
+
getChannel()?.postMessage(change);
|
|
11514
|
+
} catch {
|
|
11515
|
+
}
|
|
11516
|
+
}
|
|
11517
|
+
function onOfflineMailChange(listener) {
|
|
11518
|
+
getChannel();
|
|
11519
|
+
listeners.add(listener);
|
|
11520
|
+
return () => listeners.delete(listener);
|
|
11521
|
+
}
|
|
11522
|
+
|
|
11523
|
+
// src/mail/offline/drafts.ts
|
|
11524
|
+
var keyFor = (id) => blind("draft", id);
|
|
11525
|
+
async function saveLocalDraft(accountId, compose, id = crypto.randomUUID()) {
|
|
11526
|
+
const draft = { id, accountId, compose, updatedAt: Date.now() };
|
|
11527
|
+
await putRecord({
|
|
11528
|
+
key: await keyFor(id),
|
|
11529
|
+
kind: "draft",
|
|
11530
|
+
bucket: await blind("bucket", "drafts", accountId),
|
|
11531
|
+
sortAt: draft.updatedAt,
|
|
11532
|
+
size: 0,
|
|
11533
|
+
lastAccess: draft.updatedAt,
|
|
11534
|
+
data: await sealJson(draft)
|
|
11535
|
+
});
|
|
11536
|
+
notifyOfflineMailChange("drafts");
|
|
11537
|
+
return draft;
|
|
11538
|
+
}
|
|
11539
|
+
async function listLocalDrafts(accountId) {
|
|
11540
|
+
const drafts = [];
|
|
11541
|
+
for (const record of await recordsOfKind("draft")) {
|
|
11542
|
+
try {
|
|
11543
|
+
const draft = await openJson(record.data);
|
|
11544
|
+
if (!accountId || draft.accountId === accountId) drafts.push(draft);
|
|
11545
|
+
} catch {
|
|
11546
|
+
}
|
|
11547
|
+
}
|
|
11548
|
+
return drafts.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
11549
|
+
}
|
|
11550
|
+
async function deleteLocalDraft(id) {
|
|
11551
|
+
await deleteRecords([await keyFor(id)]);
|
|
11552
|
+
notifyOfflineMailChange("drafts");
|
|
11553
|
+
}
|
|
11554
|
+
async function syncLocalDrafts(save) {
|
|
11555
|
+
let uploaded = 0;
|
|
11556
|
+
for (const draft of await listLocalDrafts()) {
|
|
11557
|
+
if (draft.error) continue;
|
|
11558
|
+
try {
|
|
11559
|
+
await save(draft);
|
|
11560
|
+
await deleteLocalDraft(draft.id);
|
|
11561
|
+
uploaded++;
|
|
11562
|
+
} catch (error) {
|
|
11563
|
+
const status = error.status;
|
|
11564
|
+
if (isNetworkFailure(error) || status === void 0 || status === 401 || status >= 500) break;
|
|
11565
|
+
await putRecord({
|
|
11566
|
+
key: await keyFor(draft.id),
|
|
11567
|
+
kind: "draft",
|
|
11568
|
+
bucket: await blind("bucket", "drafts", draft.accountId),
|
|
11569
|
+
sortAt: draft.updatedAt,
|
|
11570
|
+
size: 0,
|
|
11571
|
+
lastAccess: Date.now(),
|
|
11572
|
+
data: await sealJson({
|
|
11573
|
+
...draft,
|
|
11574
|
+
error: error instanceof Error ? error.message : "The server refused this draft"
|
|
11575
|
+
})
|
|
11576
|
+
});
|
|
11577
|
+
notifyOfflineMailChange("drafts");
|
|
11578
|
+
}
|
|
11579
|
+
}
|
|
11580
|
+
return uploaded;
|
|
11581
|
+
}
|
|
11582
|
+
|
|
11583
|
+
// src/mail/offline/outbox.ts
|
|
11584
|
+
var bucket = () => blind("bucket", "outbox");
|
|
11585
|
+
var keyFor2 = (id) => blind("outbox", id);
|
|
11586
|
+
function outboxProblem(compose) {
|
|
11587
|
+
const missing = compose.attachments.find((a) => a.content_base64 == null);
|
|
11588
|
+
if (missing)
|
|
11589
|
+
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.`;
|
|
11590
|
+
return null;
|
|
11591
|
+
}
|
|
11592
|
+
var seqCounter = 0;
|
|
11593
|
+
async function write(item) {
|
|
11594
|
+
await putRecord({
|
|
11595
|
+
key: await keyFor2(item.id),
|
|
11596
|
+
kind: "outbox",
|
|
11597
|
+
bucket: await bucket(),
|
|
11598
|
+
sortAt: item.seq,
|
|
11599
|
+
size: 0,
|
|
11600
|
+
lastAccess: Date.now(),
|
|
11601
|
+
data: await sealJson(item)
|
|
11602
|
+
});
|
|
11603
|
+
}
|
|
11604
|
+
async function listOutbox() {
|
|
11605
|
+
const records = await recordsInBucket(await bucket());
|
|
11606
|
+
const items = [];
|
|
11607
|
+
for (const record of records) {
|
|
11608
|
+
try {
|
|
11609
|
+
items.push(await openJson(record.data));
|
|
11610
|
+
} catch {
|
|
11611
|
+
}
|
|
11612
|
+
}
|
|
11613
|
+
return items.sort((a, b) => a.seq - b.seq);
|
|
11614
|
+
}
|
|
11615
|
+
async function enqueueOutbox(input) {
|
|
11616
|
+
const problem = outboxProblem(input.compose);
|
|
11617
|
+
if (problem) throw new Error(problem);
|
|
11618
|
+
const now = Date.now();
|
|
11619
|
+
seqCounter = Math.max(seqCounter + 1, now * 1e3);
|
|
11620
|
+
const item = {
|
|
11621
|
+
id: crypto.randomUUID(),
|
|
11622
|
+
requestId: input.requestId,
|
|
11623
|
+
account: input.account,
|
|
11624
|
+
compose: input.compose,
|
|
11625
|
+
secure: input.secure,
|
|
11626
|
+
securePayload: input.securePayload,
|
|
11627
|
+
status: "queued",
|
|
11628
|
+
attempts: 0,
|
|
11629
|
+
createdAt: now,
|
|
11630
|
+
seq: seqCounter
|
|
11631
|
+
};
|
|
11632
|
+
await write(item);
|
|
11633
|
+
notifyOfflineMailChange("outbox");
|
|
11634
|
+
return item;
|
|
11635
|
+
}
|
|
11636
|
+
async function update(item, patch) {
|
|
11637
|
+
const next = { ...item, ...patch };
|
|
11638
|
+
await write(next);
|
|
11639
|
+
notifyOfflineMailChange("outbox");
|
|
11640
|
+
return next;
|
|
11641
|
+
}
|
|
11642
|
+
async function deleteOutboxItem(id) {
|
|
11643
|
+
await deleteRecords([await keyFor2(id)]);
|
|
11644
|
+
notifyOfflineMailChange("outbox");
|
|
11645
|
+
}
|
|
11646
|
+
async function retryOutboxItem(id) {
|
|
11647
|
+
const item = (await listOutbox()).find((i) => i.id === id);
|
|
11648
|
+
if (item && item.status === "failed")
|
|
11649
|
+
await update(item, { status: "queued", error: void 0, inDoubt: item.inDoubt });
|
|
11650
|
+
}
|
|
11651
|
+
var IN_DOUBT_MESSAGE = "The mail server didn't confirm this message. It may already have been sent: check Sent before sending it again.";
|
|
11652
|
+
function classify(error) {
|
|
11653
|
+
if (isNetworkFailure(error)) return { kind: "offline" };
|
|
11654
|
+
const status = error?.status;
|
|
11655
|
+
const detail = error instanceof Error ? error.message : "The message could not be sent";
|
|
11656
|
+
if (status === void 0) return { kind: "failed", error: detail, inDoubt: false };
|
|
11657
|
+
if (status === 409) return { kind: "failed", error: IN_DOUBT_MESSAGE, inDoubt: true };
|
|
11658
|
+
if (status >= 500) return { kind: "failed", error: IN_DOUBT_MESSAGE, inDoubt: true };
|
|
11659
|
+
return { kind: "failed", error: detail, inDoubt: false };
|
|
11660
|
+
}
|
|
11661
|
+
async function prepared(item, sender) {
|
|
11662
|
+
if (!item.secure || item.securePayload !== void 0) return item;
|
|
11663
|
+
return update(item, { securePayload: await sender.prepareSecure(item) });
|
|
11664
|
+
}
|
|
11665
|
+
async function attempt(item, sender) {
|
|
11666
|
+
try {
|
|
11667
|
+
const result = await sender.send(item);
|
|
11668
|
+
return { kind: "sent", serverId: result?.id };
|
|
11669
|
+
} catch (error) {
|
|
11670
|
+
const outcome = classify(error);
|
|
11671
|
+
if (error.status === 401) {
|
|
11672
|
+
try {
|
|
11673
|
+
await sender.unlock(item.account);
|
|
11674
|
+
const result = await sender.send(item);
|
|
11675
|
+
return { kind: "sent", serverId: result?.id };
|
|
11676
|
+
} catch (retryError) {
|
|
11677
|
+
return classify(retryError);
|
|
11678
|
+
}
|
|
11679
|
+
}
|
|
11680
|
+
return outcome;
|
|
11681
|
+
}
|
|
11682
|
+
}
|
|
11683
|
+
async function flushOutboxOnce(sender, isOnline) {
|
|
11684
|
+
const result = { sent: 0, failed: 0, interrupted: false };
|
|
11685
|
+
const unlocked2 = /* @__PURE__ */ new Set();
|
|
11686
|
+
for (; ; ) {
|
|
11687
|
+
if (!isOnline()) {
|
|
11688
|
+
result.interrupted = true;
|
|
11689
|
+
break;
|
|
11690
|
+
}
|
|
11691
|
+
const next = (await listOutbox()).find((i) => i.status === "queued" || i.status === "sending");
|
|
11692
|
+
if (!next) break;
|
|
11693
|
+
let item = await update(next, { status: "sending", attempts: next.attempts + 1 });
|
|
11694
|
+
try {
|
|
11695
|
+
item = await prepared(item, sender);
|
|
11696
|
+
} catch (error) {
|
|
11697
|
+
const message = error instanceof Error ? error.message : "Could not encrypt the message";
|
|
11698
|
+
await update(item, { status: "failed", error: message });
|
|
11699
|
+
result.failed++;
|
|
11700
|
+
continue;
|
|
11701
|
+
}
|
|
11702
|
+
if (!unlocked2.has(item.account.id)) {
|
|
11703
|
+
await sender.unlock(item.account).catch(() => {
|
|
11704
|
+
});
|
|
11705
|
+
unlocked2.add(item.account.id);
|
|
11706
|
+
}
|
|
11707
|
+
const outcome = await attempt(item, sender);
|
|
11708
|
+
if (outcome.kind === "sent") {
|
|
11709
|
+
item = await update(item, {
|
|
11710
|
+
status: "sent",
|
|
11711
|
+
sentAt: Date.now(),
|
|
11712
|
+
error: void 0,
|
|
11713
|
+
inDoubt: false
|
|
11714
|
+
});
|
|
11715
|
+
result.sent++;
|
|
11716
|
+
} else if (outcome.kind === "offline") {
|
|
11717
|
+
await update(item, { status: "queued" });
|
|
11718
|
+
result.interrupted = true;
|
|
11719
|
+
break;
|
|
11720
|
+
} else {
|
|
11721
|
+
await update(item, {
|
|
11722
|
+
status: "failed",
|
|
11723
|
+
error: outcome.error,
|
|
11724
|
+
inDoubt: item.inDoubt || outcome.inDoubt
|
|
11725
|
+
});
|
|
11726
|
+
result.failed++;
|
|
11727
|
+
}
|
|
11728
|
+
}
|
|
11729
|
+
return result;
|
|
11730
|
+
}
|
|
11731
|
+
async function purgeSentOutbox(olderThanMs = 0) {
|
|
11732
|
+
const cutoff = Date.now() - olderThanMs;
|
|
11733
|
+
const done = (await listOutbox()).filter((i) => i.status === "sent" && (i.sentAt ?? 0) <= cutoff);
|
|
11734
|
+
if (done.length === 0) return;
|
|
11735
|
+
await deleteRecords(await Promise.all(done.map((i) => keyFor2(i.id))));
|
|
11736
|
+
notifyOfflineMailChange("outbox");
|
|
11737
|
+
}
|
|
11738
|
+
var STATUS_KEY = "status";
|
|
11739
|
+
var FULL_SYNC_EVERY_MS = 60 * 60 * 1e3;
|
|
11740
|
+
var MAX_PAGES = 200;
|
|
11741
|
+
async function getOfflineMailStatus() {
|
|
11742
|
+
return await getMeta(STATUS_KEY) ?? { messages: 0, bytes: 0, skipped: [] };
|
|
11743
|
+
}
|
|
11744
|
+
var MB = 1024 * 1024;
|
|
11745
|
+
var time = (m) => m.received_at ? Date.parse(m.received_at) || 0 : 0;
|
|
11746
|
+
var sameHeader = (a, b) => JSON.stringify(toHeader(a)) === JSON.stringify(toHeader(b));
|
|
11747
|
+
var Budget = class {
|
|
11748
|
+
constructor(used, quota) {
|
|
11749
|
+
this.used = used;
|
|
11750
|
+
this.quota = quota;
|
|
11751
|
+
}
|
|
11752
|
+
used;
|
|
11753
|
+
quota;
|
|
11754
|
+
get full() {
|
|
11755
|
+
return this.used >= this.quota;
|
|
11756
|
+
}
|
|
11757
|
+
take(bytes) {
|
|
11758
|
+
this.used += bytes;
|
|
11759
|
+
}
|
|
11760
|
+
};
|
|
11761
|
+
async function saveMessageContent(transport, account, header, settings, budget) {
|
|
11762
|
+
const message = await transport.message(account, header.id);
|
|
11763
|
+
await saveBody(account.id, message);
|
|
11764
|
+
budget.take((message.text?.length ?? 0) + (message.html?.length ?? 0));
|
|
11765
|
+
let attachmentBytes = 0;
|
|
11766
|
+
for (const attachment of message.attachments) {
|
|
11767
|
+
if (budget.full) return;
|
|
11768
|
+
if (attachmentBytes + attachment.size > settings.attachmentCapMb * MB) continue;
|
|
11769
|
+
if (await hasAttachment(account.id, message.id, attachment.id)) continue;
|
|
11770
|
+
const bytes = attachment.content_base64 ? Uint8Array.from(atob(attachment.content_base64), (c) => c.charCodeAt(0)) : await transport.attachment(account, message.id, attachment.id);
|
|
11771
|
+
await saveAttachment(account.id, message.id, attachment.id, bytes);
|
|
11772
|
+
attachmentBytes += bytes.byteLength;
|
|
11773
|
+
budget.take(bytes.byteLength);
|
|
11774
|
+
}
|
|
11775
|
+
}
|
|
11776
|
+
async function syncFolder(transport, account, folder, settings, windowStart, full, budget) {
|
|
11777
|
+
const saved = new Map(
|
|
11778
|
+
(await loadHeaders(account.id, folder.id)).map((h) => [h.message.id, h])
|
|
11779
|
+
);
|
|
11780
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11781
|
+
let oldestSeen = Infinity;
|
|
11782
|
+
let cursor = null;
|
|
11783
|
+
let reachedEnd = false;
|
|
11784
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
11785
|
+
const result = await transport.messages(account, folder.id, cursor);
|
|
11786
|
+
let anythingNew = false;
|
|
11787
|
+
let outOfScope = false;
|
|
11788
|
+
for (const message of result.messages) {
|
|
11789
|
+
const at = time(message);
|
|
11790
|
+
if (at && at < windowStart) {
|
|
11791
|
+
outOfScope = true;
|
|
11792
|
+
break;
|
|
11793
|
+
}
|
|
11794
|
+
seen.add(message.id);
|
|
11795
|
+
if (at) oldestSeen = Math.min(oldestSeen, at);
|
|
11796
|
+
const previous = saved.get(message.id)?.message;
|
|
11797
|
+
if (!previous || !sameHeader(previous, message)) {
|
|
11798
|
+
await saveHeader(account.id, folder.id, message);
|
|
11799
|
+
anythingNew = true;
|
|
11800
|
+
}
|
|
11801
|
+
if (!budget.full && !await hasBody(account.id, message.id)) {
|
|
11802
|
+
try {
|
|
11803
|
+
await saveMessageContent(transport, account, message, settings, budget);
|
|
11804
|
+
anythingNew = true;
|
|
11805
|
+
} catch (error) {
|
|
11806
|
+
client.logger.warn("[offline-mail] could not save a message body", error);
|
|
11807
|
+
}
|
|
11808
|
+
}
|
|
11809
|
+
}
|
|
11810
|
+
cursor = result.next_cursor ?? null;
|
|
11811
|
+
if (outOfScope || !cursor) {
|
|
11812
|
+
reachedEnd = true;
|
|
11813
|
+
break;
|
|
11814
|
+
}
|
|
11815
|
+
if (!full && !anythingNew) break;
|
|
11816
|
+
}
|
|
11817
|
+
const stale = [...saved.values()].filter(
|
|
11818
|
+
(h) => !seen.has(h.message.id) && (reachedEnd || h.sortAt >= oldestSeen)
|
|
11819
|
+
);
|
|
11820
|
+
await removeKeys(stale.map((h) => h.key));
|
|
11821
|
+
}
|
|
11822
|
+
async function collectGarbage(accounts) {
|
|
11823
|
+
for (const account of accounts) {
|
|
11824
|
+
const folders = await loadFolders(account.id) ?? [];
|
|
11825
|
+
const expected = /* @__PURE__ */ new Set([await keyOf.folders(account.id)]);
|
|
11826
|
+
for (const folder of folders) {
|
|
11827
|
+
for (const { message } of await loadHeaders(account.id, folder.id)) {
|
|
11828
|
+
expected.add(await keyOf.body(account.id, message.id));
|
|
11829
|
+
for (const attachment of message.attachments)
|
|
11830
|
+
expected.add(await keyOf.attachment(account.id, message.id, attachment.id));
|
|
11831
|
+
}
|
|
11832
|
+
}
|
|
11833
|
+
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);
|
|
11834
|
+
await removeKeys(orphans);
|
|
11835
|
+
}
|
|
11836
|
+
}
|
|
11837
|
+
async function forgetAccount(accountId) {
|
|
11838
|
+
for (const folder of await loadFolders(accountId) ?? []) {
|
|
11839
|
+
const records2 = await recordsInBucket(await bucketOf2.folder(accountId, folder.id));
|
|
11840
|
+
await removeKeys(records2.map((r) => r.key));
|
|
11841
|
+
}
|
|
11842
|
+
const records = await recordsInBucket(await bucketOf2.account(accountId));
|
|
11843
|
+
await removeKeys(records.filter((r) => r.kind !== "draft").map((r) => r.key));
|
|
11844
|
+
}
|
|
11845
|
+
async function enforceQuota(quotaBytes) {
|
|
11846
|
+
let used = (await allRecordSizes()).reduce((n, r) => n + r.size, 0);
|
|
11847
|
+
if (used <= quotaBytes) return used;
|
|
11848
|
+
for (const kind of ["attachment", "body"]) {
|
|
11849
|
+
const records = (await recordsOfKind(kind)).sort(
|
|
11850
|
+
(a, b) => kind === "attachment" ? a.lastAccess - b.lastAccess : a.sortAt - b.sortAt
|
|
11851
|
+
);
|
|
11852
|
+
const evict = [];
|
|
11853
|
+
for (const record of records) {
|
|
11854
|
+
if (used <= quotaBytes) break;
|
|
11855
|
+
evict.push(record.key);
|
|
11856
|
+
used -= record.size;
|
|
11857
|
+
}
|
|
11858
|
+
await removeKeys(evict);
|
|
11859
|
+
if (used <= quotaBytes) break;
|
|
11860
|
+
}
|
|
11861
|
+
return used;
|
|
11862
|
+
}
|
|
11863
|
+
async function syncMailbox(transport, settings, options = {}) {
|
|
11864
|
+
const now = options.now ?? Date.now();
|
|
11865
|
+
const previous = await getOfflineMailStatus();
|
|
11866
|
+
const full = options.full || !previous.lastFullSyncAt || now - previous.lastFullSyncAt > FULL_SYNC_EVERY_MS;
|
|
11867
|
+
const days = SCOPE_DAYS[settings.scope];
|
|
11868
|
+
const windowStart = days === null ? 0 : now - days * 24 * 60 * 60 * 1e3;
|
|
11869
|
+
const quota = settings.quotaMb * MB;
|
|
11870
|
+
const skipped = [];
|
|
11871
|
+
let error;
|
|
11872
|
+
try {
|
|
11873
|
+
await savePolicy(await transport.policy());
|
|
11874
|
+
const accounts = await transport.accounts();
|
|
11875
|
+
const before = await loadAccounts() ?? [];
|
|
11876
|
+
await saveAccounts(accounts);
|
|
11877
|
+
for (const gone of before.filter((b) => !accounts.some((a) => a.id === b.id)))
|
|
11878
|
+
await forgetAccount(gone.id);
|
|
11879
|
+
const budget = new Budget(
|
|
11880
|
+
(await allRecordSizes()).reduce((n, r) => n + r.size, 0),
|
|
11881
|
+
quota
|
|
11882
|
+
);
|
|
11883
|
+
for (const account of accounts) {
|
|
11884
|
+
if (account.needs_reconsent) {
|
|
11885
|
+
skipped.push(account.email);
|
|
11886
|
+
continue;
|
|
11887
|
+
}
|
|
11888
|
+
try {
|
|
11889
|
+
await transport.unlock(account);
|
|
11890
|
+
const folders = await transport.folders(account);
|
|
11891
|
+
const oldFolders = await loadFolders(account.id) ?? [];
|
|
11892
|
+
await saveFolders(account.id, folders);
|
|
11893
|
+
for (const gone of oldFolders.filter((o) => !folders.some((f) => f.id === o.id))) {
|
|
11894
|
+
const records = await recordsInBucket(await bucketOf2.folder(account.id, gone.id));
|
|
11895
|
+
await removeKeys(records.map((r) => r.key));
|
|
11896
|
+
}
|
|
11897
|
+
for (const folder of folders)
|
|
11898
|
+
await syncFolder(transport, account, folder, settings, windowStart, full, budget);
|
|
11899
|
+
notifyOfflineMailChange("mailbox");
|
|
11900
|
+
} catch (accountError) {
|
|
11901
|
+
if (accountError.status === void 0) throw accountError;
|
|
11902
|
+
client.logger.warn("[offline-mail] account sync failed", accountError);
|
|
11903
|
+
skipped.push(account.email);
|
|
11904
|
+
}
|
|
11905
|
+
}
|
|
11906
|
+
if (full) await collectGarbage(accounts);
|
|
11907
|
+
} catch (syncError) {
|
|
11908
|
+
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";
|
|
11909
|
+
}
|
|
11910
|
+
const bytes = await enforceQuota(quota);
|
|
11911
|
+
const messages = (await recordsOfKind("header")).length;
|
|
11912
|
+
const status = {
|
|
11913
|
+
lastSyncedAt: error ? previous.lastSyncedAt : now,
|
|
11914
|
+
lastFullSyncAt: !error && full ? now : previous.lastFullSyncAt,
|
|
11915
|
+
messages,
|
|
11916
|
+
bytes,
|
|
11917
|
+
error,
|
|
11918
|
+
skipped
|
|
11919
|
+
};
|
|
11920
|
+
await setMeta(STATUS_KEY, status);
|
|
11921
|
+
notifyOfflineMailChange("status");
|
|
11922
|
+
return status;
|
|
11923
|
+
}
|
|
11924
|
+
|
|
11925
|
+
// src/mail/offline/live.ts
|
|
11926
|
+
var OUTBOX_FOLDER = "outbox";
|
|
11927
|
+
function sessionUser(arbi) {
|
|
11928
|
+
return arbi.session?.getState().user?.external_id ?? null;
|
|
11929
|
+
}
|
|
11930
|
+
function hasLiveSession(arbi) {
|
|
11931
|
+
return !!arbi.session?.getState().serverSessionKey;
|
|
11932
|
+
}
|
|
11933
|
+
function canReachMail(arbi) {
|
|
11934
|
+
return reactQuery.onlineManager.isOnline() && hasLiveSession(arbi);
|
|
11935
|
+
}
|
|
11936
|
+
var unlocked = { nativeUnlocked: true };
|
|
11937
|
+
function mailCall(arbi, path, init = {}) {
|
|
11938
|
+
return mailJson((p, i) => arbi.mail.fetch(p, i, unlocked), path, init);
|
|
11939
|
+
}
|
|
11940
|
+
function liveTransport(arbi) {
|
|
11941
|
+
const unlock = async (account) => {
|
|
11942
|
+
if (!hasLiveSession(arbi))
|
|
11943
|
+
throw Object.assign(new Error("Sign in to unlock your mailbox"), { status: 401 });
|
|
11944
|
+
await arbi.mail.unlockAccount(account);
|
|
11945
|
+
};
|
|
11946
|
+
return {
|
|
11947
|
+
policy: () => mailCall(arbi, "/policy"),
|
|
11948
|
+
accounts: () => mailCall(arbi, "/accounts"),
|
|
11949
|
+
unlock,
|
|
11950
|
+
folders: (account) => mailCall(arbi, `${accountPath(account.id)}/folders`),
|
|
11951
|
+
messages: (account, folderId, cursor) => mailCall(
|
|
11952
|
+
arbi,
|
|
11953
|
+
`${accountPath(account.id)}/messages?${new URLSearchParams({
|
|
11954
|
+
folder_id: folderId,
|
|
11955
|
+
q: "",
|
|
11956
|
+
limit: "100",
|
|
11957
|
+
...cursor ? { cursor } : {}
|
|
11958
|
+
})}`
|
|
11959
|
+
),
|
|
11960
|
+
message: async (account, messageId) => {
|
|
11961
|
+
const path = `${accountPath(account.id)}/messages/${encodeURIComponent(messageId)}`;
|
|
11962
|
+
try {
|
|
11963
|
+
return await mailCall(arbi, path);
|
|
11964
|
+
} catch (error) {
|
|
11965
|
+
if (error.status !== 401 || account.provider !== "native")
|
|
11966
|
+
throw error;
|
|
11967
|
+
if (hasLiveSession(arbi)) await arbi.mail.unlockNative();
|
|
11968
|
+
return mailCall(arbi, path);
|
|
11969
|
+
}
|
|
11970
|
+
},
|
|
11971
|
+
attachment: async (account, messageId, attachmentId) => {
|
|
11972
|
+
const response = await arbi.mail.fetch(
|
|
11973
|
+
`${accountPath(account.id)}/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
11974
|
+
{},
|
|
11975
|
+
unlocked
|
|
11976
|
+
);
|
|
11977
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
11978
|
+
}
|
|
11979
|
+
};
|
|
11980
|
+
}
|
|
11981
|
+
function liveSender(arbi) {
|
|
11982
|
+
const transport = liveTransport(arbi);
|
|
11983
|
+
return {
|
|
11984
|
+
prepareSecure: async (item) => {
|
|
11985
|
+
if (item.account.provider !== "native")
|
|
11986
|
+
throw new Error("Choose your native inbox to send securely");
|
|
11987
|
+
return arbi.secureMail.prepare(item.compose, item.account, item.requestId);
|
|
11988
|
+
},
|
|
11989
|
+
unlock: async (account) => {
|
|
11990
|
+
if (account.provider !== "native") await transport.unlock(account);
|
|
11991
|
+
},
|
|
11992
|
+
send: (item) => item.secure ? mailJson((p, i) => arbi.mail.fetch(p, i), "/secure/send", {
|
|
11993
|
+
method: "POST",
|
|
11994
|
+
body: JSON.stringify(item.securePayload)
|
|
11995
|
+
}) : mailJson(
|
|
11996
|
+
(p, i) => arbi.mail.fetch(p, i),
|
|
11997
|
+
`${accountPath(item.account.id)}/send`,
|
|
11998
|
+
{
|
|
11999
|
+
method: "POST",
|
|
12000
|
+
body: JSON.stringify({ ...item.compose, request_id: item.requestId })
|
|
12001
|
+
}
|
|
12002
|
+
)
|
|
12003
|
+
};
|
|
12004
|
+
}
|
|
12005
|
+
var localLocks = /* @__PURE__ */ new Set();
|
|
12006
|
+
async function exclusive(name, fn) {
|
|
12007
|
+
if (typeof navigator !== "undefined" && navigator.locks?.request) {
|
|
12008
|
+
return navigator.locks.request(name, { ifAvailable: true }, async (lock) => {
|
|
12009
|
+
if (!lock) return false;
|
|
12010
|
+
await fn();
|
|
12011
|
+
return true;
|
|
12012
|
+
});
|
|
12013
|
+
}
|
|
12014
|
+
if (localLocks.has(name)) return false;
|
|
12015
|
+
localLocks.add(name);
|
|
12016
|
+
try {
|
|
12017
|
+
await fn();
|
|
12018
|
+
return true;
|
|
12019
|
+
} finally {
|
|
12020
|
+
localLocks.delete(name);
|
|
12021
|
+
}
|
|
12022
|
+
}
|
|
12023
|
+
var retryTimer = null;
|
|
12024
|
+
async function flushOutbox(context) {
|
|
12025
|
+
const { arbi, queryClient, openOutbox } = context;
|
|
12026
|
+
if (!canReachMail(arbi)) return;
|
|
12027
|
+
const ran = await exclusive("arbi-mail-outbox", async () => {
|
|
12028
|
+
const result = await flushOutboxOnce(liveSender(arbi), () => reactQuery.onlineManager.isOnline());
|
|
12029
|
+
if (result.sent > 0) void queryClient.invalidateQueries({ queryKey: chunkU4PWVSCT_cjs.mailQueryKeys.all() });
|
|
12030
|
+
const remaining = (await listOutbox()).filter((i) => i.status !== "sent");
|
|
12031
|
+
if (result.failed > 0) {
|
|
12032
|
+
chunkDZUBZ45Y_cjs.notify.error(
|
|
12033
|
+
result.failed === 1 ? "A message in your Outbox couldn't be sent" : `${result.failed} messages in your Outbox couldn't be sent`,
|
|
12034
|
+
{
|
|
12035
|
+
description: "Open the Outbox to see why, then retry, edit or delete it.",
|
|
12036
|
+
...openOutbox && { action: { label: "Open Outbox", onClick: openOutbox } }
|
|
12037
|
+
}
|
|
12038
|
+
);
|
|
12039
|
+
} else if (result.sent > 0 && remaining.length === 0) {
|
|
12040
|
+
chunkDZUBZ45Y_cjs.notify.success(
|
|
12041
|
+
result.sent === 1 ? "Your Outbox message was sent" : `${result.sent} Outbox messages sent`
|
|
12042
|
+
);
|
|
12043
|
+
}
|
|
12044
|
+
setTimeout(() => void purgeSentOutbox(3e4), 31e3);
|
|
12045
|
+
});
|
|
12046
|
+
if (!ran) {
|
|
12047
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
12048
|
+
retryTimer = setTimeout(() => void flushOutbox(context), 3e3);
|
|
12049
|
+
}
|
|
12050
|
+
}
|
|
12051
|
+
async function uploadOfflineDrafts({
|
|
12052
|
+
arbi,
|
|
12053
|
+
queryClient
|
|
12054
|
+
}) {
|
|
12055
|
+
if (!canReachMail(arbi)) return;
|
|
12056
|
+
await exclusive("arbi-mail-drafts", async () => {
|
|
12057
|
+
const uploaded = await syncLocalDrafts(
|
|
12058
|
+
({ accountId, compose }) => mailJson(
|
|
12059
|
+
(p, i) => arbi.mail.fetch(p, i),
|
|
12060
|
+
`${accountPath(accountId)}/drafts`,
|
|
12061
|
+
{
|
|
12062
|
+
method: "POST",
|
|
12063
|
+
body: JSON.stringify(compose)
|
|
12064
|
+
}
|
|
12065
|
+
)
|
|
12066
|
+
);
|
|
12067
|
+
if (uploaded > 0) {
|
|
12068
|
+
void queryClient.invalidateQueries({ queryKey: chunkU4PWVSCT_cjs.mailQueryKeys.allMessages() });
|
|
12069
|
+
chunkDZUBZ45Y_cjs.notify.success(
|
|
12070
|
+
uploaded === 1 ? "Your offline draft was saved to Drafts" : `${uploaded} offline drafts were saved to Drafts`
|
|
12071
|
+
);
|
|
12072
|
+
}
|
|
12073
|
+
});
|
|
12074
|
+
}
|
|
12075
|
+
var syncing = false;
|
|
12076
|
+
function isMailSyncRunning() {
|
|
12077
|
+
return syncing;
|
|
12078
|
+
}
|
|
12079
|
+
async function syncOfflineMailbox({ arbi }, options = {}) {
|
|
12080
|
+
const settings = getOfflineMailSettings(sessionUser(arbi));
|
|
12081
|
+
if (!settings.enabled || !canReachMail(arbi)) return;
|
|
12082
|
+
await exclusive("arbi-mail-sync", async () => {
|
|
12083
|
+
syncing = true;
|
|
12084
|
+
notifyOfflineMailChange("status");
|
|
12085
|
+
try {
|
|
12086
|
+
await syncMailbox(liveTransport(arbi), settings, options);
|
|
12087
|
+
} catch (error) {
|
|
12088
|
+
client.logger.error("[offline-mail] sync failed", error);
|
|
12089
|
+
} finally {
|
|
12090
|
+
syncing = false;
|
|
12091
|
+
notifyOfflineMailChange("status");
|
|
12092
|
+
}
|
|
12093
|
+
});
|
|
12094
|
+
}
|
|
12095
|
+
|
|
12096
|
+
// src/mail/offline/reads.ts
|
|
12097
|
+
var NOT_SAVED_HINT = 'Turn on "Keep my mailbox available offline" in Mail settings to read mail without a connection.';
|
|
12098
|
+
async function onlineFirst(online, offline, unavailable) {
|
|
12099
|
+
if (reactQuery.onlineManager.isOnline()) {
|
|
12100
|
+
try {
|
|
12101
|
+
return await online();
|
|
12102
|
+
} catch (error) {
|
|
12103
|
+
if (!isNetworkFailure(error)) throw error;
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
const saved = await offline().catch(() => void 0);
|
|
12107
|
+
if (saved !== void 0) return saved;
|
|
12108
|
+
throw new MailOfflineUnavailableError(unavailable);
|
|
12109
|
+
}
|
|
12110
|
+
function offlineMailReads(getArbi, { keepsMail = () => true } = {}) {
|
|
12111
|
+
const call = (path) => mailJson((p, i) => getArbi().mail.fetch(p, i), path);
|
|
12112
|
+
const storeEnabled = () => getOfflineMailSettings(sessionUser(getArbi())).enabled;
|
|
12113
|
+
const remember = (write2) => {
|
|
12114
|
+
if (!keepsMail()) return;
|
|
12115
|
+
write2().catch((error) => client.logger.warn("[offline-mail] could not save for offline use", error));
|
|
12116
|
+
};
|
|
12117
|
+
return {
|
|
12118
|
+
getPolicy: () => onlineFirst(
|
|
12119
|
+
async () => {
|
|
12120
|
+
const policy = await call("/policy");
|
|
12121
|
+
remember(() => savePolicy(policy));
|
|
12122
|
+
return policy;
|
|
12123
|
+
},
|
|
12124
|
+
loadPolicy,
|
|
12125
|
+
"You're offline. Mail opens here once you've used it online on this device."
|
|
12126
|
+
),
|
|
12127
|
+
listAccounts: () => onlineFirst(
|
|
12128
|
+
async () => {
|
|
12129
|
+
const accounts = await call("/accounts");
|
|
12130
|
+
remember(() => saveAccounts(accounts));
|
|
12131
|
+
return accounts;
|
|
12132
|
+
},
|
|
12133
|
+
loadAccounts,
|
|
12134
|
+
"You're offline. Your mail accounts appear here once you've opened Mail online on this device."
|
|
12135
|
+
),
|
|
12136
|
+
listFolders: (account) => onlineFirst(
|
|
12137
|
+
async () => {
|
|
12138
|
+
const folders = await call(`${accountPath(account.id)}/folders`);
|
|
12139
|
+
if (storeEnabled()) remember(() => saveFolders(account.id, folders));
|
|
12140
|
+
return folders;
|
|
12141
|
+
},
|
|
12142
|
+
() => loadFolders(account.id),
|
|
12143
|
+
`You're offline and this mailbox isn't saved on this device. ${NOT_SAVED_HINT}`
|
|
12144
|
+
),
|
|
12145
|
+
listMessages: (account, folderId, query, cursor) => onlineFirst(
|
|
12146
|
+
async () => {
|
|
12147
|
+
const page = await call(
|
|
12148
|
+
`${accountPath(account.id)}/messages?${new URLSearchParams({ folder_id: folderId, q: query, ...cursor ? { cursor } : {} })}`
|
|
12149
|
+
);
|
|
12150
|
+
if (storeEnabled() && !query)
|
|
12151
|
+
remember(async () => {
|
|
12152
|
+
for (const message of page.messages) await saveHeader(account.id, folderId, message);
|
|
12153
|
+
});
|
|
12154
|
+
return page;
|
|
12155
|
+
},
|
|
12156
|
+
async () => {
|
|
12157
|
+
if (!await hasFolder(account.id)) return void 0;
|
|
12158
|
+
return { messages: await searchFolder(account.id, folderId, query), fromDevice: true };
|
|
12159
|
+
},
|
|
12160
|
+
`You're offline and this folder isn't saved on this device. ${NOT_SAVED_HINT}`
|
|
12161
|
+
),
|
|
12162
|
+
getMessage: (account, messageId) => onlineFirst(
|
|
12163
|
+
async () => {
|
|
12164
|
+
const message = await call(
|
|
12165
|
+
`${accountPath(account.id)}/messages/${encodeURIComponent(messageId)}`
|
|
12166
|
+
);
|
|
12167
|
+
if (storeEnabled()) remember(() => saveBody(account.id, message));
|
|
12168
|
+
return message;
|
|
12169
|
+
},
|
|
12170
|
+
() => loadBody(account.id, messageId),
|
|
12171
|
+
storeEnabled() ? "You're offline and this message hasn't been saved on this device yet." : `You're offline. ${NOT_SAVED_HINT}`
|
|
12172
|
+
)
|
|
12173
|
+
};
|
|
12174
|
+
}
|
|
12175
|
+
|
|
12176
|
+
// src/mail/offline/wipe.ts
|
|
12177
|
+
var OWNER_KEY2 = "owner";
|
|
12178
|
+
async function wipeOfflineMail() {
|
|
12179
|
+
forgetOfflineMailKeys();
|
|
12180
|
+
await deleteOfflineMailDatabase();
|
|
12181
|
+
notifyOfflineMailChange("wiped");
|
|
12182
|
+
}
|
|
12183
|
+
async function claimOfflineMail(userExtId) {
|
|
12184
|
+
const owner = await getMeta(OWNER_KEY2);
|
|
12185
|
+
if (owner && owner !== userExtId) await wipeOfflineMail();
|
|
12186
|
+
await setMeta(OWNER_KEY2, userExtId);
|
|
12187
|
+
}
|
|
12188
|
+
|
|
12189
|
+
// src/mail/offline/runtime.ts
|
|
12190
|
+
var SYNC_INTERVAL_MS = 5 * 60 * 1e3;
|
|
12191
|
+
var NEW_MAIL_DEBOUNCE_MS = 5e3;
|
|
12192
|
+
function useOfflineMailRuntime({ openOutbox } = {}) {
|
|
12193
|
+
const { user, status, serverSessionKey } = chunk2Y2WOIHH_cjs.useArbiSession();
|
|
12194
|
+
const userExtId = user?.external_id;
|
|
12195
|
+
const authenticated = status === "signed-in";
|
|
12196
|
+
const mailEnabled = chunkLUDIYWT7_cjs.useFeature("mail");
|
|
12197
|
+
const entitled = chunkLUDIYWT7_cjs.useIsEntitled("mail");
|
|
12198
|
+
const hasSession = !!serverSessionKey;
|
|
12199
|
+
const active = authenticated && mailEnabled && entitled && !!userExtId;
|
|
12200
|
+
const queryClient = reactQuery.useQueryClient();
|
|
12201
|
+
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
12202
|
+
const latest = React.useRef({ arbi, queryClient, openOutbox });
|
|
12203
|
+
latest.current = { arbi, queryClient, openOutbox };
|
|
12204
|
+
React.useEffect(() => {
|
|
12205
|
+
if (!active || !userExtId) return;
|
|
12206
|
+
claimOfflineMail(userExtId).catch(
|
|
12207
|
+
(error) => client.logger.warn("[offline-mail] could not bind the store", error)
|
|
12208
|
+
);
|
|
12209
|
+
}, [active, userExtId]);
|
|
12210
|
+
React.useEffect(() => {
|
|
12211
|
+
if (!active || !hasSession) return;
|
|
12212
|
+
const context = () => latest.current;
|
|
12213
|
+
const catchUp = async () => {
|
|
12214
|
+
await flushOutbox(context());
|
|
12215
|
+
await uploadOfflineDrafts(context());
|
|
12216
|
+
await syncOfflineMailbox(context());
|
|
12217
|
+
};
|
|
12218
|
+
const run2 = () => {
|
|
12219
|
+
catchUp().catch((error) => client.logger.warn("[offline-mail] catch-up failed", error));
|
|
12220
|
+
};
|
|
12221
|
+
run2();
|
|
12222
|
+
let wasOnline = reactQuery.onlineManager.isOnline();
|
|
12223
|
+
const offOnline = reactQuery.onlineManager.subscribe((online) => {
|
|
12224
|
+
if (online && !wasOnline) run2();
|
|
12225
|
+
wasOnline = online;
|
|
12226
|
+
});
|
|
12227
|
+
const interval = setInterval(() => {
|
|
12228
|
+
if (document.visibilityState === "visible") void syncOfflineMailbox(context());
|
|
12229
|
+
}, SYNC_INTERVAL_MS);
|
|
12230
|
+
const enabled = () => getOfflineMailSettings(sessionUser(context().arbi)).enabled;
|
|
12231
|
+
let newMailTimer = null;
|
|
12232
|
+
const offCache = queryClient.getQueryCache().subscribe((event) => {
|
|
12233
|
+
if (event.type !== "updated" || event.action.type !== "invalidate") return;
|
|
12234
|
+
if (!chunkU4PWVSCT_cjs.isUnderQueryKey(event.query.queryKey, chunkU4PWVSCT_cjs.mailQueryKeys.all()) || !enabled()) return;
|
|
12235
|
+
if (newMailTimer) clearTimeout(newMailTimer);
|
|
12236
|
+
newMailTimer = setTimeout(() => void syncOfflineMailbox(context()), NEW_MAIL_DEBOUNCE_MS);
|
|
12237
|
+
});
|
|
12238
|
+
const offSettings = onOfflineMailChange((change) => {
|
|
12239
|
+
if (change === "settings" && enabled()) void syncOfflineMailbox(context(), { full: true });
|
|
12240
|
+
if (change === "outbox") void flushOutbox(context());
|
|
12241
|
+
if (change === "drafts") void uploadOfflineDrafts(context());
|
|
12242
|
+
});
|
|
12243
|
+
return () => {
|
|
12244
|
+
offOnline();
|
|
12245
|
+
clearInterval(interval);
|
|
12246
|
+
if (newMailTimer) clearTimeout(newMailTimer);
|
|
12247
|
+
offCache();
|
|
12248
|
+
offSettings();
|
|
12249
|
+
};
|
|
12250
|
+
}, [active, hasSession, queryClient]);
|
|
12251
|
+
}
|
|
12252
|
+
function useStoreValue(load, initial, changes) {
|
|
12253
|
+
const [value, setValue] = React.useState(initial);
|
|
12254
|
+
const key = changes.join(",");
|
|
12255
|
+
React.useEffect(() => {
|
|
12256
|
+
let alive = true;
|
|
12257
|
+
const read = () => load().then((next) => alive && setValue(next)).catch((error) => client.logger.warn("[offline-mail] read failed", error));
|
|
12258
|
+
void read();
|
|
12259
|
+
const off = onOfflineMailChange((change) => {
|
|
12260
|
+
if (change === "wiped") setValue(initial);
|
|
12261
|
+
if (change === "wiped" || key.split(",").includes(change)) void read();
|
|
12262
|
+
});
|
|
12263
|
+
return () => {
|
|
12264
|
+
alive = false;
|
|
12265
|
+
off();
|
|
12266
|
+
};
|
|
12267
|
+
}, [key, load, initial]);
|
|
12268
|
+
return value;
|
|
12269
|
+
}
|
|
12270
|
+
function useOfflineMailSettings() {
|
|
12271
|
+
const userExtId = chunk2Y2WOIHH_cjs.useArbiSession().user?.external_id;
|
|
12272
|
+
const subscribe = React.useCallback(
|
|
12273
|
+
(onChange) => onOfflineMailChange((change) => {
|
|
12274
|
+
if (change === "settings") onChange();
|
|
12275
|
+
}),
|
|
12276
|
+
[]
|
|
12277
|
+
);
|
|
12278
|
+
const raw = React.useSyncExternalStore(
|
|
12279
|
+
subscribe,
|
|
12280
|
+
() => JSON.stringify([userExtId, getOfflineMailSettings(userExtId)])
|
|
12281
|
+
);
|
|
12282
|
+
const settings = React.useMemo(() => JSON.parse(raw)[1], [raw]);
|
|
12283
|
+
const update2 = React.useCallback(
|
|
12284
|
+
(patch) => void setOfflineMailSettings(userExtId, patch),
|
|
12285
|
+
[userExtId]
|
|
12286
|
+
);
|
|
12287
|
+
return [settings, update2];
|
|
12288
|
+
}
|
|
12289
|
+
var EMPTY_STATUS = { messages: 0, bytes: 0, skipped: [] };
|
|
12290
|
+
function useOfflineMailStatus() {
|
|
12291
|
+
const status = useStoreValue(getOfflineMailStatus, EMPTY_STATUS, ["status", "mailbox"]);
|
|
12292
|
+
const [syncing2, setSyncing] = React.useState(isMailSyncRunning);
|
|
12293
|
+
React.useEffect(
|
|
12294
|
+
() => onOfflineMailChange((change) => {
|
|
12295
|
+
if (change === "status") setSyncing(isMailSyncRunning());
|
|
12296
|
+
}),
|
|
12297
|
+
[]
|
|
12298
|
+
);
|
|
12299
|
+
return { ...status, syncing: syncing2 };
|
|
12300
|
+
}
|
|
12301
|
+
var NO_ITEMS = [];
|
|
12302
|
+
function useOutbox(enabled = true) {
|
|
12303
|
+
const load = React.useCallback(() => enabled ? listOutbox() : Promise.resolve(NO_ITEMS), [enabled]);
|
|
12304
|
+
return useStoreValue(load, NO_ITEMS, ["outbox"]);
|
|
12305
|
+
}
|
|
12306
|
+
var NO_DRAFTS = [];
|
|
12307
|
+
function useLocalDrafts(accountId) {
|
|
12308
|
+
const load = React.useCallback(
|
|
12309
|
+
() => accountId ? listLocalDrafts(accountId) : Promise.resolve(NO_DRAFTS),
|
|
12310
|
+
[accountId]
|
|
12311
|
+
);
|
|
12312
|
+
return useStoreValue(load, NO_DRAFTS, ["drafts"]);
|
|
12313
|
+
}
|
|
12314
|
+
function useFlushOutbox() {
|
|
12315
|
+
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
12316
|
+
const queryClient = reactQuery.useQueryClient();
|
|
12317
|
+
return React.useCallback(() => flushOutbox({ arbi, queryClient }), [arbi, queryClient]);
|
|
12318
|
+
}
|
|
12319
|
+
function useSyncOfflineMailbox() {
|
|
12320
|
+
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
12321
|
+
return React.useCallback((options) => syncOfflineMailbox({ arbi }, options), [arbi]);
|
|
12322
|
+
}
|
|
12323
|
+
|
|
12324
|
+
// src/mail/offline/confirm-sign-out.ts
|
|
12325
|
+
async function confirmLosingOfflineMail(confirm) {
|
|
12326
|
+
const [outbox, drafts] = await Promise.all([
|
|
12327
|
+
listOutbox().catch(() => []),
|
|
12328
|
+
listLocalDrafts().catch(() => [])
|
|
12329
|
+
]);
|
|
12330
|
+
const unsent = outbox.filter((item) => item.status !== "sent").length;
|
|
12331
|
+
if (unsent === 0 && drafts.length === 0) return true;
|
|
12332
|
+
const parts = [
|
|
12333
|
+
unsent > 0 ? `${unsent} unsent ${unsent === 1 ? "message" : "messages"} in your Outbox` : "",
|
|
12334
|
+
drafts.length > 0 ? `${drafts.length} ${drafts.length === 1 ? "draft" : "drafts"} saved only on this device` : ""
|
|
12335
|
+
].filter(Boolean);
|
|
12336
|
+
return confirm({
|
|
12337
|
+
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.`,
|
|
12338
|
+
title: "Sign out and delete unsent mail?"
|
|
12339
|
+
});
|
|
12340
|
+
}
|
|
11118
12341
|
function useConnectorMutation(mutationFn) {
|
|
11119
12342
|
const qc = reactQuery.useQueryClient();
|
|
11120
12343
|
return reactQuery.useMutation({
|
|
@@ -11582,9 +12805,9 @@ function useDeleteCalendar() {
|
|
|
11582
12805
|
function useCreateEvent() {
|
|
11583
12806
|
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
11584
12807
|
const qc = reactQuery.useQueryClient();
|
|
11585
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12808
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11586
12809
|
return reactQuery.useMutation({
|
|
11587
|
-
mutationFn: (body) =>
|
|
12810
|
+
mutationFn: (body) => write2({ kind: "create", body }, () => arbi.calendar.createEvent(body)),
|
|
11588
12811
|
networkMode,
|
|
11589
12812
|
onSuccess: () => {
|
|
11590
12813
|
void invalidateCalendarEvents(qc);
|
|
@@ -11594,9 +12817,9 @@ function useCreateEvent() {
|
|
|
11594
12817
|
function useUpdateEvent() {
|
|
11595
12818
|
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
11596
12819
|
const qc = reactQuery.useQueryClient();
|
|
11597
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12820
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11598
12821
|
return reactQuery.useMutation({
|
|
11599
|
-
mutationFn: ({ target, body }) =>
|
|
12822
|
+
mutationFn: ({ target, body }) => write2({ kind: "update", target, body }, () => arbi.calendar.updateEvent(target, body)),
|
|
11600
12823
|
networkMode,
|
|
11601
12824
|
onSuccess: () => {
|
|
11602
12825
|
void invalidateCalendarEvents(qc);
|
|
@@ -11606,9 +12829,9 @@ function useUpdateEvent() {
|
|
|
11606
12829
|
function useDeleteEvent() {
|
|
11607
12830
|
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
11608
12831
|
const qc = reactQuery.useQueryClient();
|
|
11609
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12832
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11610
12833
|
return reactQuery.useMutation({
|
|
11611
|
-
mutationFn: (target) =>
|
|
12834
|
+
mutationFn: (target) => write2({ kind: "delete", target }, () => arbi.calendar.deleteEvent(target)),
|
|
11612
12835
|
networkMode,
|
|
11613
12836
|
onSuccess: () => {
|
|
11614
12837
|
void invalidateCalendarEvents(qc);
|
|
@@ -11628,9 +12851,9 @@ function useMoveEvent() {
|
|
|
11628
12851
|
function useRespondToEvent() {
|
|
11629
12852
|
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
11630
12853
|
const qc = reactQuery.useQueryClient();
|
|
11631
|
-
const { write, networkMode } = useOfflineWrite();
|
|
12854
|
+
const { write: write2, networkMode } = useOfflineWrite();
|
|
11632
12855
|
return reactQuery.useMutation({
|
|
11633
|
-
mutationFn: (params) =>
|
|
12856
|
+
mutationFn: (params) => write2({ kind: "respond", params }, () => arbi.calendar.respond(params)),
|
|
11634
12857
|
networkMode,
|
|
11635
12858
|
onSuccess: () => {
|
|
11636
12859
|
void invalidateCalendarEvents(qc);
|
|
@@ -11870,7 +13093,7 @@ function useSyncAccountsOnOpen(enabled = true) {
|
|
|
11870
13093
|
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
11871
13094
|
const { data: accounts } = useCalendarAccounts(enabled);
|
|
11872
13095
|
const qc = reactQuery.useQueryClient();
|
|
11873
|
-
const [
|
|
13096
|
+
const [syncing2, setSyncing] = React.useState(false);
|
|
11874
13097
|
React.useEffect(() => {
|
|
11875
13098
|
if (!enabled || !accounts?.length) return;
|
|
11876
13099
|
const due = accounts.filter((a) => !syncedThisSession.has(a.id));
|
|
@@ -11882,7 +13105,7 @@ function useSyncAccountsOnOpen(enabled = true) {
|
|
|
11882
13105
|
void invalidateAccounts(qc);
|
|
11883
13106
|
});
|
|
11884
13107
|
}, [enabled, accounts, arbi, qc]);
|
|
11885
|
-
return { syncing };
|
|
13108
|
+
return { syncing: syncing2 };
|
|
11886
13109
|
}
|
|
11887
13110
|
function useBookingPages(enabled = true) {
|
|
11888
13111
|
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
@@ -11910,6 +13133,349 @@ function useDeleteBookingPage() {
|
|
|
11910
13133
|
onSuccess: () => qc.invalidateQueries({ queryKey: chunkU4PWVSCT_cjs.calendarQueryKeys.bookingPages() })
|
|
11911
13134
|
});
|
|
11912
13135
|
}
|
|
13136
|
+
|
|
13137
|
+
// src/offline-store/wipe.ts
|
|
13138
|
+
async function wipeStore(sealer3, store3) {
|
|
13139
|
+
sealer3.forget();
|
|
13140
|
+
await store3.deleteDatabase();
|
|
13141
|
+
}
|
|
13142
|
+
|
|
13143
|
+
// src/calendar/offline/outbox.ts
|
|
13144
|
+
var OFFLINE_CALENDAR_DB = "arbi-offline-calendar";
|
|
13145
|
+
var CACHE_LIMIT = 200;
|
|
13146
|
+
var OfflineCalendarLockedError = class extends Error {
|
|
13147
|
+
constructor() {
|
|
13148
|
+
super("Sign in to open the calendar saved on this device");
|
|
13149
|
+
this.name = "OfflineCalendarLockedError";
|
|
13150
|
+
}
|
|
13151
|
+
};
|
|
13152
|
+
var store2 = createRecordStore(OFFLINE_CALENDAR_DB);
|
|
13153
|
+
var sealer2 = client.createOfflineSealer("arbi-offline-calendar-v1", {
|
|
13154
|
+
locked: () => new OfflineCalendarLockedError()
|
|
13155
|
+
});
|
|
13156
|
+
function isCalendarOffline() {
|
|
13157
|
+
return isBrowserOffline();
|
|
13158
|
+
}
|
|
13159
|
+
function isCalendarNetworkError(error) {
|
|
13160
|
+
if (isNetworkFailure(error)) return true;
|
|
13161
|
+
const status = error?.status;
|
|
13162
|
+
return typeof status !== "number" && isBrowserOffline();
|
|
13163
|
+
}
|
|
13164
|
+
var cacheBucket = () => sealer2.blind("bucket", "cache");
|
|
13165
|
+
var cacheKey = (key) => sealer2.blind("cache", key);
|
|
13166
|
+
async function writeCalendarCache(key, data) {
|
|
13167
|
+
const sealed = await sealer2.sealJson({ key, data });
|
|
13168
|
+
const now = Date.now();
|
|
13169
|
+
await store2.putRecord({
|
|
13170
|
+
key: await cacheKey(key),
|
|
13171
|
+
kind: "cache",
|
|
13172
|
+
bucket: await cacheBucket(),
|
|
13173
|
+
sortAt: now,
|
|
13174
|
+
size: sealed.length,
|
|
13175
|
+
lastAccess: now,
|
|
13176
|
+
data: sealed
|
|
13177
|
+
});
|
|
13178
|
+
await evictCache();
|
|
13179
|
+
}
|
|
13180
|
+
async function readCalendarCache(key) {
|
|
13181
|
+
try {
|
|
13182
|
+
const blindKey = await cacheKey(key);
|
|
13183
|
+
const record = await store2.getRecord(blindKey);
|
|
13184
|
+
if (!record) return null;
|
|
13185
|
+
const opened = await sealer2.openJson(record.data);
|
|
13186
|
+
if (opened.key !== key) return null;
|
|
13187
|
+
await store2.putRecord({ ...record, lastAccess: Date.now() });
|
|
13188
|
+
return opened.data;
|
|
13189
|
+
} catch {
|
|
13190
|
+
return null;
|
|
13191
|
+
}
|
|
13192
|
+
}
|
|
13193
|
+
async function evictCache() {
|
|
13194
|
+
const rows = (await store2.allRecordAccess()).filter((r) => r.kind === "cache");
|
|
13195
|
+
if (rows.length <= CACHE_LIMIT) return;
|
|
13196
|
+
rows.sort((a, b) => a.lastAccess - b.lastAccess);
|
|
13197
|
+
await store2.deleteRecords(rows.slice(0, rows.length - CACHE_LIMIT).map((r) => r.key));
|
|
13198
|
+
}
|
|
13199
|
+
var outboxBucket = () => sealer2.blind("bucket", "outbox");
|
|
13200
|
+
var outboxKey = (requestId) => sealer2.blind("outbox", requestId);
|
|
13201
|
+
var seqCounter2 = 0;
|
|
13202
|
+
async function writeItem(item) {
|
|
13203
|
+
const sealed = await sealer2.sealJson(item);
|
|
13204
|
+
await store2.putRecord({
|
|
13205
|
+
key: await outboxKey(item.requestId),
|
|
13206
|
+
kind: "outbox",
|
|
13207
|
+
bucket: await outboxBucket(),
|
|
13208
|
+
sortAt: item.seq,
|
|
13209
|
+
size: sealed.length,
|
|
13210
|
+
lastAccess: Date.now(),
|
|
13211
|
+
data: sealed
|
|
13212
|
+
});
|
|
13213
|
+
}
|
|
13214
|
+
async function readItems() {
|
|
13215
|
+
const records = await store2.recordsInBucket(await outboxBucket());
|
|
13216
|
+
const items = [];
|
|
13217
|
+
for (const record of records) {
|
|
13218
|
+
try {
|
|
13219
|
+
items.push(await sealer2.openJson(record.data));
|
|
13220
|
+
} catch {
|
|
13221
|
+
}
|
|
13222
|
+
}
|
|
13223
|
+
return items.sort((a, b) => a.seq - b.seq);
|
|
13224
|
+
}
|
|
13225
|
+
function newRequestId() {
|
|
13226
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
13227
|
+
}
|
|
13228
|
+
async function enqueueCalendarWrite(op) {
|
|
13229
|
+
const now = Date.now();
|
|
13230
|
+
seqCounter2 = Math.max(seqCounter2 + 1, now * 1e3);
|
|
13231
|
+
const item = {
|
|
13232
|
+
requestId: newRequestId(),
|
|
13233
|
+
op,
|
|
13234
|
+
state: "queued",
|
|
13235
|
+
attempts: 0,
|
|
13236
|
+
createdAt: now,
|
|
13237
|
+
seq: seqCounter2
|
|
13238
|
+
};
|
|
13239
|
+
await writeItem(item);
|
|
13240
|
+
notify2("enqueued");
|
|
13241
|
+
return { requestId: item.requestId };
|
|
13242
|
+
}
|
|
13243
|
+
async function listCalendarOutbox() {
|
|
13244
|
+
try {
|
|
13245
|
+
return (await readItems()).map(({ requestId, op, state, inDoubt, error }) => ({
|
|
13246
|
+
requestId,
|
|
13247
|
+
op,
|
|
13248
|
+
state,
|
|
13249
|
+
inDoubt,
|
|
13250
|
+
error
|
|
13251
|
+
}));
|
|
13252
|
+
} catch {
|
|
13253
|
+
return [];
|
|
13254
|
+
}
|
|
13255
|
+
}
|
|
13256
|
+
function classify2(error) {
|
|
13257
|
+
if (isCalendarNetworkError(error)) return "offline";
|
|
13258
|
+
const status = error?.status;
|
|
13259
|
+
if (typeof status === "number" && status >= 500) return "retry";
|
|
13260
|
+
return "failed";
|
|
13261
|
+
}
|
|
13262
|
+
function run(exec, item) {
|
|
13263
|
+
const op = item.op;
|
|
13264
|
+
switch (op.kind) {
|
|
13265
|
+
case "create":
|
|
13266
|
+
return exec.create(op.body, item.requestId);
|
|
13267
|
+
case "update":
|
|
13268
|
+
return exec.update(op.target, op.body, item.requestId);
|
|
13269
|
+
case "delete":
|
|
13270
|
+
return exec.delete(op.target, item.requestId);
|
|
13271
|
+
case "respond":
|
|
13272
|
+
return exec.respond(op.params, item.requestId);
|
|
13273
|
+
}
|
|
13274
|
+
}
|
|
13275
|
+
var replaying = null;
|
|
13276
|
+
function replayCalendarOutbox(exec) {
|
|
13277
|
+
if (replaying) return replaying;
|
|
13278
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
13279
|
+
const run2 = locks?.request ? locks.request(
|
|
13280
|
+
"arbi-calendar-outbox-replay",
|
|
13281
|
+
() => replayOnce(exec)
|
|
13282
|
+
) : replayOnce(exec);
|
|
13283
|
+
const current = run2.finally(() => {
|
|
13284
|
+
replaying = null;
|
|
13285
|
+
});
|
|
13286
|
+
replaying = current;
|
|
13287
|
+
return current;
|
|
13288
|
+
}
|
|
13289
|
+
async function replayOnce(exec) {
|
|
13290
|
+
const result = { sent: 0, failed: 0, inDoubt: 0 };
|
|
13291
|
+
for (const queued of await readItems()) {
|
|
13292
|
+
if (queued.state !== "queued" && queued.state !== "sending") continue;
|
|
13293
|
+
if (isCalendarOffline()) break;
|
|
13294
|
+
let item = {
|
|
13295
|
+
...queued,
|
|
13296
|
+
inDoubt: queued.inDoubt || queued.state === "sending"
|
|
13297
|
+
};
|
|
13298
|
+
if (item.op.kind === "create" && item.inDoubt && exec.findCreated) {
|
|
13299
|
+
let found = false;
|
|
13300
|
+
try {
|
|
13301
|
+
found = await exec.findCreated(item.requestId, item.op.body);
|
|
13302
|
+
} catch (error) {
|
|
13303
|
+
if (classify2(error) === "offline") break;
|
|
13304
|
+
}
|
|
13305
|
+
if (found) {
|
|
13306
|
+
await store2.deleteRecords([await outboxKey(item.requestId)]);
|
|
13307
|
+
result.sent++;
|
|
13308
|
+
continue;
|
|
13309
|
+
}
|
|
13310
|
+
}
|
|
13311
|
+
item = { ...item, state: "sending", attempts: item.attempts + 1 };
|
|
13312
|
+
await writeItem(item);
|
|
13313
|
+
let outcome;
|
|
13314
|
+
let message;
|
|
13315
|
+
try {
|
|
13316
|
+
await run(exec, item);
|
|
13317
|
+
outcome = "sent";
|
|
13318
|
+
} catch (error) {
|
|
13319
|
+
outcome = classify2(error);
|
|
13320
|
+
message = error instanceof Error ? error.message : "The change could not be saved";
|
|
13321
|
+
}
|
|
13322
|
+
if (outcome === "sent") {
|
|
13323
|
+
await store2.deleteRecords([await outboxKey(item.requestId)]);
|
|
13324
|
+
result.sent++;
|
|
13325
|
+
continue;
|
|
13326
|
+
}
|
|
13327
|
+
if (outcome === "failed") {
|
|
13328
|
+
await writeItem({ ...item, state: "failed", error: message });
|
|
13329
|
+
result.failed++;
|
|
13330
|
+
continue;
|
|
13331
|
+
}
|
|
13332
|
+
const inDoubt = item.inDoubt || item.op.kind === "create";
|
|
13333
|
+
await writeItem({ ...item, state: "queued", inDoubt, error: message });
|
|
13334
|
+
if (inDoubt) result.inDoubt++;
|
|
13335
|
+
if (outcome === "offline") break;
|
|
13336
|
+
}
|
|
13337
|
+
notify2();
|
|
13338
|
+
return result;
|
|
13339
|
+
}
|
|
13340
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
13341
|
+
function notify2(change = "changed") {
|
|
13342
|
+
for (const l of listeners2) l(change);
|
|
13343
|
+
}
|
|
13344
|
+
function onCalendarOutboxChange(listener) {
|
|
13345
|
+
listeners2.add(listener);
|
|
13346
|
+
return () => listeners2.delete(listener);
|
|
13347
|
+
}
|
|
13348
|
+
async function wipeCalendarOffline() {
|
|
13349
|
+
await wipeStore(sealer2, store2);
|
|
13350
|
+
notify2();
|
|
13351
|
+
}
|
|
13352
|
+
async function claimCalendarOffline(userExtId) {
|
|
13353
|
+
const owner = await store2.getMeta("owner");
|
|
13354
|
+
if (owner && owner !== userExtId) await wipeCalendarOffline();
|
|
13355
|
+
await store2.setMeta("owner", userExtId);
|
|
13356
|
+
}
|
|
13357
|
+
|
|
13358
|
+
// src/calendar/offline/support.ts
|
|
13359
|
+
var calendarOfflineSupport = {
|
|
13360
|
+
async read(key, fetch2) {
|
|
13361
|
+
const cacheKey2 = JSON.stringify(key);
|
|
13362
|
+
if (!isCalendarOffline()) {
|
|
13363
|
+
try {
|
|
13364
|
+
const data = await fetch2();
|
|
13365
|
+
void writeCalendarCache(cacheKey2, data).catch(() => {
|
|
13366
|
+
});
|
|
13367
|
+
return data;
|
|
13368
|
+
} catch (err) {
|
|
13369
|
+
if (!isCalendarNetworkError(err)) throw err;
|
|
13370
|
+
}
|
|
13371
|
+
}
|
|
13372
|
+
const cached = await readCalendarCache(cacheKey2).catch(
|
|
13373
|
+
() => null
|
|
13374
|
+
);
|
|
13375
|
+
if (cached != null) return cached;
|
|
13376
|
+
throw new browser.ArbiApiError("You are offline and this view is not saved on this device");
|
|
13377
|
+
},
|
|
13378
|
+
async write(op, send) {
|
|
13379
|
+
if (!isCalendarOffline()) return send();
|
|
13380
|
+
const { requestId } = await enqueueCalendarWrite(op);
|
|
13381
|
+
return { queued: true, requestId };
|
|
13382
|
+
},
|
|
13383
|
+
pending: listCalendarOutbox,
|
|
13384
|
+
subscribe: (listener) => onCalendarOutboxChange(() => listener())
|
|
13385
|
+
};
|
|
13386
|
+
var day = (date, offset) => {
|
|
13387
|
+
const t = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
13388
|
+
t.setUTCDate(t.getUTCDate() + offset);
|
|
13389
|
+
return t.toISOString();
|
|
13390
|
+
};
|
|
13391
|
+
var calendarOutboxExecutor = (arbi) => ({
|
|
13392
|
+
create: (body) => arbi.calendar.createEvent(body),
|
|
13393
|
+
update: (target, body) => arbi.calendar.updateEvent(target, body),
|
|
13394
|
+
delete: (target) => arbi.calendar.deleteEvent(target),
|
|
13395
|
+
respond: (params) => arbi.calendar.respond(params),
|
|
13396
|
+
/** No server dedupe: an in-doubt create counts as made if an event with its title and start exists. */
|
|
13397
|
+
async findCreated(_requestId, body) {
|
|
13398
|
+
const { title, start } = body;
|
|
13399
|
+
const date = (start?.date_time ?? start?.date ?? "").slice(0, 10);
|
|
13400
|
+
if (!title || !/^\d{4}-\d{2}-\d{2}$/.test(date)) return false;
|
|
13401
|
+
const events = await arbi.calendar.listEvents({
|
|
13402
|
+
start: day(date, -1),
|
|
13403
|
+
end: day(date, 2)
|
|
13404
|
+
});
|
|
13405
|
+
return events.some(
|
|
13406
|
+
(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)
|
|
13407
|
+
);
|
|
13408
|
+
}
|
|
13409
|
+
});
|
|
13410
|
+
var SYNC_CHANNEL = "arbi-calendar-outbox";
|
|
13411
|
+
function refreshCalendarQueries(queryClient) {
|
|
13412
|
+
void queryClient.invalidateQueries({ queryKey: chunkU4PWVSCT_cjs.calendarQueryKeys.all() });
|
|
13413
|
+
}
|
|
13414
|
+
async function replay(arbi, queryClient) {
|
|
13415
|
+
const result = await replayCalendarOutbox(calendarOutboxExecutor(arbi));
|
|
13416
|
+
if (result.sent || result.failed) {
|
|
13417
|
+
refreshCalendarQueries(queryClient);
|
|
13418
|
+
try {
|
|
13419
|
+
const channel2 = new BroadcastChannel(SYNC_CHANNEL);
|
|
13420
|
+
channel2.postMessage("synced");
|
|
13421
|
+
channel2.close();
|
|
13422
|
+
} catch {
|
|
13423
|
+
}
|
|
13424
|
+
}
|
|
13425
|
+
if (result.failed)
|
|
13426
|
+
chunkDZUBZ45Y_cjs.notify.error(
|
|
13427
|
+
result.failed === 1 ? "A calendar change made offline could not be saved" : `${result.failed} calendar changes made offline could not be saved`
|
|
13428
|
+
);
|
|
13429
|
+
}
|
|
13430
|
+
function useCalendarOfflineRuntime() {
|
|
13431
|
+
const { status, serverSessionKey, user } = chunk2Y2WOIHH_cjs.useArbiSession();
|
|
13432
|
+
const userExtId = user?.external_id;
|
|
13433
|
+
const authenticated = status === "signed-in";
|
|
13434
|
+
const hasSession = !!serverSessionKey;
|
|
13435
|
+
const arbi = chunkVGMMK5OM_cjs.useArbi();
|
|
13436
|
+
const ready = authenticated && hasSession && arbi.isLoggedIn;
|
|
13437
|
+
const queryClient = reactQuery.useQueryClient();
|
|
13438
|
+
React.useEffect(() => {
|
|
13439
|
+
if (!authenticated || !userExtId) return;
|
|
13440
|
+
claimCalendarOffline(userExtId).catch(
|
|
13441
|
+
(error) => client.logger.warn("[offline-calendar] could not bind the store", error)
|
|
13442
|
+
);
|
|
13443
|
+
}, [authenticated, userExtId]);
|
|
13444
|
+
React.useEffect(() => {
|
|
13445
|
+
if (!ready) return;
|
|
13446
|
+
const run2 = () => {
|
|
13447
|
+
if (!reactQuery.onlineManager.isOnline()) return;
|
|
13448
|
+
replay(arbi, queryClient).catch(
|
|
13449
|
+
(error) => client.logger.warn("[offline-calendar] replay failed", error)
|
|
13450
|
+
);
|
|
13451
|
+
};
|
|
13452
|
+
run2();
|
|
13453
|
+
let wasOnline = reactQuery.onlineManager.isOnline();
|
|
13454
|
+
const offOnline = reactQuery.onlineManager.subscribe((online) => {
|
|
13455
|
+
if (online && !wasOnline) run2();
|
|
13456
|
+
wasOnline = online;
|
|
13457
|
+
});
|
|
13458
|
+
let queued = null;
|
|
13459
|
+
const offOutbox = onCalendarOutboxChange((change) => {
|
|
13460
|
+
if (change !== "enqueued") return;
|
|
13461
|
+
if (queued) clearTimeout(queued);
|
|
13462
|
+
queued = setTimeout(run2, 2e3);
|
|
13463
|
+
});
|
|
13464
|
+
let channel2 = null;
|
|
13465
|
+
try {
|
|
13466
|
+
channel2 = new BroadcastChannel(SYNC_CHANNEL);
|
|
13467
|
+
channel2.onmessage = () => refreshCalendarQueries(queryClient);
|
|
13468
|
+
} catch {
|
|
13469
|
+
channel2 = null;
|
|
13470
|
+
}
|
|
13471
|
+
return () => {
|
|
13472
|
+
channel2?.close();
|
|
13473
|
+
offOnline();
|
|
13474
|
+
offOutbox();
|
|
13475
|
+
if (queued) clearTimeout(queued);
|
|
13476
|
+
};
|
|
13477
|
+
}, [ready, arbi, queryClient]);
|
|
13478
|
+
}
|
|
11913
13479
|
function useWorkspaceDocs(workspaceId) {
|
|
11914
13480
|
const { data: docs = [], isLoading, isError } = chunkQ6U7ALNV_cjs.useDocuments(workspaceId);
|
|
11915
13481
|
const docIds = React.useMemo(
|
|
@@ -11938,7 +13504,7 @@ function useAiTask() {
|
|
|
11938
13504
|
artifactsRef.current = /* @__PURE__ */ new Map();
|
|
11939
13505
|
bufferRef.current = "";
|
|
11940
13506
|
}, []);
|
|
11941
|
-
const
|
|
13507
|
+
const run2 = React.useCallback(
|
|
11942
13508
|
async (question, docIds, opts) => {
|
|
11943
13509
|
setStatus("running");
|
|
11944
13510
|
setText("");
|
|
@@ -12006,7 +13572,7 @@ function useAiTask() {
|
|
|
12006
13572
|
error,
|
|
12007
13573
|
responseId,
|
|
12008
13574
|
artifacts,
|
|
12009
|
-
run,
|
|
13575
|
+
run: run2,
|
|
12010
13576
|
reset,
|
|
12011
13577
|
isRunning: status === "running",
|
|
12012
13578
|
isDone: status === "done"
|
|
@@ -12444,13 +14010,13 @@ function useSemanticSearch(docIds = [], options = {}) {
|
|
|
12444
14010
|
chunkAXKHDLBB_cjs.clearSearchSession(void 0, session);
|
|
12445
14011
|
return;
|
|
12446
14012
|
}
|
|
12447
|
-
const
|
|
14013
|
+
const run2 = session.getState().run + 1;
|
|
12448
14014
|
const searched = workspaceId ?? session.getState().workspaceId ?? void 0;
|
|
12449
14015
|
session.setState((s) => ({
|
|
12450
14016
|
query: trimmed,
|
|
12451
14017
|
status: "running",
|
|
12452
14018
|
error: null,
|
|
12453
|
-
run,
|
|
14019
|
+
run: run2,
|
|
12454
14020
|
workspaceId: workspaceId ?? s.workspaceId
|
|
12455
14021
|
}));
|
|
12456
14022
|
try {
|
|
@@ -12460,10 +14026,10 @@ function useSemanticSearch(docIds = [], options = {}) {
|
|
|
12460
14026
|
...model ? { model } : {},
|
|
12461
14027
|
...searched ? { workspaceId: searched } : {}
|
|
12462
14028
|
});
|
|
12463
|
-
if (session.getState().run !==
|
|
14029
|
+
if (session.getState().run !== run2) return;
|
|
12464
14030
|
session.setState({ results: browser.searchChunks(result), searched: true, status: "done" });
|
|
12465
14031
|
} catch (error) {
|
|
12466
|
-
if (session.getState().run !==
|
|
14032
|
+
if (session.getState().run !== run2) return;
|
|
12467
14033
|
session.setState({
|
|
12468
14034
|
status: "error",
|
|
12469
14035
|
error: error instanceof Error ? error.message : "Search failed"
|
|
@@ -12666,7 +14232,7 @@ var CitationRendererComponent = ({
|
|
|
12666
14232
|
useCitationState,
|
|
12667
14233
|
onCloseCitation,
|
|
12668
14234
|
onDismissCitation,
|
|
12669
|
-
notify:
|
|
14235
|
+
notify: notify3,
|
|
12670
14236
|
docTitles
|
|
12671
14237
|
}) => {
|
|
12672
14238
|
const chunkMap = React.useMemo(() => {
|
|
@@ -12710,7 +14276,7 @@ var CitationRendererComponent = ({
|
|
|
12710
14276
|
useCitationState,
|
|
12711
14277
|
onCloseCitation,
|
|
12712
14278
|
onDismissCitation,
|
|
12713
|
-
notify:
|
|
14279
|
+
notify: notify3
|
|
12714
14280
|
}),
|
|
12715
14281
|
[
|
|
12716
14282
|
citations,
|
|
@@ -12723,7 +14289,7 @@ var CitationRendererComponent = ({
|
|
|
12723
14289
|
useCitationState,
|
|
12724
14290
|
onCloseCitation,
|
|
12725
14291
|
onDismissCitation,
|
|
12726
|
-
|
|
14292
|
+
notify3
|
|
12727
14293
|
]
|
|
12728
14294
|
);
|
|
12729
14295
|
return /* @__PURE__ */ jsxRuntime.jsx(CitationAnchorContext.Provider, { value: anchorContext, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkS6A6FSG7_cjs.cn("citation-container", className), "data-testid": "citation-renderer", children: renderMarkdown({
|
|
@@ -12782,7 +14348,7 @@ var CitationPopover = ({
|
|
|
12782
14348
|
useCitationState,
|
|
12783
14349
|
onCloseCitation,
|
|
12784
14350
|
onDismissCitation,
|
|
12785
|
-
notify:
|
|
14351
|
+
notify: notify3,
|
|
12786
14352
|
children
|
|
12787
14353
|
}) => {
|
|
12788
14354
|
const { activeCitationKey, viewingPassageId, isOnConversationPage } = useCitationState();
|
|
@@ -12856,11 +14422,11 @@ var CitationPopover = ({
|
|
|
12856
14422
|
const source = [firstTitle, firstPage != null ? `p.${firstPage}` : null].filter(Boolean).join(", ");
|
|
12857
14423
|
try {
|
|
12858
14424
|
await navigator.clipboard.writeText(source ? `\u201C${firstText}\u201D (${source})` : firstText);
|
|
12859
|
-
|
|
14425
|
+
notify3?.("Citation copied", "success");
|
|
12860
14426
|
} catch {
|
|
12861
|
-
|
|
14427
|
+
notify3?.("Could not copy the citation", "error");
|
|
12862
14428
|
}
|
|
12863
|
-
}, [firstText, firstTitle, firstPage,
|
|
14429
|
+
}, [firstText, firstTitle, firstPage, notify3]);
|
|
12864
14430
|
const items = isStreaming || chunks.length === 0 ? [] : [
|
|
12865
14431
|
{
|
|
12866
14432
|
id: "open",
|
|
@@ -14573,12 +16139,12 @@ function annotateThread(messages, accessors, runWindowMs = RUN_WINDOW_MS) {
|
|
|
14573
16139
|
return messages.map((message, i) => {
|
|
14574
16140
|
const next = messages[i + 1];
|
|
14575
16141
|
const prev = messages[i - 1];
|
|
14576
|
-
const
|
|
14577
|
-
const continuesIntoNext = !!next && senderId(next) === senderId(message) && dayKey(createdAt(next)) ===
|
|
16142
|
+
const day2 = dayKey(createdAt(message));
|
|
16143
|
+
const continuesIntoNext = !!next && senderId(next) === senderId(message) && dayKey(createdAt(next)) === day2 && Date.parse(createdAt(next)) - Date.parse(createdAt(message)) < runWindowMs;
|
|
14578
16144
|
return {
|
|
14579
16145
|
message,
|
|
14580
16146
|
isLastOfRun: !continuesIntoNext,
|
|
14581
|
-
isFirstOfDay: !prev || dayKey(createdAt(prev)) !==
|
|
16147
|
+
isFirstOfDay: !prev || dayKey(createdAt(prev)) !== day2
|
|
14582
16148
|
};
|
|
14583
16149
|
});
|
|
14584
16150
|
}
|
|
@@ -17639,6 +19205,7 @@ exports.MIN_NARRATION_DWELL_MS = MIN_NARRATION_DWELL_MS;
|
|
|
17639
19205
|
exports.MODEL_GROUP_LABELS = MODEL_GROUP_LABELS;
|
|
17640
19206
|
exports.MailOfflineUnavailableError = MailOfflineUnavailableError;
|
|
17641
19207
|
exports.MessageArtifactCard = MessageArtifactCard;
|
|
19208
|
+
exports.OUTBOX_FOLDER = OUTBOX_FOLDER;
|
|
17642
19209
|
exports.PENDING_SYNC_PREDICATE = PENDING_SYNC_PREDICATE;
|
|
17643
19210
|
exports.POLL_EVERY_MS = POLL_EVERY_MS;
|
|
17644
19211
|
exports.PhotoCapture = PhotoCapture;
|
|
@@ -17693,6 +19260,7 @@ exports.cachedAssistantMode = cachedAssistantMode;
|
|
|
17693
19260
|
exports.cachedMentionCandidates = cachedMentionCandidates;
|
|
17694
19261
|
exports.cachedQuotaUsedUp = cachedQuotaUsedUp;
|
|
17695
19262
|
exports.calendarNotificationMeta = calendarNotificationMeta;
|
|
19263
|
+
exports.calendarOfflineSupport = calendarOfflineSupport;
|
|
17696
19264
|
exports.callNotificationMeta = callNotificationMeta;
|
|
17697
19265
|
exports.captureFrameAsBlob = captureFrameAsBlob;
|
|
17698
19266
|
exports.claimOfflineQueue = claimOfflineQueue;
|
|
@@ -17701,6 +19269,7 @@ exports.clearUserInputRequest = clearUserInputRequest;
|
|
|
17701
19269
|
exports.commitFinalMessage = commitFinalMessage;
|
|
17702
19270
|
exports.completeQueuedUserMessageByContent = completeQueuedUserMessageByContent;
|
|
17703
19271
|
exports.computeOnline = computeOnline;
|
|
19272
|
+
exports.confirmLosingOfflineMail = confirmLosingOfflineMail;
|
|
17704
19273
|
exports.connectOAuthCalendarAccount = connectOAuthCalendarAccount;
|
|
17705
19274
|
exports.connectorErrorStatus = connectorErrorStatus;
|
|
17706
19275
|
exports.connectorReturnTo = connectorReturnTo;
|
|
@@ -17721,6 +19290,8 @@ exports.cropImage = cropImage;
|
|
|
17721
19290
|
exports.dayKey = dayKey;
|
|
17722
19291
|
exports.defaultMailAlias = defaultMailAlias;
|
|
17723
19292
|
exports.defineRowsSource = defineRowsSource;
|
|
19293
|
+
exports.deleteLocalDraft = deleteLocalDraft;
|
|
19294
|
+
exports.deleteOutboxItem = deleteOutboxItem;
|
|
17724
19295
|
exports.deploymentUserMeta = deploymentUserMeta;
|
|
17725
19296
|
exports.describeActivity = describeActivity;
|
|
17726
19297
|
exports.describeCalendarNotification = describeCalendarNotification;
|
|
@@ -17730,8 +19301,10 @@ exports.disconnectAttachedRun = disconnectAttachedRun;
|
|
|
17730
19301
|
exports.documentActivityMeta = documentActivityMeta;
|
|
17731
19302
|
exports.documentActivityPhrase = documentActivityPhrase;
|
|
17732
19303
|
exports.documentDisplayName = documentDisplayName;
|
|
19304
|
+
exports.documentMailAttachment = documentMailAttachment;
|
|
17733
19305
|
exports.docxMentionMeta = docxMentionMeta;
|
|
17734
19306
|
exports.downloadRecoveryFile = downloadRecoveryFile;
|
|
19307
|
+
exports.enqueueOutbox = enqueueOutbox;
|
|
17735
19308
|
exports.ensureAssistantMessageLoading = ensureAssistantMessageLoading;
|
|
17736
19309
|
exports.extractZipFile = extractZipFile;
|
|
17737
19310
|
exports.fallbackFileName = fallbackFileName;
|
|
@@ -17769,6 +19342,7 @@ exports.isDeploymentAdmin = isDeploymentAdmin;
|
|
|
17769
19342
|
exports.isDeploymentUserNotification = isDeploymentUserNotification;
|
|
17770
19343
|
exports.isFullConfig = isFullConfig;
|
|
17771
19344
|
exports.isMessageRunning = isMessageRunning;
|
|
19345
|
+
exports.isNetworkFailure = isNetworkFailure;
|
|
17772
19346
|
exports.isNotificationRow = isNotificationRow;
|
|
17773
19347
|
exports.isOrphanedUserMessage = isOrphanedUserMessage;
|
|
17774
19348
|
exports.isQueuedCalendarWrite = isQueuedCalendarWrite;
|
|
@@ -17782,6 +19356,7 @@ exports.isWorkspaceArchivedError = isWorkspaceArchivedError;
|
|
|
17782
19356
|
exports.isWorkspaceNotificationType = isWorkspaceNotificationType;
|
|
17783
19357
|
exports.isZipFile = isZipFile;
|
|
17784
19358
|
exports.loadOfflineDocument = loadOfflineDocument;
|
|
19359
|
+
exports.loadOfflineMailAttachment = loadAttachment;
|
|
17785
19360
|
exports.mailDraftIdFromContent = mailDraftIdFromContent;
|
|
17786
19361
|
exports.mailJson = mailJson;
|
|
17787
19362
|
exports.markMessageAsFailed = markMessageAsFailed;
|
|
@@ -17790,6 +19365,7 @@ exports.migrateLegacyThreadDocuments = migrateLegacyThreadDocuments;
|
|
|
17790
19365
|
exports.mountDiditVerification = mountDiditVerification;
|
|
17791
19366
|
exports.nextAliasSuggestion = nextAliasSuggestion;
|
|
17792
19367
|
exports.notificationUserName = notificationUserName;
|
|
19368
|
+
exports.offlineMailReads = offlineMailReads;
|
|
17793
19369
|
exports.openAccessRequest = openAccessRequest;
|
|
17794
19370
|
exports.parseActivityContent = parseActivityContent;
|
|
17795
19371
|
exports.parseDashboardSpec = parseDashboardSpec;
|
|
@@ -17812,8 +19388,10 @@ exports.resetMessageContent = resetMessageContent;
|
|
|
17812
19388
|
exports.resolveActiveWorkspace = resolveActiveWorkspace;
|
|
17813
19389
|
exports.resolveConfigModel = resolveConfigModel;
|
|
17814
19390
|
exports.resolveReplyTarget = resolveReplyTarget;
|
|
19391
|
+
exports.retryOutboxItem = retryOutboxItem;
|
|
17815
19392
|
exports.rollbackOptimisticDocumentEdit = rollbackOptimisticDocumentEdit;
|
|
17816
19393
|
exports.safePortalUrl = safePortalUrl;
|
|
19394
|
+
exports.saveLocalDraft = saveLocalDraft;
|
|
17817
19395
|
exports.serializeDashboardSpec = serializeDashboardSpec;
|
|
17818
19396
|
exports.serverNotificationBellEntry = serverNotificationBellEntry;
|
|
17819
19397
|
exports.setMessageContent = setMessageContent;
|
|
@@ -17876,6 +19454,7 @@ exports.useCalendarEvent = useCalendarEvent;
|
|
|
17876
19454
|
exports.useCalendarEventByUid = useCalendarEventByUid;
|
|
17877
19455
|
exports.useCalendarEvents = useCalendarEvents;
|
|
17878
19456
|
exports.useCalendarInvitations = useCalendarInvitations;
|
|
19457
|
+
exports.useCalendarOfflineRuntime = useCalendarOfflineRuntime;
|
|
17879
19458
|
exports.useCalendarOutbox = useCalendarOutbox;
|
|
17880
19459
|
exports.useCalendarOverlays = useCalendarOverlays;
|
|
17881
19460
|
exports.useCalendarSettings = useCalendarSettings;
|
|
@@ -17996,6 +19575,7 @@ exports.useFetchCalendarEvent = useFetchCalendarEvent;
|
|
|
17996
19575
|
exports.useFilteredProjects = useFilteredProjects;
|
|
17997
19576
|
exports.useFinaliseTranslation = useFinaliseTranslation;
|
|
17998
19577
|
exports.useFindTime = useFindTime;
|
|
19578
|
+
exports.useFlushOutbox = useFlushOutbox;
|
|
17999
19579
|
exports.useFollowThread = useFollowThread;
|
|
18000
19580
|
exports.useFreeBusy = useFreeBusy;
|
|
18001
19581
|
exports.useGenerateAgentAvatar = useGenerateAgentAvatar;
|
|
@@ -18019,6 +19599,7 @@ exports.useIsWorkspaceOwner = useIsWorkspaceOwner;
|
|
|
18019
19599
|
exports.useKeepDocumentsOffline = useKeepDocumentsOffline;
|
|
18020
19600
|
exports.useLinkContact = useLinkContact;
|
|
18021
19601
|
exports.useLinkMentions = useLinkMentions;
|
|
19602
|
+
exports.useLocalDrafts = useLocalDrafts;
|
|
18022
19603
|
exports.useMailAccounts = useMailAccounts;
|
|
18023
19604
|
exports.useMailAttachmentText = useMailAttachmentText;
|
|
18024
19605
|
exports.useMailCache = useMailCache;
|
|
@@ -18049,8 +19630,12 @@ exports.useNotificationPreferences = useNotificationPreferences;
|
|
|
18049
19630
|
exports.useNotifications = useNotifications;
|
|
18050
19631
|
exports.useNotifyConversation = useNotifyConversation;
|
|
18051
19632
|
exports.useOfflineDocumentIds = useOfflineDocumentIds;
|
|
19633
|
+
exports.useOfflineMailRuntime = useOfflineMailRuntime;
|
|
19634
|
+
exports.useOfflineMailSettings = useOfflineMailSettings;
|
|
19635
|
+
exports.useOfflineMailStatus = useOfflineMailStatus;
|
|
18052
19636
|
exports.useOpenSelectedWorkspace = useOpenSelectedWorkspace;
|
|
18053
19637
|
exports.useOpenViewerDocumentId = useOpenViewerDocumentId;
|
|
19638
|
+
exports.useOutbox = useOutbox;
|
|
18054
19639
|
exports.usePanelDataset = usePanelDataset;
|
|
18055
19640
|
exports.usePassageQuotes = usePassageQuotes;
|
|
18056
19641
|
exports.usePasswordChange = usePasswordChange;
|
|
@@ -18135,6 +19720,7 @@ exports.useSupportTickets = useSupportTickets;
|
|
|
18135
19720
|
exports.useSwitchWorkspace = useSwitchWorkspace;
|
|
18136
19721
|
exports.useSyncAccountsOnOpen = useSyncAccountsOnOpen;
|
|
18137
19722
|
exports.useSyncCalendarAccount = useSyncCalendarAccount;
|
|
19723
|
+
exports.useSyncOfflineMailbox = useSyncOfflineMailbox;
|
|
18138
19724
|
exports.useTerminateSession = useTerminateSession;
|
|
18139
19725
|
exports.useThinkingContent = useThinkingContent;
|
|
18140
19726
|
exports.useThreadDocument = useThreadDocument;
|
|
@@ -18185,7 +19771,9 @@ exports.userDisplayName = userDisplayName;
|
|
|
18185
19771
|
exports.userInitials = userInitials;
|
|
18186
19772
|
exports.viewsOfTable = viewsOfTable;
|
|
18187
19773
|
exports.waitForResponseId = waitForResponseId;
|
|
19774
|
+
exports.wipeCalendarOffline = wipeCalendarOffline;
|
|
18188
19775
|
exports.wipeOfflineDocuments = wipeOfflineDocuments;
|
|
19776
|
+
exports.wipeOfflineMail = wipeOfflineMail;
|
|
18189
19777
|
exports.withRelativePath = withRelativePath;
|
|
18190
19778
|
exports.workflowStepOf = workflowStepOf;
|
|
18191
19779
|
exports.workspaceHouseTemplateId = workspaceHouseTemplateId;
|