@dereekb/firebase-server 13.0.0 → 13.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.default.js +1 -0
- package/index.cjs.js +1923 -2001
- package/index.cjs.mjs +2 -0
- package/index.esm.js +1924 -2000
- package/mailgun/index.cjs.default.js +1 -0
- package/mailgun/index.cjs.js +28 -35
- package/mailgun/index.cjs.mjs +2 -0
- package/mailgun/index.esm.js +28 -35
- package/mailgun/package.json +17 -22
- package/model/index.cjs.default.js +1 -0
- package/model/index.cjs.js +4042 -4573
- package/model/index.cjs.mjs +2 -0
- package/model/index.esm.js +4042 -4573
- package/model/package.json +23 -27
- package/package.json +42 -61
- package/src/lib/function/error.d.ts +0 -8
- package/test/index.cjs.default.js +1 -0
- package/test/index.cjs.js +867 -933
- package/test/index.cjs.mjs +2 -0
- package/test/index.esm.js +867 -933
- package/test/package.json +22 -26
- package/zoho/index.cjs.default.js +1 -0
- package/zoho/index.cjs.js +82 -85
- package/zoho/index.cjs.mjs +2 -0
- package/zoho/index.esm.js +82 -85
- package/zoho/package.json +17 -21
package/index.esm.js
CHANGED
|
@@ -3,8 +3,6 @@ import { partialServerError, isServerError, randomNumberFactory, cachedGetter, A
|
|
|
3
3
|
import { HttpsError } from 'firebase-functions/https';
|
|
4
4
|
import { hoursToMs, toISODateString } from '@dereekb/date';
|
|
5
5
|
import { BaseError } from 'make-error';
|
|
6
|
-
import 'core-js/modules/es.iterator.constructor.js';
|
|
7
|
-
import 'core-js/modules/es.iterator.for-each.js';
|
|
8
6
|
import { from } from 'rxjs';
|
|
9
7
|
import { FieldValue, FieldPath } from '@google-cloud/firestore';
|
|
10
8
|
import { Module, Injectable, ValidationPipe, Optional, Inject, Logger, ForbiddenException, createParamDecorator } from '@nestjs/common';
|
|
@@ -12,152 +10,141 @@ import { mergeModuleMetadata, ServerEnvironmentService, DEFAULT_BASE_WEBHOOK_PAT
|
|
|
12
10
|
import { https, scheduler } from 'firebase-functions/v2';
|
|
13
11
|
import { toTransformAndValidateFunctionResultFactory, transformAndValidateObjectFactory } from '@dereekb/model';
|
|
14
12
|
import admin from 'firebase-admin';
|
|
15
|
-
import 'core-js/modules/es.iterator.map.js';
|
|
16
13
|
import { ApiError } from '@google-cloud/storage';
|
|
17
14
|
import { addMilliseconds, addHours } from 'date-fns';
|
|
18
15
|
import { isUint8Array, isArrayBuffer } from 'util/types';
|
|
19
|
-
import 'core-js/modules/es.map.get-or-insert.js';
|
|
20
|
-
import 'core-js/modules/es.map.get-or-insert-computed.js';
|
|
21
16
|
import { NestFactory } from '@nestjs/core';
|
|
22
17
|
import { ExpressAdapter } from '@nestjs/platform-express';
|
|
23
18
|
import express from 'express';
|
|
24
19
|
|
|
25
|
-
/**
|
|
26
|
-
* @deprecated use DBX_FIREBASE_SERVER_NO_AUTH_ERROR_CODE instead
|
|
27
|
-
*/
|
|
28
|
-
const NO_AUTH_ERROR_CODE = DBX_FIREBASE_SERVER_NO_AUTH_ERROR_CODE;
|
|
29
20
|
function unauthenticatedContextHasNoAuthData() {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
return unauthenticatedError({
|
|
22
|
+
message: 'expected auth',
|
|
23
|
+
code: DBX_FIREBASE_SERVER_NO_AUTH_ERROR_CODE
|
|
24
|
+
});
|
|
34
25
|
}
|
|
35
|
-
/**
|
|
36
|
-
* @deprecated use DBX_FIREBASE_SERVER_NO_UID_ERROR_CODE instead
|
|
37
|
-
*/
|
|
38
|
-
const NO_UID_ERROR_CODE = DBX_FIREBASE_SERVER_NO_AUTH_ERROR_CODE;
|
|
39
26
|
function unauthenticatedContextHasNoUidError() {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
27
|
+
return unauthenticatedError({
|
|
28
|
+
message: 'no user uid',
|
|
29
|
+
code: DBX_FIREBASE_SERVER_NO_AUTH_ERROR_CODE
|
|
30
|
+
});
|
|
44
31
|
}
|
|
45
32
|
// MARK: General Errors
|
|
46
33
|
const UNAUTHENTICATED_ERROR_CODE = 'UNAUTHENTICATED';
|
|
47
34
|
function unauthenticatedError(messageOrError) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
35
|
+
const serverError = partialServerError(messageOrError);
|
|
36
|
+
return new HttpsError('unauthenticated', serverError?.message || 'unauthenticated', {
|
|
37
|
+
status: 401,
|
|
38
|
+
code: UNAUTHENTICATED_ERROR_CODE,
|
|
39
|
+
...serverError,
|
|
40
|
+
_error: undefined
|
|
41
|
+
});
|
|
55
42
|
}
|
|
56
43
|
const FORBIDDEN_ERROR_CODE = 'FORBIDDEN';
|
|
57
44
|
function forbiddenError(messageOrError) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
const serverError = partialServerError(messageOrError);
|
|
46
|
+
return new HttpsError('permission-denied', serverError?.message || 'forbidden', {
|
|
47
|
+
status: 403,
|
|
48
|
+
code: FORBIDDEN_ERROR_CODE,
|
|
49
|
+
...serverError,
|
|
50
|
+
_error: undefined
|
|
51
|
+
});
|
|
65
52
|
}
|
|
66
53
|
const PERMISSION_DENIED_ERROR_CODE = 'PERMISSION_DENIED';
|
|
67
54
|
function permissionDeniedError(messageOrError) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
55
|
+
const serverError = partialServerError(messageOrError);
|
|
56
|
+
return new HttpsError('permission-denied', serverError?.message || 'permission denied', {
|
|
57
|
+
status: 403,
|
|
58
|
+
code: PERMISSION_DENIED_ERROR_CODE,
|
|
59
|
+
...serverError,
|
|
60
|
+
_error: undefined
|
|
61
|
+
});
|
|
75
62
|
}
|
|
76
63
|
const NOT_FOUND_ERROR_CODE = 'NOT_FOUND';
|
|
77
64
|
function notFoundError(messageOrError) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
const serverError = partialServerError(messageOrError);
|
|
66
|
+
return new HttpsError('not-found', serverError?.message || 'not found', {
|
|
67
|
+
status: 404,
|
|
68
|
+
code: NOT_FOUND_ERROR_CODE,
|
|
69
|
+
...serverError,
|
|
70
|
+
_error: undefined
|
|
71
|
+
});
|
|
85
72
|
}
|
|
86
73
|
const MODEL_NOT_AVAILABLE_ERROR_CODE = 'MODEL_NOT_AVAILABLE';
|
|
87
74
|
function modelNotAvailableError(messageOrError) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
const serverError = partialServerError(messageOrError);
|
|
76
|
+
return new HttpsError('not-found', serverError?.message || 'model was not available', {
|
|
77
|
+
status: 404,
|
|
78
|
+
code: MODEL_NOT_AVAILABLE_ERROR_CODE,
|
|
79
|
+
...serverError,
|
|
80
|
+
_error: undefined
|
|
81
|
+
});
|
|
95
82
|
}
|
|
96
83
|
const BAD_REQUEST_ERROR_CODE = 'BAD_REQUEST';
|
|
97
84
|
function badRequestError(messageOrError) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
85
|
+
const serverError = partialServerError(messageOrError);
|
|
86
|
+
return new HttpsError('invalid-argument', serverError?.message || 'bad request', {
|
|
87
|
+
status: 400,
|
|
88
|
+
code: BAD_REQUEST_ERROR_CODE,
|
|
89
|
+
...serverError,
|
|
90
|
+
_error: undefined
|
|
91
|
+
});
|
|
105
92
|
}
|
|
106
93
|
const CONFLICT_ERROR_CODE = 'CONFLICT';
|
|
107
94
|
function preconditionConflictError(messageOrError) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
95
|
+
const serverError = partialServerError(messageOrError);
|
|
96
|
+
return new HttpsError('failed-precondition', serverError?.message || 'conflict', {
|
|
97
|
+
status: 409,
|
|
98
|
+
code: CONFLICT_ERROR_CODE,
|
|
99
|
+
...serverError,
|
|
100
|
+
_error: undefined
|
|
101
|
+
});
|
|
115
102
|
}
|
|
116
103
|
const ALREADY_EXISTS_ERROR_CODE = 'ALREADY_EXISTS';
|
|
117
104
|
function alreadyExistsError(messageOrError) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
105
|
+
const serverError = partialServerError(messageOrError);
|
|
106
|
+
return new HttpsError('already-exists', serverError?.message || 'already exists', {
|
|
107
|
+
status: 409,
|
|
108
|
+
code: ALREADY_EXISTS_ERROR_CODE,
|
|
109
|
+
...serverError,
|
|
110
|
+
_error: undefined
|
|
111
|
+
});
|
|
125
112
|
}
|
|
126
113
|
const UNAVAILABLE_ERROR_CODE = 'UNAVAILABLE';
|
|
127
114
|
function unavailableError(messageOrError) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
115
|
+
const serverError = partialServerError(messageOrError);
|
|
116
|
+
return new HttpsError('unavailable', serverError?.message || 'service unavailable', {
|
|
117
|
+
status: 503,
|
|
118
|
+
code: UNAVAILABLE_ERROR_CODE,
|
|
119
|
+
...serverError,
|
|
120
|
+
_error: undefined
|
|
121
|
+
});
|
|
135
122
|
}
|
|
136
123
|
const UNAVAILABLE_OR_DEACTIVATED_FUNCTION_ERROR_CODE = 'UNAVAILABLE_OR_DEACTIVATED_FUNCTION';
|
|
137
124
|
function unavailableOrDeactivatedFunctionError(messageOrError) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
125
|
+
const serverError = partialServerError(messageOrError);
|
|
126
|
+
return new HttpsError('unimplemented', serverError?.message || 'the requested function is not available or has been deactivated for use', {
|
|
127
|
+
status: 501,
|
|
128
|
+
code: UNAVAILABLE_OR_DEACTIVATED_FUNCTION_ERROR_CODE,
|
|
129
|
+
...serverError,
|
|
130
|
+
_error: undefined
|
|
131
|
+
});
|
|
145
132
|
}
|
|
146
133
|
const INTERNAL_SERVER_ERROR_CODE = 'INTERNAL_ERROR';
|
|
147
134
|
function internalServerError(messageOrError) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
135
|
+
const serverError = partialServerError(messageOrError);
|
|
136
|
+
return new HttpsError('internal', serverError?.message || 'internal error', {
|
|
137
|
+
status: 500,
|
|
138
|
+
code: INTERNAL_SERVER_ERROR_CODE,
|
|
139
|
+
...serverError,
|
|
140
|
+
_error: undefined
|
|
141
|
+
});
|
|
155
142
|
}
|
|
156
143
|
function isFirebaseHttpsError(input) {
|
|
157
|
-
|
|
144
|
+
return typeof input === 'object' && input.code != null && input.httpErrorCode != null && input.toJSON != null;
|
|
158
145
|
}
|
|
159
146
|
function isFirebaseError(input) {
|
|
160
|
-
|
|
147
|
+
return typeof input === 'object' && input.code != null && input.message != null && input.toJSON != null;
|
|
161
148
|
}
|
|
162
149
|
/**
|
|
163
150
|
* Creates a FirebaseServerErrorInfo from the input.
|
|
@@ -166,73 +153,74 @@ function isFirebaseError(input) {
|
|
|
166
153
|
* @returns
|
|
167
154
|
*/
|
|
168
155
|
function firebaseServerErrorInfo(e) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
156
|
+
let type = 'unknown';
|
|
157
|
+
let httpsError;
|
|
158
|
+
let firebaseError;
|
|
159
|
+
let firebaseErrorCode;
|
|
160
|
+
let httpsErrorDetailsServerError;
|
|
161
|
+
let serverErrorCode;
|
|
162
|
+
if (e != null) {
|
|
163
|
+
if (isFirebaseHttpsError(e)) {
|
|
164
|
+
type = 'httpsError';
|
|
165
|
+
httpsError = e;
|
|
166
|
+
firebaseErrorCode = httpsError.code;
|
|
167
|
+
if (httpsError.details && isServerError(httpsError.details)) {
|
|
168
|
+
httpsErrorDetailsServerError = httpsError.details;
|
|
169
|
+
serverErrorCode = httpsErrorDetailsServerError.code;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (isFirebaseError(e)) {
|
|
173
|
+
type = 'firebaseError';
|
|
174
|
+
firebaseError = e;
|
|
175
|
+
firebaseErrorCode = firebaseError.code;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
httpsError,
|
|
180
|
+
firebaseError,
|
|
181
|
+
firebaseErrorCode,
|
|
182
|
+
httpsErrorDetailsServerError,
|
|
183
|
+
serverErrorCode,
|
|
184
|
+
type,
|
|
185
|
+
e
|
|
186
|
+
};
|
|
199
187
|
}
|
|
200
188
|
function firebaseServerErrorInfoCodePair(e) {
|
|
201
|
-
|
|
202
|
-
|
|
189
|
+
const info = firebaseServerErrorInfo(e);
|
|
190
|
+
return [info.firebaseErrorCode, info];
|
|
203
191
|
}
|
|
204
192
|
function firebaseServerErrorInfoServerErrorPair(e) {
|
|
205
|
-
|
|
206
|
-
|
|
193
|
+
const info = firebaseServerErrorInfo(e);
|
|
194
|
+
return [info.httpsErrorDetailsServerError, info];
|
|
207
195
|
}
|
|
208
196
|
function firebaseServerErrorInfoServerErrorCodePair(e) {
|
|
209
|
-
|
|
210
|
-
|
|
197
|
+
const info = firebaseServerErrorInfo(e);
|
|
198
|
+
return [info.serverErrorCode, info];
|
|
211
199
|
}
|
|
212
200
|
function handleFirebaseError(e, handleFirebaseErrorFn) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
201
|
+
const firebaseError = e.code ? e : undefined;
|
|
202
|
+
if (firebaseError) {
|
|
203
|
+
handleFirebaseErrorFn(firebaseError);
|
|
204
|
+
}
|
|
217
205
|
}
|
|
218
206
|
|
|
219
207
|
function isContextWithAuthData(context) {
|
|
220
|
-
|
|
208
|
+
return Boolean(context.auth !== null && context.auth?.uid);
|
|
221
209
|
}
|
|
222
210
|
function assertIsContextWithAuthData(context) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
211
|
+
if (!isContextWithAuthData(context)) {
|
|
212
|
+
throw unauthenticatedContextHasNoAuthData();
|
|
213
|
+
}
|
|
226
214
|
}
|
|
227
215
|
|
|
228
216
|
function firebaseAuthTokenFromDecodedIdToken(token) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
217
|
+
return {
|
|
218
|
+
email: token.email,
|
|
219
|
+
emailVerified: token.email_verified,
|
|
220
|
+
phoneNumber: token.phone_number,
|
|
221
|
+
lastSignInTime: new Date(token.auth_time).toISOString(),
|
|
222
|
+
lastRefreshTime: new Date(token.iat).toISOString()
|
|
223
|
+
};
|
|
236
224
|
}
|
|
237
225
|
|
|
238
226
|
/**
|
|
@@ -241,483 +229,458 @@ function firebaseAuthTokenFromDecodedIdToken(token) {
|
|
|
241
229
|
* @returns
|
|
242
230
|
*/
|
|
243
231
|
async function getAuthUserOrUndefined(promise) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
232
|
+
try {
|
|
233
|
+
return await promise;
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (error?.code === FIREBASE_AUTH_USER_NOT_FOUND_ERROR) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
251
242
|
}
|
|
252
|
-
}
|
|
253
243
|
}
|
|
254
244
|
|
|
255
245
|
/**
|
|
256
246
|
* Thrown by sendSetupDetails() if the user has no setup configuration available, meaning they probably already have accepted their invite or is in an invalid state.
|
|
257
247
|
*/
|
|
258
248
|
class FirebaseServerAuthNewUserSendSetupDetailsNoSetupConfigError extends BaseError {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
249
|
+
constructor() {
|
|
250
|
+
super(`This user has no setup configuration available.`);
|
|
251
|
+
}
|
|
262
252
|
}
|
|
263
253
|
/**
|
|
264
254
|
* Thrown by sendSetupDetails() if the user was recently sent details.
|
|
265
255
|
*/
|
|
266
256
|
class FirebaseServerAuthNewUserSendSetupDetailsThrottleError extends BaseError {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
lastSentAt;
|
|
258
|
+
constructor(lastSentAt) {
|
|
259
|
+
super(`This user was recently sent details. Try again later.`);
|
|
260
|
+
this.lastSentAt = lastSentAt;
|
|
261
|
+
}
|
|
272
262
|
}
|
|
273
263
|
/**
|
|
274
264
|
* Thrown by sendSetupDetails() if the user was recently sent details.
|
|
275
265
|
*/
|
|
276
266
|
class FirebaseServerAuthNewUserSendSetupDetailsSendOnceError extends BaseError {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
267
|
+
constructor() {
|
|
268
|
+
super(`The user has been sent details before and the sendSetupDetailsOnce configuration was true.`);
|
|
269
|
+
}
|
|
280
270
|
}
|
|
281
271
|
|
|
282
|
-
const DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR = randomNumberFactory({
|
|
283
|
-
min: 100000,
|
|
284
|
-
max: 1000000,
|
|
285
|
-
round: 'floor'
|
|
286
|
-
}); // 6 digits
|
|
272
|
+
const DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR = randomNumberFactory({ min: 100000, max: 1000000, round: 'floor' }); // 6 digits
|
|
287
273
|
class AbstractFirebaseServerAuthUserContext {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
});
|
|
417
|
-
}
|
|
274
|
+
_service;
|
|
275
|
+
_uid;
|
|
276
|
+
_loadRecord = cachedGetter(() => this._service.auth.getUser(this._uid));
|
|
277
|
+
constructor(service, uid) {
|
|
278
|
+
this._service = service;
|
|
279
|
+
this._uid = uid;
|
|
280
|
+
}
|
|
281
|
+
get service() {
|
|
282
|
+
return this._service;
|
|
283
|
+
}
|
|
284
|
+
get uid() {
|
|
285
|
+
return this._uid;
|
|
286
|
+
}
|
|
287
|
+
async exists() {
|
|
288
|
+
return getAuthUserOrUndefined(this._loadRecord()).then((x) => Boolean(x));
|
|
289
|
+
}
|
|
290
|
+
loadRecord() {
|
|
291
|
+
return this._loadRecord();
|
|
292
|
+
}
|
|
293
|
+
loadDetails() {
|
|
294
|
+
return this.loadRecord().then((record) => this.service.authDetailsForRecord(record));
|
|
295
|
+
}
|
|
296
|
+
_generateResetPasswordKey() {
|
|
297
|
+
return String(DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR());
|
|
298
|
+
}
|
|
299
|
+
async beginResetPassword() {
|
|
300
|
+
const password = this._generateResetPasswordKey();
|
|
301
|
+
const passwordClaimsData = {
|
|
302
|
+
[FIREBASE_SERVER_AUTH_CLAIMS_RESET_PASSWORD_KEY]: password,
|
|
303
|
+
[FIREBASE_SERVER_AUTH_CLAIMS_RESET_LAST_COM_DATE_KEY]: toISODateString(new Date())
|
|
304
|
+
};
|
|
305
|
+
// set the claims
|
|
306
|
+
await this.updateClaims(passwordClaimsData);
|
|
307
|
+
// update the user
|
|
308
|
+
await this.updateUser({ password });
|
|
309
|
+
return passwordClaimsData;
|
|
310
|
+
}
|
|
311
|
+
async loadResetPasswordClaims() {
|
|
312
|
+
const claims = await this.loadClaims();
|
|
313
|
+
if (claims.resetPassword != null) {
|
|
314
|
+
return claims;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Sets the user's password.
|
|
322
|
+
*/
|
|
323
|
+
async setPassword(password) {
|
|
324
|
+
const record = await this.updateUser({ password });
|
|
325
|
+
// clear password reset claims
|
|
326
|
+
await this.updateClaims({
|
|
327
|
+
[FIREBASE_SERVER_AUTH_CLAIMS_RESET_PASSWORD_KEY]: null,
|
|
328
|
+
[FIREBASE_SERVER_AUTH_CLAIMS_RESET_LAST_COM_DATE_KEY]: null
|
|
329
|
+
});
|
|
330
|
+
return record;
|
|
331
|
+
}
|
|
332
|
+
async updateUser(template) {
|
|
333
|
+
return this.service.auth.updateUser(this.uid, template);
|
|
334
|
+
}
|
|
335
|
+
async loadRoles() {
|
|
336
|
+
const claims = await this.loadClaims();
|
|
337
|
+
return this.service.readRoles(claims);
|
|
338
|
+
}
|
|
339
|
+
async addRoles(roles) {
|
|
340
|
+
const claims = this._claimsForRolesChange(roles);
|
|
341
|
+
return this.updateClaims(claims);
|
|
342
|
+
}
|
|
343
|
+
async removeRoles(roles) {
|
|
344
|
+
const baseClaims = this._claimsForRolesChange(roles);
|
|
345
|
+
const claims = {};
|
|
346
|
+
forEachKeyValue(baseClaims, {
|
|
347
|
+
forEach: ([key]) => {
|
|
348
|
+
claims[key] = null; // set null on every key
|
|
349
|
+
},
|
|
350
|
+
filter: KeyValueTypleValueFilter.NONE // don't skip any key/value
|
|
351
|
+
});
|
|
352
|
+
return this.updateClaims(claims);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Sets the claims using the input roles and roles set.
|
|
356
|
+
*
|
|
357
|
+
* All other claims are cleared.
|
|
358
|
+
*
|
|
359
|
+
* Use the claimsToRetain input to retain other claims that are outside of the roles.
|
|
360
|
+
*
|
|
361
|
+
* @param roles
|
|
362
|
+
* @param claimsToRetain
|
|
363
|
+
* @returns
|
|
364
|
+
*/
|
|
365
|
+
async setRoles(roles, claimsToRetain) {
|
|
366
|
+
const claims = {
|
|
367
|
+
...claimsToRetain,
|
|
368
|
+
...this._claimsForRolesChange(Array.from(roles))
|
|
369
|
+
};
|
|
370
|
+
return this.setClaims(claims);
|
|
371
|
+
}
|
|
372
|
+
_claimsForRolesChange(roles) {
|
|
373
|
+
// filter null/undefined since the claims will contain null values for claims that are not related.
|
|
374
|
+
return filterNullAndUndefinedValues(this.service.claimsForRoles(asSet(roles)));
|
|
375
|
+
}
|
|
376
|
+
loadClaims() {
|
|
377
|
+
return this.loadRecord().then((x) => (x.customClaims ?? {}));
|
|
378
|
+
}
|
|
379
|
+
async updateClaims(claims) {
|
|
380
|
+
const currentClaims = await this.loadClaims();
|
|
381
|
+
let newClaims;
|
|
382
|
+
if (currentClaims) {
|
|
383
|
+
newClaims = {
|
|
384
|
+
...currentClaims,
|
|
385
|
+
...filterUndefinedValues(claims, false)
|
|
386
|
+
};
|
|
387
|
+
newClaims = filterNullAndUndefinedValues(newClaims);
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
newClaims = claims;
|
|
391
|
+
}
|
|
392
|
+
return this.setClaims(newClaims);
|
|
393
|
+
}
|
|
394
|
+
clearClaims() {
|
|
395
|
+
return this.setClaims(null);
|
|
396
|
+
}
|
|
397
|
+
setClaims(claims) {
|
|
398
|
+
return this.service.auth.setCustomUserClaims(this.uid, claims).then(() => {
|
|
399
|
+
this._loadRecord.reset(); // reset the cache
|
|
400
|
+
});
|
|
401
|
+
}
|
|
418
402
|
}
|
|
419
403
|
class AbstractFirebaseServerAuthContext {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
404
|
+
_service;
|
|
405
|
+
_context;
|
|
406
|
+
_authRoles = cachedGetter(() => this.service.readRoles(this.claims));
|
|
407
|
+
_isAdmin = cachedGetter(() => this.service.isAdminInRoles(this._authRoles()));
|
|
408
|
+
_hasSignedTos = cachedGetter(() => this.service.hasSignedTosInRoles(this._authRoles()));
|
|
409
|
+
_userContext = cachedGetter(() => this.service.userContext(this.context.auth.uid));
|
|
410
|
+
constructor(service, context) {
|
|
411
|
+
this._service = service;
|
|
412
|
+
this._context = context;
|
|
413
|
+
}
|
|
414
|
+
get service() {
|
|
415
|
+
return this._service;
|
|
416
|
+
}
|
|
417
|
+
get context() {
|
|
418
|
+
return this._context;
|
|
419
|
+
}
|
|
420
|
+
get userContext() {
|
|
421
|
+
return this._userContext();
|
|
422
|
+
}
|
|
423
|
+
get isAdmin() {
|
|
424
|
+
return this._isAdmin();
|
|
425
|
+
}
|
|
426
|
+
get hasSignedTos() {
|
|
427
|
+
return this._hasSignedTos();
|
|
428
|
+
}
|
|
429
|
+
get authRoles() {
|
|
430
|
+
return this._authRoles();
|
|
431
|
+
}
|
|
432
|
+
get token() {
|
|
433
|
+
return this.context.auth.token;
|
|
434
|
+
}
|
|
435
|
+
get claims() {
|
|
436
|
+
return this.context.auth.token;
|
|
437
|
+
}
|
|
438
|
+
// MARK: FirebaseServerAuthUserContext
|
|
439
|
+
get uid() {
|
|
440
|
+
return this.userContext.uid;
|
|
441
|
+
}
|
|
458
442
|
}
|
|
459
443
|
/**
|
|
460
444
|
* 1 hour
|
|
461
445
|
*/
|
|
462
446
|
const DEFAULT_SETUP_COM_THROTTLE_TIME = hoursToMs(1);
|
|
463
447
|
function userContextFromUid(authService, userContextOrUid) {
|
|
464
|
-
|
|
465
|
-
|
|
448
|
+
const userContext = typeof userContextOrUid === 'string' ? authService.userContext(userContextOrUid) : userContextOrUid;
|
|
449
|
+
return userContext;
|
|
466
450
|
}
|
|
467
451
|
class AbstractFirebaseServerNewUserService {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const user = await this.authService.auth.createUser({
|
|
626
|
-
uid,
|
|
627
|
-
displayName,
|
|
628
|
-
email,
|
|
629
|
-
phoneNumber,
|
|
630
|
-
password
|
|
631
|
-
});
|
|
632
|
-
return {
|
|
633
|
-
user,
|
|
634
|
-
password
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
generateRandomSetupPassword() {
|
|
638
|
-
return `${DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR()}`;
|
|
639
|
-
}
|
|
640
|
-
async updateClaimsToClearUser(userContext) {
|
|
641
|
-
await userContext.updateClaims({
|
|
642
|
-
[FIREBASE_SERVER_AUTH_CLAIMS_SETUP_PASSWORD_KEY]: null,
|
|
643
|
-
[FIREBASE_SERVER_AUTH_CLAIMS_SETUP_LAST_COM_DATE_KEY]: null
|
|
644
|
-
});
|
|
645
|
-
}
|
|
452
|
+
_authService;
|
|
453
|
+
setupThrottleTime = DEFAULT_SETUP_COM_THROTTLE_TIME;
|
|
454
|
+
constructor(authService) {
|
|
455
|
+
this._authService = authService;
|
|
456
|
+
}
|
|
457
|
+
get authService() {
|
|
458
|
+
return this._authService;
|
|
459
|
+
}
|
|
460
|
+
async initializeNewUser(input) {
|
|
461
|
+
const { uid, email, phone, sendSetupContent, sendSetupContentIfUserExists, sendSetupDetailsOnce, sendSetupIgnoreThrottle, sendSetupThrowErrors, data, sendDetailsInTestEnvironment } = input;
|
|
462
|
+
let userRecordPromise;
|
|
463
|
+
if (uid) {
|
|
464
|
+
userRecordPromise = this.authService.auth.getUser(uid);
|
|
465
|
+
}
|
|
466
|
+
else if (email) {
|
|
467
|
+
userRecordPromise = this.authService.auth.getUserByEmail(email);
|
|
468
|
+
}
|
|
469
|
+
else if (phone) {
|
|
470
|
+
userRecordPromise = this.authService.auth.getUserByPhoneNumber(phone);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
throw new Error('email or phone is required to initialize a new user.');
|
|
474
|
+
}
|
|
475
|
+
let userRecord = await getAuthUserOrUndefined(userRecordPromise);
|
|
476
|
+
let userRecordId;
|
|
477
|
+
let createdUser = false;
|
|
478
|
+
if (!userRecord) {
|
|
479
|
+
const createResult = await this.createNewUser(input);
|
|
480
|
+
// add the setup password to the user's credentials
|
|
481
|
+
const userContext = this.authService.userContext(createResult.user.uid);
|
|
482
|
+
await this.addNewUserSetupClaims(userContext, createResult.password);
|
|
483
|
+
createdUser = true;
|
|
484
|
+
userRecordId = userContext.uid;
|
|
485
|
+
userRecord = await userContext.loadRecord();
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
userRecordId = userRecord.uid;
|
|
489
|
+
}
|
|
490
|
+
// send content if necessary
|
|
491
|
+
if ((createdUser && sendSetupContent === true) || sendSetupContentIfUserExists) {
|
|
492
|
+
const sentEmail = await this.sendSetupContent(userRecordId, { data, sendSetupDetailsOnce, ignoreSendThrottleTime: sendSetupIgnoreThrottle, throwErrors: sendSetupThrowErrors, sendDetailsInTestEnvironment });
|
|
493
|
+
// reload the user record
|
|
494
|
+
if (sentEmail) {
|
|
495
|
+
const userContext = this.authService.userContext(userRecordId);
|
|
496
|
+
userRecord = await userContext.loadRecord();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return userRecord;
|
|
500
|
+
}
|
|
501
|
+
async addNewUserSetupClaims(userContextOrUid, setupPassword) {
|
|
502
|
+
const password = setupPassword ?? this.generateRandomSetupPassword();
|
|
503
|
+
const userContext = userContextFromUid(this.authService, userContextOrUid);
|
|
504
|
+
await userContext.updateClaims({
|
|
505
|
+
[FIREBASE_SERVER_AUTH_CLAIMS_SETUP_PASSWORD_KEY]: password
|
|
506
|
+
});
|
|
507
|
+
return userContext;
|
|
508
|
+
}
|
|
509
|
+
async sendSetupContent(userContextOrUid, config) {
|
|
510
|
+
const setupDetails = await this.loadSetupDetails(userContextOrUid, config);
|
|
511
|
+
let sentContent = false;
|
|
512
|
+
if (setupDetails) {
|
|
513
|
+
const { setupCommunicationAt } = setupDetails.claims;
|
|
514
|
+
const hasSentCommunication = Boolean(setupCommunicationAt);
|
|
515
|
+
if (config?.sendSetupDetailsOnce && hasSentCommunication) {
|
|
516
|
+
// do not send.
|
|
517
|
+
if (config?.throwErrors) {
|
|
518
|
+
throw new FirebaseServerAuthNewUserSendSetupDetailsSendOnceError();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
const lastSentAt = setupCommunicationAt ? new Date(setupCommunicationAt) : undefined;
|
|
523
|
+
const sendIsThrottled = hasSentCommunication && !config?.ignoreSendThrottleTime && isThrottled(this.setupThrottleTime, lastSentAt);
|
|
524
|
+
if (!sendIsThrottled) {
|
|
525
|
+
await this.sendSetupContentToUser(setupDetails);
|
|
526
|
+
await this.updateSetupContentSentTime(setupDetails);
|
|
527
|
+
sentContent = true;
|
|
528
|
+
}
|
|
529
|
+
else if (config?.throwErrors) {
|
|
530
|
+
throw new FirebaseServerAuthNewUserSendSetupDetailsThrottleError(lastSentAt);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else if (config?.throwErrors) {
|
|
535
|
+
throw new FirebaseServerAuthNewUserSendSetupDetailsNoSetupConfigError();
|
|
536
|
+
}
|
|
537
|
+
return sentContent;
|
|
538
|
+
}
|
|
539
|
+
async loadSetupDetails(userContextOrUid, config) {
|
|
540
|
+
const userContext = userContextFromUid(this.authService, userContextOrUid);
|
|
541
|
+
const userExists = await userContext.exists();
|
|
542
|
+
let details;
|
|
543
|
+
if (userExists) {
|
|
544
|
+
details = await this.loadSetupDetailsForUserContext(userContext, config);
|
|
545
|
+
}
|
|
546
|
+
return details;
|
|
547
|
+
}
|
|
548
|
+
async loadSetupDetailsForUserContext(userContext, config) {
|
|
549
|
+
let details;
|
|
550
|
+
const { setupPassword, setupCommunicationAt } = await userContext.loadClaims();
|
|
551
|
+
if (setupPassword) {
|
|
552
|
+
details = {
|
|
553
|
+
userContext,
|
|
554
|
+
claims: {
|
|
555
|
+
setupPassword,
|
|
556
|
+
setupCommunicationAt
|
|
557
|
+
},
|
|
558
|
+
data: config?.data,
|
|
559
|
+
sendDetailsInTestEnvironment: config?.sendDetailsInTestEnvironment
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
return details;
|
|
563
|
+
}
|
|
564
|
+
async updateSetupContentSentTime(details) {
|
|
565
|
+
const setupCommunicationAt = toISODateString(new Date());
|
|
566
|
+
await details.userContext.updateClaims({
|
|
567
|
+
setupCommunicationAt
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Update a user's claims to clear any setup-related content.
|
|
572
|
+
*
|
|
573
|
+
* Returns true if a user was updated.
|
|
574
|
+
*
|
|
575
|
+
* @param uid
|
|
576
|
+
*/
|
|
577
|
+
async markUserSetupAsComplete(uid) {
|
|
578
|
+
const userContext = this.authService.userContext(uid);
|
|
579
|
+
const userExists = await userContext.exists();
|
|
580
|
+
if (userExists) {
|
|
581
|
+
await this.updateClaimsToClearUser(userContext);
|
|
582
|
+
}
|
|
583
|
+
return userExists;
|
|
584
|
+
}
|
|
585
|
+
async createNewUser(input) {
|
|
586
|
+
const { uid, displayName, email, phone: phoneNumber, setupPassword: inputPassword } = input;
|
|
587
|
+
const password = inputPassword ?? this.generateRandomSetupPassword();
|
|
588
|
+
const user = await this.authService.auth.createUser({
|
|
589
|
+
uid,
|
|
590
|
+
displayName,
|
|
591
|
+
email,
|
|
592
|
+
phoneNumber,
|
|
593
|
+
password
|
|
594
|
+
});
|
|
595
|
+
return {
|
|
596
|
+
user,
|
|
597
|
+
password
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
generateRandomSetupPassword() {
|
|
601
|
+
return `${DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR()}`;
|
|
602
|
+
}
|
|
603
|
+
async updateClaimsToClearUser(userContext) {
|
|
604
|
+
await userContext.updateClaims({
|
|
605
|
+
[FIREBASE_SERVER_AUTH_CLAIMS_SETUP_PASSWORD_KEY]: null,
|
|
606
|
+
[FIREBASE_SERVER_AUTH_CLAIMS_SETUP_LAST_COM_DATE_KEY]: null
|
|
607
|
+
});
|
|
608
|
+
}
|
|
646
609
|
}
|
|
647
610
|
class NoSetupContentFirebaseServerNewUserService extends AbstractFirebaseServerNewUserService {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
611
|
+
async sendSetupContentToUser(user) {
|
|
612
|
+
// send nothing.
|
|
613
|
+
}
|
|
651
614
|
}
|
|
652
615
|
/**
|
|
653
616
|
* FirebaseServer auth service that provides accessors to auth-related components.
|
|
654
617
|
*/
|
|
655
|
-
class FirebaseServerAuthService {
|
|
618
|
+
class FirebaseServerAuthService {
|
|
619
|
+
}
|
|
656
620
|
/**
|
|
657
621
|
* Abstract FirebaseServerAuthService implementation.
|
|
658
622
|
*/
|
|
659
623
|
class AbstractFirebaseServerAuthService {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
};
|
|
717
|
-
}
|
|
624
|
+
_auth;
|
|
625
|
+
constructor(auth) {
|
|
626
|
+
this._auth = auth;
|
|
627
|
+
}
|
|
628
|
+
get auth() {
|
|
629
|
+
return this._auth;
|
|
630
|
+
}
|
|
631
|
+
context(context) {
|
|
632
|
+
assertIsContextWithAuthData(context);
|
|
633
|
+
return this._context(context);
|
|
634
|
+
}
|
|
635
|
+
isAdmin(claims) {
|
|
636
|
+
return this.isAdminInRoles(this.readRoles(claims));
|
|
637
|
+
}
|
|
638
|
+
isAdminInRoles(roles) {
|
|
639
|
+
return roles.has(AUTH_ADMIN_ROLE);
|
|
640
|
+
}
|
|
641
|
+
hasSignedTos(claims) {
|
|
642
|
+
return this.hasSignedTosInRoles(this.readRoles(claims));
|
|
643
|
+
}
|
|
644
|
+
hasSignedTosInRoles(roles) {
|
|
645
|
+
return roles.has(AUTH_TOS_SIGNED_ROLE);
|
|
646
|
+
}
|
|
647
|
+
newUser() {
|
|
648
|
+
return new NoSetupContentFirebaseServerNewUserService(this);
|
|
649
|
+
}
|
|
650
|
+
authContextInfo(context) {
|
|
651
|
+
const { auth } = context;
|
|
652
|
+
let result;
|
|
653
|
+
if (auth) {
|
|
654
|
+
const _roles = cachedGetter(() => this.readRoles(auth.token));
|
|
655
|
+
const getClaims = () => auth.token;
|
|
656
|
+
result = {
|
|
657
|
+
uid: auth.uid,
|
|
658
|
+
isAdmin: () => this.isAdminInRoles(_roles()),
|
|
659
|
+
getClaims,
|
|
660
|
+
getAuthRoles: _roles,
|
|
661
|
+
token: firebaseAuthTokenFromDecodedIdToken(auth.token)
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
return result;
|
|
665
|
+
}
|
|
666
|
+
authDetailsForRecord(record) {
|
|
667
|
+
return {
|
|
668
|
+
uid: record.uid,
|
|
669
|
+
email: record.email,
|
|
670
|
+
emailVerified: record.emailVerified,
|
|
671
|
+
phoneNumber: record.phoneNumber,
|
|
672
|
+
disabled: record.disabled,
|
|
673
|
+
displayName: record.displayName,
|
|
674
|
+
photoURL: record.photoURL,
|
|
675
|
+
creationTime: record.metadata.creationTime ? new Date(record.metadata.creationTime).toISOString() : undefined,
|
|
676
|
+
lastSignInTime: record.metadata.lastSignInTime ? new Date(record.metadata.lastSignInTime).toISOString() : undefined,
|
|
677
|
+
lastRefreshTime: record.metadata.lastRefreshTime ? new Date(record.metadata.lastRefreshTime).toISOString() : undefined
|
|
678
|
+
};
|
|
679
|
+
}
|
|
718
680
|
}
|
|
719
681
|
|
|
720
|
-
class FirebaseServerEnvService {
|
|
682
|
+
class FirebaseServerEnvService {
|
|
683
|
+
}
|
|
721
684
|
|
|
722
685
|
/**
|
|
723
686
|
* Creates UpdateData corresponding to the input increment update.
|
|
@@ -726,9 +689,9 @@ class FirebaseServerEnvService {}
|
|
|
726
689
|
* @returns
|
|
727
690
|
*/
|
|
728
691
|
function firestoreServerIncrementUpdateToUpdateData(input) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
692
|
+
return mapObjectMap(input, (incrementValue) => {
|
|
693
|
+
return FieldValue.increment(incrementValue ?? 0);
|
|
694
|
+
});
|
|
732
695
|
}
|
|
733
696
|
|
|
734
697
|
/**
|
|
@@ -738,26 +701,26 @@ function firestoreServerIncrementUpdateToUpdateData(input) {
|
|
|
738
701
|
* @returns
|
|
739
702
|
*/
|
|
740
703
|
function firestoreServerArrayUpdateToUpdateData(input) {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
let result;
|
|
745
|
-
if (fieldUpdate) {
|
|
746
|
-
result = mapObjectMap(fieldUpdate, arrayUpdate => {
|
|
704
|
+
const union = input?.union;
|
|
705
|
+
const remove = input?.remove;
|
|
706
|
+
function createUpdatesWithArrayFunction(fieldUpdate, arrayUpdateFunction) {
|
|
747
707
|
let result;
|
|
748
|
-
if (
|
|
749
|
-
|
|
708
|
+
if (fieldUpdate) {
|
|
709
|
+
result = mapObjectMap(fieldUpdate, (arrayUpdate) => {
|
|
710
|
+
let result;
|
|
711
|
+
if (arrayUpdate) {
|
|
712
|
+
result = arrayUpdateFunction(...arrayUpdate); // use spread operator to insert each value as an argument, as "nested arrays" are not allowed in the Firestore
|
|
713
|
+
}
|
|
714
|
+
return result;
|
|
715
|
+
});
|
|
750
716
|
}
|
|
751
717
|
return result;
|
|
752
|
-
});
|
|
753
718
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
};
|
|
760
|
-
return updateData;
|
|
719
|
+
const updateData = {
|
|
720
|
+
...createUpdatesWithArrayFunction(union, FieldValue.arrayUnion),
|
|
721
|
+
...createUpdatesWithArrayFunction(remove, FieldValue.arrayRemove)
|
|
722
|
+
};
|
|
723
|
+
return updateData;
|
|
761
724
|
}
|
|
762
725
|
|
|
763
726
|
// MARK: Accessor
|
|
@@ -765,53 +728,54 @@ function firestoreServerArrayUpdateToUpdateData(input) {
|
|
|
765
728
|
* FirestoreDocumentDataAccessor implementation for a batch.
|
|
766
729
|
*/
|
|
767
730
|
class WriteBatchFirestoreDocumentDataAccessor {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
731
|
+
documentRef;
|
|
732
|
+
_batch;
|
|
733
|
+
constructor(batch, documentRef) {
|
|
734
|
+
this.documentRef = documentRef;
|
|
735
|
+
this._batch = batch;
|
|
736
|
+
}
|
|
737
|
+
get batch() {
|
|
738
|
+
return this._batch;
|
|
739
|
+
}
|
|
740
|
+
stream() {
|
|
741
|
+
return from(this.get()); // todo
|
|
742
|
+
}
|
|
743
|
+
create(data) {
|
|
744
|
+
this.batch.create(this.documentRef, data);
|
|
745
|
+
return Promise.resolve();
|
|
746
|
+
}
|
|
747
|
+
exists() {
|
|
748
|
+
return this.get().then((x) => x.exists);
|
|
749
|
+
}
|
|
750
|
+
get() {
|
|
751
|
+
return this.documentRef.get();
|
|
752
|
+
}
|
|
753
|
+
getWithConverter(converter) {
|
|
754
|
+
return this.documentRef.withConverter(converter).get();
|
|
755
|
+
}
|
|
756
|
+
delete(params) {
|
|
757
|
+
this.batch.delete(this.documentRef, params?.precondition);
|
|
758
|
+
return Promise.resolve();
|
|
759
|
+
}
|
|
760
|
+
set(data) {
|
|
761
|
+
this.batch.set(this.documentRef, data);
|
|
762
|
+
return Promise.resolve();
|
|
763
|
+
}
|
|
764
|
+
increment(data, params) {
|
|
765
|
+
return this.update(firestoreServerIncrementUpdateToUpdateData(data), params);
|
|
766
|
+
}
|
|
767
|
+
arrayUpdate(data, params) {
|
|
768
|
+
return this.update(firestoreServerArrayUpdateToUpdateData(data), params);
|
|
769
|
+
}
|
|
770
|
+
update(data, params) {
|
|
771
|
+
if (params?.precondition != null) {
|
|
772
|
+
this.batch.update(this.documentRef, data, params?.precondition);
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
this.batch.update(this.documentRef, data);
|
|
776
|
+
}
|
|
777
|
+
return Promise.resolve();
|
|
778
|
+
}
|
|
815
779
|
}
|
|
816
780
|
/**
|
|
817
781
|
* Creates a new FirestoreDocumentDataAccessorFactory for a Batch.
|
|
@@ -820,81 +784,78 @@ class WriteBatchFirestoreDocumentDataAccessor {
|
|
|
820
784
|
* @returns
|
|
821
785
|
*/
|
|
822
786
|
function writeBatchAccessorFactory(writeBatch) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
787
|
+
return {
|
|
788
|
+
accessorFor: (ref) => new WriteBatchFirestoreDocumentDataAccessor(writeBatch, ref)
|
|
789
|
+
};
|
|
826
790
|
}
|
|
827
791
|
// MARK: Context
|
|
828
792
|
class WriteBatchFirestoreDocumentContext {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
793
|
+
_batch;
|
|
794
|
+
contextType = FirestoreDocumentContextType.BATCH;
|
|
795
|
+
accessorFactory;
|
|
796
|
+
constructor(batch) {
|
|
797
|
+
this._batch = batch;
|
|
798
|
+
this.accessorFactory = writeBatchAccessorFactory(batch);
|
|
799
|
+
}
|
|
800
|
+
get batch() {
|
|
801
|
+
return this._batch;
|
|
802
|
+
}
|
|
839
803
|
}
|
|
840
804
|
function writeBatchDocumentContext(batch) {
|
|
841
|
-
|
|
805
|
+
return new WriteBatchFirestoreDocumentContext(batch);
|
|
842
806
|
}
|
|
843
807
|
|
|
844
808
|
// MARK: Accessor
|
|
845
809
|
class DefaultFirestoreDocumentDataAccessor {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
update(data, params) {
|
|
884
|
-
return params?.precondition ? this.documentRef.update(data, params.precondition) : this.documentRef.update(data);
|
|
885
|
-
}
|
|
810
|
+
_documentRef;
|
|
811
|
+
constructor(documentRef) {
|
|
812
|
+
this._documentRef = documentRef;
|
|
813
|
+
}
|
|
814
|
+
get documentRef() {
|
|
815
|
+
return this._documentRef;
|
|
816
|
+
}
|
|
817
|
+
stream() {
|
|
818
|
+
return streamFromOnSnapshot(({ next, error }) => this.documentRef.onSnapshot(next, error));
|
|
819
|
+
}
|
|
820
|
+
create(data) {
|
|
821
|
+
return this.documentRef.create(data);
|
|
822
|
+
}
|
|
823
|
+
exists() {
|
|
824
|
+
return this.get().then((x) => x.exists);
|
|
825
|
+
}
|
|
826
|
+
get() {
|
|
827
|
+
return this.documentRef.get();
|
|
828
|
+
}
|
|
829
|
+
getWithConverter(converter) {
|
|
830
|
+
return this.documentRef.withConverter(converter).get();
|
|
831
|
+
}
|
|
832
|
+
delete(params) {
|
|
833
|
+
return this.documentRef.delete(params?.precondition);
|
|
834
|
+
}
|
|
835
|
+
set(data, options) {
|
|
836
|
+
return options ? this.documentRef.set(data, options) : this.documentRef.set(data);
|
|
837
|
+
}
|
|
838
|
+
increment(data, params) {
|
|
839
|
+
return this.update(firestoreServerIncrementUpdateToUpdateData(data), params);
|
|
840
|
+
}
|
|
841
|
+
arrayUpdate(data, params) {
|
|
842
|
+
return this.update(firestoreServerArrayUpdateToUpdateData(data), params);
|
|
843
|
+
}
|
|
844
|
+
update(data, params) {
|
|
845
|
+
return params?.precondition ? this.documentRef.update(data, params.precondition) : this.documentRef.update(data);
|
|
846
|
+
}
|
|
886
847
|
}
|
|
887
848
|
function defaultFirestoreAccessorFactory() {
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
849
|
+
return {
|
|
850
|
+
accessorFor: (ref) => new DefaultFirestoreDocumentDataAccessor(ref)
|
|
851
|
+
};
|
|
891
852
|
}
|
|
892
853
|
// MARK: Context
|
|
893
854
|
function defaultFirestoreDocumentContext() {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
855
|
+
return {
|
|
856
|
+
contextType: FirestoreDocumentContextType.NONE,
|
|
857
|
+
accessorFactory: defaultFirestoreAccessorFactory()
|
|
858
|
+
};
|
|
898
859
|
}
|
|
899
860
|
|
|
900
861
|
// MARK: Accessor
|
|
@@ -902,56 +863,57 @@ function defaultFirestoreDocumentContext() {
|
|
|
902
863
|
* FirestoreDocumentDataAccessor implementation for a transaction.
|
|
903
864
|
*/
|
|
904
865
|
class TransactionFirestoreDocumentDataAccessor {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
866
|
+
_transaction;
|
|
867
|
+
_documentRef;
|
|
868
|
+
constructor(transaction, documentRef) {
|
|
869
|
+
this._transaction = transaction;
|
|
870
|
+
this._documentRef = documentRef;
|
|
871
|
+
}
|
|
872
|
+
get transaction() {
|
|
873
|
+
return this._transaction;
|
|
874
|
+
}
|
|
875
|
+
get documentRef() {
|
|
876
|
+
return this._documentRef;
|
|
877
|
+
}
|
|
878
|
+
stream() {
|
|
879
|
+
return from(this.get());
|
|
880
|
+
}
|
|
881
|
+
create(data) {
|
|
882
|
+
this.transaction.create(this.documentRef, data);
|
|
883
|
+
return Promise.resolve();
|
|
884
|
+
}
|
|
885
|
+
exists() {
|
|
886
|
+
return this.get().then((x) => x.exists);
|
|
887
|
+
}
|
|
888
|
+
get() {
|
|
889
|
+
return this.transaction.get(this.documentRef);
|
|
890
|
+
}
|
|
891
|
+
getWithConverter(converter) {
|
|
892
|
+
return this.transaction.get(this.documentRef.withConverter(converter));
|
|
893
|
+
}
|
|
894
|
+
delete() {
|
|
895
|
+
this.transaction.delete(this.documentRef);
|
|
896
|
+
return Promise.resolve();
|
|
897
|
+
}
|
|
898
|
+
set(data, options) {
|
|
899
|
+
this.transaction.set(this.documentRef, data, options);
|
|
900
|
+
return Promise.resolve();
|
|
901
|
+
}
|
|
902
|
+
increment(data, params) {
|
|
903
|
+
return this.update(firestoreServerIncrementUpdateToUpdateData(data), params);
|
|
904
|
+
}
|
|
905
|
+
arrayUpdate(data, params) {
|
|
906
|
+
return this.update(firestoreServerArrayUpdateToUpdateData(data), params);
|
|
907
|
+
}
|
|
908
|
+
update(data, params) {
|
|
909
|
+
if (params?.precondition) {
|
|
910
|
+
this.transaction.update(this.documentRef, data, params?.precondition);
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
913
|
+
this.transaction.update(this.documentRef, data);
|
|
914
|
+
}
|
|
915
|
+
return Promise.resolve();
|
|
916
|
+
}
|
|
955
917
|
}
|
|
956
918
|
/**
|
|
957
919
|
* Creates a new FirestoreDocumentDataAccessorFactory for a Transaction.
|
|
@@ -960,122 +922,123 @@ class TransactionFirestoreDocumentDataAccessor {
|
|
|
960
922
|
* @returns
|
|
961
923
|
*/
|
|
962
924
|
function transactionAccessorFactory(transaction) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
925
|
+
return {
|
|
926
|
+
accessorFor: (ref) => new TransactionFirestoreDocumentDataAccessor(transaction, ref)
|
|
927
|
+
};
|
|
966
928
|
}
|
|
967
929
|
// MARK: Context
|
|
968
930
|
class TransactionFirestoreDocumentContext {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
931
|
+
_transaction;
|
|
932
|
+
contextType = FirestoreDocumentContextType.TRANSACTION;
|
|
933
|
+
accessorFactory;
|
|
934
|
+
constructor(transaction) {
|
|
935
|
+
this._transaction = transaction;
|
|
936
|
+
this.accessorFactory = transactionAccessorFactory(transaction);
|
|
937
|
+
}
|
|
938
|
+
get transaction() {
|
|
939
|
+
return this._transaction;
|
|
940
|
+
}
|
|
979
941
|
}
|
|
980
942
|
function transactionDocumentContext(transaction) {
|
|
981
|
-
|
|
943
|
+
return new TransactionFirestoreDocumentContext(transaction);
|
|
982
944
|
}
|
|
983
945
|
|
|
984
946
|
function collectionRefForPath(start, path, pathSegments) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
947
|
+
let ref = start.collection(path);
|
|
948
|
+
if (pathSegments?.length) {
|
|
949
|
+
if (pathSegments?.length % 2 !== 0) {
|
|
950
|
+
throw new Error(`Invalid number of path segments provided for collection. Path: "${path}" + "${pathSegments}"`);
|
|
951
|
+
}
|
|
952
|
+
const batches = batch(pathSegments, 2); // batch to tuple [string, string]
|
|
953
|
+
batches.forEach((x) => {
|
|
954
|
+
const [first, second] = x;
|
|
955
|
+
ref = ref.doc(first).collection(second);
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
return ref;
|
|
997
959
|
}
|
|
998
960
|
function docRefForPath(start, path, pathSegments) {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
961
|
+
let doc = (path ? start.doc(path) : start.doc());
|
|
962
|
+
if (pathSegments?.length) {
|
|
963
|
+
const batches = batch(pathSegments, 2); // batch to tuple [string, string]
|
|
964
|
+
batches.forEach((x) => {
|
|
965
|
+
const [first, second] = x;
|
|
966
|
+
const collection = doc.collection(first);
|
|
967
|
+
doc = second ? collection.doc(second) : collection.doc();
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
return doc;
|
|
1009
971
|
}
|
|
1010
972
|
function googleCloudFirestoreAccessorDriver() {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
973
|
+
return {
|
|
974
|
+
doc: (collection, path, ...pathSegments) => docRefForPath(collection, path, pathSegments),
|
|
975
|
+
docAtPath: (firestore, fullPath) => firestore.doc(fullPath),
|
|
976
|
+
collectionGroup: (firestore, collectionId) => firestore.collectionGroup(collectionId),
|
|
977
|
+
collection: (firestore, path, ...pathSegments) => collectionRefForPath(firestore, path, pathSegments),
|
|
978
|
+
subcollection: (document, path, ...pathSegments) => collectionRefForPath(document, path, pathSegments),
|
|
979
|
+
transactionFactoryForFirestore: (firestore) => async (fn) => await firestore.runTransaction(fn),
|
|
980
|
+
writeBatchFactoryForFirestore: (firestore) => () => firestore.batch(),
|
|
981
|
+
defaultContextFactory: defaultFirestoreDocumentContext,
|
|
982
|
+
transactionContextFactory: transactionDocumentContext,
|
|
983
|
+
writeBatchContextFactory: writeBatchDocumentContext
|
|
984
|
+
};
|
|
1023
985
|
}
|
|
1024
986
|
|
|
1025
987
|
const FIRESTORE_CLIENT_QUERY_CONSTRAINT_HANDLER_MAPPING = {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
988
|
+
[FIRESTORE_LIMIT_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.limit(data.limit),
|
|
989
|
+
[FIRESTORE_LIMIT_TO_LAST_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.limitToLast(data.limit),
|
|
990
|
+
[FIRESTORE_ORDER_BY_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.orderBy(data.fieldPath, data.directionStr),
|
|
991
|
+
[FIRESTORE_ORDER_BY_DOCUMENT_ID_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.orderBy(FieldPath.documentId(), data.directionStr),
|
|
992
|
+
[FIRESTORE_WHERE_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.where(data.fieldPath, data.opStr, data.value),
|
|
993
|
+
[FIRESTORE_WHERE_DOCUMENT_ID_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.where(FieldPath.documentId(), data.opStr, data.value),
|
|
994
|
+
[FIRESTORE_OFFSET_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.offset(data.offset),
|
|
995
|
+
[FIRESTORE_START_AT_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.startAt(data.snapshot),
|
|
996
|
+
[FIRESTORE_START_AT_VALUE_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.startAt(...data.fieldValues),
|
|
997
|
+
[FIRESTORE_START_AFTER_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.startAfter(data.snapshot),
|
|
998
|
+
[FIRESTORE_END_AT_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.endAt(data.snapshot),
|
|
999
|
+
[FIRESTORE_END_AT_VALUE_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.endAt(...data.fieldValues),
|
|
1000
|
+
[FIRESTORE_END_BEFORE_QUERY_CONSTRAINT_TYPE]: (builder, data) => builder.endBefore(data.snapshot)
|
|
1039
1001
|
};
|
|
1040
1002
|
function firestoreClientQueryConstraintFunctionsDriver() {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1003
|
+
return makeFirestoreQueryConstraintFunctionsDriver({
|
|
1004
|
+
mapping: FIRESTORE_CLIENT_QUERY_CONSTRAINT_HANDLER_MAPPING,
|
|
1005
|
+
init: (query) => query,
|
|
1006
|
+
build: (query) => query,
|
|
1007
|
+
documentIdFieldPath: () => FieldPath.documentId()
|
|
1008
|
+
});
|
|
1047
1009
|
}
|
|
1048
1010
|
function googleCloudFirestoreQueryDriver() {
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1011
|
+
return {
|
|
1012
|
+
...firestoreClientQueryConstraintFunctionsDriver(),
|
|
1013
|
+
countDocs(query) {
|
|
1014
|
+
return query
|
|
1015
|
+
.count()
|
|
1016
|
+
.get()
|
|
1017
|
+
.then((x) => x.data().count);
|
|
1018
|
+
},
|
|
1019
|
+
getDocs(query, transaction) {
|
|
1020
|
+
let result;
|
|
1021
|
+
if (transaction) {
|
|
1022
|
+
result = transaction.get(query);
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
result = query.get();
|
|
1026
|
+
}
|
|
1027
|
+
return result;
|
|
1028
|
+
},
|
|
1029
|
+
streamDocs(query) {
|
|
1030
|
+
return streamFromOnSnapshot(({ next, error }) => query.onSnapshot(next, error));
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1070
1033
|
}
|
|
1071
1034
|
|
|
1072
1035
|
function googleCloudFirestoreDrivers() {
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1036
|
+
return {
|
|
1037
|
+
firestoreDriverIdentifier: '@google-cloud/firestore',
|
|
1038
|
+
firestoreDriverType: 'production',
|
|
1039
|
+
firestoreAccessorDriver: googleCloudFirestoreAccessorDriver(),
|
|
1040
|
+
firestoreQueryDriver: googleCloudFirestoreQueryDriver()
|
|
1041
|
+
};
|
|
1079
1042
|
}
|
|
1080
1043
|
|
|
1081
1044
|
/**
|
|
@@ -1084,9 +1047,9 @@ function googleCloudFirestoreDrivers() {
|
|
|
1084
1047
|
const googleCloudFirestoreContextFactory = firestoreContextFactory(googleCloudFirestoreDrivers());
|
|
1085
1048
|
|
|
1086
1049
|
function assertContextHasAuth(context) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1050
|
+
if (!isContextWithAuthData(context)) {
|
|
1051
|
+
throw unauthenticatedContextHasNoUidError();
|
|
1052
|
+
}
|
|
1090
1053
|
}
|
|
1091
1054
|
/**
|
|
1092
1055
|
* Attempts to load data from the document. A modelNotAvailableError is thrown if the snapshot data is null/undefined (the document does not exist).
|
|
@@ -1096,13 +1059,13 @@ function assertContextHasAuth(context) {
|
|
|
1096
1059
|
* @returns
|
|
1097
1060
|
*/
|
|
1098
1061
|
async function assertSnapshotData(document, message) {
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1062
|
+
const data = await document.snapshotData();
|
|
1063
|
+
if (data == null) {
|
|
1064
|
+
throw modelNotAvailableError({
|
|
1065
|
+
message: message ?? `The ${document.modelType} was unavailable.`
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
return data;
|
|
1106
1069
|
}
|
|
1107
1070
|
/**
|
|
1108
1071
|
* Convenience function for assertSnapshotData that also attaches the id and key of the document to the data.
|
|
@@ -1112,8 +1075,8 @@ async function assertSnapshotData(document, message) {
|
|
|
1112
1075
|
* @returns
|
|
1113
1076
|
*/
|
|
1114
1077
|
async function assertSnapshotDataWithKey(document, message) {
|
|
1115
|
-
|
|
1116
|
-
|
|
1078
|
+
const data = await assertSnapshotData(document, message);
|
|
1079
|
+
return setIdAndKeyFromKeyIdRefOnDocumentData(data, document);
|
|
1117
1080
|
}
|
|
1118
1081
|
/**
|
|
1119
1082
|
* Asserts that the document exists. A modelNotAvailableError is thrown if the document does not exist.
|
|
@@ -1123,10 +1086,10 @@ async function assertSnapshotDataWithKey(document, message) {
|
|
|
1123
1086
|
* @returns
|
|
1124
1087
|
*/
|
|
1125
1088
|
async function assertDocumentExists(document, message) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1089
|
+
const exists = await document.exists();
|
|
1090
|
+
if (!exists) {
|
|
1091
|
+
throw documentModelNotAvailableError(document, message);
|
|
1092
|
+
}
|
|
1130
1093
|
}
|
|
1131
1094
|
/**
|
|
1132
1095
|
* Error thrown by assertDocumentExists().
|
|
@@ -1136,28 +1099,28 @@ async function assertDocumentExists(document, message) {
|
|
|
1136
1099
|
* @returns
|
|
1137
1100
|
*/
|
|
1138
1101
|
function documentModelNotAvailableError(document, message) {
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1102
|
+
return modelNotAvailableError({
|
|
1103
|
+
message: message ?? `The ${document.modelType} was unavailable.`
|
|
1104
|
+
});
|
|
1142
1105
|
}
|
|
1143
1106
|
|
|
1144
1107
|
const PHONE_NUMBER_ALREADY_EXISTS_ERROR_CODE = 'PHONE_NUMBER_ALREADY_EXISTS';
|
|
1145
1108
|
function phoneNumberAlreadyExistsError() {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1109
|
+
return preconditionConflictError({
|
|
1110
|
+
code: PHONE_NUMBER_ALREADY_EXISTS_ERROR_CODE,
|
|
1111
|
+
message: 'This phone number already exists in the system.'
|
|
1112
|
+
});
|
|
1150
1113
|
}
|
|
1151
1114
|
function handleFirebaseAuthError(e, handleUnknownCode) {
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1115
|
+
handleFirebaseError(e, (firebaseError) => {
|
|
1116
|
+
switch (firebaseError.code) {
|
|
1117
|
+
case 'auth/phone-number-already-exists':
|
|
1118
|
+
throw phoneNumberAlreadyExistsError();
|
|
1119
|
+
default:
|
|
1120
|
+
handleUnknownCode?.(firebaseError);
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1161
1124
|
}
|
|
1162
1125
|
|
|
1163
1126
|
/******************************************************************************
|
|
@@ -1204,10 +1167,10 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
1204
1167
|
const FIREBASE_APP_TOKEN = 'FIREBASE_APP_TOKEN';
|
|
1205
1168
|
// MARK: Firebase Admin Provider
|
|
1206
1169
|
function firebaseServerAppTokenProvider(useFactory) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1170
|
+
return {
|
|
1171
|
+
provide: FIREBASE_APP_TOKEN,
|
|
1172
|
+
useFactory
|
|
1173
|
+
};
|
|
1211
1174
|
}
|
|
1212
1175
|
|
|
1213
1176
|
// MARK: Tokens
|
|
@@ -1218,23 +1181,31 @@ const FIREBASE_AUTH_TOKEN = 'FIREBASE_AUTH_TOKEN';
|
|
|
1218
1181
|
/**
|
|
1219
1182
|
* Nest provider module for Firebase that provides a firestore, etc. from the firestore token.
|
|
1220
1183
|
*/
|
|
1221
|
-
let FirebaseServerAuthModule = class FirebaseServerAuthModule {
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1184
|
+
let FirebaseServerAuthModule = class FirebaseServerAuthModule {
|
|
1185
|
+
};
|
|
1186
|
+
FirebaseServerAuthModule = __decorate([
|
|
1187
|
+
Module({
|
|
1188
|
+
providers: [
|
|
1189
|
+
{
|
|
1190
|
+
provide: FIREBASE_AUTH_TOKEN,
|
|
1191
|
+
useFactory: (app) => app.auth(),
|
|
1192
|
+
inject: [FIREBASE_APP_TOKEN]
|
|
1193
|
+
}
|
|
1194
|
+
],
|
|
1195
|
+
exports: [FIREBASE_AUTH_TOKEN]
|
|
1196
|
+
})
|
|
1197
|
+
], FirebaseServerAuthModule);
|
|
1230
1198
|
function provideFirebaseServerAuthService(provider) {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1199
|
+
return [
|
|
1200
|
+
{
|
|
1201
|
+
...provider,
|
|
1202
|
+
inject: provider.inject ?? [FIREBASE_AUTH_TOKEN]
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
provide: FirebaseServerAuthService,
|
|
1206
|
+
useExisting: provider.provide
|
|
1207
|
+
}
|
|
1208
|
+
];
|
|
1238
1209
|
}
|
|
1239
1210
|
/**
|
|
1240
1211
|
* Convenience function used to generate ModuleMetadata for an app's Auth related modules and FirebaseServerAuthService provider.
|
|
@@ -1244,58 +1215,58 @@ function provideFirebaseServerAuthService(provider) {
|
|
|
1244
1215
|
* @returns
|
|
1245
1216
|
*/
|
|
1246
1217
|
function firebaseServerAuthModuleMetadata(config) {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1218
|
+
return mergeModuleMetadata({
|
|
1219
|
+
imports: [FirebaseServerAuthModule],
|
|
1220
|
+
exports: [FirebaseServerAuthModule, config.serviceProvider.provide],
|
|
1221
|
+
providers: provideFirebaseServerAuthService(config.serviceProvider)
|
|
1222
|
+
}, config);
|
|
1252
1223
|
}
|
|
1253
1224
|
|
|
1254
1225
|
function assertIsAdminInRequest(request) {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1226
|
+
if (!isAdminInRequest(request)) {
|
|
1227
|
+
throw forbiddenError();
|
|
1228
|
+
}
|
|
1258
1229
|
}
|
|
1259
1230
|
function isAdminInRequest(request) {
|
|
1260
|
-
|
|
1231
|
+
return request.nest.authService.context(request).isAdmin;
|
|
1261
1232
|
}
|
|
1262
1233
|
function assertIsAdminOrTargetUserInRequestData(request, requireUid) {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1234
|
+
if (!isAdminOrTargetUserInRequestData(request, requireUid)) {
|
|
1235
|
+
throw forbiddenError();
|
|
1236
|
+
}
|
|
1237
|
+
return request.data.uid ?? request.auth?.uid;
|
|
1267
1238
|
}
|
|
1268
1239
|
function isAdminOrTargetUserInRequestData(request, requireUid = false) {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1240
|
+
const uid = request.data.uid;
|
|
1241
|
+
const authUid = request.auth?.uid;
|
|
1242
|
+
let isAdminOrTargetUser = true;
|
|
1243
|
+
if ((requireUid && uid == null) || (uid != null && uid !== authUid)) {
|
|
1244
|
+
isAdminOrTargetUser = request.nest.authService.context(request).isAdmin;
|
|
1245
|
+
}
|
|
1246
|
+
return isAdminOrTargetUser;
|
|
1276
1247
|
}
|
|
1277
1248
|
function assertHasSignedTosInRequest(request) {
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1249
|
+
if (!hasSignedTosInRequest(request)) {
|
|
1250
|
+
throw forbiddenError({
|
|
1251
|
+
message: 'ToS has not been signed.'
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1283
1254
|
}
|
|
1284
1255
|
function hasSignedTosInRequest(request) {
|
|
1285
|
-
|
|
1256
|
+
return request.nest.authService.context(request).hasSignedTos;
|
|
1286
1257
|
}
|
|
1287
1258
|
function assertHasRolesInRequest(request, authRoles) {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1259
|
+
if (!hasAuthRolesInRequest(request, authRoles)) {
|
|
1260
|
+
throw forbiddenError({
|
|
1261
|
+
message: 'Missing required auth roles.',
|
|
1262
|
+
data: {
|
|
1263
|
+
roles: asArray(authRoles)
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1296
1267
|
}
|
|
1297
1268
|
function hasAuthRolesInRequest(request, authRoles) {
|
|
1298
|
-
|
|
1269
|
+
return containsAllValues(request.nest.authService.context(request).authRoles, authRoles);
|
|
1299
1270
|
}
|
|
1300
1271
|
/**
|
|
1301
1272
|
* Returns true if the claims have a FIREBASE_SERVER_AUTH_CLAIMS_SETUP_PASSWORD_KEY claims value, indicating they are a newly invited user.
|
|
@@ -1305,8 +1276,8 @@ function hasAuthRolesInRequest(request, authRoles) {
|
|
|
1305
1276
|
* @param request
|
|
1306
1277
|
*/
|
|
1307
1278
|
function hasNewUserSetupPasswordInRequest(request) {
|
|
1308
|
-
|
|
1309
|
-
|
|
1279
|
+
const claims = request.nest.authService.context(request).claims;
|
|
1280
|
+
return claims[FIREBASE_SERVER_AUTH_CLAIMS_SETUP_PASSWORD_KEY] != null;
|
|
1310
1281
|
}
|
|
1311
1282
|
|
|
1312
1283
|
/**
|
|
@@ -1316,123 +1287,114 @@ function hasNewUserSetupPasswordInRequest(request) {
|
|
|
1316
1287
|
* @returns
|
|
1317
1288
|
*/
|
|
1318
1289
|
function onCallDevelopmentFunction(map, config = {}) {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
} else {
|
|
1336
|
-
throw developmentUnknownSpecifierError(specifier);
|
|
1337
|
-
}
|
|
1338
|
-
};
|
|
1290
|
+
const { preAssert = () => undefined } = config;
|
|
1291
|
+
return (request) => {
|
|
1292
|
+
const specifier = request.data.specifier;
|
|
1293
|
+
const devFn = map[specifier];
|
|
1294
|
+
if (devFn) {
|
|
1295
|
+
preAssert({ request, specifier });
|
|
1296
|
+
return devFn({
|
|
1297
|
+
...request,
|
|
1298
|
+
specifier,
|
|
1299
|
+
data: request.data.data
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
throw developmentUnknownSpecifierError(specifier);
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1339
1306
|
}
|
|
1340
1307
|
function developmentUnknownSpecifierError(specifier) {
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1308
|
+
return badRequestError(serverError({
|
|
1309
|
+
status: 400,
|
|
1310
|
+
code: 'UNKNOWN_SPECIFIER_ERROR',
|
|
1311
|
+
message: `Invalid specifier "${specifier}" to run.`,
|
|
1312
|
+
data: {
|
|
1313
|
+
specifier
|
|
1314
|
+
}
|
|
1315
|
+
}));
|
|
1349
1316
|
}
|
|
1350
1317
|
|
|
1351
1318
|
const NO_RUN_NAME_SPECIFIED_FOR_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_CODE = 'NO_RUN_NAME_SPECIFIED_FOR_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION';
|
|
1352
1319
|
function noRunNameSpecifiedForScheduledFunctionDevelopmentFunction() {
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1320
|
+
return badRequestError({
|
|
1321
|
+
code: NO_RUN_NAME_SPECIFIED_FOR_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_CODE,
|
|
1322
|
+
message: `Must specify run parameter.`
|
|
1323
|
+
});
|
|
1357
1324
|
}
|
|
1358
1325
|
const UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_NAME_CODE = 'UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_NAME';
|
|
1359
1326
|
function unknownScheduledFunctionDevelopmentFunctionName(name) {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1327
|
+
return badRequestError({
|
|
1328
|
+
code: UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_NAME_CODE,
|
|
1329
|
+
message: `Unknown function with name "${name}"`,
|
|
1330
|
+
data: {
|
|
1331
|
+
name
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1367
1334
|
}
|
|
1368
1335
|
const UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_TYPE_CODE = 'UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_TYPE';
|
|
1369
1336
|
function unknownScheduledFunctionDevelopmentFunctionType(type) {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1337
|
+
return badRequestError({
|
|
1338
|
+
code: UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_TYPE_CODE,
|
|
1339
|
+
message: `Unknown type "${type}"`,
|
|
1340
|
+
data: {
|
|
1341
|
+
type
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1377
1344
|
}
|
|
1378
1345
|
|
|
1379
1346
|
function makeScheduledFunctionDevelopmentFunction(config) {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1347
|
+
const { allScheduledFunctions } = config;
|
|
1348
|
+
const getListValues = cachedGetter(() => {
|
|
1349
|
+
const result = [];
|
|
1350
|
+
forEachKeyValue(allScheduledFunctions, {
|
|
1351
|
+
forEach: (x) => {
|
|
1352
|
+
const [functionName, config] = x;
|
|
1353
|
+
result.push({
|
|
1354
|
+
name: functionName.toString()
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1390
1357
|
});
|
|
1391
|
-
|
|
1358
|
+
return result;
|
|
1392
1359
|
});
|
|
1393
|
-
return
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1360
|
+
return async (request) => {
|
|
1361
|
+
const { data } = request;
|
|
1362
|
+
const { type } = data;
|
|
1363
|
+
switch (type) {
|
|
1364
|
+
case 'run':
|
|
1365
|
+
const targetRunName = data.run;
|
|
1366
|
+
if (!targetRunName) {
|
|
1367
|
+
throw noRunNameSpecifiedForScheduledFunctionDevelopmentFunction();
|
|
1368
|
+
}
|
|
1369
|
+
const targetFunction = allScheduledFunctions[targetRunName];
|
|
1370
|
+
if (!targetFunction) {
|
|
1371
|
+
throw unknownScheduledFunctionDevelopmentFunctionName(targetRunName);
|
|
1372
|
+
}
|
|
1373
|
+
try {
|
|
1374
|
+
await targetFunction._runNow();
|
|
1375
|
+
}
|
|
1376
|
+
catch (e) {
|
|
1377
|
+
console.error(`Failed manually running task "${targetRunName}".`, e);
|
|
1378
|
+
throw e;
|
|
1379
|
+
}
|
|
1380
|
+
return {
|
|
1381
|
+
type: 'run',
|
|
1382
|
+
success: true
|
|
1383
|
+
};
|
|
1384
|
+
case 'list':
|
|
1385
|
+
return {
|
|
1386
|
+
type: 'list',
|
|
1387
|
+
list: getListValues()
|
|
1388
|
+
};
|
|
1389
|
+
default:
|
|
1390
|
+
throw unknownScheduledFunctionDevelopmentFunctionType(type);
|
|
1417
1391
|
}
|
|
1418
|
-
|
|
1419
|
-
type: 'run',
|
|
1420
|
-
success: true
|
|
1421
|
-
};
|
|
1422
|
-
case 'list':
|
|
1423
|
-
return {
|
|
1424
|
-
type: 'list',
|
|
1425
|
-
list: getListValues()
|
|
1426
|
-
};
|
|
1427
|
-
default:
|
|
1428
|
-
throw unknownScheduledFunctionDevelopmentFunctionType(type);
|
|
1429
|
-
}
|
|
1430
|
-
};
|
|
1392
|
+
};
|
|
1431
1393
|
}
|
|
1432
1394
|
|
|
1433
1395
|
function setNestContextOnRequest(makeNestContext, request) {
|
|
1434
|
-
|
|
1435
|
-
|
|
1396
|
+
request.nest = makeNestContext(request.nestApplication);
|
|
1397
|
+
return request;
|
|
1436
1398
|
}
|
|
1437
1399
|
/**
|
|
1438
1400
|
* Wraps the input OnCallWithNestContext function to flag it as optional to have auth data.
|
|
@@ -1441,9 +1403,9 @@ function setNestContextOnRequest(makeNestContext, request) {
|
|
|
1441
1403
|
* @returns
|
|
1442
1404
|
*/
|
|
1443
1405
|
function optionalAuthContext(fn) {
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1406
|
+
const fnWithOptionalAuth = ((request) => fn(request));
|
|
1407
|
+
fnWithOptionalAuth._requireAuth = false;
|
|
1408
|
+
return fnWithOptionalAuth;
|
|
1447
1409
|
}
|
|
1448
1410
|
/**
|
|
1449
1411
|
* Asserts that the input request has auth data if the inputOnCallWithAuthAwareNestRequireAuthRef object is flagged to require auth.
|
|
@@ -1452,9 +1414,9 @@ function optionalAuthContext(fn) {
|
|
|
1452
1414
|
* @param request
|
|
1453
1415
|
*/
|
|
1454
1416
|
function assertRequestRequiresAuthForFunction(fn, request) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1417
|
+
if (fn._requireAuth !== false) {
|
|
1418
|
+
assertIsContextWithAuthData(request);
|
|
1419
|
+
}
|
|
1458
1420
|
}
|
|
1459
1421
|
/**
|
|
1460
1422
|
* Creates an OnCallWithNestContext wrapper that validates the input CallableContext to assert the context has auth data before entering the function.
|
|
@@ -1463,64 +1425,59 @@ function assertRequestRequiresAuthForFunction(fn, request) {
|
|
|
1463
1425
|
* @returns
|
|
1464
1426
|
*/
|
|
1465
1427
|
function inAuthContext(fn) {
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1428
|
+
return (request) => {
|
|
1429
|
+
assertIsContextWithAuthData(request);
|
|
1430
|
+
return fn(request);
|
|
1431
|
+
};
|
|
1470
1432
|
}
|
|
1471
1433
|
|
|
1472
1434
|
function firebaseServerDevFunctions(config) {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1435
|
+
const { enabled, secure, nest, developerFunctionsMap, onCallFactory, allScheduledFunctions, disableDevelopmentScheduleFunction } = config;
|
|
1436
|
+
let dev;
|
|
1437
|
+
if (enabled) {
|
|
1438
|
+
const fullFunctionsMap = {
|
|
1439
|
+
...developerFunctionsMap
|
|
1440
|
+
};
|
|
1441
|
+
if (allScheduledFunctions && disableDevelopmentScheduleFunction !== false) {
|
|
1442
|
+
fullFunctionsMap[SCHEDULED_FUNCTION_DEV_FUNCTION_SPECIFIER] = makeScheduledFunctionDevelopmentFunction({
|
|
1443
|
+
allScheduledFunctions
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
let onCallFunction = onCallDevelopmentFunction(fullFunctionsMap);
|
|
1447
|
+
if (secure != false) {
|
|
1448
|
+
onCallFunction = inAuthContext(onCallFunction);
|
|
1449
|
+
}
|
|
1450
|
+
dev = onCallFactory(onCallFunction)(nest);
|
|
1451
|
+
}
|
|
1452
|
+
else {
|
|
1453
|
+
dev = onCallFactory(async (x) => {
|
|
1454
|
+
throw unavailableError({
|
|
1455
|
+
message: 'developer tools service is not enabled.'
|
|
1456
|
+
});
|
|
1457
|
+
})(nest);
|
|
1458
|
+
}
|
|
1459
|
+
return {
|
|
1460
|
+
dev
|
|
1486
1461
|
};
|
|
1487
|
-
if (allScheduledFunctions && disableDevelopmentScheduleFunction !== false) {
|
|
1488
|
-
fullFunctionsMap[SCHEDULED_FUNCTION_DEV_FUNCTION_SPECIFIER] = makeScheduledFunctionDevelopmentFunction({
|
|
1489
|
-
allScheduledFunctions
|
|
1490
|
-
});
|
|
1491
|
-
}
|
|
1492
|
-
let onCallFunction = onCallDevelopmentFunction(fullFunctionsMap);
|
|
1493
|
-
if (secure != false) {
|
|
1494
|
-
onCallFunction = inAuthContext(onCallFunction);
|
|
1495
|
-
}
|
|
1496
|
-
dev = onCallFactory(onCallFunction)(nest);
|
|
1497
|
-
} else {
|
|
1498
|
-
dev = onCallFactory(async x => {
|
|
1499
|
-
throw unavailableError({
|
|
1500
|
-
message: 'developer tools service is not enabled.'
|
|
1501
|
-
});
|
|
1502
|
-
})(nest);
|
|
1503
|
-
}
|
|
1504
|
-
return {
|
|
1505
|
-
dev
|
|
1506
|
-
};
|
|
1507
1462
|
}
|
|
1508
1463
|
|
|
1509
1464
|
let DefaultFirebaseServerEnvService = class DefaultFirebaseServerEnvService extends ServerEnvironmentService {
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* Enabled when not in production and not in a testing environment.
|
|
1467
|
+
*/
|
|
1468
|
+
get developmentSchedulerEnabled() {
|
|
1469
|
+
return !this.isProduction && !this.isTestingEnv;
|
|
1470
|
+
}
|
|
1516
1471
|
};
|
|
1517
|
-
DefaultFirebaseServerEnvService = __decorate([
|
|
1472
|
+
DefaultFirebaseServerEnvService = __decorate([
|
|
1473
|
+
Injectable()
|
|
1474
|
+
], DefaultFirebaseServerEnvService);
|
|
1518
1475
|
|
|
1519
1476
|
function nestAppIsProductionEnvironment(nest) {
|
|
1520
|
-
|
|
1477
|
+
return () => nest().then((x) => x.get(FirebaseServerEnvService).isProduction);
|
|
1521
1478
|
}
|
|
1522
1479
|
function nestAppHasDevelopmentSchedulerEnabled(nest) {
|
|
1523
|
-
|
|
1480
|
+
return () => nest().then((x) => x.get(FirebaseServerEnvService).developmentSchedulerEnabled);
|
|
1524
1481
|
}
|
|
1525
1482
|
|
|
1526
1483
|
// MARK: Tokens
|
|
@@ -1535,28 +1492,38 @@ const FIREBASE_FIRESTORE_CONTEXT_TOKEN = 'FIREBASE_FIRESTORE_CONTEXT_TOKEN';
|
|
|
1535
1492
|
/**
|
|
1536
1493
|
* Nest provider module for Firebase that provides a firestore, etc. from the firestore token.
|
|
1537
1494
|
*/
|
|
1538
|
-
let FirebaseServerFirestoreModule = class FirebaseServerFirestoreModule {
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1495
|
+
let FirebaseServerFirestoreModule = class FirebaseServerFirestoreModule {
|
|
1496
|
+
};
|
|
1497
|
+
FirebaseServerFirestoreModule = __decorate([
|
|
1498
|
+
Module({
|
|
1499
|
+
providers: [
|
|
1500
|
+
{
|
|
1501
|
+
provide: FIREBASE_FIRESTORE_TOKEN,
|
|
1502
|
+
useFactory: (app) => app.firestore(),
|
|
1503
|
+
inject: [FIREBASE_APP_TOKEN]
|
|
1504
|
+
}
|
|
1505
|
+
],
|
|
1506
|
+
exports: [FIREBASE_FIRESTORE_TOKEN]
|
|
1507
|
+
})
|
|
1508
|
+
], FirebaseServerFirestoreModule);
|
|
1547
1509
|
/**
|
|
1548
1510
|
* Nest provider module for firebase that includes the FirebaseServerFirestoreModule and provides a value for FIRESTORE_CONTEXT_TOKEN using the googleCloudFirestoreContextFactory.
|
|
1549
1511
|
*/
|
|
1550
|
-
let FirebaseServerFirestoreContextModule = class FirebaseServerFirestoreContextModule {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1512
|
+
let FirebaseServerFirestoreContextModule = class FirebaseServerFirestoreContextModule {
|
|
1513
|
+
};
|
|
1514
|
+
FirebaseServerFirestoreContextModule = __decorate([
|
|
1515
|
+
Module({
|
|
1516
|
+
imports: [FirebaseServerFirestoreModule],
|
|
1517
|
+
providers: [
|
|
1518
|
+
{
|
|
1519
|
+
provide: FIREBASE_FIRESTORE_CONTEXT_TOKEN,
|
|
1520
|
+
useFactory: googleCloudFirestoreContextFactory,
|
|
1521
|
+
inject: [FIREBASE_FIRESTORE_TOKEN]
|
|
1522
|
+
}
|
|
1523
|
+
],
|
|
1524
|
+
exports: [FirebaseServerFirestoreModule, FIREBASE_FIRESTORE_CONTEXT_TOKEN]
|
|
1525
|
+
})
|
|
1526
|
+
], FirebaseServerFirestoreContextModule);
|
|
1560
1527
|
/**
|
|
1561
1528
|
* Used to configure a Nestjs provider for a FirestoreCollections-type object that is initialized with a FirestoreContext.
|
|
1562
1529
|
*
|
|
@@ -1564,15 +1531,14 @@ FirebaseServerFirestoreContextModule = __decorate([Module({
|
|
|
1564
1531
|
* @param useFactory
|
|
1565
1532
|
* @returns
|
|
1566
1533
|
*/
|
|
1567
|
-
function provideAppFirestoreCollections({
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
}];
|
|
1534
|
+
function provideAppFirestoreCollections({ provide, useFactory }) {
|
|
1535
|
+
return [
|
|
1536
|
+
{
|
|
1537
|
+
provide,
|
|
1538
|
+
useFactory,
|
|
1539
|
+
inject: [FIREBASE_FIRESTORE_CONTEXT_TOKEN]
|
|
1540
|
+
}
|
|
1541
|
+
];
|
|
1576
1542
|
}
|
|
1577
1543
|
/**
|
|
1578
1544
|
* Convenience function used to generate ModuleMetadata for an app's Firestore related modules and an appFirestoreCollection
|
|
@@ -1582,11 +1548,11 @@ function provideAppFirestoreCollections({
|
|
|
1582
1548
|
* @returns
|
|
1583
1549
|
*/
|
|
1584
1550
|
function appFirestoreModuleMetadata(config) {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1551
|
+
return {
|
|
1552
|
+
imports: [FirebaseServerFirestoreContextModule, ...(config.imports ?? [])],
|
|
1553
|
+
exports: [FirebaseServerFirestoreContextModule, config.provide, ...(config.exports ?? [])],
|
|
1554
|
+
providers: [...provideAppFirestoreCollections(config), ...(config.providers ?? [])]
|
|
1555
|
+
};
|
|
1590
1556
|
}
|
|
1591
1557
|
|
|
1592
1558
|
/**
|
|
@@ -1597,9 +1563,9 @@ function appFirestoreModuleMetadata(config) {
|
|
|
1597
1563
|
* @returns
|
|
1598
1564
|
*/
|
|
1599
1565
|
function makeBlockingFunctionWithHandler(blockingFunctionBuilder, handler, opts) {
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1566
|
+
const blockingFn = opts != null ? blockingFunctionBuilder(opts, handler) : blockingFunctionBuilder(handler);
|
|
1567
|
+
blockingFn.__handler = handler;
|
|
1568
|
+
return blockingFn;
|
|
1603
1569
|
}
|
|
1604
1570
|
/**
|
|
1605
1571
|
* Creates a BlockingFunctionHandlerWithNestContextFactory.
|
|
@@ -1609,18 +1575,18 @@ function makeBlockingFunctionWithHandler(blockingFunctionBuilder, handler, opts)
|
|
|
1609
1575
|
* @returns
|
|
1610
1576
|
*/
|
|
1611
1577
|
function blockingFunctionHandlerWithNestContextFactory(makeNestContext) {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1578
|
+
return (fn) => {
|
|
1579
|
+
return (nestAppPromiseGetter) => {
|
|
1580
|
+
const handlerBuilder = (handler) => {
|
|
1581
|
+
const fnHandler = (event) => nestAppPromiseGetter().then((nestApplication) => handler({
|
|
1582
|
+
...event,
|
|
1583
|
+
nest: makeNestContext(nestApplication)
|
|
1584
|
+
}));
|
|
1585
|
+
return fnHandler;
|
|
1586
|
+
};
|
|
1587
|
+
return fn(handlerBuilder);
|
|
1588
|
+
};
|
|
1622
1589
|
};
|
|
1623
|
-
};
|
|
1624
1590
|
}
|
|
1625
1591
|
|
|
1626
1592
|
/**
|
|
@@ -1630,15 +1596,12 @@ function blockingFunctionHandlerWithNestContextFactory(makeNestContext) {
|
|
|
1630
1596
|
* @returns
|
|
1631
1597
|
*/
|
|
1632
1598
|
function onCallHandlerWithNestApplicationFactory(defaultOpts = {}) {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
nestApplication
|
|
1640
|
-
})));
|
|
1641
|
-
};
|
|
1599
|
+
return (fn, opts) => {
|
|
1600
|
+
return (nestAppPromiseGetter) => https.onCall({ ...defaultOpts, ...opts }, (request) => nestAppPromiseGetter().then((nestApplication) => fn({
|
|
1601
|
+
...request,
|
|
1602
|
+
nestApplication
|
|
1603
|
+
})));
|
|
1604
|
+
};
|
|
1642
1605
|
}
|
|
1643
1606
|
/**
|
|
1644
1607
|
* Creates a factory for generating OnCallWithNestContext functions with a nest context object that is generated by the input function.
|
|
@@ -1648,7 +1611,7 @@ function onCallHandlerWithNestApplicationFactory(defaultOpts = {}) {
|
|
|
1648
1611
|
* @returns
|
|
1649
1612
|
*/
|
|
1650
1613
|
function onCallHandlerWithNestContextFactory(appFactory, makeNestContext) {
|
|
1651
|
-
|
|
1614
|
+
return (fn, opts) => appFactory((request) => fn(setNestContextOnRequest(makeNestContext, request)), opts);
|
|
1652
1615
|
}
|
|
1653
1616
|
|
|
1654
1617
|
/**
|
|
@@ -1659,30 +1622,30 @@ function onCallHandlerWithNestContextFactory(appFactory, makeNestContext) {
|
|
|
1659
1622
|
* @returns
|
|
1660
1623
|
*/
|
|
1661
1624
|
function cloudEventHandlerWithNestContextFactory(makeNestContext) {
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1625
|
+
return (fn) => {
|
|
1626
|
+
return (nestAppPromiseGetter) => {
|
|
1627
|
+
const handlerBuilder = (handler) => {
|
|
1628
|
+
const fnHandler = (event) => nestAppPromiseGetter().then((nestApplication) => handler({
|
|
1629
|
+
...event,
|
|
1630
|
+
nest: makeNestContext(nestApplication)
|
|
1631
|
+
}));
|
|
1632
|
+
return fnHandler;
|
|
1633
|
+
};
|
|
1634
|
+
return fn(handlerBuilder);
|
|
1635
|
+
};
|
|
1672
1636
|
};
|
|
1673
|
-
};
|
|
1674
1637
|
}
|
|
1675
1638
|
|
|
1676
1639
|
function setNestContextOnScheduleRequest(makeNestContext, request) {
|
|
1677
|
-
|
|
1678
|
-
|
|
1640
|
+
request.nest = makeNestContext(request.nestApplication);
|
|
1641
|
+
return request;
|
|
1679
1642
|
}
|
|
1680
1643
|
|
|
1681
1644
|
function makeOnScheduleHandlerWithNestApplicationRequest(nestApplication, scheduleContext) {
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1645
|
+
return {
|
|
1646
|
+
nestApplication,
|
|
1647
|
+
scheduleContext
|
|
1648
|
+
};
|
|
1686
1649
|
}
|
|
1687
1650
|
/**
|
|
1688
1651
|
* Creates a factory for generating OnCallWithNestApplication functions.
|
|
@@ -1691,27 +1654,29 @@ function makeOnScheduleHandlerWithNestApplicationRequest(nestApplication, schedu
|
|
|
1691
1654
|
* @returns
|
|
1692
1655
|
*/
|
|
1693
1656
|
function onScheduleHandlerWithNestApplicationFactory(baseScheduleConfig) {
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1657
|
+
return (inputSchedule, fn) => {
|
|
1658
|
+
const schedule = mergeObjects([baseScheduleConfig, inputSchedule]);
|
|
1659
|
+
if (!schedule.schedule) {
|
|
1660
|
+
if (schedule.cron) {
|
|
1661
|
+
if (typeof schedule.cron === 'number') {
|
|
1662
|
+
schedule.schedule = cronExpressionRepeatingEveryNMinutes(schedule.cron);
|
|
1663
|
+
}
|
|
1664
|
+
else {
|
|
1665
|
+
schedule.schedule = schedule.cron;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
else {
|
|
1669
|
+
throw new Error('Missing required "cron" or "schedule" variable for configuration.');
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return (nestAppPromiseGetter) => {
|
|
1673
|
+
const runNow = (scheduleContext) => nestAppPromiseGetter().then((x) => fn(makeOnScheduleHandlerWithNestApplicationRequest(x, scheduleContext)));
|
|
1674
|
+
const fnn = scheduler.onSchedule(schedule, runNow);
|
|
1675
|
+
fnn._schedule = schedule;
|
|
1676
|
+
fnn._runNow = runNow;
|
|
1677
|
+
return fnn;
|
|
1678
|
+
};
|
|
1713
1679
|
};
|
|
1714
|
-
};
|
|
1715
1680
|
}
|
|
1716
1681
|
/**
|
|
1717
1682
|
* Creates a factory for generating OnCallWithNestContext functions with a nest context object that is generated by the input function.
|
|
@@ -1721,7 +1686,7 @@ function onScheduleHandlerWithNestApplicationFactory(baseScheduleConfig) {
|
|
|
1721
1686
|
* @returns
|
|
1722
1687
|
*/
|
|
1723
1688
|
function onScheduleHandlerWithNestContextFactory(appFactory, makeNestContext) {
|
|
1724
|
-
|
|
1689
|
+
return (schedule, fn) => appFactory(schedule, (request) => fn(setNestContextOnScheduleRequest(makeNestContext, request)));
|
|
1725
1690
|
}
|
|
1726
1691
|
|
|
1727
1692
|
/**
|
|
@@ -1732,37 +1697,38 @@ function onScheduleHandlerWithNestContextFactory(appFactory, makeNestContext) {
|
|
|
1732
1697
|
* @returns
|
|
1733
1698
|
*/
|
|
1734
1699
|
function taskQueueFunctionHandlerWithNestContextFactory(makeNestContext) {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1700
|
+
return (fn) => {
|
|
1701
|
+
return (nestAppPromiseGetter) => {
|
|
1702
|
+
const handlerBuilder = (handler) => {
|
|
1703
|
+
const fnHandler = (taskRequest) => nestAppPromiseGetter().then((nestApplication) => handler({
|
|
1704
|
+
...taskRequest,
|
|
1705
|
+
nest: makeNestContext(nestApplication)
|
|
1706
|
+
}));
|
|
1707
|
+
return fnHandler;
|
|
1708
|
+
};
|
|
1709
|
+
return fn(handlerBuilder);
|
|
1710
|
+
};
|
|
1745
1711
|
};
|
|
1746
|
-
};
|
|
1747
1712
|
}
|
|
1748
1713
|
// TODO(FUTURE): Add factory that also adds onTaskDispatched usage, as the above is incomplete for full usage and only sets up a function for the request.
|
|
1749
1714
|
|
|
1750
|
-
class AbstractFirebaseServerActionsContext {
|
|
1715
|
+
class AbstractFirebaseServerActionsContext {
|
|
1716
|
+
}
|
|
1751
1717
|
function firebaseServerActionsContext(options) {
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1718
|
+
return {
|
|
1719
|
+
...firebaseServerActionsTransformContext(options)
|
|
1720
|
+
};
|
|
1755
1721
|
}
|
|
1756
|
-
const defaultFirebaseServerActionsTransformFactoryLogErrorFunction = details => {
|
|
1757
|
-
|
|
1722
|
+
const defaultFirebaseServerActionsTransformFactoryLogErrorFunction = (details) => {
|
|
1723
|
+
console.log('firebaseServerActionsTransformFactory() encountered validation error: ', details);
|
|
1758
1724
|
};
|
|
1759
1725
|
function firebaseServerActionsTransformContext(options) {
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1726
|
+
const firebaseServerActionTransformFactory = firebaseServerActionsTransformFactory(options);
|
|
1727
|
+
const firebaseServerActionTransformFunctionFactory = toTransformAndValidateFunctionResultFactory(firebaseServerActionTransformFactory);
|
|
1728
|
+
return {
|
|
1729
|
+
firebaseServerActionTransformFactory,
|
|
1730
|
+
firebaseServerActionTransformFunctionFactory
|
|
1731
|
+
};
|
|
1766
1732
|
}
|
|
1767
1733
|
const FIREBASE_SERVER_VALIDATION_ERROR_CODE = 'VALIDATION_ERROR';
|
|
1768
1734
|
/**
|
|
@@ -1771,16 +1737,16 @@ const FIREBASE_SERVER_VALIDATION_ERROR_CODE = 'VALIDATION_ERROR';
|
|
|
1771
1737
|
* @returns
|
|
1772
1738
|
*/
|
|
1773
1739
|
function firebaseServerValidationServerError(validationError) {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1740
|
+
const nestValidationExceptionFactory = new ValidationPipe({
|
|
1741
|
+
forbidUnknownValues: false
|
|
1742
|
+
}).createExceptionFactory();
|
|
1743
|
+
const nestError = nestValidationExceptionFactory(validationError);
|
|
1744
|
+
const data = nestError.getResponse();
|
|
1745
|
+
return {
|
|
1746
|
+
message: 'One or more data/form validation errors occurred.',
|
|
1747
|
+
code: FIREBASE_SERVER_VALIDATION_ERROR_CODE,
|
|
1748
|
+
data
|
|
1749
|
+
};
|
|
1784
1750
|
}
|
|
1785
1751
|
/**
|
|
1786
1752
|
* Creates a new badRequestError with the validation error details as the response data.
|
|
@@ -1789,48 +1755,41 @@ function firebaseServerValidationServerError(validationError) {
|
|
|
1789
1755
|
* @returns
|
|
1790
1756
|
*/
|
|
1791
1757
|
function firebaseServerValidationError(validationError) {
|
|
1792
|
-
|
|
1793
|
-
|
|
1758
|
+
const serverError = firebaseServerValidationServerError(validationError);
|
|
1759
|
+
return badRequestError(serverError);
|
|
1794
1760
|
}
|
|
1795
1761
|
function firebaseServerActionsTransformFactory(options) {
|
|
1796
|
-
|
|
1797
|
-
logError
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
logErrorFunction(data);
|
|
1808
|
-
throw badRequestError(serverError);
|
|
1809
|
-
},
|
|
1810
|
-
defaultValidationOptions
|
|
1811
|
-
});
|
|
1762
|
+
const { logError, defaultValidationOptions } = options ?? {};
|
|
1763
|
+
const logErrorFunction = logError !== false ? (typeof logError === 'function' ? logError : defaultFirebaseServerActionsTransformFactoryLogErrorFunction) : mapIdentityFunction;
|
|
1764
|
+
return transformAndValidateObjectFactory({
|
|
1765
|
+
handleValidationError: (validationError) => {
|
|
1766
|
+
const serverError = firebaseServerValidationServerError(validationError);
|
|
1767
|
+
const { data } = serverError;
|
|
1768
|
+
logErrorFunction(data);
|
|
1769
|
+
throw badRequestError(serverError);
|
|
1770
|
+
},
|
|
1771
|
+
defaultValidationOptions
|
|
1772
|
+
});
|
|
1812
1773
|
}
|
|
1813
1774
|
|
|
1814
1775
|
function injectNestIntoRequest(nest, request) {
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1776
|
+
return {
|
|
1777
|
+
...request,
|
|
1778
|
+
nest
|
|
1779
|
+
};
|
|
1819
1780
|
}
|
|
1820
1781
|
function injectNestApplicationContextIntoRequest(nestContext, request) {
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1782
|
+
return {
|
|
1783
|
+
...request,
|
|
1784
|
+
nestApplication: nestContext
|
|
1785
|
+
};
|
|
1825
1786
|
}
|
|
1826
1787
|
|
|
1827
1788
|
/**
|
|
1828
1789
|
* Can be injected to retrieve information about the global prefix configured for the app.
|
|
1829
1790
|
*/
|
|
1830
1791
|
class GlobalRoutePrefixConfig {
|
|
1831
|
-
|
|
1832
|
-
this.globalApiRoutePrefix = void 0;
|
|
1833
|
-
}
|
|
1792
|
+
globalApiRoutePrefix;
|
|
1834
1793
|
}
|
|
1835
1794
|
|
|
1836
1795
|
/**
|
|
@@ -1839,33 +1798,38 @@ class GlobalRoutePrefixConfig {
|
|
|
1839
1798
|
* It ignores all webhook paths by default.
|
|
1840
1799
|
*/
|
|
1841
1800
|
let FirebaseAppCheckMiddleware = class FirebaseAppCheckMiddleware {
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1801
|
+
globalRoutePrefixConfig;
|
|
1802
|
+
logger = new Logger('FirebaseAppCheckMiddleware');
|
|
1803
|
+
_ignoredWebhookPath;
|
|
1804
|
+
constructor(globalRoutePrefixConfig) {
|
|
1805
|
+
this.globalRoutePrefixConfig = globalRoutePrefixConfig;
|
|
1806
|
+
this._ignoredWebhookPath = this.globalRoutePrefixConfig?.globalApiRoutePrefix ? `${this.globalRoutePrefixConfig.globalApiRoutePrefix}${DEFAULT_BASE_WEBHOOK_PATH}` : DEFAULT_BASE_WEBHOOK_PATH;
|
|
1807
|
+
}
|
|
1808
|
+
async use(req, res, next) {
|
|
1809
|
+
const isIgnoredRoute = this.isIgnoredRequest(req);
|
|
1810
|
+
let error;
|
|
1811
|
+
if (!isIgnoredRoute) {
|
|
1812
|
+
error = await verifyAppCheckInRequest(req);
|
|
1813
|
+
if (error) {
|
|
1814
|
+
this.logger.error('app check token failed verify');
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
next(error);
|
|
1818
|
+
}
|
|
1819
|
+
isIgnoredRequest(req) {
|
|
1820
|
+
const isIgnoredRoute = req.skipAppCheck || this.isIgnoredPath(req.baseUrl);
|
|
1821
|
+
return isIgnoredRoute;
|
|
1822
|
+
}
|
|
1823
|
+
isIgnoredPath(path) {
|
|
1824
|
+
return path.startsWith(this._ignoredWebhookPath);
|
|
1825
|
+
}
|
|
1867
1826
|
};
|
|
1868
|
-
FirebaseAppCheckMiddleware = __decorate([
|
|
1827
|
+
FirebaseAppCheckMiddleware = __decorate([
|
|
1828
|
+
Injectable(),
|
|
1829
|
+
__param(0, Optional()),
|
|
1830
|
+
__param(0, Inject(GlobalRoutePrefixConfig)),
|
|
1831
|
+
__metadata("design:paramtypes", [Object])
|
|
1832
|
+
], FirebaseAppCheckMiddleware);
|
|
1869
1833
|
/**
|
|
1870
1834
|
* Verifies the AppCheck parameter. If it fails, a value is returned.
|
|
1871
1835
|
*
|
|
@@ -1875,50 +1839,54 @@ FirebaseAppCheckMiddleware = __decorate([Injectable(), __param(0, Optional()), _
|
|
|
1875
1839
|
* @returns
|
|
1876
1840
|
*/
|
|
1877
1841
|
async function verifyAppCheckInRequest(req) {
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
} else {
|
|
1883
|
-
// verify the token
|
|
1884
|
-
try {
|
|
1885
|
-
await admin.appCheck().verifyToken(appCheckToken);
|
|
1886
|
-
} catch (e) {
|
|
1887
|
-
error = new ForbiddenException();
|
|
1842
|
+
const appCheckToken = req.header('X-Firebase-AppCheck');
|
|
1843
|
+
let error;
|
|
1844
|
+
if (!appCheckToken) {
|
|
1845
|
+
error = new ForbiddenException();
|
|
1888
1846
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1847
|
+
else {
|
|
1848
|
+
// verify the token
|
|
1849
|
+
try {
|
|
1850
|
+
await admin.appCheck().verifyToken(appCheckToken);
|
|
1851
|
+
}
|
|
1852
|
+
catch (e) {
|
|
1853
|
+
error = new ForbiddenException();
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return error;
|
|
1891
1857
|
}
|
|
1892
1858
|
|
|
1893
1859
|
/**
|
|
1894
1860
|
* Convenience class that mirrors the ConfigureAppCheckMiddlewareModule class in @dereekb/nestjs, but for Firebase apps.
|
|
1895
1861
|
*/
|
|
1896
1862
|
let ConfigureFirebaseAppCheckMiddlewareModule = class ConfigureFirebaseAppCheckMiddlewareModule {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
this.logger.debug('Configured firebase webhook routes with proper middleware.');
|
|
1903
|
-
}
|
|
1863
|
+
logger = new Logger('ConfigureFirebaseAppCheckMiddlewareModule');
|
|
1864
|
+
configure(consumer) {
|
|
1865
|
+
consumer.apply(FirebaseAppCheckMiddleware).forRoutes('*');
|
|
1866
|
+
this.logger.debug('Configured firebase webhook routes with proper middleware.');
|
|
1867
|
+
}
|
|
1904
1868
|
};
|
|
1905
|
-
ConfigureFirebaseAppCheckMiddlewareModule = __decorate([
|
|
1869
|
+
ConfigureFirebaseAppCheckMiddlewareModule = __decorate([
|
|
1870
|
+
Module({})
|
|
1871
|
+
], ConfigureFirebaseAppCheckMiddlewareModule);
|
|
1906
1872
|
|
|
1907
1873
|
/**
|
|
1908
1874
|
* nestjs decorator that will instruct FirebaseAppCheckMiddleware to skip AppCheck for related requests.
|
|
1909
1875
|
*/
|
|
1910
1876
|
const SkipAppCheck = createParamDecorator(async (_, context) => {
|
|
1911
|
-
|
|
1912
|
-
|
|
1877
|
+
const req = context.switchToHttp().getRequest();
|
|
1878
|
+
req.skipAppCheck = true;
|
|
1913
1879
|
});
|
|
1914
1880
|
|
|
1915
1881
|
let FirebaseRawBodyMiddleware = class FirebaseRawBodyMiddleware {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1882
|
+
use(req, res, next) {
|
|
1883
|
+
req.body = req.rawBody;
|
|
1884
|
+
next();
|
|
1885
|
+
}
|
|
1920
1886
|
};
|
|
1921
|
-
FirebaseRawBodyMiddleware = __decorate([
|
|
1887
|
+
FirebaseRawBodyMiddleware = __decorate([
|
|
1888
|
+
Injectable()
|
|
1889
|
+
], FirebaseRawBodyMiddleware);
|
|
1922
1890
|
|
|
1923
1891
|
/**
|
|
1924
1892
|
* Convenience class that mirrors the ConfigureWebhookMiddlewareModule class in @dereekb/nestjs, but for Firebase apps.
|
|
@@ -1926,60 +1894,59 @@ FirebaseRawBodyMiddleware = __decorate([Injectable()], FirebaseRawBodyMiddleware
|
|
|
1926
1894
|
* Requests to /webhook/* have their request.body value set to the rawBody.
|
|
1927
1895
|
*/
|
|
1928
1896
|
let ConfigureFirebaseWebhookMiddlewareModule = class ConfigureFirebaseWebhookMiddlewareModule {
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
this.logger.debug('Configured firebase webhook routes with proper middleware.');
|
|
1935
|
-
}
|
|
1897
|
+
logger = new Logger('ConfigureFirebaseWebhookMiddlewareModule');
|
|
1898
|
+
configure(consumer) {
|
|
1899
|
+
consumer.apply(FirebaseRawBodyMiddleware).forRoutes(DEFAULT_WEBHOOK_MIDDLEWARE_ROUTE_INFO);
|
|
1900
|
+
this.logger.debug('Configured firebase webhook routes with proper middleware.');
|
|
1901
|
+
}
|
|
1936
1902
|
};
|
|
1937
|
-
ConfigureFirebaseWebhookMiddlewareModule = __decorate([
|
|
1903
|
+
ConfigureFirebaseWebhookMiddlewareModule = __decorate([
|
|
1904
|
+
Module({})
|
|
1905
|
+
], ConfigureFirebaseWebhookMiddlewareModule);
|
|
1938
1906
|
|
|
1939
|
-
const nestFirebaseDoesNotExistError = firebaseContextGrantedModelRoles => {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1907
|
+
const nestFirebaseDoesNotExistError = (firebaseContextGrantedModelRoles) => {
|
|
1908
|
+
return modelNotAvailableError({
|
|
1909
|
+
data: {
|
|
1910
|
+
id: firebaseContextGrantedModelRoles.data?.document.key,
|
|
1911
|
+
type: firebaseContextGrantedModelRoles.data?.document.modelType
|
|
1912
|
+
}
|
|
1913
|
+
});
|
|
1946
1914
|
};
|
|
1947
1915
|
const nestFirebaseForbiddenPermissionError = (firebaseContextGrantedModelRoles, roles) => {
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1916
|
+
return forbiddenError({
|
|
1917
|
+
data: {
|
|
1918
|
+
id: firebaseContextGrantedModelRoles.data?.document.key,
|
|
1919
|
+
type: firebaseContextGrantedModelRoles.data?.document.modelType,
|
|
1920
|
+
roles
|
|
1921
|
+
}
|
|
1922
|
+
});
|
|
1955
1923
|
};
|
|
1956
1924
|
|
|
1957
1925
|
function onCallSpecifierHandler(config) {
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
return fn;
|
|
1926
|
+
const map = objectToMap(config);
|
|
1927
|
+
const fn = (request) => {
|
|
1928
|
+
const { specifier = MODEL_FUNCTION_FIREBASE_CRUD_FUNCTION_SPECIFIER_DEFAULT } = request;
|
|
1929
|
+
const handler = map.get(specifier);
|
|
1930
|
+
if (handler != null) {
|
|
1931
|
+
assertRequestRequiresAuthForFunction(handler, request);
|
|
1932
|
+
return handler(request);
|
|
1933
|
+
}
|
|
1934
|
+
else {
|
|
1935
|
+
throw unknownModelCrudFunctionSpecifierError(specifier);
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
fn._requireAuth = false;
|
|
1939
|
+
return fn;
|
|
1973
1940
|
}
|
|
1974
1941
|
function unknownModelCrudFunctionSpecifierError(specifier) {
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1942
|
+
return badRequestError(serverError({
|
|
1943
|
+
status: 400,
|
|
1944
|
+
code: 'UNKNOWN_SPECIFIER_ERROR',
|
|
1945
|
+
message: 'Invalid/unknown specifier for this function.',
|
|
1946
|
+
data: {
|
|
1947
|
+
specifier
|
|
1948
|
+
}
|
|
1949
|
+
}));
|
|
1983
1950
|
}
|
|
1984
1951
|
|
|
1985
1952
|
/**
|
|
@@ -1989,497 +1956,455 @@ function unknownModelCrudFunctionSpecifierError(specifier) {
|
|
|
1989
1956
|
* @returns
|
|
1990
1957
|
*/
|
|
1991
1958
|
function onCallModel(map, config = {}) {
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
return callFn(request);
|
|
2011
|
-
} else {
|
|
2012
|
-
throw onCallModelUnknownCallTypeError(call);
|
|
2013
|
-
}
|
|
2014
|
-
} else {
|
|
2015
|
-
throw onCallModelMissingCallTypeError();
|
|
2016
|
-
}
|
|
2017
|
-
};
|
|
1959
|
+
const { preAssert = () => undefined } = config;
|
|
1960
|
+
return (request) => {
|
|
1961
|
+
const call = request.data?.call;
|
|
1962
|
+
if (call) {
|
|
1963
|
+
const callFn = map[call];
|
|
1964
|
+
if (callFn) {
|
|
1965
|
+
const { specifier, modelType } = request.data;
|
|
1966
|
+
preAssert({ call, request, modelType, specifier });
|
|
1967
|
+
return callFn(request);
|
|
1968
|
+
}
|
|
1969
|
+
else {
|
|
1970
|
+
throw onCallModelUnknownCallTypeError(call);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
else {
|
|
1974
|
+
throw onCallModelMissingCallTypeError();
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
2018
1977
|
}
|
|
2019
1978
|
function onCallModelMissingCallTypeError() {
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
1979
|
+
return badRequestError(serverError({
|
|
1980
|
+
status: 400,
|
|
1981
|
+
code: 'CALL_TYPE_MISSING_ERROR',
|
|
1982
|
+
message: `The call type was missing from the request.`
|
|
1983
|
+
}));
|
|
2025
1984
|
}
|
|
2026
1985
|
function onCallModelUnknownCallTypeError(call) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1986
|
+
return badRequestError(serverError({
|
|
1987
|
+
status: 400,
|
|
1988
|
+
code: 'UNKNOWN_CALL_TYPE_ERROR',
|
|
1989
|
+
message: `Unknown call type "${call}".`,
|
|
1990
|
+
data: {
|
|
1991
|
+
call
|
|
1992
|
+
}
|
|
1993
|
+
}));
|
|
2035
1994
|
}
|
|
2036
1995
|
function _onCallWithCallTypeFunction(map, config) {
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
return crudFn({
|
|
2056
|
-
...request,
|
|
2057
|
-
specifier,
|
|
2058
|
-
data: request.data.data
|
|
2059
|
-
});
|
|
2060
|
-
} else {
|
|
2061
|
-
throw throwOnUnknownModelType(modelType);
|
|
2062
|
-
}
|
|
2063
|
-
};
|
|
1996
|
+
const { callType, crudType, preAssert = () => undefined, throwOnUnknownModelType } = config;
|
|
1997
|
+
return (request) => {
|
|
1998
|
+
const modelType = request.data?.modelType;
|
|
1999
|
+
const crudFn = map[modelType];
|
|
2000
|
+
if (crudFn) {
|
|
2001
|
+
const specifier = request.data.specifier;
|
|
2002
|
+
assertRequestRequiresAuthForFunction(crudFn, request);
|
|
2003
|
+
preAssert({ call: callType, request, modelType, specifier });
|
|
2004
|
+
return crudFn({
|
|
2005
|
+
...request,
|
|
2006
|
+
specifier,
|
|
2007
|
+
data: request.data.data
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
throw throwOnUnknownModelType(modelType);
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2064
2014
|
}
|
|
2065
2015
|
|
|
2066
2016
|
function onCallCreateModel(map, config = {}) {
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
throwOnUnknownModelType: createModelUnknownModelTypeError
|
|
2075
|
-
});
|
|
2017
|
+
const { preAssert } = config;
|
|
2018
|
+
return _onCallWithCallTypeFunction(map, {
|
|
2019
|
+
callType: 'create',
|
|
2020
|
+
crudType: 'create',
|
|
2021
|
+
preAssert,
|
|
2022
|
+
throwOnUnknownModelType: createModelUnknownModelTypeError
|
|
2023
|
+
});
|
|
2076
2024
|
}
|
|
2077
2025
|
function createModelUnknownModelTypeError(modelType) {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2026
|
+
return badRequestError(serverError({
|
|
2027
|
+
status: 400,
|
|
2028
|
+
code: 'UNKNOWN_TYPE_ERROR',
|
|
2029
|
+
message: `Invalid type "${modelType}" to create.`,
|
|
2030
|
+
data: {
|
|
2031
|
+
modelType
|
|
2032
|
+
}
|
|
2033
|
+
}));
|
|
2086
2034
|
}
|
|
2087
2035
|
|
|
2088
2036
|
function onCallReadModel(map, config = {}) {
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
throwOnUnknownModelType: readModelUnknownModelTypeError
|
|
2097
|
-
});
|
|
2037
|
+
const { preAssert } = config;
|
|
2038
|
+
return _onCallWithCallTypeFunction(map, {
|
|
2039
|
+
callType: 'read',
|
|
2040
|
+
crudType: 'read',
|
|
2041
|
+
preAssert,
|
|
2042
|
+
throwOnUnknownModelType: readModelUnknownModelTypeError
|
|
2043
|
+
});
|
|
2098
2044
|
}
|
|
2099
2045
|
function readModelUnknownModelTypeError(modelType) {
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2046
|
+
return badRequestError(serverError({
|
|
2047
|
+
status: 400,
|
|
2048
|
+
code: 'UNKNOWN_TYPE_ERROR',
|
|
2049
|
+
message: 'Invalid type to read.',
|
|
2050
|
+
data: {
|
|
2051
|
+
modelType
|
|
2052
|
+
}
|
|
2053
|
+
}));
|
|
2108
2054
|
}
|
|
2109
2055
|
|
|
2110
2056
|
function onCallUpdateModel(map, config = {}) {
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
throwOnUnknownModelType: updateModelUnknownModelTypeError
|
|
2119
|
-
});
|
|
2057
|
+
const { preAssert } = config;
|
|
2058
|
+
return _onCallWithCallTypeFunction(map, {
|
|
2059
|
+
callType: 'update',
|
|
2060
|
+
crudType: 'update',
|
|
2061
|
+
preAssert,
|
|
2062
|
+
throwOnUnknownModelType: updateModelUnknownModelTypeError
|
|
2063
|
+
});
|
|
2120
2064
|
}
|
|
2121
2065
|
function updateModelUnknownModelTypeError(modelType) {
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2066
|
+
return badRequestError(serverError({
|
|
2067
|
+
status: 400,
|
|
2068
|
+
code: 'UNKNOWN_TYPE_ERROR',
|
|
2069
|
+
message: 'Invalid type to update.',
|
|
2070
|
+
data: {
|
|
2071
|
+
modelType
|
|
2072
|
+
}
|
|
2073
|
+
}));
|
|
2130
2074
|
}
|
|
2131
2075
|
|
|
2132
2076
|
function onCallDeleteModel(map, config = {}) {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
throwOnUnknownModelType: deleteModelUnknownModelTypeError
|
|
2141
|
-
});
|
|
2077
|
+
const { preAssert } = config;
|
|
2078
|
+
return _onCallWithCallTypeFunction(map, {
|
|
2079
|
+
callType: 'delete',
|
|
2080
|
+
crudType: 'delete',
|
|
2081
|
+
preAssert,
|
|
2082
|
+
throwOnUnknownModelType: deleteModelUnknownModelTypeError
|
|
2083
|
+
});
|
|
2142
2084
|
}
|
|
2143
2085
|
function deleteModelUnknownModelTypeError(modelType) {
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2086
|
+
return badRequestError(serverError({
|
|
2087
|
+
status: 400,
|
|
2088
|
+
code: 'UNKNOWN_TYPE_ERROR',
|
|
2089
|
+
message: 'Invalid type to delete.',
|
|
2090
|
+
data: {
|
|
2091
|
+
modelType
|
|
2092
|
+
}
|
|
2093
|
+
}));
|
|
2152
2094
|
}
|
|
2153
2095
|
|
|
2154
2096
|
function googleCloudStorageBucketForStorageFilePath(storage, path) {
|
|
2155
|
-
|
|
2097
|
+
return storage.bucket(path.bucketId);
|
|
2156
2098
|
}
|
|
2157
2099
|
function googleCloudStorageFileForStorageFilePath(storage, path) {
|
|
2158
|
-
|
|
2100
|
+
return googleCloudStorageBucketForStorageFilePath(storage, path).file(path.pathString);
|
|
2159
2101
|
}
|
|
2160
2102
|
function googleCloudFileMetadataToStorageMetadata(file, metadata) {
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
return {
|
|
2167
|
-
bucket: file.bucket.name,
|
|
2168
|
-
fullPath,
|
|
2169
|
-
generation,
|
|
2170
|
-
metageneration,
|
|
2171
|
-
name: file.name,
|
|
2172
|
-
size,
|
|
2173
|
-
timeCreated: metadata.timeCreated,
|
|
2174
|
-
updated: metadata.updated,
|
|
2175
|
-
md5Hash: metadata.md5Hash,
|
|
2176
|
-
cacheControl: metadata.cacheControl,
|
|
2177
|
-
contentDisposition: metadata.contentDisposition,
|
|
2178
|
-
contentEncoding: metadata.contentEncoding,
|
|
2179
|
-
contentLanguage: metadata.contentLanguage,
|
|
2180
|
-
contentType: metadata.contentType,
|
|
2181
|
-
customMetadata
|
|
2182
|
-
};
|
|
2183
|
-
}
|
|
2184
|
-
function googleCloudStorageAccessorFile(storage, storagePath) {
|
|
2185
|
-
const file = googleCloudStorageFileForStorageFilePath(storage, storagePath);
|
|
2186
|
-
function makeDownloadOptions(maxDownloadSizeBytes) {
|
|
2103
|
+
const fullPath = file.name;
|
|
2104
|
+
const generation = String(metadata.generation ?? file.generation);
|
|
2105
|
+
const metageneration = String(metadata.metageneration);
|
|
2106
|
+
const size = Number(metadata.size);
|
|
2107
|
+
const customMetadata = metadata.metadata;
|
|
2187
2108
|
return {
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2109
|
+
bucket: file.bucket.name,
|
|
2110
|
+
fullPath,
|
|
2111
|
+
generation,
|
|
2112
|
+
metageneration,
|
|
2113
|
+
name: file.name,
|
|
2114
|
+
size,
|
|
2115
|
+
timeCreated: metadata.timeCreated,
|
|
2116
|
+
updated: metadata.updated,
|
|
2117
|
+
md5Hash: metadata.md5Hash,
|
|
2118
|
+
cacheControl: metadata.cacheControl,
|
|
2119
|
+
contentDisposition: metadata.contentDisposition,
|
|
2120
|
+
contentEncoding: metadata.contentEncoding,
|
|
2121
|
+
contentLanguage: metadata.contentLanguage,
|
|
2122
|
+
contentType: metadata.contentType,
|
|
2123
|
+
customMetadata
|
|
2192
2124
|
};
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
const
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
metadata: !objectHasNoKeys(customMetadata) ? customMetadata : undefined
|
|
2206
|
-
});
|
|
2207
|
-
}
|
|
2208
|
-
function makeUploadOptions(options) {
|
|
2209
|
-
let metadata;
|
|
2210
|
-
if (options != null) {
|
|
2211
|
-
metadata = _configureMetadata({
|
|
2212
|
-
metadata: {
|
|
2213
|
-
...options.metadata,
|
|
2214
|
-
contentType: options.contentType ?? options.metadata?.contentType
|
|
2215
|
-
},
|
|
2216
|
-
customMetadata: options.customMetadata
|
|
2217
|
-
});
|
|
2125
|
+
}
|
|
2126
|
+
function googleCloudStorageAccessorFile(storage, storagePath) {
|
|
2127
|
+
const file = googleCloudStorageFileForStorageFilePath(storage, storagePath);
|
|
2128
|
+
function makeDownloadOptions(maxDownloadSizeBytes) {
|
|
2129
|
+
return {
|
|
2130
|
+
...(maxDownloadSizeBytes
|
|
2131
|
+
? {
|
|
2132
|
+
// end is inclusive
|
|
2133
|
+
end: maxDownloadSizeBytes - 1
|
|
2134
|
+
}
|
|
2135
|
+
: undefined)
|
|
2136
|
+
};
|
|
2218
2137
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2138
|
+
function _configureMetadata(options) {
|
|
2139
|
+
const customMetadata = filterUndefinedValues({
|
|
2140
|
+
...options.metadata?.customMetadata,
|
|
2141
|
+
...options?.customMetadata
|
|
2142
|
+
});
|
|
2143
|
+
return filterUndefinedValues({
|
|
2144
|
+
cacheControl: options.metadata?.cacheControl,
|
|
2145
|
+
contentDisposition: options.metadata?.contentDisposition,
|
|
2146
|
+
contentEncoding: options.metadata?.contentEncoding,
|
|
2147
|
+
contentLanguage: options.metadata?.contentLanguage,
|
|
2148
|
+
contentType: options.metadata?.contentType,
|
|
2149
|
+
metadata: !objectHasNoKeys(customMetadata) ? customMetadata : undefined
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
function makeUploadOptions(options) {
|
|
2153
|
+
let metadata;
|
|
2154
|
+
if (options != null) {
|
|
2155
|
+
metadata = _configureMetadata({
|
|
2156
|
+
metadata: {
|
|
2157
|
+
...options.metadata,
|
|
2158
|
+
contentType: options.contentType ?? options.metadata?.contentType
|
|
2159
|
+
},
|
|
2160
|
+
customMetadata: options.customMetadata
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
return {
|
|
2164
|
+
// non-resumable
|
|
2165
|
+
resumable: false,
|
|
2166
|
+
// add content type and other custom metadata
|
|
2167
|
+
...(metadata ? { metadata } : undefined)
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
function asFileMetadata(metadata) {
|
|
2171
|
+
return _configureMetadata({ metadata });
|
|
2172
|
+
}
|
|
2173
|
+
function makeStoragePathForPath(newPath) {
|
|
2174
|
+
let path;
|
|
2175
|
+
if (typeof newPath === 'string') {
|
|
2176
|
+
path = {
|
|
2177
|
+
bucketId: file.bucket.name,
|
|
2178
|
+
pathString: newPath
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
else {
|
|
2182
|
+
path = newPath;
|
|
2183
|
+
}
|
|
2184
|
+
return path;
|
|
2185
|
+
}
|
|
2186
|
+
async function copy(newPath, options) {
|
|
2187
|
+
const newStoragePath = makeStoragePathForPath(newPath);
|
|
2188
|
+
const newFile = googleCloudStorageAccessorFile(storage, newStoragePath);
|
|
2189
|
+
return _copyWithFile(newFile, options);
|
|
2190
|
+
}
|
|
2191
|
+
async function _copyWithFile(newFile, options) {
|
|
2192
|
+
const copyOptions = {
|
|
2193
|
+
...options
|
|
2194
|
+
};
|
|
2195
|
+
await file.copy(newFile.reference, copyOptions);
|
|
2196
|
+
return newFile;
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Configuration for the public ACL.
|
|
2200
|
+
*/
|
|
2201
|
+
const PUBLIC_ACL = {
|
|
2202
|
+
entity: 'allUsers',
|
|
2203
|
+
role: 'READER'
|
|
2226
2204
|
};
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2205
|
+
const accessorFile = {
|
|
2206
|
+
reference: file,
|
|
2207
|
+
storagePath,
|
|
2208
|
+
exists: () => file.exists().then((x) => x[0]),
|
|
2209
|
+
getDownloadUrl: () => file.getMetadata().then(() => file.publicUrl()),
|
|
2210
|
+
getSignedUrl: async (input) => {
|
|
2211
|
+
const expires = input?.expiresAt ??
|
|
2212
|
+
(input?.expiresIn != null
|
|
2213
|
+
? addMilliseconds(new Date(), input.expiresIn) // use expiresIn if provided
|
|
2214
|
+
: addHours(new Date(), 1)); // default expiration in 1 hour
|
|
2215
|
+
const config = {
|
|
2216
|
+
...input,
|
|
2217
|
+
action: input?.action ?? 'read',
|
|
2218
|
+
expires,
|
|
2219
|
+
expiresIn: undefined, // clear from input
|
|
2220
|
+
expiresAt: undefined
|
|
2221
|
+
};
|
|
2222
|
+
return file
|
|
2223
|
+
.getSignedUrl(config)
|
|
2224
|
+
.then((x) => x[0])
|
|
2225
|
+
.catch((e) => {
|
|
2226
|
+
let publicUrlBackup;
|
|
2227
|
+
if (e && e.name === 'SigningError' && (isTestNodeEnv() || process.env.FIREBASE_STORAGE_EMULATOR_HOST)) {
|
|
2228
|
+
// NOTE: Signing does not behave properly in the emulator as it is not supported.
|
|
2229
|
+
// https://github.com/firebase/firebase-tools/issues/3400
|
|
2230
|
+
// we can return the public url instead.
|
|
2231
|
+
// This is fine, as in production this file url is protected by ACLs anyways.
|
|
2232
|
+
publicUrlBackup = file.publicUrl();
|
|
2233
|
+
}
|
|
2234
|
+
else {
|
|
2235
|
+
throw e;
|
|
2236
|
+
}
|
|
2237
|
+
return publicUrlBackup;
|
|
2238
|
+
});
|
|
2239
|
+
},
|
|
2240
|
+
getMetadata: () => file.getMetadata().then((x) => googleCloudFileMetadataToStorageMetadata(file, x[0])),
|
|
2241
|
+
setMetadata: (metadata) => file.setMetadata(asFileMetadata(metadata)).then((x) => googleCloudFileMetadataToStorageMetadata(file, x[0])),
|
|
2242
|
+
getBytes: (maxDownloadSizeBytes) => file.download(makeDownloadOptions(maxDownloadSizeBytes)).then((x) => x[0]),
|
|
2243
|
+
getStream: (maxDownloadSizeBytes) => file.createReadStream(makeDownloadOptions(maxDownloadSizeBytes)),
|
|
2244
|
+
upload: async (input, options) => {
|
|
2245
|
+
let dataToUpload;
|
|
2246
|
+
if (typeof input === 'string') {
|
|
2247
|
+
const parsedStringFormat = assertStorageUploadOptionsStringFormat(options);
|
|
2248
|
+
const stringFormat = parsedStringFormat === 'raw' ? 'utf-8' : parsedStringFormat;
|
|
2249
|
+
if (stringFormat === 'data_url') {
|
|
2250
|
+
// TODO(FUTURE): support this later if necessary. Server should really never see this type.
|
|
2251
|
+
throw new Error('"data_url" is unsupported.');
|
|
2252
|
+
}
|
|
2253
|
+
dataToUpload = Buffer.from(input, stringFormat);
|
|
2254
|
+
}
|
|
2255
|
+
else {
|
|
2256
|
+
if (Buffer.isBuffer(input)) {
|
|
2257
|
+
dataToUpload = input;
|
|
2258
|
+
}
|
|
2259
|
+
else if (isUint8Array(input)) {
|
|
2260
|
+
dataToUpload = Buffer.from(input);
|
|
2261
|
+
}
|
|
2262
|
+
else {
|
|
2263
|
+
// NOTE: these values shouldn't ever be encountered in the NodeJS environment. May remove later.
|
|
2264
|
+
if (isArrayBuffer(input)) {
|
|
2265
|
+
dataToUpload = Buffer.from(input);
|
|
2266
|
+
}
|
|
2267
|
+
else {
|
|
2268
|
+
dataToUpload = input.arrayBuffer().then((x) => Buffer.from(x));
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
const data = await dataToUpload;
|
|
2273
|
+
return file.save(data, makeUploadOptions(options));
|
|
2274
|
+
},
|
|
2275
|
+
uploadStream: (options) => file.createWriteStream(makeUploadOptions(options)),
|
|
2276
|
+
move: async (newPath, options) => {
|
|
2277
|
+
const newStoragePath = makeStoragePathForPath(newPath);
|
|
2278
|
+
const newFile = googleCloudStorageAccessorFile(storage, newStoragePath);
|
|
2279
|
+
const moveOptions = {
|
|
2280
|
+
...options
|
|
2281
|
+
};
|
|
2282
|
+
await file.moveFileAtomic(newFile.reference, moveOptions).catch(async (e) => {
|
|
2283
|
+
if (e instanceof ApiError && e.response?.statusMessage === 'Not Implemented') {
|
|
2284
|
+
// NOTE: This is not implemented in storage emulator, so it will fail with this error in testing.
|
|
2285
|
+
// https://github.com/firebase/firebase-tools/issues/3751
|
|
2286
|
+
// we can perform the same task using copy and then deleting this file.
|
|
2287
|
+
await copy(newPath, moveOptions);
|
|
2288
|
+
await accessorFile.delete();
|
|
2289
|
+
}
|
|
2290
|
+
else {
|
|
2291
|
+
throw e;
|
|
2292
|
+
}
|
|
2293
|
+
});
|
|
2294
|
+
return newFile;
|
|
2295
|
+
},
|
|
2296
|
+
copy,
|
|
2297
|
+
delete: (options) => file.delete(options).then((x) => undefined),
|
|
2298
|
+
isPublic: () => file.isPublic().then((x) => x[0]),
|
|
2299
|
+
makePublic: (setPublic) => (setPublic !== false ? file.acl.add(PUBLIC_ACL) : file.acl.delete({ entity: PUBLIC_ACL.entity })).then(() => undefined),
|
|
2300
|
+
makePrivate: (options) => file.makePrivate(options).then(() => undefined),
|
|
2301
|
+
getAcls: (options) => file.acl.get(options).then((x) => ({ acls: x[0], metadata: x[1] }))
|
|
2253
2302
|
};
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
*/
|
|
2260
|
-
const PUBLIC_ACL = {
|
|
2261
|
-
entity: 'allUsers',
|
|
2262
|
-
role: 'READER'
|
|
2263
|
-
};
|
|
2264
|
-
const accessorFile = {
|
|
2265
|
-
reference: file,
|
|
2266
|
-
storagePath,
|
|
2267
|
-
exists: () => file.exists().then(x => x[0]),
|
|
2268
|
-
getDownloadUrl: () => file.getMetadata().then(() => file.publicUrl()),
|
|
2269
|
-
getSignedUrl: async input => {
|
|
2270
|
-
const expires = input?.expiresAt ?? (input?.expiresIn != null ? addMilliseconds(new Date(), input.expiresIn) // use expiresIn if provided
|
|
2271
|
-
: addHours(new Date(), 1)); // default expiration in 1 hour
|
|
2272
|
-
const config = {
|
|
2273
|
-
...input,
|
|
2274
|
-
action: input?.action ?? 'read',
|
|
2275
|
-
expires,
|
|
2276
|
-
expiresIn: undefined,
|
|
2277
|
-
// clear from input
|
|
2278
|
-
expiresAt: undefined
|
|
2279
|
-
};
|
|
2280
|
-
return file.getSignedUrl(config).then(x => x[0]).catch(e => {
|
|
2281
|
-
let publicUrlBackup;
|
|
2282
|
-
if (e && e.name === 'SigningError' && (isTestNodeEnv() || process.env.FIREBASE_STORAGE_EMULATOR_HOST)) {
|
|
2283
|
-
// NOTE: Signing does not behave properly in the emulator as it is not supported.
|
|
2284
|
-
// https://github.com/firebase/firebase-tools/issues/3400
|
|
2285
|
-
// we can return the public url instead.
|
|
2286
|
-
// This is fine, as in production this file url is protected by ACLs anyways.
|
|
2287
|
-
publicUrlBackup = file.publicUrl();
|
|
2288
|
-
} else {
|
|
2289
|
-
throw e;
|
|
2290
|
-
}
|
|
2291
|
-
return publicUrlBackup;
|
|
2292
|
-
});
|
|
2303
|
+
return accessorFile;
|
|
2304
|
+
}
|
|
2305
|
+
const googleCloudStorageListFilesResultFactory = storageListFilesResultFactory({
|
|
2306
|
+
hasItems(result) {
|
|
2307
|
+
return Boolean(result.apiResponse.items || result.apiResponse.prefixes);
|
|
2293
2308
|
},
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
getBytes: maxDownloadSizeBytes => file.download(makeDownloadOptions(maxDownloadSizeBytes)).then(x => x[0]),
|
|
2297
|
-
getStream: maxDownloadSizeBytes => file.createReadStream(makeDownloadOptions(maxDownloadSizeBytes)),
|
|
2298
|
-
upload: async (input, options) => {
|
|
2299
|
-
let dataToUpload;
|
|
2300
|
-
if (typeof input === 'string') {
|
|
2301
|
-
const parsedStringFormat = assertStorageUploadOptionsStringFormat(options);
|
|
2302
|
-
const stringFormat = parsedStringFormat === 'raw' ? 'utf-8' : parsedStringFormat;
|
|
2303
|
-
if (stringFormat === 'data_url') {
|
|
2304
|
-
// TODO(FUTURE): support this later if necessary. Server should really never see this type.
|
|
2305
|
-
throw new Error('"data_url" is unsupported.');
|
|
2306
|
-
}
|
|
2307
|
-
dataToUpload = Buffer.from(input, stringFormat);
|
|
2308
|
-
} else {
|
|
2309
|
-
if (Buffer.isBuffer(input)) {
|
|
2310
|
-
dataToUpload = input;
|
|
2311
|
-
} else if (isUint8Array(input)) {
|
|
2312
|
-
dataToUpload = Buffer.from(input);
|
|
2313
|
-
} else {
|
|
2314
|
-
// NOTE: these values shouldn't ever be encountered in the NodeJS environment. May remove later.
|
|
2315
|
-
if (isArrayBuffer(input)) {
|
|
2316
|
-
dataToUpload = Buffer.from(input);
|
|
2317
|
-
} else {
|
|
2318
|
-
dataToUpload = input.arrayBuffer().then(x => Buffer.from(x));
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
const data = await dataToUpload;
|
|
2323
|
-
return file.save(data, makeUploadOptions(options));
|
|
2309
|
+
hasNext: (result) => {
|
|
2310
|
+
return result.nextQuery != null;
|
|
2324
2311
|
},
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
const newStoragePath = makeStoragePathForPath(newPath);
|
|
2328
|
-
const newFile = googleCloudStorageAccessorFile(storage, newStoragePath);
|
|
2329
|
-
const moveOptions = {
|
|
2330
|
-
...options
|
|
2331
|
-
};
|
|
2332
|
-
await file.moveFileAtomic(newFile.reference, moveOptions).catch(async e => {
|
|
2333
|
-
if (e instanceof ApiError && e.response?.statusMessage === 'Not Implemented') {
|
|
2334
|
-
// NOTE: This is not implemented in storage emulator, so it will fail with this error in testing.
|
|
2335
|
-
// https://github.com/firebase/firebase-tools/issues/3751
|
|
2336
|
-
// we can perform the same task using copy and then deleting this file.
|
|
2337
|
-
await copy(newPath, moveOptions);
|
|
2338
|
-
await accessorFile.delete();
|
|
2339
|
-
} else {
|
|
2340
|
-
throw e;
|
|
2341
|
-
}
|
|
2342
|
-
});
|
|
2343
|
-
return newFile;
|
|
2312
|
+
nextPageTokenFromResult(result) {
|
|
2313
|
+
return result.nextQuery?.pageToken;
|
|
2344
2314
|
},
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
const
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
},
|
|
2363
|
-
hasNext: result => {
|
|
2364
|
-
return result.nextQuery != null;
|
|
2365
|
-
},
|
|
2366
|
-
nextPageTokenFromResult(result) {
|
|
2367
|
-
return result.nextQuery?.pageToken;
|
|
2368
|
-
},
|
|
2369
|
-
next(storage, options, folder, result) {
|
|
2370
|
-
return folder.list({
|
|
2371
|
-
...options,
|
|
2372
|
-
...result.nextQuery
|
|
2373
|
-
});
|
|
2374
|
-
},
|
|
2375
|
-
file(storage, fileResult) {
|
|
2376
|
-
return googleCloudStorageAccessorFile(storage, fileResult.storagePath);
|
|
2377
|
-
},
|
|
2378
|
-
folder(storage, folderResult) {
|
|
2379
|
-
return googleCloudStorageAccessorFolder(storage, folderResult.storagePath);
|
|
2380
|
-
},
|
|
2381
|
-
filesFromResult(result) {
|
|
2382
|
-
const items = result.apiResponse?.items ?? [];
|
|
2383
|
-
return items.map(x => ({
|
|
2384
|
-
raw: x,
|
|
2385
|
-
name: slashPathName(x.name),
|
|
2386
|
-
storagePath: {
|
|
2387
|
-
bucketId: x.bucket,
|
|
2388
|
-
pathString: x.name
|
|
2389
|
-
}
|
|
2390
|
-
}));
|
|
2391
|
-
},
|
|
2392
|
-
foldersFromResult(result, folder) {
|
|
2393
|
-
const items = result.apiResponse?.prefixes ?? [];
|
|
2394
|
-
return items.map(prefix => ({
|
|
2395
|
-
raw: prefix,
|
|
2396
|
-
name: slashPathName(prefix),
|
|
2397
|
-
storagePath: {
|
|
2398
|
-
bucketId: folder.storagePath.bucketId,
|
|
2399
|
-
pathString: prefix
|
|
2400
|
-
}
|
|
2401
|
-
}));
|
|
2402
|
-
}
|
|
2315
|
+
next(storage, options, folder, result) {
|
|
2316
|
+
return folder.list({ ...options, ...result.nextQuery });
|
|
2317
|
+
},
|
|
2318
|
+
file(storage, fileResult) {
|
|
2319
|
+
return googleCloudStorageAccessorFile(storage, fileResult.storagePath);
|
|
2320
|
+
},
|
|
2321
|
+
folder(storage, folderResult) {
|
|
2322
|
+
return googleCloudStorageAccessorFolder(storage, folderResult.storagePath);
|
|
2323
|
+
},
|
|
2324
|
+
filesFromResult(result) {
|
|
2325
|
+
const items = result.apiResponse?.items ?? [];
|
|
2326
|
+
return items.map((x) => ({ raw: x, name: slashPathName(x.name), storagePath: { bucketId: x.bucket, pathString: x.name } }));
|
|
2327
|
+
},
|
|
2328
|
+
foldersFromResult(result, folder) {
|
|
2329
|
+
const items = result.apiResponse?.prefixes ?? [];
|
|
2330
|
+
return items.map((prefix) => ({ raw: prefix, name: slashPathName(prefix), storagePath: { bucketId: folder.storagePath.bucketId, pathString: prefix } }));
|
|
2331
|
+
}
|
|
2403
2332
|
});
|
|
2404
2333
|
function googleCloudStorageAccessorFolder(storage, storagePath) {
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
});
|
|
2443
|
-
}
|
|
2444
|
-
};
|
|
2445
|
-
return folder;
|
|
2334
|
+
const bucket = googleCloudStorageBucketForStorageFilePath(storage, storagePath);
|
|
2335
|
+
const file = bucket.file(storagePath.pathString);
|
|
2336
|
+
const folder = {
|
|
2337
|
+
reference: file,
|
|
2338
|
+
storagePath,
|
|
2339
|
+
exists: async () => folder.list({ maxResults: 1 }).then((x) => x.hasItems()),
|
|
2340
|
+
list: (options) => {
|
|
2341
|
+
const { maxResults, pageToken, includeNestedResults: listAll } = options ?? {};
|
|
2342
|
+
const listOptions = {
|
|
2343
|
+
maxResults,
|
|
2344
|
+
pageToken,
|
|
2345
|
+
autoPaginate: false,
|
|
2346
|
+
versions: false,
|
|
2347
|
+
...(listAll
|
|
2348
|
+
? {
|
|
2349
|
+
prefix: toRelativeSlashPathStartType(fixMultiSlashesInSlashPath(storagePath.pathString + '/'))
|
|
2350
|
+
}
|
|
2351
|
+
: {
|
|
2352
|
+
// includeTrailingDelimiter: true,
|
|
2353
|
+
delimiter: SLASH_PATH_SEPARATOR,
|
|
2354
|
+
prefix: toRelativeSlashPathStartType(fixMultiSlashesInSlashPath(storagePath.pathString + '/')) // make sure the folder always ends with a slash
|
|
2355
|
+
})
|
|
2356
|
+
};
|
|
2357
|
+
return bucket.getFiles(listOptions).then((x) => {
|
|
2358
|
+
const files = x[0];
|
|
2359
|
+
const nextQuery = x[1];
|
|
2360
|
+
const apiResponse = x[2];
|
|
2361
|
+
const result = {
|
|
2362
|
+
files: files,
|
|
2363
|
+
nextQuery,
|
|
2364
|
+
apiResponse: apiResponse
|
|
2365
|
+
};
|
|
2366
|
+
return googleCloudStorageListFilesResultFactory(storage, folder, options, result);
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
};
|
|
2370
|
+
return folder;
|
|
2446
2371
|
}
|
|
2447
2372
|
function googleCloudStorageFirebaseStorageAccessorDriver() {
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2373
|
+
return {
|
|
2374
|
+
type: 'server',
|
|
2375
|
+
file: (storage, path) => googleCloudStorageAccessorFile(storage, path),
|
|
2376
|
+
folder: (storage, path) => googleCloudStorageAccessorFolder(storage, path)
|
|
2377
|
+
};
|
|
2453
2378
|
}
|
|
2454
2379
|
|
|
2455
2380
|
function googleCloudFirebaseStorageDrivers() {
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2381
|
+
return {
|
|
2382
|
+
storageDriverIdentifier: '@google-cloud/storage',
|
|
2383
|
+
storageDriverType: 'production',
|
|
2384
|
+
storageAccessorDriver: googleCloudStorageFirebaseStorageAccessorDriver()
|
|
2385
|
+
};
|
|
2461
2386
|
}
|
|
2462
2387
|
|
|
2463
2388
|
/**
|
|
2464
2389
|
* Basic service that implements FirebaseStorageAccessor and provides a FirebaseStorageContext.
|
|
2465
2390
|
*/
|
|
2466
2391
|
class FirebaseServerStorageService {
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2392
|
+
_storageContext;
|
|
2393
|
+
constructor(storageContext) {
|
|
2394
|
+
this._storageContext = storageContext;
|
|
2395
|
+
}
|
|
2396
|
+
get storageContext() {
|
|
2397
|
+
return this._storageContext;
|
|
2398
|
+
}
|
|
2399
|
+
defaultBucket() {
|
|
2400
|
+
return this.storageContext.defaultBucket();
|
|
2401
|
+
}
|
|
2402
|
+
file(path) {
|
|
2403
|
+
return this.storageContext.file(path);
|
|
2404
|
+
}
|
|
2405
|
+
folder(path) {
|
|
2406
|
+
return this.storageContext.folder(path);
|
|
2407
|
+
}
|
|
2483
2408
|
}
|
|
2484
2409
|
|
|
2485
2410
|
/**
|
|
@@ -2493,7 +2418,7 @@ const googleCloudFirebaseStorageContextFactory = firebaseStorageContextFactory(g
|
|
|
2493
2418
|
* @returns
|
|
2494
2419
|
*/
|
|
2495
2420
|
function googleCloudStorageFromFirebaseAdminStorage(storage) {
|
|
2496
|
-
|
|
2421
|
+
return storage.storageClient;
|
|
2497
2422
|
}
|
|
2498
2423
|
|
|
2499
2424
|
// MARK: Tokens
|
|
@@ -2512,59 +2437,69 @@ const FIREBASE_STORAGE_CONTEXT_FACTORY_CONFIG_TOKEN = 'FIREBASE_STORAGE_CONTEXT_
|
|
|
2512
2437
|
/**
|
|
2513
2438
|
* Nest provider module for Firebase that provides a firestore, etc. from the firestore token.
|
|
2514
2439
|
*/
|
|
2515
|
-
let FirebaseServerStorageModule = class FirebaseServerStorageModule {
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2440
|
+
let FirebaseServerStorageModule = class FirebaseServerStorageModule {
|
|
2441
|
+
};
|
|
2442
|
+
FirebaseServerStorageModule = __decorate([
|
|
2443
|
+
Module({
|
|
2444
|
+
providers: [
|
|
2445
|
+
{
|
|
2446
|
+
provide: FIREBASE_STORAGE_TOKEN,
|
|
2447
|
+
useFactory: (app) => googleCloudStorageFromFirebaseAdminStorage(app.storage()),
|
|
2448
|
+
inject: [FIREBASE_APP_TOKEN]
|
|
2449
|
+
}
|
|
2450
|
+
],
|
|
2451
|
+
exports: [FIREBASE_STORAGE_TOKEN]
|
|
2452
|
+
})
|
|
2453
|
+
], FirebaseServerStorageModule);
|
|
2524
2454
|
/**
|
|
2525
2455
|
* Nest provider module for firebase that includes the FirebaseServerStorageModule and provides a value for STORAGE_CONTEXT_TOKEN using the googleCloudStorageContextFactory.
|
|
2526
2456
|
*/
|
|
2527
|
-
let FirebaseServerStorageContextModule = class FirebaseServerStorageContextModule {
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2457
|
+
let FirebaseServerStorageContextModule = class FirebaseServerStorageContextModule {
|
|
2458
|
+
};
|
|
2459
|
+
FirebaseServerStorageContextModule = __decorate([
|
|
2460
|
+
Module({
|
|
2461
|
+
imports: [FirebaseServerStorageModule],
|
|
2462
|
+
providers: [
|
|
2463
|
+
{
|
|
2464
|
+
provide: FIREBASE_STORAGE_CONTEXT_TOKEN,
|
|
2465
|
+
useFactory: googleCloudFirebaseStorageContextFactory,
|
|
2466
|
+
inject: [FIREBASE_STORAGE_TOKEN, FIREBASE_STORAGE_CONTEXT_FACTORY_CONFIG_TOKEN]
|
|
2467
|
+
}
|
|
2468
|
+
],
|
|
2469
|
+
exports: [FirebaseServerStorageModule, FIREBASE_STORAGE_CONTEXT_TOKEN]
|
|
2470
|
+
})
|
|
2471
|
+
], FirebaseServerStorageContextModule);
|
|
2537
2472
|
// MARK: Token Configuration
|
|
2538
2473
|
function firebaseServerStorageDefaultBucketIdTokenProvider(input) {
|
|
2539
|
-
|
|
2540
|
-
defaultBucketId
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
useValue: config
|
|
2548
|
-
};
|
|
2474
|
+
const config = typeof input === 'string' ? { defaultBucketId: input } : input;
|
|
2475
|
+
if (!config.defaultBucketId) {
|
|
2476
|
+
throw new Error('Non-empty defaultBucketId is required.');
|
|
2477
|
+
}
|
|
2478
|
+
return {
|
|
2479
|
+
provide: FIREBASE_STORAGE_CONTEXT_FACTORY_CONFIG_TOKEN,
|
|
2480
|
+
useValue: config
|
|
2481
|
+
};
|
|
2549
2482
|
}
|
|
2550
2483
|
function defaultProvideFirebaseServerStorageServiceSimple() {
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2484
|
+
return {
|
|
2485
|
+
provide: FirebaseServerStorageService,
|
|
2486
|
+
useFactory: (context) => new FirebaseServerStorageService(context)
|
|
2487
|
+
};
|
|
2555
2488
|
}
|
|
2556
2489
|
function provideFirebaseServerStorageService(provider) {
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2490
|
+
const providers = [
|
|
2491
|
+
{
|
|
2492
|
+
...provider,
|
|
2493
|
+
inject: provider.inject ?? [FIREBASE_STORAGE_CONTEXT_TOKEN]
|
|
2494
|
+
}
|
|
2495
|
+
];
|
|
2496
|
+
if (provider.provide !== FirebaseServerStorageService) {
|
|
2497
|
+
providers.push({
|
|
2498
|
+
provide: FirebaseServerStorageService,
|
|
2499
|
+
useExisting: provider.provide
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
2502
|
+
return providers;
|
|
2568
2503
|
}
|
|
2569
2504
|
/**
|
|
2570
2505
|
* Convenience function used to generate ModuleMetadata for an app's Auth related modules and FirebaseServerStorageService provider.
|
|
@@ -2574,145 +2509,133 @@ function provideFirebaseServerStorageService(provider) {
|
|
|
2574
2509
|
* @returns
|
|
2575
2510
|
*/
|
|
2576
2511
|
function firebaseServerStorageModuleMetadata(config) {
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2512
|
+
const serviceProvider = config && config.serviceProvider ? config.serviceProvider : defaultProvideFirebaseServerStorageServiceSimple();
|
|
2513
|
+
const providers = provideFirebaseServerStorageService(serviceProvider);
|
|
2514
|
+
const tokensToExport = injectionTokensFromProviders(providers);
|
|
2515
|
+
return mergeModuleMetadata({
|
|
2516
|
+
imports: [FirebaseServerStorageContextModule],
|
|
2517
|
+
exports: [FirebaseServerStorageContextModule, ...tokensToExport],
|
|
2518
|
+
providers
|
|
2519
|
+
}, config);
|
|
2585
2520
|
}
|
|
2586
2521
|
|
|
2587
|
-
class FirebaseNestServerRootModule {
|
|
2522
|
+
class FirebaseNestServerRootModule {
|
|
2523
|
+
}
|
|
2588
2524
|
function nestServerInstance(config) {
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2525
|
+
const { moduleClass, providers: additionalProviders, defaultStorageBucket: inputDefaultStorageBucket, forceStorageBucket, globalApiRoutePrefix, configureNestServerInstance } = config;
|
|
2526
|
+
const serversCache = new Map();
|
|
2527
|
+
const initNestServer = (firebaseApp, env) => {
|
|
2528
|
+
const appName = firebaseApp.name;
|
|
2529
|
+
const defaultStorageBucket = inputDefaultStorageBucket ?? firebaseApp.options.storageBucket;
|
|
2530
|
+
let nestServer = serversCache.get(appName);
|
|
2531
|
+
if (!nestServer) {
|
|
2532
|
+
const server = express();
|
|
2533
|
+
const createNestServer = async (expressInstance) => {
|
|
2534
|
+
const providers = [firebaseServerAppTokenProvider(asGetter(firebaseApp))];
|
|
2535
|
+
// configure environment providers
|
|
2536
|
+
if (env?.environment != null) {
|
|
2537
|
+
providers.push(serverEnvTokenProvider(env.environment));
|
|
2538
|
+
if (config.configureEnvService !== false) {
|
|
2539
|
+
providers.push({
|
|
2540
|
+
provide: FirebaseServerEnvService,
|
|
2541
|
+
useClass: DefaultFirebaseServerEnvService
|
|
2542
|
+
}, {
|
|
2543
|
+
provide: ServerEnvironmentService,
|
|
2544
|
+
useExisting: FirebaseServerEnvService
|
|
2545
|
+
});
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
if (additionalProviders) {
|
|
2549
|
+
pushItemOrArrayItemsIntoArray(providers, additionalProviders);
|
|
2550
|
+
}
|
|
2551
|
+
const imports = [moduleClass];
|
|
2552
|
+
// NOTE: https://cloud.google.com/functions/docs/writing/http#parsing_http_requests
|
|
2553
|
+
const options = { bodyParser: false }; // firebase already parses the requests
|
|
2554
|
+
if (config.configureWebhooks) {
|
|
2555
|
+
imports.push(ConfigureFirebaseWebhookMiddlewareModule);
|
|
2556
|
+
}
|
|
2557
|
+
if (config.appCheckEnabled != false) {
|
|
2558
|
+
imports.push(ConfigureFirebaseAppCheckMiddlewareModule);
|
|
2559
|
+
}
|
|
2560
|
+
if (defaultStorageBucket) {
|
|
2561
|
+
providers.push(firebaseServerStorageDefaultBucketIdTokenProvider({
|
|
2562
|
+
defaultBucketId: defaultStorageBucket,
|
|
2563
|
+
forceBucket: forceStorageBucket
|
|
2564
|
+
}));
|
|
2565
|
+
}
|
|
2566
|
+
// provide the global prefix config to the app
|
|
2567
|
+
providers.push({
|
|
2568
|
+
provide: GlobalRoutePrefixConfig,
|
|
2569
|
+
useValue: {
|
|
2570
|
+
globalApiRoutePrefix
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
const providersModule = {
|
|
2574
|
+
module: FirebaseNestServerRootModule,
|
|
2575
|
+
imports,
|
|
2576
|
+
providers,
|
|
2577
|
+
exports: providers,
|
|
2578
|
+
global: true
|
|
2579
|
+
};
|
|
2580
|
+
let nestApp = await NestFactory.create(providersModule, new ExpressAdapter(expressInstance), options);
|
|
2581
|
+
if (globalApiRoutePrefix) {
|
|
2582
|
+
nestApp = nestApp.setGlobalPrefix(globalApiRoutePrefix);
|
|
2583
|
+
}
|
|
2584
|
+
if (configureNestServerInstance) {
|
|
2585
|
+
nestApp = configureNestServerInstance(nestApp) || nestApp;
|
|
2586
|
+
}
|
|
2587
|
+
return nestApp.init();
|
|
2588
|
+
};
|
|
2589
|
+
const nest = createNestServer(server).catch((err) => {
|
|
2590
|
+
console.error('Nest failed startup.', err);
|
|
2591
|
+
throw err;
|
|
2616
2592
|
});
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
if (additionalProviders) {
|
|
2620
|
-
pushItemOrArrayItemsIntoArray(providers, additionalProviders);
|
|
2621
|
-
}
|
|
2622
|
-
const imports = [moduleClass];
|
|
2623
|
-
// NOTE: https://cloud.google.com/functions/docs/writing/http#parsing_http_requests
|
|
2624
|
-
const options = {
|
|
2625
|
-
bodyParser: false
|
|
2626
|
-
}; // firebase already parses the requests
|
|
2627
|
-
if (config.configureWebhooks) {
|
|
2628
|
-
imports.push(ConfigureFirebaseWebhookMiddlewareModule);
|
|
2593
|
+
nestServer = { server, nest: makeGetter(nest) };
|
|
2594
|
+
serversCache.set(appName, nestServer);
|
|
2629
2595
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2596
|
+
return nestServer;
|
|
2597
|
+
};
|
|
2598
|
+
const removeNestServer = async (firebaseApp) => {
|
|
2599
|
+
const appName = firebaseApp.name;
|
|
2600
|
+
const nestServer = serversCache.get(appName);
|
|
2601
|
+
let removed;
|
|
2602
|
+
if (nestServer) {
|
|
2603
|
+
removed = nestServer.nest().then((x) => {
|
|
2604
|
+
serversCache.delete(appName);
|
|
2605
|
+
return x.close().then(() => true);
|
|
2606
|
+
});
|
|
2632
2607
|
}
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
defaultBucketId: defaultStorageBucket,
|
|
2636
|
-
forceBucket: forceStorageBucket
|
|
2637
|
-
}));
|
|
2608
|
+
else {
|
|
2609
|
+
removed = Promise.resolve(false);
|
|
2638
2610
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
const providersModule = {
|
|
2647
|
-
module: FirebaseNestServerRootModule,
|
|
2648
|
-
imports,
|
|
2649
|
-
providers,
|
|
2650
|
-
exports: providers,
|
|
2651
|
-
global: true
|
|
2652
|
-
};
|
|
2653
|
-
let nestApp = await NestFactory.create(providersModule, new ExpressAdapter(expressInstance), options);
|
|
2654
|
-
if (globalApiRoutePrefix) {
|
|
2655
|
-
nestApp = nestApp.setGlobalPrefix(globalApiRoutePrefix);
|
|
2656
|
-
}
|
|
2657
|
-
if (configureNestServerInstance) {
|
|
2658
|
-
nestApp = configureNestServerInstance(nestApp) || nestApp;
|
|
2659
|
-
}
|
|
2660
|
-
return nestApp.init();
|
|
2661
|
-
};
|
|
2662
|
-
const nest = createNestServer(server).catch(err => {
|
|
2663
|
-
console.error('Nest failed startup.', err);
|
|
2664
|
-
throw err;
|
|
2665
|
-
});
|
|
2666
|
-
nestServer = {
|
|
2667
|
-
server,
|
|
2668
|
-
nest: makeGetter(nest)
|
|
2669
|
-
};
|
|
2670
|
-
serversCache.set(appName, nestServer);
|
|
2671
|
-
}
|
|
2672
|
-
return nestServer;
|
|
2673
|
-
};
|
|
2674
|
-
const removeNestServer = async firebaseApp => {
|
|
2675
|
-
const appName = firebaseApp.name;
|
|
2676
|
-
const nestServer = serversCache.get(appName);
|
|
2677
|
-
let removed;
|
|
2678
|
-
if (nestServer) {
|
|
2679
|
-
removed = nestServer.nest().then(x => {
|
|
2680
|
-
serversCache.delete(appName);
|
|
2681
|
-
return x.close().then(() => true);
|
|
2682
|
-
});
|
|
2683
|
-
} else {
|
|
2684
|
-
removed = Promise.resolve(false);
|
|
2685
|
-
}
|
|
2686
|
-
return removed;
|
|
2687
|
-
};
|
|
2688
|
-
return {
|
|
2689
|
-
moduleClass,
|
|
2690
|
-
initNestServer,
|
|
2691
|
-
removeNestServer
|
|
2692
|
-
};
|
|
2611
|
+
return removed;
|
|
2612
|
+
};
|
|
2613
|
+
return {
|
|
2614
|
+
moduleClass,
|
|
2615
|
+
initNestServer,
|
|
2616
|
+
removeNestServer
|
|
2617
|
+
};
|
|
2693
2618
|
}
|
|
2694
2619
|
|
|
2695
2620
|
/**
|
|
2696
2621
|
* Abstract class that wraps an INestApplicationContext value.
|
|
2697
2622
|
*/
|
|
2698
2623
|
class AbstractNestContext {
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2624
|
+
_nest;
|
|
2625
|
+
constructor(nest) {
|
|
2626
|
+
this._nest = nest;
|
|
2627
|
+
}
|
|
2628
|
+
get nest() {
|
|
2629
|
+
return this._nest;
|
|
2630
|
+
}
|
|
2706
2631
|
}
|
|
2707
2632
|
class AbstractFirebaseNestContext extends AbstractNestContext {
|
|
2708
|
-
constructor(...args) {
|
|
2709
|
-
super(...args);
|
|
2710
2633
|
/**
|
|
2711
2634
|
* FirebasePermissionErrorContextErrorFunction to use with makeModelContext().
|
|
2712
2635
|
*
|
|
2713
2636
|
* Defaults to nestFirebaseForbiddenPermissionError().
|
|
2714
2637
|
*/
|
|
2715
|
-
|
|
2638
|
+
makePermissionError = nestFirebaseForbiddenPermissionError;
|
|
2716
2639
|
/**
|
|
2717
2640
|
* FirebaseDoesNotExistErrorContextErrorFunction to use with makeModelContext().
|
|
2718
2641
|
*
|
|
@@ -2721,55 +2644,56 @@ class AbstractFirebaseNestContext extends AbstractNestContext {
|
|
|
2721
2644
|
* Some configurations may prefer to use nestFirebaseForbiddenPermissionError instead, which returns a forbidden error instead.
|
|
2722
2645
|
* This prevents the leaking of information about the existence of an object.
|
|
2723
2646
|
*/
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2647
|
+
makeDoesNotExistError = nestFirebaseDoesNotExistError;
|
|
2648
|
+
get envService() {
|
|
2649
|
+
return this.nest.get(FirebaseServerEnvService);
|
|
2650
|
+
}
|
|
2651
|
+
get storageService() {
|
|
2652
|
+
return this.nest.get(FirebaseServerStorageService);
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Creates a FirebaseAppModelContext instance.
|
|
2656
|
+
*
|
|
2657
|
+
* @param auth
|
|
2658
|
+
* @param buildFn
|
|
2659
|
+
* @returns
|
|
2660
|
+
*/
|
|
2661
|
+
makeModelContext(auth, buildFn) {
|
|
2662
|
+
const base = {
|
|
2663
|
+
auth: this.authService.authContextInfo(auth),
|
|
2664
|
+
app: this.app,
|
|
2665
|
+
makePermissionError: this.makePermissionError,
|
|
2666
|
+
makeDoesNotExistError: this.makeDoesNotExistError
|
|
2667
|
+
};
|
|
2668
|
+
return buildFn
|
|
2669
|
+
? build({
|
|
2670
|
+
base,
|
|
2671
|
+
build: buildFn
|
|
2672
|
+
})
|
|
2673
|
+
: base;
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* Creates a InContextFirebaseModelsService given the input context and parameters.
|
|
2677
|
+
*
|
|
2678
|
+
* @param context
|
|
2679
|
+
* @param buildFn
|
|
2680
|
+
* @returns
|
|
2681
|
+
*/
|
|
2682
|
+
model(context, buildFn) {
|
|
2683
|
+
const firebaseModelContext = this.makeModelContext(context, buildFn);
|
|
2684
|
+
return inContextFirebaseModelsServiceFactory(this.firebaseModelsService)(firebaseModelContext);
|
|
2685
|
+
}
|
|
2686
|
+
async useModel(type, select) {
|
|
2687
|
+
const context = this.makeModelContext(select.request, select.buildFn);
|
|
2688
|
+
const usePromise = useFirebaseModelsService(this.firebaseModelsService, type, {
|
|
2689
|
+
context,
|
|
2690
|
+
key: select.key,
|
|
2691
|
+
roles: select.roles,
|
|
2692
|
+
rolesSetIncludes: select.rolesSetIncludes
|
|
2693
|
+
});
|
|
2694
|
+
const use = select.use ?? ((x) => x);
|
|
2695
|
+
return usePromise(use);
|
|
2696
|
+
}
|
|
2773
2697
|
}
|
|
2774
2698
|
|
|
2775
|
-
export { ALREADY_EXISTS_ERROR_CODE, AbstractFirebaseNestContext, AbstractFirebaseServerActionsContext, AbstractFirebaseServerAuthContext, AbstractFirebaseServerAuthService, AbstractFirebaseServerAuthUserContext, AbstractFirebaseServerNewUserService, AbstractNestContext, BAD_REQUEST_ERROR_CODE, CONFLICT_ERROR_CODE, ConfigureFirebaseAppCheckMiddlewareModule, ConfigureFirebaseWebhookMiddlewareModule, DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR, DEFAULT_SETUP_COM_THROTTLE_TIME, DefaultFirebaseServerEnvService, FIREBASE_APP_TOKEN, FIREBASE_AUTH_TOKEN, FIREBASE_FIRESTORE_CONTEXT_TOKEN, FIREBASE_FIRESTORE_TOKEN, FIREBASE_SERVER_VALIDATION_ERROR_CODE, FIREBASE_STORAGE_CONTEXT_FACTORY_CONFIG_TOKEN, FIREBASE_STORAGE_CONTEXT_TOKEN, FIREBASE_STORAGE_TOKEN, FIRESTORE_CLIENT_QUERY_CONSTRAINT_HANDLER_MAPPING, FORBIDDEN_ERROR_CODE, FirebaseAppCheckMiddleware, FirebaseNestServerRootModule, FirebaseRawBodyMiddleware, FirebaseServerAuthModule, FirebaseServerAuthNewUserSendSetupDetailsNoSetupConfigError, FirebaseServerAuthNewUserSendSetupDetailsSendOnceError, FirebaseServerAuthNewUserSendSetupDetailsThrottleError, FirebaseServerAuthService, FirebaseServerEnvService, FirebaseServerFirestoreContextModule, FirebaseServerFirestoreModule, FirebaseServerStorageContextModule, FirebaseServerStorageModule, FirebaseServerStorageService, INTERNAL_SERVER_ERROR_CODE, MODEL_NOT_AVAILABLE_ERROR_CODE, NOT_FOUND_ERROR_CODE,
|
|
2699
|
+
export { ALREADY_EXISTS_ERROR_CODE, AbstractFirebaseNestContext, AbstractFirebaseServerActionsContext, AbstractFirebaseServerAuthContext, AbstractFirebaseServerAuthService, AbstractFirebaseServerAuthUserContext, AbstractFirebaseServerNewUserService, AbstractNestContext, BAD_REQUEST_ERROR_CODE, CONFLICT_ERROR_CODE, ConfigureFirebaseAppCheckMiddlewareModule, ConfigureFirebaseWebhookMiddlewareModule, DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR, DEFAULT_SETUP_COM_THROTTLE_TIME, DefaultFirebaseServerEnvService, FIREBASE_APP_TOKEN, FIREBASE_AUTH_TOKEN, FIREBASE_FIRESTORE_CONTEXT_TOKEN, FIREBASE_FIRESTORE_TOKEN, FIREBASE_SERVER_VALIDATION_ERROR_CODE, FIREBASE_STORAGE_CONTEXT_FACTORY_CONFIG_TOKEN, FIREBASE_STORAGE_CONTEXT_TOKEN, FIREBASE_STORAGE_TOKEN, FIRESTORE_CLIENT_QUERY_CONSTRAINT_HANDLER_MAPPING, FORBIDDEN_ERROR_CODE, FirebaseAppCheckMiddleware, FirebaseNestServerRootModule, FirebaseRawBodyMiddleware, FirebaseServerAuthModule, FirebaseServerAuthNewUserSendSetupDetailsNoSetupConfigError, FirebaseServerAuthNewUserSendSetupDetailsSendOnceError, FirebaseServerAuthNewUserSendSetupDetailsThrottleError, FirebaseServerAuthService, FirebaseServerEnvService, FirebaseServerFirestoreContextModule, FirebaseServerFirestoreModule, FirebaseServerStorageContextModule, FirebaseServerStorageModule, FirebaseServerStorageService, INTERNAL_SERVER_ERROR_CODE, MODEL_NOT_AVAILABLE_ERROR_CODE, NOT_FOUND_ERROR_CODE, NO_RUN_NAME_SPECIFIED_FOR_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_CODE, NoSetupContentFirebaseServerNewUserService, PERMISSION_DENIED_ERROR_CODE, PHONE_NUMBER_ALREADY_EXISTS_ERROR_CODE, SkipAppCheck, UNAUTHENTICATED_ERROR_CODE, UNAVAILABLE_ERROR_CODE, UNAVAILABLE_OR_DEACTIVATED_FUNCTION_ERROR_CODE, UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_NAME_CODE, UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_TYPE_CODE, _onCallWithCallTypeFunction, alreadyExistsError, appFirestoreModuleMetadata, assertContextHasAuth, assertDocumentExists, assertHasRolesInRequest, assertHasSignedTosInRequest, assertIsAdminInRequest, assertIsAdminOrTargetUserInRequestData, assertIsContextWithAuthData, assertRequestRequiresAuthForFunction, assertSnapshotData, assertSnapshotDataWithKey, badRequestError, blockingFunctionHandlerWithNestContextFactory, cloudEventHandlerWithNestContextFactory, collectionRefForPath, createModelUnknownModelTypeError, defaultFirebaseServerActionsTransformFactoryLogErrorFunction, defaultProvideFirebaseServerStorageServiceSimple, deleteModelUnknownModelTypeError, developmentUnknownSpecifierError, docRefForPath, documentModelNotAvailableError, firebaseAuthTokenFromDecodedIdToken, firebaseServerActionsContext, firebaseServerActionsTransformContext, firebaseServerActionsTransformFactory, firebaseServerAppTokenProvider, firebaseServerAuthModuleMetadata, firebaseServerDevFunctions, firebaseServerErrorInfo, firebaseServerErrorInfoCodePair, firebaseServerErrorInfoServerErrorCodePair, firebaseServerErrorInfoServerErrorPair, firebaseServerStorageDefaultBucketIdTokenProvider, firebaseServerStorageModuleMetadata, firebaseServerValidationError, firebaseServerValidationServerError, firestoreClientQueryConstraintFunctionsDriver, firestoreServerIncrementUpdateToUpdateData, forbiddenError, getAuthUserOrUndefined, googleCloudFileMetadataToStorageMetadata, googleCloudFirebaseStorageContextFactory, googleCloudFirebaseStorageDrivers, googleCloudFirestoreAccessorDriver, googleCloudFirestoreContextFactory, googleCloudFirestoreDrivers, googleCloudFirestoreQueryDriver, googleCloudStorageAccessorFile, googleCloudStorageAccessorFolder, googleCloudStorageBucketForStorageFilePath, googleCloudStorageFileForStorageFilePath, googleCloudStorageFirebaseStorageAccessorDriver, googleCloudStorageFromFirebaseAdminStorage, googleCloudStorageListFilesResultFactory, handleFirebaseAuthError, handleFirebaseError, hasAuthRolesInRequest, hasNewUserSetupPasswordInRequest, hasSignedTosInRequest, inAuthContext, injectNestApplicationContextIntoRequest, injectNestIntoRequest, internalServerError, isAdminInRequest, isAdminOrTargetUserInRequestData, isContextWithAuthData, isFirebaseError, isFirebaseHttpsError, makeBlockingFunctionWithHandler, makeOnScheduleHandlerWithNestApplicationRequest, makeScheduledFunctionDevelopmentFunction, modelNotAvailableError, nestAppHasDevelopmentSchedulerEnabled, nestAppIsProductionEnvironment, nestFirebaseDoesNotExistError, nestFirebaseForbiddenPermissionError, nestServerInstance, noRunNameSpecifiedForScheduledFunctionDevelopmentFunction, notFoundError, onCallCreateModel, onCallDeleteModel, onCallDevelopmentFunction, onCallHandlerWithNestApplicationFactory, onCallHandlerWithNestContextFactory, onCallModel, onCallModelMissingCallTypeError, onCallModelUnknownCallTypeError, onCallReadModel, onCallSpecifierHandler, onCallUpdateModel, onScheduleHandlerWithNestApplicationFactory, onScheduleHandlerWithNestContextFactory, optionalAuthContext, permissionDeniedError, phoneNumberAlreadyExistsError, preconditionConflictError, provideAppFirestoreCollections, provideFirebaseServerAuthService, provideFirebaseServerStorageService, readModelUnknownModelTypeError, setNestContextOnRequest, setNestContextOnScheduleRequest, taskQueueFunctionHandlerWithNestContextFactory, unauthenticatedContextHasNoAuthData, unauthenticatedContextHasNoUidError, unauthenticatedError, unavailableError, unavailableOrDeactivatedFunctionError, unknownModelCrudFunctionSpecifierError, unknownScheduledFunctionDevelopmentFunctionName, unknownScheduledFunctionDevelopmentFunctionType, updateModelUnknownModelTypeError, userContextFromUid, verifyAppCheckInRequest };
|