Your contacts
By the end of this page the contact book lists everyone you share a space with, once each, with the spaces you share, and you can remove someone from a space.
Background
Section titled “Background”A contact is anyone who shares a space with you. The contact book keeps no list of its own: it reads each space’s members and merges them by DID, since a member’s DID is the same in every space.
A member is an identity with an access level: pull, read, edit or admin. Removing a member is a revocation, the
signed opposite of the delegation that let them in. The space’s group key changes with it, so they cannot read anything
written after. An admin can remove anyone; changing a member’s access instead is on the
Space API page.
-
List your contacts.
useContactsreturns everyone who shares a space with you, once each, with the spaces you share. It followsSpace.spacesand each space’sSpace.members, leaves you out, and re-renders when someone joins, leaves or comes online. The Contacts tab shows a row for each contact.src/app/Contacts.tsx import {typeContact,type Contact = {readonly did: IdentityDid;readonly displayName?: string;readonly online: boolean;readonly spaces: readonly Space.Info[];}Someone who shares at least one ready space with the local identity.
useContacts,const useContacts: () => readonly Contact[]Returns everyone the local identity shares a ready space with, once each, with the spaces they share. Reactive: re-renders when a space is joined or left, or its members change.
useHaloServices,const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
useMembers,const useMembers: (spaceId?: SpaceId) => readonly Space.Member[]Returns the membership of a space. Reactive: re-renders on join/leave/role/presence changes. Replaces
@dxos/react-client'suseMembers(which took aPublicKey; this takes aSpaceId
).
} from '@dxos/halo-react';22 collapsed linesimport { typeSpaceId } from '@dxos/keys';type SpaceId = string & {__SpaceId: true;}const SpaceId: Codec<SpaceId, string, never, never> & {byteLength: number;encode: (value: Uint8Array) => SpaceId;decode: (value: SpaceId) => Uint8Array;isValid: (value: unknown) => value is SpaceId;make: (value: string) => SpaceId;random: () => SpaceId;}A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).
import {Person } from './ui.tsx';const Person: ({ name, online, }: {name?: string;online: boolean;}) => React.JSX.Element/** The members of one space, before the app lists contacts. */export constMembers = ({const Members: ({ spaceId }: {spaceId: SpaceId;}) => React.JSX.ElementThe members of one space, before the app lists contacts.
spaceId }: {spaceId: SpaceIdspaceId:spaceId: SpaceIdSpaceId }) => {type SpaceId = string & {__SpaceId: true;}A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).
constmembers =const members: readonly {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}[]useMembers(function useMembers(spaceId?: SpaceId): readonly Space.Member[]Returns the membership of a space. Reactive: re-renders on join/leave/role/presence changes. Replaces
@dxos/react-client'suseMembers(which took aPublicKey; this takes aSpaceId
).
spaceId);spaceId: SpaceIdreturn (<ulReact.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>className='cb-list'>React.HTMLAttributes<HTMLUListElement>.className?: string | undefined{members.const members: readonly {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}[]map((ReadonlyArray<{ readonly role: "pull" | "read" | "edit" | "admin"; readonly online: boolean; readonly did?: IdentityDid | undefined; readonly identityKey?: string | undefined; readonly displayName?: string | undefined; readonly data?: { ...; } | undefined; }>.map<React.JSX.Element>(callbackfn: (value: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}, index: number, array: readonly {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}[]) => React.JSX.Element, thisArg?: any): React.JSX.Element[]Calls a defined callback function on each element of an array, and returns an array that contains the results.
member) => (member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}<liReact.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>key={React.Attributes.key?: React.Key | null | undefinedmember.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}did ??did?: IdentityDid | undefinedDID of the member identity, when known.
member.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}identityKey}identityKey?: string | undefinedHex-encoded identity key of the member, for avatar/presence keying.
className='cb-item'>React.HTMLAttributes<T>.className?: string | undefined<Personconst Person: ({ name, online, }: {name?: string;online: boolean;}) => React.JSX.Elementname={name?: string | undefinedmember.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}displayName}displayName?: string | undefinedonline={online: booleanmember.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}online} />online: booleanWhether the member is currently online.
</li>React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>))}</ul>React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>);};/** Everyone you share a space with, once each, with the spaces you share. */export constContacts = () => {const Contacts: () => React.JSX.ElementEveryone you share a space with, once each, with the spaces you share.
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
/** Everyone you share a space with, re-rendering on each change. */constcontacts =const contacts: readonly Contact[]Everyone you share a space with, re-rendering on each change.
useContacts();function useContacts(): readonly Contact[]Returns everyone the local identity shares a ready space with, once each, with the spaces they share. Reactive: re-renders when a space is joined or left, or its members change.
-
Remove someone from a space.
Space.removeMemberrevokes their membership. The × on a space beside a contact calls this, and that space leaves their row.src/app/Contacts.tsx import * asEffect from 'effect/Effect';import Effect2 collapsed linesimportReact from 'react';(alias) namespace Reactimport Reactimport {Space } from '@dxos/halo';import Spaceimport {typeContact,type Contact = {readonly did: IdentityDid;readonly displayName?: string;readonly online: boolean;readonly spaces: readonly Space.Info[];}Someone who shares at least one ready space with the local identity.
useContacts,const useContacts: () => readonly Contact[]Returns everyone the local identity shares a ready space with, once each, with the spaces they share. Reactive: re-renders when a space is joined or left, or its members change.
useHaloServices,const useHaloServices: () => Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
useMembers,const useMembers: (spaceId?: SpaceId) => readonly Space.Member[]Returns the membership of a space. Reactive: re-renders on join/leave/role/presence changes. Replaces
@dxos/react-client'suseMembers(which took aPublicKey; this takes aSpaceId
).
} from '@dxos/halo-react';25 collapsed linesimport { typeSpaceId } from '@dxos/keys';type SpaceId = string & {__SpaceId: true;}const SpaceId: Codec<SpaceId, string, never, never> & {byteLength: number;encode: (value: Uint8Array) => SpaceId;decode: (value: SpaceId) => Uint8Array;isValid: (value: unknown) => value is SpaceId;make: (value: string) => SpaceId;random: () => SpaceId;}A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).
import {Person } from './ui.tsx';const Person: ({ name, online, }: {name?: string;online: boolean;}) => React.JSX.Element/** The members of one space, before the app lists contacts. */export constMembers = ({const Members: ({ spaceId }: {spaceId: SpaceId;}) => React.JSX.ElementThe members of one space, before the app lists contacts.
spaceId }: {spaceId: SpaceIdspaceId:spaceId: SpaceIdSpaceId }) => {type SpaceId = string & {__SpaceId: true;}A unique identifier for a space. Space keys are generated by creating a keypair, and then taking the first 20 bytes of the SHA-256 hash of the public key and encoding them to multibase RFC4648 base-32 format (prefixed with B, see Multibase Table).
constmembers =const members: readonly {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}[]useMembers(function useMembers(spaceId?: SpaceId): readonly Space.Member[]Returns the membership of a space. Reactive: re-renders on join/leave/role/presence changes. Replaces
@dxos/react-client'suseMembers(which took aPublicKey; this takes aSpaceId
).
spaceId);spaceId: SpaceIdreturn (<ulReact.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>className='cb-list'>React.HTMLAttributes<HTMLUListElement>.className?: string | undefined{members.const members: readonly {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}[]map((ReadonlyArray<{ readonly role: "pull" | "read" | "edit" | "admin"; readonly online: boolean; readonly did?: IdentityDid | undefined; readonly identityKey?: string | undefined; readonly displayName?: string | undefined; readonly data?: { ...; } | undefined; }>.map<React.JSX.Element>(callbackfn: (value: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}, index: number, array: readonly {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}[]) => React.JSX.Element, thisArg?: any): React.JSX.Element[]Calls a defined callback function on each element of an array, and returns an array that contains the results.
member) => (member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}<liReact.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>key={React.Attributes.key?: React.Key | null | undefinedmember.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}did ??did?: IdentityDid | undefinedDID of the member identity, when known.
member.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}identityKey}identityKey?: string | undefinedHex-encoded identity key of the member, for avatar/presence keying.
className='cb-item'>React.HTMLAttributes<T>.className?: string | undefined<Personconst Person: ({ name, online, }: {name?: string;online: boolean;}) => React.JSX.Elementname={name?: string | undefinedmember.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}displayName}displayName?: string | undefinedonline={online: booleanmember.member: {readonly role: "pull" | "read" | "edit" | "admin";readonly online: boolean;readonly did?: IdentityDid | undefined;readonly identityKey?: string | undefined;readonly displayName?: string | undefined;readonly data?: {readonly [x: string]: any;} | undefined;}online} />online: booleanWhether the member is currently online.
</li>React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>))}</ul>React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>);};/** Everyone you share a space with, once each, with the spaces you share. */export constContacts = () => {const Contacts: () => React.JSX.ElementEveryone you share a space with, once each, with the spaces you share.
constservices =const services: Context<HaloServices>useHaloServices();function useHaloServices(): Context<HaloServices>Returns the HALO services context. Throws if no
HaloProvider
is above in the tree.
/** Everyone you share a space with, re-rendering on each change. */constcontacts =const contacts: readonly Contact[]Everyone you share a space with, re-rendering on each change.
useContacts();function useContacts(): readonly Contact[]Returns everyone the local identity shares a ready space with, once each, with the spaces they share. Reactive: re-renders when a space is joined or left, or its members change.
/** Removes `contact` from `space`. */consthandleRemove = (const handleRemove: (contact: Contact, space: Space.Info) => Promise<void>Removes
contactfromspace.contact:contact: ContactContact,type Contact = {readonly did: IdentityDid;readonly displayName?: string;readonly online: boolean;readonly spaces: readonly Space.Info[];}Someone who shares at least one ready space with the local identity.
space:space: {readonly id: SpaceId;readonly state: "inactive" | "closed" | "ready";readonly name?: string | undefined;}Space.import SpaceInfo) =>type Info = {readonly id: SpaceId;readonly state: "inactive" | "closed" | "ready";readonly name?: string | undefined;}Public view of a space. A plain snapshot — database access stays on the ECHO
Databaseservice keyed bySpaceId
, not on this value.
Space.import SpaceremoveMember(const removeMember: (id: SpaceId, subject: IdentityDid) => Effect.Effect<void, SpaceError, Space.Service>Remove a member (requires
Service
).
space.space: {readonly id: SpaceId;readonly state: "inactive" | "closed" | "ready";readonly name?: string | undefined;}id,id: SpaceIdcontact.contact: Contactdid).did: IdentityDidpipe(Pipeable.pipe<Effect.Effect<void, SpaceError, Space.Service>, Effect.Effect<void, SpaceError, never>, Promise<void>>(this: Effect.Effect<void, SpaceError, Space.Service>, ab: (_: Effect.Effect<void, SpaceError, Space.Service>) => Effect.Effect<void, SpaceError, never>, bc: (_: Effect.Effect<void, SpaceError, never>) => Promise<void>): Promise<void> (+21 overloads)Effect.import Effectprovide(const provide: <HaloServices>(context: Context<HaloServices>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, HaloServices>> (+5 overloads)Provides dependencies to an effect using layers or a context. Use
options.localto build the layer every time; by default, layers are shared between provide calls.Example (Providing dependencies with a layer)
import { Context, Effect, Layer } from "effect"interface Database {readonly query: (sql: string) => Effect.Effect<string>}const Database = Context.Service<Database>("Database")const DatabaseLayer = Layer.succeed(Database)({query: Effect.fn("Database.query")((sql: string) => Effect.succeed(`Result for: ${sql}`))})const program = Effect.gen(function*() {const db = yield* Databasereturn yield* db.query("SELECT * FROM users")})const provided = Effect.provide(program, DatabaseLayer)await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"services),const services: Context<HaloServices>Effect.import EffectrunPromise,const runPromise: <A, E>(effect: Effect.Effect<A, E>, options?: Effect.RunOptions | undefined) => Promise<A>Executes an effect and returns the result as a
Promise.When to use
Use when you need to execute an effect and work with the result using
Promisesyntax, typically for compatibility with other promise-based code.If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.
Example (Running a successful effect as a Promise)
import { Effect } from "effect"await Effect.runPromise(Effect.succeed(1)) // => 1Example (Running effects as promises)
//Example: Handling a Failing Effect as a Rejected Promiseimport { Effect } from "effect"const output: Array<unknown> = []await Effect.runPromise(Effect.fail("my error")).catch(() => {void output.push("rejected")})output // => ["rejected"]);
Try it
Section titled “Try it”Alice, Bob and Carol are all in Book club, and Alice and Carol are in Climbing. Open Contacts on any device: Alice sees Bob with Book club, and Carol with Book club and Climbing. Remove Carol from Climbing on Alice’s device, and Climbing leaves her row.
What you can do now
Section titled “What you can do now”You can list everyone you share a space with and remove someone from a space. Next, Add a device puts the same contacts on your phone.