Skip to content

Commit

Permalink
add option to block agent for the duration of a call
Browse files Browse the repository at this point in the history
  • Loading branch information
matthme committed Dec 8, 2024
1 parent 6dc8e37 commit daaf5db
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 172 deletions.
4 changes: 3 additions & 1 deletion ui/src/agent-connection-status-icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
Profile,
} from '@holochain-open-dev/profiles';
import { EntryRecord } from '@holochain-open-dev/utils';
import { ConnectionStatus } from './room-view';
import { ConnectionStatus } from './streams-store';
import { connectionStatusToColor } from './utils';
import { sharedStyles } from './sharedStyles';
import './holo-identicon';
Expand Down Expand Up @@ -102,6 +102,8 @@ export class AgentConnectionStatusIcon extends LitElement {
}...`;
case 'SdpExchange':
return 'exchanging SDP data...';
case "Blocked":
return "Blocked";
default:
return 'unknown status type';
}
Expand Down
141 changes: 114 additions & 27 deletions ui/src/agent-connection-status.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { consume } from '@lit/context';
import { hashProperty, sharedStyles } from '@holochain-open-dev/elements';
import {
hashProperty,
sharedStyles,
wrapPathInSvg,
} from '@holochain-open-dev/elements';
import { css, html, LitElement, PropertyValueMap } from 'lit';
import { property, customElement } from 'lit/decorators.js';
import { property, customElement, state } from 'lit/decorators.js';
import { AgentPubKey, encodeHashToBase64 } from '@holochain/client';
import { localized, msg } from '@lit/localize';
import { StoreSubscriber } from '@holochain-open-dev/stores';
import { mdiCancel } from '@mdi/js';

import '@holochain-open-dev/elements/dist/elements/display-error.js';
import '@shoelace-style/shoelace/dist/components/avatar/avatar.js';
Expand All @@ -17,14 +22,17 @@ import {
Profile,
} from '@holochain-open-dev/profiles';
import { EntryRecord } from '@holochain-open-dev/utils';
import { ConnectionStatus } from './room-view';
import { ConnectionStatus, StreamsStore } from './streams-store';
import { connectionStatusToColor } from './utils';
import './holo-identicon';
import { streamsStoreContext } from './contexts';

@localized()
@customElement('agent-connection-status')
export class AgentConnectionStatus extends LitElement {
/** Public properties */
@consume({ context: streamsStoreContext, subscribe: true })
@state()
streamsStore!: StreamsStore;

/**
* REQUIRED. The public key identifying the agent whose profile is going to be shown.
Expand Down Expand Up @@ -62,6 +70,13 @@ export class AgentConnectionStatus extends LitElement {
() => [this.agentPubKey, this.store]
);

_isAgentBlocked = new StoreSubscriber(
this,
() =>
this.streamsStore.isAgentBlocked(encodeHashToBase64(this.agentPubKey)),
() => [this.agentPubKey, this.streamsStore]
);

async willUpdate(
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
) {
Expand Down Expand Up @@ -109,6 +124,8 @@ export class AgentConnectionStatus extends LitElement {
}...`;
case 'SdpExchange':
return 'exchanging SDP data...';
case 'Blocked':
return 'Blocked';
default:
return 'unknown status type';
}
Expand All @@ -117,42 +134,73 @@ export class AgentConnectionStatus extends LitElement {
renderProfile(profile: EntryRecord<Profile> | undefined) {
return html`
<div
class="row"
title="${this.appVersion ? `Uses Presence v${this.appVersion}` : `Uses unknown Presence version`}"
class="row flex-1"
style="align-items: center; margin: 0; padding: 0; ${!this
.connectionStatus || this.connectionStatus.type === 'Disconnected'
? 'opacity: 0.5'
: ''}"
>
${profile && profile.entry.fields.avatar
? html`
<img
style="height: ${this.size}px; width: ${this
.size}px; border-radius: 50%;"
src=${profile.entry.fields.avatar}
alt="${profile.entry.nickname}'s avatar"
/>
`
: html`
<holo-identicon
.disableCopy=${true}
.disableTooltip=${true}
.hash=${this.agentPubKey}
.size=${this.size}
title="${encodeHashToBase64(this.agentPubKey)}"
>
</holo-identicon>
`}
<sl-tooltip
class="tooltip-filled"
content="${this.appVersion
? `Uses Presence v${this.appVersion}`
: `Uses unknown Presence version`}"
>
${profile && profile.entry.fields.avatar
? html`
<img
style="height: ${this.size}px; width: ${this
.size}px; border-radius: 50%;"
src=${profile.entry.fields.avatar}
alt="${profile.entry.nickname}'s avatar"
/>
`
: html`
<holo-identicon
.disableCopy=${true}
.disableTooltip=${true}
.hash=${this.agentPubKey}
.size=${this.size}
title="${encodeHashToBase64(this.agentPubKey)}"
>
</holo-identicon>
`}
</sl-tooltip>
<div class="column" style="align-items: flex-start;">
<span
style="margin-left: 10px; margin-bottom: -12px; font-size: 23px; font-weight: 600; color: #c3c9eb;"
>${profile ? profile.entry.nickname : 'Unknown'}</span
>
<span
style="margin-left: 12px; font-size: 14px; color: ${connectionStatusToColor(this.connectionStatus, 'gray')}; font-weight: 600;"
style="margin-left: 12px; font-size: 14px; color: ${connectionStatusToColor(
this.connectionStatus,
'gray'
)}; font-weight: 600;"
>${this.statusToText(this.connectionStatus)}</span
>
</div>
<span style="display: flex; flex: 1;"></span>
<sl-tooltip
class="tooltip-filled ${this._isAgentBlocked.value ? 'unblock' : ''}"
content=${this._isAgentBlocked.value
? msg('Unblock this person')
: msg('Block this person for the duration of this call.')}
>
<sl-icon-button
src=${wrapPathInSvg(mdiCancel)}
@click=${() => {
if (this._isAgentBlocked.value) {
this.streamsStore.unblockAgent(
encodeHashToBase64(this.agentPubKey)
);
} else {
this.streamsStore.blockAgent(
encodeHashToBase64(this.agentPubKey)
);
}
}}
></sl-icon-button>
</sl-tooltip>
</div>
`;
}
Expand All @@ -179,5 +227,44 @@ export class AgentConnectionStatus extends LitElement {
}
}

