@ai-matrx/vault 0.1.12 → 0.1.14

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 CHANGED
@@ -1,6 +1,16 @@
1
1
  # Changelog
2
2
 
3
- ### Unreleased
3
+ ## 0.1.14
4
+
5
+ UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
+ Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/vault/src`.
7
+
8
+ ## 0.1.13
9
+
10
+ - **Turn on also turns the item on.** A restore leaves the whole item disabled (`status: "disabled"`), not only its fields, so a turned-on field still answered "not found". `VaultItem.turnedOff` reads it; every **Turn on** turns the item on first (`fields.updateItem(item, { active: true })` → `status: "active"`), and turning the one-time password on also re-enables its codes. An off item says "This item is turned off" and its rows read Turned off. Guard: `src/restored-item.test.tsx`, `src/fields/fields.test.ts`. Consumer action: none.
11
+ - **A dead session no longer throws a burst of unhandled rejections**: `load()` answers every part it started, so only the one "You've been signed out" shows. Guard: `src/load-dead-session.test.ts`. Consumer action: none.
12
+
13
+ ## 0.1.12
4
14
 
5
15
  - **Restored items are repairable in place.** An item back from Trash says "N fields are turned off" with **Turn on** (all), and each off row has its own **Turn on**; the one-time password reads "Turned off" + Turn on instead of "Set up" (which invited replacing a 2FA secret it still has), and editing it says "Paste a new key to replace". Plain words in settings: "One-time password key" (was "Totp seed"), "Always shown / Hidden / Never shown" (was "Anyone / On request / Sealed"), "Turned on" (was "Active"). Guard: `src/restored-item.test.tsx`. Consumer action: none.
6
16
  - **Continue with Google checks who came back.** `createSupabaseIdentity` remembers the person across the provider redirect (`sessionStorage`, `CONFIRM_RETURN_KEY`) and `VaultIdentity.checkReturn()` (run by `IdentityGate` on load) refuses a different account: it is signed out of THIS device through the new `onDifferentPerson(message)` option (default `auth.signOut({ scope: "local" })`) and a "Different account" dialog says so. Guard: `src/auth/identity.test.ts`, `src/auth/identity-gate.test.tsx`. **Consumer action:** web app: pass `onDifferentPerson` running the app's device-scoped sign-out. Extension and desktop: none (they check in place).
@@ -14,11 +24,6 @@
14
24
  - **Confirm in place.** `VaultIdentity.withProvider` may resolve `"confirmed"` when the host confirms without leaving the page (the desktop's Touch ID); "Confirm it's you" then closes and runs the refused call again — one confirmation, the value appears. Before, the dialog stayed open and the person had to cancel and press reveal again. Labels for `touch_id` ("Touch ID") and `browser`. Guard: `src/auth/identity-gate.test.tsx` (fails on the old gate). **Consumer action:** Matrx Desktop: return `"confirmed"` from `withProvider` once the confirmation succeeded in place. Web app and extension: none.
15
25
  - **`VaultApp look="web"|"desktop"`.** The same screens and features, two skins: `"web"` (default) is the website's and the extension's look, unchanged; `"desktop"` draws the workspace in the desktop standard through `@ai-matrx/design-system`'s `LookScope` — the system font and Apple text styles, AppKit-size controls, System Settings/Passwords grouped boxes (no border, hairlines between rows, bold section titles), the source list's open row in the accent, and every panel as a modal macOS sheet, light and dark. The playground's Desktop host uses it. Guard: `src/look.test.tsx`. **Consumer action:** Matrx Desktop: pass `look="desktop"` to `<VaultApp>` (Account › Vault). Web app and extension: none.
16
26
 
17
- ## 0.1.12
18
-
19
- UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
20
- Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/vault/src`.
21
-
22
27
  ## 0.1.11
23
28
 
24
29
  SOURCE CHANGED WITH NO RELEASE NOTE. This release shipped source changes that nobody described in an Unreleased entry,
