Today the customer's "Perpanjang" only reaches the mitra via session-
scoped WS. If the mitra is on Home/Undangan, in a different session, or
backgrounded, the WS send no-ops and the 10s safeguard timeout fires
auto-reject (or auto-approve if the mitra happens to also have an
active general WS, depending on config) — either way the mitra never
saw the request.
Backend:
- extension.service.js::requestExtension now falls back to FCM via
notification.service when the mitra isn't on the session WS. Mirrors
the pairing notifyMitra pattern (Curhat Baru). Customer display name
is pulled into the session lookup for the FCM body.
- shared.chat.routes.js: /chat/:sessionId/info now returns
pending_extension (extension_id, duration_minutes, price,
requested_at, expires_at, timeout_seconds) so the chat screen can
rehydrate the accept/reject UI after a cold-start FCM tap. expires_at
is derived from requested_at + extension_timeout_seconds config.
Mitra app:
- mitra_chat_notifier.dart::connect parses pending_extension from /info
and seeds MitraChatConnectedData.extensionRequest — the existing
_buildExtensionView renders unchanged.
- notification_service.dart::_navigateFromMessage handles
type=extension_request → pushes /chat/session/<id>. Composes with
the new /info pending_extension to bring the mitra straight into the
accept/reject view.
Verified end-to-end on dev backend (FCM call returned sent=true; /info
returns pending_extension when within timeout window). Visual delivery
on emulator-5556 deferred — API 24 AVD queues FCM 5-30 min per
feedback-emulator-avd-versions.
Out of scope (follow-ups):
- Customer-side FCM for EXTENSION_RESPONSE (accepted/rejected/timeout)
- Perpanjang tab list endpoint + Flutter provider + UI
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Show local notification (sound + vibrate) when chat_request arrives
via WebSocket while mitra app is backgrounded
- Add NotificationService.showLocalNotification() for programmatic use
- Fix router redirect: don't redirect auth routes to splash during loading
- Handle binary/string WebSocket frames in ChatRequestNotifier
- Remove debug logging from backend and Flutter
- Control center: mitra ping config UI
- Both apps: dynamic ping, FCM deep-linking, unread badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 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>