@ai-matrx/vault 0.1.4 → 0.1.6
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 +24 -0
- package/README.md +1 -1
- package/dist/index.cjs +338 -240
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -5
- package/dist/index.d.ts +62 -5
- package/dist/index.js +129 -31
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -147,9 +147,6 @@ interface ItemDraft {
|
|
|
147
147
|
authenticator: string;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
type NoticeKind = "success" | "error" | "info";
|
|
151
|
-
type Notify = (message: string, kind?: NoticeKind) => void;
|
|
152
|
-
|
|
153
150
|
/** One recorded earlier value of a secret field. Value-free: the value comes only from `revealHistory`. */
|
|
154
151
|
interface HistoryEntry {
|
|
155
152
|
fieldId: string;
|
|
@@ -1096,7 +1093,8 @@ interface VaultPort {
|
|
|
1096
1093
|
* What only some hosts can do. A missing capability hides its control; it never shows a dead one.
|
|
1097
1094
|
* - Every host with a signed-in person supplies `identity` (confirm it's you before a secret shows).
|
|
1098
1095
|
* - The browser extension supplies `page` (the tab's address, the logins that match it) and `fill`.
|
|
1099
|
-
* - The desktop app supplies `nativeProvider` (the system credential provider's state)
|
|
1096
|
+
* - The desktop app supplies `nativeProvider` (the system credential provider's state), and its own
|
|
1097
|
+
* `lock`, `clipboard` custody and `revealSeconds`.
|
|
1100
1098
|
*/
|
|
1101
1099
|
interface VaultHostCapabilities {
|
|
1102
1100
|
identity?: VaultIdentity;
|
|
@@ -1114,12 +1112,71 @@ interface VaultHostCapabilities {
|
|
|
1114
1112
|
/** Desktop: the native passkey importer (file chooser, parsing and keys stay native). */
|
|
1115
1113
|
nativeImport?: NativeImportBridge;
|
|
1116
1114
|
openUrl?: (url: string) => void;
|
|
1115
|
+
/** A lock the host holds (`lock.ts`): while locked the vault shows Unlock and refuses every port call. */
|
|
1116
|
+
lock?: VaultLock;
|
|
1117
|
+
/** The host's clipboard: every copy goes through it; a secret is the host's to clear (`lock.ts`). */
|
|
1118
|
+
clipboard?: VaultClipboard;
|
|
1119
|
+
/** How long a shown value stays shown, in seconds (an organization knob the host reads); default 20. */
|
|
1120
|
+
revealSeconds?: number;
|
|
1117
1121
|
nativeProvider?: {
|
|
1118
1122
|
status: "ready" | "needs_setup" | "unavailable";
|
|
1119
1123
|
openSetup: () => void;
|
|
1120
1124
|
};
|
|
1121
1125
|
}
|
|
1122
1126
|
|
|
1127
|
+
/**
|
|
1128
|
+
* A HOST-HELD LOCK. Some hosts lock the vault themselves (the desktop app: Touch ID, starts locked,
|
|
1129
|
+
* locks after idle, on sleep and on screen lock). The host owns the lock and enforces it on its own
|
|
1130
|
+
* side; the vault shows a locked screen with Unlock, and while locked refuses every port call — a
|
|
1131
|
+
* value never reaches a screen while the host says locked. Without `lock` nothing changes.
|
|
1132
|
+
*/
|
|
1133
|
+
interface VaultLock {
|
|
1134
|
+
/** Locked right now. */
|
|
1135
|
+
locked(): boolean;
|
|
1136
|
+
/** Told on every change; returns the unsubscribe. */
|
|
1137
|
+
subscribe(listener: () => void): () => void;
|
|
1138
|
+
/** Ask the host to unlock (Touch ID, a click…). Rejects with a sentence a person can act on. */
|
|
1139
|
+
unlock(): Promise<void>;
|
|
1140
|
+
/** Lock now. When given, the vault offers "Lock". */
|
|
1141
|
+
lock?: () => void | Promise<void>;
|
|
1142
|
+
/** The unlock button's words ("Unlock with Touch ID"); default "Unlock". */
|
|
1143
|
+
unlockLabel?: string;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* HOST CLIPBOARD CUSTODY. When given, every copy in the vault goes through the host. `secret` asks
|
|
1147
|
+
* the host to take custody: clear it after a while if it is still there, and on lock or quit.
|
|
1148
|
+
*/
|
|
1149
|
+
interface VaultClipboard {
|
|
1150
|
+
copy(text: string, how: {
|
|
1151
|
+
secret: boolean;
|
|
1152
|
+
}): Promise<void>;
|
|
1153
|
+
}
|
|
1154
|
+
declare const LOCKED_MESSAGE = "The vault is locked. Unlock it first.";
|
|
1155
|
+
declare class VaultLockedError extends Error {
|
|
1156
|
+
readonly code = "vault_locked";
|
|
1157
|
+
constructor();
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* The port with every method (areas included) refused while `locked()` says so. The check runs at
|
|
1161
|
+
* call time, so a screen still mounted when the lock lands cannot read a value.
|
|
1162
|
+
*/
|
|
1163
|
+
declare function guardPort(port: VaultPort, locked: () => boolean): VaultPort;
|
|
1164
|
+
|
|
1165
|
+
type NoticeKind = "success" | "error" | "info";
|
|
1166
|
+
type Notify = (message: string, kind?: NoticeKind) => void;
|
|
1167
|
+
interface CopyOptions {
|
|
1168
|
+
/** A secret (a password, a code, a key): the host's clipboard takes custody. Default true. */
|
|
1169
|
+
secret?: boolean;
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Writes `text` and says so (or says why not). Through the host's clipboard when it has one (custody:
|
|
1173
|
+
* the host clears a secret later), else the browser's. Pure over its inputs, so it is tested alone.
|
|
1174
|
+
*/
|
|
1175
|
+
declare function copyValue(text: string, label: string, options: CopyOptions, deps: {
|
|
1176
|
+
clipboard?: VaultClipboard | undefined;
|
|
1177
|
+
notify: Notify;
|
|
1178
|
+
}): Promise<boolean>;
|
|
1179
|
+
|
|
1123
1180
|
type VaultLayout = "auto" | "full" | "split" | "stack";
|
|
1124
1181
|
interface VaultAppProps {
|
|
1125
1182
|
port: VaultPort;
|
|
@@ -1183,4 +1240,4 @@ declare function GenerateButton({ onUse, label, limits, disabled }: {
|
|
|
1183
1240
|
disabled?: boolean;
|
|
1184
1241
|
}): react.JSX.Element | null;
|
|
1185
1242
|
|
|
1186
|
-
export { type ApiVaultPortOptions, CATEGORIES, type CategoryId, DEFAULT_LOCATION, type DefinitionField, type FieldHandling, type FieldKind, GenerateButton, type IdentityAccount, type IdentityMethod, type ItemDraft, type NoticeKind, type Notify, type SaveResult, type SupabaseAuthLike, type TotpReading, type UriMatchMode, type UrlGuess, VaultApp, type VaultAppProps, type VaultAttachment, type VaultAuthenticator, type VaultCustomField, type VaultDefinition, type VaultField, type VaultHostCapabilities, type VaultIdentity, type VaultItem, type VaultItemCapabilities, type VaultKnobs, type VaultLayout, type VaultLocation, type VaultOrganizationRef, type VaultOwner, type VaultPort, type VaultSelection, type VaultSnapshot, type VaultSort, type VaultUserStateDoor, categoryOf, createApiVaultPort, createSupabaseIdentity, createVaultKnobs, inferFromUrl, locationFromParams, locationToParams, matchesQuery, needsRecentSignIn, parseDefinitions, siteOf, sortItems };
|
|
1243
|
+
export { type ApiVaultPortOptions, CATEGORIES, type CategoryId, type CopyOptions, DEFAULT_LOCATION, type DefinitionField, type FieldHandling, type FieldKind, GenerateButton, type IdentityAccount, type IdentityMethod, type ItemDraft, LOCKED_MESSAGE, type NoticeKind, type Notify, type SaveResult, type SupabaseAuthLike, type TotpReading, type UriMatchMode, type UrlGuess, VaultApp, type VaultAppProps, type VaultAttachment, type VaultAuthenticator, type VaultClipboard, type VaultCustomField, type VaultDefinition, type VaultField, type VaultHostCapabilities, type VaultIdentity, type VaultItem, type VaultItemCapabilities, type VaultKnobs, type VaultLayout, type VaultLocation, type VaultLock, VaultLockedError, type VaultOrganizationRef, type VaultOwner, type VaultPort, type VaultSelection, type VaultSnapshot, type VaultSort, type VaultUserStateDoor, categoryOf, copyValue, createApiVaultPort, createSupabaseIdentity, createVaultKnobs, guardPort, inferFromUrl, locationFromParams, locationToParams, matchesQuery, needsRecentSignIn, parseDefinitions, siteOf, sortItems };
|
package/dist/index.d.ts
CHANGED
|
@@ -147,9 +147,6 @@ interface ItemDraft {
|
|
|
147
147
|
authenticator: string;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
type NoticeKind = "success" | "error" | "info";
|
|
151
|
-
type Notify = (message: string, kind?: NoticeKind) => void;
|
|
152
|
-
|
|
153
150
|
/** One recorded earlier value of a secret field. Value-free: the value comes only from `revealHistory`. */
|
|
154
151
|
interface HistoryEntry {
|
|
155
152
|
fieldId: string;
|
|
@@ -1096,7 +1093,8 @@ interface VaultPort {
|
|
|
1096
1093
|
* What only some hosts can do. A missing capability hides its control; it never shows a dead one.
|
|
1097
1094
|
* - Every host with a signed-in person supplies `identity` (confirm it's you before a secret shows).
|
|
1098
1095
|
* - The browser extension supplies `page` (the tab's address, the logins that match it) and `fill`.
|
|
1099
|
-
* - The desktop app supplies `nativeProvider` (the system credential provider's state)
|
|
1096
|
+
* - The desktop app supplies `nativeProvider` (the system credential provider's state), and its own
|
|
1097
|
+
* `lock`, `clipboard` custody and `revealSeconds`.
|
|
1100
1098
|
*/
|
|
1101
1099
|
interface VaultHostCapabilities {
|
|
1102
1100
|
identity?: VaultIdentity;
|
|
@@ -1114,12 +1112,71 @@ interface VaultHostCapabilities {
|
|
|
1114
1112
|
/** Desktop: the native passkey importer (file chooser, parsing and keys stay native). */
|
|
1115
1113
|
nativeImport?: NativeImportBridge;
|
|
1116
1114
|
openUrl?: (url: string) => void;
|
|
1115
|
+
/** A lock the host holds (`lock.ts`): while locked the vault shows Unlock and refuses every port call. */
|
|
1116
|
+
lock?: VaultLock;
|
|
1117
|
+
/** The host's clipboard: every copy goes through it; a secret is the host's to clear (`lock.ts`). */
|
|
1118
|
+
clipboard?: VaultClipboard;
|
|
1119
|
+
/** How long a shown value stays shown, in seconds (an organization knob the host reads); default 20. */
|
|
1120
|
+
revealSeconds?: number;
|
|
1117
1121
|
nativeProvider?: {
|
|
1118
1122
|
status: "ready" | "needs_setup" | "unavailable";
|
|
1119
1123
|
openSetup: () => void;
|
|
1120
1124
|
};
|
|
1121
1125
|
}
|
|
1122
1126
|
|
|
1127
|
+
/**
|
|
1128
|
+
* A HOST-HELD LOCK. Some hosts lock the vault themselves (the desktop app: Touch ID, starts locked,
|
|
1129
|
+
* locks after idle, on sleep and on screen lock). The host owns the lock and enforces it on its own
|
|
1130
|
+
* side; the vault shows a locked screen with Unlock, and while locked refuses every port call — a
|
|
1131
|
+
* value never reaches a screen while the host says locked. Without `lock` nothing changes.
|
|
1132
|
+
*/
|
|
1133
|
+
interface VaultLock {
|
|
1134
|
+
/** Locked right now. */
|
|
1135
|
+
locked(): boolean;
|
|
1136
|
+
/** Told on every change; returns the unsubscribe. */
|
|
1137
|
+
subscribe(listener: () => void): () => void;
|
|
1138
|
+
/** Ask the host to unlock (Touch ID, a click…). Rejects with a sentence a person can act on. */
|
|
1139
|
+
unlock(): Promise<void>;
|
|
1140
|
+
/** Lock now. When given, the vault offers "Lock". */
|
|
1141
|
+
lock?: () => void | Promise<void>;
|
|
1142
|
+
/** The unlock button's words ("Unlock with Touch ID"); default "Unlock". */
|
|
1143
|
+
unlockLabel?: string;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* HOST CLIPBOARD CUSTODY. When given, every copy in the vault goes through the host. `secret` asks
|
|
1147
|
+
* the host to take custody: clear it after a while if it is still there, and on lock or quit.
|
|
1148
|
+
*/
|
|
1149
|
+
interface VaultClipboard {
|
|
1150
|
+
copy(text: string, how: {
|
|
1151
|
+
secret: boolean;
|
|
1152
|
+
}): Promise<void>;
|
|
1153
|
+
}
|
|
1154
|
+
declare const LOCKED_MESSAGE = "The vault is locked. Unlock it first.";
|
|
1155
|
+
declare class VaultLockedError extends Error {
|
|
1156
|
+
readonly code = "vault_locked";
|
|
1157
|
+
constructor();
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* The port with every method (areas included) refused while `locked()` says so. The check runs at
|
|
1161
|
+
* call time, so a screen still mounted when the lock lands cannot read a value.
|
|
1162
|
+
*/
|
|
1163
|
+
declare function guardPort(port: VaultPort, locked: () => boolean): VaultPort;
|
|
1164
|
+
|
|
1165
|
+
type NoticeKind = "success" | "error" | "info";
|
|
1166
|
+
type Notify = (message: string, kind?: NoticeKind) => void;
|
|
1167
|
+
interface CopyOptions {
|
|
1168
|
+
/** A secret (a password, a code, a key): the host's clipboard takes custody. Default true. */
|
|
1169
|
+
secret?: boolean;
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Writes `text` and says so (or says why not). Through the host's clipboard when it has one (custody:
|
|
1173
|
+
* the host clears a secret later), else the browser's. Pure over its inputs, so it is tested alone.
|
|
1174
|
+
*/
|
|
1175
|
+
declare function copyValue(text: string, label: string, options: CopyOptions, deps: {
|
|
1176
|
+
clipboard?: VaultClipboard | undefined;
|
|
1177
|
+
notify: Notify;
|
|
1178
|
+
}): Promise<boolean>;
|
|
1179
|
+
|
|
1123
1180
|
type VaultLayout = "auto" | "full" | "split" | "stack";
|
|
1124
1181
|
interface VaultAppProps {
|
|
1125
1182
|
port: VaultPort;
|
|
@@ -1183,4 +1240,4 @@ declare function GenerateButton({ onUse, label, limits, disabled }: {
|
|
|
1183
1240
|
disabled?: boolean;
|
|
1184
1241
|
}): react.JSX.Element | null;
|
|
1185
1242
|
|
|
1186
|
-
export { type ApiVaultPortOptions, CATEGORIES, type CategoryId, DEFAULT_LOCATION, type DefinitionField, type FieldHandling, type FieldKind, GenerateButton, type IdentityAccount, type IdentityMethod, type ItemDraft, type NoticeKind, type Notify, type SaveResult, type SupabaseAuthLike, type TotpReading, type UriMatchMode, type UrlGuess, VaultApp, type VaultAppProps, type VaultAttachment, type VaultAuthenticator, type VaultCustomField, type VaultDefinition, type VaultField, type VaultHostCapabilities, type VaultIdentity, type VaultItem, type VaultItemCapabilities, type VaultKnobs, type VaultLayout, type VaultLocation, type VaultOrganizationRef, type VaultOwner, type VaultPort, type VaultSelection, type VaultSnapshot, type VaultSort, type VaultUserStateDoor, categoryOf, createApiVaultPort, createSupabaseIdentity, createVaultKnobs, inferFromUrl, locationFromParams, locationToParams, matchesQuery, needsRecentSignIn, parseDefinitions, siteOf, sortItems };
|
|
1243
|
+
export { type ApiVaultPortOptions, CATEGORIES, type CategoryId, type CopyOptions, DEFAULT_LOCATION, type DefinitionField, type FieldHandling, type FieldKind, GenerateButton, type IdentityAccount, type IdentityMethod, type ItemDraft, LOCKED_MESSAGE, type NoticeKind, type Notify, type SaveResult, type SupabaseAuthLike, type TotpReading, type UriMatchMode, type UrlGuess, VaultApp, type VaultAppProps, type VaultAttachment, type VaultAuthenticator, type VaultClipboard, type VaultCustomField, type VaultDefinition, type VaultField, type VaultHostCapabilities, type VaultIdentity, type VaultItem, type VaultItemCapabilities, type VaultKnobs, type VaultLayout, type VaultLocation, type VaultLock, VaultLockedError, type VaultOrganizationRef, type VaultOwner, type VaultPort, type VaultSelection, type VaultSnapshot, type VaultSort, type VaultUserStateDoor, categoryOf, copyValue, createApiVaultPort, createSupabaseIdentity, createVaultKnobs, guardPort, inferFromUrl, locationFromParams, locationToParams, matchesQuery, needsRecentSignIn, parseDefinitions, siteOf, sortItems };
|
package/dist/index.js
CHANGED
|
@@ -115,9 +115,28 @@ var Ctx = createContext(() => void 0);
|
|
|
115
115
|
function useNotify() {
|
|
116
116
|
return useContext(Ctx);
|
|
117
117
|
}
|
|
118
|
+
async function copyValue(text, label, options, deps) {
|
|
119
|
+
const secret = options.secret ?? true;
|
|
120
|
+
if (deps.clipboard) {
|
|
121
|
+
try {
|
|
122
|
+
await deps.clipboard.copy(text, { secret });
|
|
123
|
+
deps.notify(`${label} copied`, "success");
|
|
124
|
+
return true;
|
|
125
|
+
} catch (e) {
|
|
126
|
+
deps.notify(e instanceof Error && e.message ? e.message : `${label} could not be copied`, "error");
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return copyText(text, { successMessage: `${label} copied`, failureMessage: `${label} could not be copied`, notify: (m, k) => deps.notify(m, k === "error" ? "error" : "success") });
|
|
131
|
+
}
|
|
132
|
+
var ClipboardCtx = createContext(void 0);
|
|
133
|
+
function ClipboardProvider({ clipboard, children }) {
|
|
134
|
+
return /* @__PURE__ */ jsx(ClipboardCtx.Provider, { value: clipboard, children });
|
|
135
|
+
}
|
|
118
136
|
function useCopy() {
|
|
119
137
|
const notify = useNotify();
|
|
120
|
-
|
|
138
|
+
const clipboard = useContext(ClipboardCtx);
|
|
139
|
+
return useCallback((text, label, options = {}) => copyValue(text, label, options, { clipboard, notify }), [notify, clipboard]);
|
|
121
140
|
}
|
|
122
141
|
function NoticeProvider({ notify, children }) {
|
|
123
142
|
const [shown, setShown] = useState(null);
|
|
@@ -494,8 +513,16 @@ import { Badge, Button as Button2, Field as Field2, Textarea } from "@ai-matrx/d
|
|
|
494
513
|
import { cn as cn3 } from "@ai-matrx/design-system";
|
|
495
514
|
|
|
496
515
|
// src/hooks.ts
|
|
497
|
-
import { useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState3 } from "react";
|
|
498
|
-
|
|
516
|
+
import { createContext as createContext3, createElement, useCallback as useCallback2, useContext as useContext3, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState3 } from "react";
|
|
517
|
+
var DEFAULT_REVEAL_MS = 2e4;
|
|
518
|
+
var RevealMs = createContext3(DEFAULT_REVEAL_MS);
|
|
519
|
+
function RevealSecondsProvider({ seconds, children }) {
|
|
520
|
+
const ms = seconds !== void 0 && Number.isFinite(seconds) && seconds > 0 ? seconds * 1e3 : DEFAULT_REVEAL_MS;
|
|
521
|
+
return createElement(RevealMs.Provider, { value: ms }, children);
|
|
522
|
+
}
|
|
523
|
+
function useTransientSecret(given) {
|
|
524
|
+
const fromHost = useContext3(RevealMs);
|
|
525
|
+
const ms = given ?? fromHost;
|
|
499
526
|
const [value, setValue] = useState3(null);
|
|
500
527
|
const timer2 = useRef3(null);
|
|
501
528
|
const clear = useCallback2(() => {
|
|
@@ -531,12 +558,12 @@ function useElementWidth(ref) {
|
|
|
531
558
|
}
|
|
532
559
|
|
|
533
560
|
// src/ui.tsx
|
|
534
|
-
import { createContext as
|
|
561
|
+
import { createContext as createContext4, useContext as useContext4, useEffect as useEffect4, useState as useState4 } from "react";
|
|
535
562
|
import { Dialog as Dialog2, DialogContent as DialogContent2, DialogFooter, DialogHeader as DialogHeader2, DialogTitle as DialogTitle2 } from "@ai-matrx/design-system";
|
|
536
563
|
import { cn as cn2 } from "@ai-matrx/design-system";
|
|
537
564
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
538
|
-
var InPanel =
|
|
539
|
-
var Room =
|
|
565
|
+
var InPanel = createContext4(false);
|
|
566
|
+
var Room = createContext4(null);
|
|
540
567
|
var ROOMY_AT = 560;
|
|
541
568
|
function useRoomOf(ref) {
|
|
542
569
|
const [room, setRoom] = useState4(null);
|
|
@@ -553,7 +580,7 @@ function useRoomOf(ref) {
|
|
|
553
580
|
}
|
|
554
581
|
var RoomProvider = Room.Provider;
|
|
555
582
|
function Section({ title, actions, children, className }) {
|
|
556
|
-
const flat =
|
|
583
|
+
const flat = useContext4(InPanel);
|
|
557
584
|
return /* @__PURE__ */ jsxs4("section", { className: cn2("flex flex-col gap-1.5", className), children: [
|
|
558
585
|
title || actions ? /* @__PURE__ */ jsxs4("div", { className: "flex min-h-7 items-center justify-between gap-2 px-1", children: [
|
|
559
586
|
title ? /* @__PURE__ */ jsx4("h3", { className: "text-xs font-medium text-muted-foreground", children: title }) : /* @__PURE__ */ jsx4("span", {}),
|
|
@@ -568,7 +595,7 @@ var ROW_GRID = {
|
|
|
568
595
|
viewport: "grid-cols-[5.75rem_minmax(0,1fr)_auto] gap-x-2 px-3 sm:grid-cols-[9rem_minmax(0,1fr)_auto] sm:gap-x-3 sm:px-4"
|
|
569
596
|
};
|
|
570
597
|
function Row({ label, hint, children, actions, top = false, className }) {
|
|
571
|
-
const room =
|
|
598
|
+
const room = useContext4(Room);
|
|
572
599
|
return /* @__PURE__ */ jsxs4("div", { className: cn2("grid min-h-11 py-1", ROW_GRID[room ?? "viewport"], top ? "items-start" : "items-center", className), children: [
|
|
573
600
|
/* @__PURE__ */ jsxs4("div", { className: cn2("flex min-w-0 flex-col text-xs leading-tight text-muted-foreground", top && "pt-2.5"), children: [
|
|
574
601
|
/* @__PURE__ */ jsx4("span", { className: room === "roomy" ? "truncate" : room === "compact" ? "line-clamp-2 break-words" : "line-clamp-2 break-words sm:truncate", children: label }),
|
|
@@ -610,7 +637,7 @@ function SecretText({ value, multiline }) {
|
|
|
610
637
|
var quietError = (e, fallback) => wasCancelled(e) ? null : e instanceof Error ? e.message : fallback;
|
|
611
638
|
function SecretRow({ port, item, field, extra }) {
|
|
612
639
|
const confirmed = useConfirmed();
|
|
613
|
-
const
|
|
640
|
+
const copyValue2 = useCopy();
|
|
614
641
|
const secret = useTransientSecret();
|
|
615
642
|
const [plain, setPlain] = useState5(null);
|
|
616
643
|
const [busy, setBusy] = useState5(false);
|
|
@@ -651,7 +678,7 @@ function SecretRow({ port, item, field, extra }) {
|
|
|
651
678
|
}
|
|
652
679
|
async function copy() {
|
|
653
680
|
const v = await fetchValue();
|
|
654
|
-
if (v !== null) await
|
|
681
|
+
if (v !== null) await copyValue2(v, field.label, { secret: !visible });
|
|
655
682
|
}
|
|
656
683
|
const multiline = field.kind === "multiline";
|
|
657
684
|
const shown = visible ? plain : secret.value;
|
|
@@ -670,7 +697,7 @@ function SecretRow({ port, item, field, extra }) {
|
|
|
670
697
|
);
|
|
671
698
|
}
|
|
672
699
|
function PlainRow({ label, value, hint, openUrl, extra, multiline = false }) {
|
|
673
|
-
const
|
|
700
|
+
const copyValue2 = useCopy();
|
|
674
701
|
const isUrl = /^https?:\/\//i.test(value);
|
|
675
702
|
return /* @__PURE__ */ jsx5(
|
|
676
703
|
Row,
|
|
@@ -681,7 +708,7 @@ function PlainRow({ label, value, hint, openUrl, extra, multiline = false }) {
|
|
|
681
708
|
actions: /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
682
709
|
extra,
|
|
683
710
|
isUrl && openUrl ? /* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: /* @__PURE__ */ jsx5(ExternalGlyph, {}), "aria-label": `Open ${value}`, onClick: () => openUrl(value) }) : null,
|
|
684
|
-
/* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: /* @__PURE__ */ jsx5(CopyGlyph, {}), "aria-label": `Copy ${typeof label === "string" ? label : "value"}`, onClick: () => void
|
|
711
|
+
/* @__PURE__ */ jsx5(Button2, { variant: "quiet", icon: /* @__PURE__ */ jsx5(CopyGlyph, {}), "aria-label": `Copy ${typeof label === "string" ? label : "value"}`, onClick: () => void copyValue2(value, typeof label === "string" && label ? label : "Value", { secret: false }) })
|
|
685
712
|
] }),
|
|
686
713
|
children: isUrl && openUrl ? (
|
|
687
714
|
// A real link (right-click, copy link address, open in a new tab); a click goes through the host's opener.
|
|
@@ -1327,7 +1354,7 @@ function HistoryPanel({ item, port, arg, onClose, onChanged }) {
|
|
|
1327
1354
|
function HistoryRow({ item, port, page, entry, label, onRestored, onConflict, onError }) {
|
|
1328
1355
|
const confirmed = useConfirmed();
|
|
1329
1356
|
const secret = useTransientSecret();
|
|
1330
|
-
const
|
|
1357
|
+
const copyValue2 = useCopy();
|
|
1331
1358
|
const [busy, setBusy] = useState7(false);
|
|
1332
1359
|
const when = formatDate(entry.recordedAt, true);
|
|
1333
1360
|
async function read() {
|
|
@@ -1350,7 +1377,7 @@ function HistoryRow({ item, port, page, entry, label, onRestored, onConflict, on
|
|
|
1350
1377
|
}
|
|
1351
1378
|
async function copy() {
|
|
1352
1379
|
const v = await read();
|
|
1353
|
-
if (v !== null) await
|
|
1380
|
+
if (v !== null) await copyValue2(v, `Earlier ${label.toLowerCase()}`);
|
|
1354
1381
|
}
|
|
1355
1382
|
async function restore() {
|
|
1356
1383
|
const yes = await confirm({
|
|
@@ -1535,13 +1562,13 @@ function CountdownRing({ remaining, period, loading = false, className }) {
|
|
|
1535
1562
|
}
|
|
1536
1563
|
function useCopied() {
|
|
1537
1564
|
const [copied, setCopied] = useState8(false);
|
|
1538
|
-
const
|
|
1565
|
+
const copyValue2 = useCopy();
|
|
1539
1566
|
const t = useRef6(null);
|
|
1540
1567
|
useEffect8(() => () => void (t.current && clearTimeout(t.current)), []);
|
|
1541
1568
|
return {
|
|
1542
1569
|
copied,
|
|
1543
1570
|
copy: async (code) => {
|
|
1544
|
-
const ok = await
|
|
1571
|
+
const ok = await copyValue2(code, "One-time code");
|
|
1545
1572
|
if (!ok) return;
|
|
1546
1573
|
setCopied(true);
|
|
1547
1574
|
if (t.current) clearTimeout(t.current);
|
|
@@ -2683,7 +2710,7 @@ function ItemSettingsPanel({ item, port, onClose, onChanged, onPanel }) {
|
|
|
2683
2710
|
}
|
|
2684
2711
|
|
|
2685
2712
|
// src/generator/PasswordGenerator.tsx
|
|
2686
|
-
import { createContext as
|
|
2713
|
+
import { createContext as createContext5, useContext as useContext5, useEffect as useEffect12, useRef as useRef10, useState as useState15 } from "react";
|
|
2687
2714
|
import { Popover, PopoverContent, PopoverTrigger, cn as cn7 } from "@ai-matrx/design-system";
|
|
2688
2715
|
import { Button as Button11, NumberStepper, SegmentedControl as SegmentedControl2, Select as Select4, Switch as Switch2 } from "@ai-matrx/design-system/controls";
|
|
2689
2716
|
|
|
@@ -2744,12 +2771,12 @@ function strengthOf(bits) {
|
|
|
2744
2771
|
|
|
2745
2772
|
// src/generator/PasswordGenerator.tsx
|
|
2746
2773
|
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2747
|
-
var LimitsCtx =
|
|
2774
|
+
var LimitsCtx = createContext5(null);
|
|
2748
2775
|
function GeneratorLimitsProvider({ resolve: resolve2, children }) {
|
|
2749
2776
|
return /* @__PURE__ */ jsx14(LimitsCtx.Provider, { value: resolve2 ?? null, children });
|
|
2750
2777
|
}
|
|
2751
2778
|
function useGeneratorLimitsSource() {
|
|
2752
|
-
return
|
|
2779
|
+
return useContext5(LimitsCtx);
|
|
2753
2780
|
}
|
|
2754
2781
|
var MASK3 = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
|
2755
2782
|
function PasswordGenerator({ limits: source, onUse, useLabel = "Use" }) {
|
|
@@ -2759,7 +2786,7 @@ function PasswordGenerator({ limits: source, onUse, useLabel = "Use" }) {
|
|
|
2759
2786
|
const [candidate, setCandidate] = useState15(null);
|
|
2760
2787
|
const [revealed, setRevealed] = useState15(false);
|
|
2761
2788
|
const [note, setNote] = useState15(null);
|
|
2762
|
-
const
|
|
2789
|
+
const copyValue2 = useCopy();
|
|
2763
2790
|
const expiry = useRef10(null);
|
|
2764
2791
|
const drop = () => {
|
|
2765
2792
|
if (expiry.current) clearTimeout(expiry.current);
|
|
@@ -2819,7 +2846,7 @@ function PasswordGenerator({ limits: source, onUse, useLabel = "Use" }) {
|
|
|
2819
2846
|
const strength = candidate ? strengthOf(candidate.bits) : null;
|
|
2820
2847
|
const copy = async () => {
|
|
2821
2848
|
if (!candidate) return;
|
|
2822
|
-
const ok = await
|
|
2849
|
+
const ok = await copyValue2(candidate.value, "Generated password");
|
|
2823
2850
|
setNote(ok ? { tone: "success", text: "Copied. Your clipboard keeps it." } : { tone: "destructive", text: "Couldn\u2019t copy. Reveal it and copy by hand." });
|
|
2824
2851
|
};
|
|
2825
2852
|
const use = () => {
|
|
@@ -2930,6 +2957,45 @@ function importLimitsFrom(resolved) {
|
|
|
2930
2957
|
return out;
|
|
2931
2958
|
}
|
|
2932
2959
|
|
|
2960
|
+
// src/lock.ts
|
|
2961
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
2962
|
+
var LOCKED_MESSAGE = "The vault is locked. Unlock it first.";
|
|
2963
|
+
var VaultLockedError = class extends Error {
|
|
2964
|
+
code = "vault_locked";
|
|
2965
|
+
constructor() {
|
|
2966
|
+
super(LOCKED_MESSAGE);
|
|
2967
|
+
this.name = "VaultLockedError";
|
|
2968
|
+
}
|
|
2969
|
+
};
|
|
2970
|
+
function guardPort(port, locked) {
|
|
2971
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
2972
|
+
const wrap = (target) => {
|
|
2973
|
+
const hit = cache.get(target);
|
|
2974
|
+
if (hit) return hit;
|
|
2975
|
+
const proxy = new Proxy(target, {
|
|
2976
|
+
get(obj2, key, receiver) {
|
|
2977
|
+
const value = Reflect.get(obj2, key, receiver);
|
|
2978
|
+
if (typeof value === "function") {
|
|
2979
|
+
return (...args) => {
|
|
2980
|
+
if (locked()) return Promise.reject(new VaultLockedError());
|
|
2981
|
+
return value.apply(obj2, args);
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
if (value && typeof value === "object") return wrap(value);
|
|
2985
|
+
return value;
|
|
2986
|
+
}
|
|
2987
|
+
});
|
|
2988
|
+
cache.set(target, proxy);
|
|
2989
|
+
return proxy;
|
|
2990
|
+
};
|
|
2991
|
+
return wrap(port);
|
|
2992
|
+
}
|
|
2993
|
+
var NEVER = () => () => void 0;
|
|
2994
|
+
var UNLOCKED = () => false;
|
|
2995
|
+
function useLocked(lock) {
|
|
2996
|
+
return useSyncExternalStore2(lock ? lock.subscribe : NEVER, lock ? () => lock.locked() : UNLOCKED, UNLOCKED);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2933
2999
|
// src/io/BackupPanel.tsx
|
|
2934
3000
|
import { useEffect as useEffect13, useRef as useRef11, useState as useState17 } from "react";
|
|
2935
3001
|
import { Button as Button13, Field as Field6, SegmentedControl as SegmentedControl3, Switch as Switch3 } from "@ai-matrx/design-system/controls";
|
|
@@ -4806,7 +4872,7 @@ var MASK4 = "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\
|
|
|
4806
4872
|
var say2 = (e, fallback) => e instanceof Error && e.message ? e.message : fallback;
|
|
4807
4873
|
function RecoveryCodesRow({ item, field, port, onChanged }) {
|
|
4808
4874
|
const confirmed = useConfirmed();
|
|
4809
|
-
const
|
|
4875
|
+
const copyValue2 = useCopy();
|
|
4810
4876
|
const secret = useTransientSecret();
|
|
4811
4877
|
const [busy, setBusy] = useState21(false);
|
|
4812
4878
|
const [error, setError] = useState21(null);
|
|
@@ -4894,7 +4960,7 @@ function RecoveryCodesRow({ item, field, port, onChanged }) {
|
|
|
4894
4960
|
children: sealed ? /* @__PURE__ */ jsx21(Badge6, { icon: /* @__PURE__ */ jsx21(LockGlyph, {}), children: "Sealed" }) : codes ? /* @__PURE__ */ jsxs20("ul", { className: "flex flex-col gap-0.5", "aria-label": "Recovery codes", children: [
|
|
4895
4961
|
codes.map((code, i) => /* @__PURE__ */ jsxs20("li", { className: "flex min-h-7 items-center gap-1", children: [
|
|
4896
4962
|
/* @__PURE__ */ jsx21("code", { className: "min-w-0 flex-1 break-all font-mono text-sm text-foreground", children: code }),
|
|
4897
|
-
/* @__PURE__ */ jsx21(Button17, { variant: "quiet", icon: /* @__PURE__ */ jsx21(CopyGlyph, {}), "aria-label": `Copy code ${i + 1}`, onClick: () => void
|
|
4963
|
+
/* @__PURE__ */ jsx21(Button17, { variant: "quiet", icon: /* @__PURE__ */ jsx21(CopyGlyph, {}), "aria-label": `Copy code ${i + 1}`, onClick: () => void copyValue2(code, "Recovery code") }),
|
|
4898
4964
|
canEdit ? /* @__PURE__ */ jsx21(Button17, { variant: "quiet", icon: /* @__PURE__ */ jsx21(CheckGlyph, {}), disabled: busy, onClick: () => void markUsed(i), children: "Mark used" }) : null
|
|
4899
4965
|
] }, `${i}-${code}`)),
|
|
4900
4966
|
error ? /* @__PURE__ */ jsx21("li", { role: "alert", className: "text-xs text-destructive", children: error }) : null
|
|
@@ -5963,7 +6029,7 @@ function ListRow({ item, family, port, on, onSelect, withOwner }) {
|
|
|
5963
6029
|
setBusy(true);
|
|
5964
6030
|
try {
|
|
5965
6031
|
const v = await confirmed(() => port.reveal(item, key));
|
|
5966
|
-
await copy(v, label);
|
|
6032
|
+
await copy(v, label, { secret: key !== user?.key || user.handling !== "visible" });
|
|
5967
6033
|
} catch (e) {
|
|
5968
6034
|
if (!wasCancelled(e)) notify(e instanceof Error ? e.message : `${label} could not be read`, "error");
|
|
5969
6035
|
} finally {
|
|
@@ -7133,16 +7199,16 @@ function Importer({ bridge, readLimits, activeOrganizationId, organizations, onC
|
|
|
7133
7199
|
|
|
7134
7200
|
// src/extension/PageView.tsx
|
|
7135
7201
|
import { useState as useState38 } from "react";
|
|
7136
|
-
import { DropdownMenu as DropdownMenu5, DropdownMenuContent as DropdownMenuContent5, DropdownMenuItem as DropdownMenuItem4, DropdownMenuTrigger as DropdownMenuTrigger5 } from "@ai-matrx/design-system";
|
|
7202
|
+
import { DropdownMenu as DropdownMenu5, DropdownMenuContent as DropdownMenuContent5, DropdownMenuItem as DropdownMenuItem4, DropdownMenuTrigger as DropdownMenuTrigger5, SiteIcon } from "@ai-matrx/design-system";
|
|
7137
7203
|
import { Button as Button32 } from "@ai-matrx/design-system/controls";
|
|
7138
7204
|
|
|
7139
7205
|
// src/extension/CaptureCard.tsx
|
|
7140
|
-
import { useState as useState35, useSyncExternalStore as
|
|
7206
|
+
import { useState as useState35, useSyncExternalStore as useSyncExternalStore3 } from "react";
|
|
7141
7207
|
import { Button as Button29, SearchField as SearchField3 } from "@ai-matrx/design-system/controls";
|
|
7142
7208
|
import { Fragment as Fragment19, jsx as jsx34, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
7143
7209
|
var SEARCH_FROM = 4;
|
|
7144
7210
|
function CaptureCard({ capture, onChanged }) {
|
|
7145
|
-
const prompt =
|
|
7211
|
+
const prompt = useSyncExternalStore3(capture.subscribe, capture.get, capture.get);
|
|
7146
7212
|
const notify = useNotify();
|
|
7147
7213
|
const [busy, setBusy] = useState35(false);
|
|
7148
7214
|
const [q, setQ] = useState35("");
|
|
@@ -7201,12 +7267,12 @@ function CaptureCard({ capture, onChanged }) {
|
|
|
7201
7267
|
}
|
|
7202
7268
|
|
|
7203
7269
|
// src/extension/FillSetupCard.tsx
|
|
7204
|
-
import { useRef as useRef18, useState as useState36, useSyncExternalStore as
|
|
7270
|
+
import { useRef as useRef18, useState as useState36, useSyncExternalStore as useSyncExternalStore4 } from "react";
|
|
7205
7271
|
import { Button as Button30, Field as Field12 } from "@ai-matrx/design-system/controls";
|
|
7206
7272
|
import { jsx as jsx35, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
7207
7273
|
function FillSetupCard({ fillSetup }) {
|
|
7208
7274
|
const notify = useNotify();
|
|
7209
|
-
const state =
|
|
7275
|
+
const state = useSyncExternalStore4(fillSetup.subscribe, fillSetup.get, fillSetup.get);
|
|
7210
7276
|
const box = useRef18(null);
|
|
7211
7277
|
const [busy, setBusy] = useState36(false);
|
|
7212
7278
|
const [error, setError] = useState36(null);
|
|
@@ -7405,7 +7471,7 @@ function PageView({ port, page, fill, capture, fillSetup, newPasswordFields, ite
|
|
|
7405
7471
|
return /* @__PURE__ */ jsx37("div", { className: "flex h-full min-h-0 flex-col overflow-y-auto bg-background text-foreground", children: /* @__PURE__ */ jsxs35("div", { className: "flex flex-col gap-3 px-3 pb-4 pt-3", children: [
|
|
7406
7472
|
header,
|
|
7407
7473
|
/* @__PURE__ */ jsxs35("div", { className: "flex min-h-7 items-center gap-2 px-1", children: [
|
|
7408
|
-
/* @__PURE__ */ jsx37(
|
|
7474
|
+
/* @__PURE__ */ jsx37(SiteIcon, { site: page.url, fallback: GlobeGlyph, className: "size-4" }),
|
|
7409
7475
|
/* @__PURE__ */ jsx37("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", title: page.url, children: site || "No page open" }),
|
|
7410
7476
|
page.refresh ? /* @__PURE__ */ jsx37(Button32, { variant: "quiet", icon: /* @__PURE__ */ jsx37(RefreshGlyph, {}), "aria-label": "Check again", onClick: page.refresh }) : null,
|
|
7411
7477
|
onBrowse ? /* @__PURE__ */ jsx37(Button32, { variant: "quiet", icon: /* @__PURE__ */ jsx37(ListGlyph, {}), onClick: onBrowse, children: "All items" }) : null
|
|
@@ -7641,7 +7707,31 @@ function ImportWithLimits(props) {
|
|
|
7641
7707
|
return /* @__PURE__ */ jsx39(Panel, { title: "Import", onClose: rest.onClose, children: /* @__PURE__ */ jsx39(Empty, { children: state ? state.error : "Reading this organization\u2019s import settings\u2026" }) });
|
|
7642
7708
|
}
|
|
7643
7709
|
function VaultApp(props) {
|
|
7644
|
-
|
|
7710
|
+
const lock = props.host?.lock;
|
|
7711
|
+
const locked = useLocked(lock);
|
|
7712
|
+
const port = useMemo6(() => lock ? guardPort(props.port, () => lock.locked()) : props.port, [props.port, lock]);
|
|
7713
|
+
return /* @__PURE__ */ jsx39("div", { className: "relative h-full min-h-0 w-full", children: /* @__PURE__ */ jsx39(NoticeProvider, { notify: props.notify, children: lock && locked ? /* @__PURE__ */ jsx39(LockedScreen, { lock }) : /* @__PURE__ */ jsx39(ClipboardProvider, { clipboard: props.host?.clipboard, children: /* @__PURE__ */ jsx39(RevealSecondsProvider, { seconds: props.host?.revealSeconds, children: /* @__PURE__ */ jsx39(IdentityGate, { identity: props.host?.identity, children: /* @__PURE__ */ jsx39(GeneratorLimitsProvider, { resolve: generatorLimitsFrom(props.host?.knobs), children: /* @__PURE__ */ jsx39(Workspace, { ...props, port }) }) }) }) }) }) });
|
|
7714
|
+
}
|
|
7715
|
+
function LockedScreen({ lock }) {
|
|
7716
|
+
const [busy, setBusy] = useState40(false);
|
|
7717
|
+
const [error, setError] = useState40(null);
|
|
7718
|
+
const unlock = async () => {
|
|
7719
|
+
setBusy(true);
|
|
7720
|
+
setError(null);
|
|
7721
|
+
try {
|
|
7722
|
+
await lock.unlock();
|
|
7723
|
+
} catch (e) {
|
|
7724
|
+
setError(e instanceof Error && e.message ? e.message : "The vault stayed locked.");
|
|
7725
|
+
} finally {
|
|
7726
|
+
setBusy(false);
|
|
7727
|
+
}
|
|
7728
|
+
};
|
|
7729
|
+
return /* @__PURE__ */ jsxs37("div", { role: "region", "aria-label": "Vault locked", className: "flex h-full min-h-0 flex-col items-center justify-center gap-3 p-6 text-center", children: [
|
|
7730
|
+
/* @__PURE__ */ jsx39(LockGlyph, { className: "size-8 text-muted-foreground" }),
|
|
7731
|
+
/* @__PURE__ */ jsx39("div", { className: "text-base font-medium text-foreground", children: "Vault locked" }),
|
|
7732
|
+
/* @__PURE__ */ jsx39(Button33, { variant: "primary", disabled: busy, onClick: () => void unlock(), children: lock.unlockLabel ?? "Unlock" }),
|
|
7733
|
+
error ? /* @__PURE__ */ jsx39("div", { role: "alert", className: "max-w-sm text-sm text-destructive", children: error }) : null
|
|
7734
|
+
] });
|
|
7645
7735
|
}
|
|
7646
7736
|
function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocationChange }) {
|
|
7647
7737
|
const rootRef = useRef19(null);
|
|
@@ -7794,6 +7884,10 @@ function Workspace({ port, host = NO_HOST, layout = "auto", location, onLocation
|
|
|
7794
7884
|
host.nativeProvider && host.nativeProvider.status !== "unavailable" ? /* @__PURE__ */ jsxs37(DropdownMenuItem5, { onSelect: host.nativeProvider.openSetup, children: [
|
|
7795
7885
|
/* @__PURE__ */ jsx39(ShieldGlyph, { className: "size-4" }),
|
|
7796
7886
|
"System AutoFill"
|
|
7887
|
+
] }) : null,
|
|
7888
|
+
host.lock?.lock ? /* @__PURE__ */ jsxs37(DropdownMenuItem5, { onSelect: () => void host.lock?.lock?.(), children: [
|
|
7889
|
+
/* @__PURE__ */ jsx39(LockGlyph, { className: "size-4" }),
|
|
7890
|
+
"Lock"
|
|
7797
7891
|
] }) : null
|
|
7798
7892
|
] })
|
|
7799
7893
|
] })
|
|
@@ -8826,11 +8920,15 @@ export {
|
|
|
8826
8920
|
CATEGORIES,
|
|
8827
8921
|
DEFAULT_LOCATION,
|
|
8828
8922
|
GenerateButton,
|
|
8923
|
+
LOCKED_MESSAGE,
|
|
8829
8924
|
VaultApp,
|
|
8925
|
+
VaultLockedError,
|
|
8830
8926
|
categoryOf,
|
|
8927
|
+
copyValue,
|
|
8831
8928
|
createApiVaultPort,
|
|
8832
8929
|
createSupabaseIdentity,
|
|
8833
8930
|
createVaultKnobs,
|
|
8931
|
+
guardPort,
|
|
8834
8932
|
inferFromUrl,
|
|
8835
8933
|
locationFromParams,
|
|
8836
8934
|
locationToParams,
|