2026-06-18 01:28:43 -05:00
"use strict" ;
var _ _createBinding = ( this && this . _ _createBinding ) || ( Object . create ? ( function ( o , m , k , k2 ) {
if ( k2 === undefined ) k2 = k ;
var desc = Object . getOwnPropertyDescriptor ( m , k ) ;
if ( ! desc || ( "get" in desc ? ! m . _ _esModule : desc . writable || desc . configurable ) ) {
desc = { enumerable : true , get : function ( ) { return m [ k ] ; } } ;
}
Object . defineProperty ( o , k2 , desc ) ;
} ) : ( function ( o , m , k , k2 ) {
if ( k2 === undefined ) k2 = k ;
o [ k2 ] = m [ k ] ;
} ) ) ;
var _ _setModuleDefault = ( this && this . _ _setModuleDefault ) || ( Object . create ? ( function ( o , v ) {
Object . defineProperty ( o , "default" , { enumerable : true , value : v } ) ;
} ) : function ( o , v ) {
o [ "default" ] = v ;
} ) ;
var _ _importStar = ( this && this . _ _importStar ) || ( function ( ) {
var ownKeys = function ( o ) {
ownKeys = Object . getOwnPropertyNames || function ( o ) {
var ar = [ ] ;
for ( var k in o ) if ( Object . prototype . hasOwnProperty . call ( o , k ) ) ar [ ar . length ] = k ;
return ar ;
} ;
return ownKeys ( o ) ;
} ;
return function ( mod ) {
if ( mod && mod . _ _esModule ) return mod ;
var result = { } ;
if ( mod != null ) for ( var k = ownKeys ( mod ) , i = 0 ; i < k . length ; i ++ ) if ( k [ i ] !== "default" ) _ _createBinding ( result , mod , k [ i ] ) ;
_ _setModuleDefault ( result , mod ) ;
return result ;
} ;
} ) ( ) ;
Object . defineProperty ( exports , "__esModule" , { value : true } ) ;
2026-07-08 00:08:18 -05:00
exports . onDesireSyncPartFinished = exports . onHowWellPartFinished = exports . onWheelPartFinished = exports . onThisOrThatPartFinished = exports . onGameSessionUpdate = void 0 ;
2026-06-18 01:28:43 -05:00
const admin = _ _importStar ( require ( "firebase-admin" ) ) ;
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
const firestore _1 = require ( "firebase-functions/v2/firestore" ) ;
2026-06-28 10:00:25 -05:00
const quietHours _1 = require ( "../notifications/quietHours" ) ;
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
const push _1 = require ( "../notifications/push" ) ;
const log _1 = require ( "../log" ) ;
2026-06-18 01:28:43 -05:00
/ * *
* Firestore trigger that notifies partners when a game session is created or completed .
*
* Path : couples / { coupleId } / sessions / { sessionId }
* Condition : onWrite ( create , update , delete )
* /
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
exports . onGameSessionUpdate = ( 0 , firestore _1 . onDocumentWritten ) ( 'couples/{coupleId}/sessions/{sessionId}' , async ( event ) => {
2026-06-30 02:38:31 -05:00
var _a , _b , _c , _d , _e , _f , _g , _h ;
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
const { coupleId , sessionId } = event . params ;
2026-06-26 20:04:05 -05:00
// The per-couple active-session lock lives at sessions/_active — it is a pointer, not a
// game session, so it must never produce a partner notification.
if ( sessionId === '_active' )
return ;
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
const change = event . data ;
if ( ! change )
return ;
2026-07-08 00:08:18 -05:00
if ( ! change . after . exists )
return ; // deletion — nothing to notify (skip the four reads below)
2026-06-18 01:28:43 -05:00
const db = admin . firestore ( ) ;
const messaging = admin . messaging ( ) ;
// Get the session document
const sessionDoc = await db . collection ( 'couples' ) . doc ( coupleId ) . collection ( 'sessions' ) . doc ( sessionId ) . get ( ) ;
const session = sessionDoc . data ( ) ;
if ( ! session ) {
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
log _1 . logger . log ( ` [onGameSessionUpdate] session ${ sessionId } not found, skipping ` ) ;
2026-06-18 01:28:43 -05:00
return ;
}
// Get couple info
const coupleDoc = await db . collection ( 'couples' ) . doc ( coupleId ) . get ( ) ;
if ( ! coupleDoc . exists ) {
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
log _1 . logger . warn ( ` [onGameSessionUpdate] couple ${ coupleId } not found ` ) ;
2026-06-18 01:28:43 -05:00
return ;
}
const coupleData = ( _a = coupleDoc . data ( ) ) !== null && _a !== void 0 ? _a : { } ;
const userIds = ( ( _b = coupleData . userIds ) !== null && _b !== void 0 ? _b : [ ] ) ;
if ( userIds . length !== 2 ) {
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
log _1 . logger . warn ( ` [onGameSessionUpdate] invalid couple ${ coupleId } : expected 2 users, got ${ userIds . length } ` ) ;
2026-06-18 01:28:43 -05:00
return ;
}
const partnerA = userIds [ 0 ] ;
const partnerB = userIds [ 1 ] ;
const userA = await db . collection ( 'users' ) . doc ( partnerA ) . get ( ) ;
const userB = await db . collection ( 'users' ) . doc ( partnerB ) . get ( ) ;
2026-06-30 02:38:31 -05:00
// displayName is E2EE in users/{uid}, so the OS-rendered push uses a generic label; the app shows
// the real name in-app (resolved locally). Avatar (photoUrl) stays plaintext and is still sent.
const partnerAName = 'Your partner' ;
const partnerBName = 'Your partner' ;
const avatarA = ( _c = userA . data ( ) ) === null || _c === void 0 ? void 0 : _c . photoUrl ;
const avatarB = ( _d = userB . data ( ) ) === null || _d === void 0 ? void 0 : _d . photoUrl ;
2026-06-28 10:00:25 -05:00
// M-001: per-recipient quiet-hours lookup ("no notifications" promise). Fail-open.
const dataFor = ( uid ) => ( uid === partnerA ? userA . data ( ) : userB . data ( ) ) ;
2026-06-30 02:38:31 -05:00
const currentData = ( _e = change . after . data ( ) ) !== null && _e !== void 0 ? _e : { } ;
2026-06-26 20:04:05 -05:00
const status = currentData . status ;
const sessionRef = db . collection ( 'couples' ) . doc ( coupleId ) . collection ( 'sessions' ) . doc ( sessionId ) ;
// Detect start/finish from the session's CURRENT state + a one-time flag, NOT from the
// change.before→change.after status diff. The atomic session start (F-RACE-001) writes the
// session doc AND the sessions/_active pointer in a single transaction; transactional writes
// can be delivered to onWrite with change.before === change.after (the "Snapshot has no
// readTime" path), which made the inactive→active edge unreliable and intermittently dropped
// the partner_started_game push. Claiming a flag inside a transaction makes each notification
// fire exactly once no matter how the event is delivered (and prevents double-sends).
// ── New session started ──────────────────────────────────────────────
if ( status === 'active' && ! currentData . startNotifiedAt ) {
const claimed = await db . runTransaction ( async ( tx ) => {
const fresh = await tx . get ( sessionRef ) ;
const d = fresh . data ( ) ;
if ( ! fresh . exists || ! d || d . status !== 'active' || d . startNotifiedAt )
return false ;
tx . update ( sessionRef , { startNotifiedAt : admin . firestore . FieldValue . serverTimestamp ( ) } ) ;
return true ;
} ) ;
if ( claimed ) {
const startedBy = currentData . startedByUserId ;
2026-06-30 02:38:31 -05:00
const gameType = ( _f = currentData . gameType ) !== null && _f !== void 0 ? _f : 'wheel' ;
2026-06-26 20:04:05 -05:00
const recipientId = startedBy === partnerA ? partnerB : partnerA ;
const starterName = startedBy === partnerA ? partnerAName : partnerBName ;
const starterAvatar = startedBy === partnerA ? avatarA : avatarB ;
2026-06-28 10:00:25 -05:00
if ( ( 0 , quietHours _1 . recipientInQuietHours ) ( dataFor ( recipientId ) ) ) {
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
log _1 . logger . log ( ` [onGameSessionUpdate] recipient ${ recipientId } in quiet hours — suppressing start push ` ) ;
2026-06-28 10:00:25 -05:00
}
else {
await notifyPartner ( db , messaging , recipientId , starterName , gameType , 'partner_started_game' , ` ${ starterName } has started a game. Tap to join! ` , coupleId , starterAvatar , sessionId ) ;
}
2026-06-26 20:04:05 -05:00
}
2026-06-18 01:28:43 -05:00
return ;
}
2026-06-28 22:24:46 -05:00
// ── Partner joined an active session ─────────────────────────────────
// The non-starter opening the session writes their uid into `joinedByUsers` (client, rule-gated).
// Notify the STARTER once, with the joiner's name + avatar. One-time via `joinNotifiedAt`
// (server-only flag, claimed in a transaction — same pattern as start/finishNotifiedAt).
if ( status === 'active' && ! currentData . joinNotifiedAt && Array . isArray ( currentData . joinedByUsers ) ) {
const startedBy = currentData . startedByUserId ;
const joiner = currentData . joinedByUsers . find ( ( u ) => u && u !== startedBy ) ;
if ( joiner ) {
const claimed = await db . runTransaction ( async ( tx ) => {
const fresh = await tx . get ( sessionRef ) ;
const d = fresh . data ( ) ;
if ( ! fresh . exists || ! d || d . status !== 'active' || d . joinNotifiedAt )
return false ;
const j = Array . isArray ( d . joinedByUsers ) ? d . joinedByUsers : [ ] ;
if ( ! j . some ( ( u ) => u && u !== d . startedByUserId ) )
return false ;
tx . update ( sessionRef , { joinNotifiedAt : admin . firestore . FieldValue . serverTimestamp ( ) } ) ;
return true ;
} ) ;
if ( claimed ) {
2026-06-30 02:38:31 -05:00
const gameType = ( _g = currentData . gameType ) !== null && _g !== void 0 ? _g : 'wheel' ;
2026-06-28 22:24:46 -05:00
const joinerName = joiner === partnerA ? partnerAName : partnerBName ;
const joinerAvatar = joiner === partnerA ? avatarA : avatarB ;
if ( ( 0 , quietHours _1 . recipientInQuietHours ) ( dataFor ( startedBy ) ) ) {
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
log _1 . logger . log ( ` [onGameSessionUpdate] starter ${ startedBy } in quiet hours — suppressing join push ` ) ;
2026-06-28 22:24:46 -05:00
}
else {
await notifyPartner ( db , messaging , startedBy , joinerName , gameType , 'partner_joined_game' , ` ${ joinerName } joined your game — tap to play together. ` , coupleId , joinerAvatar , sessionId ) ;
}
}
}
return ;
}
2026-06-26 20:04:05 -05:00
// ── Session completed (reveal ready for both) ────────────────────────
2026-07-07 22:31:02 -05:00
// Only a REAL completion has results: the async flow marks each user complete at reveal, so
// status flips to 'completed' with both uids in completedByUsers. An abandoned/quit session
// also lands on 'completed' (the abandon write) but with an empty list — pushing
// "finished — see your results!" for those was a false notification into an empty reveal.
const completedBy = Array . isArray ( currentData . completedByUsers ) ? currentData . completedByUsers : [ ] ;
if ( status === 'completed' && ! currentData . finishNotifiedAt && completedBy . length >= 2 ) {
2026-06-26 20:04:05 -05:00
const claimed = await db . runTransaction ( async ( tx ) => {
const fresh = await tx . get ( sessionRef ) ;
const d = fresh . data ( ) ;
if ( ! fresh . exists || ! d || d . status !== 'completed' || d . finishNotifiedAt )
return false ;
tx . update ( sessionRef , { finishNotifiedAt : admin . firestore . FieldValue . serverTimestamp ( ) } ) ;
return true ;
} ) ;
if ( claimed ) {
2026-06-30 02:38:31 -05:00
const gt = ( _h = currentData . gameType ) !== null && _h !== void 0 ? _h : 'wheel' ;
2026-06-28 10:00:25 -05:00
// Notify BOTH partners, each naming the OTHER. M-001: skip a recipient in quiet hours.
if ( ( 0 , quietHours _1 . recipientInQuietHours ) ( dataFor ( partnerA ) ) ) {
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
log _1 . logger . log ( ` [onGameSessionUpdate] ${ partnerA } in quiet hours — suppressing finish push ` ) ;
2026-06-28 10:00:25 -05:00
}
else {
await notifyPartner ( db , messaging , partnerA , partnerBName , gt , 'partner_finished_game' , ` ${ partnerBName } finished — tap to see your results! ` , coupleId , avatarB , sessionId ) ;
}
if ( ( 0 , quietHours _1 . recipientInQuietHours ) ( dataFor ( partnerB ) ) ) {
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
log _1 . logger . log ( ` [onGameSessionUpdate] ${ partnerB } in quiet hours — suppressing finish push ` ) ;
2026-06-28 10:00:25 -05:00
}
else {
await notifyPartner ( db , messaging , partnerB , partnerAName , gt , 'partner_finished_game' , ` ${ partnerAName } finished — tap to see your results! ` , coupleId , avatarA , sessionId ) ;
}
2026-06-26 20:04:05 -05:00
}
2026-06-18 01:28:43 -05:00
return ;
}
} ) ;
2026-06-27 13:31:09 -05:00
/ * *
* Notify the WAITING partner the moment the FIRST player finishes their part of an async game .
*
* Async games ( this _or _that / wheel / how _well / desire _sync ) write each player ' s answers to
* couples / { coupleId } / { gameType } / { sessionId } . answers [ uid ] ; the SESSION doc only flips to
* 'completed' once BOTH have answered ( which onGameSessionUpdate turns into partner _finished _game ) .
* Between first - finish and both - finish the waiting partner got NOTHING — they never learned it was
* their turn ( the symptom : "X finished a game but the partner was never notified" ) . The
* PARTNER _COMPLETED _PART client route already exists ; this is the trigger that finally emits it .
*
* Path : couples / { coupleId } / { gameType } / { sessionId } ( answer doc ; same id as the session doc ) .
2026-07-08 00:08:18 -05:00
*
* Deployed as four narrow , explicitly - pathed triggers ( one per async - game collection ) sharing the
* handler below , rather than one broad ` {gameType} ` wildcard — the wildcard fired a ( no - op )
* invocation on every write to ANY couple subcollection ( messages , reactions , … ) ; the narrow paths
* only fire for the four game collections .
2026-06-27 13:31:09 -05:00
* /
2026-07-08 00:08:18 -05:00
async function handleGamePartFinished ( coupleId , gameType , sessionId , change ) {
2026-06-30 02:38:31 -05:00
var _a , _b , _c , _d , _e ;
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
if ( ! ( change === null || change === void 0 ? void 0 : change . after . exists ) )
2026-06-27 13:31:09 -05:00
return ;
const answers = ( ( _b = ( _a = change . after . data ( ) ) === null || _a === void 0 ? void 0 : _a . answers ) !== null && _b !== void 0 ? _b : { } ) ;
const answerUids = Object . keys ( answers ) ;
// Only the FIRST finisher (exactly one answer present) nudges the partner. Zero = session just
// created; two = both done → the session flips to completed and onGameSessionUpdate sends
// partner_finished_game instead.
if ( answerUids . length !== 1 )
return ;
const finisherUid = answerUids [ 0 ] ;
const db = admin . firestore ( ) ;
const messaging = admin . messaging ( ) ;
const sessionRef = db . collection ( 'couples' ) . doc ( coupleId ) . collection ( 'sessions' ) . doc ( sessionId ) ;
// Claim a one-time flag on the SESSION doc (consistent with start/finishNotifiedAt; rule-safe;
// writing it re-fires onGameSessionUpdate but that no-ops on an active+already-started session).
const claimed = await db . runTransaction ( async ( tx ) => {
const fresh = await tx . get ( sessionRef ) ;
const d = fresh . data ( ) ;
if ( ! fresh . exists || ! d )
return false ;
if ( d . status === 'completed' || d . partFinishNotifiedAt )
return false ;
tx . update ( sessionRef , { partFinishNotifiedAt : admin . firestore . FieldValue . serverTimestamp ( ) } ) ;
return true ;
} ) ;
if ( ! claimed )
return ;
const coupleDoc = await db . collection ( 'couples' ) . doc ( coupleId ) . get ( ) ;
const userIds = ( ( _d = ( _c = coupleDoc . data ( ) ) === null || _c === void 0 ? void 0 : _c . userIds ) !== null && _d !== void 0 ? _d : [ ] ) ;
const recipient = userIds . find ( ( u ) => u !== finisherUid ) ;
if ( ! recipient )
return ;
const finisher = await db . collection ( 'users' ) . doc ( finisherUid ) . get ( ) ;
2026-06-30 02:38:31 -05:00
const finisherName = 'Your partner' ; // displayName is E2EE; the app shows the real name in-app
const finisherAvatar = ( _e = finisher . data ( ) ) === null || _e === void 0 ? void 0 : _e . photoUrl ;
2026-07-07 22:31:02 -05:00
await notifyPartner ( db , messaging , recipient , finisherName , gameType , 'partner_completed_part' , yourTurnBody ( gameType ) , coupleId , finisherAvatar , sessionId ) ;
2026-07-08 00:08:18 -05:00
}
exports . onThisOrThatPartFinished = ( 0 , firestore _1 . onDocumentWritten ) ( 'couples/{coupleId}/this_or_that/{sessionId}' , ( event ) => handleGamePartFinished ( event . params . coupleId , 'this_or_that' , event . params . sessionId , event . data ) ) ;
exports . onWheelPartFinished = ( 0 , firestore _1 . onDocumentWritten ) ( 'couples/{coupleId}/wheel/{sessionId}' , ( event ) => handleGamePartFinished ( event . params . coupleId , 'wheel' , event . params . sessionId , event . data ) ) ;
exports . onHowWellPartFinished = ( 0 , firestore _1 . onDocumentWritten ) ( 'couples/{coupleId}/how_well/{sessionId}' , ( event ) => handleGamePartFinished ( event . params . coupleId , 'how_well' , event . params . sessionId , event . data ) ) ;
exports . onDesireSyncPartFinished = ( 0 , firestore _1 . onDocumentWritten ) ( 'couples/{coupleId}/desire_sync/{sessionId}' , ( event ) => handleGamePartFinished ( event . params . coupleId , 'desire_sync' , event . params . sessionId , event . data ) ) ;
2026-07-07 22:31:02 -05:00
/ * *
* Per - game body for the "your turn" ( partner _completed _part ) push — mirrors the client banner ' s
* YOUR _TURN copy in ui / components / GamePromptBanner . kt so foreground and background read the same .
* /
function yourTurnBody ( gameType ) {
switch ( gameType ) {
case 'how_well' : return 'Your turn — guess their answers' ;
case 'desire_sync' : return 'Your turn — only mutual yeses ever show' ;
case 'this_or_that' : return 'Your turn — see where you line up' ;
default : return 'Your turn — reveal how you line up' ;
}
}
2026-06-18 01:28:43 -05:00
/ * *
* Send notification to partner via FCM and write to notification _queue .
* /
2026-06-25 12:40:38 -05:00
async function notifyPartner ( db , messaging , partnerId , partnerName , gameType , notificationType , body , coupleId , senderAvatarUrl , sessionId ) {
2026-06-24 16:15:30 -05:00
const title = notificationType === 'partner_finished_game'
? ` ${ partnerName } finished the game `
2026-06-27 13:31:09 -05:00
: notificationType === 'partner_completed_part'
? ` ${ partnerName } finished their part `
2026-06-28 22:24:46 -05:00
: notificationType === 'partner_joined_game'
? ` ${ partnerName } joined your game `
: ` ${ partnerName } is playing ` ;
2026-06-18 01:28:43 -05:00
// Write an in-app notification record for the partner
await db
. collection ( 'users' )
. doc ( partnerId )
. collection ( 'notification_queue' )
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
. add ( {
type : notificationType ,
title ,
body ,
read : false ,
createdAt : admin . firestore . FieldValue . serverTimestamp ( ) ,
2026-06-18 01:28:43 -05:00
} ) ;
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
await ( 0 , push _1 . sendPushToUser ) ( db , messaging , partnerId , {
notification : { title , body } ,
2026-06-25 12:40:38 -05:00
// Put backgrounded notifications on the Games channel instead of the FCM fallback channel,
// so importance/sound and the per-category toggle apply. E-OBS.
android : { notification : { channelId : 'game_activity' } } ,
refactor(functions): B1 migrate 13 Firestore triggers to v2 + harden
Migrate all Firestore triggers off the v1 API to firebase-functions/v2/firestore
(onDocumentCreated/Updated/Written); context.params→event.params, snap→event.data,
change.before/after→event.data.before/after. Region stays us-central1 (global option).
Hardening folded in (all reuse in-repo patterns):
- Adopt the shared sendPushToUser()/getUserTokens() helper in every trigger, removing
~7 copied token readers and the copied send/prune blocks. FCM tokens are no longer
logged in plaintext anywhere here (redacted inside push.ts).
- console.* → firebase-functions/logger (structured).
- Idempotency: new claimOnce() (atomic create-if-absent marker under
couples/{id}/notif_marks) dedupes at-least-once redelivery on the non-idempotent
senders (onAnswerWritten/Revealed, onMessageWritten, onCoupleLeave, onEntitlementChanged,
onDateReflectionWritten/Revealed, onDateHistoryCreated). Fail-open. onGameSessionUpdate/
onGamePartFinished/notifyOnDateMatch already had transactional claim-flags — preserved.
- onRestoreRequested: the plan's "claim" is implemented as the existing 60s time-WINDOW
(lastRestorePartnerAlertAt), not a permanent recipientUid marker — a permanent marker
would wrongly block legitimate re-requests (restore docs are deleted+recreated by design).
Faithful port of onGameSessionUpdate/onGamePartFinished (broad wildcard + allowlist kept);
the trigger split and read reorder are deferred to B6 as separate commits.
Build clean; 70 tests green (+ new idempotency.test.ts, push token/prune tests). Emulator
discovery loads all triggers as v2 in us-central1. firebase-functions still v5.1.1 (v6 bump
deferred to B6). dist rebuilt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:46:17 -05:00
data : Object . assign ( Object . assign ( { type : notificationType , couple _id : coupleId , game _type : gameType ,
2026-06-28 22:24:46 -05:00
// The acting partner's display name (public; also in the title) so the in-app foreground
// banner can name them instead of a generic "Your partner".
sender _name : partnerName } , ( sessionId ? { game _session _id : sessionId } : { } ) ) , ( senderAvatarUrl && senderAvatarUrl . length > 0
2026-06-24 16:15:30 -05:00
? { sender _avatar _url : senderAvatarUrl }
: { } ) ) ,
2026-06-18 01:28:43 -05:00
} ) ;
}
//# sourceMappingURL=onGameSessionUpdate.js.map