diff --git a/Discussion_Points.txt b/Discussion_Points.txt index c7bb9fa..8765999 100644 --- a/Discussion_Points.txt +++ b/Discussion_Points.txt @@ -3,6 +3,18 @@ Discussion Points for EluxNetworks Team ──────────────────────────────────────────────────────────── +CURRENT STATUS + +I've uploaded the latest build to TestFlight. Please test on iOS. + +Tomorrow I'll start working on Android deployment. + +We need to schedule a call to discuss these questions in more detail. +I've hit some bottlenecks and need clarification before moving forward. +All questions are listed below and in the project scheme. + +──────────────────────────────────────────────────────────── + 1. BACKEND/API DEVELOPMENT ACCESS Request: Can I get access to develop backend/API endpoints myself? @@ -19,7 +31,7 @@ Option A: Access Granted (Preferred) I need: - Repository access (or separate repo) - - Database documentation + - Database access OR database dump (so I can set up my own) - Architecture overview I will: @@ -57,6 +69,17 @@ Hide: Reason: Mobile app has its own navigation and auth. +──────────────────────────────────────────────────────────── + +3. ACCOUNT ACCESS NEEDED + +Apple Developer Account + - Need access for App Store submissions, certificates, push notifications setup + +Stripe Account (if we're integrating payments/subscriptions) + - Need API keys for payment integration + - Need access to configure products, prices, webhooks + ──────────────────────────────────────────────────────────── SUMMARY diff --git a/app/(auth)/login.tsx b/app/(auth)/login.tsx index 84b772a..9ef1743 100644 --- a/app/(auth)/login.tsx +++ b/app/(auth)/login.tsx @@ -40,6 +40,8 @@ export default function LoginScreen() { const success = await login({ username: username.trim(), password }); if (success) { + // Clear password from memory after successful login + setPassword(''); router.replace('/(tabs)'); } }, [username, password, login, clearError]); diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx index 251d46a..f9250a2 100644 --- a/app/(tabs)/chat.tsx +++ b/app/(tabs)/chat.tsx @@ -30,11 +30,30 @@ export default function ChatScreen() { const [input, setInput] = useState(''); const [isSending, setIsSending] = useState(false); const flatListRef = useRef(null); + const lastSendTimeRef = useRef(0); + const SEND_COOLDOWN_MS = 1000; // 1 second cooldown between messages const handleSend = useCallback(async () => { const trimmedInput = input.trim(); if (!trimmedInput || isSending) return; + // Debounce: prevent rapid-fire messages + const now = Date.now(); + if (now - lastSendTimeRef.current < SEND_COOLDOWN_MS) { + return; + } + lastSendTimeRef.current = now; + + // Security: require beneficiary to be selected + if (!currentBeneficiary?.id) { + Alert.alert( + 'Select Patient', + 'Please select a patient from the Patients tab before starting a conversation.', + [{ text: 'OK' }] + ); + return; + } + const userMessage: Message = { id: Date.now().toString(), role: 'user', @@ -53,8 +72,8 @@ export default function ChatScreen() { ? `${beneficiaryContext} ${trimmedInput}` : trimmedInput; - // Pass deployment_id from selected beneficiary (fallback to '21' if not selected) - const deploymentId = currentBeneficiary?.id?.toString() || '21'; + // Pass deployment_id from selected beneficiary (required, no fallback) + const deploymentId = currentBeneficiary.id.toString(); const response = await api.sendMessage(questionWithContext, deploymentId); if (response.ok && response.data?.response) { @@ -85,7 +104,7 @@ export default function ChatScreen() { } finally { setIsSending(false); } - }, [input, isSending, getBeneficiaryContext]); + }, [input, isSending, currentBeneficiary, getBeneficiaryContext]); const renderMessage = ({ item }: { item: Message }) => { const isUser = item.role === 'user'; diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx index 09efa57..ae9fbb5 100644 --- a/contexts/AuthContext.tsx +++ b/contexts/AuthContext.tsx @@ -1,5 +1,5 @@ -import React, { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'; -import { api } from '@/services/api'; +import React, { createContext, useContext, useState, useEffect, useCallback, useRef, type ReactNode } from 'react'; +import { api, setOnUnauthorizedCallback } from '@/services/api'; import type { User, LoginCredentials, ApiError } from '@/types'; interface AuthState { @@ -30,6 +30,20 @@ export function AuthProvider({ children }: { children: ReactNode }) { checkAuth(); }, []); + // Set up callback for 401 responses - auto logout + useEffect(() => { + setOnUnauthorizedCallback(() => { + api.logout().then(() => { + setState({ + user: null, + isLoading: false, + isAuthenticated: false, + error: { message: 'Session expired. Please login again.' }, + }); + }); + }); + }, []); + const checkAuth = async () => { try { const isAuth = await api.isAuthenticated(); diff --git a/package-lock.json b/package-lock.json index a1ce803..a59a2b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@react-navigation/native": "^7.1.8", "expo": "~54.0.29", "expo-constants": "~18.0.12", + "expo-crypto": "~15.0.8", "expo-font": "~14.0.10", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", @@ -6581,6 +6582,18 @@ "react-native": "*" } }, + "node_modules/expo-crypto": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-15.0.8.tgz", + "integrity": "sha512-aF7A914TB66WIlTJvl5J6/itejfY78O7dq3ibvFltL9vnTALJ/7LYHvLT4fwmx9yUNS6ekLBtDGWivFWnj2Fcw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-file-system": { "version": "19.0.21", "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz", diff --git a/package.json b/package.json index 17b3905..154bebc 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@react-navigation/native": "^7.1.8", "expo": "~54.0.29", "expo-constants": "~18.0.12", + "expo-crypto": "~15.0.8", "expo-font": "~14.0.10", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", diff --git a/services/api.ts b/services/api.ts index 0fa9d3a..8de35ec 100644 --- a/services/api.ts +++ b/services/api.ts @@ -1,6 +1,14 @@ import * as SecureStore from 'expo-secure-store'; +import * as Crypto from 'expo-crypto'; import type { AuthResponse, ChatResponse, Beneficiary, ApiResponse, ApiError, DashboardSingleResponse, PatientDashboardData } from '@/types'; +// Callback for handling unauthorized responses (401) +let onUnauthorizedCallback: (() => void) | null = null; + +export function setOnUnauthorizedCallback(callback: () => void) { + onUnauthorizedCallback = callback; +} + const API_BASE_URL = 'https://eluxnetworks.net/function/well-api/api'; const CLIENT_ID = 'MA_001'; @@ -51,7 +59,10 @@ class ApiService { } private generateNonce(): string { - return Math.floor(Math.random() * 1000000).toString(); + const randomBytes = Crypto.getRandomBytes(16); + return Array.from(randomBytes) + .map(b => b.toString(16).padStart(2, '0')) + .join(''); } private async makeRequest(params: Record): Promise> { @@ -71,6 +82,21 @@ class ApiService { const data = await response.json(); + // Handle 401 Unauthorized - trigger logout + if (response.status === 401 || data.status === '401' || data.error === 'Unauthorized') { + if (onUnauthorizedCallback) { + onUnauthorizedCallback(); + } + return { + ok: false, + error: { + message: 'Session expired. Please login again.', + code: 'UNAUTHORIZED', + status: 401, + }, + }; + } + if (data.status === '200 OK' || data.ok === true) { return { data: data as T, ok: true }; } @@ -309,8 +335,11 @@ class ApiService { return { data: patients, ok: true }; } - // AI Chat - async sendMessage(question: string, deploymentId: string = '21'): Promise> { + // AI Chat - deploymentId is required, no default value for security + async sendMessage(question: string, deploymentId: string): Promise> { + if (!deploymentId) { + return { ok: false, error: { message: 'Please select a patient first', code: 'NO_PATIENT_SELECTED' } }; + } const token = await this.getToken(); const userName = await this.getUserName(); diff --git a/wellnuoSheme/03_DiscussionQuestions.json b/wellnuoSheme/03_DiscussionQuestions.json index 60ca626..229e759 100644 --- a/wellnuoSheme/03_DiscussionQuestions.json +++ b/wellnuoSheme/03_DiscussionQuestions.json @@ -40,6 +40,18 @@ "x": 520, "y": 300 }, + { + "id": "q_account_access", + "type": "card", + "title": "Account Access", + "borderColor": "yellow", + "tags": [ + "integration" + ], + "description": "Need access to:\n\n1. Apple Developer Account\n - App Store submissions\n - Certificates & provisioning\n - Push notifications setup\n\n2. Stripe Account (if using payments)\n - API keys\n - Products & prices config\n - Webhooks setup", + "x": 520, + "y": 480 + }, { "id": "api_yes", "type": "card", @@ -190,6 +202,10 @@ "from": "header", "to": "q_api_access" }, + { + "from": "header", + "to": "q_account_access" + }, { "from": "q_api_access", "to": "api_yes",