mirror of
https://github.com/Such-Software/smirk-website.git
synced 2026-11-13 09:14:03 +01:00
fix(settings): read the v0.3 username response shape
The v0.3 backend answers GET /users/me/username with {"username": ...};
the legacy backend sent a bare string. The page put the object into state
and rendered it, so /settings threw for every signed-in user.
parseUsernameResponse accepts both shapes and returns only a string or
null. Legacy-only fields on /auth/me are optional. Adds node:test cases,
run with npm test.
This commit is contained in:
+8
-4
@@ -1,3 +1,5 @@
|
||||
import { parseUsernameResponse } from './username';
|
||||
|
||||
// API client for Smirk backend
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
|
||||
@@ -24,9 +26,11 @@ export interface AuthResponse {
|
||||
|
||||
export interface MeResponse {
|
||||
id: string;
|
||||
username: string | null;
|
||||
telegram_id: number | null;
|
||||
telegram_username: string | null;
|
||||
username?: string | null;
|
||||
/** Legacy backend only; absent on the v0.3 backend. */
|
||||
telegram_id?: number | null;
|
||||
/** Legacy backend only; absent on the v0.3 backend. */
|
||||
telegram_username?: string | null;
|
||||
}
|
||||
|
||||
export async function getChallenge(origin: string): Promise<ChallengeResponse> {
|
||||
@@ -353,7 +357,7 @@ export async function getMyUsername(token: string): Promise<string | null> {
|
||||
throw new Error('Failed to get username');
|
||||
}
|
||||
|
||||
return res.json();
|
||||
return parseUsernameResponse(await res.json().catch(() => null));
|
||||
}
|
||||
|
||||
export async function setUsername(token: string, username: string): Promise<{ username: string }> {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { parseUsernameResponse } from './username.ts';
|
||||
|
||||
test('accepts the v0.3 object shape', () => {
|
||||
assert.equal(parseUsernameResponse({ username: 'alice' }), 'alice');
|
||||
assert.equal(parseUsernameResponse({ username: null }), null);
|
||||
});
|
||||
|
||||
test('accepts the legacy bare string shape', () => {
|
||||
assert.equal(parseUsernameResponse('alice'), 'alice');
|
||||
assert.equal(parseUsernameResponse(null), null);
|
||||
});
|
||||
|
||||
test('never returns a non-string', () => {
|
||||
for (const input of [undefined, '', 42, true, [], {}, { username: 7 }, { username: { a: 1 } }]) {
|
||||
const result = parseUsernameResponse(input);
|
||||
assert.ok(result === null || typeof result === 'string');
|
||||
assert.equal(result, null);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Normalize a `GET /users/me/username` response to a username or null.
|
||||
*
|
||||
* The legacy backend answered with a bare JSON string (or null). The v0.3
|
||||
* backend answers with `{ "username": string | null }`. Anything else, including
|
||||
* an empty string, is treated as "no username" so a shape change can never put a
|
||||
* non-string into React state and crash the page.
|
||||
*/
|
||||
export function parseUsernameResponse(data: unknown): string | null {
|
||||
const value =
|
||||
data !== null && typeof data === 'object' && 'username' in data
|
||||
? (data as { username: unknown }).username
|
||||
: data;
|
||||
return typeof value === 'string' && value.length > 0 ? value : null;
|
||||
}
|
||||
Reference in New Issue
Block a user