learning_ai_common_plat/packages/kill-switch-client/src/index.ts

74 lines
2.1 KiB
TypeScript

/**
* Browser/React Native-safe kill switch client for platform-service.
*
* Checks GET /api/flags/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)}`;
const res = await globalThis.fetch(
`${baseUrl}/flags/kill-switch?platform=${encodeURIComponent(platform)}`,
{
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 };
}