firebase-tools 11.16.0 → 11.17.0
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/lib/deploy/extensions/planner.js +2 -1
- package/lib/deploy/functions/build.js +3 -0
- package/lib/deploy/functions/deploy.js +5 -2
- package/lib/deploy/functions/release/fabricator.js +2 -2
- package/lib/deploy/functions/release/sourceTokenScraper.js +34 -7
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
- package/lib/deploy/lifecycleHooks.js +2 -1
- package/lib/emulator/auth/apiSpec.js +549 -201
- package/lib/emulator/auth/operations.js +4 -2
- package/lib/emulator/auth/server.js +19 -8
- package/lib/emulator/download.js +2 -1
- package/lib/emulator/downloadableEmulators.js +3 -3
- package/lib/emulator/eventarcEmulator.js +2 -1
- package/lib/emulator/eventarcEmulatorUtils.js +60 -0
- package/lib/emulator/extensionsEmulator.js +1 -1
- package/lib/emulator/functionsEmulator.js +13 -5
- package/lib/emulator/storage/apis/firebase.js +7 -27
- package/lib/emulator/storage/apis/gcloud.js +9 -33
- package/lib/emulator/storage/apis/shared.js +43 -0
- package/lib/emulator/storage/rules/config.js +9 -0
- package/lib/emulator/storage/upload.js +2 -2
- package/lib/extensions/extensionsHelper.js +8 -5
- package/lib/utils.js +1 -1
- package/npm-shrinkwrap.json +38 -72
- package/package.json +1 -1
- package/templates/emulators/default_storage.rules +8 -0
- package/templates/init/functions/golang/functions.go +1 -1
- package/templates/init/functions/javascript/index.js +2 -2
- package/templates/init/functions/typescript/index.ts +1 -1
|
@@ -1126,7 +1126,8 @@ async function signInWithIdp(state, reqBody) {
|
|
|
1126
1126
|
oauthExpiresIn: coercePrimitiveToString(response.oauthExpireIn),
|
|
1127
1127
|
};
|
|
1128
1128
|
if (response.isNewUser) {
|
|
1129
|
-
|
|
1129
|
+
const timestamp = new Date();
|
|
1130
|
+
let updates = Object.assign(Object.assign({}, accountUpdates.fields), { createdAt: timestamp.getTime().toString(), lastLoginAt: timestamp.getTime().toString(), providerUserInfo: [providerUserInfo], tenantId: state instanceof state_1.TenantProjectState ? state.tenantId : undefined });
|
|
1130
1131
|
const localId = state.generateLocalId();
|
|
1131
1132
|
const userBeforeCreate = Object.assign({ localId }, updates);
|
|
1132
1133
|
const blockingResponse = await fetchBlockingFunction(state, state_1.BlockingFunctionEvents.BEFORE_CREATE, userBeforeCreate, {
|
|
@@ -1187,7 +1188,8 @@ async function signInWithIdp(state, reqBody) {
|
|
|
1187
1188
|
async function signInWithPassword(state, reqBody) {
|
|
1188
1189
|
(0, errors_1.assert)(!state.disableAuth, "PROJECT_DISABLED");
|
|
1189
1190
|
(0, errors_1.assert)(state.allowPasswordSignup, "PASSWORD_LOGIN_DISABLED");
|
|
1190
|
-
(0, errors_1.assert)(reqBody.email, "MISSING_EMAIL");
|
|
1191
|
+
(0, errors_1.assert)(reqBody.email !== undefined, "MISSING_EMAIL");
|
|
1192
|
+
(0, errors_1.assert)((0, utils_1.isValidEmailAddress)(reqBody.email), "INVALID_EMAIL");
|
|
1191
1193
|
(0, errors_1.assert)(reqBody.password, "MISSING_PASSWORD");
|
|
1192
1194
|
if (reqBody.captchaResponse || reqBody.captchaChallenge) {
|
|
1193
1195
|
throw new errors_1.NotImplementedError("captcha unimplemented");
|
|
@@ -97,14 +97,24 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
|
|
|
97
97
|
registerLegacyRoutes(app);
|
|
98
98
|
(0, handlers_1.registerHandlers)(app, (apiKey, tenantId) => getProjectStateById(getProjectIdByApiKey(apiKey), tenantId));
|
|
99
99
|
const apiKeyAuthenticator = (ctx, info) => {
|
|
100
|
-
if (info.in !== "query") {
|
|
101
|
-
throw new Error('apiKey must be defined as in: "query" in API spec.');
|
|
102
|
-
}
|
|
103
100
|
if (!info.name) {
|
|
104
|
-
throw new Error("apiKey param name is undefined in API spec.");
|
|
101
|
+
throw new Error("apiKey param/header name is undefined in API spec.");
|
|
102
|
+
}
|
|
103
|
+
let key;
|
|
104
|
+
const req = ctx.req;
|
|
105
|
+
switch (info.in) {
|
|
106
|
+
case "header":
|
|
107
|
+
key = req.get(info.name);
|
|
108
|
+
break;
|
|
109
|
+
case "query": {
|
|
110
|
+
const q = req.query[info.name];
|
|
111
|
+
key = typeof q === "string" ? q : undefined;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
default:
|
|
115
|
+
throw new Error('apiKey must be defined as in: "query" or "header" in API spec.');
|
|
105
116
|
}
|
|
106
|
-
|
|
107
|
-
if (typeof key === "string" && key.length > 0) {
|
|
117
|
+
if (key) {
|
|
108
118
|
return { type: "success", user: getProjectIdByApiKey(key) };
|
|
109
119
|
}
|
|
110
120
|
else {
|
|
@@ -138,7 +148,8 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
|
|
|
138
148
|
const apis = await exegesisExpress.middleware(specForRouter(), {
|
|
139
149
|
controllers: { auth: toExegesisController(operations_1.authOperations, getProjectStateById) },
|
|
140
150
|
authenticators: {
|
|
141
|
-
|
|
151
|
+
apiKeyQuery: apiKeyAuthenticator,
|
|
152
|
+
apiKeyHeader: apiKeyAuthenticator,
|
|
142
153
|
Oauth2: oauth2Authenticator,
|
|
143
154
|
},
|
|
144
155
|
autoHandleHttpErrors(err) {
|
|
@@ -206,7 +217,7 @@ async function createApp(defaultProjectId, singleProjectMode = index_1.SinglePro
|
|
|
206
217
|
postController(ctx) {
|
|
207
218
|
if (ctx.res.statusCode === 401) {
|
|
208
219
|
const requirements = ctx.api.operationObject.security;
|
|
209
|
-
if (requirements === null || requirements === void 0 ? void 0 : requirements.some((req) => req.
|
|
220
|
+
if (requirements === null || requirements === void 0 ? void 0 : requirements.some((req) => req.apiKeyQuery || req.apiKeyHeader)) {
|
|
210
221
|
throw new errors_2.PermissionDeniedError("The request is missing a valid API key.");
|
|
211
222
|
}
|
|
212
223
|
else {
|
package/lib/emulator/download.js
CHANGED
|
@@ -27,6 +27,7 @@ async function downloadEmulator(name) {
|
|
|
27
27
|
if (emulator.unzipDir) {
|
|
28
28
|
await unzip(emulator.downloadPath, emulator.unzipDir);
|
|
29
29
|
}
|
|
30
|
+
await new Promise((f) => setTimeout(f, 2000));
|
|
30
31
|
const executablePath = emulator.binaryPath || emulator.downloadPath;
|
|
31
32
|
fs.chmodSync(executablePath, 0o755);
|
|
32
33
|
removeOldFiles(name, emulator);
|
|
@@ -54,7 +55,7 @@ function unzip(zipPath, unzipDir) {
|
|
|
54
55
|
fs.createReadStream(zipPath)
|
|
55
56
|
.pipe(unzipper.Extract({ path: unzipDir }))
|
|
56
57
|
.on("error", reject)
|
|
57
|
-
.on("
|
|
58
|
+
.on("close", resolve);
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
61
|
function removeOldFiles(name, emulator, removeAllVersions = false) {
|
|
@@ -40,9 +40,9 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
40
40
|
expectedChecksum: "a4944414518be206280b495f526f18bf",
|
|
41
41
|
},
|
|
42
42
|
pubsub: {
|
|
43
|
-
version: "0.1
|
|
44
|
-
expectedSize:
|
|
45
|
-
expectedChecksum: "
|
|
43
|
+
version: "0.7.1",
|
|
44
|
+
expectedSize: 65137179,
|
|
45
|
+
expectedChecksum: "b59a6e705031a54a69e5e1dced7ca9bf",
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
48
|
exports.DownloadDetails = {
|
|
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
const emulatorLogger_1 = require("./emulatorLogger");
|
|
9
9
|
const registry_1 = require("./registry");
|
|
10
10
|
const error_1 = require("../error");
|
|
11
|
+
const eventarcEmulatorUtils_1 = require("./eventarcEmulatorUtils");
|
|
11
12
|
class EventarcEmulator {
|
|
12
13
|
constructor(args) {
|
|
13
14
|
this.args = args;
|
|
@@ -89,7 +90,7 @@ class EventarcEmulator {
|
|
|
89
90
|
.request({
|
|
90
91
|
method: "POST",
|
|
91
92
|
path: `/functions/projects/${trigger.projectId}/triggers/${trigger.triggerName}`,
|
|
92
|
-
body: JSON.stringify(event),
|
|
93
|
+
body: JSON.stringify((0, eventarcEmulatorUtils_1.cloudEventFromProtoToJson)(event)),
|
|
93
94
|
responseType: "stream",
|
|
94
95
|
resolveOnHTTPError: true,
|
|
95
96
|
})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cloudEventFromProtoToJson = void 0;
|
|
4
|
+
const error_1 = require("../error");
|
|
5
|
+
const BUILT_IN_ATTRS = ["time", "datacontenttype", "subject"];
|
|
6
|
+
function cloudEventFromProtoToJson(ce) {
|
|
7
|
+
if (ce["id"] === undefined) {
|
|
8
|
+
throw new error_1.FirebaseError("CloudEvent 'id' is required.");
|
|
9
|
+
}
|
|
10
|
+
if (ce["type"] === undefined) {
|
|
11
|
+
throw new error_1.FirebaseError("CloudEvent 'type' is required.");
|
|
12
|
+
}
|
|
13
|
+
if (ce["specVersion"] === undefined) {
|
|
14
|
+
throw new error_1.FirebaseError("CloudEvent 'specVersion' is required.");
|
|
15
|
+
}
|
|
16
|
+
if (ce["source"] === undefined) {
|
|
17
|
+
throw new error_1.FirebaseError("CloudEvent 'source' is required.");
|
|
18
|
+
}
|
|
19
|
+
const out = {
|
|
20
|
+
id: ce["id"],
|
|
21
|
+
type: ce["type"],
|
|
22
|
+
specversion: ce["specVersion"],
|
|
23
|
+
source: ce["source"],
|
|
24
|
+
subject: getOptionalAttribute(ce, "subject", "ceString"),
|
|
25
|
+
time: getRequiredAttribute(ce, "time", "ceTimestamp"),
|
|
26
|
+
data: getData(ce),
|
|
27
|
+
datacontenttype: getRequiredAttribute(ce, "datacontenttype", "ceString"),
|
|
28
|
+
};
|
|
29
|
+
for (const attr in ce["attributes"]) {
|
|
30
|
+
if (BUILT_IN_ATTRS.includes(attr)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
out[attr] = getRequiredAttribute(ce, attr, "ceString");
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
exports.cloudEventFromProtoToJson = cloudEventFromProtoToJson;
|
|
38
|
+
function getOptionalAttribute(ce, attr, type) {
|
|
39
|
+
return ce["attributes"][attr][type];
|
|
40
|
+
}
|
|
41
|
+
function getRequiredAttribute(ce, attr, type) {
|
|
42
|
+
const val = ce["attributes"][attr][type];
|
|
43
|
+
if (val === undefined) {
|
|
44
|
+
throw new error_1.FirebaseError("CloudEvent must contain " + attr + " attribute");
|
|
45
|
+
}
|
|
46
|
+
return val;
|
|
47
|
+
}
|
|
48
|
+
function getData(ce) {
|
|
49
|
+
const contentType = getRequiredAttribute(ce, "datacontenttype", "ceString");
|
|
50
|
+
switch (contentType) {
|
|
51
|
+
case "application/json":
|
|
52
|
+
return JSON.parse(ce["textData"]);
|
|
53
|
+
case "text/plain":
|
|
54
|
+
return ce["textData"];
|
|
55
|
+
case undefined:
|
|
56
|
+
return undefined;
|
|
57
|
+
default:
|
|
58
|
+
throw new error_1.FirebaseError("Unsupported content type: " + contentType);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -44,7 +44,7 @@ class ExtensionsEmulator {
|
|
|
44
44
|
if (!functionsEmulator) {
|
|
45
45
|
throw new error_1.FirebaseError("Extensions Emulator is running but Functions emulator is not. This should never happen.");
|
|
46
46
|
}
|
|
47
|
-
return functionsEmulator.getInfo();
|
|
47
|
+
return Object.assign(Object.assign({}, functionsEmulator.getInfo()), { name: this.getName() });
|
|
48
48
|
}
|
|
49
49
|
getName() {
|
|
50
50
|
return types_1.Emulators.EXTENSIONS;
|
|
@@ -137,10 +137,11 @@ class FunctionsEmulator {
|
|
|
137
137
|
method: req.method,
|
|
138
138
|
path: `/functions/projects/${projectId}/triggers/${triggerId}`,
|
|
139
139
|
headers: req.headers,
|
|
140
|
-
}
|
|
140
|
+
});
|
|
141
141
|
trigReq.on("error", reject);
|
|
142
142
|
trigReq.write(rawBody);
|
|
143
143
|
trigReq.end();
|
|
144
|
+
resolve();
|
|
144
145
|
});
|
|
145
146
|
});
|
|
146
147
|
});
|
|
@@ -333,7 +334,7 @@ class FunctionsEmulator {
|
|
|
333
334
|
added = await this.addFirestoreTrigger(this.args.projectId, key, definition.eventTrigger);
|
|
334
335
|
break;
|
|
335
336
|
case constants_1.Constants.SERVICE_REALTIME_DATABASE:
|
|
336
|
-
added = await this.addRealtimeDatabaseTrigger(this.args.projectId, key, definition.eventTrigger, signature, definition.region);
|
|
337
|
+
added = await this.addRealtimeDatabaseTrigger(this.args.projectId, definition.id, key, definition.eventTrigger, signature, definition.region);
|
|
337
338
|
break;
|
|
338
339
|
case constants_1.Constants.SERVICE_PUBSUB:
|
|
339
340
|
added = await this.addPubsubTrigger(definition.name, key, definition.eventTrigger, signature, definition.schedule);
|
|
@@ -437,7 +438,7 @@ class FunctionsEmulator {
|
|
|
437
438
|
}
|
|
438
439
|
return { bundle, apiPath, instance };
|
|
439
440
|
}
|
|
440
|
-
getV2DatabaseApiAttributes(projectId, key, eventTrigger, region) {
|
|
441
|
+
getV2DatabaseApiAttributes(projectId, id, key, eventTrigger, region) {
|
|
441
442
|
var _a, _b, _c;
|
|
442
443
|
const instance = ((_a = eventTrigger.eventFilters) === null || _a === void 0 ? void 0 : _a.instance) || ((_b = eventTrigger.eventFilterPathPatterns) === null || _b === void 0 ? void 0 : _b.instance);
|
|
443
444
|
if (!instance) {
|
|
@@ -447,6 +448,9 @@ class FunctionsEmulator {
|
|
|
447
448
|
if (!ref) {
|
|
448
449
|
throw new error_1.FirebaseError("A database reference must be supplied.");
|
|
449
450
|
}
|
|
451
|
+
if (region !== "us-central1") {
|
|
452
|
+
this.logger.logLabeled("WARN", `functions[${id}]`, `function region is defined outside the database region, will not trigger.`);
|
|
453
|
+
}
|
|
450
454
|
const bundle = JSON.stringify({
|
|
451
455
|
name: `projects/${projectId}/locations/${region}/triggers/${key}`,
|
|
452
456
|
path: ref,
|
|
@@ -457,12 +461,12 @@ class FunctionsEmulator {
|
|
|
457
461
|
const apiPath = "/.settings/functionTriggers.json";
|
|
458
462
|
return { bundle, apiPath, instance };
|
|
459
463
|
}
|
|
460
|
-
async addRealtimeDatabaseTrigger(projectId, key, eventTrigger, signature, region) {
|
|
464
|
+
async addRealtimeDatabaseTrigger(projectId, id, key, eventTrigger, signature, region) {
|
|
461
465
|
if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.DATABASE)) {
|
|
462
466
|
return false;
|
|
463
467
|
}
|
|
464
468
|
const { bundle, apiPath, instance } = signature === "cloudevent"
|
|
465
|
-
? this.getV2DatabaseApiAttributes(projectId, key, eventTrigger, region)
|
|
469
|
+
? this.getV2DatabaseApiAttributes(projectId, id, key, eventTrigger, region)
|
|
466
470
|
: this.getV1DatabaseApiAttributes(projectId, key, eventTrigger);
|
|
467
471
|
logger_1.logger.debug(`addRealtimeDatabaseTrigger[${instance}]`, JSON.stringify(bundle));
|
|
468
472
|
const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE);
|
|
@@ -880,6 +884,10 @@ class FunctionsEmulator {
|
|
|
880
884
|
return;
|
|
881
885
|
}
|
|
882
886
|
const record = this.getTriggerRecordByKey(triggerId);
|
|
887
|
+
if (!record.enabled) {
|
|
888
|
+
res.status(204).send("Background triggers are currently disabled.");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
883
891
|
const trigger = record.def;
|
|
884
892
|
logger_1.logger.debug(`Accepted request ${method} ${req.url} --> ${triggerId}`);
|
|
885
893
|
const reqBody = req.rawBody;
|
|
@@ -4,9 +4,9 @@ exports.createFirebaseEndpoints = void 0;
|
|
|
4
4
|
const emulatorLogger_1 = require("../../emulatorLogger");
|
|
5
5
|
const types_1 = require("../../types");
|
|
6
6
|
const uuid = require("uuid");
|
|
7
|
-
const zlib_1 = require("zlib");
|
|
8
7
|
const metadata_1 = require("../metadata");
|
|
9
8
|
const express_1 = require("express");
|
|
9
|
+
const shared_1 = require("./shared");
|
|
10
10
|
const registry_1 = require("../../registry");
|
|
11
11
|
const multipart_1 = require("../multipart");
|
|
12
12
|
const errors_1 = require("../errors");
|
|
@@ -17,7 +17,7 @@ function createFirebaseEndpoints(emulator) {
|
|
|
17
17
|
const { storageLayer, uploadService } = emulator;
|
|
18
18
|
if (process.env.STORAGE_EMULATOR_DEBUG) {
|
|
19
19
|
firebaseStorageAPI.use((req, res, next) => {
|
|
20
|
-
console.log("--------------INCOMING REQUEST--------------");
|
|
20
|
+
console.log("--------------INCOMING FIREBASE REQUEST--------------");
|
|
21
21
|
console.log(`${req.method.toUpperCase()} ${req.path}`);
|
|
22
22
|
console.log("-- query:");
|
|
23
23
|
console.log(JSON.stringify(req.query, undefined, 2));
|
|
@@ -98,24 +98,7 @@ function createFirebaseEndpoints(emulator) {
|
|
|
98
98
|
metadata.addDownloadToken(true);
|
|
99
99
|
}
|
|
100
100
|
if (req.query.alt === "media") {
|
|
101
|
-
|
|
102
|
-
if (isGZipped) {
|
|
103
|
-
data = (0, zlib_1.gunzipSync)(data);
|
|
104
|
-
}
|
|
105
|
-
res.setHeader("Accept-Ranges", "bytes");
|
|
106
|
-
res.setHeader("Content-Type", metadata.contentType || "application/octet-stream");
|
|
107
|
-
res.setHeader("Content-Disposition", metadata.contentDisposition || "inline");
|
|
108
|
-
setObjectHeaders(res, metadata, { "Content-Encoding": isGZipped ? "identity" : undefined });
|
|
109
|
-
const byteRange = req.range(data.byteLength, { combine: true });
|
|
110
|
-
if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
|
|
111
|
-
const range = byteRange[0];
|
|
112
|
-
res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
|
|
113
|
-
res.status(206).end(data.slice(range.start, range.end + 1));
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
res.end(data);
|
|
117
|
-
}
|
|
118
|
-
return;
|
|
101
|
+
return (0, shared_1.sendFileBytes)(metadata, data, req, res);
|
|
119
102
|
}
|
|
120
103
|
return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
|
|
121
104
|
});
|
|
@@ -215,7 +198,7 @@ function createFirebaseEndpoints(emulator) {
|
|
|
215
198
|
const upload = uploadService.startResumableUpload({
|
|
216
199
|
bucketId,
|
|
217
200
|
objectId,
|
|
218
|
-
|
|
201
|
+
metadata: req.body,
|
|
219
202
|
authorization: req.header("authorization"),
|
|
220
203
|
});
|
|
221
204
|
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
@@ -330,7 +313,7 @@ function createFirebaseEndpoints(emulator) {
|
|
|
330
313
|
const upload = uploadService.multipartUpload({
|
|
331
314
|
bucketId,
|
|
332
315
|
objectId,
|
|
333
|
-
metadataRaw,
|
|
316
|
+
metadata: JSON.parse(metadataRaw),
|
|
334
317
|
dataRaw: dataRaw,
|
|
335
318
|
authorization: req.header("authorization"),
|
|
336
319
|
});
|
|
@@ -480,14 +463,11 @@ function createFirebaseEndpoints(emulator) {
|
|
|
480
463
|
return firebaseStorageAPI;
|
|
481
464
|
}
|
|
482
465
|
exports.createFirebaseEndpoints = createFirebaseEndpoints;
|
|
483
|
-
function setObjectHeaders(res, metadata
|
|
466
|
+
function setObjectHeaders(res, metadata) {
|
|
484
467
|
if (metadata.contentDisposition) {
|
|
485
468
|
res.setHeader("Content-Disposition", metadata.contentDisposition);
|
|
486
469
|
}
|
|
487
|
-
if (
|
|
488
|
-
res.setHeader("Content-Encoding", headerOverride["Content-Encoding"]);
|
|
489
|
-
}
|
|
490
|
-
else if (metadata.contentEncoding) {
|
|
470
|
+
if (metadata.contentEncoding) {
|
|
491
471
|
res.setHeader("Content-Encoding", metadata.contentEncoding);
|
|
492
472
|
}
|
|
493
473
|
if (metadata.cacheControl) {
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createCloudEndpoints = void 0;
|
|
4
4
|
const express_1 = require("express");
|
|
5
|
-
const zlib_1 = require("zlib");
|
|
6
5
|
const types_1 = require("../../types");
|
|
7
6
|
const metadata_1 = require("../metadata");
|
|
7
|
+
const shared_1 = require("./shared");
|
|
8
8
|
const registry_1 = require("../../registry");
|
|
9
9
|
const emulatorLogger_1 = require("../../emulatorLogger");
|
|
10
|
-
const crc_1 = require("../crc");
|
|
11
10
|
const multipart_1 = require("../multipart");
|
|
12
11
|
const upload_1 = require("../upload");
|
|
13
12
|
const errors_1 = require("../errors");
|
|
@@ -17,7 +16,7 @@ function createCloudEndpoints(emulator) {
|
|
|
17
16
|
const { adminStorageLayer, uploadService } = emulator;
|
|
18
17
|
if (process.env.STORAGE_EMULATOR_DEBUG) {
|
|
19
18
|
gcloudStorageAPI.use((req, res, next) => {
|
|
20
|
-
console.log("--------------INCOMING REQUEST--------------");
|
|
19
|
+
console.log("--------------INCOMING GCS REQUEST--------------");
|
|
21
20
|
console.log(`${req.method.toUpperCase()} ${req.path}`);
|
|
22
21
|
console.log("-- query:");
|
|
23
22
|
console.log(JSON.stringify(req.query, undefined, 2));
|
|
@@ -86,7 +85,7 @@ function createCloudEndpoints(emulator) {
|
|
|
86
85
|
throw err;
|
|
87
86
|
}
|
|
88
87
|
if (req.query.alt === "media") {
|
|
89
|
-
return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
|
|
88
|
+
return (0, shared_1.sendFileBytes)(getObjectResponse.metadata, getObjectResponse.data, req, res);
|
|
90
89
|
}
|
|
91
90
|
return res.json(new metadata_1.CloudStorageObjectMetadata(getObjectResponse.metadata));
|
|
92
91
|
});
|
|
@@ -110,7 +109,7 @@ function createCloudEndpoints(emulator) {
|
|
|
110
109
|
}
|
|
111
110
|
return res.json(new metadata_1.CloudStorageObjectMetadata(updatedMetadata));
|
|
112
111
|
});
|
|
113
|
-
gcloudStorageAPI.get("/b/:bucketId/o", async (req, res) => {
|
|
112
|
+
gcloudStorageAPI.get(["/b/:bucketId/o", "/storage/v1/b/:bucketId/o"], async (req, res) => {
|
|
114
113
|
var _a;
|
|
115
114
|
let listResponse;
|
|
116
115
|
try {
|
|
@@ -228,10 +227,11 @@ function createCloudEndpoints(emulator) {
|
|
|
228
227
|
res.sendStatus(400);
|
|
229
228
|
return;
|
|
230
229
|
}
|
|
230
|
+
const contentType = req.header("x-upload-content-type");
|
|
231
231
|
const upload = uploadService.startResumableUpload({
|
|
232
232
|
bucketId: req.params.bucketId,
|
|
233
233
|
objectId: name,
|
|
234
|
-
|
|
234
|
+
metadata: Object.assign({ contentType }, req.body),
|
|
235
235
|
authorization: req.header("authorization"),
|
|
236
236
|
});
|
|
237
237
|
const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
|
|
@@ -256,6 +256,7 @@ function createCloudEndpoints(emulator) {
|
|
|
256
256
|
}
|
|
257
257
|
if (uploadType === "multipart") {
|
|
258
258
|
const contentTypeHeader = req.header("content-type") || req.header("x-upload-content-type");
|
|
259
|
+
const contentType = req.header("x-upload-content-type");
|
|
259
260
|
if (!contentTypeHeader) {
|
|
260
261
|
return res.sendStatus(400);
|
|
261
262
|
}
|
|
@@ -283,7 +284,7 @@ function createCloudEndpoints(emulator) {
|
|
|
283
284
|
const upload = uploadService.multipartUpload({
|
|
284
285
|
bucketId: req.params.bucketId,
|
|
285
286
|
objectId: name,
|
|
286
|
-
|
|
287
|
+
metadata: Object.assign({ contentType }, JSON.parse(metadataRaw)),
|
|
287
288
|
dataRaw: dataRaw,
|
|
288
289
|
authorization: req.header("authorization"),
|
|
289
290
|
});
|
|
@@ -318,7 +319,7 @@ function createCloudEndpoints(emulator) {
|
|
|
318
319
|
}
|
|
319
320
|
throw err;
|
|
320
321
|
}
|
|
321
|
-
return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
|
|
322
|
+
return (0, shared_1.sendFileBytes)(getObjectResponse.metadata, getObjectResponse.data, req, res);
|
|
322
323
|
});
|
|
323
324
|
gcloudStorageAPI.post("/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => {
|
|
324
325
|
if (req.params.method === "rewriteTo" && req.query.rewriteToken) {
|
|
@@ -375,31 +376,6 @@ function createCloudEndpoints(emulator) {
|
|
|
375
376
|
return gcloudStorageAPI;
|
|
376
377
|
}
|
|
377
378
|
exports.createCloudEndpoints = createCloudEndpoints;
|
|
378
|
-
function sendFileBytes(md, data, req, res) {
|
|
379
|
-
const isGZipped = md.contentEncoding === "gzip";
|
|
380
|
-
if (isGZipped) {
|
|
381
|
-
data = (0, zlib_1.gunzipSync)(data);
|
|
382
|
-
}
|
|
383
|
-
res.setHeader("Accept-Ranges", "bytes");
|
|
384
|
-
res.setHeader("Content-Type", md.contentType || "application/octet-stream");
|
|
385
|
-
res.setHeader("Content-Disposition", md.contentDisposition || "attachment");
|
|
386
|
-
res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding || "");
|
|
387
|
-
res.setHeader("ETag", md.etag);
|
|
388
|
-
res.setHeader("Cache-Control", md.cacheControl || "");
|
|
389
|
-
res.setHeader("x-goog-generation", `${md.generation}`);
|
|
390
|
-
res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
|
|
391
|
-
res.setHeader("x-goog-storage-class", md.storageClass);
|
|
392
|
-
res.setHeader("x-goog-hash", `crc32c=${(0, crc_1.crc32cToString)(md.crc32c)},md5=${md.md5Hash}`);
|
|
393
|
-
const byteRange = req.range(data.byteLength, { combine: true });
|
|
394
|
-
if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
|
|
395
|
-
const range = byteRange[0];
|
|
396
|
-
res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
|
|
397
|
-
res.status(206).end(data.slice(range.start, range.end + 1));
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
res.end(data);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
379
|
function sendObjectNotFound(req, res) {
|
|
404
380
|
res.status(404);
|
|
405
381
|
const message = `No such object: ${req.params.bucketId}/${req.params.objectId}`;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendFileBytes = void 0;
|
|
4
|
+
const zlib_1 = require("zlib");
|
|
5
|
+
const crc_1 = require("../crc");
|
|
6
|
+
function sendFileBytes(md, data, req, res) {
|
|
7
|
+
let didGunzip = false;
|
|
8
|
+
if (md.contentEncoding === "gzip") {
|
|
9
|
+
const acceptEncoding = req.header("accept-encoding") || "";
|
|
10
|
+
const shouldGunzip = !acceptEncoding.includes("gzip");
|
|
11
|
+
if (shouldGunzip) {
|
|
12
|
+
data = (0, zlib_1.gunzipSync)(data);
|
|
13
|
+
didGunzip = true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
res.setHeader("Accept-Ranges", "bytes");
|
|
17
|
+
res.setHeader("Content-Type", md.contentType || "application/octet-stream");
|
|
18
|
+
res.setHeader("Content-Disposition", md.contentDisposition || "attachment");
|
|
19
|
+
if (didGunzip) {
|
|
20
|
+
res.setHeader("Transfer-Encoding", "chunked");
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
res.setHeader("Content-Encoding", md.contentEncoding || "");
|
|
24
|
+
}
|
|
25
|
+
res.setHeader("ETag", md.etag);
|
|
26
|
+
res.setHeader("Cache-Control", md.cacheControl || "");
|
|
27
|
+
res.setHeader("x-goog-generation", `${md.generation}`);
|
|
28
|
+
res.setHeader("x-goog-metadatageneration", `${md.metageneration}`);
|
|
29
|
+
res.setHeader("x-goog-storage-class", md.storageClass);
|
|
30
|
+
res.setHeader("x-goog-hash", `crc32c=${(0, crc_1.crc32cToString)(md.crc32c)},md5=${md.md5Hash}`);
|
|
31
|
+
const shouldRespectContentRange = !didGunzip;
|
|
32
|
+
if (shouldRespectContentRange) {
|
|
33
|
+
const byteRange = req.range(data.byteLength, { combine: true });
|
|
34
|
+
if (Array.isArray(byteRange) && byteRange.type === "bytes" && byteRange.length > 0) {
|
|
35
|
+
const range = byteRange[0];
|
|
36
|
+
res.setHeader("Content-Range", `${byteRange.type} ${range.start}-${range.end}/${data.byteLength}`);
|
|
37
|
+
res.status(206).end(data.slice(range.start, range.end + 1));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
res.end(data);
|
|
42
|
+
}
|
|
43
|
+
exports.sendFileBytes = sendFileBytes;
|
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getStorageRulesConfig = void 0;
|
|
4
4
|
const error_1 = require("../../../error");
|
|
5
5
|
const fsutils_1 = require("../../../fsutils");
|
|
6
|
+
const constants_1 = require("../../constants");
|
|
7
|
+
const types_1 = require("../../types");
|
|
8
|
+
const emulatorLogger_1 = require("../../emulatorLogger");
|
|
6
9
|
function getSourceFile(rules, options) {
|
|
7
10
|
const path = options.config.path(rules);
|
|
8
11
|
return { name: path, content: (0, fsutils_1.readFile)(path) };
|
|
@@ -10,6 +13,12 @@ function getSourceFile(rules, options) {
|
|
|
10
13
|
function getStorageRulesConfig(projectId, options) {
|
|
11
14
|
const storageConfig = options.config.data.storage;
|
|
12
15
|
if (!storageConfig) {
|
|
16
|
+
if (constants_1.Constants.isDemoProject(projectId)) {
|
|
17
|
+
const storageLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
|
|
18
|
+
storageLogger.logLabeled("BULLET", "storage", `Detected demo project ID "${projectId}", using a default (open) rules configuration.`);
|
|
19
|
+
const path = __dirname + "/../../../../templates/emulators/default_storage.rules";
|
|
20
|
+
return { name: path, content: (0, fsutils_1.readFile)(path) };
|
|
21
|
+
}
|
|
13
22
|
throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
|
|
14
23
|
}
|
|
15
24
|
if (!Array.isArray(storageConfig)) {
|
|
@@ -50,7 +50,7 @@ class UploadService {
|
|
|
50
50
|
objectId: request.objectId,
|
|
51
51
|
uploadType: UploadType.MULTIPART,
|
|
52
52
|
dataRaw: request.dataRaw,
|
|
53
|
-
metadata:
|
|
53
|
+
metadata: request.metadata,
|
|
54
54
|
authorization: request.authorization,
|
|
55
55
|
});
|
|
56
56
|
this._persistence.deleteFile(upload.path, true);
|
|
@@ -82,7 +82,7 @@ class UploadService {
|
|
|
82
82
|
type: UploadType.RESUMABLE,
|
|
83
83
|
path: this.getStagingFileName(id, request.bucketId, request.objectId),
|
|
84
84
|
status: UploadStatus.ACTIVE,
|
|
85
|
-
metadata:
|
|
85
|
+
metadata: request.metadata,
|
|
86
86
|
size: 0,
|
|
87
87
|
authorization: request.authorization,
|
|
88
88
|
};
|
|
@@ -558,12 +558,15 @@ async function diagnoseAndFixProject(options) {
|
|
|
558
558
|
}
|
|
559
559
|
}
|
|
560
560
|
exports.diagnoseAndFixProject = diagnoseAndFixProject;
|
|
561
|
-
async function canonicalizeRefInput(
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
561
|
+
async function canonicalizeRefInput(refInput) {
|
|
562
|
+
let inferredRef = refInput;
|
|
563
|
+
if (refInput.split("/").length < 2) {
|
|
564
|
+
inferredRef = `firebase/${inferredRef}`;
|
|
565
565
|
}
|
|
566
|
-
|
|
566
|
+
if (refInput.split("@").length < 2) {
|
|
567
|
+
inferredRef = `${inferredRef}@latest`;
|
|
568
|
+
}
|
|
569
|
+
const ref = refs.parse(inferredRef);
|
|
567
570
|
ref.version = await (0, planner_1.resolveVersion)(ref);
|
|
568
571
|
return refs.toExtensionVersionRef(ref);
|
|
569
572
|
}
|
package/lib/utils.js
CHANGED
|
@@ -363,7 +363,7 @@ function datetimeString(d) {
|
|
|
363
363
|
}
|
|
364
364
|
exports.datetimeString = datetimeString;
|
|
365
365
|
function isCloudEnvironment() {
|
|
366
|
-
return !!process.env.CODESPACES;
|
|
366
|
+
return !!process.env.CODESPACES || !!process.env.GOOGLE_CLOUD_WORKSTATIONS;
|
|
367
367
|
}
|
|
368
368
|
exports.isCloudEnvironment = isCloudEnvironment;
|
|
369
369
|
function isRunningInWSL() {
|