@@ -28,6 +28,8 @@ import {
28
28
Change ,
29
29
https ,
30
30
config ,
31
+ database ,
32
+ firestore ,
31
33
} from 'firebase-functions' ;
32
34
33
35
/** Fields of the event context that can be overridden/customized. */
@@ -159,7 +161,7 @@ export function wrap<T>(
159
161
[ 'eventId' , 'timestamp' , 'params' , 'auth' , 'authType' , 'resource' ] ,
160
162
options
161
163
) ;
162
- const defaultContext = _makeDefaultContext ( cloudFunction , options ) ;
164
+ const defaultContext = _makeDefaultContext ( cloudFunction , options , data ) ;
163
165
164
166
if (
165
167
has ( defaultContext , 'eventType' ) &&
@@ -218,25 +220,104 @@ function _checkOptionValidity(
218
220
219
221
function _makeDefaultContext < T > (
220
222
cloudFunction : CloudFunction < T > ,
221
- options : ContextOptions
223
+ options : ContextOptions ,
224
+ triggerData ?: T
222
225
) : EventContext {
223
226
let eventContextOptions = options as EventContextOptions ;
227
+ const eventResource = cloudFunction . __trigger . eventTrigger ?. resource ;
228
+ const eventType = cloudFunction . __trigger . eventTrigger ?. eventType ;
229
+
230
+ const optionsParams = eventContextOptions . params ?? { } ;
231
+ let triggerParams = { } ;
232
+ if ( eventResource && eventType && triggerData ) {
233
+ if ( eventType . startsWith ( 'google.firebase.database.ref.' ) ) {
234
+ let data : database . DataSnapshot ;
235
+ if ( eventType . endsWith ( '.write' ) ) {
236
+ // Triggered with change
237
+ if ( ! ( triggerData instanceof Change ) ) {
238
+ throw new Error ( 'Must be triggered by database change' ) ;
239
+ }
240
+ data = triggerData . before ;
241
+ } else {
242
+ data = triggerData as any ;
243
+ }
244
+ triggerParams = _extractDatabaseParams ( eventResource , data ) ;
245
+ } else if ( eventType . startsWith ( 'google.firestore.document.' ) ) {
246
+ let data : firestore . DocumentSnapshot ;
247
+ if ( eventType . endsWith ( '.write' ) ) {
248
+ // Triggered with change
249
+ if ( ! ( triggerData instanceof Change ) ) {
250
+ throw new Error ( 'Must be triggered by firestore document change' ) ;
251
+ }
252
+ data = triggerData . before ;
253
+ } else {
254
+ data = triggerData as any ;
255
+ }
256
+ triggerParams = _extractFirestoreDocumentParams ( eventResource , data ) ;
257
+ }
258
+ }
259
+ const params = merge ( { } , triggerParams , optionsParams ) ;
260
+
224
261
const defaultContext : EventContext = {
225
262
eventId : _makeEventId ( ) ,
226
- resource : cloudFunction . __trigger . eventTrigger && {
227
- service : cloudFunction . __trigger . eventTrigger . service ,
228
- name : _makeResourceName (
229
- cloudFunction . __trigger . eventTrigger . resource ,
230
- has ( eventContextOptions , 'params' ) && eventContextOptions . params
231
- ) ,
263
+ resource : eventResource && {
264
+ service : cloudFunction . __trigger . eventTrigger ?. service ,
265
+ name : _makeResourceName ( eventResource , params ) ,
232
266
} ,
233
- eventType : get ( cloudFunction , '__trigger.eventTrigger.eventType' ) ,
267
+ eventType,
234
268
timestamp : new Date ( ) . toISOString ( ) ,
235
- params : { } ,
269
+ params,
236
270
} ;
237
271
return defaultContext ;
238
272
}
239
273
274
+ function _extractDatabaseParams (
275
+ triggerResource : string ,
276
+ data : database . DataSnapshot
277
+ ) : EventContext [ 'params' ] {
278
+ const path = data . ref . toString ( ) . replace ( data . ref . root . toString ( ) , '' ) ;
279
+ return _extractParams ( triggerResource , path ) ;
280
+ }
281
+
282
+ function _extractFirestoreDocumentParams (
283
+ triggerResource : string ,
284
+ data : firestore . DocumentSnapshot
285
+ ) : EventContext [ 'params' ] {
286
+ // Resource format: databases/(default)/documents/<path>
287
+ return _extractParams (
288
+ triggerResource . replace ( / ^ d a t a b a s e s \/ [ ^ \/ ] + \/ d o c u m e n t s \/ / , '' ) ,
289
+ data . ref . path
290
+ ) ;
291
+ }
292
+
293
+ /**
294
+ * Extracts the `{wildcard}` values from `dataPath`.
295
+ * E.g. A wildcard path of `users/{userId}` with `users/FOO` would result in `{ userId: 'FOO' }`.
296
+ * @internal
297
+ */
298
+ export function _extractParams (
299
+ wildcardTriggerPath : string ,
300
+ dataPath : string
301
+ ) : EventContext [ 'params' ] {
302
+ // Trim start and end / and split into path components
303
+ const wildcardPaths = wildcardTriggerPath
304
+ . replace ( / ^ \/ ? ( .* ?) \/ ? $ / , '$1' )
305
+ . split ( '/' ) ;
306
+ const dataPaths = dataPath . replace ( / ^ \/ ? ( .* ?) \/ ? $ / , '$1' ) . split ( '/' ) ;
307
+ const params = { } ;
308
+ if ( wildcardPaths . length === dataPaths . length ) {
309
+ for ( let idx = 0 ; idx < wildcardPaths . length ; idx ++ ) {
310
+ const wildcardPath = wildcardPaths [ idx ] ;
311
+ const name = wildcardPath . replace ( / ^ { ( [ ^ / { } ] * ) } $ / , '$1' ) ;
312
+ if ( name !== wildcardPath ) {
313
+ // Wildcard parameter
314
+ params [ name ] = dataPaths [ idx ] ;
315
+ }
316
+ }
317
+ }
318
+ return params ;
319
+ }
320
+
240
321
/** Make a Change object to be used as test data for Firestore and real time database onWrite and onUpdate functions. */
241
322
export function makeChange < T > ( before : T , after : T ) : Change < T > {
242
323
return Change . fromObjects ( before , after ) ;
0 commit comments