Skip to content

Add a device

By the end of this page Alice’s phone joins her identity, and shows her name, her spaces and her contacts.

Each device has its own key. Adding a device is the same handshake as inviting a person, with a device invitation instead of a space invitation: the device already in the identity is the host, and the new device the guest. At the end, the host writes a delegation that makes the new device a member of the identity’s group.

The new device then syncs the identity’s personal space and every space the identity is in, from the other device or through EDGE. Nothing about the identity has to be typed in again.

  1. Share your identity. Identity.share starts a device invitation on the device you have. Add a device on the Devices tab calls this and shows the code.

    src/app/Devices.tsx
    import * as
    import Effect
    Effect
    from 'effect/Effect';
    2 collapsed lines
    import
    (alias) namespace React
    import React
    React
    , {
    function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    } from 'react';
    import {
    import Identity
    Identity
    ,
    import Invitation
    Invitation
    } from '@dxos/halo';
    9 collapsed lines
    import {
    const useDevices: () => readonly Identity.DeviceInfo[]

    Returns the devices belonging to the local identity. Reactive. Replaces @dxos/react-client's useDevices.

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

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    } from '@dxos/halo-react';
    import {
    const Host: ({ start, send, }: {
    start: () => Promise<Flow>;
    send: string;
    }) => React.JSX.Element

    The host's side of an invitation: the code to send, then the authorization code to tell.

    Host
    } from './Invitations.tsx';
    /** Your identity's devices, and a way to add another. */
    export const
    const Devices: () => React.JSX.Element

    Your identity's devices, and a way to add another.

    Devices
    = () => {
    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
    ();
    const [
    const adding: boolean
    adding
    ,
    const setAdding: React.Dispatch<React.SetStateAction<boolean>>
    setAdding
    ] =
    useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    (false);
    /** Starts an invitation for another device of yours, and returns the flow and the code to open there. */
    const
    const handleAdd: () => Promise<{
    flow: Invitation.Flow;
    code: string;
    }>

    Starts an invitation for another device of yours, and returns the flow and the code to open there.

    handleAdd
    = () =>
    import Effect
    Effect
    .
    const gen: <Effect.Effect<Invitation.Flow, IdentityError, Identity.Service> | Effect.Effect<string, InvitationError, never>, {
    flow: Invitation.Flow;
    code: string;
    }>(f: () => Generator<Effect.Effect<Invitation.Flow, IdentityError, Identity.Service> | Effect.Effect<string, InvitationError, never>, {
    flow: Invitation.Flow;
    code: string;
    }, never>) => Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, Identity.Service> (+1 overload)

    Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

    When to use

    Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

    Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

    Example (Sequencing effects with generators)

    import { Data, Effect } from "effect"
    class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
    const addServiceCharge = (amount: number) => amount + 1
    const applyDiscount = (
    total: number,
    discountRate: number
    ): Effect.Effect<number, DiscountRateError> =>
    discountRate === 0
    ? Effect.fail(new DiscountRateError())
    : Effect.succeed(total - (total * discountRate) / 100)
    const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
    const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
    export const program = Effect.gen(function*() {
    const transactionAmount = yield* fetchTransactionAmount
    const discountRate = yield* fetchDiscountRate
    const discountedAmount = yield* applyDiscount(
    transactionAmount,
    discountRate
    )
    const finalAmount = addServiceCharge(discountedAmount)
    return `Final amount to charge: ${finalAmount}`
    })
    await Effect.runPromise(program) // => "Final amount to charge: 96"

    @stability ― stable

    @category ― constructors

    @since ― 2.0.0

    gen
    (function* () {
    const
    const flow: Invitation.Flow
    flow
    = yield*
    import Identity
    Identity
    .
    const share: (options?: Invitation.ShareOptions) => Effect.Effect<Invitation.Flow, IdentityError, Identity.Service>

    Initiate a device invitation (requires

    Service

    ).

    share
    ({
    authMethod?: Invitation.AuthMethod | undefined
    authMethod
    : 'shared-secret' });
    const
    const code: string
    code
    = yield*
    import Invitation
    Invitation
    .
    const code: (flow: Invitation.Flow) => Effect.Effect<string, InvitationError>

    Encoded, shareable code for a flow.

    code
    (
    const flow: Invitation.Flow
    flow
    );
    return {
    flow: Invitation.Flow
    flow
    ,
    code: string
    code
    };
    }).
    Pipeable.pipe<Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, Identity.Service>, Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, never>, Promise<{
    flow: Invitation.Flow;
    code: string;
    }>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, Identity.Service>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+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
    );
  2. Join from the new device. On the new device, before it has an identity, Identity.join redeems the code. The handshake and its authorization code are the same as for a space, so the welcome screen hands the flow to the same guest panel as Join. Join your other device calls this.

    src/app/Welcome.tsx
    import * as
    import Effect
    Effect
    from 'effect/Effect';
    2 collapsed lines
    import
    (alias) namespace React
    import React
    React
    , {
    function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    } from 'react';
    import {
    import Identity
    Identity
    } from '@dxos/halo';
    24 collapsed lines
    import {
    const useHaloServices: () => Context<HaloServices>

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    } from '@dxos/halo-react';
    import { type
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    ,
    const reached: (stage: Stage, at: Stage) => boolean

    Whether stage includes what at adds.

    reached
    } from '../stage.ts';
    import {
    const Guest: ({ label, join, onDone, }: {
    label: string;
    join: (code: string) => Promise<Flow>;
    onDone?: () => void;
    }) => React.JSX.Element

    The guest's side of an invitation: paste the code, then type the authorization code.

    Guest
    } from './Invitations.tsx';
    import {
    const Prompt: ({ label, action, initial, multiline, compact, autoFocus, onSubmit, }: {
    label: string;
    action: string;
    initial?: string;
    multiline?: boolean;
    compact?: boolean;
    autoFocus?: boolean;
    onSubmit: (value: string) => Promise<unknown>;
    }) => React.JSX.Element

    A field and a button, which shows the error if onSubmit fails.

    Prompt
    } from './ui.tsx';
    /** The device before it has an identity: start one, join one from another device, or recover one. */
    export const
    const Welcome: ({ stage }: {
    stage: Stage;
    }) => React.JSX.Element

    The device before it has an identity: start one, join one from another device, or recover one.

    Welcome
    = ({
    stage: Stage
    stage
    }: {
    stage: Stage
    stage
    :
    type Stage = "name" | "spaces" | "invite" | "contacts" | "devices" | "recovery"

    How far a page of the learning path has built the app, in the order the path builds it.

    Stage
    }) => {
    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
    ();
    const [
    const mode: "new" | "device" | "recover"
    mode
    ,
    const setMode: React.Dispatch<React.SetStateAction<"new" | "device" | "recover">>
    setMode
    ] =
    useState<"new" | "device" | "recover">(initialState: "new" | "device" | "recover" | (() => "new" | "device" | "recover")): ["new" | "device" | "recover", React.Dispatch<React.SetStateAction<"new" | "device" | "recover">>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    <'new' | 'device' | 'recover'>('new');
    // Focusing the first field on load would scroll the page to the embed.
    const [
    const picked: boolean
    picked
    ,
    const setPicked: React.Dispatch<React.SetStateAction<boolean>>
    setPicked
    ] =
    useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    (false);
    const
    const pick: (next: "new" | "device" | "recover") => void
    pick
    = (
    next: "new" | "device" | "recover"
    next
    : typeof
    const mode: "new" | "device" | "recover"
    mode
    ) => {
    const setPicked: (value: React.SetStateAction<boolean>) => void
    setPicked
    (true);
    const setMode: (value: React.SetStateAction<"new" | "device" | "recover">) => void
    setMode
    (
    next: "new" | "device" | "recover"
    next
    );
    };
    /** Creates the identity, with this device as its first member. */
    const
    const handleStart: (name: string) => Promise<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }>

    Creates the identity, with this device as its first member.

    handleStart
    = (
    name: string
    name
    : string) =>
    import Identity
    Identity
    .
    const create: (options?: {
    displayName?: string;
    data?: Record<string, unknown>;
    deviceLabel?: string;
    }) => Effect.Effect<Identity.Info, IdentityError, Identity.Service>

    Create the local identity (requires

    Service

    ).

    create
    ({
    displayName?: string | undefined
    displayName
    :
    name: string
    name
    }).
    Pipeable.pipe<Effect.Effect<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }, IdentityError, Identity.Service>, Effect.Effect<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }, IdentityError, never>, Promise<{
    readonly did: IdentityDid;
    readonly displayName?: string | undefined;
    readonly identityKey?: string | undefined;
    readonly data?: {
    readonly [x: string]: any;
    } | undefined;
    }>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+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
    ,
    );
    /** Redeems the invitation `code` from your other device, and returns the flow. */
    const
    const handleJoin: (code: string) => Promise<Flow>

    Redeems the invitation code from your other device, and returns the flow.

    handleJoin
    = (
    code: string
    code
    : string) =>
    import Identity
    Identity
    .
    const join: (code: string) => Effect.Effect<Flow, IdentityError, Identity.Service>

    Redeem a device-invitation code (requires

    Service

    ).

    join
    (
    code: string
    code
    ).
    Pipeable.pipe<Effect.Effect<Flow, IdentityError, Identity.Service>, Effect.Effect<Flow, IdentityError, never>, Promise<Flow>>(this: Effect.Effect<Flow, IdentityError, Identity.Service>, ab: (_: Effect.Effect<Flow, IdentityError, Identity.Service>) => Effect.Effect<Flow, IdentityError, never>, bc: (_: Effect.Effect<Flow, IdentityError, never>) => Promise<Flow>): Promise<...> (+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
    );
  3. List your devices. useDevices returns the identity’s devices and re-renders when one joins or comes online. It follows Identity.devices. Each device has a key, a kind, whether it is online, and whether it is the device the code runs on. The Devices tab shows them.

    src/app/Devices.tsx
    import {
    const useDevices: () => readonly Identity.DeviceInfo[]

    Returns the devices belonging to the local identity. Reactive. Replaces @dxos/react-client's useDevices.

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

    Returns the HALO services context. Throws if no

    HaloProvider

    is above in the tree.

    useHaloServices
    } from '@dxos/halo-react';
    16 collapsed lines
    import {
    const Host: ({ start, send, }: {
    start: () => Promise<Flow>;
    send: string;
    }) => React.JSX.Element

    The host's side of an invitation: the code to send, then the authorization code to tell.

    Host
    } from './Invitations.tsx';
    /** Your identity's devices, and a way to add another. */
    export const
    const Devices: () => React.JSX.Element

    Your identity's devices, and a way to add another.

    Devices
    = () => {
    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
    ();
    const [
    const adding: boolean
    adding
    ,
    const setAdding: React.Dispatch<React.SetStateAction<boolean>>
    setAdding
    ] =
    useState<boolean>(initialState: boolean | (() => boolean)): [boolean, React.Dispatch<React.SetStateAction<boolean>>] (+1 overload)

    Returns a stateful value, and a function to update it.

    @version ― 16.8.0

    @see ― https://react.dev/reference/react/useState

    useState
    (false);
    /** Starts an invitation for another device of yours, and returns the flow and the code to open there. */
    const
    const handleAdd: () => Promise<{
    flow: Invitation.Flow;
    code: string;
    }>

    Starts an invitation for another device of yours, and returns the flow and the code to open there.

    handleAdd
    = () =>
    import Effect
    Effect
    .
    const gen: <Effect.Effect<Invitation.Flow, IdentityError, Identity.Service> | Effect.Effect<string, InvitationError, never>, {
    flow: Invitation.Flow;
    code: string;
    }>(f: () => Generator<Effect.Effect<Invitation.Flow, IdentityError, Identity.Service> | Effect.Effect<string, InvitationError, never>, {
    flow: Invitation.Flow;
    code: string;
    }, never>) => Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, Identity.Service> (+1 overload)

    Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

    When to use

    Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

    Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

    Example (Sequencing effects with generators)

    import { Data, Effect } from "effect"
    class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
    const addServiceCharge = (amount: number) => amount + 1
    const applyDiscount = (
    total: number,
    discountRate: number
    ): Effect.Effect<number, DiscountRateError> =>
    discountRate === 0
    ? Effect.fail(new DiscountRateError())
    : Effect.succeed(total - (total * discountRate) / 100)
    const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
    const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
    export const program = Effect.gen(function*() {
    const transactionAmount = yield* fetchTransactionAmount
    const discountRate = yield* fetchDiscountRate
    const discountedAmount = yield* applyDiscount(
    transactionAmount,
    discountRate
    )
    const finalAmount = addServiceCharge(discountedAmount)
    return `Final amount to charge: ${finalAmount}`
    })
    await Effect.runPromise(program) // => "Final amount to charge: 96"

    @stability ― stable

    @category ― constructors

    @since ― 2.0.0

    gen
    (function* () {
    const
    const flow: Invitation.Flow
    flow
    = yield*
    import Identity
    Identity
    .
    const share: (options?: Invitation.ShareOptions) => Effect.Effect<Invitation.Flow, IdentityError, Identity.Service>

    Initiate a device invitation (requires

    Service

    ).

    share
    ({
    authMethod?: Invitation.AuthMethod | undefined
    authMethod
    : 'shared-secret' });
    const
    const code: string
    code
    = yield*
    import Invitation
    Invitation
    .
    const code: (flow: Invitation.Flow) => Effect.Effect<string, InvitationError>

    Encoded, shareable code for a flow.

    code
    (
    const flow: Invitation.Flow
    flow
    );
    return {
    flow: Invitation.Flow
    flow
    ,
    code: string
    code
    };
    }).
    Pipeable.pipe<Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, Identity.Service>, Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, never>, Promise<{
    flow: Invitation.Flow;
    code: string;
    }>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<{
    flow: Invitation.Flow;
    code: string;
    }, IdentityError | InvitationError, Identity.Service>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Promise<...>): Promise<...> (+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
    );
    /** Your devices, re-rendering on each change. */
    const
    const devices: readonly {
    readonly key: string;
    readonly current: boolean;
    readonly kind?: "unknown" | "browser" | "native" | "mobile" | "agent" | "agent-managed" | undefined;
    readonly label?: string | undefined;
    readonly os?: string | undefined;
    readonly platform?: string | undefined;
    readonly presence?: "online" | "offline" | "removed" | undefined;
    }[]

    Your devices, re-rendering on each change.

    devices
    =
    function useDevices(): readonly Identity.DeviceInfo[]

    Returns the devices belonging to the local identity. Reactive. Replaces @dxos/react-client's useDevices.

    useDevices
    ();

Alice’s laptop has her identity, her spaces and her contacts; her phone has nothing.

  1. On the laptop, open Devices, press Add a device and copy the code.
  2. On the phone, press Join your other device, paste it and press Join.
  3. Type the authorization code the laptop shows into the phone and press Send.

The phone now shows Alice’s name, both spaces and both contacts, and each device lists the other.

You can add a device to your identity and list your devices. Next, Recover an identity covers losing all of them.