Phase 3 testing fixes: Fastify 5, SSE→WebSocket+FCM, enums, security, session lifecycle

- Upgrade Fastify 4→5 with all plugins (@fastify/websocket 11, cors 11, sensible 6)
- Migrate all SSE endpoints to WebSocket + FCM push (mitra chat requests, customer pairing status)
- Add flutter_local_notifications for foreground push notifications with sound
- Add splash screen to both apps (hide auth loading flash)
- Introduce constants/enums across entire codebase (no raw string literals)
- Move price tiers from hardcoded array to app_config DB (data-driven, includes 1-min test tier)
- Add session ownership validation on all shared chat routes
- Add ownership checks on endSession, respondToExtension, requestExtension
- Fix session timer: auto-complete expired/stale sessions on server restart
- Add 5-min grace period for abandoned closing sessions
- Fix extension flow: proper session_resumed handling, clearExtensionRequest, closure grace timer cleanup
- Fix chat screens: ConnectChat in initState, session status check on connect
- Fix customer expired view: 5-min countdown, closure state priority over expired state
- Fix mitra extension UI: loading spinner, disable buttons, handle EXTENSION_RESOLVED error
- Fix GoRouter navigation consistency (no more Navigator.pushNamed)
- Fix goodbye view keyboard overflow (SingleChildScrollView)
- Add active session card on customer home screen with refresh on navigate back
- Fix PricingBottomSheet extension mode (RequestExtension instead of new pairing)
- Send session_resumed to both parties on extension accept

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 00:17:25 +08:00
parent b4efcf14c2
commit b0502ac92b
58 changed files with 2148 additions and 709 deletions

101
backend/src/constants.js Normal file
View File

@@ -0,0 +1,101 @@
// User types
export const UserType = Object.freeze({
CUSTOMER: 'customer',
MITRA: 'mitra',
})
// Chat session statuses
export const SessionStatus = Object.freeze({
SEARCHING: 'searching',
PENDING_ACCEPTANCE: 'pending_acceptance',
PENDING_PAYMENT: 'pending_payment',
ACTIVE: 'active',
EXTENDING: 'extending',
CLOSING: 'closing',
COMPLETED: 'completed',
CANCELLED: 'cancelled',
EXPIRED: 'expired',
})
// Chat message statuses
export const MessageStatus = Object.freeze({
SENT: 'sent',
DELIVERED: 'delivered',
READ: 'read',
})
// Chat message types
export const MessageType = Object.freeze({
TEXT: 'text',
})
// Chat request notification responses
export const NotificationResponse = Object.freeze({
ACCEPTED: 'accepted',
DECLINED: 'declined',
IGNORED: 'ignored',
})
// Session extension statuses
export const ExtensionStatus = Object.freeze({
PENDING: 'pending',
ACCEPTED: 'accepted',
REJECTED: 'rejected',
TIMEOUT: 'timeout',
})
// Customer transaction types
export const TransactionType = Object.freeze({
FREE_TRIAL: 'free_trial',
PAID: 'paid',
EXTENSION: 'extension',
})
// Who ended a session
export const EndedBy = Object.freeze({
SYSTEM: 'system',
CUSTOMER: 'customer',
MITRA: 'mitra',
})
// WebSocket message types
export const WsMessage = Object.freeze({
// Auth
AUTH: 'auth',
AUTH_OK: 'auth_ok',
ERROR: 'error',
// Chat
MESSAGE: 'message',
MESSAGE_ACK: 'message_ack',
MESSAGE_STATUS: 'message_status',
TYPING: 'typing',
// Pairing
CHAT_REQUEST: 'chat_request',
CHAT_REQUEST_CLOSED: 'chat_request_closed',
PAIRED: 'paired',
// Session lifecycle
SESSION_TIMER: 'session_timer',
SESSION_EXPIRED: 'session_expired',
SESSION_CLOSING: 'session_closing',
SESSION_COMPLETED: 'session_completed',
SESSION_ENDED: 'session_ended',
SESSION_PAUSED: 'session_paused',
SESSION_RESUMED: 'session_resumed',
SESSION_ASSIGNED: 'session_assigned',
SESSION_REROUTED: 'session_rerouted',
REROUTED: 'rerouted',
// Extension
EXTENSION_REQUEST: 'extension_request',
EXTENSION_RESPONSE: 'extension_response',
// Early end
EARLY_END: 'early_end',
// Delivery
DELIVERED: 'delivered',
READ: 'read',
})