Tutorial 03

Use the generated client like a remote function

Import the sibling chat.socket.ts file from +page.svelte and use the typed browser client it becomes.

Outcome

You will render room messages in Svelte and keep listener and emit types tied to chat.socket.ts.

1

Page usage

Import the contract directly in +page.svelte

The page imports ./chat.socket and calls chat(). That returns the browser client with listeners, emits, joins, connection state, and cleanup helpers.

The import starts as server code and becomes browser code

chat.socket.ts is server code. In +page.svelte, LiveRPC replaces the import with a generated browser client before the page reaches the browser.

Terms

Previously: The Contract page created chat.socket.ts with toClient.message, rooms.chat, and fromClient.send.

Code focus: This shows the browser side of the same route: create the channel, listen for server messages, and emit typed input.

Lives in

src/routes/rooms/[roomId]/+page.svelte

browser page for the same route This page renders the chat room and imports ./chat.socket from the same route folder. The socket connection opens only after the page registers a listener or sends an event.

Route map

src/routes/rooms/[roomId]/

+page.svelte

chat.socket.ts <- realtime contract

src/hooks.socket.ts

svelte src/routes/rooms/[roomId]/+page.svelte · generated client usage
<script lang="ts">
  import { chat } from './chat.socket';

  type Message = {
    id: string;
    roomId: string;
    text: string;
    sentAt: string;
  };

  const room = chat();
  let text = $state('');
  let messages = $state<Message[]>([]);

  // The listener payload is inferred from toClient.message.
  room.on.message((message) => {
    messages = [message, ...messages];
  });

  async function send() {
    // The emit payload is inferred from fromClient.send.
    await room.emit.send({ text });
    text = '';
  }
</script>

<form onsubmit={(event) => { event.preventDefault(); void send(); }}>
  <input bind:value={text} aria-label="Message" />
  <button type="submit">Send</button>
</form>

Result: The browser page can now listen and send through typed methods while LiveRPC owns the Socket.IO client setup.

Checkpoint

Load the page in two tabs. Sending from one tab should render in both tabs for the active route room.

2

Refresh

Reconnect after auth or cookie changes

When a login action changes cookies, reconnect active realtime clients so src/hooks.socket.ts can read the new cookie values.

Refresh reruns the socket hook

The page should not send user ids to chat.socket.ts. It calls refreshRealtime(), LiveRPC reconnects, and src/hooks.socket.ts returns fresh socket locals from the new cookies.

Terms

Code focus: This helper is separate from chat.socket.ts because it reacts to auth state, not room behavior.

Lives in

src/routes/login/+page.svelte or your login action caller

browser code that runs after login completes Call refreshRealtime after the login action changes cookies. Do it from the login flow because the chat contract should only read the finished connection state.

Route map

src/routes/login/+page.svelte <- call refreshRealtime()

src/hooks.socket.ts <- re-resolves locals

typescript login completion · refresh active generated clients
import { refreshRealtime } from 'liverpc/client';

await submitLogin(formData);

// Reconnect active channels so hooks.socket.ts reruns with new cookies.
await refreshRealtime();

Result: Existing realtime channels reconnect and handlers that read locals.user see the newly authenticated session.

Checkpoint

After login, trigger a realtime action that reads locals.user. It should use the user from the new session cookie.

Run the safe realtime lab