style: trim narration comments from recent changes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0189ASmkMee4F5aJ3jnKeKQv
This commit is contained in:
@@ -140,8 +140,6 @@ export const Route = createRootRouteWithContext<{
|
|||||||
);
|
);
|
||||||
const locale = resolveLocale(auth?.metadata?.locale);
|
const locale = resolveLocale(auth?.metadata?.locale);
|
||||||
await ensureMessages(locale);
|
await ensureMessages(locale);
|
||||||
// Dehydrate the catalog with the page so the client hydrates in the
|
|
||||||
// active locale instead of falling back to en until a chunk loads.
|
|
||||||
if (typeof window === "undefined" && locale !== DEFAULT_LOCALE) {
|
if (typeof window === "undefined" && locale !== DEFAULT_LOCALE) {
|
||||||
context.queryClient.setQueryData(
|
context.queryClient.setQueryData(
|
||||||
i18nMessagesQueryKey(locale),
|
i18nMessagesQueryKey(locale),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Trans, useLingui } from '@lingui/react/macro'
|
|||||||
import { useAuth } from '@/contexts/auth-context'
|
import { useAuth } from '@/contexts/auth-context'
|
||||||
import { useIsMobile } from '@/hooks/use-is-mobile'
|
import { useIsMobile } from '@/hooks/use-is-mobile'
|
||||||
|
|
||||||
// Navbar geometry: 4rem height + 0.5rem margin + its safe-area bottom offset.
|
|
||||||
const ABOVE_MOBILE_NAV_OFFSET = 'calc(4.5rem + env(safe-area-inset-bottom, 0px))'
|
const ABOVE_MOBILE_NAV_OFFSET = 'calc(4.5rem + env(safe-area-inset-bottom, 0px))'
|
||||||
|
|
||||||
export function IOSInstallPrompt() {
|
export function IOSInstallPrompt() {
|
||||||
@@ -43,8 +42,6 @@ export function IOSInstallPrompt() {
|
|||||||
? t`Tap Share → Add to Home Screen`
|
? t`Tap Share → Add to Home Screen`
|
||||||
: t`Tap Menu (⋮) → Add to Home screen`
|
: t`Tap Menu (⋮) → Add to Home screen`
|
||||||
|
|
||||||
// Navbar renders only under _authed, so a signed-in mobile user on a public
|
|
||||||
// route has no nav to clear.
|
|
||||||
const aboveBottomNav = Boolean(user) && isMobile
|
const aboveBottomNav = Boolean(user) && isMobile
|
||||||
const bottomStyle = aboveBottomNav
|
const bottomStyle = aboveBottomNav
|
||||||
? { bottom: ABOVE_MOBILE_NAV_OFFSET, paddingBottom: '8px' }
|
? { bottom: ABOVE_MOBILE_NAV_OFFSET, paddingBottom: '8px' }
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ const eventListerOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useVisualViewportSize = () => {
|
const useVisualViewportSize = () => {
|
||||||
// Starts at zero on server and client alike, filling in after mount — the
|
|
||||||
// same contract as Mantine's useViewportSize. Reading window during render
|
|
||||||
// makes the client's first render disagree with the SSR markup.
|
|
||||||
const [windowSize, setWindowSize] = useState({
|
const [windowSize, setWindowSize] = useState({
|
||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export async function ensureMessages(locale: AppLocale): Promise<Messages> {
|
|||||||
const loader = catalogLoaders[`/src/locales/${locale}/messages.po`];
|
const loader = catalogLoaders[`/src/locales/${locale}/messages.po`];
|
||||||
if (!loader) return messageCache[DEFAULT_LOCALE]!;
|
if (!loader) return messageCache[DEFAULT_LOCALE]!;
|
||||||
|
|
||||||
// Dedupe concurrent loads; a rejected load is dropped so a retry can succeed.
|
|
||||||
const pending = (inflightLoads[locale] ??= loader()
|
const pending = (inflightLoads[locale] ??= loader()
|
||||||
.then(({ messages }) => {
|
.then(({ messages }) => {
|
||||||
messageCache[locale] = messages;
|
messageCache[locale] = messages;
|
||||||
@@ -57,8 +56,6 @@ export function primeMessages(locale: AppLocale, messages: Messages) {
|
|||||||
messageCache[locale] ??= messages;
|
messageCache[locale] ??= messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seeded into the SSR-dehydrated query cache so hydration renders the active
|
|
||||||
// locale without depending on a client-side catalog chunk load.
|
|
||||||
export const i18nMessagesQueryKey = (locale: AppLocale) =>
|
export const i18nMessagesQueryKey = (locale: AppLocale) =>
|
||||||
["i18n-messages", locale] as const;
|
["i18n-messages", locale] as const;
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ export const LinguiProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
const locale = resolveLocale(data?.metadata?.locale);
|
const locale = resolveLocale(data?.metadata?.locale);
|
||||||
const [catalogVersion, setCatalogVersion] = useState(0);
|
const [catalogVersion, setCatalogVersion] = useState(0);
|
||||||
|
|
||||||
// Fresh instance per locale (and per SSR request tree). __root beforeLoad
|
|
||||||
// dehydrates the active locale's catalog with the page, so hydration primes
|
|
||||||
// it from the query cache and never regresses to en for the SSR'd locale.
|
|
||||||
// Public routes (login) may still lack it — createI18n falls back to en
|
|
||||||
// until the effect below loads it and bumps catalogVersion.
|
|
||||||
const i18n = useMemo(
|
const i18n = useMemo(
|
||||||
() => {
|
() => {
|
||||||
if (!hasCachedMessages(locale)) {
|
if (!hasCachedMessages(locale)) {
|
||||||
@@ -59,8 +54,6 @@ export const LinguiProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (hasCachedMessages(locale)) return;
|
if (hasCachedMessages(locale)) return;
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
// A failed chunk load must not strand the UI in en — retry until the
|
|
||||||
// catalog arrives or the locale changes.
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
ensureMessages(locale).then(
|
ensureMessages(locale).then(
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -19,17 +19,13 @@ class PocketBaseAdminClient {
|
|||||||
this.pb = new PocketBase(process.env.POCKETBASE_URL);
|
this.pb = new PocketBase(process.env.POCKETBASE_URL);
|
||||||
|
|
||||||
this.pb.beforeSend = async (url, options) => {
|
this.pb.beforeSend = async (url, options) => {
|
||||||
// The auth requests themselves must skip the gate below: gating them on
|
// Auth requests skip the gate: awaiting authPromise here would deadlock them.
|
||||||
// authPromise would make them await their own completion (deadlock).
|
|
||||||
if (!url.includes("/collections/_superusers/auth-")) {
|
if (!url.includes("/collections/_superusers/auth-")) {
|
||||||
try {
|
try {
|
||||||
await this.authPromise;
|
await this.authPromise;
|
||||||
} catch {
|
} catch {}
|
||||||
// Swallow: fall through to self-heal so a rejected authPromise can't strand every request.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.pb.authStore.isValid) {
|
if (!this.pb.authStore.isValid) {
|
||||||
// Self-heal: re-auth once PocketBase is reachable again, no restart.
|
|
||||||
this.authPromise = this.authenticate();
|
this.authPromise = this.authenticate();
|
||||||
await this.authPromise;
|
await this.authPromise;
|
||||||
this.startTokenRefresh();
|
this.startTokenRefresh();
|
||||||
@@ -43,8 +39,6 @@ class PocketBaseAdminClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The SDK stamps Authorization before this hook runs, so a request
|
|
||||||
// built while auth was still in flight carries no (or a stale) token.
|
|
||||||
options.headers = {
|
options.headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
Authorization: this.pb.authStore.token,
|
Authorization: this.pb.authStore.token,
|
||||||
@@ -75,8 +69,6 @@ class PocketBaseAdminClient {
|
|||||||
Object.assign(this, createPushService(this.pb));
|
Object.assign(this, createPushService(this.pb));
|
||||||
|
|
||||||
this.authPromise = this.authenticate();
|
this.authPromise = this.authenticate();
|
||||||
// Fail soft at boot: an unreachable PocketBase must not crash the process;
|
|
||||||
// beforeSend self-heals on the next request.
|
|
||||||
this.authPromise
|
this.authPromise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.startTokenRefresh();
|
this.startTokenRefresh();
|
||||||
|
|||||||
@@ -2,17 +2,9 @@ import type PocketBase from "pocketbase";
|
|||||||
|
|
||||||
export type FilterParam = string | number | boolean | Date | null;
|
export type FilterParam = string | number | boolean | Date | null;
|
||||||
|
|
||||||
// Any key the SDK's own `replaceAll("{:" + key + "}", …)` loop would substitute,
|
|
||||||
// so which keys resolve does not depend on the characters they are spelled with.
|
|
||||||
const PLACEHOLDER = /\{:([^}]+)\}/g;
|
const PLACEHOLDER = /\{:([^}]+)\}/g;
|
||||||
|
|
||||||
// `pb.filter` substitutes with String.replaceAll and a *string* replacement, so
|
// pb.filter $-expands string replacements; doubling `$` keeps the value literal.
|
||||||
// `$&`, `` $` ``, `$'` and `$$` inside a value are expanded as replacement
|
|
||||||
// patterns: the value's own text, or a slice of the surrounding expression,
|
|
||||||
// gets spliced into the quoted literal. Doubling every `$` first collapses back
|
|
||||||
// to the exact literal inside that same replaceAll. The FilterParam union is
|
|
||||||
// load-bearing — it keeps objects and arrays out of the SDK's JSON.stringify
|
|
||||||
// branch, which would reintroduce an undoubled `$`.
|
|
||||||
const quote = (pb: PocketBase, value: FilterParam) =>
|
const quote = (pb: PocketBase, value: FilterParam) =>
|
||||||
pb.filter("{:v}", {
|
pb.filter("{:v}", {
|
||||||
v: typeof value === "string" ? value.replaceAll("$", () => "$$") : value,
|
v: typeof value === "string" ? value.replaceAll("$", () => "$$") : value,
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
// `pb.filter()` escapes single quotes and nothing else, so a term ending in a
|
// Escapes LIKE metacharacters and wraps in `%` for a literal substring match.
|
||||||
// backslash escapes the closing quote of the literal it is substituted into and
|
|
||||||
// PocketBase rejects the whole expression with 400 validation_invalid_filter.
|
|
||||||
// Escaping `\ % _` and appending the wildcards here keeps the operand's last
|
|
||||||
// character a literal `%`, and makes `~` an unconditional substring match:
|
|
||||||
// PocketBase only auto-wraps (and only auto-escapes) operands that contain no
|
|
||||||
// `%` of their own, so a term carrying one would otherwise silently become a
|
|
||||||
// prefix match, and a bare `_` would match every row.
|
|
||||||
export const likePattern = (term: string) =>
|
export const likePattern = (term: string) =>
|
||||||
`%${term.replace(/[\\%_]/g, (char) => `\\${char}`)}%`;
|
`%${term.replace(/[\\%_]/g, (char) => `\\${char}`)}%`;
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
// Shared redaction for logs + audit rows so the two never drift.
|
|
||||||
export const SENSITIVE_KEY = /token|secret|password|phone|otp|code|auth|key/i;
|
export const SENSITIVE_KEY = /token|secret|password|phone|otp|code|auth|key/i;
|
||||||
|
|
||||||
const REDACTED = "[redacted]";
|
const REDACTED = "[redacted]";
|
||||||
|
|
||||||
// Deep-redact: keys matching SENSITIVE_KEY become "[redacted]"; primitives pass through.
|
|
||||||
export const redactValue = (value: unknown): unknown => {
|
export const redactValue = (value: unknown): unknown => {
|
||||||
if (Array.isArray(value)) return value.map(redactValue);
|
if (Array.isArray(value)) return value.map(redactValue);
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
// Keep Error serializable.
|
|
||||||
if (value instanceof Error) {
|
if (value instanceof Error) {
|
||||||
return { name: value.name, message: value.message, stack: value.stack };
|
return { name: value.name, message: value.message, stack: value.stack };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ export const createServerError = (
|
|||||||
context,
|
context,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Audit rows are written by serverFnLoggingMiddleware, which reads the
|
|
||||||
// returned envelope's success flag — never write them here.
|
|
||||||
export const toServerResult = async <T>(
|
export const toServerResult = async <T>(
|
||||||
serverFn: () => Promise<T>
|
serverFn: () => Promise<T>
|
||||||
): Promise<ServerResult<T>> => {
|
): Promise<ServerResult<T>> => {
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ msgstr "Aktivitäten"
|
|||||||
msgid "Activity Details"
|
msgid "Activity Details"
|
||||||
msgstr "Aktivitätsdetails"
|
msgstr "Aktivitätsdetails"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:61
|
#: src/components/ios-install-prompt.tsx:58
|
||||||
msgid "Add FLXN to your home screen for a smoother experience"
|
msgid "Add FLXN to your home screen for a smoother experience"
|
||||||
msgstr "Leg FLXN auf deinen Homescreen für ein flüssigeres Erlebnis"
|
msgstr "Leg FLXN auf deinen Homescreen für ein flüssigeres Erlebnis"
|
||||||
|
|
||||||
@@ -845,7 +845,7 @@ msgstr "deviceId ist für die Übertragungsaktion erforderlich"
|
|||||||
msgid "Disconnect Spotify"
|
msgid "Disconnect Spotify"
|
||||||
msgstr "Spotify trennen"
|
msgstr "Spotify trennen"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:68
|
#: src/components/ios-install-prompt.tsx:65
|
||||||
msgid "Dismiss"
|
msgid "Dismiss"
|
||||||
msgstr "Schließen"
|
msgstr "Schließen"
|
||||||
|
|
||||||
@@ -2324,11 +2324,11 @@ msgstr "T"
|
|||||||
msgid "Tap an opponent below to compare"
|
msgid "Tap an opponent below to compare"
|
||||||
msgstr "Tippe unten auf einen Gegner zum Vergleichen"
|
msgstr "Tippe unten auf einen Gegner zum Vergleichen"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:44
|
#: src/components/ios-install-prompt.tsx:43
|
||||||
msgid "Tap Menu (⋮) → Add to Home screen"
|
msgid "Tap Menu (⋮) → Add to Home screen"
|
||||||
msgstr "Tippe auf Menü (⋮) → Zum Home-Bildschirm hinzufügen"
|
msgstr "Tippe auf Menü (⋮) → Zum Home-Bildschirm hinzufügen"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:43
|
#: src/components/ios-install-prompt.tsx:42
|
||||||
msgid "Tap Share → Add to Home Screen"
|
msgid "Tap Share → Add to Home Screen"
|
||||||
msgstr "Tippe auf Teilen → Zum Home-Bildschirm hinzufügen"
|
msgstr "Tippe auf Teilen → Zum Home-Bildschirm hinzufügen"
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ msgstr "Activities"
|
|||||||
msgid "Activity Details"
|
msgid "Activity Details"
|
||||||
msgstr "Activity Details"
|
msgstr "Activity Details"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:61
|
#: src/components/ios-install-prompt.tsx:58
|
||||||
msgid "Add FLXN to your home screen for a smoother experience"
|
msgid "Add FLXN to your home screen for a smoother experience"
|
||||||
msgstr "Add FLXN to your home screen for a smoother experience"
|
msgstr "Add FLXN to your home screen for a smoother experience"
|
||||||
|
|
||||||
@@ -845,7 +845,7 @@ msgstr "deviceId is required for transfer action"
|
|||||||
msgid "Disconnect Spotify"
|
msgid "Disconnect Spotify"
|
||||||
msgstr "Disconnect Spotify"
|
msgstr "Disconnect Spotify"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:68
|
#: src/components/ios-install-prompt.tsx:65
|
||||||
msgid "Dismiss"
|
msgid "Dismiss"
|
||||||
msgstr "Dismiss"
|
msgstr "Dismiss"
|
||||||
|
|
||||||
@@ -2324,11 +2324,11 @@ msgstr "T"
|
|||||||
msgid "Tap an opponent below to compare"
|
msgid "Tap an opponent below to compare"
|
||||||
msgstr "Tap an opponent below to compare"
|
msgstr "Tap an opponent below to compare"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:44
|
#: src/components/ios-install-prompt.tsx:43
|
||||||
msgid "Tap Menu (⋮) → Add to Home screen"
|
msgid "Tap Menu (⋮) → Add to Home screen"
|
||||||
msgstr "Tap Menu (⋮) → Add to Home screen"
|
msgstr "Tap Menu (⋮) → Add to Home screen"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:43
|
#: src/components/ios-install-prompt.tsx:42
|
||||||
msgid "Tap Share → Add to Home Screen"
|
msgid "Tap Share → Add to Home Screen"
|
||||||
msgstr "Tap Share → Add to Home Screen"
|
msgstr "Tap Share → Add to Home Screen"
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ msgstr "Actividades"
|
|||||||
msgid "Activity Details"
|
msgid "Activity Details"
|
||||||
msgstr "Detalles de la actividad"
|
msgstr "Detalles de la actividad"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:61
|
#: src/components/ios-install-prompt.tsx:58
|
||||||
msgid "Add FLXN to your home screen for a smoother experience"
|
msgid "Add FLXN to your home screen for a smoother experience"
|
||||||
msgstr "Agrega FLXN a tu pantalla de inicio para una experiencia más fluida"
|
msgstr "Agrega FLXN a tu pantalla de inicio para una experiencia más fluida"
|
||||||
|
|
||||||
@@ -845,7 +845,7 @@ msgstr "Se requiere deviceId para la acción de transferencia"
|
|||||||
msgid "Disconnect Spotify"
|
msgid "Disconnect Spotify"
|
||||||
msgstr "Desconectar Spotify"
|
msgstr "Desconectar Spotify"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:68
|
#: src/components/ios-install-prompt.tsx:65
|
||||||
msgid "Dismiss"
|
msgid "Dismiss"
|
||||||
msgstr "Cerrar"
|
msgstr "Cerrar"
|
||||||
|
|
||||||
@@ -2324,11 +2324,11 @@ msgstr "T"
|
|||||||
msgid "Tap an opponent below to compare"
|
msgid "Tap an opponent below to compare"
|
||||||
msgstr "Toca un oponente abajo para comparar"
|
msgstr "Toca un oponente abajo para comparar"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:44
|
#: src/components/ios-install-prompt.tsx:43
|
||||||
msgid "Tap Menu (⋮) → Add to Home screen"
|
msgid "Tap Menu (⋮) → Add to Home screen"
|
||||||
msgstr "Toca Menú (⋮) → Agregar a pantalla de inicio"
|
msgstr "Toca Menú (⋮) → Agregar a pantalla de inicio"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:43
|
#: src/components/ios-install-prompt.tsx:42
|
||||||
msgid "Tap Share → Add to Home Screen"
|
msgid "Tap Share → Add to Home Screen"
|
||||||
msgstr "Toca Compartir → Agregar a pantalla de inicio"
|
msgstr "Toca Compartir → Agregar a pantalla de inicio"
|
||||||
|
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ msgstr "アクティビティ"
|
|||||||
msgid "Activity Details"
|
msgid "Activity Details"
|
||||||
msgstr "アクティビティの詳細"
|
msgstr "アクティビティの詳細"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:61
|
#: src/components/ios-install-prompt.tsx:58
|
||||||
msgid "Add FLXN to your home screen for a smoother experience"
|
msgid "Add FLXN to your home screen for a smoother experience"
|
||||||
msgstr "FLXNをホーム画面に追加すると、もっと快適に使えます"
|
msgstr "FLXNをホーム画面に追加すると、もっと快適に使えます"
|
||||||
|
|
||||||
@@ -845,7 +845,7 @@ msgstr "転送操作にはdeviceIdが必要です"
|
|||||||
msgid "Disconnect Spotify"
|
msgid "Disconnect Spotify"
|
||||||
msgstr "Spotifyの連携を解除"
|
msgstr "Spotifyの連携を解除"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:68
|
#: src/components/ios-install-prompt.tsx:65
|
||||||
msgid "Dismiss"
|
msgid "Dismiss"
|
||||||
msgstr "閉じる"
|
msgstr "閉じる"
|
||||||
|
|
||||||
@@ -2324,11 +2324,11 @@ msgstr "T"
|
|||||||
msgid "Tap an opponent below to compare"
|
msgid "Tap an opponent below to compare"
|
||||||
msgstr "下の対戦相手をタップして比較"
|
msgstr "下の対戦相手をタップして比較"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:44
|
#: src/components/ios-install-prompt.tsx:43
|
||||||
msgid "Tap Menu (⋮) → Add to Home screen"
|
msgid "Tap Menu (⋮) → Add to Home screen"
|
||||||
msgstr "メニュー (⋮) をタップ →「ホーム画面に追加」"
|
msgstr "メニュー (⋮) をタップ →「ホーム画面に追加」"
|
||||||
|
|
||||||
#: src/components/ios-install-prompt.tsx:43
|
#: src/components/ios-install-prompt.tsx:42
|
||||||
msgid "Tap Share → Add to Home Screen"
|
msgid "Tap Share → Add to Home Screen"
|
||||||
msgstr "共有をタップ →「ホーム画面に追加」"
|
msgstr "共有をタップ →「ホーム画面に追加」"
|
||||||
|
|
||||||
|
|||||||
@@ -41,19 +41,13 @@ type Actor = { player_id?: string };
|
|||||||
|
|
||||||
type ServerFnMeta = { name?: string };
|
type ServerFnMeta = { name?: string };
|
||||||
|
|
||||||
// Start's default generateFunctionId hashes the entry id, so the URL segment is
|
// The URL segment is a hash of the fn id; serverFnMeta carries the readable name.
|
||||||
// a sha256 and the compile-time meta is the only readable name. The path stays
|
|
||||||
// as the fallback for requests that carry no meta.
|
|
||||||
const serverFnName = (request: Request, meta?: ServerFnMeta): string => {
|
const serverFnName = (request: Request, meta?: ServerFnMeta): string => {
|
||||||
if (meta?.name) return meta.name;
|
if (meta?.name) return meta.name;
|
||||||
const pathParts = new URL(request.url).pathname.split("/").filter(Boolean);
|
const pathParts = new URL(request.url).pathname.split("/").filter(Boolean);
|
||||||
return pathParts[pathParts.length - 1] || "unknown";
|
return pathParts[pathParts.length - 1] || "unknown";
|
||||||
};
|
};
|
||||||
|
|
||||||
// The admin middleware refuses before serverFnLoggingMiddleware ever runs, so
|
|
||||||
// a denial has to write its own row — otherwise an operator reviewing the log
|
|
||||||
// during a probing attempt sees a clean history. The rejected payload is left
|
|
||||||
// out: it never reached a validator, so it is unbounded attacker input.
|
|
||||||
export const recordDeniedServerFn = (
|
export const recordDeniedServerFn = (
|
||||||
request: Request,
|
request: Request,
|
||||||
actor?: Actor,
|
actor?: Actor,
|
||||||
@@ -85,7 +79,6 @@ export const serverFnLoggingMiddleware = createMiddleware({
|
|||||||
const result = await next();
|
const result = await next();
|
||||||
const duration = Date.now() - startTime;
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
// Single audit writer; toServerResult resolves { success:false } instead of throwing, so read the flag.
|
|
||||||
const envelope = (result as { result?: unknown })?.result;
|
const envelope = (result as { result?: unknown })?.result;
|
||||||
const failed =
|
const failed =
|
||||||
!!envelope &&
|
!!envelope &&
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ export const superTokensAdminFunctionMiddleware = createMiddleware({
|
|||||||
return next({ context });
|
return next({ context });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifiers only — the full context carries phone + metadata.
|
|
||||||
logger.error("Unauthorized user in admin function.", {
|
logger.error("Unauthorized user in admin function.", {
|
||||||
userAuthId: context.userAuthId,
|
userAuthId: context.userAuthId,
|
||||||
roles: context.roles,
|
roles: context.roles,
|
||||||
|
|||||||
Reference in New Issue
Block a user