学习如何构建multiplayer.dev,这是一个合作应用,它展示了使用实时的广播、Presence和Postgres CDC。

安装supabase-js 客户端

  npm install @supabase/supabase-js
  

光标位置

广播允许一个客户端发送消息,多个客户端接收消息。广播的消息是短暂的。它们不会被持久化到数据库中,而是直接通过实时服务器转发。这对于发送像光标位置这样的信息是很理想的,因为最小的延迟是很重要的,但持久化则不是。

multiplayer.dev中,客户端的光标位置被发送到房间里的其他客户端。然而,在这个例子中,光标位置将是随机生成的。

你需要从你的项目的API设置中获得公共的anon访问令牌。然后你就可以设置Supabase客户端,并开始发送一个客户端的光标位置到通道room1中的其他客户端。

  const { createClient } = require('@supabase/supabase-js')

const supabase = createClient('https://your-project-ref.supabase.co', 'anon-key', {
  realtime: {
    params: {
      eventsPerSecond: 10,
    },
  },
})

// Channel name can be any string.
// Create channels with the same name for both the broadcasting and receiving clients.
const channel = supabase.channel('room1')

// Subscribe registers your client with the server
channel.subscribe((status) => {
  if (status === 'SUBSCRIBED') {
    // now you can start broadcasting cursor positions
    setInterval(() => {
      channel.send({
        type: 'broadcast',
        event: 'cursor-pos',
        payload: { x: Math.random(), y: Math.random() },
      })
      console.log(status)
    }, 100)
  }
})
  

另一个客户端可以订阅频道room1并接收游标位置。

  // Supabase client setup

// Listen to broadcast messages.
supabase
  .channel('room1')
  .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
  .subscribe((status) => {
    if (status === 'SUBSCRIBED') {
      // your callback function will now be called with the messages broadcast by the other client
    }
  })
  

往返延时

你也可以配置通道,使服务器必须返回一个确认函,表明它收到了 “广播 “信息。如果你想测量往返的延迟,这很有用。

  // Supabase client setup

const channel = supabase.channel('calc-latency', {
  config: {
    broadcast: { ack: true },
  },
})

channel.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    const begin = performance.now()

    await channel.send({
      type: 'broadcast',
      event: 'latency',
      payload: {},
    })

    const end = performance.now()

    console.log(`Latency is ${end - begin} milliseconds`)
  }
})
  

跟踪和显示哪些用户在线

Presence存储和同步各客户端的共享状态。每当共享状态发生变化时,就会触发sync事件。join事件在新客户加入频道时被触发,leave事件在客户离开时被触发。

每个客户端可以使用通道的track方法来存储共享状态中的对象。每个客户端只能跟踪一个对象,如果track被同一个客户端再次调用,那么新的对象就会覆盖共享状态中先前跟踪的对象。你可以使用一个客户端来跟踪和显示在线的用户。

  // Supabase client setup

const channel = supabase.channel('online-users', {
  config: {
    presence: {
      key: 'user1',
    },
  },
})

channel.on('presence', { event: 'sync' }, () => {
  console.log('Online users: ', channel.presenceState())
})

channel.on('presence', { event: 'join' }, ({ newPresences }) => {
  console.log('New users have joined: ', newPresences)
})

channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
  console.log('Users have left: ', newPresences)
})

channel.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    const status = await channel.track({ online_at: new Date().toISOString() })
    console.log(status)
  }
})
  

然后,您可以使用其他客户端将其他用户添加到频道的存在状态:

  // Supabase client setup

const channel = supabase.channel('online-users', {
  config: {
    presence: {
      key: 'user2',
    },
  },
})

// Presence event handlers setup

channel.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    const status = await channel.track({ online_at: new Date().toISOString() })
    console.log(status)
  }
})
  

如果一个频道在没有存在密钥的情况下被设置,服务器会生成一个随机的UUID。type必须是presenceevent必须是syncjoin,或leave

插入和接收持久的信息

Postgres Change Data Capture (CDC)使你的客户能够插入、更新或删除数据库记录,并将这些变化发送给客户。创建一个messages表来跟踪用户在特定房间创建的消息。

  create table messages (
  id serial primary key,
  message text,
  user_id text,
  room_id text,
  created_at timestamptz default now()
)

alter table messages enable row level security;

create policy "anon_ins_policy"
ON messages
for insert
to anon
with check (true);

create policy "anon_sel_policy"
ON messages
for select
to anon
using (true);
  

如果它还不存在,创建一个supabase_realtime发布,并将messages表添加到该出发布中。

  begin;
  -- remove the supabase_realtime publication
  drop publication if exists supabase_realtime;

  -- re-create the supabase_realtime publication with no tables and only for insert
  create publication supabase_realtime with (publish = 'insert');
commit;

-- add a table to the publication
alter publication supabase_realtime add table messages;
  

然后你可以让客户端监听特定房间的messages表的变化,并发送和接收持久化的信息。

  // Supabase client setup

const channel = supabase.channel('db-messages')

const roomId = 'room1'
const userId = 'user1'

channel.on(
  'postgres_changes',
  {
    event: 'INSERT',
    schema: 'public',
    table: 'messages',
    filter: `room_id=eq.${roomId}`,
  },
  (payload) => console.log(payload)
)

channel.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') {
    const res = await supabase.from('messages').insert({
      room_id: roomId,
      user_id: userId,
      message: 'Welcome to Realtime!',
    })
    console.log(res)
  }
})