@ai-matrx/vault 0.1.26 → 0.1.28
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 +12 -0
- package/dist/index.cjs +183 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -2
- package/dist/index.d.ts +46 -2
- package/dist/index.js +183 -21
- package/dist/index.js.map +1 -1
- package/dist/io/bitwarden-json.worker.js.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.28
|
|
4
|
+
|
|
5
|
+
- **A website typed as a bare domain always saves.** Every bare domain is sent as `https://…` on create, edit, assign and the item settings call, whether or not the server could reach it (an intranet, a site not live yet) (`normalizeLoginUrls`); the Website box shows the `https://` form after leaving it, and "Not a public website. You can still save it." is a warning, never a block. Guard: `src/review-0-1-28.test.tsx` § 1. Consumer action: none.
|
|
6
|
+
- **Every vault refusal shows the server's reason.** `createApiVaultPort` wraps the host's `Api` once (`withServerReasons`): a 4xx whose sentence is generic ("Invalid request. Please check your input and try again.") is re-worded, on the same error object, with the server's own reason ("Login URL must start with https:// or http://…"), so every action inherits it. Guard: § 1. Consumer action: none.
|
|
7
|
+
- **A restored item turns back on in one press.** Restore still comes back as the server's quarantine (item, fields, codes, browser fill and sandbox use off; shares never recreated). The restore result now offers **Turn these back on**: it turns on the item, its fields, its one-time codes and browser fill (when it has a website), then says what it turned on; sharing and sandbox use stay the person's choice (`RestorePanel turnOn`, `turnBackOn`, `turnedOnSentence`; the item's own Turn on uses the same `turnBackOn`). Not offered when the item was already off before it was deleted. Guard: § 2. Consumer action: none.
|
|
8
|
+
- **The first save of a new item leaves the form clean.** Once create resolves the pane reports clean and Save is off, even while a host's address has not moved to the saved item yet (it used to keep "Unsaved" up and ask "Discard your changes?"); `onDirty` is said when it changes, never re-said for a new callback. Guard: § 3. Consumer action: none.
|
|
9
|
+
- Guard for every row copy ending in "<label> copied" or a failure line (refused clipboard, unreadable value): § 4. Consumer action: none.
|
|
10
|
+
|
|
11
|
+
## 0.1.27
|
|
12
|
+
|
|
13
|
+
- **`emptyReadAlarm`, `holdingsDoorIsAbsent`, `HOLDINGS_DOOR_MIGRATION` and the `HoldingRow`/`Holdings` types are exported from the root** so the web app can drop its whole-file copy (`features/secrets/empty-read.ts`). Consumer action: none.
|
|
14
|
+
|
|
3
15
|
## 0.1.26
|
|
4
16
|
|
|
5
17
|
- **`glyphs.tsx` draws only `SafeGlyph` itself.** The other 46 vault glyphs (`KeyGlyph`, `CardGlyph`, `EyeGlyph`, `QrGlyph`, ...) are aliases of the matching `@ai-matrx/design-system/icons` glyphs, so they now render the exact lucide 1.48 drawing and carry the `lucide-<name>` optical-trim class instead of a hand-set `--matrx-glyph-air-*` style. Every export name and the `Glyph` type are unchanged. Consumer action: none.
|
package/dist/index.cjs
CHANGED
|
@@ -140,6 +140,7 @@ __export(src_exports, {
|
|
|
140
140
|
DEFAULT_LOCATION: () => DEFAULT_LOCATION,
|
|
141
141
|
GenerateButton: () => GenerateButton,
|
|
142
142
|
HIDDEN_ITEM_TAG: () => HIDDEN_ITEM_TAG,
|
|
143
|
+
HOLDINGS_DOOR_MIGRATION: () => HOLDINGS_DOOR_MIGRATION,
|
|
143
144
|
IdentityGate: () => IdentityGate,
|
|
144
145
|
LOCKED_MESSAGE: () => LOCKED_MESSAGE,
|
|
145
146
|
VaultApp: () => VaultApp,
|
|
@@ -149,7 +150,9 @@ __export(src_exports, {
|
|
|
149
150
|
createApiVaultPort: () => createApiVaultPort,
|
|
150
151
|
createSupabaseIdentity: () => createSupabaseIdentity,
|
|
151
152
|
createVaultKnobs: () => createVaultKnobs,
|
|
153
|
+
emptyReadAlarm: () => emptyReadAlarm,
|
|
152
154
|
guardPort: () => guardPort,
|
|
155
|
+
holdingsDoorIsAbsent: () => holdingsDoorIsAbsent,
|
|
153
156
|
inferFromUrl: () => inferFromUrl,
|
|
154
157
|
isHiddenItem: () => isHiddenItem,
|
|
155
158
|
itemIdAt: () => itemIdAt,
|
|
@@ -2660,9 +2663,12 @@ function normalizeLoginUrl(input) {
|
|
|
2660
2663
|
return null;
|
|
2661
2664
|
}
|
|
2662
2665
|
}
|
|
2666
|
+
function normalizeLoginUrls(inputs) {
|
|
2667
|
+
return inputs.map((u) => u.trim()).filter(Boolean).map((u) => normalizeLoginUrl(u) ?? u);
|
|
2668
|
+
}
|
|
2663
2669
|
function describeDestination(r) {
|
|
2664
2670
|
if (r.state === "reachable") return { url: r.normalizedUrl, state: r.state, tone: "success", message: "Website confirmed" };
|
|
2665
|
-
if (r.state === "invalid") return { url: r.normalizedUrl, state: r.state, tone: "
|
|
2671
|
+
if (r.state === "invalid") return { url: r.normalizedUrl, state: r.state, tone: "warning", message: "Not a public website. You can still save it." };
|
|
2666
2672
|
if (r.state === "not_found") return { url: r.normalizedUrl, state: r.state, tone: "warning", message: "Site answered, but this page was not found" };
|
|
2667
2673
|
return { url: r.normalizedUrl, state: r.state, tone: "warning", message: "Couldn\u2019t confirm it. You can still save it." };
|
|
2668
2674
|
}
|
|
@@ -5365,6 +5371,40 @@ function AssignResult({ sent, onAnother, onDone }) {
|
|
|
5365
5371
|
] });
|
|
5366
5372
|
}
|
|
5367
5373
|
|
|
5374
|
+
// src/lists/turn-back-on.ts
|
|
5375
|
+
async function turnBackOn(port, item, fields, options = {}) {
|
|
5376
|
+
const run = options.run ?? ((op) => op());
|
|
5377
|
+
const done = [];
|
|
5378
|
+
if (item.turnedOff) {
|
|
5379
|
+
await run(() => port.fields.updateItem(item, { active: true }));
|
|
5380
|
+
done.push("the item");
|
|
5381
|
+
}
|
|
5382
|
+
let n = 0;
|
|
5383
|
+
for (const f of fields) {
|
|
5384
|
+
if (f.active) continue;
|
|
5385
|
+
await run(() => port.fields.updateField(item, f.id, { active: true }));
|
|
5386
|
+
n += 1;
|
|
5387
|
+
}
|
|
5388
|
+
if (n) done.push(n === 1 ? "1 field" : `${n} fields`);
|
|
5389
|
+
if (fields.some((f) => f.key === OTP_SEED_KEY)) {
|
|
5390
|
+
const codes = await port.authenticator.get(item);
|
|
5391
|
+
if (codes && !codes.enabled) {
|
|
5392
|
+
await run(() => port.authenticator.setEnabled(item, true));
|
|
5393
|
+
done.push("one-time codes");
|
|
5394
|
+
}
|
|
5395
|
+
}
|
|
5396
|
+
if (options.browserFill && !item.browserFillEnabled && item.loginUrls.length > 0) {
|
|
5397
|
+
await run(() => port.fields.updateItem(item, { browserFillEnabled: true }));
|
|
5398
|
+
done.push("browser fill");
|
|
5399
|
+
}
|
|
5400
|
+
return done;
|
|
5401
|
+
}
|
|
5402
|
+
function turnedOnSentence(done) {
|
|
5403
|
+
if (done.length === 0) return "Nothing here was off.";
|
|
5404
|
+
const list = done.length === 1 ? done[0] : `${done.slice(0, -1).join(", ")} and ${done[done.length - 1]}`;
|
|
5405
|
+
return `Turned on ${list}.`;
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5368
5408
|
// src/ItemPane.tsx
|
|
5369
5409
|
var import_icons11 = require("@ai-matrx/design-system/icons");
|
|
5370
5410
|
var import_drafts = require("@ai-matrx/kit/drafts");
|
|
@@ -5978,7 +6018,8 @@ function ItemPane(props) {
|
|
|
5978
6018
|
const [showBrowser, setShowBrowser] = (0, import_react28.useState)(false);
|
|
5979
6019
|
const [error, setError] = (0, import_react28.useState)(null);
|
|
5980
6020
|
const [destinations, setDestinations] = (0, import_react28.useState)({});
|
|
5981
|
-
const
|
|
6021
|
+
const [savedNew, setSavedNew] = (0, import_react28.useState)(false);
|
|
6022
|
+
const dirty = editing && !savedNew && isDirty(base, draft);
|
|
5982
6023
|
const declared = definition?.fields ?? [];
|
|
5983
6024
|
const prev = (0, import_react28.useRef)(null);
|
|
5984
6025
|
const paneRef = (0, import_react28.useRef)(null);
|
|
@@ -6001,8 +6042,11 @@ function ItemPane(props) {
|
|
|
6001
6042
|
setShowTags(false);
|
|
6002
6043
|
setShowDescription(false);
|
|
6003
6044
|
setShowRecovery(false);
|
|
6045
|
+
setSavedNew(false);
|
|
6004
6046
|
}, [item?.id, mode]);
|
|
6005
|
-
(0, import_react28.
|
|
6047
|
+
const onDirtyRef = (0, import_react28.useRef)(onDirty);
|
|
6048
|
+
onDirtyRef.current = onDirty;
|
|
6049
|
+
(0, import_react28.useEffect)(() => onDirtyRef.current(dirty), [dirty]);
|
|
6006
6050
|
const keepKey = draftKey(item?.id ?? null, definition?.key ?? item?.definitionKey ?? null);
|
|
6007
6051
|
(0, import_react28.useEffect)(() => {
|
|
6008
6052
|
if (editing && dirty) (0, import_drafts.keepDraft)(keepKey, keptFieldsOf(draft), { basedOn: item?.updatedAt ?? "new" });
|
|
@@ -6059,6 +6103,8 @@ function ItemPane(props) {
|
|
|
6059
6103
|
const [withCode, n0, enrolled] = await withOtp(r.item, r.notice);
|
|
6060
6104
|
const [next, n] = await storeQueuedFiles(withCode, n0);
|
|
6061
6105
|
(0, import_drafts.dropKeptDraft)(keepKey);
|
|
6106
|
+
setSavedNew(true);
|
|
6107
|
+
onDirty(false);
|
|
6062
6108
|
onSaved(next, true, n, enrolled);
|
|
6063
6109
|
}
|
|
6064
6110
|
} catch (e) {
|
|
@@ -6121,12 +6167,7 @@ function ItemPane(props) {
|
|
|
6121
6167
|
if (!item) return;
|
|
6122
6168
|
setError(null);
|
|
6123
6169
|
try {
|
|
6124
|
-
|
|
6125
|
-
for (const f of fields) await confirmed(() => port.fields.updateField(item, f.id, { active: true }));
|
|
6126
|
-
if (fields.some((f) => f.key === OTP_SEED_KEY)) {
|
|
6127
|
-
const codes = await port.authenticator.get(item);
|
|
6128
|
-
if (codes && !codes.enabled) await confirmed(() => port.authenticator.setEnabled(item, true));
|
|
6129
|
-
}
|
|
6170
|
+
await turnBackOn(port, item, fields, { run: confirmed });
|
|
6130
6171
|
onChanged();
|
|
6131
6172
|
} catch (e) {
|
|
6132
6173
|
if (!wasCancelled(e)) setError(e instanceof Error ? e.message : "It could not be turned on.");
|
|
@@ -6181,7 +6222,7 @@ function ItemPane(props) {
|
|
|
6181
6222
|
"Unsaved"
|
|
6182
6223
|
] }) : null,
|
|
6183
6224
|
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "quiet", disabled: busy, onClick: () => mode === "new" ? onCancelNew() : onMode("view"), children: dirty ? "Discard" : "Cancel" }),
|
|
6184
|
-
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "primary", disabled: busy || sent !== null || draft.name.trim() === "" || mode === "edit" && !dirty || mode === "new" && owner === "person" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(recipient.trim()), onClick: () => void save(), children: mode === "new" && owner === "person" ? "Send" : "Save" })
|
|
6225
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)(import_controls22.Button, { variant: "primary", disabled: busy || savedNew || sent !== null || draft.name.trim() === "" || mode === "edit" && !dirty || mode === "new" && owner === "person" && !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(recipient.trim()), onClick: () => void save(), children: mode === "new" && owner === "person" ? "Send" : "Save" })
|
|
6185
6226
|
] }) : item ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
6186
6227
|
host.page && canUseOnSite(item, host.page.url) ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UseOnSiteButton, { port, item, pageUrl: host.page.url, onChanged: (next) => onChanged(next) }) : null,
|
|
6187
6228
|
host.fill && item.capabilities.canUse ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
@@ -6349,7 +6390,8 @@ function ItemPane(props) {
|
|
|
6349
6390
|
else delete next[i];
|
|
6350
6391
|
return next;
|
|
6351
6392
|
});
|
|
6352
|
-
|
|
6393
|
+
const stored = r?.url || normalizeLoginUrl(u);
|
|
6394
|
+
if (stored && stored !== u) setDraft((d) => ({ ...d, loginUrls: d.loginUrls.map((x, j) => j === i && x === u ? stored : x) }));
|
|
6353
6395
|
}),
|
|
6354
6396
|
onRemove: () => patch({ loginUrls: draft.loginUrls.filter((_, j) => j !== i) }),
|
|
6355
6397
|
removeLabel: "Remove website"
|
|
@@ -6761,8 +6803,16 @@ var import_react30 = require("react");
|
|
|
6761
6803
|
var import_controls24 = require("@ai-matrx/design-system/controls");
|
|
6762
6804
|
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
6763
6805
|
var count = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
|
|
6764
|
-
function RestorePanel({
|
|
6806
|
+
function RestorePanel({
|
|
6807
|
+
lists,
|
|
6808
|
+
item,
|
|
6809
|
+
restore,
|
|
6810
|
+
turnOn,
|
|
6811
|
+
onClose
|
|
6812
|
+
}) {
|
|
6765
6813
|
const [preview, setPreview] = (0, import_react30.useState)(null);
|
|
6814
|
+
const [restored, setRestored] = (0, import_react30.useState)(null);
|
|
6815
|
+
const [turnedOn, setTurnedOn] = (0, import_react30.useState)(null);
|
|
6766
6816
|
const [error, setError] = (0, import_react30.useState)(null);
|
|
6767
6817
|
const [busy, setBusy] = (0, import_react30.useState)(false);
|
|
6768
6818
|
const [loading, setLoading] = (0, import_react30.useState)(Boolean(lists.preview));
|
|
@@ -6791,13 +6841,54 @@ function RestorePanel({ lists, item, restore, onClose }) {
|
|
|
6791
6841
|
setError(null);
|
|
6792
6842
|
try {
|
|
6793
6843
|
const result = await restore(preview?.supported ? preview.deletionId : void 0);
|
|
6794
|
-
if (result)
|
|
6844
|
+
if (!result) return;
|
|
6845
|
+
if (turnOn && !result.alreadyRestored && preview?.supported && !preview.priorWasDisabled) setRestored(result);
|
|
6846
|
+
else onClose();
|
|
6795
6847
|
} catch (e) {
|
|
6796
6848
|
setError(e instanceof Error && e.message ? e.message : "Restore did not complete. Try again.");
|
|
6797
6849
|
} finally {
|
|
6798
6850
|
setBusy(false);
|
|
6799
6851
|
}
|
|
6800
6852
|
};
|
|
6853
|
+
const runTurnOn = async () => {
|
|
6854
|
+
if (!restored || !turnOn) return;
|
|
6855
|
+
setBusy(true);
|
|
6856
|
+
setError(null);
|
|
6857
|
+
try {
|
|
6858
|
+
const said = await turnOn(restored.item);
|
|
6859
|
+
if (said !== null) setTurnedOn(said);
|
|
6860
|
+
} catch (e) {
|
|
6861
|
+
setError(e instanceof Error && e.message ? e.message : "It could not be turned on. Turn it on from the item.");
|
|
6862
|
+
} finally {
|
|
6863
|
+
setBusy(false);
|
|
6864
|
+
}
|
|
6865
|
+
};
|
|
6866
|
+
if (restored) {
|
|
6867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
6868
|
+
Panel,
|
|
6869
|
+
{
|
|
6870
|
+
title: `Restored "${item.name}"`,
|
|
6871
|
+
size: "md",
|
|
6872
|
+
onClose,
|
|
6873
|
+
footer: turnedOn ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_controls24.Button, { variant: "primary", onClick: onClose, children: "Done" }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
|
|
6874
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_controls24.Button, { variant: "quiet", onClick: onClose, children: "Leave off" }),
|
|
6875
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_controls24.Button, { variant: "primary", icon: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(RestoreGlyph, {}), disabled: busy, onClick: () => void runTurnOn(), children: busy ? "Turning on\u2026" : "Turn these back on" })
|
|
6876
|
+
] }),
|
|
6877
|
+
children: [
|
|
6878
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(VaultSection, { title: turnedOn ? "Turned on" : "Back, turned off", children: [
|
|
6879
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Came back", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "text-foreground", children: [
|
|
6880
|
+
count(restored.restoredFields, "field"),
|
|
6881
|
+
", ",
|
|
6882
|
+
count(restored.restoredAttachments, "file")
|
|
6883
|
+
] }) }),
|
|
6884
|
+
turnedOn ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Now on", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { role: "status", className: "text-foreground", children: turnedOn }) }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Turns on", hint: "One press, nothing else changes", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-muted-foreground", children: "The item, its fields, codes and browser fill" }) }),
|
|
6885
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)(VaultRow, { label: "Stays off", hint: "Choose these again yourself", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-muted-foreground", children: "Sharing, sandbox use" }) })
|
|
6886
|
+
] }),
|
|
6887
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { role: "alert", className: "px-1 text-sm text-destructive", children: error }) : null
|
|
6888
|
+
]
|
|
6889
|
+
}
|
|
6890
|
+
);
|
|
6891
|
+
}
|
|
6801
6892
|
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
|
|
6802
6893
|
Panel,
|
|
6803
6894
|
{
|
|
@@ -6838,7 +6929,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
|
|
|
6838
6929
|
setMessage(null);
|
|
6839
6930
|
try {
|
|
6840
6931
|
const r = await confirmed(() => port.lists.restore(item, deletionId));
|
|
6841
|
-
setMessage({ tone: "ok", text: r.alreadyRestored ? `"${item.name}" was already restored.` : `Restored "${item.name}" with ${r.restoredFields} fields and ${r.restoredAttachments} files
|
|
6932
|
+
setMessage({ tone: "ok", text: r.alreadyRestored ? `"${item.name}" was already restored.` : `Restored "${item.name}" with ${r.restoredFields} fields and ${r.restoredAttachments} files, turned off.` });
|
|
6842
6933
|
onChanged();
|
|
6843
6934
|
return r;
|
|
6844
6935
|
} catch (e) {
|
|
@@ -6848,6 +6939,19 @@ function TrashView({ port, deleted, query, header, onChanged }) {
|
|
|
6848
6939
|
setBusyId(null);
|
|
6849
6940
|
}
|
|
6850
6941
|
};
|
|
6942
|
+
const turnOn = async (item) => {
|
|
6943
|
+
try {
|
|
6944
|
+
const done = await turnBackOn(port, item, item.fields, { browserFill: true, run: confirmed });
|
|
6945
|
+
const said = turnedOnSentence(done);
|
|
6946
|
+
setMessage({ tone: "ok", text: `"${item.name}": ${said}` });
|
|
6947
|
+
onChanged();
|
|
6948
|
+
return said;
|
|
6949
|
+
} catch (e) {
|
|
6950
|
+
onChanged();
|
|
6951
|
+
if (wasCancelled(e)) return null;
|
|
6952
|
+
throw e;
|
|
6953
|
+
}
|
|
6954
|
+
};
|
|
6851
6955
|
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-3", children: [
|
|
6852
6956
|
header,
|
|
6853
6957
|
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(VaultSection, { title: "Trash", children: shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Empty, { children: deleted.length === 0 ? "The trash is empty." : "Nothing matches." }) : shown.map((item) => (
|
|
@@ -6861,7 +6965,7 @@ function TrashView({ port, deleted, query, header, onChanged }) {
|
|
|
6861
6965
|
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_controls25.Button, { variant: "outline", icon: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(RestoreGlyph, {}), disabled: busyId !== null || !item.deletion, onClick: () => setRestoring(item), children: "Restore" })
|
|
6862
6966
|
] }, item.id)
|
|
6863
6967
|
)) }),
|
|
6864
|
-
restoring ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(RestorePanel, { lists: port.lists, item: restoring, restore: (deletionId) => restore(restoring, deletionId), onClose: () => setRestoring(null) }) : null,
|
|
6968
|
+
restoring ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(RestorePanel, { lists: port.lists, item: restoring, restore: (deletionId) => restore(restoring, deletionId), turnOn, onClose: () => setRestoring(null) }) : null,
|
|
6865
6969
|
message2 ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("p", { role: "status", className: message2.tone === "error" ? "px-1 text-sm text-destructive" : "px-1 text-sm text-muted-foreground", children: message2.text }) : null
|
|
6866
6970
|
] });
|
|
6867
6971
|
}
|
|
@@ -9126,7 +9230,7 @@ function createFieldsPort(ctx) {
|
|
|
9126
9230
|
const body = { initiation: "user" };
|
|
9127
9231
|
if (patch.uriMatchMode !== void 0) body.uri_match_mode = patch.uriMatchMode;
|
|
9128
9232
|
if (patch.browserFillEnabled !== void 0) body.browser_fill_enabled = patch.browserFillEnabled;
|
|
9129
|
-
if (patch.loginUrls !== void 0) body.login_urls = patch.loginUrls;
|
|
9233
|
+
if (patch.loginUrls !== void 0) body.login_urls = normalizeLoginUrls(patch.loginUrls);
|
|
9130
9234
|
if (patch.active !== void 0) body.status = patch.active ? "active" : "disabled";
|
|
9131
9235
|
await account3.updateItem(api, { path: { item_id: item.id }, body }, ctx.optsFor(item));
|
|
9132
9236
|
return reread(item);
|
|
@@ -9138,6 +9242,33 @@ function createFieldsPort(ctx) {
|
|
|
9138
9242
|
};
|
|
9139
9243
|
}
|
|
9140
9244
|
|
|
9245
|
+
// src/server-reasons.ts
|
|
9246
|
+
var GENERIC = [/please check your input/i, /^\s*request failed\s*\(\d+\)\s*$/i, /^\s*bad request\.?\s*$/i, /^\s*HTTP \d+\s*$/i];
|
|
9247
|
+
var isGeneric = (s) => GENERIC.some((re) => re.test(s));
|
|
9248
|
+
function cleanReason(detail) {
|
|
9249
|
+
const s = detail.replace(/^\s*validation error:\s*/i, "").replace(/^\s*value error,\s*/i, "").trim();
|
|
9250
|
+
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
|
|
9251
|
+
}
|
|
9252
|
+
function serverReason(e) {
|
|
9253
|
+
if (!e || typeof e !== "object") return null;
|
|
9254
|
+
const { status, detail, message: message2 } = e;
|
|
9255
|
+
if (typeof status !== "number" || status < 400 || status >= 500) return null;
|
|
9256
|
+
if (typeof detail !== "string" || !detail.trim() || isGeneric(detail)) return null;
|
|
9257
|
+
const shown = typeof message2 === "string" ? message2 : "";
|
|
9258
|
+
if (shown && !isGeneric(shown)) return null;
|
|
9259
|
+
return cleanReason(detail);
|
|
9260
|
+
}
|
|
9261
|
+
function withReason(e) {
|
|
9262
|
+
const reason = serverReason(e);
|
|
9263
|
+
if (reason) Object.assign(e, { message: reason, userMessage: reason });
|
|
9264
|
+
return e;
|
|
9265
|
+
}
|
|
9266
|
+
function withServerReasons(api) {
|
|
9267
|
+
const wrapped = Object.create(api);
|
|
9268
|
+
wrapped.call = (...args) => api.call(...args).catch((e) => Promise.reject(withReason(e)));
|
|
9269
|
+
return wrapped;
|
|
9270
|
+
}
|
|
9271
|
+
|
|
9141
9272
|
// src/io/api.ts
|
|
9142
9273
|
var account4 = __toESM(require("@ai-matrx/api/account"), 1);
|
|
9143
9274
|
var orgOpts = (organizationId) => organizationId ? { organizationId } : void 0;
|
|
@@ -9410,7 +9541,7 @@ function createSharingPort(ctx, options = {}) {
|
|
|
9410
9541
|
definition_key: definition.key,
|
|
9411
9542
|
provider_key: definition.providerKey,
|
|
9412
9543
|
tags: draft.tags,
|
|
9413
|
-
login_urls: draft.loginUrls,
|
|
9544
|
+
login_urls: normalizeLoginUrls(draft.loginUrls),
|
|
9414
9545
|
notes: draft.notes || null,
|
|
9415
9546
|
generate_field_key: generateFieldKey,
|
|
9416
9547
|
initiation: "user",
|
|
@@ -9607,7 +9738,8 @@ async function retrying(run, tries = 3) {
|
|
|
9607
9738
|
}
|
|
9608
9739
|
}
|
|
9609
9740
|
}
|
|
9610
|
-
function createApiVaultPort(
|
|
9741
|
+
function createApiVaultPort(hostApi, options = {}) {
|
|
9742
|
+
const api = withServerReasons(hostApi);
|
|
9611
9743
|
let defs = /* @__PURE__ */ new Map();
|
|
9612
9744
|
let auth = /* @__PURE__ */ new Map();
|
|
9613
9745
|
let orgs = [];
|
|
@@ -9762,6 +9894,7 @@ function createApiVaultPort(api, options = {}) {
|
|
|
9762
9894
|
const declared = definition.fields;
|
|
9763
9895
|
const encrypted = declared.filter((f) => f.storage === "encrypted" && (draft.values[f.key] ?? "") !== "");
|
|
9764
9896
|
const meta = declared.filter((f) => f.storage === "metadata" && (draft.values[f.key] ?? "") !== "");
|
|
9897
|
+
const loginUrls = normalizeLoginUrls(draft.loginUrls);
|
|
9765
9898
|
const w = await account9.createItem(api, {
|
|
9766
9899
|
body: {
|
|
9767
9900
|
display_name: draft.name.trim(),
|
|
@@ -9769,10 +9902,10 @@ function createApiVaultPort(api, options = {}) {
|
|
|
9769
9902
|
definition_key: definition.key,
|
|
9770
9903
|
provider_key: definition.providerKey,
|
|
9771
9904
|
tags: draft.tags,
|
|
9772
|
-
login_urls:
|
|
9905
|
+
login_urls: loginUrls,
|
|
9773
9906
|
// A login a person saves with its website is offered on that website (the server's own
|
|
9774
9907
|
// default is off, which made "Save a login" on a page answer "No saved login" there).
|
|
9775
|
-
...draft.browser ? { uri_match_mode: draft.browser.uriMatchMode, browser_fill_enabled: draft.browser.browserFillEnabled } :
|
|
9908
|
+
...draft.browser ? { uri_match_mode: draft.browser.uriMatchMode, browser_fill_enabled: draft.browser.browserFillEnabled } : loginUrls.length > 0 ? { browser_fill_enabled: true } : {},
|
|
9776
9909
|
notes: draft.notes || null,
|
|
9777
9910
|
principal: organizationId ? { type: "organization", organization_id: organizationId } : { type: "user" },
|
|
9778
9911
|
initiation: "user",
|
|
@@ -9799,7 +9932,7 @@ function createApiVaultPort(api, options = {}) {
|
|
|
9799
9932
|
else body.description = draft.description.trim();
|
|
9800
9933
|
}
|
|
9801
9934
|
if (JSON.stringify(draft.tags) !== JSON.stringify(item.tags)) body.tags = draft.tags;
|
|
9802
|
-
if (JSON.stringify(draft.loginUrls) !== JSON.stringify(item.loginUrls)) body.login_urls = draft.loginUrls;
|
|
9935
|
+
if (JSON.stringify(draft.loginUrls) !== JSON.stringify(item.loginUrls)) body.login_urls = normalizeLoginUrls(draft.loginUrls);
|
|
9803
9936
|
if (draft.notes !== item.notes) {
|
|
9804
9937
|
if (draft.notes === "") body.clear_notes = true;
|
|
9805
9938
|
else body.notes = draft.notes;
|
|
@@ -9882,4 +10015,33 @@ function vaultInventoryScope(inventory, clicked = null) {
|
|
|
9882
10015
|
function itemIdAt(target) {
|
|
9883
10016
|
return target?.closest("[data-item]")?.getAttribute("data-item") ?? null;
|
|
9884
10017
|
}
|
|
10018
|
+
|
|
10019
|
+
// src/io/empty-read.ts
|
|
10020
|
+
var HOLDINGS_DOOR_MIGRATION = "migrations/campaign/errorshonest_s8_a_credential_says_how_much_it_holds.sql";
|
|
10021
|
+
function holdingsDoorIsAbsent(code) {
|
|
10022
|
+
return code === "PGRST202" || code === "42883";
|
|
10023
|
+
}
|
|
10024
|
+
function staleAlarm(total) {
|
|
10025
|
+
return `Your vault has ${total} ${total === 1 ? "item" : "items"} but none of their fields or files could be read. Either every one of these items is genuinely still empty, or the database refused the read without reporting an error \u2014 the second is a known failure mode of this screen (DD-160). Nothing has been lost: reload, and if the items are still blank, report it rather than re-entering the credentials.`;
|
|
10026
|
+
}
|
|
10027
|
+
function emptyReadAlarm(total, emptyIds, holdings) {
|
|
10028
|
+
if (emptyIds.length === 0) return null;
|
|
10029
|
+
if (holdings.state === "answered") {
|
|
10030
|
+
const asked = new Set(emptyIds);
|
|
10031
|
+
const unread = holdings.rows.filter(
|
|
10032
|
+
(r) => asked.has(r.credential_item_id) && (r.field_count > 0 || r.file_count > 0)
|
|
10033
|
+
);
|
|
10034
|
+
if (unread.length === 0) return null;
|
|
10035
|
+
const n = unread.length;
|
|
10036
|
+
return `${n === 1 ? "One credential" : `${n} credentials`} in this vault ${n === 1 ? "holds" : "hold"} fields or files this screen could not read \u2014 the database refused the read without reporting an error, a known failure mode of this screen (DD-160). Nothing has been lost: reload, and if ${n === 1 ? "it is" : "they are"} still blank, report it rather than re-entering the credentials.`;
|
|
10037
|
+
}
|
|
10038
|
+
if (emptyIds.length < total) return null;
|
|
10039
|
+
if (holdings.state === "absent") {
|
|
10040
|
+
console.warn(
|
|
10041
|
+
`[vault] users.credential_item_holdings is not on this database (${holdings.why}); the empty-read alarm falls back to the whole-vault rule until ${HOLDINGS_DOOR_MIGRATION} is applied.`
|
|
10042
|
+
);
|
|
10043
|
+
return staleAlarm(total);
|
|
10044
|
+
}
|
|
10045
|
+
return `${staleAlarm(total)} (The check that tells an empty credential from an unreadable one did not answer either: ${holdings.why})`;
|
|
10046
|
+
}
|
|
9885
10047
|
//# sourceMappingURL=index.cjs.map
|