All files / src/utils event-listeners.js

100% Statements 8/8
100% Branches 0/0
100% Functions 4/4
100% Lines 8/8

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64                                          1x                 10x 5x 4x                           10x             10x 4x       1x          
/**
 * Event-listener wiring — boots all event-driven listeners that should run
 * for the lifetime of the Express process. Today: the owner-left RTDB
 * listener. Future event-driven additions plug in here so src/index.js
 * stays a thin call-site.
 *
 * Architectural notes:
 *  - The presence checker is constructed here (not inside the listener) so
 *    the listener's contract stays platform-neutral and the path is
 *    centralised. The checker PROPAGATES errors (unlike the route-handler
 *    `isUserPresent` which fail-safes to "present") because the listener
 *    relies on errors to preserve the signal entry for retry.
 *  - Returns a stop() that detaches all listeners — used by tests and
 *    process shutdown hooks (`wireProcessShutdown` in process-shutdown.js).
 *  - INTENTIONALLY unconditional (not gated on NODE_ENV === 'production')
 *    despite `startCronJobs` being prod-only: event-driven RTDB listeners
 *    cost no Firestore quota (no polling), and they MUST run in local/dev
 *    so journey tests can exercise the full owner-disconnect flow against
 *    Firebase emulators. The asymmetry with crons is deliberate.
 */
 
const { registerOwnerLeftListener } = require('./owner-left-listener');
 
/**
 * Build a presence checker that reads `rooms/{roomId}/presence/{userId}` and
 * THROWS on RTDB read errors. The owner-left orchestrator catches the throw
 * and rejects up to the listener, which leaves the signal entry in place
 * for retry on next signal-fire or restart-scan.
 */
function buildPresenceChecker(rtdb) {
  return async function presenceChecker(roomId, userId) {
    const snap = await rtdb.ref(`rooms/${roomId}/presence/${userId}`).get();
    return snap.exists();
  };
}
 
/**
 * Start all event listeners. Returns a stop() that detaches each.
 *
 * @param {object} args
 * @param {object} args.db - firebase-admin Firestore
 * @param {object} args.rtdb - firebase-admin Realtime Database
 * @param {object} args.log - logger
 * @returns {() => void}
 */
function startEventListeners({ db, rtdb, log }) {
  const detachOwnerLeft = registerOwnerLeftListener({
    db,
    rtdb,
    log,
    presenceChecker: buildPresenceChecker(rtdb),
  });
 
  return function stop() {
    detachOwnerLeft();
  };
}
 
module.exports = {
  startEventListeners,
  // Exported for direct testing.
  buildPresenceChecker,
};