@@ -26,7 +26,8 @@ import * as validator from '../utils/validator';
2626import { TaskOptions } from './functions-api' ;
2727import { ComputeEngineCredential } from '../app/credential-internal' ;
2828
29- const CLOUD_TASKS_API_URL_FORMAT = 'https://cloudtasks.googleapis.com/v2/projects/{projectId}/locations/{locationId}/queues/{resourceId}/tasks' ;
29+ const CLOUD_TASKS_API_RESOURCE_PATH = 'projects/{projectId}/locations/{locationId}/queues/{resourceId}/tasks' ;
30+ const CLOUD_TASKS_API_URL_FORMAT = 'https://cloudtasks.googleapis.com/v2/' + CLOUD_TASKS_API_RESOURCE_PATH ;
3031const FIREBASE_FUNCTION_URL_FORMAT = 'https://{locationId}-{projectId}.cloudfunctions.net/{resourceId}' ;
3132
3233const FIREBASE_FUNCTIONS_CONFIG_HEADERS = {
@@ -54,6 +55,61 @@ export class FunctionsApiClient {
5455 }
5556 this . httpClient = new AuthorizedHttpClient ( app as FirebaseApp ) ;
5657 }
58+ /**
59+ * Deletes a task from a queue.
60+ *
61+ * @param id - The ID of the task to delete.
62+ * @param functionName - The function name of the queue.
63+ * @param extensionId - Optional canonical ID of the extension.
64+ */
65+ public async delete ( id : string , functionName : string , extensionId ?: string ) : Promise < void > {
66+ if ( ! validator . isNonEmptyString ( functionName ) ) {
67+ throw new FirebaseFunctionsError (
68+ 'invalid-argument' , 'Function name must be a non empty string' ) ;
69+ }
70+ if ( ! validator . isTaskId ( id ) ) {
71+ 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.' ) ;
74+ }
75+
76+ let resources : utils . ParsedResource ;
77+ try {
78+ resources = utils . parseResourceName ( functionName , 'functions' ) ;
79+ } catch ( err ) {
80+ throw new FirebaseFunctionsError (
81+ 'invalid-argument' , 'Function name must be a single string or a qualified resource name' ) ;
82+ }
83+ resources . projectId = resources . projectId || await this . getProjectId ( ) ;
84+ resources . locationId = resources . locationId || DEFAULT_LOCATION ;
85+ if ( ! validator . isNonEmptyString ( resources . resourceId ) ) {
86+ throw new FirebaseFunctionsError (
87+ 'invalid-argument' , 'No valid function name specified to enqueue tasks for.' ) ;
88+ }
89+ if ( typeof extensionId !== 'undefined' && validator . isNonEmptyString ( extensionId ) ) {
90+ resources . resourceId = `ext-${ extensionId } -${ resources . resourceId } ` ;
91+ }
92+
93+ try {
94+ const serviceUrl = await this . getUrl ( resources , CLOUD_TASKS_API_URL_FORMAT . concat ( '/' , id ) ) ;
95+ const request : HttpRequestConfig = {
96+ method : 'DELETE' ,
97+ url : serviceUrl ,
98+ headers : FIREBASE_FUNCTIONS_CONFIG_HEADERS ,
99+ } ;
100+ await this . httpClient . send ( request ) ;
101+ } catch ( err : unknown ) {
102+ if ( err instanceof HttpError ) {
103+ if ( err . response . status === 404 ) {
104+ // if no task with the provided ID exists, then ignore the delete.
105+ return ;
106+ }
107+ throw this . toFirebaseError ( err ) ;
108+ } else {
109+ throw err ;
110+ }
111+ }
112+ }
57113
58114 /**
59115 * Creates a task and adds it to a queue.
@@ -63,47 +119,53 @@ export class FunctionsApiClient {
63119 * @param extensionId - Optional canonical ID of the extension.
64120 * @param opts - Optional options when enqueuing a new task.
65121 */
66- public enqueue ( data : any , functionName : string , extensionId ?: string , opts ?: TaskOptions ) : Promise < void > {
122+ public async enqueue ( data : any , functionName : string , extensionId ?: string , opts ?: TaskOptions ) : Promise < void > {
67123 if ( ! validator . isNonEmptyString ( functionName ) ) {
68124 throw new FirebaseFunctionsError (
69125 'invalid-argument' , 'Function name must be a non empty string' ) ;
70126 }
71127
72- const task = this . validateTaskOptions ( data , opts ) ;
73128 let resources : utils . ParsedResource ;
74129 try {
75130 resources = utils . parseResourceName ( functionName , 'functions' ) ;
76- }
77- catch ( err ) {
131+ } catch ( err ) {
78132 throw new FirebaseFunctionsError (
79133 'invalid-argument' , 'Function name must be a single string or a qualified resource name' ) ;
80134 }
81-
135+ resources . projectId = resources . projectId || await this . getProjectId ( ) ;
136+ resources . locationId = resources . locationId || DEFAULT_LOCATION ;
137+ if ( ! validator . isNonEmptyString ( resources . resourceId ) ) {
138+ throw new FirebaseFunctionsError (
139+ 'invalid-argument' , 'No valid function name specified to enqueue tasks for.' ) ;
140+ }
82141 if ( typeof extensionId !== 'undefined' && validator . isNonEmptyString ( extensionId ) ) {
83142 resources . resourceId = `ext-${ extensionId } -${ resources . resourceId } ` ;
84143 }
85-
86- return this . getUrl ( resources , CLOUD_TASKS_API_URL_FORMAT )
87- . then ( ( serviceUrl ) => {
88- return this . updateTaskPayload ( task , resources , extensionId )
89- . then ( ( task ) => {
90- const request : HttpRequestConfig = {
91- method : 'POST' ,
92- url : serviceUrl ,
93- headers : FIREBASE_FUNCTIONS_CONFIG_HEADERS ,
94- data : {
95- task,
96- }
97- } ;
98- return this . httpClient . send ( request ) ;
99- } )
100- } )
101- . then ( ( ) => {
102- return ;
103- } )
104- . catch ( ( err ) => {
105- throw this . toFirebaseError ( err ) ;
106- } ) ;
144+
145+ const task = this . validateTaskOptions ( data , resources , opts ) ;
146+ try {
147+ const serviceUrl = await this . getUrl ( resources , CLOUD_TASKS_API_URL_FORMAT ) ;
148+ const taskPayload = await this . updateTaskPayload ( task , resources , extensionId ) ;
149+ const request : HttpRequestConfig = {
150+ method : 'POST' ,
151+ url : serviceUrl ,
152+ headers : FIREBASE_FUNCTIONS_CONFIG_HEADERS ,
153+ data : {
154+ task : taskPayload ,
155+ }
156+ } ;
157+ await this . httpClient . send ( request ) ;
158+ } catch ( err : unknown ) {
159+ if ( err instanceof HttpError ) {
160+ if ( err . response . status === 409 ) {
161+ throw new FirebaseFunctionsError ( 'task-already-exists' , `A task with ID ${ opts ?. id } already exists` ) ;
162+ } else {
163+ throw this . toFirebaseError ( err ) ;
164+ }
165+ } else {
166+ throw err ;
167+ }
168+ }
107169 }
108170
109171 private getUrl ( resourceName : utils . ParsedResource , urlFormat : string ) : Promise < string > {
@@ -167,15 +229,18 @@ export class FunctionsApiClient {
167229 } ) ;
168230 }
169231
170- private validateTaskOptions ( data : any , opts ?: TaskOptions ) : Task {
232+ private validateTaskOptions ( data : any , resources : utils . ParsedResource , opts ?: TaskOptions ) : Task {
171233 const task : Task = {
172234 httpRequest : {
173235 url : '' ,
174236 oidcToken : {
175237 serviceAccountEmail : '' ,
176238 } ,
177239 body : Buffer . from ( JSON . stringify ( { data } ) ) . toString ( 'base64' ) ,
178- headers : { 'Content-Type' : 'application/json' }
240+ headers : {
241+ 'Content-Type' : 'application/json' ,
242+ ...opts ?. headers ,
243+ }
179244 }
180245 }
181246
@@ -214,6 +279,19 @@ export class FunctionsApiClient {
214279 }
215280 task . dispatchDeadline = `${ opts . dispatchDeadlineSeconds } s` ;
216281 }
282+ if ( 'id' in opts && typeof opts . id !== 'undefined' ) {
283+ if ( ! validator . isTaskId ( opts . id ) ) {
284+ throw new FirebaseFunctionsError (
285+ 'invalid-argument' , 'id can contain only letters ([A-Za-z]), numbers ([0-9]), '
286+ + 'hyphens (-), or underscores (_). The maximum length is 500 characters.' ) ;
287+ }
288+ const resourcePath = utils . formatString ( CLOUD_TASKS_API_RESOURCE_PATH , {
289+ projectId : resources . projectId ,
290+ locationId : resources . locationId ,
291+ resourceId : resources . resourceId ,
292+ } ) ;
293+ task . name = resourcePath . concat ( '/' , opts . id ) ;
294+ }
217295 if ( typeof opts . uri !== 'undefined' ) {
218296 if ( ! validator . isURL ( opts . uri ) ) {
219297 throw new FirebaseFunctionsError (
@@ -280,6 +358,7 @@ interface Error {
280358 * containing the relevant fields for enqueueing tasks that tirgger Cloud Functions.
281359 */
282360export interface Task {
361+ name ?: string ;
283362 // A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional
284363 // digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".
285364 scheduleTime ?: string ;
@@ -317,7 +396,8 @@ export type FunctionsErrorCode =
317396 | 'permission-denied'
318397 | 'unauthenticated'
319398 | 'not-found'
320- | 'unknown-error' ;
399+ | 'unknown-error'
400+ | 'task-already-exists' ;
321401
322402/**
323403 * Firebase Functions error code structure. This extends PrefixedFirebaseError.
0 commit comments