Fix desyncs when multiple clients connected

This commit is contained in:
Max Goodhart
2025-06-23 01:31:06 -07:00
parent 1031f6f8f8
commit cc8baead06
2 changed files with 33 additions and 14 deletions

View File

@@ -29,10 +29,19 @@ const base62 = baseX(
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
)
function rand62(len: number) {
export function rand62(len: number) {
return base62.encode(randomBytes(len))
}
export function uniqueRand62(len: number, map: Map<string, unknown>) {
let val = rand62(len)
while (map.has(val)) {
// Regenerate in case of a collision
val = rand62(len)
}
return val
}
async function hashToken62(token: string, salt: string) {
const hashBuffer = await scrypt(token, salt, 24)
return base62.encode(hashBuffer as Buffer)
@@ -162,12 +171,7 @@ export class Auth extends EventEmitter<AuthEvents> {
throw new Error(`invalid role: ${role}`)
}
let tokenId = rand62(8)
while (this.tokensById.has(tokenId)) {
// Regenerate in case of an id collision
tokenId = rand62(8)
}
const tokenId = uniqueRand62(8, this.tokensById)
const secret = rand62(24)
const tokenHash = await hashToken62(secret, this.salt)
const tokenData = {

View File

@@ -16,12 +16,13 @@ import {
stateDiff,
type StreamwallRole,
} from 'streamwall-shared'
import { Auth, StateWrapper } from './auth.ts'
import { Auth, StateWrapper, uniqueRand62 } from './auth.ts'
import { loadStorage, type StorageDB } from './storage.ts'
export const SESSION_COOKIE_NAME = 's'
interface Client {
clientId: string
ws: WebSocket
lastStateSent: any
identity: AuthTokenInfo
@@ -248,7 +249,7 @@ async function initApp({ baseURL, clientStaticPath }: AppOptions) {
console.error('Failed to send Streamwall doc update')
}
for (const client of clients.values()) {
if (client.identity.tokenId === origin) {
if (client.clientId === origin) {
continue
}
try {
@@ -299,25 +300,39 @@ async function initApp({ baseURL, clientStaticPath }: AppOptions) {
return
}
const clientId = uniqueRand62(8, clients)
const client: Client = {
clientId,
ws,
lastStateSent: null,
identity,
}
clients.set(identity.tokenId, client)
clients.set(clientId, client)
const pingInterval = setInterval(() => {
ws.ping()
}, 20 * 1000)
ws.on('close', () => {
clients.delete(identity.tokenId)
clients.delete(clientId)
clearInterval(pingInterval)
console.log('Client disconnected from', request.ip, client.identity)
console.log(
'Client',
clientId,
'disconnected from',
request.ip,
client.identity,
)
})
console.log('Client connected from', request.ip, client.identity)
console.log(
'Client',
clientId,
'connected from',
request.ip,
client.identity,
)
handleMessage(async (rawData) => {
let msg: ControlCommandMessage
@@ -350,7 +365,7 @@ async function initApp({ baseURL, clientStaticPath }: AppOptions) {
Y.applyUpdate(
streamwallConn.stateDoc,
new Uint8Array(rawData),
identity.tokenId,
clientId,
)
return
}