static styles = [sharedStyles, css``];
static styles = [
sharedStyles,
css`
sl-icon-button::part(base) {
color: #c72100;
}
sl-icon-button::part(base):hover,
sl-icon-button::part(base):focus {
color: #e35d42;
}
sl-icon-button::part(base):active {
color: #e35d42;
}
.unblock {
background: 'green';
}
.unblock sl-icon-button::part(base) {
color: #09b500;
}
.unblock sl-icon-button::part(base):hover,
.unblock sl-icon-button::part(base):focus {
color: #39e430;
}
.unblock sl-icon-button::part(base):active {
color: #39e430;
}
.tooltip-filled {
--sl-tooltip-background-color: #c3c9eb;
--sl-tooltip-arrow-size: 6px;
--sl-tooltip-border-radius: 5px;
--sl-tooltip-padding: 4px;
--sl-tooltip-font-size: 14px;
--sl-tooltip-color: #0d1543;
--sl-tooltip-font-family: 'Ubuntu', sans-serif;
}
`,
];
}
146 changes: 14 additions & 132 deletions ui/src/room-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,136 +43,12 @@ import './agent-connection-status';
import './agent-connection-status-icon';
import './toggle-switch';
import { sortConnectionStatuses } from './utils';
import { StreamsStore } from './streams-store';

const ICE_CONFIG = [
{ urls: 'stun:global.stun.twilio.com:3478' },
{ urls: 'stun:stun.l.google.com:19302' },
];

/**
* If an InitRequest does not succeed within this duration (ms) another InitRequest will be sent
*/
const INIT_RETRY_THRESHOLD = 5000;

const PING_INTERVAL = 2000;

type ConnectionId = string;

type RTCMessage =
| {
type: 'action';
message: 'video-off' | 'audio-off' | 'audio-on';
}
| {
type: 'text';
message: string;
};

