07234cc9a8
Applies the brand platform consistently across the codebase: - Name: Semantika everywhere — app name/slug/scheme, bundle ids (com.semantika.app), workspace package names, CDK stack, Cognito domain prefix, secret name (semantika/app), database name, product ids (semantika_monthly/_yearly), and storage keys. - System instructions: Semantika is positioned as an intelligent reflection partner — not therapy, not self-help, not a course. It starts by exploring how the user interprets their situation and supports reflection and perspective-taking rather than delivering finished answers. Tone rules added: never judging, dramatic, overenthusiastic or preaching; always calm, curious, clear, respectful, structured, thoughtful. - Honest claims: instructions and knowledge base now explicitly frame neurosemantics and NLP as models and inspiration for reflection, not scientifically established methods — including a note that several NLP claims lack support in controlled studies. Claiming or implying proven effects is forbidden. - README: brand section (position, promise, mission, vision, positioning, tone, design words) and updated product philosophy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0118DaxZR36RpnY524vRqx3z
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import {
|
|
AuthRequest,
|
|
exchangeCodeAsync,
|
|
makeRedirectUri,
|
|
refreshAsync,
|
|
type DiscoveryDocument,
|
|
} from 'expo-auth-session';
|
|
import * as SecureStore from 'expo-secure-store';
|
|
import * as WebBrowser from 'expo-web-browser';
|
|
import { appConfig } from '../config';
|
|
|
|
WebBrowser.maybeCompleteAuthSession();
|
|
|
|
export type Provider = 'apple' | 'google' | 'email';
|
|
|
|
interface StoredSession {
|
|
accessToken: string;
|
|
refreshToken?: string;
|
|
expiresAt: number; // epoch ms
|
|
}
|
|
|
|
const STORE_KEY = 'semantika.session';
|
|
|
|
const discovery: DiscoveryDocument = {
|
|
authorizationEndpoint: `${appConfig.cognitoDomain}/oauth2/authorize`,
|
|
tokenEndpoint: `${appConfig.cognitoDomain}/oauth2/token`,
|
|
revocationEndpoint: `${appConfig.cognitoDomain}/oauth2/revoke`,
|
|
};
|
|
|
|
const redirectUri = makeRedirectUri({ scheme: 'semantika', path: 'redirect' });
|
|
|
|
/** Maps our provider names to Cognito hosted UI identity providers. */
|
|
const IDP: Record<Provider, string | undefined> = {
|
|
apple: 'SignInWithApple',
|
|
google: 'Google',
|
|
email: undefined, // Hosted UI's own email/password form
|
|
};
|
|
|
|
async function persist(session: StoredSession): Promise<void> {
|
|
await SecureStore.setItemAsync(STORE_KEY, JSON.stringify(session));
|
|
}
|
|
|
|
/** Signs in via the Cognito hosted UI with PKCE. Returns the session. */
|
|
export async function signIn(provider: Provider): Promise<StoredSession> {
|
|
const request = new AuthRequest({
|
|
clientId: appConfig.cognitoClientId,
|
|
redirectUri,
|
|
scopes: ['openid', 'email'],
|
|
usePKCE: true,
|
|
extraParams: IDP[provider] ? { identity_provider: IDP[provider] as string } : {},
|
|
});
|
|
|
|
const result = await request.promptAsync(discovery);
|
|
if (result.type !== 'success' || !result.params.code) {
|
|
throw new Error('Sign-in was cancelled');
|
|
}
|
|
|
|
const tokens = await exchangeCodeAsync(
|
|
{
|
|
clientId: appConfig.cognitoClientId,
|
|
code: result.params.code,
|
|
redirectUri,
|
|
extraParams: { code_verifier: request.codeVerifier ?? '' },
|
|
},
|
|
discovery,
|
|
);
|
|
|
|
const session: StoredSession = {
|
|
accessToken: tokens.accessToken,
|
|
refreshToken: tokens.refreshToken,
|
|
expiresAt: Date.now() + (tokens.expiresIn ?? 3600) * 1000,
|
|
};
|
|
await persist(session);
|
|
return session;
|
|
}
|
|
|
|
/** Returns a valid access token, refreshing if needed, or null if signed out. */
|
|
export async function getAccessToken(): Promise<string | null> {
|
|
const raw = await SecureStore.getItemAsync(STORE_KEY);
|
|
if (!raw) return null;
|
|
const session = JSON.parse(raw) as StoredSession;
|
|
|
|
if (Date.now() < session.expiresAt - 60_000) return session.accessToken;
|
|
if (!session.refreshToken) return null;
|
|
|
|
try {
|
|
const tokens = await refreshAsync(
|
|
{ clientId: appConfig.cognitoClientId, refreshToken: session.refreshToken },
|
|
discovery,
|
|
);
|
|
const refreshed: StoredSession = {
|
|
accessToken: tokens.accessToken,
|
|
refreshToken: tokens.refreshToken ?? session.refreshToken,
|
|
expiresAt: Date.now() + (tokens.expiresIn ?? 3600) * 1000,
|
|
};
|
|
await persist(refreshed);
|
|
return refreshed.accessToken;
|
|
} catch {
|
|
await signOut();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function signOut(): Promise<void> {
|
|
await SecureStore.deleteItemAsync(STORE_KEY);
|
|
}
|