@peers-app/peers-ui 0.27.5 → 0.28.1
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/dist/components/message-logs/tool-access-approval-card.js +2 -1
- package/dist/globals.d.ts +1 -0
- package/dist/screens/groups/group-members.js +3 -2
- package/dist/screens/network-viewer/sync-recovery.js +6 -0
- package/dist/ui-router/routes-loader.d.ts +2 -0
- package/dist/ui-router/routes-loader.js +9 -40
- package/dist/ui-router/runnable-version.d.ts +12 -0
- package/dist/ui-router/runnable-version.js +36 -0
- package/dist/ui-router/ui-loader.js +6 -22
- package/package.json +3 -3
|
@@ -21,6 +21,7 @@ function ToolAccessApprovalCard(props) {
|
|
|
21
21
|
const userContext = await (0, peers_sdk_1.getUserContext)();
|
|
22
22
|
return (0, peers_sdk_1.getCurrentUserAccessLevel)(userContext.getDataContext(approval.dataContextId));
|
|
23
23
|
}, (0, peers_sdk_1.recordAccessLevel)({ accessLevel: 100 }), [approval.dataContextId]);
|
|
24
|
+
const isGroup = (0, hooks_1.usePromise)(async () => !!(await (0, peers_sdk_1.getUserContext)()).getDataContext(approval.dataContextId).groupId, true, [approval.dataContextId]);
|
|
24
25
|
const required = approval.requiredAccessLevel;
|
|
25
26
|
const current = userLevel ?? 100;
|
|
26
27
|
const pending = approval.status === peers_sdk_1.ToolAccessApprovalStatus.pending;
|
|
@@ -53,7 +54,7 @@ function ToolAccessApprovalCard(props) {
|
|
|
53
54
|
setBusy(false);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: "card my-2 bg-body-secondary border-secondary-subtle", "data-testid": "tool-access-approval-card", children: (0, jsx_runtime_1.jsx)("div", { className: "card-body py-2 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-start gap-2", children: [(0, jsx_runtime_1.jsx)("i", { className: `bi ${statusStyle.icon} ${statusStyle.iconClass} mt-1`, "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-grow-1 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "fw-semibold text-body", children: pending ? "Approval required" : `Access ${approval.status}` }), (0, jsx_runtime_1.jsxs)("div", { className: "small text-body", children: [(0, jsx_runtime_1.jsx)("code", { className: "text-body", children: approval.toolName }), " needs", " ", (0, peers_sdk_1.getAccessLevelLabel)(required), " (", required, "). You have ", (0, peers_sdk_1.getAccessLevelLabel)(current), " ", "(", current, ")."] }), argsPreview && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-2 mt-2 p-2 bg-body-tertiary border rounded text-body", style: { whiteSpace: "pre-wrap" }, children: argsPreview })), (0, jsx_runtime_1.jsxs)("div", { className: "text-body-secondary small mt-1", children: ["Expires ", approval.expiresAt.toLocaleTimeString(), expired ? " (expired)" : ""] }), approval.resultSummary && ((0, jsx_runtime_1.jsx)("div", { className: "small text-body mt-1", children: approval.resultSummary })), error && (0, jsx_runtime_1.jsx)("div", { className: "text-danger small mt-1", children: error }), pending && !expired && ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex flex-wrap gap-1 mt-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", disabled: busy, onClick: () => decide("once"), children: "Allow once" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("thread"), children: ["Allow in thread (", GRANT_TTL_MINUTES, " min)"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("context"), children: ["Allow in this context (", GRANT_TTL_MINUTES, " min)"] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger", disabled: busy, onClick: () => decide(undefined, true), children: "Deny" })] }))] })] }) }) }));
|
|
57
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "card my-2 bg-body-secondary border-secondary-subtle", "data-testid": "tool-access-approval-card", children: (0, jsx_runtime_1.jsx)("div", { className: "card-body py-2 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-start gap-2", children: [(0, jsx_runtime_1.jsx)("i", { className: `bi ${statusStyle.icon} ${statusStyle.iconClass} mt-1`, "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-grow-1 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "fw-semibold text-body", children: pending ? "Approval required" : `Access ${approval.status}` }), (0, jsx_runtime_1.jsxs)("div", { className: "small text-body", children: [(0, jsx_runtime_1.jsx)("code", { className: "text-body", children: approval.toolName }), " needs", " ", (0, peers_sdk_1.getAccessLevelLabel)(required), " (", required, "). You have ", (0, peers_sdk_1.getAccessLevelLabel)(current), " ", "(", current, ")."] }), argsPreview && ((0, jsx_runtime_1.jsx)("pre", { className: "small mb-2 mt-2 p-2 bg-body-tertiary border rounded text-body", style: { whiteSpace: "pre-wrap" }, children: argsPreview })), (0, jsx_runtime_1.jsxs)("div", { className: "text-body-secondary small mt-1", children: ["Expires ", approval.expiresAt.toLocaleTimeString(), expired ? " (expired)" : ""] }), approval.resultSummary && ((0, jsx_runtime_1.jsx)("div", { className: "small text-body mt-1", children: approval.resultSummary })), error && (0, jsx_runtime_1.jsx)("div", { className: "text-danger small mt-1", children: error }), pending && !expired && ((0, jsx_runtime_1.jsxs)("div", { className: "d-flex flex-wrap gap-1 mt-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-primary", disabled: busy, onClick: () => decide("once"), children: "Allow once" }), !isGroup && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("thread"), children: ["Allow in thread (", GRANT_TTL_MINUTES, " min)"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-sm btn-outline-secondary", disabled: busy, onClick: () => decide("context"), children: ["Allow in this context (", GRANT_TTL_MINUTES, " min)"] })] })), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-sm btn-outline-danger", disabled: busy, onClick: () => decide(undefined, true), children: "Deny" })] }))] })] }) }) }));
|
|
57
58
|
}
|
|
58
59
|
function meaningfulArgsPreview(preview) {
|
|
59
60
|
const trimmed = preview?.trim();
|
package/dist/globals.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export declare const groups: import("@peers-app/peers-sdk").Observable<{
|
|
|
30
30
|
founderUserId: string;
|
|
31
31
|
disabled?: boolean | undefined;
|
|
32
32
|
iconClassName?: string | undefined;
|
|
33
|
+
signedAt?: number | undefined;
|
|
33
34
|
publicRole?: import("@peers-app/peers-sdk").GroupMemberRole | undefined;
|
|
34
35
|
}[]>;
|
|
35
36
|
/**
|
|
@@ -110,8 +110,9 @@ const GroupMembersUI = (props) => {
|
|
|
110
110
|
try {
|
|
111
111
|
const member = await groupMembersTable.get(memberId);
|
|
112
112
|
if (member) {
|
|
113
|
-
member
|
|
114
|
-
|
|
113
|
+
// Without `publicKey`, signing binds the grant to the member's key as this device knows it.
|
|
114
|
+
const { publicKey: _granted, ...rest } = member;
|
|
115
|
+
await groupMembersTable.signAndSave({ ...rest, role: newRole });
|
|
115
116
|
setRefreshKey((prev) => prev + 1);
|
|
116
117
|
}
|
|
117
118
|
}
|
|
@@ -23,6 +23,9 @@ function statusLabel(entry) {
|
|
|
23
23
|
if (entry.parkedReason === "unresolvable") {
|
|
24
24
|
return "Parked, needs a decision";
|
|
25
25
|
}
|
|
26
|
+
if (entry.parkedReason === "authorization") {
|
|
27
|
+
return "Parked, not authorized";
|
|
28
|
+
}
|
|
26
29
|
return "Parked";
|
|
27
30
|
}
|
|
28
31
|
function statusExplanation(entry) {
|
|
@@ -38,6 +41,9 @@ function statusExplanation(entry) {
|
|
|
38
41
|
if (entry.parkedReason === "unresolvable") {
|
|
39
42
|
return "An automatic fix could not be applied safely — the other record was missing, more than one row matched, or the write failed. The rows were left as they are. Choose an action when you know which version should remain.";
|
|
40
43
|
}
|
|
44
|
+
if (entry.parkedReason === "authorization") {
|
|
45
|
+
return "The change isn't signed by someone allowed to make it — for example, a membership or package release that needs an Admin. This device kept its current row. It checks again on its own whenever the group's members or roles change, so a change that was only waiting for its signer's membership to arrive is written then.";
|
|
46
|
+
}
|
|
41
47
|
return "This entry is waiting and is not being sent to other devices. Retry tries it again. The other actions clear it.";
|
|
42
48
|
}
|
|
43
49
|
/** Show a validation payload as indented JSON when the error ends with one. */
|
|
@@ -4,5 +4,7 @@ import "../ui-defaults";
|
|
|
4
4
|
export type IPackageWithNavs = IPackage & {
|
|
5
5
|
appNavs?: IAppNav[];
|
|
6
6
|
};
|
|
7
|
+
/** Packages with authorized navigation metadata available in the current context. */
|
|
7
8
|
export declare const allPackages: import("@peers-app/peers-sdk").Observable<IPackageWithNavs[]>;
|
|
9
|
+
/** Load package routes and subscribe to changes in the current context. */
|
|
8
10
|
export declare const loadAllRoutes: () => Promise<true | undefined>;
|
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.loadAllRoutes = exports.allPackages = void 0;
|
|
4
4
|
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
5
5
|
require("../ui-defaults");
|
|
6
|
+
const runnable_version_1 = require("./runnable-version");
|
|
7
|
+
/** Packages with authorized navigation metadata available in the current context. */
|
|
6
8
|
exports.allPackages = (0, peers_sdk_1.observable)([]);
|
|
7
9
|
let allRoutesLoaded = false;
|
|
8
10
|
const prefsSubscribed = new Set();
|
|
@@ -34,28 +36,10 @@ function subscribeToPrefs(packageId) {
|
|
|
34
36
|
* Enrich an IPackage with appNavs from the device's active PackageVersion record.
|
|
35
37
|
*/
|
|
36
38
|
async function enrichWithPvAppNavs(pkg) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
40
|
-
await pvar.loadingPromise;
|
|
41
|
-
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
/* use group default */
|
|
45
|
-
}
|
|
46
|
-
if (!pvId)
|
|
47
|
-
return pkg;
|
|
48
|
-
try {
|
|
49
|
-
const pv = await (0, peers_sdk_1.PackageVersions)().get(pvId);
|
|
50
|
-
if (pv?.appNavs) {
|
|
51
|
-
return { ...pkg, appNavs: pv.appNavs };
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
/* PV not found */
|
|
56
|
-
}
|
|
57
|
-
return pkg;
|
|
39
|
+
const appNavs = (await (0, runnable_version_1.resolveRunnableVersion)(pkg))?.pv.appNavs;
|
|
40
|
+
return appNavs ? { ...pkg, appNavs } : pkg;
|
|
58
41
|
}
|
|
42
|
+
/** Load package routes and subscribe to changes in the current context. */
|
|
59
43
|
const loadAllRoutes = async () => {
|
|
60
44
|
if (allRoutesLoaded)
|
|
61
45
|
return;
|
|
@@ -115,30 +99,15 @@ function loadRoutesBundle(pkg, forceRefresh) {
|
|
|
115
99
|
console.log(`waiting for registerPeersUIRoute to be defined on the window object`);
|
|
116
100
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
117
101
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
121
|
-
await pvar.loadingPromise;
|
|
122
|
-
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
/* use group default */
|
|
126
|
-
}
|
|
127
|
-
let routesBundleFileId;
|
|
128
|
-
if (pvId) {
|
|
129
|
-
try {
|
|
130
|
-
const pv = await (0, peers_sdk_1.PackageVersions)().get(pvId);
|
|
131
|
-
routesBundleFileId = pv?.routesBundleFileId;
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
/* no version record */
|
|
135
|
-
}
|
|
136
|
-
}
|
|
102
|
+
const selected = await (0, runnable_version_1.resolveRunnableVersion)(pkg);
|
|
103
|
+
const routesBundleFileId = selected?.pv.routesBundleFileId;
|
|
137
104
|
let bundleCode = "";
|
|
138
105
|
if (routesBundleFileId) {
|
|
139
106
|
bundleCode = await peers_sdk_1.rpcServerCalls.getFileContents(routesBundleFileId);
|
|
140
107
|
}
|
|
141
108
|
if (bundleCode) {
|
|
109
|
+
if (selected?.dataContext.groupId)
|
|
110
|
+
(0, peers_sdk_1.assertPackageBundleIntegrity)(selected.pv, "routes", bundleCode);
|
|
142
111
|
const exportRoutes = (peerRoutes) => {
|
|
143
112
|
const rw = window;
|
|
144
113
|
peerRoutes.routes.forEach((route) => {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type DataContext, type IPackage, type IPackageVersion } from "@peers-app/peers-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* The version of `pkg` this device runs, when it may run here: the device's own selection, else
|
|
4
|
+
* the group's, checked with `assertPackageVersionRunnable`. A refusal is logged and gives
|
|
5
|
+
* undefined, so the package's app links, routes and screens are left out rather than loaded.
|
|
6
|
+
*
|
|
7
|
+
* @param pkg - The package.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveRunnableVersion(pkg: IPackage): Promise<{
|
|
10
|
+
pv: IPackageVersion;
|
|
11
|
+
dataContext: DataContext;
|
|
12
|
+
} | undefined>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRunnableVersion = resolveRunnableVersion;
|
|
4
|
+
const peers_sdk_1 = require("@peers-app/peers-sdk");
|
|
5
|
+
/**
|
|
6
|
+
* The version of `pkg` this device runs, when it may run here: the device's own selection, else
|
|
7
|
+
* the group's, checked with `assertPackageVersionRunnable`. A refusal is logged and gives
|
|
8
|
+
* undefined, so the package's app links, routes and screens are left out rather than loaded.
|
|
9
|
+
*
|
|
10
|
+
* @param pkg - The package.
|
|
11
|
+
*/
|
|
12
|
+
async function resolveRunnableVersion(pkg) {
|
|
13
|
+
let pvId = pkg.activePackageVersionId;
|
|
14
|
+
try {
|
|
15
|
+
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
16
|
+
await pvar.loadingPromise;
|
|
17
|
+
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
/* use group default */
|
|
21
|
+
}
|
|
22
|
+
if (!pvId)
|
|
23
|
+
return undefined;
|
|
24
|
+
try {
|
|
25
|
+
const dataContext = await (0, peers_sdk_1.getDefaultDataContext)();
|
|
26
|
+
const pv = await (0, peers_sdk_1.PackageVersions)(dataContext).get(pvId);
|
|
27
|
+
if (!pv)
|
|
28
|
+
return undefined;
|
|
29
|
+
await (0, peers_sdk_1.assertPackageVersionRunnable)(pkg, pv, dataContext);
|
|
30
|
+
return { pv, dataContext };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.warn(`Not showing ${pkg.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -44,6 +44,7 @@ const globals_1 = require("../globals");
|
|
|
44
44
|
const hooks_1 = require("../hooks");
|
|
45
45
|
require("../ui-defaults");
|
|
46
46
|
const routes_loader_1 = require("./routes-loader");
|
|
47
|
+
const runnable_version_1 = require("./runnable-version");
|
|
47
48
|
const peersUIs = {};
|
|
48
49
|
class UIErrorBoundary extends react_1.Component {
|
|
49
50
|
constructor(props) {
|
|
@@ -182,38 +183,21 @@ const UILoader = (args) => {
|
|
|
182
183
|
return (0, jsx_runtime_1.jsx)(UIAsyncLoader, { peersUIId: peersUIId, props: props }, peersUIId);
|
|
183
184
|
};
|
|
184
185
|
const uiLoadingPromises = {};
|
|
185
|
-
async function resolveUiBundleFileId(pkg) {
|
|
186
|
-
let pvId = pkg.activePackageVersionId;
|
|
187
|
-
try {
|
|
188
|
-
const pvar = (0, peers_sdk_1.packagePrefsVar)(pkg.packageId);
|
|
189
|
-
await pvar.loadingPromise;
|
|
190
|
-
pvId = pvar()?.activePackageVersionId ?? pvId;
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
/* use group default */
|
|
194
|
-
}
|
|
195
|
-
if (!pvId)
|
|
196
|
-
return undefined;
|
|
197
|
-
try {
|
|
198
|
-
const pv = await (0, peers_sdk_1.PackageVersions)().get(pvId);
|
|
199
|
-
return pv?.uiBundleFileId;
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
186
|
function loadUIBundle(pkg, forceRefresh) {
|
|
206
187
|
let importPromise = uiLoadingPromises[pkg.packageId];
|
|
207
188
|
if (!importPromise || forceRefresh) {
|
|
208
189
|
const sTime = Date.now();
|
|
209
190
|
console.log(`loading ui bundle for ${pkg.name}`);
|
|
210
191
|
importPromise = (async () => {
|
|
211
|
-
const
|
|
212
|
-
|
|
192
|
+
const selected = await (0, runnable_version_1.resolveRunnableVersion)(pkg);
|
|
193
|
+
const uiBundleFileId = selected?.pv.uiBundleFileId;
|
|
194
|
+
if (!selected || !uiBundleFileId) {
|
|
213
195
|
return;
|
|
214
196
|
}
|
|
215
197
|
const bundleCode = await peers_sdk_1.rpcServerCalls.getFileContents(uiBundleFileId);
|
|
216
198
|
if (bundleCode) {
|
|
199
|
+
if (selected.dataContext.groupId)
|
|
200
|
+
(0, peers_sdk_1.assertPackageBundleIntegrity)(selected.pv, "ui", bundleCode);
|
|
217
201
|
const exportUIs = (peerUIs) => {
|
|
218
202
|
peerUIs?.uis?.forEach((ui) => {
|
|
219
203
|
peersUIs[ui.peersUIId] = ui;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peers-app/peers-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/peers-app/peers-ui.git"
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"lint:fix": "biome check --write ."
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@peers-app/peers-sdk": "^0.
|
|
39
|
+
"@peers-app/peers-sdk": "^0.28.1",
|
|
40
40
|
"bootstrap": "^5.3.3",
|
|
41
41
|
"react": "^18.0.0",
|
|
42
42
|
"react-dom": "^18.0.0"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@babel/preset-react": "^7.24.1",
|
|
48
48
|
"@babel/preset-typescript": "^7.27.1",
|
|
49
49
|
"@biomejs/biome": "2.3.11",
|
|
50
|
-
"@peers-app/peers-sdk": "0.
|
|
50
|
+
"@peers-app/peers-sdk": "0.28.1",
|
|
51
51
|
"@testing-library/dom": "^10.4.0",
|
|
52
52
|
"@testing-library/jest-dom": "^6.6.3",
|
|
53
53
|
"@testing-library/react": "^16.3.0",
|