Open History to see the last commit
1import { VoiceTranscriptionDestination } from "@t3tools/contracts";
2import * as Schema from "effect/Schema";
3import { File, Paths } from "expo-file-system";
4import * as SecureStore from "expo-secure-store";
5import { whenProtectedDataAvailable } from "../../lib/protectedData";
6
7const key = "t3code.voice.audio-grant.v1";
8const deviceOnly = { keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY };
9// Both stores must name the same grant; clearing either one revokes it on the next launch.
10const markerFile = () => new File(Paths.document, "voice-audio-grant-id");
11const Grant = Schema.Struct({
12 id: Schema.NonEmptyString,
13 destination: VoiceTranscriptionDestination,
14});
15export type VoiceGrant = typeof Grant.Type;
16const decodeGrant = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.NullOr(Grant)));
17
18export async function readVoiceGrant() {
19 await whenProtectedDataAvailable();
20 try {
21 const raw = await SecureStore.getItemAsync(key, deviceOnly);
22 const grant = raw ? decodeGrant(raw) : null;
23 return grant && markerFile().textSync() === grant.id ? grant : null;
24 } catch {
25 return null;
26 }
27}
28
29export function writeVoiceGrant(grant: VoiceGrant | null) {
30 try {
31 markerFile().writeSync(grant?.id ?? "");
32 } catch (cause) {
33 SecureStore.setItem(key, "null", deviceOnly);
34 if (grant) throw cause;
35 return;
36 }
37 try {
38 SecureStore.setItem(key, JSON.stringify(grant), deviceOnly);
39 } catch (cause) {
40 if (grant) throw cause;
41 }
42}