mirror of
https://github.com/tiennm99/rplace.git
synced 2026-05-28 16:21:52 +00:00
42d1ca19ee
- resolveIdentity prefers an opaque rplace_id cookie; falls back to a
cf-connecting-ip hash; in production a request with neither now returns
500 no_identity instead of bucketing all such traffic together
- /api/canvas issues Set-Cookie when no cookie is present so subsequent
requests escape NAT-shared IP buckets (mobile/CGNAT users)
- DO maintains an in-memory monotonic broadcast counter; broadcast frames
carry { seq } so the client can detect missed pixels and refetch
- client tracks lastSeq, refetches on gap, resets on every (re)connect
NAT/CGNAT users previously shared a single 1Hz bucket per egress IP. With
cookie identity they each get their own bucket. Cookie is HttpOnly, Secure,
SameSite=Lax, 1y Max-Age. Stripped/cleared cookies fall through to IP.
The seq counter resets on DO hibernation rehydrate; client always refetches
on reconnect, so a reset is indistinguishable from a fresh connect.
Plan: plans/260510-0232-fix-do-migration-followups/phase-02-cookie-ip-identity.md
62 lines
1.8 KiB
JavaScript
62 lines
1.8 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { parseCookie, formatSetCookie } from '../../src/lib/cookie.js';
|
|
|
|
describe('parseCookie', () => {
|
|
it('returns empty Map for null/undefined header', () => {
|
|
expect(parseCookie(null).size).toBe(0);
|
|
expect(parseCookie(undefined).size).toBe(0);
|
|
expect(parseCookie('').size).toBe(0);
|
|
});
|
|
|
|
it('parses a single name=value pair', () => {
|
|
const m = parseCookie('rplace_id=abc');
|
|
expect(m.get('rplace_id')).toBe('abc');
|
|
expect(m.size).toBe(1);
|
|
});
|
|
|
|
it('parses multiple cookies separated by ;', () => {
|
|
const m = parseCookie('a=1; b=2; c=3');
|
|
expect(m.get('a')).toBe('1');
|
|
expect(m.get('b')).toBe('2');
|
|
expect(m.get('c')).toBe('3');
|
|
});
|
|
|
|
it('trims whitespace around names and values', () => {
|
|
const m = parseCookie(' a = 1 ; b=2');
|
|
expect(m.get('a')).toBe('1');
|
|
expect(m.get('b')).toBe('2');
|
|
});
|
|
|
|
it('skips malformed entries (no equals)', () => {
|
|
const m = parseCookie('a; b=2');
|
|
expect(m.has('a')).toBe(false);
|
|
expect(m.get('b')).toBe('2');
|
|
});
|
|
|
|
it('handles values with embedded equals', () => {
|
|
const m = parseCookie('token=abc=def=');
|
|
expect(m.get('token')).toBe('abc=def=');
|
|
});
|
|
});
|
|
|
|
describe('formatSetCookie', () => {
|
|
it('formats minimum required attributes', () => {
|
|
expect(formatSetCookie('a', '1')).toBe('a=1');
|
|
});
|
|
|
|
it('emits Path, Max-Age, HttpOnly, Secure, SameSite in expected order', () => {
|
|
const s = formatSetCookie('rplace_id', 'uuid', {
|
|
httpOnly: true,
|
|
secure: true,
|
|
sameSite: 'Lax',
|
|
path: '/',
|
|
maxAge: 31536000,
|
|
});
|
|
expect(s).toBe('rplace_id=uuid; Path=/; Max-Age=31536000; HttpOnly; Secure; SameSite=Lax');
|
|
});
|
|
|
|
it('omits attributes that are not set', () => {
|
|
expect(formatSetCookie('a', '1', { secure: true })).toBe('a=1; Secure');
|
|
});
|
|
});
|