Files
cinny/src/app/plugins/call/expectedDevices.ts
T

75 lines
2.9 KiB
TypeScript
Raw Normal View History

/**
* [Gitea #121] The fork's expected-device check. Before joining, EC reports
* the user's usual mic / output (the one they last picked) as missing; we ask
* "Join anyway" or "Cancel". In a call, losing it is a toast. Pure helpers.
*/
export type DeviceKind = 'audioinput' | 'audiooutput';
export type MissingDevice = { kind: DeviceKind; expected: string; fallback: string | null };
const isKind = (k: unknown): k is DeviceKind => k === 'audioinput' || k === 'audiooutput';
const parseOne = (v: unknown): MissingDevice | undefined => {
if (!v || typeof v !== 'object') return undefined;
const { kind, expected, fallback } = v as Record<string, unknown>;
if (!isKind(kind) || typeof expected !== 'string' || !expected) return undefined;
return { kind, expected, fallback: typeof fallback === 'string' && fallback ? fallback : null };
};
/** `io.lotus.device_missing` → the missing devices (empty if malformed). */
export const parseDeviceMissing = (data: unknown): MissingDevice[] => {
const list = (data as { missing?: unknown } | undefined)?.missing;
if (!Array.isArray(list)) return [];
return list.map(parseOne).filter((d): d is MissingDevice => !!d);
};
/** `io.lotus.device_lost` → the device that went away. */
export const parseDeviceLost = (data: unknown): MissingDevice | undefined => parseOne(data);
const what = (kind: DeviceKind): string => (kind === 'audioinput' ? 'microphone' : 'audio output');
/** "Your usual microphone **Arctis 7** isn't connected." lines + the fallback. */
export const describeMissing = (
d: MissingDevice,
): { title: string; expected: string; fallback: string } => ({
title: `Your usual ${what(d.kind)} isn't connected`,
expected: d.expected,
fallback:
d.fallback ?? (d.kind === 'audioinput' ? 'the default microphone' : 'the default output'),
});
export const describeLost = (d: MissingDevice): string =>
`${d.expected} disconnected — now using ${
d.fallback ?? (d.kind === 'audioinput' ? 'the default microphone' : 'the default output')
}.`;
// "Join anyway" holds for the rest of the session for the same device(s).
const ACCEPTED_KEY = 'lotus-device-check-accepted';
const keyOf = (missing: MissingDevice[]): string =>
missing
.map((d) => `${d.kind}:${d.expected}`)
.sort()
.join('|');
const readAccepted = (): string[] => {
try {
const v: unknown = JSON.parse(sessionStorage.getItem(ACCEPTED_KEY) ?? '[]');
return Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : [];
} catch {
return [];
}
};
export const isAcceptedThisSession = (missing: MissingDevice[]): boolean =>
readAccepted().includes(keyOf(missing));
export const acceptForSession = (missing: MissingDevice[]): void => {
try {
const list = readAccepted();
const k = keyOf(missing);
if (!list.includes(k)) sessionStorage.setItem(ACCEPTED_KEY, JSON.stringify([...list, k]));
} catch {
// storage unavailable: ask again next time
}
};