Skip to content

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.

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.

  1. List your contacts. useContacts returns everyone who shares a space with you, once each, with the spaces you share. It follows Space.spaces and each space’s Space.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 {
    type
    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.

    Contact
    ,
    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.

    useContacts
    ,
    const useHaloServices: () => Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    ,
    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's useMembers (which took a PublicKey; this takes a

    SpaceId

    ).

    useMembers
    ,
    } from '@dxos/halo-react';
    22 collapsed lines
    import { type
    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).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    } from '@dxos/keys';
    import {
    const Person: ({ name, online, }: {
    name?: string;
    online: boolean;
    }) => React.JSX.Element
    Person
    } from './ui.tsx';
    /** The members of one space, before the app lists contacts. */
    export const
    const Members: ({ spaceId }: {
    spaceId: SpaceId;
    }) => React.JSX.Element

    The members of one space, before the app lists contacts.

    Members
    = ({
    spaceId: SpaceId
    spaceId
    }: {
    spaceId: SpaceId
    spaceId
    :
    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).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    }) => {
    const
    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;
    }[]
    members
    =
    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's useMembers (which took a PublicKey; this takes a

    SpaceId

    ).

    useMembers
    (
    spaceId: SpaceId
    spaceId
    );
    return (
    <
    React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
    ul
    React.HTMLAttributes<HTMLUListElement>.className?: string | undefined
    className
    ='cb-list'>
    {
    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;
    }[]
    members
    .
    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.

    @param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

    @param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

    map
    ((
    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;
    }
    member
    ) => (
    <
    React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
    li
    React.Attributes.key?: React.Key | null | undefined
    key
    ={
    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;
    }
    member
    .
    did?: IdentityDid | undefined

    DID of the member identity, when known.

    did
    ??
    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;
    }
    member
    .
    identityKey?: string | undefined

    Hex-encoded identity key of the member, for avatar/presence keying.

    identityKey
    }
    React.HTMLAttributes<T>.className?: string | undefined
    className
    ='cb-item'>
    <
    const Person: ({ name, online, }: {
    name?: string;
    online: boolean;
    }) => React.JSX.Element
    Person
    name?: string | undefined
    name
    ={
    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;
    }
    member
    .
    displayName?: string | undefined
    displayName
    }
    online: boolean
    online
    ={
    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;
    }
    member
    .
    online: boolean

    Whether the member is currently online.

    online
    } />
    </
    React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
    li
    >
    ))}
    </
    React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
    ul
    >
    );
    };
    /** Everyone you share a space with, once each, with the spaces you share. */
    export const
    const Contacts: () => React.JSX.Element

    Everyone you share a space with, once each, with the spaces you share.

    Contacts
    = () => {
    const
    const services: Context<HaloServices>
    services
    =
    function useHaloServices(): Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    ();
    /** Everyone you share a space with, re-rendering on each change. */
    const
    const contacts: readonly Contact[]

    Everyone you share a space with, re-rendering on each change.

    contacts
    =
    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.

    useContacts
    ();
  2. Remove someone from a space. Space.removeMember revokes their membership. The × on a space beside a contact calls this, and that space leaves their row.

    src/app/Contacts.tsx
    import * as
    import Effect
    Effect
    from 'effect/Effect';
    2 collapsed lines
    import
    (alias) namespace React
    import React
    React
    from 'react';
    import {
    import Space
    Space
    } from '@dxos/halo';
    import {
    type
    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.

    Contact
    ,
    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.

    useContacts
    ,
    const useHaloServices: () => Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    ,
    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's useMembers (which took a PublicKey; this takes a

    SpaceId

    ).

    useMembers
    ,
    } from '@dxos/halo-react';
    25 collapsed lines
    import { type
    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).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    } from '@dxos/keys';
    import {
    const Person: ({ name, online, }: {
    name?: string;
    online: boolean;
    }) => React.JSX.Element
    Person
    } from './ui.tsx';
    /** The members of one space, before the app lists contacts. */
    export const
    const Members: ({ spaceId }: {
    spaceId: SpaceId;
    }) => React.JSX.Element

    The members of one space, before the app lists contacts.

    Members
    = ({
    spaceId: SpaceId
    spaceId
    }: {
    spaceId: SpaceId
    spaceId
    :
    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).

    @example BA25QRC2FEWCSAMRP4RZL65LWJ7352CKE

    SpaceId
    }) => {
    const
    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;
    }[]
    members
    =
    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's useMembers (which took a PublicKey; this takes a

    SpaceId

    ).

    useMembers
    (
    spaceId: SpaceId
    spaceId
    );
    return (
    <
    React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
    ul
    React.HTMLAttributes<HTMLUListElement>.className?: string | undefined
    className
    ='cb-list'>
    {
    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;
    }[]
    members
    .
    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.

    @param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

    @param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

    map
    ((
    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;
    }
    member
    ) => (
    <
    React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
    li
    React.Attributes.key?: React.Key | null | undefined
    key
    ={
    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;
    }
    member
    .
    did?: IdentityDid | undefined

    DID of the member identity, when known.

    did
    ??
    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;
    }
    member
    .
    identityKey?: string | undefined

    Hex-encoded identity key of the member, for avatar/presence keying.

    identityKey
    }
    React.HTMLAttributes<T>.className?: string | undefined
    className
    ='cb-item'>
    <
    const Person: ({ name, online, }: {
    name?: string;
    online: boolean;
    }) => React.JSX.Element
    Person
    name?: string | undefined
    name
    ={
    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;
    }
    member
    .
    displayName?: string | undefined
    displayName
    }
    online: boolean
    online
    ={
    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;
    }
    member
    .
    online: boolean

    Whether the member is currently online.

    online
    } />
    </
    React.JSX.IntrinsicElements.li: React.DetailedHTMLProps<React.LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>
    li
    >
    ))}
    </
    React.JSX.IntrinsicElements.ul: React.DetailedHTMLProps<React.HTMLAttributes<HTMLUListElement>, HTMLUListElement>
    ul
    >
    );
    };
    /** Everyone you share a space with, once each, with the spaces you share. */
    export const
    const Contacts: () => React.JSX.Element

    Everyone you share a space with, once each, with the spaces you share.

    Contacts
    = () => {
    const
    const services: Context<HaloServices>
    services
    =
    function useHaloServices(): Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    ();
    /** Everyone you share a space with, re-rendering on each change. */
    const
    const contacts: readonly Contact[]

    Everyone you share a space with, re-rendering on each change.

    contacts
    =
    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.

    useContacts
    ();
    /** Removes `contact` from `space`. */
    const
    const handleRemove: (contact: Contact, space: Space.Info) => Promise<void>

    Removes contact from space.

    handleRemove
    = (
    contact: Contact
    contact
    :
    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.

    Contact
    ,
    space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    :
    import Space
    Space
    .
    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 Database service keyed by

    SpaceId

    , not on this value.

    Info
    ) =>
    import Space
    Space
    .
    const removeMember: (id: SpaceId, subject: IdentityDid) => Effect.Effect<void, SpaceError, Space.Service>

    Remove a member (requires

    Service

    ).

    removeMember
    (
    space: {
    readonly id: SpaceId;
    readonly state: "inactive" | "closed" | "ready";
    readonly name?: string | undefined;
    }
    space
    .
    id: SpaceId
    id
    ,
    contact: Contact
    contact
    .
    did: IdentityDid
    did
    ).
    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)
    pipe
    (
    import Effect
    Effect
    .
    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.local to 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* Database
    return yield* db.query("SELECT * FROM users")
    })
    const provided = Effect.provide(program, DatabaseLayer)
    await Effect.runPromise(provided) // => "Result for: SELECT * FROM users"

    @stability ― stable

    @category ― providing services

    @since ― 2.0.0

    provide
    (
    const services: Context<HaloServices>
    services
    ),
    import Effect
    Effect
    .
    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 Promise syntax, 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)) // => 1

    Example (Running effects as promises)

    //Example: Handling a Failing Effect as a Rejected Promise
    import { Effect } from "effect"
    const output: Array<unknown> = []
    await Effect.runPromise(Effect.fail("my error")).catch(() => {
    void output.push("rejected")
    })
    output // => ["rejected"]

    @see ― runPromiseExit for a version that returns an Exit type instead of rejecting.

    @stability ― stable

    @category ― running

    @since ― 2.0.0

    runPromise
    ,
    );

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.

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.