@@ -29,6 +29,7 @@ import { ComputeEngineCredential } from '../app/credential-internal';
2929const CLOUD_TASKS_API_RESOURCE_PATH = 'projects/{projectId}/locations/{locationId}/queues/{resourceId}/tasks' ;
3030const CLOUD_TASKS_API_URL_FORMAT = 'https://cloudtasks.googleapis.com/v2/' + CLOUD_TASKS_API_RESOURCE_PATH ;
3131const FIREBASE_FUNCTION_URL_FORMAT = 'https://{locationId}-{projectId}.cloudfunctions.net/{resourceId}' ;
32+ export const EMULATED_SERVICE_ACCOUNT_DEFAULT = '[email protected] ' ; 3233
3334const FIREBASE_FUNCTIONS_CONFIG_HEADERS = {
3435 'X-Firebase-Client' : `fire-admin-node/${ utils . getSdkVersion ( ) } `
@@ -69,8 +70,8 @@ export class FunctionsApiClient {
6970 }
7071 if ( ! validator . isTaskId ( id ) ) {
7172 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.' ) ;
7475 }
7576
7677 let resources : utils . ParsedResource ;
@@ -91,7 +92,8 @@ export class FunctionsApiClient {
9192 }
9293
9394 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 ) ) ;
9597 const request : HttpRequestConfig = {
9698 method : 'DELETE' ,
9799 url : serviceUrl ,
@@ -144,7 +146,10 @@ export class FunctionsApiClient {
144146
145147 const task = this . validateTaskOptions ( data , resources , opts ) ;
146148 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+
148153 const taskPayload = await this . updateTaskPayload ( task , resources , extensionId ) ;
149154 const request : HttpRequestConfig = {
150155 method : 'POST' ,
@@ -237,7 +242,7 @@ export class FunctionsApiClient {
237242 serviceAccountEmail : '' ,
238243 } ,
239244 body : Buffer . from ( JSON . stringify ( { data } ) ) . toString ( 'base64' ) ,
240- headers : {
245+ headers : {
241246 'Content-Type' : 'application/json' ,
242247 ...opts ?. headers ,
243248 }
@@ -252,7 +257,7 @@ export class FunctionsApiClient {
252257 if ( 'scheduleTime' in opts && 'scheduleDelaySeconds' in opts ) {
253258 throw new FirebaseFunctionsError (
254259 'invalid-argument' , 'Both scheduleTime and scheduleDelaySeconds are provided. '
255- + 'Only one value should be set.' ) ;
260+ + 'Only one value should be set.' ) ;
256261 }
257262 if ( 'scheduleTime' in opts && typeof opts . scheduleTime !== 'undefined' ) {
258263 if ( ! ( opts . scheduleTime instanceof Date ) ) {
@@ -275,15 +280,15 @@ export class FunctionsApiClient {
275280 || opts . dispatchDeadlineSeconds > 1800 ) {
276281 throw new FirebaseFunctionsError (
277282 '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.' ) ;
279284 }
280285 task . dispatchDeadline = `${ opts . dispatchDeadlineSeconds } s` ;
281286 }
282287 if ( 'id' in opts && typeof opts . id !== 'undefined' ) {
283288 if ( ! validator . isTaskId ( opts . id ) ) {
284289 throw new FirebaseFunctionsError (
285290 '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.' ) ;
287292 }
288293 const resourcePath = utils . formatString ( CLOUD_TASKS_API_RESOURCE_PATH , {
289294 projectId : resources . projectId ,
@@ -304,9 +309,14 @@ export class FunctionsApiClient {
304309 }
305310
306311 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+ ''
309314 : await this . getUrl ( resources , FIREBASE_FUNCTION_URL_FORMAT ) ;
315+
316+ const functionUrl = validator . isNonEmptyString ( task . httpRequest . url )
317+ ? task . httpRequest . url
318+ : defaultUrl ;
319+
310320 task . httpRequest . url = functionUrl ;
311321 // When run from a deployed extension, we should be using ComputeEngineCredentials
312322 if ( validator . isNonEmptyString ( extensionId ) && this . app . options . credential instanceof ComputeEngineCredential ) {
@@ -315,8 +325,16 @@ export class FunctionsApiClient {
315325 // Don't send httpRequest.oidcToken if we set Authorization header, or Cloud Tasks will overwrite it.
316326 delete task . httpRequest . oidcToken ;
317327 } 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+ }
320338 }
321339 return task ;
322340 }
@@ -417,3 +435,10 @@ export class FirebaseFunctionsError extends PrefixedFirebaseError {
417435 ( this as any ) . __proto__ = FirebaseFunctionsError . prototype ;
418436 }
419437}
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