firebase-tools 11.5.0 → 11.8.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.
Files changed (63) hide show
  1. package/lib/command.js +33 -7
  2. package/lib/commands/crashlytics-mappingfile-generateid.js +26 -0
  3. package/lib/commands/crashlytics-mappingfile-upload.js +46 -0
  4. package/lib/commands/crashlytics-symbols-upload.js +18 -87
  5. package/lib/commands/emulators-exec.js +4 -1
  6. package/lib/commands/emulators-export.js +5 -2
  7. package/lib/commands/emulators-start.js +23 -17
  8. package/lib/commands/ext-dev-publish.js +3 -0
  9. package/lib/commands/functions-delete.js +2 -0
  10. package/lib/commands/functions-secrets-get.js +2 -0
  11. package/lib/commands/index.js +3 -0
  12. package/lib/commands/login.js +2 -2
  13. package/lib/crashlytics/buildToolsJarHelper.js +51 -0
  14. package/lib/deploy/functions/backend.js +4 -4
  15. package/lib/deploy/functions/build.js +76 -9
  16. package/lib/deploy/functions/checkIam.js +6 -5
  17. package/lib/deploy/functions/params.js +22 -16
  18. package/lib/deploy/functions/prepare.js +1 -1
  19. package/lib/deploy/functions/release/fabricator.js +22 -5
  20. package/lib/deploy/functions/release/index.js +2 -0
  21. package/lib/deploy/functions/runtimes/discovery/index.js +1 -16
  22. package/lib/deploy/functions/runtimes/discovery/parsing.js +16 -0
  23. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +59 -131
  24. package/lib/deploy/functions/runtimes/node/parseTriggers.js +1 -1
  25. package/lib/emulator/auth/index.js +7 -2
  26. package/lib/emulator/auth/operations.js +10 -10
  27. package/lib/emulator/commandUtils.js +32 -15
  28. package/lib/emulator/constants.js +14 -6
  29. package/lib/emulator/controller.js +49 -17
  30. package/lib/emulator/downloadableEmulators.js +7 -7
  31. package/lib/emulator/eventarcEmulator.js +148 -0
  32. package/lib/emulator/extensionsEmulator.js +3 -1
  33. package/lib/emulator/functionsEmulator.js +44 -4
  34. package/lib/emulator/functionsEmulatorRuntime.js +12 -23
  35. package/lib/emulator/functionsEmulatorShared.js +6 -1
  36. package/lib/emulator/hub.js +7 -3
  37. package/lib/emulator/hubClient.js +2 -2
  38. package/lib/emulator/hubExport.js +22 -2
  39. package/lib/emulator/registry.js +1 -0
  40. package/lib/emulator/storage/apis/firebase.js +145 -129
  41. package/lib/emulator/storage/apis/gcloud.js +102 -42
  42. package/lib/emulator/storage/files.js +39 -17
  43. package/lib/emulator/storage/metadata.js +76 -55
  44. package/lib/emulator/storage/multipart.js +2 -2
  45. package/lib/emulator/storage/rules/runtime.js +12 -4
  46. package/lib/emulator/storage/server.js +2 -1
  47. package/lib/emulator/storage/upload.js +46 -9
  48. package/lib/emulator/types.js +3 -0
  49. package/lib/emulator/ui.js +7 -2
  50. package/lib/extensions/extensionsApi.js +2 -1
  51. package/lib/extensions/extensionsHelper.js +29 -1
  52. package/lib/functions/constants.js +14 -0
  53. package/lib/functions/env.js +9 -9
  54. package/lib/gcp/cloudfunctions.js +15 -18
  55. package/lib/gcp/cloudfunctionsv2.js +15 -18
  56. package/lib/gcp/cloudscheduler.js +32 -14
  57. package/lib/serve/index.js +15 -0
  58. package/lib/track.js +122 -3
  59. package/lib/utils.js +14 -1
  60. package/npm-shrinkwrap.json +542 -9
  61. package/package.json +5 -4
  62. package/schema/firebase-config.json +12 -0
  63. package/templates/extensions/CHANGELOG.md +1 -7
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.BLOCKING_LABEL = exports.CODEBASE_LABEL = exports.API_VERSION = void 0;
3
+ exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
4
4
  const clc = require("colorette");
5
5
  const error_1 = require("../error");
6
6
  const logger_1 = require("../logger");
