Realtime

Presence

Share state between users with Realtime Presence.

Let's explore how to implement Realtime Presence to track state between multiple users.

Usage

You can use the Datafuse client libraries to track Presence state between users.

Initialize the client

Go to your Datafuse project's API Settings and grab the URL and anon public API key.

<Tabs scrollable size="small" type="underlined" defaultActiveId="js" queryGroup="language"

import { createClient } from '@datafuse/datafuse-js'

const SUPABASE_URL = 'https://<project>.datafuse.co'
const SUPABASE_KEY = '<your-anon-key>'

const datafuse = createClient(SUPABASE_URL, SUPABASE_KEY)
void main() {
  Datafuse.initialize(
    url: 'https://<project>.datafuse.co',
    anonKey: '<your-anon-key>',
  );

  runApp(MyApp());
}

final datafuse = Datafuse.instance.client;
let datafuseURL = "https://<project>.datafuse.co"
let datafuseKey = "<your-anon-key>"
let datafuse = DatafuseClient(datafuseURL: URL(string: datafuseURL)!, datafuseKey: datafuseKey)

let realtime = datafuse.realtime
val datafuseUrl = "https://<project>.datafuse.co"
val datafuseKey = "<your-anon-key>"
val datafuse = createDatafuseClient(datafuseUrl, datafuseKey) {
    install(Realtime)
}

Sync and track state

<Tabs scrollable size="small" type="underlined" defaultActiveId="js" queryGroup="language"

Listen to the sync, join, and leave events triggered whenever any client joins or leaves the channel or changes their slice of state:

const roomOne = datafuse.channel('room_01')

roomOne
  .on('presence', { event: 'sync' }, () => {
    const newState = roomOne.presenceState()
    console.log('sync', newState)
  })
  .on('presence', { event: 'join' }, ({ key, newPresences }) => {
    console.log('join', key, newPresences)
  })
  .on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
    console.log('leave', key, leftPresences)
  })
  .subscribe()
final datafuse = Datafuse.instance.client;

final roomOne = datafuse.channel('room_01');

roomOne.onPresenceSync((_) {
  final newState = roomOne.presenceState();
  print('sync: $newState');
}).onPresenceJoin((payload) {
  print('join: $payload');
}).onPresenceLeave((payload) {
  print('leave: $payload');
}).subscribe();

Listen to the presence change stream, emitting a new PresenceAction whenever someone joins or leaves:

let roomOne = await datafuse.channel("room_01")
let presenceStream = await roomOne.presenceChange()

await roomOne.subscribe()

for await presence in presenceStream {
  print(presence.join) // You can also use presence.decodeJoins(as: MyType.self)
  print(presence.leaves) // You can also use presence.decodeLeaves(as: MyType.self)
}

Listen to the presence change flow, emitting new a new PresenceAction whenever someone joins or leaves:

val roomOne = datafuse.channel("room_01")
val presenceFlow: Flow<PresenceAction> = roomOne.presenceChangeFlow()
presenceFlow
    .onEach {
        println(it.joins) //You can also use it.decodeJoinsAs<YourType>()
        println(it.leaves) //You can also use it.decodeLeavesAs<YourType>()
    }
    .launchIn(yourCoroutineScope) //You can also use .collect { } here

roomOne.subscribe()

Sending state

You can send state to all subscribers using track():

<Tabs scrollable size="small" type="underlined" defaultActiveId="js" queryGroup="language"

{/* prettier-ignore */}

const roomOne = datafuse.channel('room_01')

const userStatus = {
  user: 'user-1',
  online_at: new Date().toISOString(),
}

roomOne.subscribe(async (status) => {
  if (status !== 'SUBSCRIBED') { return }

  const presenceTrackStatus = await roomOne.track(userStatus)
  console.log(presenceTrackStatus)
})
final roomOne = datafuse.channel('room_01');

final userStatus = {
  'user': 'user-1',
  'online_at': DateTime.now().toIso8601String(),
};

roomOne.subscribe((status, error) async {
  if (status != RealtimeSubscribeStatus.subscribed) return;

  final presenceTrackStatus = await roomOne.track(userStatus);
  print(presenceTrackStatus);
});
let roomOne = await datafuse.channel("room_01")

// Using a custom type
let userStatus = UserStatus(
    user: "user-1",
    onlineAt: Date().timeIntervalSince1970
)

await roomOne.subscribe()

try await roomOne.track(userStatus)

// Or using a raw JSONObject.
await roomOne.track(
  [
    "user": .string("user-1"),
    "onlineAt": .double(Date().timeIntervalSince1970)
  ]
)
val roomOne = datafuse.channel("room_01")

val userStatus = UserStatus( //Your custom class
    user = "user-1",
    onlineAt = Clock.System.now().toEpochMilliseconds()
)

roomOne.subscribe(blockUntilSubscribed = true) //You can also use the roomOne.status flow instead, but this parameter will block the coroutine until the status is joined.

roomOne.track(userStatus)

A client will receive state from any other client that is subscribed to the same topic (in this case room_01). It will also automatically trigger its own sync and join event handlers.

Stop tracking

You can stop tracking presence using the untrack() method. This will trigger the sync and leave event handlers.

<Tabs scrollable size="small" type="underlined" defaultActiveId="js" queryGroup="language"

const untrackPresence = async () => {
  const presenceUntrackStatus = await roomOne.untrack()
  console.log(presenceUntrackStatus)
}

untrackPresence()
final roomOne = datafuse.channel('room_01');

untrackPresence() async {
  final presenceUntrackStatus = await roomOne.untrack();
  print(presenceUntrackStatus);
}

untrackPresence();
await roomOne.untrack()
suspend fun untrackPresence() {
    roomOne.untrack()
}

untrackPresence()

Presence options

You can pass configuration options while initializing the Datafuse Client.

Presence key

By default, Presence will generate a unique UUIDv1 key on the server to track a client channel's state. If you prefer, you can provide a custom key when creating the channel. This key should be unique among clients.

<Tabs scrollable size="small" type="underlined" defaultActiveId="js" queryGroup="language"

import { createClient } from '@datafuse/datafuse-js'

const channelC = datafuse.channel('test', {
  config: {
    presence: {
      key: 'userId-123',
    },
  },
})
final channelC = datafuse.channel(
  'test',
  opts: const RealtimeChannelConfig(key: 'userId-123'),
);
let channelC = await datafuse.channel("test") {
  $0.presence.key = "userId-123"
}
val channelC = datafuse.channel("test") {
    presence {
        key = "userId-123"
    }
}

Resources

Features

Company

Copyright © 2024. All rights reserved.