Saltar para o conteúdo

Tempo real (WebSocket)

O WebSocket da plataforma - o ticket de uso único, o subprotocolo memberfy-realtime, as salas automáticas e as pedidas, os avisos invalidate, os limites e os códigos de fecho.

A app recebe as mudanças de imediato por um WebSocket: o sino, o chat, o "Online agora", quem está em cada ecrã, a faixa de evento ao vivo, os números das campanhas. Qualquer membro da comunidade, de qualquer função, abre o socket, com o chat ligado ou desligado.

Abrir a ligação

  1. Peça um ticket com POST /api/realtime/ticket, com o token e o X-CommunityId (ver Autenticação e X-CommunityId). O ticket é de uso único, vale 60 segundos e fica preso à pessoa, à comunidade e à sessão.
  2. Abra wss://<api>/api/realtime/socket (o data.path) passando o ticket como segundo subprotocolo, depois de memberfy-realtime (o data.protocols). O servidor responde com memberfy-realtime.
const { data } = await api.post('/api/realtime/ticket'); // com Authorization e X-CommunityId
const ws = new WebSocket(`wss://api.memberfy.net${data.path}`, data.protocols);

O ticket nunca vai no URL (na query, é ignorado). Sem ticket válido, a ligação é recusada antes de se tornar WebSocket (401). Mudar de comunidade fecha o socket: peça outro ticket. Sem o serviço de tempo real, o ticket devolve 503, e a app continua a ir buscar pelas rotas.

POST /api/chat/ticket e /api/chat/socket continuam a funcionar como outros nomes destes durante um deploy e depois saem: use os novos.

Salas

Toda a ligação entra logo em três salas:

SalaQuem
meA própria pessoa
communityTodos da comunidade
staffSó a equipa (proprietário, administrador, moderador e financeiro)

As outras são pedidas pelo cliente, com a mesma regra do GET correspondente, sempre nesta comunidade:

SalaQuem entra
space:<id>Quem abre o espaço
item:event:<id>Quem abre o evento
item:post:<id>Quem abre a publicação
{ "type": "subscribe", "rooms": ["space:5b0e7c1a-…", "item:event:9d2f…"] }
{ "type": "unsubscribe", "rooms": ["space:5b0e7c1a-…"] }

Há também salas para os editores, só para as funções que podem gravar ali: item:course:<id>, item:form:<id> (a equipa e quem gere o formulário; um membro quando está publicado e abre o espaço), item:campaign:<id>, item:email:<id> (e-mail da biblioteca), item:workflow:<id>, item:survey:<id> (proprietário e administrador), item:certificateTemplate:<id> (quem emite certificados no espaço), item:emailOverride:<emailKey> (e-mail predefinido, proprietário e administrador). E as salas de área, uma por ecrã de secção: area:home (todos), area:members, area:campaigns, area:emails, area:workflows (equipa), area:moderation (proprietário, administrador e moderador), area:analytics, area:dashboard, area:settings (proprietário e administrador) e area:finance (proprietário, administrador e financeiro).

No máximo 20 salas por ligação (as automáticas não contam). A resposta é subscribed com as salas aceites e um subscribe.denied por sala recusada, sem motivo. Quando o acesso da pessoa muda (saiu, nova função, grupo, acesso do espaço), o servidor reavalia as salas, manda subscribe.denied das que perdeu e depois access.changed.

Avisos de mudança (invalidate)

{ "type": "invalidate", "room": "community", "topic": "event.live", "ids": ["9d2f…"], "at": "2026-10-23T14:05:00.000Z" }

O aviso traz só os ids e o tópico, nunca título, texto, valor ou nome: ao recebê-lo, refaça o GET do que está no ecrã pelas rotas de sempre.

TópicoSalaQuando
event.livecommunity, space:<id>, item:event:<id>Um evento começou ou terminou a transmissão
eventscommunity, space:<id>, item:event:<id>Um evento foi criado, editado ou removido
analytics.onlinestaff (proprietário e administrador)O "Online agora" mudou (no máximo 1 a cada 5 segundos)
campaigns.statsstaffUm envio de campanha terminou
menu.changedcommunityO menu mudou: secção ou espaço criado, editado (nome, ícone, endereço, secção, acesso, visibilidade), removido ou reordenado. Sem ids e sem nenhum dado do menu: cada pessoa refaz o GET /api/sections com as próprias permissões. Mudanças seguidas resultam num só aviso, em até 1 segundo

Os avisos de evento chegam só a quem vê esse espaço; evento privado ou em moderação, só à equipa.

Quem está aqui

A presença de área (os rostos no topo de cada ecrã, ver Quem está aqui e edição em equipa) usa as mesmas salas.

