learning_ai_common_plat/packages/kill-switch-client/src/index.ts
saravanakumardb1 7e08cce95f fix(kill-switch-client): point at /settings/kill-switch (the real endpoint)
The client was calling GET ${baseUrl}/flags/kill-switch which does
not exist on platform-service. The actual kill-switch endpoint lives
under /settings/kill-switch in the settings module (public, no auth
required). The bug was silently masked by the client's fail-open
behavior on non-OK responses, but it produced a 404 on every page
load for every consumer (NoteLett, MindLyst, ChronoMind, FlowMonk,
NomGap, PeakPulse, JarvisJr, LysnrAI, ActionTrail, EffoRise, Local
Memory GPT).

Discovery: running the deployed NoteLett docker stack against the
sibling platform-service, every page load triggered:
  GET http://localhost:4003/api/flags/kill-switch?platform=web → 404
Confirmed by curl-ing both endpoints directly:
  /api/flags/kill-switch        → {"message":"Route GET:/api/flags/kill-switch not found"}
  /api/settings/kill-switch     → {"enabled":true,"disabled":false,"message":""}

Also adds the productId as a query param. The server route accepts
productId from the query string OR an x-product-id header — sending
both is harmless and improves debuggability when grepping logs.

Updated JSDoc and the corresponding test assertion. Test count
unchanged (6 passed).

Verified:
  pnpm --filter @bytelyst/kill-switch-client test → 6/6 passed
  pnpm --filter @bytelyst/kill-switch-client build → ok
  curl /api/settings/kill-switch?productId=notelett → 200 with payload
2026-05-23 10:17:42 -07:00

81 lines
2.6 KiB
TypeScript

/**
* Browser/React Native-safe kill switch client for platform-service.
*
* Checks GET /api/settings/kill-switch to determine if the app is disabled.
* Fail-open: returns { disabled: false } on any network error.
*
* @example
* ```ts
* import { createKillSwitchClient } from '@bytelyst/kill-switch-client';
*
* const ks = createKillSwitchClient({
* baseUrl: 'http://localhost:4003/api',
* productId: 'nomgap',
* });
*
* const result = await ks.check();
* if (result.disabled) showBlockScreen(result.message);
* ```
*/
export interface KillSwitchClientConfig {
/** Platform-service base URL (e.g. "http://localhost:4003/api"). */
baseUrl: string;
/** Product identifier sent as x-product-id header. */
productId: string;
/** Platform string for the query (e.g. "mobile", "web"). Default: "mobile". */
platform?: string;
}
export interface KillSwitchResult {
disabled: boolean;
message: string | null;
}
export interface KillSwitchClient {
/** Check if the app is disabled. Fail-open on any error. */
check(): Promise<KillSwitchResult>;
}
export function createKillSwitchClient(config: KillSwitchClientConfig): KillSwitchClient {
const { baseUrl, productId, platform = 'mobile' } = config;
async function check(): Promise<KillSwitchResult> {
try {
const requestId =
typeof globalThis.crypto?.randomUUID === 'function'
? globalThis.crypto.randomUUID()
: `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
// Endpoint lives under /settings/kill-switch in platform-service's
// settings module (no auth required). The route reads the
// `kill_switch` feature flag from the flags container and returns
// { enabled, disabled, message }. Older versions of this client
// pointed at /flags/kill-switch which does not exist; that bug
// was masked by fail-open behavior on 404 but produced noisy
// 404s on every page load.
const res = await globalThis.fetch(
`${baseUrl}/settings/kill-switch?platform=${encodeURIComponent(platform)}&productId=${encodeURIComponent(productId)}`,
{
headers: { 'x-product-id': productId, 'x-request-id': requestId },
}
);
if (!res.ok) return { disabled: false, message: null };
const data = (await res.json()) as KillSwitchResult;
return {
disabled: data.disabled ?? false,
message: data.message ?? null,
};
} catch {
// Fail-open: network errors should NOT block the user
return { disabled: false, message: null };
}
}
return { check };
}