package/dist/index.cjs CHANGED
@@ -749,7 +749,7 @@ function SecretRow({ port, item, field, extra, onTurnOn }) {
749
749
  const [error, setError] = (0, import_react5.useState)(null);
750
750
  const sealed = field.handling === "sealed";
751
751
  const visible = field.handling === "visible";
752
- const off = field.active === false;
752
+ const off = field.active === false || item.turnedOff === true;
753
753
  const canSee = !sealed && !off && (visible ? item.capabilities.canUse : item.capabilities.canReveal);
754
754
  (0, import_react5.useEffect)(() => {
755
755
  if (!visible || !canSee) return;
@@ -4150,7 +4150,7 @@ var import_zip2 = require("@zip.js/zip.js");
4150
4150
  var safe = (value) => Number.isSafeInteger(value) && value >= 0;
4151
4151
  var unsafeName = (name) => /[\\\0-\x1F\x7F-\x9F]/.test(name);
4152
4152
  function stopIfCancelled(signal) {
4153
- if (signal?.aborted) throw new Error("The archive import was cancelled.");
4153
+ if (signal?.aborted) throw new Error("The archive import was canceled.");
4154
4154
  }
4155
4155
  function hasWarnings(reader, entry) {
4156
4156
  return Boolean(reader.warnings?.length || entry.warnings?.length);
@@ -4251,7 +4251,7 @@ async function openBoundedZip(file, limits, signal) {
4251
4251
  };
4252
4252
  } catch (error) {
4253
4253
  await reader.close();
4254
- if (signal?.aborted) throw new Error("The archive import was cancelled.");
4254
+ if (signal?.aborted) throw new Error("The archive import was canceled.");
4255
4255
  throw error;
4256
4256
  }
4257
4257
  }
@@ -4290,7 +4290,7 @@ async function readDashlaneCsvArchive(file, limits, signal) {
4290
4290
  return { credentialsText, omittedCsvCount };
4291
4291
  } catch (error) {
4292
4292
  credentialsText = void 0;
4293
- if (signal?.aborted) throw new Error("The Dashlane import was cancelled.");
4293
+ if (signal?.aborted) throw new Error("The Dashlane import was canceled.");
4294
4294
  throw error;
4295
4295
  } finally {
4296
4296
  credentialsText = void 0;
@@ -4425,7 +4425,7 @@ function hasValidStructuredImportFileNotices(value, maxCount) {
4425
4425
  }
4426
4426
 
4427
4427
  // src/io/parse-source.ts
4428
- var cancelled = () => new Error("The import was cancelled.");
4428
+ var cancelled = () => new Error("The import was canceled.");
4429
4429
  function parseStructured(source, file, limits, signal) {
4430
4430
  const descriptor = structuredImportSource(source);
4431
4431
  if (!descriptor) return Promise.reject(new Error("This kind of file cannot be imported."));
@@ -5832,13 +5832,19 @@ function ItemPane(props) {
5832
5832
  }
5833
5833
  }
5834
5834
  const offFields = item ? item.fields.filter((f) => !f.active) : [];
5835
+ const itemOff = item?.turnedOff === true;
5835
5836
  const seedField = item?.fields.find((f) => f.key === OTP_SEED_KEY) ?? null;
5836
5837
  const canTurnOn = Boolean(item && canEdit);
5837
5838
  async function turnOn(fields) {
5838
- if (!item || fields.length === 0) return;
5839
+ if (!item) return;
5839
5840
  setError(null);
5840
5841
  try {
5842
+ if (item.turnedOff) await confirmed(() => port.fields.updateItem(item, { active: true }));
5841
5843
  for (const f of fields) await confirmed(() => port.fields.updateField(item, f.id, { active: true }));
5844
+ if (fields.some((f) => f.key === OTP_SEED_KEY)) {
5845
+ const codes = await port.authenticator.get(item);
5846
+ if (codes && !codes.enabled) await confirmed(() => port.authenticator.setEnabled(item, true));
5847
+ }
5842
5848
  onChanged();
5843
5849
  } catch (e) {
5844
5850
  if (!wasCancelled(e)) setError(e instanceof Error ? e.message : "It could not be turned on.");
@@ -5941,8 +5947,8 @@ function ItemPane(props) {
5941
5947
  header,
5942
5948
  error ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { role: "alert", className: "border-b border-border bg-muted px-5 py-2 text-sm text-destructive", children: error }) : null,
5943
5949
  notice && !editing ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { role: "status", className: "border-b border-border bg-muted px-5 py-2 text-sm text-warning", children: notice }) : null,
5944
- item && !editing && offFields.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-5 py-2 text-sm", children: [
5945
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "min-w-0 flex-1 truncate text-warning", children: offFields.length === 1 ? "1 field is turned off" : `${offFields.length} fields are turned off` }),
5950
+ item && !editing && (itemOff || offFields.length > 0) ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { role: "status", className: "flex items-center gap-3 border-b border-border bg-muted px-5 py-2 text-sm", children: [
5951
+ /* @__PURE__ */ (0, import_jsx_runtime26.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` }),
5946
5952
  canTurnOn ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(TurnOnButton, { label: "all fields", onTurnOn: () => turnOn(offFields) }) : null
5947
5953
  ] }) : null,
5948
5954
  /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mx-auto flex w-full max-w-3xl flex-col gap-5 px-3 py-4 sm:px-6 sm:py-6", children: editing ? /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
@@ -7045,7 +7051,7 @@ var plural2 = (n, one) => `${n} ${one}${n === 1 ? "" : "s"}`;
7045
7051
  function terminalMessage(s) {
7046
7052
  if (s.phase === "completed") return `Imported ${plural2(s.committed, "passkey")}.`;
7047
7053
  if (s.phase === "partial") return `${s.committed} imported; ${s.unsupported + s.failed + s.uncertain + s.not_attempted} need attention.`;
7048
- if (s.phase === "cancelled") return "Import cancelled. Nothing else will be imported.";
7054
+ if (s.phase === "cancelled") return "Import canceled. Nothing else will be imported.";
7049
7055
  if (s.phase === "unavailable") return "Passkey import is unavailable here.";
7050
7056
  if (s.phase === "failed") return "The import did not finish. Recover it before using a new file.";
7051
7057
  return s.message;
@@ -7230,7 +7236,7 @@ function useNativeImport(bridge, stillAllowed, onImported) {
7230
7236
  present(g, { kind: "status", status: result, message: terminalMessage(result) });
7231
7237
  if (!done && ACTIVE_PHASES.has(result.phase)) schedulePoll(active.id);
7232
7238
  } catch (e) {
7233
- keepOperation(g, fail(e, "The import could not be cancelled."));
7239
+ keepOperation(g, fail(e, "The import could not be canceled."));
7234
7240
  } finally {
7235
7241
  if (current(g)) setBusy(false);
7236
7242
  }
@@ -8522,6 +8528,7 @@ function createFieldsPort(ctx) {
8522
8528
  if (patch.uriMatchMode !== void 0) body.uri_match_mode = patch.uriMatchMode;
8523
8529
  if (patch.browserFillEnabled !== void 0) body.browser_fill_enabled = patch.browserFillEnabled;
8524
8530
  if (patch.loginUrls !== void 0) body.login_urls = patch.loginUrls;
8531
+ if (patch.active !== void 0) body.status = patch.active ? "active" : "disabled";
8525
8532
  await account3.updateItem(api, { path: { item_id: item.id }, body }, ctx.optsFor(item));
8526
8533
  return reread(item);
8527
8534
  },
@@ -8934,6 +8941,7 @@ function itemOf(w, owner, defs, auth) {
8934
8941
  uriMatchMode: MATCH_MODES2.find((m) => m === w.uri_match_mode) ?? "host",
8935
8942
  browserFillEnabled: w.browser_fill_enabled ?? true,
8936
8943
  accessMode: w.access_mode ?? null,
8944
+ turnedOff: w.status === "disabled",
8937
8945
  ownerUserId: w.user_id ?? null,
8938
8946
  deletion: recovery?.state === "deleted" ? { deletionId: typeof recovery.deletion_id === "string" ? recovery.deletion_id : null, deletedAt: typeof recovery.deleted_at === "string" ? recovery.deleted_at : null } : null,
8939
8947
  updatedAt: w.updated_at,
@@ -8993,9 +9001,12 @@ function createApiVaultPort(api, options = {}) {
8993
9001
  const authP = retrying(() => apps2.listAuthenticators(api));
8994
9002
  const userP = retrying(() => account9.listItems(api, { query: { principal_type: "user" } }));
8995
9003
  const sharedP = retrying(() => account9.sharedWithMe(api));
9004
+ const quiet = (p) => void p.catch(() => void 0);
9005
+ [catalogP, authP, userP, sharedP].forEach(quiet);
8996
9006
  const org = options.organizations ? null : await retrying(() => platform.listMyOrganizations(api));
8997
9007
  orgs = [...new Map((options.organizations ?? (org?.organizations ?? []).filter((o) => !o.archived_at).map((o) => ({ id: o.id, name: o.name }))).map((o) => [o.id, o])).values()];
8998
9008
  const orgListsP = orgs.map((o) => retrying(() => account9.listItems(api, { query: { principal_type: "organization", organization_id: o.id } }, { organizationId: o.id })));
9009
+ orgListsP.forEach(quiet);
8999
9010
  const [catalog, mine] = await Promise.all([catalogP, userP]);
9000
9011
  const [authSettled, sharedSettled, ...orgSettled] = await Promise.allSettled([authP, sharedP, ...orgListsP]);
9001
9012
  const unavailable = [];