@@ -11,18 +11,9 @@ const runtimes = require("../deploy/functions/runtimes");
11
11
  const projectConfig = require("../functions/projectConfig");
12
12
  const apiv2_1 = require("../apiv2");
13
13
  const api_1 = require("../api");
14
+ const constants_1 = require("../functions/constants");
14
15
  exports.API_VERSION = "v1";
15
- exports.CODEBASE_LABEL = "firebase-functions-codebase";
16
16
  const client = new apiv2_1.Client({ urlPrefix: api_1.functionsOrigin, apiVersion: exports.API_VERSION });
17
- exports.BLOCKING_LABEL = "deployment-blocking";
18
- const BLOCKING_LABEL_KEY_TO_EVENT = {
19
- "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate",
20
- "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn",
21
- };
22
- const BLOCKING_EVENT_TO_LABEL_KEY = {
23
- "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create",
24
- "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in",
25
- };
26
17
  function functionsOpLogReject(funcName, type, err) {
27
18
  var _a, _b;
28
19
  if (((_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) === 429) {
@@ -199,7 +190,7 @@ async function listAllFunctions(projectId) {
199
190
  }
200
191
  exports.listAllFunctions = listAllFunctions;
201
192
  function endpointFromFunction(gcfFunction) {
202
- var _a, _b, _c, _d, _e, _f, _g;
193
+ var _a, _b, _c, _d, _e, _f, _g, _h;
203
194
  const [, project, , region, , id] = gcfFunction.name.split("/");
204
195
  let trigger;
205
196
  let uri;
@@ -220,10 +211,10 @@ function endpointFromFunction(gcfFunction) {
220
211
  callableTrigger: {},
221
212
  };
222
213
  }
223
- else if ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[exports.BLOCKING_LABEL]) {
214
+ else if ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[constants_1.BLOCKING_LABEL]) {
224
215
  trigger = {
225
216
  blockingTrigger: {
226
- eventType: BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[exports.BLOCKING_LABEL]],
217
+ eventType: constants_1.BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[constants_1.BLOCKING_LABEL]],
227
218
  },
228
219
  };
229
220
  }
@@ -263,7 +254,10 @@ function endpointFromFunction(gcfFunction) {
263
254
  endpoint.vpc = { connector: gcfFunction.vpcConnector };
264
255
  proto.convertIfPresent(endpoint.vpc, gcfFunction, "egressSettings", "vpcConnectorEgressSettings", (raw) => raw);
265
256
  }
266
- endpoint.codebase = ((_g = gcfFunction.labels) === null || _g === void 0 ? void 0 : _g[exports.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
257
+ endpoint.codebase = ((_g = gcfFunction.labels) === null || _g === void 0 ? void 0 : _g[constants_1.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
258
+ if ((_h = gcfFunction.labels) === null || _h === void 0 ? void 0 : _h[constants_1.HASH_LABEL]) {
259
+ endpoint.hash = gcfFunction.labels[constants_1.HASH_LABEL];
260
+ }
267
261
  return endpoint;
268
262
  }
269
263
  exports.endpointFromFunction = endpointFromFunction;
@@ -312,7 +306,7 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
312
306
  }
313
307
  else if (backend.isBlockingTriggered(endpoint)) {
314
308
  gcfFunction.httpsTrigger = {};
315
- gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.BLOCKING_LABEL]: BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
309
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.BLOCKING_LABEL]: constants_1.BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
316
310
  }
317
311
  else {
318
312
  gcfFunction.httpsTrigger = {};
@@ -337,10 +331,13 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
337
331
  }
338
332
  const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE;
339
333
  if (codebase !== projectConfig.DEFAULT_CODEBASE) {
340
- gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.CODEBASE_LABEL]: codebase });
334
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.CODEBASE_LABEL]: codebase });
341
335
  }
342
336
  else {
343
- (_b = gcfFunction.labels) === null || _b === void 0 ? true : delete _b[exports.CODEBASE_LABEL];
337
+ (_b = gcfFunction.labels) === null || _b === void 0 ? true : delete _b[constants_1.CODEBASE_LABEL];
338
+ }
339
+ if (endpoint.hash) {
340
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.HASH_LABEL]: endpoint.hash });
344
341
  }
345
342
  return gcfFunction;
