@peers-app/peers-ui 0.25.1 → 0.25.3
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/README.md +2 -1
- package/dist/components/markdown-editor/mention-node.d.ts +4 -0
- package/dist/components/markdown-editor/mention-node.js +4 -1
- package/dist/components/responsive-section-nav.d.ts +34 -0
- package/dist/components/responsive-section-nav.js +19 -0
- package/dist/components/responsive-section-nav.test.d.ts +1 -0
- package/dist/components/responsive-section-nav.test.js +45 -0
- package/dist/components/router.d.ts +2 -3
- package/dist/components/router.js +6 -9
- package/dist/globals-hash.test.d.ts +1 -0
- package/dist/globals-hash.test.js +16 -0
- package/dist/globals.d.ts +19 -0
- package/dist/globals.js +51 -12
- package/dist/hooks/use-host-contract.d.ts +18 -0
- package/dist/hooks/use-host-contract.js +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/peers-services-registration.d.ts +1 -5
- package/dist/peers-services-registration.js +6 -16
- package/dist/root-layout/root-layout-host.js +7 -4
- package/dist/root-layout/root-layout-host.test.js +3 -6
- package/dist/screens/account/account-screen.d.ts +1 -0
- package/dist/screens/account/account-screen.js +29 -53
- package/dist/screens/account/account-screen.test.d.ts +1 -0
- package/dist/screens/account/account-screen.test.js +47 -0
- package/dist/screens/contacts/contact-details.js +2 -23
- package/dist/screens/contacts/contact-list.d.ts +24 -1
- package/dist/screens/contacts/contact-list.js +128 -143
- package/dist/screens/contacts/contact-list.test.d.ts +1 -0
- package/dist/screens/contacts/contact-list.test.js +48 -0
- package/dist/screens/contacts/index.d.ts +0 -2
- package/dist/screens/contacts/index.js +0 -3
- package/dist/screens/device-pairing/device-pairing.js +17 -16
- package/dist/screens/device-pairing/device-pairing.test.js +4 -9
- package/dist/screens/device-pairing/identity-ipc.d.ts +11 -0
- package/dist/screens/device-pairing/identity-ipc.js +20 -0
- package/dist/screens/device-pairing/use-device-pairing-state.d.ts +2 -2
- package/dist/screens/device-pairing/use-device-pairing-state.js +7 -25
- package/dist/screens/devices/devices-list.d.ts +18 -0
- package/dist/screens/devices/devices-list.js +173 -0
- package/dist/screens/devices/devices-list.test.d.ts +1 -0
- package/dist/screens/devices/devices-list.test.js +53 -0
- package/dist/screens/devices/index.d.ts +1 -0
- package/dist/screens/devices/index.js +5 -0
- package/dist/screens/groups/group-details.d.ts +1 -0
- package/dist/screens/groups/group-details.js +4 -63
- package/dist/screens/groups/group-invite-panel.js +2 -2
- package/dist/screens/groups/group-list.d.ts +18 -1
- package/dist/screens/groups/group-list.js +107 -136
- package/dist/screens/groups/group-list.test.d.ts +1 -0
- package/dist/screens/groups/group-list.test.js +38 -0
- package/dist/screens/groups/group-members.d.ts +1 -0
- package/dist/screens/groups/group-members.js +2 -1
- package/dist/screens/groups/index.d.ts +0 -3
- package/dist/screens/groups/index.js +0 -4
- package/dist/screens/identity/identity-routes.d.ts +107 -0
- package/dist/screens/identity/identity-routes.js +245 -0
- package/dist/screens/identity/identity-routes.test.d.ts +1 -0
- package/dist/screens/identity/identity-routes.test.js +51 -0
- package/dist/screens/identity/identity-screen.d.ts +9 -0
- package/dist/screens/identity/identity-screen.js +143 -0
- package/dist/screens/identity/identity-screen.test.d.ts +1 -0
- package/dist/screens/identity/identity-screen.test.js +103 -0
- package/dist/screens/identity/index.d.ts +2 -0
- package/dist/screens/identity/index.js +6 -0
- package/dist/screens/identity/invite-contact-panel.d.ts +4 -0
- package/dist/screens/identity/invite-contact-panel.js +30 -0
- package/dist/screens/identity/profile-invite.d.ts +7 -0
- package/dist/screens/identity/profile-invite.js +62 -0
- package/dist/screens/invites/accept-invite.js +1 -1
- package/dist/screens/invites/activity-view.d.ts +24 -0
- package/dist/screens/invites/activity-view.js +64 -0
- package/dist/screens/invites/activity-view.test.d.ts +1 -0
- package/dist/screens/invites/activity-view.test.js +108 -0
- package/dist/screens/invites/index.d.ts +1 -2
- package/dist/screens/invites/index.js +1 -3
- package/dist/screens/invites/invite-capture.d.ts +0 -2
- package/dist/screens/invites/invite-capture.js +1 -3
- package/dist/screens/invites/invite-classify.test.d.ts +1 -0
- package/dist/screens/invites/invite-classify.test.js +98 -0
- package/dist/screens/invites/invites-ipc.d.ts +1 -1
- package/dist/screens/invites/invites-ipc.js +5 -45
- package/dist/screens/invites/pending-invites-list.d.ts +35 -16
- package/dist/screens/invites/pending-invites-list.js +69 -30
- package/dist/screens/network-viewer/network-viewer-ipc.js +4 -59
- package/dist/screens/search/global-search.d.ts +1 -0
- package/dist/screens/search/global-search.js +2 -1
- package/dist/screens/settings/settings-page.js +25 -58
- package/dist/screens/setup-user.js +5 -5
- package/dist/screens/tools/tool-access-ipc.d.ts +2 -0
- package/dist/screens/tools/tool-access-ipc.js +8 -55
- package/dist/screens/welcome-modal.js +3 -2
- package/dist/system-apps/identity.app.d.ts +3 -0
- package/dist/system-apps/identity.app.js +10 -0
- package/dist/system-apps/index.d.ts +1 -4
- package/dist/system-apps/index.js +5 -17
- package/dist/tabs-layout/network-status-indicator.js +10 -3
- package/dist/tabs-layout/network-status-indicator.test.js +15 -0
- package/dist/tabs-layout/tabs-layout.js +3 -1
- package/dist/tabs-layout/tabs-state.d.ts +79 -1
- package/dist/tabs-layout/tabs-state.js +256 -79
- package/dist/tabs-layout/tabs-state.test.d.ts +1 -0
- package/dist/tabs-layout/tabs-state.test.js +110 -0
- package/package.json +3 -3
- package/src/components/markdown-editor/mention-node.ts +5 -1
- package/src/components/responsive-section-nav.test.tsx +88 -0
- package/src/components/responsive-section-nav.tsx +92 -0
- package/src/components/router.tsx +6 -9
- package/src/globals-hash.test.ts +19 -0
- package/src/globals.tsx +53 -13
- package/src/hooks/use-host-contract.ts +75 -0
- package/src/index.tsx +1 -0
- package/src/peers-services-registration.ts +6 -17
- package/src/root-layout/root-layout-host.test.tsx +4 -7
- package/src/root-layout/root-layout-host.tsx +9 -11
- package/src/screens/account/account-screen.test.tsx +61 -0
- package/src/screens/account/account-screen.tsx +116 -95
- package/src/screens/contacts/contact-details.tsx +4 -26
- package/src/screens/contacts/contact-list.test.tsx +59 -0
- package/src/screens/contacts/contact-list.tsx +246 -260
- package/src/screens/contacts/index.ts +0 -4
- package/src/screens/device-pairing/device-pairing.test.tsx +7 -9
- package/src/screens/device-pairing/device-pairing.tsx +19 -23
- package/src/screens/device-pairing/identity-ipc.ts +25 -0
- package/src/screens/device-pairing/use-device-pairing-state.ts +10 -33
- package/src/screens/devices/devices-list.test.tsx +68 -0
- package/src/screens/devices/devices-list.tsx +361 -0
- package/src/screens/devices/index.ts +1 -0
- package/src/screens/groups/group-details.tsx +8 -142
- package/src/screens/groups/group-invite-panel.tsx +2 -7
- package/src/screens/groups/group-list.test.tsx +44 -0
- package/src/screens/groups/group-list.tsx +227 -288
- package/src/screens/groups/group-members.tsx +3 -2
- package/src/screens/groups/index.ts +0 -5
- package/src/screens/identity/identity-routes.test.ts +70 -0
- package/src/screens/identity/identity-routes.ts +301 -0
- package/src/screens/identity/identity-screen.test.tsx +138 -0
- package/src/screens/identity/identity-screen.tsx +282 -0
- package/src/screens/identity/index.ts +2 -0
- package/src/screens/identity/invite-contact-panel.tsx +45 -0
- package/src/screens/identity/profile-invite.tsx +93 -0
- package/src/screens/invites/accept-invite.tsx +1 -1
- package/src/screens/invites/activity-view.test.tsx +127 -0
- package/src/screens/invites/activity-view.tsx +162 -0
- package/src/screens/invites/index.ts +1 -4
- package/src/screens/invites/invite-capture.ts +0 -3
- package/src/screens/invites/invite-classify.test.ts +110 -0
- package/src/screens/invites/invites-ipc.ts +6 -55
- package/src/screens/invites/pending-invites-list.tsx +85 -61
- package/src/screens/network-viewer/network-viewer-ipc.ts +8 -82
- package/src/screens/search/global-search.tsx +2 -1
- package/src/screens/settings/settings-page.tsx +53 -126
- package/src/screens/setup-user.tsx +5 -6
- package/src/screens/tools/tool-access-ipc.ts +13 -74
- package/src/screens/welcome-modal.tsx +3 -3
- package/src/system-apps/identity.app.ts +9 -0
- package/src/system-apps/index.ts +3 -12
- package/src/tabs-layout/network-status-indicator.test.tsx +22 -0
- package/src/tabs-layout/network-status-indicator.tsx +31 -0
- package/src/tabs-layout/tabs-layout.tsx +7 -1
- package/src/tabs-layout/tabs-state.test.ts +152 -0
- package/src/tabs-layout/tabs-state.ts +300 -83
- package/dist/screens/invites/invites-screen.d.ts +0 -9
- package/dist/screens/invites/invites-screen.js +0 -36
- package/dist/system-apps/account.app.d.ts +0 -2
- package/dist/system-apps/account.app.js +0 -8
- package/dist/system-apps/contacts.app.d.ts +0 -2
- package/dist/system-apps/contacts.app.js +0 -9
- package/dist/system-apps/groups.app.d.ts +0 -2
- package/dist/system-apps/groups.app.js +0 -9
- package/dist/system-apps/invites.app.d.ts +0 -2
- package/dist/system-apps/invites.app.js +0 -9
- package/src/screens/invites/invites-screen.tsx +0 -78
- package/src/system-apps/account.app.ts +0 -7
- package/src/system-apps/contacts.app.ts +0 -8
- package/src/system-apps/groups.app.ts +0 -8
- package/src/system-apps/invites.app.ts +0 -8
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createContractConsumer,
|
|
3
|
-
getHostContractTransport,
|
|
4
2
|
getNetworkControlContractDefinition,
|
|
5
3
|
getNetworkDiagnosticsContractDefinition,
|
|
6
|
-
|
|
4
|
+
hostContractConsumer,
|
|
7
5
|
type INetworkControlConsumer,
|
|
8
6
|
type INetworkDiagnosticsConsumer,
|
|
9
|
-
subscribeHostContractTransport,
|
|
10
|
-
waitForHostContractTransport,
|
|
11
7
|
} from "@peers-app/peers-sdk";
|
|
12
8
|
|
|
13
|
-
/** Default wait for the host transport before treating diagnostics as unavailable. */
|
|
14
|
-
const HOST_TRANSPORT_TIMEOUT_MS = 10_000;
|
|
15
|
-
|
|
16
9
|
/** Electron-only capabilities intentionally excluded from portable network contracts. */
|
|
17
10
|
export type NetworkViewerPlatformApi = {
|
|
18
11
|
downloadRemoteDatabase?: (groupId: string, deviceId: string) => Promise<unknown>;
|
|
@@ -23,88 +16,21 @@ type WindowWithNetworkViewer = Window & {
|
|
|
23
16
|
electronAPI?: { networkViewer?: NetworkViewerPlatformApi };
|
|
24
17
|
};
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let cachedConsumers: INetworkViewerConsumers | undefined;
|
|
33
|
-
let pendingConsumers: Promise<INetworkViewerConsumers> | undefined;
|
|
34
|
-
|
|
35
|
-
function disposeCachedConsumers(): void {
|
|
36
|
-
cachedConsumers?.diagnostics.dispose();
|
|
37
|
-
cachedConsumers?.control.dispose();
|
|
38
|
-
cachedConsumers = undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
subscribeHostContractTransport(
|
|
42
|
-
(transport) => {
|
|
43
|
-
if (cachedConsumers && cachedConsumers.transport !== transport) {
|
|
44
|
-
disposeCachedConsumers();
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
{ emitCurrent: false },
|
|
19
|
+
const diagnostics = hostContractConsumer<INetworkDiagnosticsConsumer>(
|
|
20
|
+
getNetworkDiagnosticsContractDefinition(),
|
|
21
|
+
);
|
|
22
|
+
const control = hostContractConsumer<INetworkControlConsumer>(
|
|
23
|
+
getNetworkControlContractDefinition(),
|
|
48
24
|
);
|
|
49
|
-
|
|
50
|
-
async function loadConsumers(transport: IContractTransport): Promise<INetworkViewerConsumers> {
|
|
51
|
-
disposeCachedConsumers();
|
|
52
|
-
const diagnostics = createContractConsumer(
|
|
53
|
-
getNetworkDiagnosticsContractDefinition(),
|
|
54
|
-
transport,
|
|
55
|
-
) as unknown as INetworkDiagnosticsConsumer;
|
|
56
|
-
const control = createContractConsumer(
|
|
57
|
-
getNetworkControlContractDefinition(),
|
|
58
|
-
transport,
|
|
59
|
-
) as unknown as INetworkControlConsumer;
|
|
60
|
-
try {
|
|
61
|
-
await diagnostics.loadingPromise;
|
|
62
|
-
if (getHostContractTransport() !== transport) {
|
|
63
|
-
diagnostics.dispose();
|
|
64
|
-
control.dispose();
|
|
65
|
-
// Clear before recursing so getConsumers can start a fresh in-flight memo for the
|
|
66
|
-
// current transport, and so this promise's finally does not wipe that newer memo.
|
|
67
|
-
pendingConsumers = undefined;
|
|
68
|
-
return getConsumers();
|
|
69
|
-
}
|
|
70
|
-
cachedConsumers = { transport, diagnostics, control };
|
|
71
|
-
return cachedConsumers;
|
|
72
|
-
} catch (error) {
|
|
73
|
-
diagnostics.dispose();
|
|
74
|
-
control.dispose();
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function getConsumers(): Promise<INetworkViewerConsumers> {
|
|
80
|
-
const transport =
|
|
81
|
-
getHostContractTransport() ??
|
|
82
|
-
(await waitForHostContractTransport({ timeoutMs: HOST_TRANSPORT_TIMEOUT_MS }));
|
|
83
|
-
if (cachedConsumers?.transport === transport) {
|
|
84
|
-
return cachedConsumers;
|
|
85
|
-
}
|
|
86
|
-
if (pendingConsumers) {
|
|
87
|
-
return pendingConsumers;
|
|
88
|
-
}
|
|
89
|
-
const loadPromise = loadConsumers(transport);
|
|
90
|
-
pendingConsumers = loadPromise;
|
|
91
|
-
try {
|
|
92
|
-
return await loadPromise;
|
|
93
|
-
} finally {
|
|
94
|
-
if (pendingConsumers === loadPromise) {
|
|
95
|
-
pendingConsumers = undefined;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
25
|
|
|
100
26
|
/** Return a typed diagnostics consumer bound to the current local host transport. */
|
|
101
27
|
export async function getNetworkDiagnosticsConsumer(): Promise<INetworkDiagnosticsConsumer> {
|
|
102
|
-
return (
|
|
28
|
+
return diagnostics.get();
|
|
103
29
|
}
|
|
104
30
|
|
|
105
31
|
/** Return a typed control consumer bound to the current local host transport. */
|
|
106
32
|
export async function getNetworkControlConsumer(): Promise<INetworkControlConsumer> {
|
|
107
|
-
return (
|
|
33
|
+
return control.get();
|
|
108
34
|
}
|
|
109
35
|
|
|
110
36
|
/** Return the context-isolated desktop-only capability bridge, when available. */
|
|
@@ -39,9 +39,10 @@ const searchConfigs: Array<{
|
|
|
39
39
|
{ config: assistantMentionConfig, category: "Assistants", navigationPath: "assistants" },
|
|
40
40
|
{ config: workflowMentionConfig, category: "Workflows", navigationPath: "workflows" },
|
|
41
41
|
{ config: valueTypeMentionConfig, category: "Types", navigationPath: "peer-types" },
|
|
42
|
-
{ config: userMentionConfig, category: "
|
|
42
|
+
{ config: userMentionConfig, category: "People", navigationPath: "identity/people" },
|
|
43
43
|
];
|
|
44
44
|
|
|
45
|
+
/** Search system apps and known entities, then navigate to their canonical routes. */
|
|
45
46
|
export function GlobalSearch() {
|
|
46
47
|
const [_colorMode] = useObservable(colorMode);
|
|
47
48
|
const [packages] = useObservable(allPackages);
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
autoUpdatePeersCore,
|
|
3
|
-
Devices,
|
|
4
3
|
DISPLAY_NAME_MAX_LENGTH,
|
|
5
4
|
getUserContext,
|
|
6
|
-
type IDevice,
|
|
7
5
|
packagesRootDir,
|
|
8
6
|
reloadPackagesOnPageRefresh,
|
|
9
7
|
rpcServerCalls,
|
|
10
|
-
TrustLevel,
|
|
11
|
-
thisDeviceId,
|
|
12
8
|
Users,
|
|
13
9
|
} from "@peers-app/peers-sdk";
|
|
14
10
|
import type React from "react";
|
|
@@ -17,9 +13,10 @@ import { Input } from "../../components/input";
|
|
|
17
13
|
import { Tabs } from "../../components/tabs";
|
|
18
14
|
import { Tooltip } from "../../components/tooltip";
|
|
19
15
|
import * as globals from "../../globals";
|
|
20
|
-
import { useObservable,
|
|
16
|
+
import { useObservable, usePromise } from "../../hooks";
|
|
21
17
|
import { LayoutPicker } from "../../root-layout/layout-picker";
|
|
22
|
-
import {
|
|
18
|
+
import { identityTools } from "../device-pairing/identity-ipc";
|
|
19
|
+
import { IDENTITY_ACCOUNT_PATH, IDENTITY_PAIR_PATH } from "../identity/identity-routes";
|
|
23
20
|
import { ColorModeDropdown } from "./color-mode-dropdown";
|
|
24
21
|
|
|
25
22
|
/** Electron file picker attaches a filesystem path on `File`. */
|
|
@@ -46,24 +43,29 @@ function getAppVersion() {
|
|
|
46
43
|
/** Application settings organized by user, system, and developer concerns. */
|
|
47
44
|
export const SettingsPage: React.FC = () => {
|
|
48
45
|
return (
|
|
49
|
-
<div className="
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
46
|
+
<div className="d-flex flex-column h-100 overflow-hidden" style={{ minHeight: 0 }}>
|
|
47
|
+
<div className="container-fluid flex-grow-1 overflow-auto" style={{ minHeight: 0 }}>
|
|
48
|
+
<Tabs
|
|
49
|
+
tabs={[
|
|
50
|
+
{
|
|
51
|
+
name: "User",
|
|
52
|
+
content: <UserSettingsTab />,
|
|
53
|
+
active: true,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "Appearance",
|
|
57
|
+
content: <AppearanceSettingsTab />,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "Advanced",
|
|
61
|
+
content: <AdvancedSettingsTab />,
|
|
62
|
+
},
|
|
63
|
+
]}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<footer className="flex-shrink-0 border-top px-3 py-2 small text-muted">
|
|
67
|
+
Peers {getAppVersion()}
|
|
68
|
+
</footer>
|
|
67
69
|
</div>
|
|
68
70
|
);
|
|
69
71
|
};
|
|
@@ -72,6 +74,7 @@ const UserSettingsTab: React.FC = () => {
|
|
|
72
74
|
return (
|
|
73
75
|
<>
|
|
74
76
|
<ProfileSection />
|
|
77
|
+
<IdentityAccountSection />
|
|
75
78
|
<AddDeviceSection />
|
|
76
79
|
<LogoutSection />
|
|
77
80
|
</>
|
|
@@ -109,31 +112,11 @@ const AdvancedSettingsTab: React.FC = () => {
|
|
|
109
112
|
};
|
|
110
113
|
|
|
111
114
|
const ProfileSection: React.FC = () => {
|
|
112
|
-
const [deviceId] = useObservable(thisDeviceId);
|
|
113
|
-
const deviceName = useObservableState<string>("");
|
|
114
|
-
const savedDeviceName = useObservableState<string>("");
|
|
115
|
-
|
|
116
115
|
const me = usePromise(async () => {
|
|
117
116
|
const userContext = await getUserContext();
|
|
118
117
|
return Users(userContext.userDataContext).initDoc(globals.me);
|
|
119
118
|
});
|
|
120
119
|
|
|
121
|
-
// Load device name
|
|
122
|
-
useEffect(() => {
|
|
123
|
-
const loadDevice = async () => {
|
|
124
|
-
if (!deviceId) return;
|
|
125
|
-
try {
|
|
126
|
-
const userContext = await getUserContext();
|
|
127
|
-
const device = await Devices(userContext.userDataContext).get(deviceId);
|
|
128
|
-
deviceName((device?.name || "").slice(0, DISPLAY_NAME_MAX_LENGTH));
|
|
129
|
-
savedDeviceName(device?.name || "");
|
|
130
|
-
} catch (err) {
|
|
131
|
-
console.error("Error loading device name:", err);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
loadDevice();
|
|
135
|
-
}, [deviceId, deviceName, savedDeviceName]);
|
|
136
|
-
|
|
137
120
|
const handleSave = async () => {
|
|
138
121
|
try {
|
|
139
122
|
// Save user profile (only when it actually changed). Pass me.toJS() — the
|
|
@@ -147,22 +130,6 @@ const ProfileSection: React.FC = () => {
|
|
|
147
130
|
me.qs.name(updatedUser.name);
|
|
148
131
|
me.q(0); // Reset change counter
|
|
149
132
|
}
|
|
150
|
-
|
|
151
|
-
// Save device name if changed
|
|
152
|
-
if (deviceId && deviceName() !== savedDeviceName()) {
|
|
153
|
-
const userContext = await getUserContext();
|
|
154
|
-
const devicesTable = Devices(userContext.userDataContext);
|
|
155
|
-
const device: IDevice = (await devicesTable.get(deviceId)) || {
|
|
156
|
-
deviceId,
|
|
157
|
-
userId: userContext.userId,
|
|
158
|
-
firstSeen: new Date(),
|
|
159
|
-
lastSeen: new Date(),
|
|
160
|
-
trustLevel: TrustLevel.NewDevice,
|
|
161
|
-
};
|
|
162
|
-
device.name = deviceName() || undefined;
|
|
163
|
-
await devicesTable.save(device);
|
|
164
|
-
savedDeviceName(deviceName());
|
|
165
|
-
}
|
|
166
133
|
} catch (err) {
|
|
167
134
|
console.error("Error saving profile:", err);
|
|
168
135
|
alert(`Failed to save: ${(err as Error).message}`);
|
|
@@ -171,103 +138,63 @@ const ProfileSection: React.FC = () => {
|
|
|
171
138
|
|
|
172
139
|
// Subscribe to changes
|
|
173
140
|
useObservable(me?.q);
|
|
174
|
-
useObservable(deviceName);
|
|
175
141
|
|
|
176
142
|
if (!me) {
|
|
177
143
|
return null;
|
|
178
144
|
}
|
|
179
145
|
|
|
180
|
-
const hasChanges = !!me.q()
|
|
146
|
+
const hasChanges = !!me.q();
|
|
181
147
|
|
|
182
148
|
return (
|
|
183
149
|
<>
|
|
184
150
|
<div className="d-flex align-items-center mb-3">
|
|
185
|
-
<h5 className="mb-0 me-auto">
|
|
151
|
+
<h5 className="mb-0 me-auto">Profile</h5>
|
|
186
152
|
<button className="btn btn-primary btn-sm" onClick={handleSave} disabled={!hasChanges}>
|
|
187
153
|
Save
|
|
188
154
|
</button>
|
|
189
155
|
</div>
|
|
190
156
|
|
|
191
157
|
<div className="mb-3">
|
|
192
|
-
<
|
|
158
|
+
<label className="form-label small" htmlFor="settings-display-name">
|
|
159
|
+
Display name
|
|
160
|
+
</label>
|
|
193
161
|
<Input
|
|
162
|
+
id="settings-display-name"
|
|
194
163
|
value={me.qs.name}
|
|
195
164
|
className="form-control"
|
|
196
165
|
maxLength={DISPLAY_NAME_MAX_LENGTH}
|
|
197
166
|
placeholder="Enter your name"
|
|
198
167
|
/>
|
|
199
168
|
</div>
|
|
200
|
-
|
|
201
|
-
<div className="mb-3">
|
|
202
|
-
<small className="form-label">
|
|
203
|
-
Device Name:
|
|
204
|
-
<Tooltip markdownContent="Give this device a friendly name to identify it in your network." />
|
|
205
|
-
</small>
|
|
206
|
-
<Input
|
|
207
|
-
value={deviceName}
|
|
208
|
-
className="form-control"
|
|
209
|
-
maxLength={DISPLAY_NAME_MAX_LENGTH}
|
|
210
|
-
placeholder="e.g., My Laptop"
|
|
211
|
-
/>
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
<div className="mb-3">
|
|
215
|
-
<small className="form-label">
|
|
216
|
-
User ID:
|
|
217
|
-
<Tooltip markdownContent="This uniquely identifies you to all other users." />
|
|
218
|
-
</small>
|
|
219
|
-
<Input value={me.userId} className="form-control" disabled />
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
<div className="row">
|
|
223
|
-
<div className="col-md-6 mb-3">
|
|
224
|
-
<small className="form-label">
|
|
225
|
-
Public Key:
|
|
226
|
-
<Tooltip markdownContent="This is your public key that other users will use to verify your signatures." />
|
|
227
|
-
</small>
|
|
228
|
-
<Input value={me.publicKey} className="form-control form-control-sm" disabled />
|
|
229
|
-
</div>
|
|
230
|
-
<div className="col-md-6 mb-3">
|
|
231
|
-
<small className="form-label">
|
|
232
|
-
Public Box Key:
|
|
233
|
-
<Tooltip markdownContent="This is the public key that other users can use to encrypt data so that only you can open it." />
|
|
234
|
-
</small>
|
|
235
|
-
<Input value={me.publicBoxKey} className="form-control form-control-sm" disabled />
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
<div className="mb-3">
|
|
240
|
-
<small className="form-label">
|
|
241
|
-
Device ID:
|
|
242
|
-
<Tooltip markdownContent="This uniquely identifies this device on Peers networks." />
|
|
243
|
-
</small>
|
|
244
|
-
<Input value={deviceId || ""} className="form-control form-control-sm" disabled />
|
|
245
|
-
</div>
|
|
246
|
-
|
|
247
|
-
<div className="mb-3">
|
|
248
|
-
<small className="form-label">App Version:</small>
|
|
249
|
-
<Input value={getAppVersion()} className="form-control form-control-sm" disabled />
|
|
250
|
-
</div>
|
|
251
169
|
</>
|
|
252
170
|
);
|
|
253
171
|
};
|
|
254
172
|
|
|
173
|
+
const IdentityAccountSection: React.FC = () => {
|
|
174
|
+
return (
|
|
175
|
+
<section className="mt-4 pt-3 border-top">
|
|
176
|
+
<h6>Identity account</h6>
|
|
177
|
+
<p className="small text-muted">
|
|
178
|
+
View account identifiers, security keys, this device’s ID, and Peers Services registration.
|
|
179
|
+
</p>
|
|
180
|
+
<a href={`#${IDENTITY_ACCOUNT_PATH}`} className="btn btn-outline-primary">
|
|
181
|
+
View Identity account
|
|
182
|
+
</a>
|
|
183
|
+
</section>
|
|
184
|
+
);
|
|
185
|
+
};
|
|
186
|
+
|
|
255
187
|
const AddDeviceSection: React.FC = () => {
|
|
256
|
-
const [open, setOpen] = useState(false);
|
|
257
188
|
return (
|
|
258
189
|
<section className="mt-4 pt-3 border-top">
|
|
259
190
|
<h6>Add another device</h6>
|
|
260
191
|
<p className="small text-muted">
|
|
261
|
-
On the new device choose “Sign in from another device”, then type the code it shows
|
|
262
|
-
approve. Account access is transferred only after you approve.
|
|
192
|
+
On the new device choose “Sign in from another device”, then type the code it shows on
|
|
193
|
+
Identity → Devices and approve. Account access is transferred only after you approve.
|
|
263
194
|
</p>
|
|
264
|
-
{
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<button type="button" className="btn btn-outline-primary" onClick={() => setOpen(true)}>
|
|
268
|
-
Add another device
|
|
269
|
-
</button>
|
|
270
|
-
)}
|
|
195
|
+
<a href={`#${IDENTITY_PAIR_PATH}`} className="btn btn-outline-primary">
|
|
196
|
+
Add another device
|
|
197
|
+
</a>
|
|
271
198
|
</section>
|
|
272
199
|
);
|
|
273
200
|
};
|
|
@@ -728,7 +655,7 @@ const LogoutSection: React.FC = () => {
|
|
|
728
655
|
"Make sure you have your User ID and Secret Key saved securely, or you will lose access to your account.",
|
|
729
656
|
);
|
|
730
657
|
if (!confirmed) return;
|
|
731
|
-
await
|
|
658
|
+
await (await identityTools()).signOut();
|
|
732
659
|
}}
|
|
733
660
|
>
|
|
734
661
|
Logout
|
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
newid,
|
|
5
5
|
newKeys,
|
|
6
6
|
openMessageWithPublicKey,
|
|
7
|
-
rpcServerCalls,
|
|
8
7
|
signMessageWithSecretKey,
|
|
9
8
|
} from "@peers-app/peers-sdk";
|
|
10
9
|
import { useState } from "react";
|
|
11
10
|
import { useObservable } from "../hooks";
|
|
12
11
|
import { DestinationDevicePairing } from "./device-pairing";
|
|
12
|
+
import { identityTools } from "./device-pairing/identity-ipc";
|
|
13
13
|
import { colorMode } from "./settings/color-mode-dropdown";
|
|
14
14
|
|
|
15
15
|
type SetupStep = "select-user-type" | "complete";
|
|
@@ -37,9 +37,9 @@ export const SetupUser = () => {
|
|
|
37
37
|
const keys = newKeys();
|
|
38
38
|
const _userId = newid();
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
await
|
|
40
|
+
// signIn triggers device init, which installs peers-core into the personal
|
|
41
|
+
// context (S3-first, bundle fallback).
|
|
42
|
+
await (await identityTools()).signIn({ userId: _userId, secretKey: keys.secretKey });
|
|
43
43
|
|
|
44
44
|
// Success - reload the app (peers-services registration runs after Welcome modal)
|
|
45
45
|
window.location.reload();
|
|
@@ -76,8 +76,7 @@ export const SetupUser = () => {
|
|
|
76
76
|
throw new Error("Secret key validation failed");
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
await rpcServerCalls.setUserIdAndSecretKey(userId, keys.secretKey);
|
|
79
|
+
await (await identityTools()).signIn({ userId, secretKey: keys.secretKey });
|
|
81
80
|
|
|
82
81
|
// Success - reload the app
|
|
83
82
|
window.location.reload();
|
|
@@ -1,88 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
-
createContractConsumer,
|
|
3
|
-
getHostContractTransport,
|
|
4
2
|
getToolAccessControlContractDefinition,
|
|
5
|
-
|
|
3
|
+
hostContractConsumer,
|
|
4
|
+
type IHostContractConsumer,
|
|
6
5
|
type IToolAccessControlConsumer,
|
|
7
|
-
subscribeHostContractTransport,
|
|
8
|
-
waitForHostContractTransport,
|
|
9
6
|
} from "@peers-app/peers-sdk";
|
|
10
7
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
type CachedConsumer = {
|
|
14
|
-
transport: IContractTransport;
|
|
15
|
-
consumer: IToolAccessControlConsumer;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const cachedByContext = new Map<string, CachedConsumer>();
|
|
19
|
-
const pendingByContext = new Map<string, Promise<IToolAccessControlConsumer>>();
|
|
20
|
-
|
|
21
|
-
function disposeCachedConsumers(): void {
|
|
22
|
-
for (const cached of cachedByContext.values()) {
|
|
23
|
-
cached.consumer.dispose();
|
|
24
|
-
}
|
|
25
|
-
cachedByContext.clear();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
subscribeHostContractTransport(
|
|
29
|
-
(transport) => {
|
|
30
|
-
if ([...cachedByContext.values()].some((cached) => cached.transport !== transport)) {
|
|
31
|
-
disposeCachedConsumers();
|
|
32
|
-
pendingByContext.clear();
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
{ emitCurrent: false },
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
async function loadConsumer(
|
|
39
|
-
transport: IContractTransport,
|
|
40
|
-
dataContextId: string,
|
|
41
|
-
): Promise<IToolAccessControlConsumer> {
|
|
42
|
-
cachedByContext.get(dataContextId)?.consumer.dispose();
|
|
43
|
-
cachedByContext.delete(dataContextId);
|
|
44
|
-
const consumer = createContractConsumer(getToolAccessControlContractDefinition(), transport, {
|
|
45
|
-
dataContextId,
|
|
46
|
-
}) as unknown as IToolAccessControlConsumer;
|
|
47
|
-
try {
|
|
48
|
-
await consumer.loadingPromise;
|
|
49
|
-
if (getHostContractTransport() !== transport) {
|
|
50
|
-
consumer.dispose();
|
|
51
|
-
pendingByContext.delete(dataContextId);
|
|
52
|
-
return getToolAccessControlConsumer(dataContextId);
|
|
53
|
-
}
|
|
54
|
-
cachedByContext.set(dataContextId, { transport, consumer });
|
|
55
|
-
return consumer;
|
|
56
|
-
} catch (error) {
|
|
57
|
-
consumer.dispose();
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
8
|
+
const consumers = new Map<string, IHostContractConsumer<IToolAccessControlConsumer>>();
|
|
61
9
|
|
|
62
10
|
/**
|
|
63
11
|
* Return a typed local-host consumer for Tool Access Control in `dataContextId`.
|
|
12
|
+
*
|
|
13
|
+
* One consumer is cached per data context and rebuilt when the host transport changes.
|
|
64
14
|
*/
|
|
65
15
|
export async function getToolAccessControlConsumer(
|
|
66
16
|
dataContextId: string,
|
|
67
17
|
): Promise<IToolAccessControlConsumer> {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const pending = pendingByContext.get(dataContextId);
|
|
76
|
-
if (pending) {
|
|
77
|
-
return pending;
|
|
78
|
-
}
|
|
79
|
-
const loadPromise = loadConsumer(transport, dataContextId);
|
|
80
|
-
pendingByContext.set(dataContextId, loadPromise);
|
|
81
|
-
try {
|
|
82
|
-
return await loadPromise;
|
|
83
|
-
} finally {
|
|
84
|
-
if (pendingByContext.get(dataContextId) === loadPromise) {
|
|
85
|
-
pendingByContext.delete(dataContextId);
|
|
86
|
-
}
|
|
18
|
+
let handle = consumers.get(dataContextId);
|
|
19
|
+
if (!handle) {
|
|
20
|
+
handle = hostContractConsumer<IToolAccessControlConsumer>(
|
|
21
|
+
getToolAccessControlContractDefinition(),
|
|
22
|
+
{ dataContextId: async () => dataContextId },
|
|
23
|
+
);
|
|
24
|
+
consumers.set(dataContextId, handle);
|
|
87
25
|
}
|
|
26
|
+
return handle.get();
|
|
88
27
|
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
getUserContext,
|
|
6
6
|
hasShownWelcomeModal,
|
|
7
7
|
type IDevice,
|
|
8
|
-
rpcServerCalls,
|
|
9
8
|
TrustLevel,
|
|
10
9
|
thisDeviceId,
|
|
11
10
|
Users,
|
|
@@ -14,6 +13,7 @@ import type React from "react";
|
|
|
14
13
|
import { useEffect, useState } from "react";
|
|
15
14
|
import { useObservable, usePromise } from "../hooks";
|
|
16
15
|
import { persistPeersServicesRegistration } from "../peers-services-registration";
|
|
16
|
+
import { identityTools } from "./device-pairing/identity-ipc";
|
|
17
17
|
|
|
18
18
|
interface WelcomeModalProps {
|
|
19
19
|
colorMode: string;
|
|
@@ -22,8 +22,8 @@ interface WelcomeModalProps {
|
|
|
22
22
|
|
|
23
23
|
async function tryRegisterPeersServicesAfterWelcome(): Promise<void> {
|
|
24
24
|
try {
|
|
25
|
-
const token = await
|
|
26
|
-
persistPeersServicesRegistration(token);
|
|
25
|
+
const { token } = await (await identityTools()).registerWithPeersServices();
|
|
26
|
+
await persistPeersServicesRegistration(token);
|
|
27
27
|
} catch (err: unknown) {
|
|
28
28
|
console.warn("Peers services registration after welcome modal:", err);
|
|
29
29
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IAppNav } from "@peers-app/peers-sdk";
|
|
2
|
+
|
|
3
|
+
/** Identity system app: activity, people, groups, devices, and account. */
|
|
4
|
+
export const identityApp: IAppNav = {
|
|
5
|
+
name: "Identity",
|
|
6
|
+
displayName: "Identity",
|
|
7
|
+
iconClassName: "bi-person-badge",
|
|
8
|
+
navigationPath: "identity",
|
|
9
|
+
};
|
package/src/system-apps/index.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import type { IAppNav } from "@peers-app/peers-sdk";
|
|
2
2
|
import type { IPackageWithNavs } from "../ui-router/routes-loader";
|
|
3
3
|
|
|
4
|
-
export { accountApp } from "./account.app";
|
|
5
4
|
export { assistantsApp } from "./assistants.app";
|
|
6
5
|
export { consoleLogsApp } from "./console-logs.app";
|
|
7
|
-
export { contactsApp } from "./contacts.app";
|
|
8
6
|
export { dataExplorerApp } from "./data-explorer.app";
|
|
9
|
-
export {
|
|
10
|
-
export { invitesApp } from "./invites.app";
|
|
7
|
+
export { identityApp } from "./identity.app";
|
|
11
8
|
export { mobileSettingsApp } from "./mobile-settings.app";
|
|
12
9
|
export { networkViewerApp } from "./network-viewer.app";
|
|
13
10
|
export { packagesApp } from "./packages.app";
|
|
@@ -20,13 +17,10 @@ export { typesApp } from "./types.app";
|
|
|
20
17
|
export { variablesApp } from "./variables.app";
|
|
21
18
|
export { workflowsApp } from "./workflows.app";
|
|
22
19
|
|
|
23
|
-
import { accountApp } from "./account.app";
|
|
24
20
|
import { assistantsApp } from "./assistants.app";
|
|
25
21
|
import { consoleLogsApp } from "./console-logs.app";
|
|
26
|
-
import { contactsApp } from "./contacts.app";
|
|
27
22
|
import { dataExplorerApp } from "./data-explorer.app";
|
|
28
|
-
import {
|
|
29
|
-
import { invitesApp } from "./invites.app";
|
|
23
|
+
import { identityApp } from "./identity.app";
|
|
30
24
|
import { mobileSettingsApp } from "./mobile-settings.app";
|
|
31
25
|
import { networkViewerApp } from "./network-viewer.app";
|
|
32
26
|
import { packagesApp } from "./packages.app";
|
|
@@ -62,12 +56,9 @@ export const systemApps: IAppNav[] = [
|
|
|
62
56
|
workflowsApp,
|
|
63
57
|
assistantsApp,
|
|
64
58
|
packagesApp,
|
|
65
|
-
|
|
66
|
-
groupsApp,
|
|
67
|
-
invitesApp,
|
|
59
|
+
identityApp,
|
|
68
60
|
|
|
69
61
|
// User & Settings Apps
|
|
70
|
-
accountApp,
|
|
71
62
|
settingsApp,
|
|
72
63
|
// Mobile Settings (only in React Native)
|
|
73
64
|
...(isReactNative() ? [mobileSettingsApp] : []),
|
|
@@ -2,6 +2,7 @@ import type { INetworkConnectionInfo, INetworkStatusSummary } from "@peers-app/p
|
|
|
2
2
|
import { TrustLevel } from "@peers-app/peers-sdk";
|
|
3
3
|
import { act, fireEvent, render, waitFor } from "@testing-library/react";
|
|
4
4
|
import moment from "moment";
|
|
5
|
+
import { IDENTITY_ACCEPT_PATH, IDENTITY_ACCOUNT_PATH } from "../screens/identity/identity-routes";
|
|
5
6
|
import {
|
|
6
7
|
connectionIdentityLabel,
|
|
7
8
|
formatRelativeSyncTime,
|
|
@@ -496,6 +497,27 @@ describe("NetworkStatusIndicator", () => {
|
|
|
496
497
|
});
|
|
497
498
|
});
|
|
498
499
|
|
|
500
|
+
it("offers shortcuts to use an invite and open the Identity account", () => {
|
|
501
|
+
mockViewState = { availability: "ready", status: status({ state: "synced" }) };
|
|
502
|
+
const view = render(<NetworkStatusIndicator />);
|
|
503
|
+
|
|
504
|
+
fireEvent.click(view.getByRole("button", { name: /Network synced/ }));
|
|
505
|
+
expect(
|
|
506
|
+
view.getByRole("button", { name: "Use Invite" }).querySelector(".bi-qr-code"),
|
|
507
|
+
).toBeTruthy();
|
|
508
|
+
expect(
|
|
509
|
+
view.getByRole("button", { name: "Identity" }).querySelector(".bi-person-badge"),
|
|
510
|
+
).toBeTruthy();
|
|
511
|
+
fireEvent.click(view.getByRole("button", { name: "Use Invite" }));
|
|
512
|
+
expect(goToTabPath).toHaveBeenCalledWith(IDENTITY_ACCEPT_PATH);
|
|
513
|
+
expect(view.queryByRole("dialog", { name: "Network status details" })).toBeNull();
|
|
514
|
+
|
|
515
|
+
fireEvent.click(view.getByRole("button", { name: /Network synced/ }));
|
|
516
|
+
fireEvent.click(view.getByRole("button", { name: "Identity" }));
|
|
517
|
+
expect(goToTabPath).toHaveBeenCalledWith(IDENTITY_ACCOUNT_PATH);
|
|
518
|
+
expect(view.queryByRole("dialog", { name: "Network status details" })).toBeNull();
|
|
519
|
+
});
|
|
520
|
+
|
|
499
521
|
it("updates on rerender and opens details before navigating to Network Viewer", async () => {
|
|
500
522
|
const view = render(<NetworkStatusIndicator />);
|
|
501
523
|
expect(view.getByRole("button", { name: /loading/ })).toBeTruthy();
|