@ai-matrx/vault 0.1.56 → 0.1.57
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/CHANGELOG.md +6 -1
- package/dist/index.cjs +241 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +291 -219
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -432,7 +432,7 @@ function PasswordHealthView(props) {
|
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
// src/VaultApp.tsx
|
|
435
|
-
import { useCallback as useCallback4, useEffect as useEffect23, useMemo as useMemo6, useRef as
|
|
435
|
+
import { useCallback as useCallback4, useEffect as useEffect23, useMemo as useMemo6, useRef as useRef21, useState as useState41, useSyncExternalStore as useSyncExternalStore5 } from "react";
|
|
436
436
|
import { DropdownMenu as DropdownMenu5, DropdownMenuContent as DropdownMenuContent5, DropdownMenuItem as DropdownMenuItem5, DropdownMenuTrigger as DropdownMenuTrigger5, LookScope } from "@ai-matrx/design-system";
|
|
437
437
|
import { Button as Button34, SearchField as SearchField4, Select as Select12 } from "@ai-matrx/design-system/controls";
|
|
438
438
|
import { confirm as confirm14 } from "@ai-matrx/kit/confirm-opener";
|
|
@@ -1440,7 +1440,7 @@ function ItemIcon({ definitionKey, family, site, size = "md", inverted = false }
|
|
|
1440
1440
|
}
|
|
1441
1441
|
|
|
1442
1442
|
// src/authenticator/AddOtpPanel.tsx
|
|
1443
|
-
import { useState as useState11 } from "react";
|
|
1443
|
+
import { useRef as useRef9, useState as useState11 } from "react";
|
|
1444
1444
|
import { Button as Button8, Field as Field3, Select as Select2 } from "@ai-matrx/design-system/controls";
|
|
1445
1445
|
|
|
1446
1446
|
// src/authenticator/CodeParts.tsx
|
|
@@ -1968,7 +1968,7 @@ function OtpSetupRow({ value, onChange, label = "Authenticator", placeholder = S
|
|
|
1968
1968
|
}
|
|
1969
1969
|
|
|
1970
1970
|
// src/authenticator/TotpRows.tsx
|
|
1971
|
-
import { useState as useState10 } from "react";
|
|
1971
|
+
import { useRef as useRef8, useState as useState10 } from "react";
|
|
1972
1972
|
import { Button as Button7, Select } from "@ai-matrx/design-system/controls";
|
|
1973
1973
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@ai-matrx/design-system";
|
|
1974
1974
|
import { confirm as confirm2 } from "@ai-matrx/kit/confirm-opener";
|
|
@@ -2085,10 +2085,22 @@ function TotpRow({ port, item, onChanged }) {
|
|
|
2085
2085
|
function TotpEditRow({ value, onChange, hasExisting, item, port, onEnrolled, onRecognized }) {
|
|
2086
2086
|
const confirmed = useConfirmed();
|
|
2087
2087
|
const [status, setStatus] = useState10(null);
|
|
2088
|
+
const [retryImage, setRetryImage] = useState10(null);
|
|
2089
|
+
const latestItem = useRef8(item);
|
|
2090
|
+
latestItem.current = item;
|
|
2088
2091
|
const uploadImage = item && port ? async (file) => {
|
|
2092
|
+
setRetryImage(file);
|
|
2089
2093
|
setStatus("Adding from image\u2026");
|
|
2090
2094
|
try {
|
|
2091
|
-
const entry = await confirmed(() =>
|
|
2095
|
+
const entry = await confirmed(async () => {
|
|
2096
|
+
const current = await port.get(item);
|
|
2097
|
+
const visible = latestItem.current;
|
|
2098
|
+
if (!visible || visible.id !== item.id || visible.authenticatorState !== "known" || current.authenticatorState !== "known" || !item.authenticator && (visible.authenticator || current.authenticator)) {
|
|
2099
|
+
throw new Error("Authenticator status unavailable. Retry after refreshing.");
|
|
2100
|
+
}
|
|
2101
|
+
return port.authenticator.enrolQrImage(current, file, file.name);
|
|
2102
|
+
});
|
|
2103
|
+
setRetryImage(null);
|
|
2092
2104
|
setStatus(entry.issuer ? `Added \xB7 ${entry.issuer}` : "Added");
|
|
2093
2105
|
onChange("");
|
|
2094
2106
|
onEnrolled?.({ ...item, authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } });
|
|
@@ -2105,7 +2117,11 @@ function TotpEditRow({ value, onChange, hasExisting, item, port, onEnrolled, onR
|
|
|
2105
2117
|
onChange(v);
|
|
2106
2118
|
},
|
|
2107
2119
|
placeholder: hasExisting ? "Paste a new key to replace" : SETUP_KEY_PLACEHOLDER,
|
|
2108
|
-
status,
|
|
2120
|
+
status: status && retryImage && uploadImage ? /* @__PURE__ */ jsxs8(Fragment6, { children: [
|
|
2121
|
+
status,
|
|
2122
|
+
" ",
|
|
2123
|
+
/* @__PURE__ */ jsx9(Button7, { variant: "quiet", onClick: () => void uploadImage(retryImage), children: "Retry" })
|
|
2124
|
+
] }) : status,
|
|
2109
2125
|
...uploadImage ? { onImage: (f) => void uploadImage(f) } : {},
|
|
2110
2126
|
...onRecognized ? { onRecognized } : {}
|
|
2111
2127
|
}
|
|
@@ -2166,6 +2182,8 @@ function normalizeWebsite(raw) {
|
|
|
2166
2182
|
}
|
|
2167
2183
|
function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
|
|
2168
2184
|
const confirmed = useConfirmed();
|
|
2185
|
+
const itemsRef = useRef9(items);
|
|
2186
|
+
itemsRef.current = items;
|
|
2169
2187
|
const candidates = items.filter((i) => i.authenticatorState === "known" && !i.authenticator && !i.deletion && (i.capabilities.canEdit || i.capabilities.canManage)).sort((a, b) => a.name.localeCompare(b.name, void 0, { sensitivity: "base" }));
|
|
2170
2188
|
const [input, setInput] = useState11("");
|
|
2171
2189
|
const [image, setImage] = useState11(null);
|
|
@@ -2194,17 +2212,25 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2194
2212
|
setBusy(true);
|
|
2195
2213
|
setError(null);
|
|
2196
2214
|
try {
|
|
2197
|
-
let item = isNew ? created :
|
|
2215
|
+
let item = isNew ? created : itemsRef.current.find((i) => i.id === target) ?? null;
|
|
2216
|
+
let freshlyCreated = false;
|
|
2217
|
+
if (!isNew && (!item || item.authenticatorState !== "known" || item.authenticator)) throw new Error("Authenticator status unavailable. Retry after refreshing.");
|
|
2198
2218
|
if (!item) {
|
|
2199
2219
|
const def = LOGIN_KEYS.map((k) => definitions.find((d) => d.key === k)).find((d) => Boolean(d));
|
|
2200
2220
|
if (!def) throw new Error("No login type is available.");
|
|
2201
2221
|
const r = await port.create(def, null, { ...draftOf(null), name: name.trim(), loginUrls: websiteUrl ? [websiteUrl] : [] });
|
|
2202
2222
|
item = r.item;
|
|
2223
|
+
freshlyCreated = true;
|
|
2203
2224
|
setCreated(item);
|
|
2204
2225
|
onChanged();
|
|
2205
2226
|
}
|
|
2206
2227
|
const it = item;
|
|
2207
|
-
const entry = await confirmed(() =>
|
|
2228
|
+
const entry = await confirmed(async () => {
|
|
2229
|
+
const latest = freshlyCreated ? it : await port.get(it);
|
|
2230
|
+
const listed = itemsRef.current.find((i) => i.id === it.id);
|
|
2231
|
+
if (!freshlyCreated && (latest.authenticatorState !== "known" || latest.authenticator || !isNew && !listed) || listed && (listed.authenticatorState !== "known" || listed.authenticator)) throw new Error("Authenticator status unavailable. Retry after refreshing.");
|
|
2232
|
+
return image ? port.authenticator.enrolQrImage(latest, image, image.name) : port.authenticator.enrol(latest, input);
|
|
2233
|
+
});
|
|
2208
2234
|
let next = { ...it, authenticatorState: "known", authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
|
|
2209
2235
|
const better = authenticatorName(it.name, { issuer: entry.issuer, account: entry.account });
|
|
2210
2236
|
if (!isNew && better !== it.name) {
|
|
@@ -2291,7 +2317,11 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2291
2317
|
/* @__PURE__ */ jsx10(VaultRow, { label: "Website", hint: websiteBad ? /* @__PURE__ */ jsx10("span", { className: "text-destructive", children: "Not a web address" }) : void 0, children: /* @__PURE__ */ jsx10(Field3, { className: "w-full", type: "url", value: website, disabled: busy || created !== null, onChange: (e) => setWebsite(e.target.value), placeholder: "example.com", "aria-label": "Website", "aria-invalid": websiteBad || void 0 }) })
|
|
2292
2318
|
] }) : null
|
|
2293
2319
|
] }),
|
|
2294
|
-
error ? /* @__PURE__ */
|
|
2320
|
+
error ? /* @__PURE__ */ jsxs9("p", { className: "px-1 text-sm text-destructive", role: "alert", children: [
|
|
2321
|
+
error,
|
|
2322
|
+
" ",
|
|
2323
|
+
/* @__PURE__ */ jsx10(Button8, { variant: "quiet", onClick: onChanged, children: "Retry" })
|
|
2324
|
+
] }) : null
|
|
2295
2325
|
]
|
|
2296
2326
|
}
|
|
2297
2327
|
);
|
|
@@ -2326,23 +2356,7 @@ function otpGroups(items, entries, query, hidden = /* @__PURE__ */ new Set()) {
|
|
|
2326
2356
|
const rank = (k) => k === "me" ? 0 : k === "shared" ? 2 : 1;
|
|
2327
2357
|
return [...groups.entries()].sort(([a, x], [b, y]) => rank(a) - rank(b) || x.label.localeCompare(y.label)).map(([key, g]) => ({ key, ...g }));
|
|
2328
2358
|
}
|
|
2329
|
-
function
|
|
2330
|
-
if (added.size === 0) return items;
|
|
2331
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2332
|
-
const out = items.map((i) => {
|
|
2333
|
-
seen.add(i.id);
|
|
2334
|
-
const mine = added.get(i.id);
|
|
2335
|
-
return mine && !i.authenticator ? { ...i, authenticator: mine.authenticator, name: mine.name } : i;
|
|
2336
|
-
});
|
|
2337
|
-
for (const [id, a] of added) if (!seen.has(id)) out.push(a);
|
|
2338
|
-
return out;
|
|
2339
|
-
}
|
|
2340
|
-
function AuthenticatorView({ port, host, items: listed, definitions, query, onOpen, onChanged, header }) {
|
|
2341
|
-
const [added, setAdded] = useState12(/* @__PURE__ */ new Map());
|
|
2342
|
-
const items = withAdded(listed, added);
|
|
2343
|
-
useEffect10(() => {
|
|
2344
|
-
if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
|
|
2345
|
-
}, [listed, added]);
|
|
2359
|
+
function AuthenticatorView({ port, host, items, definitions, query, onOpen, onChanged, header }) {
|
|
2346
2360
|
const [incomplete, setIncomplete] = useState12(true);
|
|
2347
2361
|
const [entries, setEntries] = useState12(/* @__PURE__ */ new Map());
|
|
2348
2362
|
const [hidden, setHidden] = useState12(/* @__PURE__ */ new Set());
|
|
@@ -2366,7 +2380,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2366
2380
|
return () => {
|
|
2367
2381
|
live = false;
|
|
2368
2382
|
};
|
|
2369
|
-
}, [port,
|
|
2383
|
+
}, [port, items]);
|
|
2370
2384
|
const groups = otpGroups(items, entries, query, hidden);
|
|
2371
2385
|
const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
|
|
2372
2386
|
const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
@@ -2379,7 +2393,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2379
2393
|
] }),
|
|
2380
2394
|
total > 0 ? /* @__PURE__ */ jsx11(Button9, { variant: "outline", icon: /* @__PURE__ */ jsx11(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add" }) : null
|
|
2381
2395
|
] }),
|
|
2382
|
-
total > 0 && incomplete ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
|
|
2396
|
+
total > 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ jsxs10("div", { className: "px-4 text-sm text-muted-foreground", children: [
|
|
2383
2397
|
"Some codes unavailable ",
|
|
2384
2398
|
/* @__PURE__ */ jsx11(Button9, { variant: "quiet", onClick: onChanged, children: "Retry" })
|
|
2385
2399
|
] }) : null,
|
|
@@ -2419,7 +2433,6 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2419
2433
|
definitions,
|
|
2420
2434
|
onClose: () => setAdding(false),
|
|
2421
2435
|
onChanged,
|
|
2422
|
-
onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
|
|
2423
2436
|
onDone: (id) => {
|
|
2424
2437
|
setAdding(false);
|
|
2425
2438
|
onOpen(id);
|
|
@@ -2537,7 +2550,7 @@ function OtpListRow({ port, item, family, entry, onOpen, onOpenInNewTab, onRenam
|
|
|
2537
2550
|
}
|
|
2538
2551
|
|
|
2539
2552
|
// src/fields/SettingsPanels.tsx
|
|
2540
|
-
import { useRef as
|
|
2553
|
+
import { useRef as useRef10, useState as useState13 } from "react";
|
|
2541
2554
|
import { Badge as Badge3, Button as Button10, DisclosureHeader, Field as Field4, SegmentedControl, Select as Select3, Switch } from "@ai-matrx/design-system/controls";
|
|
2542
2555
|
import { confirm as confirm3 } from "@ai-matrx/kit/confirm-opener";
|
|
2543
2556
|
import { GlobeIcon } from "@ai-matrx/design-system/icons";
|
|
@@ -2670,7 +2683,7 @@ function FieldSettings({ item, port, field, onClose, onChanged }) {
|
|
|
2670
2683
|
const { busy, error, setError, save } = useSaver((next) => onChanged(next));
|
|
2671
2684
|
const [envDraft, setEnvDraft] = useState13(field.envKey ?? "");
|
|
2672
2685
|
const [descDraft, setDescDraft] = useState13(field.description);
|
|
2673
|
-
const envRef =
|
|
2686
|
+
const envRef = useRef10(null);
|
|
2674
2687
|
const canEdit = item.capabilities.canEdit;
|
|
2675
2688
|
const envProblem = envKeyProblem(envDraft);
|
|
2676
2689
|
const blocker = sandboxBlocker(envDraft, field.envKey);
|
|
@@ -2834,7 +2847,7 @@ function ItemSettingsPanel({ item, port, onClose, onChanged, onPanel }) {
|
|
|
2834
2847
|
}
|
|
2835
2848
|
|
|
2836
2849
|
// src/generator/PasswordGenerator.tsx
|
|
2837
|
-
import { createContext as createContext3, useContext as useContext3, useEffect as useEffect11, useRef as
|
|
2850
|
+
import { createContext as createContext3, useContext as useContext3, useEffect as useEffect11, useRef as useRef11, useState as useState14 } from "react";
|
|
2838
2851
|
import { Popover, PopoverContent, PopoverTrigger, cn as cn6 } from "@ai-matrx/design-system";
|
|
2839
2852
|
import { Button as Button11, NumberStepper, SegmentedControl as SegmentedControl2, Select as Select4, Switch as Switch2 } from "@ai-matrx/design-system/controls";
|
|
2840
2853
|
import { CopyIcon as CopyIcon6 } from "@ai-matrx/design-system/icons";
|
|
@@ -2912,7 +2925,7 @@ function PasswordGenerator({ limits: source, onUse, useLabel = "Use" }) {
|
|
|
2912
2925
|
const [revealed, setRevealed] = useState14(false);
|
|
2913
2926
|
const [note, setNote] = useState14(null);
|
|
2914
2927
|
const copyValue2 = useCopy();
|
|
2915
|
-
const expiry =
|
|
2928
|
+
const expiry = useRef11(null);
|
|
2916
2929
|
const drop = () => {
|
|
2917
2930
|
if (expiry.current) clearTimeout(expiry.current);
|
|
2918
2931
|
expiry.current = null;
|
|
@@ -3138,11 +3151,11 @@ function organizationHints(orgs) {
|
|
|
3138
3151
|
}
|
|
3139
3152
|
|
|
3140
3153
|
// src/shown-until-move.ts
|
|
3141
|
-
import { useEffect as useEffect12, useRef as
|
|
3154
|
+
import { useEffect as useEffect12, useRef as useRef12, useState as useState15 } from "react";
|
|
3142
3155
|
var keyOf = (loc) => `${String(loc.vault)}|${loc.view}|${loc.itemId ?? ""}|${loc.mode}|${loc.panel ?? ""}|${loc.kind ?? ""}`;
|
|
3143
3156
|
function useShownUntilMove(loc) {
|
|
3144
3157
|
const [shown, setShown] = useState15(null);
|
|
3145
|
-
const home =
|
|
3158
|
+
const home = useRef12(null);
|
|
3146
3159
|
const key = keyOf(loc);
|
|
3147
3160
|
useEffect12(() => {
|
|
3148
3161
|
if (!shown) {
|
|
@@ -3200,7 +3213,7 @@ function useLocked(lock) {
|
|
|
3200
3213
|
|
|
3201
3214
|
// src/io/BackupPanel.tsx
|
|
3202
3215
|
import { formatFileSize } from "@ai-matrx/kit/format";
|
|
3203
|
-
import { useEffect as useEffect13, useRef as
|
|
3216
|
+
import { useEffect as useEffect13, useRef as useRef13, useState as useState17 } from "react";
|
|
3204
3217
|
import { Button as Button13, Field as Field5, SegmentedControl as SegmentedControl3, Switch as Switch3 } from "@ai-matrx/design-system/controls";
|
|
3205
3218
|
import { downloadFile } from "@ai-matrx/kit/download";
|
|
3206
3219
|
import { confirm as confirm4 } from "@ai-matrx/kit/confirm-opener";
|
|
@@ -3357,8 +3370,8 @@ function Omissions({ omissions, accepted, onAccepted, verb }) {
|
|
|
3357
3370
|
function BackupPanel({ port, host, items, onClose, onChanged }) {
|
|
3358
3371
|
const minLength = passphraseMinimum(host.backupPassphraseMinLength);
|
|
3359
3372
|
const confirmed = useConfirmed();
|
|
3360
|
-
const fileInput =
|
|
3361
|
-
const abort =
|
|
3373
|
+
const fileInput = useRef13(null);
|
|
3374
|
+
const abort = useRef13(null);
|
|
3362
3375
|
const [mode, setMode] = useState17("backup");
|
|
3363
3376
|
const [busy, setBusy] = useState17(false);
|
|
3364
3377
|
const [error, setError] = useState17(null);
|
|
@@ -3710,7 +3723,7 @@ function ExportPanel({ port, items, onClose }) {
|
|
|
3710
3723
|
}
|
|
3711
3724
|
|
|
3712
3725
|
// src/io/ImportPanel.tsx
|
|
3713
|
-
import { useEffect as useEffect14, useMemo as useMemo2, useRef as
|
|
3726
|
+
import { useEffect as useEffect14, useMemo as useMemo2, useRef as useRef14, useState as useState19 } from "react";
|
|
3714
3727
|
import { Button as Button15, Select as Select6, Switch as Switch4, Textarea as Textarea2 } from "@ai-matrx/design-system/controls";
|
|
3715
3728
|
|
|
3716
3729
|
// src/io/csv-import.ts
|
|
@@ -4573,10 +4586,10 @@ var ME = "me";
|
|
|
4573
4586
|
function ImportPanel({ port, host, items, definitions, organizations, onClose, onChanged, limits }) {
|
|
4574
4587
|
const confirmed = useConfirmed();
|
|
4575
4588
|
const [otpFor, setOtpFor] = useState19(null);
|
|
4576
|
-
const fileInput =
|
|
4577
|
-
const abort =
|
|
4578
|
-
const stop =
|
|
4579
|
-
const keys =
|
|
4589
|
+
const fileInput = useRef14(null);
|
|
4590
|
+
const abort = useRef14(null);
|
|
4591
|
+
const stop = useRef14(false);
|
|
4592
|
+
const keys = useRef14(/* @__PURE__ */ new Map());
|
|
4580
4593
|
const [source, setSource] = useState19("auto");
|
|
4581
4594
|
const [envText, setEnvText] = useState19("");
|
|
4582
4595
|
const [injectSandbox, setInjectSandbox] = useState19(false);
|
|
@@ -4877,14 +4890,14 @@ function Notices({ draft }) {
|
|
|
4877
4890
|
}
|
|
4878
4891
|
|
|
4879
4892
|
// src/ItemList.tsx
|
|
4880
|
-
import { useEffect as useEffect17, useRef as
|
|
4893
|
+
import { useEffect as useEffect17, useRef as useRef17, useState as useState28 } from "react";
|
|
4881
4894
|
import { DropdownMenu as DropdownMenu4, DropdownMenuContent as DropdownMenuContent4, DropdownMenuItem as DropdownMenuItem4, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator as DropdownMenuSeparator4, DropdownMenuTrigger as DropdownMenuTrigger4 } from "@ai-matrx/design-system";
|
|
4882
4895
|
import { cn as cn11 } from "@ai-matrx/design-system";
|
|
4883
4896
|
import { Button as Button23, SearchField, Tile as Tile3 } from "@ai-matrx/design-system/controls";
|
|
4884
4897
|
import { CopyIcon as CopyIcon8 } from "@ai-matrx/design-system/icons";
|
|
4885
4898
|
|
|
4886
4899
|
// src/ItemPane.tsx
|
|
4887
|
-
import { Fragment as Fragment17, useEffect as useEffect16, useMemo as useMemo3, useRef as
|
|
4900
|
+
import { Fragment as Fragment17, useEffect as useEffect16, useMemo as useMemo3, useRef as useRef16, useState as useState27 } from "react";
|
|
4888
4901
|
import { DropdownMenu as DropdownMenu3, DropdownMenuContent as DropdownMenuContent3, DropdownMenuItem as DropdownMenuItem3, DropdownMenuSeparator as DropdownMenuSeparator3, DropdownMenuTrigger as DropdownMenuTrigger3 } from "@ai-matrx/design-system";
|
|
4889
4902
|
import { cn as cn10 } from "@ai-matrx/design-system";
|
|
4890
4903
|
import { Badge as Badge7, Button as Button22, CONTROLS_CONTAINER_CLASS, OptionCombobox, Switch as Switch6 } from "@ai-matrx/design-system/controls";
|
|
@@ -4892,7 +4905,7 @@ import { confirm as confirm10 } from "@ai-matrx/kit/confirm-opener";
|
|
|
4892
4905
|
|
|
4893
4906
|
// src/activity/FilesSection.tsx
|
|
4894
4907
|
import { formatFileSize as formatFileSize2 } from "@ai-matrx/kit/format";
|
|
4895
|
-
import { useRef as
|
|
4908
|
+
import { useRef as useRef15, useState as useState20 } from "react";
|
|
4896
4909
|
import { Badge as Badge5, Button as Button16, Field as Field6, SegmentedControl as SegmentedControl4 } from "@ai-matrx/design-system/controls";
|
|
4897
4910
|
import { cn as cn7 } from "@ai-matrx/design-system";
|
|
4898
4911
|
import { confirm as confirm6 } from "@ai-matrx/kit/confirm-opener";
|
|
@@ -4914,7 +4927,7 @@ function FilesSection({ item, port, editing, onChanged }) {
|
|
|
4914
4927
|
const [error, setError] = useState20(null);
|
|
4915
4928
|
const [pending, setPending] = useState20(null);
|
|
4916
4929
|
const [dragging, setDragging] = useState20(false);
|
|
4917
|
-
const picker =
|
|
4930
|
+
const picker = useRef15(null);
|
|
4918
4931
|
if (!canAdd && item.attachments.length === 0) return null;
|
|
4919
4932
|
async function run(key, work, fallback) {
|
|
4920
4933
|
setBusy(key);
|
|
@@ -4992,7 +5005,7 @@ function FilesSection({ item, port, editing, onChanged }) {
|
|
|
4992
5005
|
}
|
|
4993
5006
|
function EditableFile({ port, item: it, file: a, busy: b, run: go, onDownload, onTooBig }) {
|
|
4994
5007
|
const [open, setOpen] = useState20(false);
|
|
4995
|
-
const replacer =
|
|
5008
|
+
const replacer = useRef15(null);
|
|
4996
5009
|
async function remove() {
|
|
4997
5010
|
const yes = await confirm6({ title: `Delete ${a.label}?`, description: "The encrypted file is removed from this item.", confirmLabel: "Delete", variant: "destructive" });
|
|
4998
5011
|
if (yes) await go(`delete:${a.id}`, () => port.activity.deleteFile(it, a.id), "The file could not be deleted.");
|
|
@@ -5104,7 +5117,7 @@ function DraftFilesSection({ files, onFiles }) {
|
|
|
5104
5117
|
const [pending, setPending] = useState20(null);
|
|
5105
5118
|
const [error, setError] = useState20(null);
|
|
5106
5119
|
const [dragging, setDragging] = useState20(false);
|
|
5107
|
-
const picker =
|
|
5120
|
+
const picker = useRef15(null);
|
|
5108
5121
|
const choose = (file) => {
|
|
5109
5122
|
if (!file) return;
|
|
5110
5123
|
if (fileTooBig(file.size)) return setError(TOO_BIG);
|
|
@@ -6027,6 +6040,10 @@ function confirmDelete(item) {
|
|
|
6027
6040
|
function ItemPane(props) {
|
|
6028
6041
|
const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
|
|
6029
6042
|
const confirmed = useConfirmed();
|
|
6043
|
+
const latestItem = useRef16(item);
|
|
6044
|
+
latestItem.current = item;
|
|
6045
|
+
const [createdForRetry, setCreatedForRetry] = useState27(null);
|
|
6046
|
+
const enrolledForRetry = useRef16(null);
|
|
6030
6047
|
const notify = useNotify();
|
|
6031
6048
|
const editing = mode !== "view";
|
|
6032
6049
|
const seedName = seed?.name ?? "";
|
|
@@ -6059,8 +6076,8 @@ function ItemPane(props) {
|
|
|
6059
6076
|
const [savedNew, setSavedNew] = useState27(false);
|
|
6060
6077
|
const dirty = editing && !savedNew && isDirty(base, draft);
|
|
6061
6078
|
const declared = definition?.fields ?? [];
|
|
6062
|
-
const prev =
|
|
6063
|
-
const paneRef =
|
|
6079
|
+
const prev = useRef16(null);
|
|
6080
|
+
const paneRef = useRef16(null);
|
|
6064
6081
|
const room = useRoomOf(paneRef);
|
|
6065
6082
|
useEffect16(() => {
|
|
6066
6083
|
const was = prev.current;
|
|
@@ -6082,7 +6099,7 @@ function ItemPane(props) {
|
|
|
6082
6099
|
setShowRecovery(false);
|
|
6083
6100
|
setSavedNew(false);
|
|
6084
6101
|
}, [item?.id, mode]);
|
|
6085
|
-
const onDirtyRef =
|
|
6102
|
+
const onDirtyRef = useRef16(onDirty);
|
|
6086
6103
|
onDirtyRef.current = onDirty;
|
|
6087
6104
|
useEffect16(() => onDirtyRef.current(dirty), [dirty]);
|
|
6088
6105
|
const keepKey = draftKey(item?.id ?? null, definition?.key ?? item?.definitionKey ?? null);
|
|
@@ -6112,23 +6129,39 @@ function ItemPane(props) {
|
|
|
6112
6129
|
setError(null);
|
|
6113
6130
|
const out = { ...draft, values: Object.fromEntries(changedValues(base, draft)) };
|
|
6114
6131
|
if (out.values[RECOVERY_CODES_KEY] !== void 0) out.values[RECOVERY_CODES_KEY] = parseRecoveryCodes(out.values[RECOVERY_CODES_KEY]).join("\n");
|
|
6115
|
-
const withOtp = async (saved, notice2) => {
|
|
6132
|
+
const withOtp = async (saved, notice2, freshlyCreated = false) => {
|
|
6116
6133
|
const setup = out.authenticator.trim();
|
|
6117
6134
|
if (!setup) return [saved, notice2, false];
|
|
6118
6135
|
try {
|
|
6119
|
-
|
|
6136
|
+
if (enrolledForRetry.current?.itemId !== saved.id || enrolledForRetry.current.setup !== setup) await confirmed(async () => {
|
|
6137
|
+
const current = freshlyCreated ? saved : await port.get(saved);
|
|
6138
|
+
const visible = latestItem.current?.id === saved.id ? latestItem.current : null;
|
|
6139
|
+
const ownEnrollment = enrolledForRetry.current?.itemId === saved.id;
|
|
6140
|
+
if (!freshlyCreated && (current.authenticatorState !== "known" || !item?.authenticator && current.authenticator && !ownEnrollment || item && !visible) || visible && (visible.authenticatorState !== "known" || !item?.authenticator && visible.authenticator && !ownEnrollment)) throw new Error("Authenticator status unavailable. Retry after refreshing");
|
|
6141
|
+
await port.authenticator.enrol(current, setup);
|
|
6142
|
+
enrolledForRetry.current = { itemId: saved.id, setup };
|
|
6143
|
+
});
|
|
6120
6144
|
return [await port.get(saved), notice2, true];
|
|
6121
6145
|
} catch (e) {
|
|
6146
|
+
if (enrolledForRetry.current?.itemId === saved.id && enrolledForRetry.current.setup === setup) return [saved, `Saved and authenticator set up, but refresh failed: ${e instanceof Error ? e.message : "Retry"}.`, false];
|
|
6122
6147
|
const why = wasCancelled(e) ? "it was not confirmed" : e instanceof Error ? e.message : "it was refused";
|
|
6123
6148
|
return [saved, `Saved, but the authenticator was not set up: ${why}. Add it with Edit.`, false];
|
|
6124
6149
|
}
|
|
6125
6150
|
};
|
|
6126
6151
|
try {
|
|
6127
|
-
if (item) {
|
|
6128
|
-
const r = await port.save(item, definition, out);
|
|
6152
|
+
if (item || createdForRetry) {
|
|
6153
|
+
const r = await port.save(item ?? createdForRetry, definition, out);
|
|
6129
6154
|
const [next, n, enrolled] = await withOtp(r.item, r.notice);
|
|
6130
|
-
|
|
6131
|
-
|
|
6155
|
+
if (out.authenticator.trim() && !enrolled) {
|
|
6156
|
+
setError(n);
|
|
6157
|
+
onChanged();
|
|
6158
|
+
return;
|
|
6159
|
+
}
|
|
6160
|
+
if (createdForRetry) await finishNew(next, n, enrolled);
|
|
6161
|
+
else {
|
|
6162
|
+
dropKeptDraft(keepKey);
|
|
6163
|
+
onSaved(next, false, n, enrolled);
|
|
6164
|
+
}
|
|
6132
6165
|
} else if (definition && owner === "person") {
|
|
6133
6166
|
const secretKey = declared.find((f) => f.storage === "encrypted" && f.kind === "secret")?.key ?? null;
|
|
6134
6167
|
const r = await port.sharing.assign({ recipientEmail: recipient.trim(), definition, draft: out, generateFieldKey: generatePrivately ? secretKey : null });
|
|
@@ -6138,12 +6171,14 @@ function ItemPane(props) {
|
|
|
6138
6171
|
setSent(r);
|
|
6139
6172
|
} else if (definition) {
|
|
6140
6173
|
const r = await port.create(definition, owner === "me" ? null : owner, out);
|
|
6141
|
-
|
|
6142
|
-
const [
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6174
|
+
setCreatedForRetry(r.item);
|
|
6175
|
+
const [withCode, n0, enrolled] = await withOtp(r.item, r.notice, true);
|
|
6176
|
+
if (out.authenticator.trim() && !enrolled) {
|
|
6177
|
+
setError(n0);
|
|
6178
|
+
onChanged();
|
|
6179
|
+
return;
|
|
6180
|
+
}
|
|
6181
|
+
await finishNew(withCode, n0, enrolled);
|
|
6147
6182
|
}
|
|
6148
6183
|
} catch (e) {
|
|
6149
6184
|
setError(e instanceof Error ? e.message : "Saving failed. Nothing was changed.");
|
|
@@ -6151,6 +6186,13 @@ function ItemPane(props) {
|
|
|
6151
6186
|
setBusy(false);
|
|
6152
6187
|
}
|
|
6153
6188
|
}
|
|
6189
|
+
async function finishNew(saved, notice2, enrolled) {
|
|
6190
|
+
const [next, finalNotice] = await storeQueuedFiles(saved, notice2);
|
|
6191
|
+
dropKeptDraft(keepKey);
|
|
6192
|
+
setSavedNew(true);
|
|
6193
|
+
onDirty(false);
|
|
6194
|
+
onSaved(next, true, finalNotice, enrolled);
|
|
6195
|
+
}
|
|
6154
6196
|
async function storeQueuedFiles(saved, notice2) {
|
|
6155
6197
|
let next = saved;
|
|
6156
6198
|
const failed = [];
|
|
@@ -6332,7 +6374,11 @@ function ItemPane(props) {
|
|
|
6332
6374
|
const filesProps = item ? { item, port, host, definition, organizations, arg: null, onClose: () => void 0, onChanged, onPanel } : null;
|
|
6333
6375
|
return /* @__PURE__ */ jsx25(RoomProvider, { value: room, children: /* @__PURE__ */ jsxs23("article", { ref: paneRef, "aria-label": item?.name ?? "New item", className: cn10(CONTROLS_CONTAINER_CLASS, "flex h-full min-h-0 flex-col bg-background"), children: [
|
|
6334
6376
|
header,
|
|
6335
|
-
error ? /* @__PURE__ */
|
|
6377
|
+
error ? /* @__PURE__ */ jsxs23("div", { role: "alert", className: "border-b border-border bg-muted px-5 py-2 text-sm text-destructive", children: [
|
|
6378
|
+
error,
|
|
6379
|
+
" ",
|
|
6380
|
+
error.includes("Retry after refreshing") ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged(), children: "Retry" }) : null
|
|
6381
|
+
] }) : null,
|
|
6336
6382
|
notice && !editing ? /* @__PURE__ */ jsx25("div", { role: "status", className: "border-b border-border bg-muted px-5 py-2 text-sm text-warning", children: notice }) : null,
|
|
6337
6383
|
item && !editing && (itemOff || offFields.length > 0) ? /* @__PURE__ */ jsxs23("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-5 py-2 text-sm", children: [
|
|
6338
6384
|
/* @__PURE__ */ jsx25("span", { className: "min-w-0 flex-1 truncate text-warning", children: offFields.length === 0 ? "This item is turned off" : offFields.length === 1 ? "1 field is turned off" : `${offFields.length} fields are turned off` }),
|
|
@@ -6436,10 +6482,11 @@ function ItemPane(props) {
|
|
|
6436
6482
|
i
|
|
6437
6483
|
)),
|
|
6438
6484
|
/* @__PURE__ */ jsx25(AddRow, { label: draft.loginUrls.length === 0 ? "Website" : "", onAdd: () => patch({ loginUrls: [...draft.loginUrls, ""] }), children: draft.loginUrls.length === 0 ? "Add website" : "Add another website" }),
|
|
6439
|
-
|
|
6485
|
+
/* @__PURE__ */ jsx25("div", { hidden: Boolean(item && item.authenticatorState !== "known"), children: /* @__PURE__ */ jsx25(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }) }),
|
|
6486
|
+
item && item.authenticatorState !== "known" ? /* @__PURE__ */ jsxs23(VaultRow, { label: "Authenticator", children: [
|
|
6440
6487
|
/* @__PURE__ */ jsx25("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
|
|
6441
6488
|
item.authenticatorState === "unavailable" ? /* @__PURE__ */ jsx25(Button22, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
|
|
6442
|
-
] }),
|
|
6489
|
+
] }) : null,
|
|
6443
6490
|
item && isLogin && !item.authenticator ? /* @__PURE__ */ jsx25(TwoFactorMethodRow, { port, item, onChanged }) : null,
|
|
6444
6491
|
!item && isLogin && draft.authenticator.trim() === "" ? /* @__PURE__ */ jsx25(DraftTwoFactorRow, { custom: draft.custom, onCustom: (custom) => patch({ custom }) }) : null,
|
|
6445
6492
|
recoveryEditing ? /* @__PURE__ */ jsx25(
|
|
@@ -6598,7 +6645,7 @@ function ItemList({ title, items, definitions, port, selectedId, onSelect, query
|
|
|
6598
6645
|
const pinned = matchingIds ? items.filter((i) => matchingIds.has(i.id)) : [];
|
|
6599
6646
|
const rest = matchingIds ? items.filter((i) => !matchingIds.has(i.id)) : items;
|
|
6600
6647
|
const ordered = [...pinned, ...rest];
|
|
6601
|
-
const listRef =
|
|
6648
|
+
const listRef = useRef17(null);
|
|
6602
6649
|
useEffect17(() => {
|
|
6603
6650
|
if (selectedId) listRef.current?.querySelector(`[data-item="${selectedId}"]`)?.scrollIntoView({ block: "nearest" });
|
|
6604
6651
|
}, [selectedId, items.length]);
|
|
@@ -6702,7 +6749,7 @@ function ListRow({ item, family, port, on, onSelect, withOwner, ownContextMenu,
|
|
|
6702
6749
|
...manageEntries(item, onAction),
|
|
6703
6750
|
...hostEntries.map((h) => ({ ...h, kind: h.kind ?? "host" }))
|
|
6704
6751
|
];
|
|
6705
|
-
const entriesRef =
|
|
6752
|
+
const entriesRef = useRef17(entries);
|
|
6706
6753
|
entriesRef.current = entries;
|
|
6707
6754
|
const glyphOf = (e) => e.id === "copy:username" ? /* @__PURE__ */ jsx26(UserGlyph, { className: "size-4" }) : e.id === "copy:password" ? /* @__PURE__ */ jsx26(KeyGlyph, { className: "size-4" }) : e.id === "copy:code" ? /* @__PURE__ */ jsx26(ClockGlyph, { className: "size-4" }) : e.kind === "favorite" ? /* @__PURE__ */ jsx26(StarGlyph, { className: cn11("size-4", favorite?.on && "fill-current text-warning") }) : e.kind === "open" ? /* @__PURE__ */ jsx26(ChevronRightGlyph, { className: "size-4" }) : e.kind === "host" || e.kind === "manage" ? null : /* @__PURE__ */ jsx26(CopyIcon8, { className: "size-4" });
|
|
6708
6755
|
const menuItem = (e) => /* @__PURE__ */ jsxs24(DropdownMenuItem4, { disabled: e.disabled ?? false, onSelect: e.onSelect, children: [
|
|
@@ -7109,7 +7156,7 @@ function NewItemFlow({ definitions, url, onUrl, onChoose, onCancel }) {
|
|
|
7109
7156
|
}
|
|
7110
7157
|
|
|
7111
7158
|
// src/sharing/SecureLinkPanel.tsx
|
|
7112
|
-
import { useEffect as useEffect19, useRef as
|
|
7159
|
+
import { useEffect as useEffect19, useRef as useRef18, useState as useState32 } from "react";
|
|
7113
7160
|
import { confirm as confirm11 } from "@ai-matrx/kit/confirm-opener";
|
|
7114
7161
|
import { Checkbox as Checkbox3 } from "@ai-matrx/design-system";
|
|
7115
7162
|
import { Badge as Badge8, Button as Button27, Field as Field11, RegionSkeleton as RegionSkeleton2, SegmentedControl as SegmentedControl6, Textarea as Textarea4 } from "@ai-matrx/design-system/controls";
|
|
@@ -7234,7 +7281,7 @@ function SecureLinkPanel({ item, port, onClose }) {
|
|
|
7234
7281
|
};
|
|
7235
7282
|
}, [sharing, item, nonce]);
|
|
7236
7283
|
const [phoneTyped, setPhoneTyped] = useState32(false);
|
|
7237
|
-
const prefilled =
|
|
7284
|
+
const prefilled = useRef18(false);
|
|
7238
7285
|
useEffect19(() => {
|
|
7239
7286
|
const lookup = sharing.phoneOnFile;
|
|
7240
7287
|
if (!lookup || phoneTyped) return;
|
|
@@ -7711,19 +7758,19 @@ async function loadFullPreview(bridge, operationId, current) {
|
|
|
7711
7758
|
}
|
|
7712
7759
|
|
|
7713
7760
|
// src/native/use-native-import.ts
|
|
7714
|
-
import { useEffect as useEffect21, useRef as
|
|
7761
|
+
import { useEffect as useEffect21, useRef as useRef19, useState as useState34 } from "react";
|
|
7715
7762
|
var fail = (e, fallback) => e instanceof Error && e.message ? e.message : fallback;
|
|
7716
7763
|
var RESTART = "Your account changed. The import was stopped; start it again.";
|
|
7717
7764
|
function useNativeImport(bridge, stillAllowed, onImported) {
|
|
7718
7765
|
const [screen, setScreen] = useState34({ kind: "idle", message: null });
|
|
7719
7766
|
const [busy, setBusy] = useState34(false);
|
|
7720
|
-
const generation =
|
|
7721
|
-
const mounted =
|
|
7722
|
-
const op =
|
|
7723
|
-
const last =
|
|
7724
|
-
const timer2 =
|
|
7725
|
-
const polls =
|
|
7726
|
-
const refreshRef =
|
|
7767
|
+
const generation = useRef19(0);
|
|
7768
|
+
const mounted = useRef19(false);
|
|
7769
|
+
const op = useRef19(null);
|
|
7770
|
+
const last = useRef19(null);
|
|
7771
|
+
const timer2 = useRef19(null);
|
|
7772
|
+
const polls = useRef19(0);
|
|
7773
|
+
const refreshRef = useRef19(async () => void 0);
|
|
7727
7774
|
const current = (g) => mounted.current && g === generation.current;
|
|
7728
7775
|
const stopPolling = () => {
|
|
7729
7776
|
if (timer2.current !== null) clearTimeout(timer2.current);
|
|
@@ -7892,7 +7939,7 @@ function useNativeImport(bridge, stillAllowed, onImported) {
|
|
|
7892
7939
|
setBusy(false);
|
|
7893
7940
|
setScreen({ kind: "idle", message: null });
|
|
7894
7941
|
};
|
|
7895
|
-
const invalidateRef =
|
|
7942
|
+
const invalidateRef = useRef19(invalidate);
|
|
7896
7943
|
invalidateRef.current = invalidate;
|
|
7897
7944
|
useEffect21(() => {
|
|
7898
7945
|
mounted.current = true;
|
|
@@ -8064,13 +8111,13 @@ function CaptureCard({ capture, onChanged }) {
|
|
|
8064
8111
|
}
|
|
8065
8112
|
|
|
8066
8113
|
// src/extension/FillSetupCard.tsx
|
|
8067
|
-
import { useRef as
|
|
8114
|
+
import { useRef as useRef20, useState as useState37, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
8068
8115
|
import { Button as Button31, Field as Field12 } from "@ai-matrx/design-system/controls";
|
|
8069
8116
|
import { jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
8070
8117
|
function FillSetupCard({ fillSetup }) {
|
|
8071
8118
|
const notify = useNotify();
|
|
8072
8119
|
const state = useSyncExternalStore4(fillSetup.subscribe, fillSetup.get, fillSetup.get);
|
|
8073
|
-
const box =
|
|
8120
|
+
const box = useRef20(null);
|
|
8074
8121
|
const [busy, setBusy] = useState37(false);
|
|
8075
8122
|
const [error, setError] = useState37(null);
|
|
8076
8123
|
if (state.status !== "off") return null;
|
|
@@ -8610,7 +8657,7 @@ function LockedScreen({ lock }) {
|
|
|
8610
8657
|
] });
|
|
8611
8658
|
}
|
|
8612
8659
|
function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocationChange, onInventory }) {
|
|
8613
|
-
const rootRef =
|
|
8660
|
+
const rootRef = useRef21(null);
|
|
8614
8661
|
const width = useElementWidth(rootRef);
|
|
8615
8662
|
const mode = layout !== "auto" ? layout : width >= FULL_AT ? "full" : width >= SPLIT_AT ? "split" : "stack";
|
|
8616
8663
|
const notify = useNotify();
|
|
@@ -8622,7 +8669,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8622
8669
|
const [error, setError] = useState41(null);
|
|
8623
8670
|
const [inner, setInner] = useState41(DEFAULT_LOCATION);
|
|
8624
8671
|
const loc = location ?? inner;
|
|
8625
|
-
const dirty =
|
|
8672
|
+
const dirty = useRef21(false);
|
|
8626
8673
|
const [notice, setNotice] = useState41(null);
|
|
8627
8674
|
const [firstCode, setFirstCode] = useShownUntilMove(loc);
|
|
8628
8675
|
const move = useCallback4(
|
|
@@ -8651,7 +8698,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8651
8698
|
},
|
|
8652
8699
|
[loc, move]
|
|
8653
8700
|
);
|
|
8654
|
-
const keyState =
|
|
8701
|
+
const keyState = useRef21({ loc, go });
|
|
8655
8702
|
keyState.current = { loc, go };
|
|
8656
8703
|
useEffect23(() => {
|
|
8657
8704
|
const onKey = (e) => {
|
|
@@ -8675,7 +8722,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8675
8722
|
document.addEventListener("keydown", onKey);
|
|
8676
8723
|
return () => document.removeEventListener("keydown", onKey);
|
|
8677
8724
|
}, []);
|
|
8678
|
-
const keepSearchFocus =
|
|
8725
|
+
const keepSearchFocus = useRef21(false);
|
|
8679
8726
|
useEffect23(() => {
|
|
8680
8727
|
if (!keepSearchFocus.current) return;
|
|
8681
8728
|
keepSearchFocus.current = false;
|
|
@@ -8685,13 +8732,14 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8685
8732
|
search.setSelectionRange(search.value.length, search.value.length);
|
|
8686
8733
|
}, [loc.q]);
|
|
8687
8734
|
const hasPrompts = useHostPrompts(host.capture, host.fillSetup);
|
|
8688
|
-
const knobsRef =
|
|
8735
|
+
const knobsRef = useRef21(host.knobs);
|
|
8689
8736
|
knobsRef.current = host.knobs;
|
|
8690
8737
|
const [signedOut, setSignedOut] = useState41(false);
|
|
8691
8738
|
const [refreshError, setRefreshError] = useState41(null);
|
|
8692
8739
|
const [refreshing, setRefreshing] = useState41(false);
|
|
8693
|
-
const loadedAt =
|
|
8694
|
-
const inFlight =
|
|
8740
|
+
const loadedAt = useRef21(0);
|
|
8741
|
+
const inFlight = useRef21(false);
|
|
8742
|
+
const loadGeneration = useRef21(0);
|
|
8695
8743
|
const apply = useCallback4(
|
|
8696
8744
|
(s) => {
|
|
8697
8745
|
loadedAt.current = Date.now();
|
|
@@ -8708,11 +8756,15 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8708
8756
|
const refresh = useCallback4(() => {
|
|
8709
8757
|
if (inFlight.current) return;
|
|
8710
8758
|
inFlight.current = true;
|
|
8759
|
+
const generation = ++loadGeneration.current;
|
|
8711
8760
|
setRefreshing(true);
|
|
8712
8761
|
port.load().then((s) => {
|
|
8762
|
+
if (generation !== loadGeneration.current) return;
|
|
8713
8763
|
setRefreshError(null);
|
|
8714
8764
|
apply(s);
|
|
8715
|
-
}).catch((e) =>
|
|
8765
|
+
}).catch((e) => {
|
|
8766
|
+
if (generation === loadGeneration.current) setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."));
|
|
8767
|
+
}).finally(() => {
|
|
8716
8768
|
inFlight.current = false;
|
|
8717
8769
|
setRefreshing(false);
|
|
8718
8770
|
});
|
|
@@ -8720,18 +8772,20 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8720
8772
|
port.lists.recents?.list().then(setRecents, () => void 0);
|
|
8721
8773
|
}, [port, apply]);
|
|
8722
8774
|
const load = useCallback4(() => {
|
|
8775
|
+
const generation = ++loadGeneration.current;
|
|
8723
8776
|
setError(null);
|
|
8724
8777
|
setRefreshError(null);
|
|
8725
8778
|
let finished = false;
|
|
8726
8779
|
port.load((partial) => {
|
|
8727
|
-
if (finished) return;
|
|
8780
|
+
if (finished || generation !== loadGeneration.current) return;
|
|
8728
8781
|
setSnap(partial);
|
|
8729
8782
|
setItems(partial.items);
|
|
8730
8783
|
setDeleted(partial.deleted);
|
|
8731
8784
|
}).then((s) => {
|
|
8732
8785
|
finished = true;
|
|
8733
|
-
apply(s);
|
|
8786
|
+
if (generation === loadGeneration.current) apply(s);
|
|
8734
8787
|
}).catch((e) => {
|
|
8788
|
+
if (generation !== loadGeneration.current) return;
|
|
8735
8789
|
setSignedOut(e?.status === 401);
|
|
8736
8790
|
setError(e instanceof Error ? e.message : "The vault could not be loaded.");
|
|
8737
8791
|
});
|
|
@@ -8740,7 +8794,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8740
8794
|
port.lists.recents?.list().then(setRecents, () => void 0);
|
|
8741
8795
|
}, [port, apply]);
|
|
8742
8796
|
useEffect23(load, [load]);
|
|
8743
|
-
const quietRef =
|
|
8797
|
+
const quietRef = useRef21({ loc, ready: false });
|
|
8744
8798
|
quietRef.current = { loc, ready: snap !== null && !error };
|
|
8745
8799
|
const [knobSeconds, setKnobSeconds] = useState41(null);
|
|
8746
8800
|
useEffect23(() => {
|
|
@@ -8776,7 +8830,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
8776
8830
|
if (timer2) clearInterval(timer2);
|
|
8777
8831
|
};
|
|
8778
8832
|
}, [refresh, everySeconds]);
|
|
8779
|
-
const inventoryRef =
|
|
8833
|
+
const inventoryRef = useRef21(onInventory);
|
|
8780
8834
|
inventoryRef.current = onInventory;
|
|
8781
8835
|
useEffect23(() => {
|
|
8782
8836
|
if (snap) inventoryRef.current?.({ items, definitions: snap.definitions });
|
|
@@ -9940,7 +9994,7 @@ function itemOf(w, owner, defs, auth, state = "known", unavailable = [], invento
|
|
|
9940
9994
|
fields: (w.fields ?? []).filter((f) => f.execution_purpose !== "passkey_private").map((f) => fieldOf(f, def)),
|
|
9941
9995
|
custom: (w.non_secret_fields ?? []).map((c) => ({ key: c["key"] ?? "", label: c["label"] ?? humanize(c["key"] ?? ""), value: c["value"] ?? "" })),
|
|
9942
9996
|
authenticator: a ? { digits: a.digits, period: a.period, enabled: a.enabled } : null,
|
|
9943
|
-
authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) ? "unavailable" : "known",
|
|
9997
|
+
authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) || (w.fields ?? []).some((f) => f.field_key === OTP_SEED_KEY && f.is_active !== false) ? "unavailable" : "known",
|
|
9944
9998
|
capabilities: { canUse: cap.can_use ?? false, canEdit: cap.can_edit ?? false, canReveal: cap.can_reveal ?? false, canManage: cap.can_manage ?? false },
|
|
9945
9999
|
attachments: (w.attachments ?? []).map((a2) => ({ id: a2.id, label: a2.label, description: a2.description ?? "", fileName: a2.file_name, mediaType: a2.media_type ?? "application/octet-stream", sizeBytes: a2.size_bytes, handling: handlingOf(a2.handling), createdAt: a2.created_at, updatedAt: a2.updated_at })),
|
|
9946
10000
|
uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
|
|
@@ -9981,16 +10035,12 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
9981
10035
|
let authUnavailable = [];
|
|
9982
10036
|
let inventoryOwners = /* @__PURE__ */ new Map();
|
|
9983
10037
|
let orgs = [];
|
|
10038
|
+
let loadGeneration = 0;
|
|
10039
|
+
let authMutation = 0;
|
|
9984
10040
|
const orgOf = (id, userId, mine) => {
|
|
9985
10041
|
if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
|
|
9986
10042
|
return mine || userId ? { kind: "me", label: "Personal", organizationId: null } : { kind: "shared", label: "Shared with me", organizationId: null };
|
|
9987
10043
|
};
|
|
9988
|
-
const ownerInOrgList = (w, via, meId) => {
|
|
9989
|
-
if (w.organization_id) return orgOf(w.organization_id, null, false);
|
|
9990
|
-
if (w.user_id && meId && w.user_id === meId) return { kind: "me", label: "Personal", organizationId: null };
|
|
9991
|
-
if (via) return { kind: "organization", label: via.name, organizationId: via.id, memberShared: { userId: w.user_id ?? null } };
|
|
9992
|
-
return { kind: "shared", label: "Shared with me", organizationId: null };
|
|
9993
|
-
};
|
|
9994
10044
|
async function refetch(id, owner) {
|
|
9995
10045
|
const w = await account9.getItem(api, { path: { item_id: id } });
|
|
9996
10046
|
return itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwners.get(id) ?? owner);
|
|
@@ -10003,6 +10053,7 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
10003
10053
|
definition: (key) => defs.get(key),
|
|
10004
10054
|
organizations: () => orgs,
|
|
10005
10055
|
setAuthenticator: (itemId, entry) => {
|
|
10056
|
+
authMutation++;
|
|
10006
10057
|
const next = new Map(auth);
|
|
10007
10058
|
if (entry) next.set(itemId, entry);
|
|
10008
10059
|
else next.delete(itemId);
|
|
@@ -10020,134 +10071,155 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
10020
10071
|
fields: createFieldsPort(ctx),
|
|
10021
10072
|
lists: createListsPort(ctx, options.userState),
|
|
10022
10073
|
async load(onProgress) {
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
defs = new Map(parseDefinitions(catalog2.filter((e) => e.is_active).map((e) => ({ key: e.key, payload: e.payload, sort_order: e.sort_order }))).map((d) => [d.key, d]));
|
|
10045
|
-
const mineItems = raw.mine?.items ?? [];
|
|
10046
|
-
const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
|
|
10047
|
-
const seen = /* @__PURE__ */ new Map();
|
|
10048
|
-
const deleted = /* @__PURE__ */ new Map();
|
|
10049
|
-
const take = (w, owner, inventoryOwner = owner) => {
|
|
10050
|
-
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
|
|
10051
|
-
inventoryOwners.set(w.id, inventoryOwner);
|
|
10052
|
-
const item = itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwner);
|
|
10053
|
-
(isDeleted(w) ? deleted : seen).set(w.id, item);
|
|
10074
|
+
const run = async (restarts) => {
|
|
10075
|
+
const generation = ++loadGeneration;
|
|
10076
|
+
const mutationAtStart = authMutation;
|
|
10077
|
+
let localAuth = /* @__PURE__ */ new Map();
|
|
10078
|
+
let localState = "pending";
|
|
10079
|
+
let localUnavailable = [];
|
|
10080
|
+
let localDefs = /* @__PURE__ */ new Map();
|
|
10081
|
+
let localOrgs = [];
|
|
10082
|
+
const localOwners = /* @__PURE__ */ new Map();
|
|
10083
|
+
const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
|
|
10084
|
+
const authP = retrying(() => apps2.listAuthenticators(api));
|
|
10085
|
+
const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
|
|
10086
|
+
const sharedP = retrying(() => account9.sharedWithMe(api));
|
|
10087
|
+
const quiet = (p) => void p.catch(() => void 0);
|
|
10088
|
+
[catalogP, authP, userP, sharedP].forEach(quiet);
|
|
10089
|
+
const orgItemsP = retrying(() => account9.organizationItems(api));
|
|
10090
|
+
quiet(orgItemsP);
|
|
10091
|
+
const raw = {
|
|
10092
|
+
orgLists: [],
|
|
10093
|
+
orgsKnown: options.organizations !== void 0,
|
|
10094
|
+
meId: null
|
|
10054
10095
|
};
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
10068
|
-
|
|
10096
|
+
const unavailable = [];
|
|
10097
|
+
let done = false;
|
|
10098
|
+
const build = () => {
|
|
10099
|
+
const catalog2 = raw.catalog ?? [];
|
|
10100
|
+
localDefs = new Map(parseDefinitions(catalog2.filter((e) => e.is_active).map((e) => ({ key: e.key, payload: e.payload, sort_order: e.sort_order }))).map((d) => [d.key, d]));
|
|
10101
|
+
const mineItems = raw.mine?.items ?? [];
|
|
10102
|
+
const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
|
|
10103
|
+
const seen = /* @__PURE__ */ new Map();
|
|
10104
|
+
const deleted = /* @__PURE__ */ new Map();
|
|
10105
|
+
const localOrgOf = (id, mine2) => id ? { kind: "organization", label: localOrgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id } : mine2 ? { kind: "me", label: "Personal", organizationId: null } : { kind: "shared", label: "Shared with me", organizationId: null };
|
|
10106
|
+
const take = (w, owner, inventoryOwner = owner) => {
|
|
10107
|
+
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
|
|
10108
|
+
localOwners.set(w.id, inventoryOwner);
|
|
10109
|
+
const item = itemOf(w, owner, localDefs, localAuth, localState, localUnavailable, inventoryOwner);
|
|
10110
|
+
(isDeleted(w) ? deleted : seen).set(w.id, item);
|
|
10111
|
+
};
|
|
10112
|
+
for (const w of mineItems) take(w, localOrgOf(w.organization_id, true));
|
|
10113
|
+
raw.orgLists.forEach((l, i) => {
|
|
10114
|
+
const via = localOrgs[i];
|
|
10115
|
+
for (const w of l?.items ?? []) take(w, w.organization_id ? localOrgOf(w.organization_id, false) : w.user_id && meId && w.user_id === meId ? localOrgOf(null, true) : via ? { kind: "organization", label: via.name, organizationId: via.id, memberShared: { userId: w.user_id ?? null } } : localOrgOf(null, false));
|
|
10116
|
+
});
|
|
10117
|
+
for (const w of raw.shared?.items ?? []) take(w, w.organization_id ? localOrgOf(w.organization_id, false) : { kind: "shared", label: "Shared with me", organizationId: null }, { kind: "shared", label: "Shared with me", organizationId: null });
|
|
10118
|
+
const pending = done ? [] : [...raw.orgsKnown ? localOrgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
|
|
10119
|
+
return {
|
|
10120
|
+
unavailable: [...unavailable],
|
|
10121
|
+
...pending.length > 0 ? { pending } : {},
|
|
10122
|
+
items: [...seen.values()],
|
|
10123
|
+
deleted: [...deleted.values()],
|
|
10124
|
+
organizations: localOrgs,
|
|
10125
|
+
definitions: [...localDefs.values()].sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label))
|
|
10126
|
+
};
|
|
10069
10127
|
};
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10128
|
+
const progress = () => {
|
|
10129
|
+
if (!done && generation === loadGeneration && mutationAtStart === authMutation && onProgress && raw.catalog && raw.mine) onProgress(build());
|
|
10130
|
+
};
|
|
10131
|
+
const orgReportP = options.organizations ? null : retrying(() => platform.listMyOrganizations(api));
|
|
10132
|
+
if (orgReportP) quiet(orgReportP);
|
|
10133
|
+
const orgListsReady = (async () => {
|
|
10134
|
+
const report = orgReportP ? await orgReportP : null;
|
|
10135
|
+
raw.meId = report?.user_id ?? null;
|
|
10136
|
+
localOrgs = [...new Map((options.organizations ?? (report?.organizations ?? []).filter((o) => !o.archived_at).map((o) => ({ id: o.id, name: o.name }))).map((o) => [o.id, o])).values()];
|
|
10137
|
+
raw.orgLists = localOrgs.map(() => void 0);
|
|
10138
|
+
raw.orgsKnown = true;
|
|
10139
|
+
progress();
|
|
10140
|
+
const one = await orgItemsP.then(
|
|
10141
|
+
(r) => r,
|
|
10142
|
+
(e) => {
|
|
10143
|
+
if (e?.status === 404) {
|
|
10144
|
+
hostProblem("GET /vault/organization-items answered 404; reading each organization vault separately", e);
|
|
10145
|
+
return null;
|
|
10146
|
+
}
|
|
10147
|
+
return "failed";
|
|
10089
10148
|
}
|
|
10090
|
-
|
|
10149
|
+
);
|
|
10150
|
+
if (one) {
|
|
10151
|
+
const byOrg = one === "failed" ? /* @__PURE__ */ new Map() : new Map(one.views.map((v) => [v.organization_id, v.items]));
|
|
10152
|
+
const failed = one === "failed" ? new Set(localOrgs.map((o) => o.id)) : new Set(one.unavailable);
|
|
10153
|
+
const settled = localOrgs.map((o, i) => {
|
|
10154
|
+
const items = byOrg.get(o.id);
|
|
10155
|
+
if (failed.has(o.id) || items === void 0) return Promise.reject(new Error(`${o.name} is unavailable`));
|
|
10156
|
+
raw.orgLists[i] = { items, count: items.length };
|
|
10157
|
+
return Promise.resolve(raw.orgLists[i]);
|
|
10158
|
+
});
|
|
10159
|
+
settled.forEach(quiet);
|
|
10160
|
+
progress();
|
|
10161
|
+
return settled;
|
|
10091
10162
|
}
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
return
|
|
10163
|
+
return localOrgs.map((o, i) => {
|
|
10164
|
+
const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
|
|
10165
|
+
const arrived = p.then((l) => {
|
|
10166
|
+
raw.orgLists[i] = l;
|
|
10167
|
+
progress();
|
|
10168
|
+
return l;
|
|
10169
|
+
});
|
|
10170
|
+
quiet(arrived);
|
|
10171
|
+
return arrived;
|
|
10101
10172
|
});
|
|
10102
|
-
|
|
10173
|
+
})();
|
|
10174
|
+
quiet(orgListsReady);
|
|
10175
|
+
const authDone = authP.then((a) => {
|
|
10176
|
+
localUnavailable = a.unavailable_scopes ?? [];
|
|
10177
|
+
const fresh = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
|
|
10178
|
+
localAuth = fresh;
|
|
10179
|
+
localState = "known";
|
|
10180
|
+
if (localUnavailable.length) unavailable.push("One-time codes");
|
|
10103
10181
|
progress();
|
|
10104
|
-
|
|
10105
|
-
|
|
10106
|
-
|
|
10107
|
-
const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
|
|
10108
|
-
const arrived = p.then((l) => {
|
|
10109
|
-
raw.orgLists[i] = l;
|
|
10110
|
-
progress();
|
|
10111
|
-
return l;
|
|
10112
|
-
});
|
|
10113
|
-
quiet(arrived);
|
|
10114
|
-
return arrived;
|
|
10182
|
+
}, () => {
|
|
10183
|
+
localState = "unavailable";
|
|
10184
|
+
unavailable.push("One-time codes");
|
|
10115
10185
|
});
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
authState = "unavailable";
|
|
10127
|
-
unavailable.push("One-time codes");
|
|
10128
|
-
});
|
|
10129
|
-
const sharedDone = sharedP.then((sh) => {
|
|
10130
|
-
raw.shared = sh;
|
|
10186
|
+
const sharedDone = sharedP.then((sh) => {
|
|
10187
|
+
raw.shared = sh;
|
|
10188
|
+
progress();
|
|
10189
|
+
}, () => {
|
|
10190
|
+
raw.shared = { items: [], count: 0 };
|
|
10191
|
+
unavailable.push("Shared with me");
|
|
10192
|
+
});
|
|
10193
|
+
const [catalog, mine] = await Promise.all([catalogP, userP]);
|
|
10194
|
+
raw.catalog = catalog;
|
|
10195
|
+
raw.mine = mine;
|
|
10131
10196
|
progress();
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
unavailable.push(orgs[i]?.name ?? "An organization");
|
|
10197
|
+
const orgPs = await orgListsReady;
|
|
10198
|
+
const orgSettled = await Promise.allSettled(orgPs);
|
|
10199
|
+
orgSettled.forEach((r, i) => {
|
|
10200
|
+
if (r.status === "rejected") {
|
|
10201
|
+
raw.orgLists[i] = { items: [], count: 0 };
|
|
10202
|
+
unavailable.push(localOrgs[i]?.name ?? "An organization");
|
|
10203
|
+
}
|
|
10204
|
+
});
|
|
10205
|
+
await Promise.all([authDone, sharedDone]);
|
|
10206
|
+
done = true;
|
|
10207
|
+
if (generation === loadGeneration && mutationAtStart !== authMutation) {
|
|
10208
|
+
if (restarts >= 2) throw new Error("Authenticator changed while loading. Retry");
|
|
10209
|
+
return run(restarts + 1);
|
|
10146
10210
|
}
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10211
|
+
const snapshot = build();
|
|
10212
|
+
if (generation === loadGeneration) {
|
|
10213
|
+
defs = localDefs;
|
|
10214
|
+
auth = localAuth;
|
|
10215
|
+
authState = localState;
|
|
10216
|
+
authUnavailable = localUnavailable;
|
|
10217
|
+
inventoryOwners = localOwners;
|
|
10218
|
+
orgs = localOrgs;
|
|
10219
|
+
}
|
|
10220
|
+
return snapshot;
|
|
10221
|
+
};
|
|
10222
|
+
return run(0);
|
|
10151
10223
|
},
|
|
10152
10224
|
async reveal(item, fieldKey2) {
|
|
10153
10225
|
const field = item.fields.find((f) => f.key === fieldKey2);
|