Liveblocks Client SDK
This writeup goes into trying to understand how liveblocks client sdk works by diving into codebase & devtools. I would be using the Open source liveblocks repo & nextjs-logo-builder as a example to help me setup & understand the sdk with ease.
If you want to use you will need 3 packages:
@liveblocks/node
@liveblocks/client
@liveblocks/react
I wanted to link the packages so that I can use them locally in my example. My first approach was to use yarn link
but then I found the handy link-liveblocks.sh
script to do it.
../../scripts/link-liveblocks.sh
==> Rebuilding (in /Users/deepankarbhade/Desktop/liveblocks/packages/liveblocks-client)
> @liveblocks/client@0.17.5-dev build
> rollup -c && cp ./package.json ./README.md ./lib
...
Now my example app uses local liveblocks sdk builds instead of pointing to npm ones.
The first thing all react/nextjs devs would do is to wrap the root component with <RoomProvider />
. This magically handles join/leave but how does it do that?
import { createRoomContext } from '@liveblocks/react';
const client = createClient({
authEndpoint: '/api/auth',
});
export const { RoomProvider } = createRoomContex(client);
const App = () => {
return <RoomProvider>{/* Components */}</RoomProvider>;
};
Let's peep into the code of createRoomContext
.
Inside liveblocks-react/src/factory.tsx
file we can check the implementation of createRoomContext
On mounting enter
& unmounting leave
actions are called.
React.useEffect(() => {
setRoom(
_client.enter(roomId, {
initialPresence: frozen.initialPresence,
initialStorage: frozen.initialStorage,
defaultPresence: frozen.defaultPresence, // Will get removed in 0.18
defaultStorageRoot: frozen.defaultStorageRoot, // Will get removed in 0.18
DO_NOT_USE_withoutConnecting: typeof window === "undefined",
} as RoomInitializers<TPresence, TStorage>)
);
return () => {
_client.leave(roomId);
};
}, [_client, roomId, frozen]);
Link to Code If we look at the implementation of createClient
we can observe that it creates a new Map of rooms and implements enter and leave actions.
- enter -> creates a new room and adds it to the Map & calls
connect
- leave -> calls
disconnect
and deletes the room from the Map
Let's dive into enter
as it's more interesting.
enter
creates an internalRoom via createRoom
function which returns connect, and disconnect which is called later.
Every room associates 3 data components machine, room, and state.
For understanding and play around with it quickly I mounted all 3 of them in the window.
if (typeof window !== 'undefined') {
window.__room = room;
window.__state = state;
window.__machine = machine;
}
connect
is one of many actions of a machine that sets the state.
- fetch the auth token from auth endpoint prepareAuthEndpoint
- set token to the state
- create a new websocket instance prepareCreateWebSocket
If we reload the page and inspect in the network tab we can see the API call.
Now it passes the auth object to the authenticate
function which is a "listener" (not sure). It ultimately sets the token to the state
Setting token to state
Now that we have our WebSocket connection open let's see how presence and storage are sent across. I focused on the input and then changed the theme here's what I see in my WebSocket network tab.
Websocket Panel
We can see that each message has a type
and its value is associated with the kind of message it is. Focusing/Unfocusing input sends a message of 100 (Presence update) & Changing theme sends 201 (Storage update).
Here's the message codes for Client & Server
export enum ClientMsgCode {
// For Presence
UPDATE_PRESENCE = 100,
BROADCAST_EVENT = 103,
// For Storage
FETCH_STORAGE = 200,
UPDATE_STORAGE = 201,
}
export enum ServerMsgCode {
// For Presence
UPDATE_PRESENCE = 100,
USER_JOINED = 101,
USER_LEFT = 102,
BROADCASTED_EVENT = 103,
ROOM_STATE = 104,
// For Storage
INITIAL_STORAGE_STATE = 200,
UPDATE_STORAGE = 201,
}
I love when companies provide react hooks for their SDKs this is something I did at 100ms and I am glad liveblocks has great support for it.
This made me curious about how they work under the hood. Let's see the most common hook useOthers
which gives us info on other users in the room. Let's try to get the value from the window object that we set.
Use Others
Now if we see its actual implementation.
function useOthers(): Others<TPresence, TUserMeta> {
const room = useRoom();
const rerender = useRerender();
React.useEffect(() => {
const unsubscribe = room.subscribe('others', rerender);
return () => {
unsubscribe();
};
}, [room, rerender]);
return room.getOthers();
}
room.getOthers()
under the hood calls machine.selectors.getOthers()
See the implementation
Bonus:
This is a part dedicated to my love for liveblock's DX
Handling polyfills
I love how polyfills for fetch, WebSocket & atob are handled.
Handling polyfills
Doc links in error
Missing any param? These helpful error messages would help.
Doc links in Error
API call responses
Handling edge cases of not passing keys.
API error response
Motivation
I have known liveblocks for over a year & have been dwelling in its codebase, blogs, and examples.
Ever since then I had the interest to join Liveblocks but I waited for the "Right moment". The truth is there is no such thing as the "right" time. If you wait for the perfect moment, you may end up waiting for a while. Instead, take a moment and start.
Also beautifully said on the Liveblocks career page:
Sometimes all it takes is a simple message
Motivation behind this write-up
Thank you & have a great day!