@@ -29,6 +29,7 @@ import { ComputeEngineCredential } from '../app/credential-internal';
29
29
const CLOUD_TASKS_API_RESOURCE_PATH = 'projects/{projectId}/locations/{locationId}/queues/{resourceId}/tasks' ;
30
30
const CLOUD_TASKS_API_URL_FORMAT = 'https://cloudtasks.googleapis.com/v2/' + CLOUD_TASKS_API_RESOURCE_PATH ;
31
31
const FIREBASE_FUNCTION_URL_FORMAT = 'https://{locationId}-{projectId}.cloudfunctions.net/{resourceId}' ;
32
+ export const EMULATED_SERVICE_ACCOUNT_DEFAULT = '[email protected] ' ;
32
33
33
34
const FIREBASE_FUNCTIONS_CONFIG_HEADERS = {
34
35
'X-Firebase-Client' : `fire-admin-node/${ utils . getSdkVersion ( ) } `
@@ -69,8 +70,8 @@ export class FunctionsApiClient {
69
70
}
70
71
if ( ! validator . isTaskId ( id ) ) {
71
72
throw new FirebaseFunctionsError (
72
- 'invalid-argument' , 'id can contain only letters ([A-Za-z]), numbers ([0-9]), '
73
- + 'hyphens (-), or underscores (_). The maximum length is 500 characters.' ) ;
73
+ 'invalid-argument' , 'id can contain only letters ([A-Za-z]), numbers ([0-9]), '
74
+ + 'hyphens (-), or underscores (_). The maximum length is 500 characters.' ) ;
74
75
}
75
76
76
77
let resources : utils . ParsedResource ;
@@ -91,7 +92,8 @@ export class FunctionsApiClient {
91
92
}
92
93
93
94
try {
94
- const serviceUrl = await this . getUrl ( resources , CLOUD_TASKS_API_URL_FORMAT . concat ( '/' , id ) ) ;
95
+ const serviceUrl = tasksEmulatorUrl ( resources , functionName ) ?. concat ( '/' , id )
96
+ ?? await this . getUrl ( resources , CLOUD_TASKS_API_URL_FORMAT . concat ( '/' , id ) ) ;
95
97
const request : HttpRequestConfig = {
96
98
method : 'DELETE' ,
97
99
url : serviceUrl ,
@@ -144,7 +146,10 @@ export class FunctionsApiClient {
144
146
145
147
const task = this . validateTaskOptions ( data , resources , opts ) ;
146
148
try {
147
- const serviceUrl = await this . getUrl ( resources , CLOUD_TASKS_API_URL_FORMAT ) ;
149
+ const serviceUrl =
150
+ tasksEmulatorUrl ( resources , functionName ) ??
151
+ await this . getUrl ( resources , CLOUD_TASKS_API_URL_FORMAT ) ;
152
+
148
153
const taskPayload = await this . updateTaskPayload ( task , resources , extensionId ) ;
149
154
const request : HttpRequestConfig = {
150
155
method : 'POST' ,
@@ -237,7 +242,7 @@ export class FunctionsApiClient {
237
242
serviceAccountEmail : '' ,
238
243
} ,
239
244
body : Buffer . from ( JSON . stringify ( { data } ) ) . toString ( 'base64' ) ,
240
- headers : {
245
+ headers : {
241
246
'Content-Type' : 'application/json' ,
242
247
...opts ?. headers ,
243
248
}
@@ -252,7 +257,7 @@ export class FunctionsApiClient {
252
257
if ( 'scheduleTime' in opts && 'scheduleDelaySeconds' in opts ) {
253
258
throw new FirebaseFunctionsError (
254
259
'invalid-argument' , 'Both scheduleTime and scheduleDelaySeconds are provided. '
255
- + 'Only one value should be set.' ) ;
260
+ + 'Only one value should be set.' ) ;
256
261
}
257
262
if ( 'scheduleTime' in opts && typeof opts . scheduleTime !== 'undefined' ) {
258
263
if ( ! ( opts . scheduleTime instanceof Date ) ) {
@@ -275,15 +280,15 @@ export class FunctionsApiClient {
275
280
|| opts . dispatchDeadlineSeconds > 1800 ) {
276
281
throw new FirebaseFunctionsError (
277
282
'invalid-argument' , 'dispatchDeadlineSeconds must be a non-negative duration in seconds '
278
- + 'and must be in the range of 15s to 30 mins.' ) ;
283
+ + 'and must be in the range of 15s to 30 mins.' ) ;
279
284
}
280
285
task . dispatchDeadline = `${ opts . dispatchDeadlineSeconds } s` ;
281
286
}
282
287
if ( 'id' in opts && typeof opts . id !== 'undefined' ) {
283
288
if ( ! validator . isTaskId ( opts . id ) ) {
284
289
throw new FirebaseFunctionsError (
285
290
'invalid-argument' , 'id can contain only letters ([A-Za-z]), numbers ([0-9]), '
286
- + 'hyphens (-), or underscores (_). The maximum length is 500 characters.' ) ;
291
+ + 'hyphens (-), or underscores (_). The maximum length is 500 characters.' ) ;
287
292
}
288
293
const resourcePath = utils . formatString ( CLOUD_TASKS_API_RESOURCE_PATH , {
289
294
projectId : resources . projectId ,
@@ -304,9 +309,14 @@ export class FunctionsApiClient {
304
309
}
305
310
306
311
private async updateTaskPayload ( task : Task , resources : utils . ParsedResource , extensionId ?: string ) : Promise < Task > {
307
- const functionUrl = validator . isNonEmptyString ( task . httpRequest . url )
308
- ? task . httpRequest . url
312
+ const defaultUrl = process . env . CLOUD_TASKS_EMULATOR_HOST ?
313
+ ''
309
314
: await this . getUrl ( resources , FIREBASE_FUNCTION_URL_FORMAT ) ;
315
+
316
+ const functionUrl = validator . isNonEmptyString ( task . httpRequest . url )
317
+ ? task . httpRequest . url
318
+ : defaultUrl ;
319
+
310
320
task . httpRequest . url = functionUrl ;
311
321
// When run from a deployed extension, we should be using ComputeEngineCredentials
312
322
if ( validator . isNonEmptyString ( extensionId ) && this . app . options . credential instanceof ComputeEngineCredential ) {
@@ -315,8 +325,16 @@ export class FunctionsApiClient {
315
325
// Don't send httpRequest.oidcToken if we set Authorization header, or Cloud Tasks will overwrite it.
316
326
delete task . httpRequest . oidcToken ;
317
327
} else {
318
- const account = await this . getServiceAccount ( ) ;
319
- task . httpRequest . oidcToken = { serviceAccountEmail : account } ;
328
+ try {
329
+ const account = await this . getServiceAccount ( ) ;
330
+ task . httpRequest . oidcToken = { serviceAccountEmail : account } ;
331
+ } catch ( e ) {
332
+ if ( process . env . CLOUD_TASKS_EMULATOR_HOST ) {
333
+ task . httpRequest . oidcToken = { serviceAccountEmail : EMULATED_SERVICE_ACCOUNT_DEFAULT } ;
334
+ } else {
335
+ throw e ;
336
+ }
337
+ }
320
338
}
321
339
return task ;
322
340
}
@@ -417,3 +435,10 @@ export class FirebaseFunctionsError extends PrefixedFirebaseError {
417
435
( this as any ) . __proto__ = FirebaseFunctionsError . prototype ;
418
436
}
419
437
}
438
+
439
+ function tasksEmulatorUrl ( resources : utils . ParsedResource , functionName : string ) : string | undefined {
440
+ if ( process . env . CLOUD_TASKS_EMULATOR_HOST ) {
441
+ return `http://${ process . env . CLOUD_TASKS_EMULATOR_HOST } /projects/${ resources . projectId } /locations/${ resources . locationId } /queues/${ functionName } /tasks` ;
442
+ }
443
+ return undefined ;
444
+ }
0 commit comments