@@ -26,7 +26,8 @@ import * as validator from '../utils/validator';
26
26
import { TaskOptions } from './functions-api' ;
27
27
import { ComputeEngineCredential } from '../app/credential-internal' ;
28
28
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 ;
30
31
const FIREBASE_FUNCTION_URL_FORMAT = 'https://{locationId}-{projectId}.cloudfunctions.net/{resourceId}' ;
31
32
32
33
const FIREBASE_FUNCTIONS_CONFIG_HEADERS = {
@@ -54,6 +55,61 @@ export class FunctionsApiClient {
54
55
}
55
56
this . httpClient = new AuthorizedHttpClient ( app as FirebaseApp ) ;
56
57
}
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
+ }
57
113
58
114
/**
59
115
* Creates a task and adds it to a queue.
@@ -63,47 +119,53 @@ export class FunctionsApiClient {
63
119
* @param extensionId - Optional canonical ID of the extension.
64
120
* @param opts - Optional options when enqueuing a new task.
65
121
*/
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 > {
67
123
if ( ! validator . isNonEmptyString ( functionName ) ) {
68
124
throw new FirebaseFunctionsError (
69
125
'invalid-argument' , 'Function name must be a non empty string' ) ;
70
126
}
71
127
72
- const task = this . validateTaskOptions ( data , opts ) ;
73
128
let resources : utils . ParsedResource ;
74
129
try {
75
130
resources = utils . parseResourceName ( functionName , 'functions' ) ;
76
- }
77
- catch ( err ) {
131
+ } catch ( err ) {
78
132
throw new FirebaseFunctionsError (
79
133
'invalid-argument' , 'Function name must be a single string or a qualified resource name' ) ;
80
134
}
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
+ }
82
141
if ( typeof extensionId !== 'undefined' && validator . isNonEmptyString ( extensionId ) ) {
83
142
resources . resourceId = `ext-${ extensionId } -${ resources . resourceId } ` ;
84
143
}
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
+ }
107
169
}
108
170
109
171
private getUrl ( resourceName : utils . ParsedResource , urlFormat : string ) : Promise < string > {
@@ -167,15 +229,18 @@ export class FunctionsApiClient {
167
229
} ) ;
168
230
}
169
231
170
- private validateTaskOptions ( data : any , opts ?: TaskOptions ) : Task {
232
+ private validateTaskOptions ( data : any , resources : utils . ParsedResource , opts ?: TaskOptions ) : Task {
171
233
const task : Task = {
172
234
httpRequest : {
173
235
url : '' ,
174
236
oidcToken : {
175
237
serviceAccountEmail : '' ,
176
238
} ,
177
239
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
+ }
179
244
}
180
245
}
181
246
@@ -214,6 +279,19 @@ export class FunctionsApiClient {
214
279
}
215
280
task . dispatchDeadline = `${ opts . dispatchDeadlineSeconds } s` ;
216
281
}
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
+ }
217
295
if ( typeof opts . uri !== 'undefined' ) {
218
296
if ( ! validator . isURL ( opts . uri ) ) {
219
297
throw new FirebaseFunctionsError (
@@ -280,6 +358,7 @@ interface Error {
280
358
* containing the relevant fields for enqueueing tasks that tirgger Cloud Functions.
281
359
*/
282
360
export interface Task {
361
+ name ?: string ;
283
362
// A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional
284
363
// digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".
285
364
scheduleTime ?: string ;
@@ -317,7 +396,8 @@ export type FunctionsErrorCode =
317
396
| 'permission-denied'
318
397
| 'unauthenticated'
319
398
| 'not-found'
320
- | 'unknown-error' ;
399
+ | 'unknown-error'
400
+ | 'task-already-exists' ;
321
401
322
402
/**
323
403
* Firebase Functions error code structure. This extends PrefixedFirebaseError.
0 commit comments