Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
|
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
|
|
|
import NetInfo from '@react-native-community/netinfo';
|
|
|
|
|
|
|
|
|
|
const DRAFT_KEY = 'proposal_draft';
|
|
|
|
|
const OFFLINE_QUEUE_KEY = 'proposal_offline_queue';
|
|
|
|
|
|
|
|
|
|
export function useOfflineDraft<T extends Record<string, unknown>>(
|
|
|
|
|
initialData: T,
|
|
|
|
|
) {
|
|
|
|
|
const [draft, setDraft] = useState<T>(initialData);
|
|
|
|
|
const [isLoaded, setIsLoaded] = useState(false);
|
2026-05-18 15:30:30 -04:00
|
|
|
const saveTimeout = useRef<ReturnType<typeof setTimeout>>(null);
|
Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
AsyncStorage.getItem(DRAFT_KEY).then((stored) => {
|
|
|
|
|
if (stored) {
|
|
|
|
|
try {
|
|
|
|
|
setDraft(JSON.parse(stored));
|
|
|
|
|
} catch {
|
|
|
|
|
// Corrupted draft, use initial data
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setIsLoaded(true);
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const updateDraft = useCallback(
|
|
|
|
|
(updates: Partial<T>) => {
|
|
|
|
|
setDraft((prev) => {
|
|
|
|
|
const next = { ...prev, ...updates };
|
|
|
|
|
if (saveTimeout.current) clearTimeout(saveTimeout.current);
|
|
|
|
|
saveTimeout.current = setTimeout(() => {
|
|
|
|
|
AsyncStorage.setItem(DRAFT_KEY, JSON.stringify(next));
|
|
|
|
|
}, 1000);
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const clearDraft = useCallback(async () => {
|
|
|
|
|
if (saveTimeout.current) clearTimeout(saveTimeout.current);
|
|
|
|
|
await AsyncStorage.removeItem(DRAFT_KEY);
|
|
|
|
|
setDraft(initialData);
|
|
|
|
|
}, [initialData]);
|
|
|
|
|
|
|
|
|
|
return { draft, updateDraft, clearDraft, isLoaded };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface QueuedSubmission {
|
|
|
|
|
data: unknown;
|
|
|
|
|
timestamp: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function queueOfflineSubmission(data: unknown): Promise<void> {
|
|
|
|
|
const stored = await AsyncStorage.getItem(OFFLINE_QUEUE_KEY);
|
|
|
|
|
const queue: QueuedSubmission[] = stored ? JSON.parse(stored) : [];
|
|
|
|
|
queue.push({ data, timestamp: new Date().toISOString() });
|
|
|
|
|
await AsyncStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(queue));
|
|
|
|
|
}
|
|
|
|
|
|
audit: fix all Critical and High security/reliability issues across monorepo
6-domain audit (API, web, mobile, lambdas, infra, QA) with fixes:
API security: scope internal API key middleware to allowed paths only,
return 401 on invalid key instead of falling through, remove unvalidated
JWT code path, sanitize error messages, add UpdateProposal validator,
remove status field from UpdateProposalRequest to prevent over-posting,
log swallowed exceptions in ProposalService.
Infrastructure: enforce SSL on all S3 buckets, encrypt SQS queues,
enable optional MFA on Cognito, add API Gateway access logging.
Lambdas: fix _retry_request undefined variable across all 4 Lambdas,
re-raise exceptions in pdf-extract/pdf-generate instead of swallowing,
add idempotency guard to suggestions Lambda.
Web: add ErrorBoundary, add auth loading state to ProtectedRoute,
add mutation error toasts in AdminWorkspace, fix dead Cognito link.
Mobile: add mutex to offline queue processing, distinguish permanent
vs retryable failures, register all screens for both roles, log sync
errors.
Swagger/OpenAPI: add Swashbuckle with JWT bearer security definition,
add ProducesResponseType attributes to key endpoints.
Includes AUDIT-REPORT.md with complete findings and CLAUDE.md project
instructions.
2026-05-27 15:33:27 -04:00
|
|
|
let _isProcessing = false;
|
|
|
|
|
|
Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
export async function processOfflineQueue(
|
|
|
|
|
submitFn: (data: unknown) => Promise<unknown>,
|
|
|
|
|
): Promise<number> {
|
audit: fix all Critical and High security/reliability issues across monorepo
6-domain audit (API, web, mobile, lambdas, infra, QA) with fixes:
API security: scope internal API key middleware to allowed paths only,
return 401 on invalid key instead of falling through, remove unvalidated
JWT code path, sanitize error messages, add UpdateProposal validator,
remove status field from UpdateProposalRequest to prevent over-posting,
log swallowed exceptions in ProposalService.
Infrastructure: enforce SSL on all S3 buckets, encrypt SQS queues,
enable optional MFA on Cognito, add API Gateway access logging.
Lambdas: fix _retry_request undefined variable across all 4 Lambdas,
re-raise exceptions in pdf-extract/pdf-generate instead of swallowing,
add idempotency guard to suggestions Lambda.
Web: add ErrorBoundary, add auth loading state to ProtectedRoute,
add mutation error toasts in AdminWorkspace, fix dead Cognito link.
Mobile: add mutex to offline queue processing, distinguish permanent
vs retryable failures, register all screens for both roles, log sync
errors.
Swagger/OpenAPI: add Swashbuckle with JWT bearer security definition,
add ProducesResponseType attributes to key endpoints.
Includes AUDIT-REPORT.md with complete findings and CLAUDE.md project
instructions.
2026-05-27 15:33:27 -04:00
|
|
|
if (_isProcessing) return 0;
|
|
|
|
|
_isProcessing = true;
|
Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
|
audit: fix all Critical and High security/reliability issues across monorepo
6-domain audit (API, web, mobile, lambdas, infra, QA) with fixes:
API security: scope internal API key middleware to allowed paths only,
return 401 on invalid key instead of falling through, remove unvalidated
JWT code path, sanitize error messages, add UpdateProposal validator,
remove status field from UpdateProposalRequest to prevent over-posting,
log swallowed exceptions in ProposalService.
Infrastructure: enforce SSL on all S3 buckets, encrypt SQS queues,
enable optional MFA on Cognito, add API Gateway access logging.
Lambdas: fix _retry_request undefined variable across all 4 Lambdas,
re-raise exceptions in pdf-extract/pdf-generate instead of swallowing,
add idempotency guard to suggestions Lambda.
Web: add ErrorBoundary, add auth loading state to ProtectedRoute,
add mutation error toasts in AdminWorkspace, fix dead Cognito link.
Mobile: add mutex to offline queue processing, distinguish permanent
vs retryable failures, register all screens for both roles, log sync
errors.
Swagger/OpenAPI: add Swashbuckle with JWT bearer security definition,
add ProducesResponseType attributes to key endpoints.
Includes AUDIT-REPORT.md with complete findings and CLAUDE.md project
instructions.
2026-05-27 15:33:27 -04:00
|
|
|
try {
|
|
|
|
|
const state = await NetInfo.fetch();
|
|
|
|
|
if (!state.isConnected) return 0;
|
|
|
|
|
|
|
|
|
|
const stored = await AsyncStorage.getItem(OFFLINE_QUEUE_KEY);
|
|
|
|
|
if (!stored) return 0;
|
|
|
|
|
|
|
|
|
|
const queue: QueuedSubmission[] = JSON.parse(stored);
|
|
|
|
|
let processed = 0;
|
Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
|
audit: fix all Critical and High security/reliability issues across monorepo
6-domain audit (API, web, mobile, lambdas, infra, QA) with fixes:
API security: scope internal API key middleware to allowed paths only,
return 401 on invalid key instead of falling through, remove unvalidated
JWT code path, sanitize error messages, add UpdateProposal validator,
remove status field from UpdateProposalRequest to prevent over-posting,
log swallowed exceptions in ProposalService.
Infrastructure: enforce SSL on all S3 buckets, encrypt SQS queues,
enable optional MFA on Cognito, add API Gateway access logging.
Lambdas: fix _retry_request undefined variable across all 4 Lambdas,
re-raise exceptions in pdf-extract/pdf-generate instead of swallowing,
add idempotency guard to suggestions Lambda.
Web: add ErrorBoundary, add auth loading state to ProtectedRoute,
add mutation error toasts in AdminWorkspace, fix dead Cognito link.
Mobile: add mutex to offline queue processing, distinguish permanent
vs retryable failures, register all screens for both roles, log sync
errors.
Swagger/OpenAPI: add Swashbuckle with JWT bearer security definition,
add ProducesResponseType attributes to key endpoints.
Includes AUDIT-REPORT.md with complete findings and CLAUDE.md project
instructions.
2026-05-27 15:33:27 -04:00
|
|
|
for (const item of queue) {
|
|
|
|
|
try {
|
|
|
|
|
await submitFn(item.data);
|
|
|
|
|
processed++;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const status = (error as { response?: { status?: number } })?.response?.status;
|
|
|
|
|
if (status && status >= 400 && status < 500) {
|
|
|
|
|
console.error('Permanent failure for queued submission, skipping:', error);
|
|
|
|
|
processed++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
|
audit: fix all Critical and High security/reliability issues across monorepo
6-domain audit (API, web, mobile, lambdas, infra, QA) with fixes:
API security: scope internal API key middleware to allowed paths only,
return 401 on invalid key instead of falling through, remove unvalidated
JWT code path, sanitize error messages, add UpdateProposal validator,
remove status field from UpdateProposalRequest to prevent over-posting,
log swallowed exceptions in ProposalService.
Infrastructure: enforce SSL on all S3 buckets, encrypt SQS queues,
enable optional MFA on Cognito, add API Gateway access logging.
Lambdas: fix _retry_request undefined variable across all 4 Lambdas,
re-raise exceptions in pdf-extract/pdf-generate instead of swallowing,
add idempotency guard to suggestions Lambda.
Web: add ErrorBoundary, add auth loading state to ProtectedRoute,
add mutation error toasts in AdminWorkspace, fix dead Cognito link.
Mobile: add mutex to offline queue processing, distinguish permanent
vs retryable failures, register all screens for both roles, log sync
errors.
Swagger/OpenAPI: add Swashbuckle with JWT bearer security definition,
add ProducesResponseType attributes to key endpoints.
Includes AUDIT-REPORT.md with complete findings and CLAUDE.md project
instructions.
2026-05-27 15:33:27 -04:00
|
|
|
if (processed === queue.length) {
|
|
|
|
|
await AsyncStorage.removeItem(OFFLINE_QUEUE_KEY);
|
|
|
|
|
} else if (processed > 0) {
|
|
|
|
|
await AsyncStorage.setItem(
|
|
|
|
|
OFFLINE_QUEUE_KEY,
|
|
|
|
|
JSON.stringify(queue.slice(processed)),
|
|
|
|
|
);
|
Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
}
|
|
|
|
|
|
audit: fix all Critical and High security/reliability issues across monorepo
6-domain audit (API, web, mobile, lambdas, infra, QA) with fixes:
API security: scope internal API key middleware to allowed paths only,
return 401 on invalid key instead of falling through, remove unvalidated
JWT code path, sanitize error messages, add UpdateProposal validator,
remove status field from UpdateProposalRequest to prevent over-posting,
log swallowed exceptions in ProposalService.
Infrastructure: enforce SSL on all S3 buckets, encrypt SQS queues,
enable optional MFA on Cognito, add API Gateway access logging.
Lambdas: fix _retry_request undefined variable across all 4 Lambdas,
re-raise exceptions in pdf-extract/pdf-generate instead of swallowing,
add idempotency guard to suggestions Lambda.
Web: add ErrorBoundary, add auth loading state to ProtectedRoute,
add mutation error toasts in AdminWorkspace, fix dead Cognito link.
Mobile: add mutex to offline queue processing, distinguish permanent
vs retryable failures, register all screens for both roles, log sync
errors.
Swagger/OpenAPI: add Swashbuckle with JWT bearer security definition,
add ProducesResponseType attributes to key endpoints.
Includes AUDIT-REPORT.md with complete findings and CLAUDE.md project
instructions.
2026-05-27 15:33:27 -04:00
|
|
|
return processed;
|
|
|
|
|
} finally {
|
|
|
|
|
_isProcessing = false;
|
Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getOfflineQueueCount(): Promise<number> {
|
|
|
|
|
const stored = await AsyncStorage.getItem(OFFLINE_QUEUE_KEY);
|
|
|
|
|
if (!stored) return 0;
|
|
|
|
|
return JSON.parse(stored).length;
|
|
|
|
|
}
|