{ "type": "presence.join", "room": "area:campaigns", "editing": false }
{ "type": "presence.leave", "room": "area:campaigns" }
{ "type": "presence.active" }
  • presence.join ao abrir o ecrã: a sala é avaliada como no subscribe (e subscrita, se ainda não estava), no máximo 3 lugares por ligação. A resposta é presence.joined (room, editing) seguido de um retrato presence, ou presence.denied (room), sem motivo.
  • presence.leave ao sair. Não há heartbeat: o ping do servidor mantém o lugar, que desaparece 45 segundos depois da última resposta; fechar a ligação tira-o de imediato.
  • presence.active quando há interação (tecla, clique, foco), no máximo a cada 10 segundos. Entrar conta como atividade.

A cada mudança (no máximo 1 por segundo por sala), a sala recebe um retrato montado para cada ligação:

{
  "type": "presence", "room": "area:campaigns", "area": "area:campaigns",
  "members": [{ "memberId": "…", "name": "Maria", "avatarUrl": "…", "editing": false, "state": "active", "lastActiveAt": "…" }],
  "count": 3, "editingCount": 1, "at": "2026-10-28T14:05:00.000Z"
}
  • Nunca está na sua própria lista. members traz no máximo 5 pessoas (a editar e ativas primeiro, depois as ativas, depois as inativas); count é o total das outras pessoas na sala.
  • Quem só lê: nas salas de espaço e de item, quem não está a editar ali recebe só memberId, name, avatarUrl e state, sem editing, lastActiveAt nem editingCount, para preservar quem edita. Quem edita o mesmo item e as salas de área da equipa recebem o retrato completo.
  • state: "idle": ligada, sem atividade há 30 segundos ou mais. Continua na lista.
  • Privacidade: quem desligou Disponível para conversar não aparece nem conta para os outros, mas vê quem está. Um bloqueio no chat esconde as duas pessoas uma da outra.
  • Chat da comunidade desligado: as salas de área e quem só lê não recebem ninguém (members vazio, count 0), incluindo a equipa. Só quem edita o mesmo item continua a ver os outros editores; o item.saved e o 409 de versão não mudam.

Edição e item.saved

Num editor (evento, campanha, e-mail da biblioteca, workflow, curso, formulário, inquérito, modelo de certificado, e-mail predefinido e as definições, em area:settings), entre com "editing": true. Só vale para quem pode gravar ali (a equipa e, no formulário, quem o gere). Quem edita aparece com editing: true aos outros editores da sala, mesmo com o estado do chat desligado; quem só lê a página não fica a saber. editingCount conta só quem edita ativo.

A cada gravação, a sala recebe, para a equipa e para quem está a editar:

{ "type": "item.saved", "topic": "item.saved", "room": "item:campaign:<id>", "itemType": "campaign", "itemId": "<id>", "by": { "memberId": "…", "name": "Maria" }, "updatedAt": "<nova versão>", "at": "…" }

Compare updatedAt com a versão que tem para ignorar a sua própria gravação. itemType: event, campaign, email, workflow, course (incluindo módulos e aulas), form, survey, certificateTemplate, emailOverride (itemId = emailKey) e, na sala area:settings (itemId = a comunidade), settings, chatSettings, notificationSettings, messagingSettings (SMS + WhatsApp), creditSettings, businessInformation, payoutSettings.

As gravações aceitam a versão lida e respondem 409 quando outra veio antes: ver Conflito de versão.

Outros eventos

Do servidor, em JSON { type, data }: ready (com profileId, communityId, chatAvailable, chatStatus (o estado escolhido no chat), chatEnabled, rooms, maxRooms e pingIntervalMs), notification.new, notification.read e notification.removed (o sino, em todas as comunidades), chat.settings, session.revoked e pong. Os eventos do chat só chegam com o chat ligado; com o chat desligado, o socket continua de pé e só esses param.

Do cliente: subscribe, unsubscribe, presence.join, presence.leave, presence.active, presence.idle ({ "type": "presence.idle", "idle": true } quando o separador fica 10 minutos parado, false quando volta: a pessoa aparece Ausente quando todos os separadores dela estão parados), typing (só com o chat ligado) e ping.

Limites e códigos de fecho

  • Quadros até 4 KB, sem binário, no máximo 5 por segundo.
  • O servidor manda ping a cada 25 segundos.
  • Para voltar a ligar, espere um tempo aleatório e crescente e peça um ticket novo.
CódigoO que significaO que fazer
4004Sessão terminadaNão volte a ligar com a mesma sessão
4009Substituída: a pessoa abriu mais de 10 ligações, e a mais antiga fechouNada, se for o separador antigo
4029Servidor cheioVolte a ligar mais tarde e use as rotas entretanto
1008Quadros a maisAbrande o ritmo
1001Servidor a reiniciarVolte a ligar

Relacionados