346
343
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.mebibytes = exports.BLOCKING_LABEL = exports.CODEBASE_LABEL = exports.API_VERSION = void 0;
3
+ exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.mebibytes = exports.API_VERSION = void 0;
4
4
  const clc = require("colorette");
5
5
  const apiv2_1 = require("../apiv2");
6
6
  const error_1 = require("../error");
@@ -12,22 +12,13 @@ const runtimes = require("../deploy/functions/runtimes");
12
12
  const proto = require("./proto");
13
13
  const utils = require("../utils");
14
14
  const projectConfig = require("../functions/projectConfig");
15
+ const constants_1 = require("../functions/constants");
15
16
  exports.API_VERSION = "v2alpha";
16
- exports.CODEBASE_LABEL = "firebase-functions-codebase";
17
17
  const client = new apiv2_1.Client({
18
18
  urlPrefix: api_1.functionsV2Origin,
19
19
  auth: true,
20
20
  apiVersion: exports.API_VERSION,
21
21
  });
22
- exports.BLOCKING_LABEL = "deployment-blocking";
23
- const BLOCKING_LABEL_KEY_TO_EVENT = {
24
- "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate",
25
- "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn",
26
- };
27
- const BLOCKING_EVENT_TO_LABEL_KEY = {
28
- "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create",
29
- "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in",
30
- };
31
22
  const BYTES_PER_UNIT = {
32
23
  "": 1,
33
24
  k: 1e3,
@@ -243,20 +234,23 @@ function functionFromEndpoint(endpoint, source) {
243
234
  gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-callable": "true" });
244
235
  }
245
236
  else if (backend.isBlockingTriggered(endpoint)) {
246
- gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.BLOCKING_LABEL]: BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
237
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.BLOCKING_LABEL]: constants_1.BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
247
238
  }
248
239
  const codebase = endpoint.codebase || projectConfig.DEFAULT_CODEBASE;
249
240
  if (codebase !== projectConfig.DEFAULT_CODEBASE) {
250
- gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.CODEBASE_LABEL]: codebase });
241
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.CODEBASE_LABEL]: codebase });
251
242
  }
252
243
  else {
253
- (_b = gcfFunction.labels) === null || _b === void 0 ? true : delete _b[exports.CODEBASE_LABEL];
244
+ (_b = gcfFunction.labels) === null || _b === void 0 ? true : delete _b[constants_1.CODEBASE_LABEL];
245
+ }
246
+ if (endpoint.hash) {
247
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.HASH_LABEL]: endpoint.hash });
254
248
  }
255
249
  return gcfFunction;
256
250
  }
257
251
  exports.functionFromEndpoint = functionFromEndpoint;
258
252
  function endpointFromFunction(gcfFunction) {
259
- var _a, _b, _c, _d, _e;
253
+ var _a, _b, _c, _d, _e, _f;
260
254
  const [, project, , region, , id] = gcfFunction.name.split("/");
261
255
  let trigger;
262
256
  if (((_a = gcfFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) === "true") {
@@ -274,10 +268,10 @@ function endpointFromFunction(gcfFunction) {
274
268
  callableTrigger: {},
275
269
  };
276
270
  }
277
- else if ((_d = gcfFunction.labels) === null || _d === void 0 ? void 0 : _d[exports.BLOCKING_LABEL]) {
271
+ else if ((_d = gcfFunction.labels) === null || _d === void 0 ? void 0 : _d[constants_1.BLOCKING_LABEL]) {
278
272
  trigger = {
279
273
  blockingTrigger: {
280
- eventType: BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[exports.BLOCKING_LABEL]],
274
+ eventType: constants_1.BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[constants_1.BLOCKING_LABEL]],
281
275
  },
282
276
  };
283
277
  }
@@ -341,7 +335,10 @@ function endpointFromFunction(gcfFunction) {
341
335
  endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector };
342
336
  proto.renameIfPresent(endpoint.vpc, gcfFunction.serviceConfig, "egressSettings", "vpcConnectorEgressSettings");
343
337
  }
