From 4a7373b653badc01f7c2447e8fb4267cd1f93c58 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Sep 2024 13:32:41 +0200 Subject: [PATCH] added feature to learn from others about known peers and display if peer is only known through others instead of from the all_agents anchor --- package-lock.json | 10 +- package.json | 4 +- ui/package.json | 2 +- ui/src/agent-connection-status-icon.ts | 60 +++++-- ui/src/agent-connection-status.ts | 4 + ui/src/room-view.ts | 221 +++++++++++++++++-------- ui/src/utils.ts | 22 +++ 7 files changed, 238 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4164a8d..459517c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ ], "devDependencies": { "@holochain-playground/cli": "^0.1.1", - "@theweave/cli": "0.13.0-beta.6", + "@theweave/cli": "0.13.0-beta.7", "concurrently": "^6.2.1", "electron": "^25.6.0", "rimraf": "^3.0.2" @@ -1992,9 +1992,9 @@ } }, "node_modules/@theweave/cli": { - "version": "0.13.0-beta.6", - "resolved": "https://registry.npmjs.org/@theweave/cli/-/cli-0.13.0-beta.6.tgz", - "integrity": "sha512-4EATx9+Ito4rzZIhwMeJqVv66E0cRE5SgqoZqUl3Oalh+Dbu66TPYqSGOJnZgJ532Np4pmHddJpJHlI1f3BuWQ==", + "version": "0.13.0-beta.7", + "resolved": "https://registry.npmjs.org/@theweave/cli/-/cli-0.13.0-beta.7.tgz", + "integrity": "sha512-7HjGXNPFFxVSOgDlswRrlN3y8tw2wZWYq76Fc6PCw6bYrtMKlFSP9VNAqcZnwjZpgdt0BYu4u83Dk3i3MI4C6A==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -8674,7 +8674,7 @@ } }, "ui": { - "version": "0.7.0", + "version": "0.7.1", "dependencies": { "@fontsource-variable/baloo-2": "5.0.19", "@fontsource-variable/noto-sans-sc": "^5.0.5", diff --git a/package.json b/package.json index 6a7e969..c18f1b8 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "network": "hc s clean && npm run build:happ && UI_PORT=8888 concurrently \"npm start -w ui\" \"npm run launch:happ\"", "test": "npm run build:zomes && hc app pack workdir --recursive && npm t -w tests", "applet-dev": "concurrently \"UI_PORT=8888 npm run start -w ui\" \"weave --agent-idx 1 --dev-config ./weave.dev.config.ts\" \"sleep 5 && weave --agent-idx 2 --dev-config ./weave.dev.config.ts --sync-time 6000\"", - "applet-dev-3": "concurrently \"UI_PORT=8888 npm run start -w ui\" \"weave --agent-idx 1 --dev-config ./weave.dev.config.ts\" \"sleep 5 && weave --agent-idx 2 --dev-config ./weave.dev.config.ts --sync-time 6000\" \"sleep 5 && weave --agent-idx 3 --dev-config ./weave.dev.config.ts --sync-time 6000\"", + "applet-dev-3": "concurrently \"UI_PORT=8888 npm run start -w ui\" \"weave --agent-idx 1 --dev-config ./weave.dev.config.ts\" \"sleep 5 && weave --agent-idx 2 --dev-config ./weave.dev.config.ts --sync-time 10000\" \"sleep 5 && weave --agent-idx 3 --dev-config ./weave.dev.config.ts --sync-time 10000\"", "applet-dev-1": "concurrently \"UI_PORT=8888 npm run start -w ui\" \"weave --agent-idx 1 --dev-config ./weave.dev.config.ts\"", "help": "weave --help", "launch:happ": "concurrently \"hc run-local-services --bootstrap-port 9998 --signal-port 9999\" \"echo pass | RUST_LOG=warn hc launch --piped -n $AGENTS workdir/presence.happ --ui-port $UI_PORT network --bootstrap http://127.0.0.1:9998 webrtc ws://127.0.0.1:9999\"", @@ -22,7 +22,7 @@ }, "devDependencies": { "@holochain-playground/cli": "^0.1.1", - "@theweave/cli": "0.13.0-beta.6", + "@theweave/cli": "0.13.0-beta.7", "concurrently": "^6.2.1", "electron": "^25.6.0", "rimraf": "^3.0.2" diff --git a/ui/package.json b/ui/package.json index 019e444..d7fb53c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "ui", - "version": "0.7.0", + "version": "0.7.1", "scripts": { "start": "vite --port $UI_PORT --clearScreen false", "build": "vite build", diff --git a/ui/src/agent-connection-status-icon.ts b/ui/src/agent-connection-status-icon.ts index 022dbfa..7305d48 100644 --- a/ui/src/agent-connection-status-icon.ts +++ b/ui/src/agent-connection-status-icon.ts @@ -1,5 +1,5 @@ import { consume } from '@lit/context'; -import { hashProperty, sharedStyles } from '@holochain-open-dev/elements'; +import { hashProperty } from '@holochain-open-dev/elements'; import { css, html, LitElement, PropertyValueMap } from 'lit'; import { property, customElement } from 'lit/decorators.js'; import { AgentPubKey, encodeHashToBase64 } from '@holochain/client'; @@ -20,6 +20,7 @@ import { import { EntryRecord } from '@holochain-open-dev/utils'; import { ConnectionStatus } from './room-view'; import { connectionStatusToColor } from './utils'; +import { sharedStyles } from './sharedStyles'; @localized() @customElement('agent-connection-status-icon') @@ -41,6 +42,9 @@ export class AgentConnectionStatusIcon extends LitElement { @property() connectionStatus: ConnectionStatus | undefined; + @property() + onlyToldAbout = false; + /** Dependencies */ /** @@ -113,14 +117,33 @@ export class AgentConnectionStatusIcon extends LitElement { renderProfile(profile: EntryRecord | undefined) { return html` -
- +
+ ${this.onlyToldAbout + ? html` + +
!
+
+ ` + : html``} ${profile && profile.entry.fields.avatar ? html` `} - -
+
+ `; } @@ -188,6 +211,23 @@ export class AgentConnectionStatusIcon extends LitElement { --sl-tooltip-color: #0d1543; --sl-tooltip-font-family: 'Ubuntu', sans-serif; } + + .tooltip-red { + --sl-tooltip-background-color: #ebc3c3; + } + + .only-told-indicator { + position: absolute; + bottom: -1px; + right: -1px; + font-weight: bold; + color: white; + font-size: 12px; + background: red; + border-radius: 50%; + width: 14px; + height: 14px; + } `, ]; } diff --git a/ui/src/agent-connection-status.ts b/ui/src/agent-connection-status.ts index d60fa7a..5c21429 100644 --- a/ui/src/agent-connection-status.ts +++ b/ui/src/agent-connection-status.ts @@ -41,6 +41,9 @@ export class AgentConnectionStatus extends LitElement { @property() connectionStatus: ConnectionStatus | undefined; + @property() + appVersion: string | undefined; + /** Dependencies */ /** @@ -115,6 +118,7 @@ export class AgentConnectionStatus extends LitElement { return html`
this.roomStore.allAgents, () => [this.roomStore] @@ -191,6 +207,9 @@ export class RoomView extends LitElement { () => [this.roomStore] ); + @state() + _knownAgents: Record = {}; + @state() _recentAttachmentChanges: Record[]> = { added: [], @@ -268,6 +287,7 @@ export class RoomView extends LitElement { { lastUpdated: number; statuses: ConnectionStatuses; + knownAgents?: Record; } > = {}; @@ -952,6 +972,8 @@ export class RoomView extends LitElement { formatVersion: 1, data: { connectionStatuses: this._connectionStatuses, + knownAgents: this._knownAgents, + appVersion: __APP_VERSION__, }, }; await this.roomStore.client.pongFrontend({ @@ -967,7 +989,7 @@ export class RoomView extends LitElement { const now = Date.now(); console.log(`Got PongUI from ${pubkeyB64}: `, signal); - // Update their connection statuses + // Update their connection statuses and the list of known agents try { const metaData: PongMetaData = JSON.parse( signal.meta_data @@ -976,8 +998,39 @@ export class RoomView extends LitElement { othersConnectionStatuses[pubkeyB64] = { lastUpdated: now, statuses: metaData.data.connectionStatuses, + knownAgents: metaData.data.knownAgents, }; this._othersConnectionStatuses = othersConnectionStatuses; + + // Update known agents based on the agents that they know + const knownAgents = this._knownAgents; + const maybeKnownAgent = knownAgents[pubkeyB64]; + if (maybeKnownAgent) { + maybeKnownAgent.appVersion = metaData.data.appVersion; + } else { + knownAgents[pubkeyB64] = { + pubkey: pubkeyB64, + type: 'told', + appVersion: metaData.data.appVersion, + }; + } + if (metaData.data.knownAgents) { + const myPubKeyB64 = encodeHashToBase64( + this.roomStore.client.client.myPubKey + ); + Object.entries(metaData.data.knownAgents).forEach( + ([agentB64, agentInfo]) => { + if (!knownAgents[agentB64] && agentB64 !== myPubKeyB64) { + knownAgents[agentB64] = { + pubkey: agentB64, + type: 'told', + appVersion: agentInfo.appVersion, + }; + } + } + ); + } + this._knownAgents = knownAgents; this.requestUpdate(); } catch (e) { console.warn('Failed to parse pong meta data.'); @@ -1422,14 +1475,38 @@ export class RoomView extends LitElement { } async pingAgents() { - if (this._allAgents.value.status === 'complete') { - // This could potentially be optimized by only pinging agents that are online according to Moss - const agentsToPing = this._allAgents.value.value.filter( - agent => - agent.toString() !== this.roomStore.client.client.myPubKey.toString() + if (this._allAgentsFromAnchor.value.status === 'complete') { + // Update known agents + const myPubKeyB64 = encodeHashToBase64( + this.roomStore.client.client.myPubKey ); - await this.roomStore.client.pingFrontend(agentsToPing); + const knownAgents = this._knownAgents; + this._allAgentsFromAnchor.value.value + .map(agent => encodeHashToBase64(agent)) + .forEach(agentB64 => { + if (agentB64 !== myPubKeyB64) { + knownAgents[agentB64] = { pubkey: agentB64, type: 'known' }; + } + }); + this._knownAgents = knownAgents; } + // Update connection statuses with known people for which we do not yet have a connection status + const connectionStatuses = this._connectionStatuses; + Object.keys(this._knownAgents).forEach(agentB64 => { + if (!connectionStatuses[agentB64]) { + connectionStatuses[agentB64] = { + type: 'Disconnected', + }; + } + }); + this._connectionStatuses = connectionStatuses; + + // Ping known agents + // This could potentially be optimized by only pinging agents that are online according to Moss (which would only work in shared rooms though) + const agentsToPing = Object.keys(this._knownAgents).map(pubkeyB64 => + decodeHashFromBase64(pubkeyB64) + ); + await this.roomStore.client.pingFrontend(agentsToPing); } async addAttachment() { @@ -1643,33 +1720,53 @@ export class RoomView extends LitElement { } renderConnectionStatuses() { - if (this._allAgents.value.status === 'complete') { - const presentAgents = this._allAgents.value.value - .map(agent => encodeHashToBase64(agent)) - .filter(pubkeyB64 => { - const status = this._connectionStatuses[pubkeyB64]; - return !!status && status.type !== 'Disconnected'; - }) - .sort((key_a, key_b) => key_a.localeCompare(key_b)); - const absentAgents = this._allAgents.value.value - .map(agent => encodeHashToBase64(agent)) - .filter(pubkeyB64 => { - const status = this._connectionStatuses[pubkeyB64]; - return !status || status.type === 'Disconnected'; - }) - .sort((key_a, key_b) => key_a.localeCompare(key_b)); - return html` -
-
-
Present
-
-
- ${presentAgents.length > 0 - ? repeat( - presentAgents, + const knownAgentsKeysB64 = Object.keys(this._knownAgents); + + const presentAgents = knownAgentsKeysB64 + .filter(pubkeyB64 => { + const status = this._connectionStatuses[pubkeyB64]; + return !!status && status.type !== 'Disconnected'; + }) + .sort((key_a, key_b) => key_a.localeCompare(key_b)); + const absentAgents = knownAgentsKeysB64 + .filter(pubkeyB64 => { + const status = this._connectionStatuses[pubkeyB64]; + return !status || status.type === 'Disconnected'; + }) + .sort((key_a, key_b) => key_a.localeCompare(key_b)); + return html` +
+
+
Present
+
+
+ ${presentAgents.length > 0 + ? repeat( + presentAgents, + pubkey => pubkey, + pubkey => html` + + ` + ) + : html`no one else present.`} + ${absentAgents.length > 0 + ? html` +
+
Absent
+
+
+ ${repeat( + absentAgents, pubkey => pubkey, pubkey => html` ` - ) - : html`look into the mirror, there's no one else :)`} - ${absentAgents.length > 0 - ? html` -
-
Absent
-
-
- ${repeat( - absentAgents, - pubkey => pubkey, - pubkey => html` - - ` - )} - ` - : html``} -
- `; - } - return html`Loading profiles...`; + )} + ` + : html``} +
+ `; } renderAttachmentPanel() { @@ -1969,27 +2044,39 @@ export class RoomView extends LitElement { >Unkown connection statuses`; - const nConnections = Object.keys(statuses.statuses).length; + const nConnections = Object.values(statuses.statuses).filter( + status => status.type === 'Connected' + ).length; const now = Date.now(); // if the info is older than >2.8 PING_INTERVAL, show the info opaque to indicated that it's outdated const staleInfo = now - statuses.lastUpdated > 2.8 * PING_INTERVAL; const sortedStatuses = Object.entries(statuses.statuses).sort( - ([pubkey_a, _a], [pubkey_b, b]) => pubkey_a.localeCompare(pubkey_b) + sortConnectionStatuses ); return html`
${repeat( sortedStatuses, ([pubkeyb64, _status]) => pubkeyb64, - ([pubkeyb64, status]) => - html` { + // Check whether the agent for which the statuses are rendered has only been told by others that + // the rendered agent exists + const onlyToldAbout = !!( + statuses.knownAgents && + statuses.knownAgents[pubkeyb64] && + statuses.knownAgents[pubkeyb64].type === 'told' + ); + + return html`` + .onlyToldAbout=${onlyToldAbout} + >`; + } )} { + const [pubkey_a, status_a] = a; + const [pubkey_b, status_b] = b; + // If both have equal connection status, sort by pubkey + if (status_a.type === status_b.type) { + return pubkey_a.localeCompare(pubkey_b); + } + // Disconnected is last + if (status_a.type === "Disconnected") return 1; + if (status_b.type === "Disconnected") return -1; + // Everything else gets sorted by pubkey + return pubkey_a.localeCompare(pubkey_b); + + // Connected is first - this is not being used for now as it may be harder to follow + // state changes visually if the icons move around + // if (status_a.type === "Connected") return 1; + // if (status_b.type === "Connected") return -1; + + +}