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