344
- endpoint.codebase = ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[exports.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
338
+ endpoint.codebase = ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[constants_1.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
339
+ if ((_f = gcfFunction.labels) === null || _f === void 0 ? void 0 : _f[constants_1.HASH_LABEL]) {
340
+ endpoint.hash = gcfFunction.labels[constants_1.HASH_LABEL];
341
+ }
345
342
  return endpoint;
346
343
  }
347
344
  exports.endpointFromFunction = endpointFromFunction;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.jobFromEndpoint = exports.topicNameForEndpoint = exports.jobNameForEndpoint = exports.createOrReplaceJob = exports.updateJob = exports.getJob = exports.deleteJob = exports.createJob = void 0;
3
+ exports.jobFromEndpoint = exports.topicNameForEndpoint = exports.jobNameForEndpoint = exports.createOrReplaceJob = exports.getJob = exports.deleteJob = void 0;
4
4
  const _ = require("lodash");
5
5
  const error_1 = require("../error");
6
6
  const logger_1 = require("../logger");
@@ -8,15 +8,18 @@ const api_1 = require("../api");
8
8
  const apiv2_1 = require("../apiv2");
9
9
  const backend = require("../deploy/functions/backend");
10
10
  const proto = require("./proto");
11
+ const checkIam_1 = require("../deploy/functions/checkIam");
11
12
  const functional_1 = require("../functional");
12
13
  const VERSION = "v1";
13
- const DEFAULT_TIME_ZONE = "America/Los_Angeles";
14
+ const DEFAULT_TIME_ZONE_V1 = "America/Los_Angeles";
15
+ const DEFAULT_TIME_ZONE_V2 = "UTC";
14
16
  const apiClient = new apiv2_1.Client({ urlPrefix: api_1.cloudschedulerOrigin, apiVersion: VERSION });
15
17
  function createJob(job) {
16
18
  const strippedName = job.name.substring(0, job.name.lastIndexOf("/"));
17
- return apiClient.post(`/${strippedName}`, Object.assign({ timeZone: DEFAULT_TIME_ZONE }, job));
19
+ const json = job.pubsubTarget
20
+ ? Object.assign({ timeZone: DEFAULT_TIME_ZONE_V1 }, job) : Object.assign({ timeZone: DEFAULT_TIME_ZONE_V2 }, job);
21
+ return apiClient.post(`/${strippedName}`, json);
18
22
  }
19
- exports.createJob = createJob;
20
23
  function deleteJob(name) {
21
24
  return apiClient.delete(`/${name}`);
22
25
  }
@@ -28,14 +31,22 @@ function getJob(name) {
28
31
  }
29
32
  exports.getJob = getJob;
30
33
  function updateJob(job) {
31
- const fieldMasks = proto.fieldMasks(job, "pubsubTarget");
32
- return apiClient.patch(`/${job.name}`, Object.assign({ timeZone: DEFAULT_TIME_ZONE }, job), {
34
+ let fieldMasks;
35
+ let json;
36
+ if (job.pubsubTarget) {
37
+ fieldMasks = proto.fieldMasks(job, "pubsubTarget");
38
+ json = Object.assign({ timeZone: DEFAULT_TIME_ZONE_V1 }, job);
39
+ }
40
+ else {
41
+ fieldMasks = proto.fieldMasks(job, "httpTarget");
42
+ json = Object.assign({ timeZone: DEFAULT_TIME_ZONE_V2 }, job);
43
+ }
44
+ return apiClient.patch(`/${job.name}`, json, {
33
45
  queryParams: {
34
46
  updateMask: fieldMasks.join(","),
35
47
  },
36
48
  });
37
49
  }
38
- exports.updateJob = updateJob;
39
50
  async function createOrReplaceJob(job) {
40
51
  var _a, _b;
41
52
  const jobName = job.name.split("/").pop();
@@ -56,7 +67,7 @@ async function createOrReplaceJob(job) {
56
67
  return newJob;
57
68
  }
58
69
  if (!job.timeZone) {
59
- job.timeZone = DEFAULT_TIME_ZONE;
70
+ job.timeZone = job.pubsubTarget ? DEFAULT_TIME_ZONE_V1 : DEFAULT_TIME_ZONE_V2;
60
71
  }
61
72
  if (!needUpdate(existingJob.body, job)) {
62
73
  logger_1.logger.debug(`scheduler job ${jobName} is up to date, no changes required`);
@@ -90,9 +101,9 @@ function needUpdate(existingJob, newJob) {
90
101
  }
91
102
  return false;
92
103
  }
93
- function jobNameForEndpoint(endpoint, appEngineLocation) {
104
+ function jobNameForEndpoint(endpoint, location) {
94
105
  const id = backend.scheduleIdForFunction(endpoint);
95
- return `projects/${endpoint.project}/locations/${appEngineLocation}/jobs/${id}`;
106
+ return `projects/${endpoint.project}/locations/${location}/jobs/${id}`;
96
107
  }
97
108
  exports.jobNameForEndpoint = jobNameForEndpoint;
98
109
  function topicNameForEndpoint(endpoint) {
@@ -100,10 +111,11 @@ function topicNameForEndpoint(endpoint) {
100
111
  return `projects/${endpoint.project}/topics/${id}`;
101
112
  }
102
113
  exports.topicNameForEndpoint = topicNameForEndpoint;
103
- function jobFromEndpoint(endpoint, appEngineLocation) {
114
+ function jobFromEndpoint(endpoint, location, projectNumber) {
104
115
  const job = {};
116
+ job.name = jobNameForEndpoint(endpoint, location);
105
117
  if (endpoint.platform === "gcfv1") {
106
- job.name = jobNameForEndpoint(endpoint, appEngineLocation);
118
+ job.timeZone = endpoint.scheduleTrigger.timeZone || DEFAULT_TIME_ZONE_V1;
107
119
  job.pubsubTarget = {
108
120
  topicName: topicNameForEndpoint(endpoint),
109
121
  attributes: {
@@ -112,7 +124,14 @@ function jobFromEndpoint(endpoint, appEngineLocation) {
112
124
  };
113
125
  }
114
126
  else if (endpoint.platform === "gcfv2") {
115
- throw new error_1.FirebaseError("Do not know how to create a scheduled GCFv2 function");
127
+ job.timeZone = endpoint.scheduleTrigger.timeZone || DEFAULT_TIME_ZONE_V2;
128
+ job.httpTarget = {
129
+ uri: endpoint.uri,
130
+ httpMethod: "POST",
131
+ oidcToken: {
132
+ serviceAccountEmail: (0, checkIam_1.getDefaultComputeServiceAgent)(projectNumber),
133
+ },
134
+ };
116
135
  }
117
136
  else {
118
137
  (0, functional_1.assertExhaustive)(endpoint.platform);
@@ -121,7 +140,6 @@ function jobFromEndpoint(endpoint, appEngineLocation) {
121
140
  throw new error_1.FirebaseError("Cannot create a scheduler job without a schedule:" + JSON.stringify(endpoint));
122
141
  }
123
142
  job.schedule = endpoint.scheduleTrigger.schedule;
124
- job.timeZone = endpoint.scheduleTrigger.timeZone || DEFAULT_TIME_ZONE;
125
143
  if (endpoint.scheduleTrigger.retryConfig) {
126
144
  job.retryConfig = {};
127
145
  proto.copyIfPresent(job.retryConfig, endpoint.scheduleTrigger.retryConfig, "maxDoublings", "retryCount");
@@ -4,6 +4,9 @@ exports.serve = void 0;
4
4
  const logger_1 = require("../logger");
5
5
  const frameworks_1 = require("../frameworks");
6
6
  const previews_1 = require("../previews");
7
+ const track_1 = require("../track");
8
+ const projectUtils_1 = require("../projectUtils");
9
+ const constants_1 = require("../emulator/constants");
7
10
  const { FunctionsServer } = require("./functions");
8
11
  const TARGETS = {
9
12
  hosting: require("./hosting"),
@@ -17,12 +20,24 @@ async function serve(options) {
17
20
  [].concat(options.config.get("hosting")).some((it) => it.source)) {
18
21
  await (0, frameworks_1.prepareFrameworks)(targetNames, options, options);
19
22
  }
23
+ const isDemoProject = constants_1.Constants.isDemoProject((0, projectUtils_1.getProjectId)(options) || "");
24
+ targetNames.forEach((targetName) => {
25
+ void (0, track_1.trackEmulator)("emulator_run", {
26
+ emulator_name: targetName,
27
+ is_demo_project: String(isDemoProject),
28
+ });
29
+ });
20
30
  await Promise.all(targetNames.map((targetName) => {
21
31
  return TARGETS[targetName].start(options);
22
32
  }));
23
33
  await Promise.all(targetNames.map((targetName) => {
24
34
  return TARGETS[targetName].connect();
25
35
  }));
36
+ void (0, track_1.trackEmulator)("emulators_started", {
37
+ count: targetNames.length,
38
+ count_all: targetNames.length,
39
+ is_demo_project: String(isDemoProject),
40
+ });
26
41
  await new Promise((resolve) => {
27
42
  process.on("SIGINT", () => {
28
43
  logger_1.logger.info("Shutting down...");
package/lib/track.js CHANGED
@@ -1,16 +1,25 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.track = void 0;
3
+ exports.emulatorSession = exports.trackEmulator = exports.track = exports.usageEnabled = exports.EMULATOR_GA4_MEASUREMENT_ID = void 0;
4
+ const node_fetch_1 = require("node-fetch");
4
5
  const ua = require("universal-analytics");
5
6
  const uuid_1 = require("uuid");
7
+ const auth_1 = require("./auth");
6
8
  const configstore_1 = require("./configstore");
9
+ const logger_1 = require("./logger");
7
10
  const pkg = require("../package.json");
11
+ exports.EMULATOR_GA4_MEASUREMENT_ID = process.env.FIREBASE_EMULATOR_GA4_MEASUREMENT_ID || "G-KYP2JMPFC0";
12
+ function usageEnabled() {
13
+ return !!configstore_1.configstore.get("usage");
14
+ }
15
+ exports.usageEnabled = usageEnabled;
16
+ const FIREBASE_ANALYTICS_UA = process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3";
8
17
  let anonId = configstore_1.configstore.get("analytics-uuid");
9
18
  if (!anonId) {
10
19
  anonId = (0, uuid_1.v4)();
11
20
  configstore_1.configstore.set("analytics-uuid", anonId);
12
21
  }
13
- const visitor = ua(process.env.FIREBASE_ANALYTICS_UA || "UA-29174744-3", anonId, {
22
+ const visitor = ua(FIREBASE_ANALYTICS_UA, anonId, {
14
23
  strictCidFormat: false,
15
24
  https: true,
16
25
  });
@@ -19,7 +28,7 @@ visitor.set("cd2", process.version);
19
28
  visitor.set("cd3", process.env.FIREPIT_VERSION || "none");
20
29
  function track(action, label, duration = 0) {
21
30
  return new Promise((resolve) => {
22
- if (configstore_1.configstore.get("tokens") && configstore_1.configstore.get("usage")) {
31
+ if (configstore_1.configstore.get("tokens") && usageEnabled()) {
23
32
  visitor.event("Firebase CLI " + pkg.version, action, label, duration).send(() => {
24
33
  resolve();
25
34
  });
@@ -30,3 +39,113 @@ function track(action, label, duration = 0) {
30
39
  });
31
40
  }
32
41
  exports.track = track;
42
+ const EMULATOR_GA4_API_SECRET = process.env.FIREBASE_EMULATOR_GA4_API_SECRET || "2V_zBYc4TdeoppzDaIu0zw";
43
+ const EMULATOR_GA4_USER_PROPS = {
44
+ node_platform: {
45
+ value: process.platform,
46
+ },
47
+ node_version: {
48
+ value: process.version,
49
+ },
50
+ cli_version: {
51
+ value: pkg.version,
52
+ },
53
+ firepit_version: {
54
+ value: process.env.FIREPIT_VERSION || "none",
55
+ },
56
+ };
57
+ async function trackEmulator(eventName, params) {
58
+ const session = emulatorSession();
59
+ if (!session) {
60
+ return;
61
+ }
62
+ const oldTotalEngagementSeconds = session.totalEngagementSeconds;
63
+ session.totalEngagementSeconds = process.uptime();
64
+ session.commandName = (params === null || params === void 0 ? void 0 : params.command_name) || session.commandName;
65
+ const search = `?api_secret=${EMULATOR_GA4_API_SECRET}&measurement_id=${session.measurementId}`;
66
+ const validate = session.validateOnly ? "debug/" : "";
67
+ const url = `https://www.google-analytics.com/${validate}mp/collect${search}`;
68
+ const body = {
69
+ timestamp_micros: `${Date.now()}000`,
70
+ client_id: session.clientId,
71
+ user_properties: Object.assign(Object.assign({}, EMULATOR_GA4_USER_PROPS), { java_major_version: session.javaMajorVersion
72
+ ? { value: session.javaMajorVersion }
73
+ : undefined }),
74
+ validationBehavior: session.validateOnly ? "ENFORCE_RECOMMENDATIONS" : undefined,
75
+ events: [
76
+ {
77
+ name: eventName,
78
+ params: Object.assign({ session_id: session.sessionId, engagement_time_msec: (session.totalEngagementSeconds - oldTotalEngagementSeconds)
79
+ .toFixed(3)
80
+ .replace(".", "")
81
+ .replace(/^0+/, ""), debug_mode: session.debugMode ? true : undefined, command_name: session.commandName }, params),
82
+ },
83
+ ],
84
+ };
85
+ if (session.validateOnly) {
86
+ logger_1.logger.info(`Sending Analytics for event ${eventName}`, params, body);
87
+ }
88
+ try {
89
+ const response = await (0, node_fetch_1.default)(url, {
90
+ method: "POST",
91
+ headers: {
92
+ "content-type": "application/json;charset=UTF-8",
93
+ },
94
+ body: JSON.stringify(body),
95
+ });
96
+ if (session.validateOnly) {
97
+ if (!response.ok) {
98
+ logger_1.logger.warn(`Analytics validation HTTP error: ${response.status}`);
99
+ }
100
+ const respBody = await response.text();
101
+ logger_1.logger.info(`Analytics validation result: ${respBody}`);
102
+ }
103
+ }
104
+ catch (e) {
105
+ if (session.validateOnly) {
106
+ throw e;
107
+ }
108
+ return;
109
+ }
110
+ }
111
+ exports.trackEmulator = trackEmulator;
112
+ function emulatorSession() {
113
+ const validateOnly = !!process.env.FIREBASE_CLI_MP_VALIDATE;
114
+ if (!usageEnabled()) {
115
+ if (validateOnly) {
116
+ logger_1.logger.warn("Google Analytics is DISABLED. To enable, (re)login and opt in to collection.");
117
+ }
118
+ return;
119
+ }
120
+ if (!currentEmulatorSession) {
121
+ let clientId = configstore_1.configstore.get("emulator-analytics-clientId");
122
+ if (!clientId) {
123
+ clientId = (0, uuid_1.v4)();
124
+ configstore_1.configstore.set("emulator-analytics-clientId", clientId);
125
+ }
126
+ currentEmulatorSession = {
127
+ measurementId: exports.EMULATOR_GA4_MEASUREMENT_ID,
128
+ clientId,
129
+ sessionId: (Math.random() * Number.MAX_SAFE_INTEGER).toFixed(0),
130
+ totalEngagementSeconds: 0,
131
+ debugMode: isDebugMode(),
132
+ validateOnly,
133
+ };
134
+ }
135
+ return currentEmulatorSession;
136
+ }
137
+ exports.emulatorSession = emulatorSession;
138
+ let currentEmulatorSession = undefined;
139
+ function isDebugMode() {
140
+ const account = (0, auth_1.getGlobalDefaultAccount)();
141
+ if (account === null || account === void 0 ? void 0 : account.user.email.endsWith("@google.com")) {
142
+ try {
143
+ require("../tsconfig.json");
144
+ logger_1.logger.info(`Using Google Analytics in DEBUG mode. Emulators (+ UI) events will be shown in GA Debug View only.`);
145
+ return true;
146
+ }
147
+ catch (_a) {
148
+ }
149
+ }
150
+ return false;
151
+ }
package/lib/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
3
+ exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
4
4
  const _ = require("lodash");
5
5
  const url = require("url");
6
6
  const clc = require("colorette");
@@ -248,6 +248,19 @@ async function promiseWhile(action, check, interval = 2500) {
248
248
  });
249
249
  }
250
250
  exports.promiseWhile = promiseWhile;
251
+ function withTimeout(timeoutMs, promise) {
252
+ return new Promise((resolve, reject) => {
253
+ const timeout = setTimeout(() => reject(new Error("Timed out.")), timeoutMs);
254
+ promise.then((value) => {
255
+ clearTimeout(timeout);
256
+ resolve(value);
257
+ }, (err) => {
258
+ clearTimeout(timeout);
259
+ reject(err);
260
+ });
261
+ });
262
+ }
263
+ exports.withTimeout = withTimeout;
251
264
  async function promiseProps(obj) {
252
265
  const resultObj = {};
253
266
  const promises = Object.keys(obj).map(async (key) => {