Um protocolo de quatro bytes para presença de cursores
Como coordenadas normalizadas e slots controlados pela conexão substituem atualizações JSON cheias de estrutura.
O ponteiro se move muitas vezes, mas uma atualização de cursor não precisa de muita informação. A primeira versão desta funcionalidade enviava um objeto JSON para cada posição amostrada. Funcionava, mas o payload carregava um identificador e uma cor junto com dois números que mudavam o tempo todo.
É muita estrutura para uma mensagem pequena e passageira. Troquei esse formato por um protocolo binário compartilhado entre o navegador e o servidor.
Três bytes saindo do navegador
O navegador transforma a posição do ponteiro em coordenadas relativas ao documento. Cada coordenada é normalizada para um inteiro de 12 bits, entre 0 e 4095; a coordenada vertical usa a altura total de rolagem do documento. Dois valores de 12 bits ocupam 24 bits, então uma atualização de movimento usa três bytes:
byte 0: x[11..4]
byte 1: bits 7..4 = x[3..0], bits 3..0 = y[11..8]
byte 2: y[7..0]
O módulo shared/cursor.ts controla os dois lados desse empacotamento. Ele limita posições inválidas antes de codificar e verifica o tamanho exato do frame ao decodificar. Em 1920 pixels, cada passo da quantização tem menos de meio pixel, uma precisão maior do que o marcador precisa.
Quem recebe a mensagem não presume que o outro dispositivo tem as mesmas dimensões de documento. Os valores normalizados são expandidos de acordo com a própria largura do documento e altura total de rolagem. Assim, o cursor continua preso a uma posição relativa na página, e a camada absoluta acompanha naturalmente o documento durante a rolagem.
A identidade pertence à conexão
O protocolo antigo pedia que o cliente enviasse um ID e usava um cookie HTTP-only para verificá-lo. O protocolo binário remove as duas partes. Quando um WebSocket é aberto, o servidor tenta atribuir a ele um de 64 slots. A conexão é dona desse slot; o navegador nunca o escolhe. Se todos os slots estiverem ocupados, o servidor fecha a conexão excedente em vez de manter uma conexão que não pode participar.
Os frames de movimento do servidor acrescentam o slot aos três bytes da posição:
byte 0: opcode[7..6] + slot[5..0]
bytes 1-3: posição empacotada
Mensagens de entrada e saída precisam de apenas um byte porque não carregam posição. O servidor guarda a última posição empacotada de cada conexão para que um novo assinante receba um snapshot imediatamente. Ele também publica movimentos e saídas somente para os outros assinantes.
Isso deixa o limite de confiança mais claro. O servidor só aceita do cliente frames de movimento com três bytes. Ele decide a qual slot esse frame pertence, valida seu tamanho e acrescenta a identidade da conexão antes de transmiti-lo.
Estado pequeno, limpeza explícita
O navegador mantém os cursores remotos em um mapa indexado pelo slot. Um frame de saída remove um cursor imediatamente; um timeout de sete segundos cobre conexões que desaparecem sem um fechamento limpo. O cursor local nunca passa por esse estado remoto: ele é renderizado diretamente a partir da posição atual do ponteiro.
O resultado não é uma camada de serialização genérica. É um contrato estreito para dados frequentes, efêmeros e fáceis de reconstruir. Três bytes do cliente, quatro bytes do servidor e um byte para mudanças de presença bastam para esta funcionalidade.
A otimização útil também foi uma pequena correção de design: a identidade pertence à conexão, enquanto a posição pertence à mensagem.