type OpenConnectionInfo = {
connectionId: ConnectionId;
peer: SimplePeer.Instance;
video: boolean;
audio: boolean;
connected: boolean;
direction: 'outgoing' | 'incoming' | 'duplex'; // In which direction streams are expected
};

type PendingInit = {
/**
* UUID to identify the connection
*/
connectionId: ConnectionId;
/**
* Timestamp when init was sent. If InitAccept is not received within a certain duration
* after t0, a next InitRequest is sent.
*/
t0: number;
};

type PendingAccept = {
/**
* UUID to identify the connection
*/
connectionId: ConnectionId;
/**
* Peer instance that was created with this accept. Gets destroyed if another Peer object makes it through
* to connected state instead for a connection with the same Agent.
*/
peer: SimplePeer.Instance;
};

type PongMetaData<T> = {
formatVersion: number;
data: T;
};

type PongMetaDataV1 = {
connectionStatuses: ConnectionStatuses;
screenShareConnectionStatuses?: ConnectionStatuses;
knownAgents?: Record<AgentPubKeyB64, AgentInfo>;
appVersion?: string;
};

type ConnectionStatuses = Record<AgentPubKeyB64, ConnectionStatus>;

/**
* Connection status with a peer
*/
export type ConnectionStatus =
| {
/**
* No WebRTC connection or freshly disconnected
*/
type: 'Disconnected';
}
| {
/**
* Waiting for an init of a peer whose pubkey is alphabetically higher than ours
*/
type: 'AwaitingInit';
}
| {
/**
* Waiting for an Accept of a peer whose pubkey is alphabetically lower than ours
*/
type: 'InitSent';
attemptCount?: number;
}
| {
/**
* Waiting for SDP exchange to start
*/
type: 'AcceptSent';
attemptCount?: number;
}
| {
/**
* SDP exchange is ongoing
*/
type: 'SdpExchange';
}
| {
/**
* WebRTC connection is established
*/
type: 'Connected';
};

type AgentInfo = {
pubkey: AgentPubKeyB64;
/**
* If I know from the all_agents anchor that this agent exists in the Room, the
* type is "known". If I've learnt about this agent only from other's Pong meta data
* or from receiving a Pong from that agent themselves the type is "told".
*/
type: 'known' | 'told';
/**
* last time when a PongUi from this agent was received
*/
lastSeen?: number;
appVersion?: string;
};
import {
AgentInfo,
ConnectionStatuses,
PING_INTERVAL,
StreamsStore,
} from './streams-store';

@localized()
@customElement('room-view')
Expand Down Expand Up @@ -643,13 +519,17 @@ export class RoomView extends LitElement {
const presentAgents = knownAgentsKeysB64
.filter(pubkeyB64 => {
const status = this._connectionStatuses.value[pubkeyB64];
return !!status && status.type !== 'Disconnected';
return (
!!status &&
status.type !== 'Disconnected' &&
status.type !== 'Blocked'
);
})
.sort((key_a, key_b) => key_a.localeCompare(key_b));
const absentAgents = knownAgentsKeysB64
.filter(pubkeyB64 => {
const status = this._connectionStatuses.value[pubkeyB64];
return !status || status.type === 'Disconnected';
return !status || status.type === 'Disconnected' || status.type === "Blocked";
})
.sort((key_a, key_b) => key_a.localeCompare(key_b));
return html`
Expand All @@ -667,6 +547,7 @@ export class RoomView extends LitElement {
pubkey => pubkey,
pubkey => html`
<agent-connection-status
style="width: 100%;"
.agentPubKey=${decodeHashFromBase64(pubkey)}
.connectionStatus=${this._connectionStatuses.value[pubkey]}
.appVersion=${this._knownAgents.value[pubkey].appVersion}
Expand All @@ -688,6 +569,7 @@ export class RoomView extends LitElement {
pubkey => pubkey,
pubkey => html`
<agent-connection-status
style="width: 100%;"
.agentPubKey=${decodeHashFromBase64(pubkey)}
.connectionStatus=${this._connectionStatuses.value[pubkey]}
></agent-connection-status>
Expand Down
Loading

0 comments on commit daaf5db

Please sign in to comment.