@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.cjs
CHANGED
|
@@ -2621,10 +2621,22 @@ function TotpRow({ port, item, onChanged }) {
|
|
|
2621
2621
|
function TotpEditRow({ value, onChange, hasExisting, item, port, onEnrolled, onRecognized }) {
|
|
2622
2622
|
const confirmed = useConfirmed();
|
|
2623
2623
|
const [status, setStatus] = (0, import_react12.useState)(null);
|
|
2624
|
+
const [retryImage, setRetryImage] = (0, import_react12.useState)(null);
|
|
2625
|
+
const latestItem = (0, import_react12.useRef)(item);
|
|
2626
|
+
latestItem.current = item;
|
|
2624
2627
|
const uploadImage = item && port ? async (file) => {
|
|
2628
|
+
setRetryImage(file);
|
|
2625
2629
|
setStatus("Adding from image\u2026");
|
|
2626
2630
|
try {
|
|
2627
|
-
const entry = await confirmed(() =>
|
|
2631
|
+
const entry = await confirmed(async () => {
|
|
2632
|
+
const current = await port.get(item);
|
|
2633
|
+
const visible = latestItem.current;
|
|
2634
|
+
if (!visible || visible.id !== item.id || visible.authenticatorState !== "known" || current.authenticatorState !== "known" || !item.authenticator && (visible.authenticator || current.authenticator)) {
|
|
2635
|
+
throw new Error("Authenticator status unavailable. Retry after refreshing.");
|
|
2636
|
+
}
|
|
2637
|
+
return port.authenticator.enrolQrImage(current, file, file.name);
|
|
2638
|
+
});
|
|
2639
|
+
setRetryImage(null);
|
|
2628
2640
|
setStatus(entry.issuer ? `Added \xB7 ${entry.issuer}` : "Added");
|
|
2629
2641
|
onChange("");
|
|
2630
2642
|
onEnrolled?.({ ...item, authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } });
|
|
@@ -2641,7 +2653,11 @@ function TotpEditRow({ value, onChange, hasExisting, item, port, onEnrolled, onR
|
|
|
2641
2653
|
onChange(v);
|
|
2642
2654
|
},
|
|
2643
2655
|
placeholder: hasExisting ? "Paste a new key to replace" : SETUP_KEY_PLACEHOLDER,
|
|
2644
|
-
status,
|
|
2656
|
+
status: status && retryImage && uploadImage ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
|
|
2657
|
+
status,
|
|
2658
|
+
" ",
|
|
2659
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_controls8.Button, { variant: "quiet", onClick: () => void uploadImage(retryImage), children: "Retry" })
|
|
2660
|
+
] }) : status,
|
|
2645
2661
|
...uploadImage ? { onImage: (f) => void uploadImage(f) } : {},
|
|
2646
2662
|
...onRecognized ? { onRecognized } : {}
|
|
2647
2663
|
}
|
|
@@ -2702,6 +2718,8 @@ function normalizeWebsite(raw) {
|
|
|
2702
2718
|
}
|
|
2703
2719
|
function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChanged, onAdded, onDone }) {
|
|
2704
2720
|
const confirmed = useConfirmed();
|
|
2721
|
+
const itemsRef = (0, import_react13.useRef)(items);
|
|
2722
|
+
itemsRef.current = items;
|
|
2705
2723
|
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" }));
|
|
2706
2724
|
const [input, setInput] = (0, import_react13.useState)("");
|
|
2707
2725
|
const [image, setImage] = (0, import_react13.useState)(null);
|
|
@@ -2730,17 +2748,25 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2730
2748
|
setBusy(true);
|
|
2731
2749
|
setError(null);
|
|
2732
2750
|
try {
|
|
2733
|
-
let item = isNew ? created :
|
|
2751
|
+
let item = isNew ? created : itemsRef.current.find((i) => i.id === target) ?? null;
|
|
2752
|
+
let freshlyCreated = false;
|
|
2753
|
+
if (!isNew && (!item || item.authenticatorState !== "known" || item.authenticator)) throw new Error("Authenticator status unavailable. Retry after refreshing.");
|
|
2734
2754
|
if (!item) {
|
|
2735
2755
|
const def = LOGIN_KEYS.map((k) => definitions.find((d) => d.key === k)).find((d) => Boolean(d));
|
|
2736
2756
|
if (!def) throw new Error("No login type is available.");
|
|
2737
2757
|
const r = await port.create(def, null, { ...draftOf(null), name: name.trim(), loginUrls: websiteUrl ? [websiteUrl] : [] });
|
|
2738
2758
|
item = r.item;
|
|
2759
|
+
freshlyCreated = true;
|
|
2739
2760
|
setCreated(item);
|
|
2740
2761
|
onChanged();
|
|
2741
2762
|
}
|
|
2742
2763
|
const it = item;
|
|
2743
|
-
const entry = await confirmed(() =>
|
|
2764
|
+
const entry = await confirmed(async () => {
|
|
2765
|
+
const latest = freshlyCreated ? it : await port.get(it);
|
|
2766
|
+
const listed = itemsRef.current.find((i) => i.id === it.id);
|
|
2767
|
+
if (!freshlyCreated && (latest.authenticatorState !== "known" || latest.authenticator || !isNew && !listed) || listed && (listed.authenticatorState !== "known" || listed.authenticator)) throw new Error("Authenticator status unavailable. Retry after refreshing.");
|
|
2768
|
+
return image ? port.authenticator.enrolQrImage(latest, image, image.name) : port.authenticator.enrol(latest, input);
|
|
2769
|
+
});
|
|
2744
2770
|
let next = { ...it, authenticatorState: "known", authenticator: { digits: entry.digits, period: entry.period, enabled: entry.enabled } };
|
|
2745
2771
|
const better = authenticatorName(it.name, { issuer: entry.issuer, account: entry.account });
|
|
2746
2772
|
if (!isNew && better !== it.name) {
|
|
@@ -2827,7 +2853,11 @@ function AddOtpPanel({ port, items, definitions, itemId = null, onClose, onChang
|
|
|
2827
2853
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VaultRow, { label: "Website", hint: websiteBad ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-destructive", children: "Not a web address" }) : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_controls9.Field, { 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 }) })
|
|
2828
2854
|
] }) : null
|
|
2829
2855
|
] }),
|
|
2830
|
-
error ? /* @__PURE__ */ (0, import_jsx_runtime13.
|
|
2856
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { className: "px-1 text-sm text-destructive", role: "alert", children: [
|
|
2857
|
+
error,
|
|
2858
|
+
" ",
|
|
2859
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_controls9.Button, { variant: "quiet", onClick: onChanged, children: "Retry" })
|
|
2860
|
+
] }) : null
|
|
2831
2861
|
]
|
|
2832
2862
|
}
|
|
2833
2863
|
);
|
|
@@ -2862,23 +2892,7 @@ function otpGroups(items, entries, query, hidden = /* @__PURE__ */ new Set()) {
|
|
|
2862
2892
|
const rank = (k) => k === "me" ? 0 : k === "shared" ? 2 : 1;
|
|
2863
2893
|
return [...groups.entries()].sort(([a, x], [b, y]) => rank(a) - rank(b) || x.label.localeCompare(y.label)).map(([key, g]) => ({ key, ...g }));
|
|
2864
2894
|
}
|
|
2865
|
-
function
|
|
2866
|
-
if (added.size === 0) return items;
|
|
2867
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2868
|
-
const out = items.map((i) => {
|
|
2869
|
-
seen.add(i.id);
|
|
2870
|
-
const mine = added.get(i.id);
|
|
2871
|
-
return mine && !i.authenticator ? { ...i, authenticator: mine.authenticator, name: mine.name } : i;
|
|
2872
|
-
});
|
|
2873
|
-
for (const [id, a] of added) if (!seen.has(id)) out.push(a);
|
|
2874
|
-
return out;
|
|
2875
|
-
}
|
|
2876
|
-
function AuthenticatorView({ port, host, items: listed, definitions, query, onOpen, onChanged, header }) {
|
|
2877
|
-
const [added, setAdded] = (0, import_react14.useState)(/* @__PURE__ */ new Map());
|
|
2878
|
-
const items = withAdded(listed, added);
|
|
2879
|
-
(0, import_react14.useEffect)(() => {
|
|
2880
|
-
if (added.size > 0 && [...added.keys()].every((id) => listed.some((i) => i.id === id && i.authenticator))) setAdded(/* @__PURE__ */ new Map());
|
|
2881
|
-
}, [listed, added]);
|
|
2895
|
+
function AuthenticatorView({ port, host, items, definitions, query, onOpen, onChanged, header }) {
|
|
2882
2896
|
const [incomplete, setIncomplete] = (0, import_react14.useState)(true);
|
|
2883
2897
|
const [entries, setEntries] = (0, import_react14.useState)(/* @__PURE__ */ new Map());
|
|
2884
2898
|
const [hidden, setHidden] = (0, import_react14.useState)(/* @__PURE__ */ new Set());
|
|
@@ -2902,7 +2916,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2902
2916
|
return () => {
|
|
2903
2917
|
live = false;
|
|
2904
2918
|
};
|
|
2905
|
-
}, [port,
|
|
2919
|
+
}, [port, items]);
|
|
2906
2920
|
const groups = otpGroups(items, entries, query, hidden);
|
|
2907
2921
|
const total = items.filter((i) => i.authenticator && !i.deletion && !hidden.has(i.id)).length;
|
|
2908
2922
|
const familyOf2 = (key) => definitions.find((d) => d.key === key)?.family ?? "generic";
|
|
@@ -2915,7 +2929,7 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2915
2929
|
] }),
|
|
2916
2930
|
total > 0 ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_controls10.Button, { variant: "outline", icon: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PlusGlyph, {}), onClick: () => setAdding(true), children: "Add" }) : null
|
|
2917
2931
|
] }),
|
|
2918
|
-
total > 0 && incomplete ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "px-4 text-sm text-muted-foreground", children: [
|
|
2932
|
+
total > 0 && (incomplete || items.some((i) => i.authenticatorState !== "known")) ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "px-4 text-sm text-muted-foreground", children: [
|
|
2919
2933
|
"Some codes unavailable ",
|
|
2920
2934
|
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_controls10.Button, { variant: "quiet", onClick: onChanged, children: "Retry" })
|
|
2921
2935
|
] }) : null,
|
|
@@ -2955,7 +2969,6 @@ function AuthenticatorView({ port, host, items: listed, definitions, query, onOp
|
|
|
2955
2969
|
definitions,
|
|
2956
2970
|
onClose: () => setAdding(false),
|
|
2957
2971
|
onChanged,
|
|
2958
|
-
onAdded: (item) => setAdded((cur) => new Map(cur).set(item.id, item)),
|
|
2959
2972
|
onDone: (id) => {
|
|
2960
2973
|
setAdding(false);
|
|
2961
2974
|
onOpen(id);
|
|
@@ -6560,6 +6573,10 @@ function confirmDelete(item) {
|
|
|
6560
6573
|
function ItemPane(props) {
|
|
6561
6574
|
const { port, item, definition, mode, host, organizations, defaultOwner, seed, onBack, onMode, onSaved, onDeleted, onDirty, onCancelNew, onPanel, onChanged, favorite, notice, refreshKey } = props;
|
|
6562
6575
|
const confirmed = useConfirmed();
|
|
6576
|
+
const latestItem = (0, import_react30.useRef)(item);
|
|
6577
|
+
latestItem.current = item;
|
|
6578
|
+
const [createdForRetry, setCreatedForRetry] = (0, import_react30.useState)(null);
|
|
6579
|
+
const enrolledForRetry = (0, import_react30.useRef)(null);
|
|
6563
6580
|
const notify = useNotify();
|
|
6564
6581
|
const editing = mode !== "view";
|
|
6565
6582
|
const seedName = seed?.name ?? "";
|
|
@@ -6645,23 +6662,39 @@ function ItemPane(props) {
|
|
|
6645
6662
|
setError(null);
|
|
6646
6663
|
const out = { ...draft, values: Object.fromEntries(changedValues(base, draft)) };
|
|
6647
6664
|
if (out.values[RECOVERY_CODES_KEY] !== void 0) out.values[RECOVERY_CODES_KEY] = parseRecoveryCodes(out.values[RECOVERY_CODES_KEY]).join("\n");
|
|
6648
|
-
const withOtp = async (saved, notice2) => {
|
|
6665
|
+
const withOtp = async (saved, notice2, freshlyCreated = false) => {
|
|
6649
6666
|
const setup = out.authenticator.trim();
|
|
6650
6667
|
if (!setup) return [saved, notice2, false];
|
|
6651
6668
|
try {
|
|
6652
|
-
|
|
6669
|
+
if (enrolledForRetry.current?.itemId !== saved.id || enrolledForRetry.current.setup !== setup) await confirmed(async () => {
|
|
6670
|
+
const current = freshlyCreated ? saved : await port.get(saved);
|
|
6671
|
+
const visible = latestItem.current?.id === saved.id ? latestItem.current : null;
|
|
6672
|
+
const ownEnrollment = enrolledForRetry.current?.itemId === saved.id;
|
|
6673
|
+
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");
|
|
6674
|
+
await port.authenticator.enrol(current, setup);
|
|
6675
|
+
enrolledForRetry.current = { itemId: saved.id, setup };
|
|
6676
|
+
});
|
|
6653
6677
|
return [await port.get(saved), notice2, true];
|
|
6654
6678
|
} catch (e) {
|
|
6679
|
+
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];
|
|
6655
6680
|
const why = wasCancelled(e) ? "it was not confirmed" : e instanceof Error ? e.message : "it was refused";
|
|
6656
6681
|
return [saved, `Saved, but the authenticator was not set up: ${why}. Add it with Edit.`, false];
|
|
6657
6682
|
}
|
|
6658
6683
|
};
|
|
6659
6684
|
try {
|
|
6660
|
-
if (item) {
|
|
6661
|
-
const r = await port.save(item, definition, out);
|
|
6685
|
+
if (item || createdForRetry) {
|
|
6686
|
+
const r = await port.save(item ?? createdForRetry, definition, out);
|
|
6662
6687
|
const [next, n, enrolled] = await withOtp(r.item, r.notice);
|
|
6663
|
-
(
|
|
6664
|
-
|
|
6688
|
+
if (out.authenticator.trim() && !enrolled) {
|
|
6689
|
+
setError(n);
|
|
6690
|
+
onChanged();
|
|
6691
|
+
return;
|
|
6692
|
+
}
|
|
6693
|
+
if (createdForRetry) await finishNew(next, n, enrolled);
|
|
6694
|
+
else {
|
|
6695
|
+
(0, import_drafts.dropKeptDraft)(keepKey);
|
|
6696
|
+
onSaved(next, false, n, enrolled);
|
|
6697
|
+
}
|
|
6665
6698
|
} else if (definition && owner === "person") {
|
|
6666
6699
|
const secretKey = declared.find((f) => f.storage === "encrypted" && f.kind === "secret")?.key ?? null;
|
|
6667
6700
|
const r = await port.sharing.assign({ recipientEmail: recipient.trim(), definition, draft: out, generateFieldKey: generatePrivately ? secretKey : null });
|
|
@@ -6671,12 +6704,14 @@ function ItemPane(props) {
|
|
|
6671
6704
|
setSent(r);
|
|
6672
6705
|
} else if (definition) {
|
|
6673
6706
|
const r = await port.create(definition, owner === "me" ? null : owner, out);
|
|
6674
|
-
|
|
6675
|
-
const [
|
|
6676
|
-
(
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6707
|
+
setCreatedForRetry(r.item);
|
|
6708
|
+
const [withCode, n0, enrolled] = await withOtp(r.item, r.notice, true);
|
|
6709
|
+
if (out.authenticator.trim() && !enrolled) {
|
|
6710
|
+
setError(n0);
|
|
6711
|
+
onChanged();
|
|
6712
|
+
return;
|
|
6713
|
+
}
|
|
6714
|
+
await finishNew(withCode, n0, enrolled);
|
|
6680
6715
|
}
|
|
6681
6716
|
} catch (e) {
|
|
6682
6717
|
setError(e instanceof Error ? e.message : "Saving failed. Nothing was changed.");
|
|
@@ -6684,6 +6719,13 @@ function ItemPane(props) {
|
|
|
6684
6719
|
setBusy(false);
|
|
6685
6720
|
}
|
|
6686
6721
|
}
|
|
6722
|
+
async function finishNew(saved, notice2, enrolled) {
|
|
6723
|
+
const [next, finalNotice] = await storeQueuedFiles(saved, notice2);
|
|
6724
|
+
(0, import_drafts.dropKeptDraft)(keepKey);
|
|
6725
|
+
setSavedNew(true);
|
|
6726
|
+
onDirty(false);
|
|
6727
|
+
onSaved(next, true, finalNotice, enrolled);
|
|
6728
|
+
}
|
|
6687
6729
|
async function storeQueuedFiles(saved, notice2) {
|
|
6688
6730
|
let next = saved;
|
|
6689
6731
|
const failed = [];
|
|
@@ -6865,7 +6907,11 @@ function ItemPane(props) {
|
|
|
6865
6907
|
const filesProps = item ? { item, port, host, definition, organizations, arg: null, onClose: () => void 0, onChanged, onPanel } : null;
|
|
6866
6908
|
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(RoomProvider, { value: room, children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("article", { ref: paneRef, "aria-label": item?.name ?? "New item", className: (0, import_design_system19.cn)(import_controls23.CONTROLS_CONTAINER_CLASS, "flex h-full min-h-0 flex-col bg-background"), children: [
|
|
6867
6909
|
header,
|
|
6868
|
-
error ? /* @__PURE__ */ (0, import_jsx_runtime28.
|
|
6910
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { role: "alert", className: "border-b border-border bg-muted px-5 py-2 text-sm text-destructive", children: [
|
|
6911
|
+
error,
|
|
6912
|
+
" ",
|
|
6913
|
+
error.includes("Retry after refreshing") ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", onClick: () => onChanged(), children: "Retry" }) : null
|
|
6914
|
+
] }) : null,
|
|
6869
6915
|
notice && !editing ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { role: "status", className: "border-b border-border bg-muted px-5 py-2 text-sm text-warning", children: notice }) : null,
|
|
6870
6916
|
item && !editing && (itemOff || offFields.length > 0) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-5 py-2 text-sm", children: [
|
|
6871
6917
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("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` }),
|
|
@@ -6969,10 +7015,11 @@ function ItemPane(props) {
|
|
|
6969
7015
|
i
|
|
6970
7016
|
)),
|
|
6971
7017
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)(AddRow, { label: draft.loginUrls.length === 0 ? "Website" : "", onAdd: () => patch({ loginUrls: [...draft.loginUrls, ""] }), children: draft.loginUrls.length === 0 ? "Add website" : "Add another website" }),
|
|
6972
|
-
|
|
7018
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { hidden: Boolean(item && item.authenticatorState !== "known"), children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TotpEditRow, { value: draft.authenticator, onChange: (v) => patch({ authenticator: v }), hasExisting: Boolean(item?.authenticator || seedField), item, port, onEnrolled: onChanged }) }),
|
|
7019
|
+
item && item.authenticatorState !== "known" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(VaultRow, { label: "Authenticator", children: [
|
|
6973
7020
|
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "text-muted-foreground", children: item.authenticatorState === "pending" ? "Loading" : "Unavailable" }),
|
|
6974
7021
|
item.authenticatorState === "unavailable" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_controls23.Button, { variant: "quiet", onClick: () => onChanged?.(), children: "Retry" }) : null
|
|
6975
|
-
] }),
|
|
7022
|
+
] }) : null,
|
|
6976
7023
|
item && isLogin && !item.authenticator ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(TwoFactorMethodRow, { port, item, onChanged }) : null,
|
|
6977
7024
|
!item && isLogin && draft.authenticator.trim() === "" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(DraftTwoFactorRow, { custom: draft.custom, onCustom: (custom) => patch({ custom }) }) : null,
|
|
6978
7025
|
recoveryEditing ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
@@ -9225,6 +9272,7 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
9225
9272
|
const [refreshing, setRefreshing] = (0, import_react44.useState)(false);
|
|
9226
9273
|
const loadedAt = (0, import_react44.useRef)(0);
|
|
9227
9274
|
const inFlight = (0, import_react44.useRef)(false);
|
|
9275
|
+
const loadGeneration = (0, import_react44.useRef)(0);
|
|
9228
9276
|
const apply = (0, import_react44.useCallback)(
|
|
9229
9277
|
(s) => {
|
|
9230
9278
|
loadedAt.current = Date.now();
|
|
@@ -9241,11 +9289,15 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
9241
9289
|
const refresh = (0, import_react44.useCallback)(() => {
|
|
9242
9290
|
if (inFlight.current) return;
|
|
9243
9291
|
inFlight.current = true;
|
|
9292
|
+
const generation = ++loadGeneration.current;
|
|
9244
9293
|
setRefreshing(true);
|
|
9245
9294
|
port.load().then((s) => {
|
|
9295
|
+
if (generation !== loadGeneration.current) return;
|
|
9246
9296
|
setRefreshError(null);
|
|
9247
9297
|
apply(s);
|
|
9248
|
-
}).catch((e) =>
|
|
9298
|
+
}).catch((e) => {
|
|
9299
|
+
if (generation === loadGeneration.current) setRefreshError(plainLoadError(e instanceof Error && e.message ? e.message : "The vault could not be read."));
|
|
9300
|
+
}).finally(() => {
|
|
9249
9301
|
inFlight.current = false;
|
|
9250
9302
|
setRefreshing(false);
|
|
9251
9303
|
});
|
|
@@ -9253,18 +9305,20 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
9253
9305
|
port.lists.recents?.list().then(setRecents, () => void 0);
|
|
9254
9306
|
}, [port, apply]);
|
|
9255
9307
|
const load = (0, import_react44.useCallback)(() => {
|
|
9308
|
+
const generation = ++loadGeneration.current;
|
|
9256
9309
|
setError(null);
|
|
9257
9310
|
setRefreshError(null);
|
|
9258
9311
|
let finished = false;
|
|
9259
9312
|
port.load((partial) => {
|
|
9260
|
-
if (finished) return;
|
|
9313
|
+
if (finished || generation !== loadGeneration.current) return;
|
|
9261
9314
|
setSnap(partial);
|
|
9262
9315
|
setItems(partial.items);
|
|
9263
9316
|
setDeleted(partial.deleted);
|
|
9264
9317
|
}).then((s) => {
|
|
9265
9318
|
finished = true;
|
|
9266
|
-
apply(s);
|
|
9319
|
+
if (generation === loadGeneration.current) apply(s);
|
|
9267
9320
|
}).catch((e) => {
|
|
9321
|
+
if (generation !== loadGeneration.current) return;
|
|
9268
9322
|
setSignedOut(e?.status === 401);
|
|
9269
9323
|
setError(e instanceof Error ? e.message : "The vault could not be loaded.");
|
|
9270
9324
|
});
|
|
@@ -10473,7 +10527,7 @@ function itemOf(w, owner, defs, auth, state = "known", unavailable = [], invento
|
|
|
10473
10527
|
fields: (w.fields ?? []).filter((f) => f.execution_purpose !== "passkey_private").map((f) => fieldOf(f, def)),
|
|
10474
10528
|
custom: (w.non_secret_fields ?? []).map((c) => ({ key: c["key"] ?? "", label: c["label"] ?? humanize(c["key"] ?? ""), value: c["value"] ?? "" })),
|
|
10475
10529
|
authenticator: a ? { digits: a.digits, period: a.period, enabled: a.enabled } : null,
|
|
10476
|
-
authenticatorState: a ? "known" : state === "pending" ? "pending" : state === "unavailable" || scopeUnavailable(inventoryOwner, unavailable) ? "unavailable" : "known",
|
|
10530
|
+
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",
|
|
10477
10531
|
capabilities: { canUse: cap.can_use ?? false, canEdit: cap.can_edit ?? false, canReveal: cap.can_reveal ?? false, canManage: cap.can_manage ?? false },
|
|
10478
10532
|
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 })),
|
|
10479
10533
|
uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
|
|
@@ -10514,16 +10568,12 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
10514
10568
|
let authUnavailable = [];
|
|
10515
10569
|
let inventoryOwners = /* @__PURE__ */ new Map();
|
|
10516
10570
|
let orgs = [];
|
|
10571
|
+
let loadGeneration = 0;
|
|
10572
|
+
let authMutation = 0;
|
|
10517
10573
|
const orgOf = (id, userId, mine) => {
|
|
10518
10574
|
if (id) return { kind: "organization", label: orgs.find((o) => o.id === id)?.name ?? "Organization", organizationId: id };
|
|
10519
10575
|
return mine || userId ? { kind: "me", label: "Personal", organizationId: null } : { kind: "shared", label: "Shared with me", organizationId: null };
|
|
10520
10576
|
};
|
|
10521
|
-
const ownerInOrgList = (w, via, meId) => {
|
|
10522
|
-
if (w.organization_id) return orgOf(w.organization_id, null, false);
|
|
10523
|
-
if (w.user_id && meId && w.user_id === meId) return { kind: "me", label: "Personal", organizationId: null };
|
|
10524
|
-
if (via) return { kind: "organization", label: via.name, organizationId: via.id, memberShared: { userId: w.user_id ?? null } };
|
|
10525
|
-
return { kind: "shared", label: "Shared with me", organizationId: null };
|
|
10526
|
-
};
|
|
10527
10577
|
async function refetch(id, owner) {
|
|
10528
10578
|
const w = await account9.getItem(api, { path: { item_id: id } });
|
|
10529
10579
|
return itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwners.get(id) ?? owner);
|
|
@@ -10536,6 +10586,7 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
10536
10586
|
definition: (key) => defs.get(key),
|
|
10537
10587
|
organizations: () => orgs,
|
|
10538
10588
|
setAuthenticator: (itemId, entry) => {
|
|
10589
|
+
authMutation++;
|
|
10539
10590
|
const next = new Map(auth);
|
|
10540
10591
|
if (entry) next.set(itemId, entry);
|
|
10541
10592
|
else next.delete(itemId);
|
|
@@ -10553,134 +10604,155 @@ function createApiVaultPort(hostApi, options = {}) {
|
|
|
10553
10604
|
fields: createFieldsPort(ctx),
|
|
10554
10605
|
lists: createListsPort(ctx, options.userState),
|
|
10555
10606
|
async load(onProgress) {
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
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]));
|
|
10578
|
-
const mineItems = raw.mine?.items ?? [];
|
|
10579
|
-
const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
|
|
10580
|
-
const seen = /* @__PURE__ */ new Map();
|
|
10581
|
-
const deleted = /* @__PURE__ */ new Map();
|
|
10582
|
-
const take = (w, owner, inventoryOwner = owner) => {
|
|
10583
|
-
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
|
|
10584
|
-
inventoryOwners.set(w.id, inventoryOwner);
|
|
10585
|
-
const item = itemOf(w, owner, defs, auth, authState, authUnavailable, inventoryOwner);
|
|
10586
|
-
(isDeleted(w) ? deleted : seen).set(w.id, item);
|
|
10607
|
+
const run = async (restarts) => {
|
|
10608
|
+
const generation = ++loadGeneration;
|
|
10609
|
+
const mutationAtStart = authMutation;
|
|
10610
|
+
let localAuth = /* @__PURE__ */ new Map();
|
|
10611
|
+
let localState = "pending";
|
|
10612
|
+
let localUnavailable = [];
|
|
10613
|
+
let localDefs = /* @__PURE__ */ new Map();
|
|
10614
|
+
let localOrgs = [];
|
|
10615
|
+
const localOwners = /* @__PURE__ */ new Map();
|
|
10616
|
+
const catalogP = retrying(() => apps2.getAppCatalogKind(api, { path: { app: "matrx", kind: "credential_definition" } }));
|
|
10617
|
+
const authP = retrying(() => apps2.listAuthenticators(api));
|
|
10618
|
+
const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
|
|
10619
|
+
const sharedP = retrying(() => account9.sharedWithMe(api));
|
|
10620
|
+
const quiet = (p) => void p.catch(() => void 0);
|
|
10621
|
+
[catalogP, authP, userP, sharedP].forEach(quiet);
|
|
10622
|
+
const orgItemsP = retrying(() => account9.organizationItems(api));
|
|
10623
|
+
quiet(orgItemsP);
|
|
10624
|
+
const raw = {
|
|
10625
|
+
orgLists: [],
|
|
10626
|
+
orgsKnown: options.organizations !== void 0,
|
|
10627
|
+
meId: null
|
|
10587
10628
|
};
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
10591
|
-
|
|
10592
|
-
|
|
10593
|
-
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10629
|
+
const unavailable = [];
|
|
10630
|
+
let done = false;
|
|
10631
|
+
const build = () => {
|
|
10632
|
+
const catalog2 = raw.catalog ?? [];
|
|
10633
|
+
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]));
|
|
10634
|
+
const mineItems = raw.mine?.items ?? [];
|
|
10635
|
+
const meId = raw.meId ?? mineItems.find((w) => w.user_id)?.user_id ?? null;
|
|
10636
|
+
const seen = /* @__PURE__ */ new Map();
|
|
10637
|
+
const deleted = /* @__PURE__ */ new Map();
|
|
10638
|
+
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 };
|
|
10639
|
+
const take = (w, owner, inventoryOwner = owner) => {
|
|
10640
|
+
if (seen.has(w.id) || deleted.has(w.id) || isHiddenItem(w)) return;
|
|
10641
|
+
localOwners.set(w.id, inventoryOwner);
|
|
10642
|
+
const item = itemOf(w, owner, localDefs, localAuth, localState, localUnavailable, inventoryOwner);
|
|
10643
|
+
(isDeleted(w) ? deleted : seen).set(w.id, item);
|
|
10644
|
+
};
|
|
10645
|
+
for (const w of mineItems) take(w, localOrgOf(w.organization_id, true));
|
|
10646
|
+
raw.orgLists.forEach((l, i) => {
|
|
10647
|
+
const via = localOrgs[i];
|
|
10648
|
+
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));
|
|
10649
|
+
});
|
|
10650
|
+
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 });
|
|
10651
|
+
const pending = done ? [] : [...raw.orgsKnown ? localOrgs.filter((_, i) => raw.orgLists[i] === void 0).map((o) => o.name) : ["your organizations"], ...raw.shared ? [] : ["Shared with me"]];
|
|
10652
|
+
return {
|
|
10653
|
+
unavailable: [...unavailable],
|
|
10654
|
+
...pending.length > 0 ? { pending } : {},
|
|
10655
|
+
items: [...seen.values()],
|
|
10656
|
+
deleted: [...deleted.values()],
|
|
10657
|
+
organizations: localOrgs,
|
|
10658
|
+
definitions: [...localDefs.values()].sort((a, b) => a.sortOrder - b.sortOrder || a.label.localeCompare(b.label))
|
|
10659
|
+
};
|
|
10602
10660
|
};
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
|
|
10620
|
-
|
|
10621
|
-
|
|
10661
|
+
const progress = () => {
|
|
10662
|
+
if (!done && generation === loadGeneration && mutationAtStart === authMutation && onProgress && raw.catalog && raw.mine) onProgress(build());
|
|
10663
|
+
};
|
|
10664
|
+
const orgReportP = options.organizations ? null : retrying(() => platform.listMyOrganizations(api));
|
|
10665
|
+
if (orgReportP) quiet(orgReportP);
|
|
10666
|
+
const orgListsReady = (async () => {
|
|
10667
|
+
const report = orgReportP ? await orgReportP : null;
|
|
10668
|
+
raw.meId = report?.user_id ?? null;
|
|
10669
|
+
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()];
|
|
10670
|
+
raw.orgLists = localOrgs.map(() => void 0);
|
|
10671
|
+
raw.orgsKnown = true;
|
|
10672
|
+
progress();
|
|
10673
|
+
const one = await orgItemsP.then(
|
|
10674
|
+
(r) => r,
|
|
10675
|
+
(e) => {
|
|
10676
|
+
if (e?.status === 404) {
|
|
10677
|
+
hostProblem("GET /vault/organization-items answered 404; reading each organization vault separately", e);
|
|
10678
|
+
return null;
|
|
10679
|
+
}
|
|
10680
|
+
return "failed";
|
|
10622
10681
|
}
|
|
10623
|
-
|
|
10682
|
+
);
|
|
10683
|
+
if (one) {
|
|
10684
|
+
const byOrg = one === "failed" ? /* @__PURE__ */ new Map() : new Map(one.views.map((v) => [v.organization_id, v.items]));
|
|
10685
|
+
const failed = one === "failed" ? new Set(localOrgs.map((o) => o.id)) : new Set(one.unavailable);
|
|
10686
|
+
const settled = localOrgs.map((o, i) => {
|
|
10687
|
+
const items = byOrg.get(o.id);
|
|
10688
|
+
if (failed.has(o.id) || items === void 0) return Promise.reject(new Error(`${o.name} is unavailable`));
|
|
10689
|
+
raw.orgLists[i] = { items, count: items.length };
|
|
10690
|
+
return Promise.resolve(raw.orgLists[i]);
|
|
10691
|
+
});
|
|
10692
|
+
settled.forEach(quiet);
|
|
10693
|
+
progress();
|
|
10694
|
+
return settled;
|
|
10624
10695
|
}
|
|
10625
|
-
|
|
10626
|
-
|
|
10627
|
-
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
return
|
|
10696
|
+
return localOrgs.map((o, i) => {
|
|
10697
|
+
const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
|
|
10698
|
+
const arrived = p.then((l) => {
|
|
10699
|
+
raw.orgLists[i] = l;
|
|
10700
|
+
progress();
|
|
10701
|
+
return l;
|
|
10702
|
+
});
|
|
10703
|
+
quiet(arrived);
|
|
10704
|
+
return arrived;
|
|
10634
10705
|
});
|
|
10635
|
-
|
|
10706
|
+
})();
|
|
10707
|
+
quiet(orgListsReady);
|
|
10708
|
+
const authDone = authP.then((a) => {
|
|
10709
|
+
localUnavailable = a.unavailable_scopes ?? [];
|
|
10710
|
+
const fresh = new Map((a.entries ?? []).map((e) => [e.credential_item_id, e]));
|
|
10711
|
+
localAuth = fresh;
|
|
10712
|
+
localState = "known";
|
|
10713
|
+
if (localUnavailable.length) unavailable.push("One-time codes");
|
|
10636
10714
|
progress();
|
|
10637
|
-
|
|
10638
|
-
|
|
10639
|
-
|
|
10640
|
-
const p = retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id }));
|
|
10641
|
-
const arrived = p.then((l) => {
|
|
10642
|
-
raw.orgLists[i] = l;
|
|
10643
|
-
progress();
|
|
10644
|
-
return l;
|
|
10645
|
-
});
|
|
10646
|
-
quiet(arrived);
|
|
10647
|
-
return arrived;
|
|
10715
|
+
}, () => {
|
|
10716
|
+
localState = "unavailable";
|
|
10717
|
+
unavailable.push("One-time codes");
|
|
10648
10718
|
});
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
authState = "unavailable";
|
|
10660
|
-
unavailable.push("One-time codes");
|
|
10661
|
-
});
|
|
10662
|
-
const sharedDone = sharedP.then((sh) => {
|
|
10663
|
-
raw.shared = sh;
|
|
10719
|
+
const sharedDone = sharedP.then((sh) => {
|
|
10720
|
+
raw.shared = sh;
|
|
10721
|
+
progress();
|
|
10722
|
+
}, () => {
|
|
10723
|
+
raw.shared = { items: [], count: 0 };
|
|
10724
|
+
unavailable.push("Shared with me");
|
|
10725
|
+
});
|
|
10726
|
+
const [catalog, mine] = await Promise.all([catalogP, userP]);
|
|
10727
|
+
raw.catalog = catalog;
|
|
10728
|
+
raw.mine = mine;
|
|
10664
10729
|
progress();
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
unavailable.push(orgs[i]?.name ?? "An organization");
|
|
10730
|
+
const orgPs = await orgListsReady;
|
|
10731
|
+
const orgSettled = await Promise.allSettled(orgPs);
|
|
10732
|
+
orgSettled.forEach((r, i) => {
|
|
10733
|
+
if (r.status === "rejected") {
|
|
10734
|
+
raw.orgLists[i] = { items: [], count: 0 };
|
|
10735
|
+
unavailable.push(localOrgs[i]?.name ?? "An organization");
|
|
10736
|
+
}
|
|
10737
|
+
});
|
|
10738
|
+
await Promise.all([authDone, sharedDone]);
|
|
10739
|
+
done = true;
|
|
10740
|
+
if (generation === loadGeneration && mutationAtStart !== authMutation) {
|
|
10741
|
+
if (restarts >= 2) throw new Error("Authenticator changed while loading. Retry");
|
|
10742
|
+
return run(restarts + 1);
|
|
10679
10743
|
}
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10744
|
+
const snapshot = build();
|
|
10745
|
+
if (generation === loadGeneration) {
|
|
10746
|
+
defs = localDefs;
|
|
10747
|
+
auth = localAuth;
|
|
10748
|
+
authState = localState;
|
|
10749
|
+
authUnavailable = localUnavailable;
|
|
10750
|
+
inventoryOwners = localOwners;
|
|
10751
|
+
orgs = localOrgs;
|
|
10752
|
+
}
|
|
10753
|
+
return snapshot;
|
|
10754
|
+
};
|
|
10755
|
+
return run(0);
|
|
10684
10756
|
},
|
|
10685
10757
|
async reveal(item, fieldKey2) {
|
|
10686
10758
|
const field = item.fields.find((f) => f.key === fieldKey2);
|