@hot-updater/firebase 1.0.0-rc.32 → 1.0.0-rc.34
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/dist/firebase/functions/index.cjs +97 -147
- package/dist/index.d.cts +7 -19
- package/dist/index.d.mts +7 -19
- package/package.json +7 -7
|
@@ -4524,7 +4524,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
|
|
|
4524
4524
|
};
|
|
4525
4525
|
//#endregion
|
|
4526
4526
|
//#region ../../packages/server/dist/version.mjs
|
|
4527
|
-
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.
|
|
4527
|
+
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.34";
|
|
4528
4528
|
//#endregion
|
|
4529
4529
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
4530
4530
|
/**
|
|
@@ -7686,8 +7686,8 @@ const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(va
|
|
|
7686
7686
|
const isString = (value) => typeof value === "string";
|
|
7687
7687
|
const isDatabaseHttpResponse = (value) => isDatabaseJsonObject(value) && (value.resource === "catalog" || value.resource === "artifact") && isString(value.path) && Number.isSafeInteger(value.status) && (value.body === null || isString(value.body)) && typeof value.body_truncated === "boolean" && Number.isSafeInteger(value.received_at_ms);
|
|
7688
7688
|
/** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
|
|
7689
|
-
const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString)
|
|
7690
|
-
const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean")
|
|
7689
|
+
const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString);
|
|
7690
|
+
const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
|
|
7691
7691
|
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7692
7692
|
const isIdentity$1 = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
|
|
7693
7693
|
const isTextOrNull = (value) => value === null || typeof value === "string";
|
|
@@ -7892,7 +7892,7 @@ const createValidatedInsightsModel = (model) => ({
|
|
|
7892
7892
|
async getReleaseActivity(input) {
|
|
7893
7893
|
if (!isRecord$2(input) || !hasOnlyKeys(input, ["releases"]) || !Array.isArray(input.releases) || input.releases.length < 1 || input.releases.length > 100 || !input.releases.every(isReleaseReference)) invalidQuery();
|
|
7894
7894
|
const result = await model.getReleaseActivity(input);
|
|
7895
|
-
if (!isRecord$2(result) || !isTimestamp(result.measuredAtMs) || !Array.isArray(result.data) || result.data.some((item) => !isRecord$2(item) || !isReleaseReference(item.release) || !isRecord$2(item.metrics) || !isTimestamp(item.metrics.downloads) || !isTimestamp(item.metrics.
|
|
7895
|
+
if (!isRecord$2(result) || !isTimestamp(result.measuredAtMs) || !Array.isArray(result.data) || result.data.some((item) => !isRecord$2(item) || !isReleaseReference(item.release) || !isRecord$2(item.metrics) || !isTimestamp(item.metrics.downloads) || !isTimestamp(item.metrics.applies) || !isTimestamp(item.metrics.failedLaunches))) invalidResult();
|
|
7896
7896
|
return result;
|
|
7897
7897
|
},
|
|
7898
7898
|
async getAppUsage(input) {
|
|
@@ -8027,7 +8027,7 @@ const DELIVERIES = [
|
|
|
8027
8027
|
"manifest",
|
|
8028
8028
|
"archive"
|
|
8029
8029
|
];
|
|
8030
|
-
/** A storage error code
|
|
8030
|
+
/** A storage error code: letters, digits, and `._-`. */
|
|
8031
8031
|
const CODE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
8032
8032
|
const readCode = (value) => typeof value === "string" && CODE.test(value) ? value : void 0;
|
|
8033
8033
|
const readMetadata = (payload) => isRecord$1(payload.metadata) ? payload.metadata : {};
|
|
@@ -8043,7 +8043,6 @@ function readFailure(value) {
|
|
|
8043
8043
|
const originCode = readCode(failure.originCode);
|
|
8044
8044
|
const errorMessage = typeof failure.errorMessage === "string" && failure.errorMessage.length <= 2048 ? failure.errorMessage : void 0;
|
|
8045
8045
|
const errorStack = typeof failure.errorStack === "string" && failure.errorStack.length <= 4096 ? failure.errorStack : void 0;
|
|
8046
|
-
const previousProcessExit = readCode(failure.previousProcessExit);
|
|
8047
8046
|
return {
|
|
8048
8047
|
stage: oneOf(STAGES, failure.stage),
|
|
8049
8048
|
reason: oneOf(REASONS, failure.reason),
|
|
@@ -8052,8 +8051,7 @@ function readFailure(value) {
|
|
|
8052
8051
|
...failure.transport == null ? {} : { transport: oneOf(TRANSPORTS, failure.transport) },
|
|
8053
8052
|
...originCode === void 0 ? {} : { originCode },
|
|
8054
8053
|
...errorMessage === void 0 ? {} : { errorMessage },
|
|
8055
|
-
...errorStack === void 0 ? {} : { errorStack }
|
|
8056
|
-
...previousProcessExit === void 0 ? {} : { previousProcessExit }
|
|
8054
|
+
...errorStack === void 0 ? {} : { errorStack }
|
|
8057
8055
|
};
|
|
8058
8056
|
}
|
|
8059
8057
|
function readHttpResponse(value) {
|
|
@@ -8143,21 +8141,11 @@ function requireEvent(payload) {
|
|
|
8143
8141
|
...Object.keys(read).length === 0 ? {} : { metadata: read }
|
|
8144
8142
|
};
|
|
8145
8143
|
}
|
|
8146
|
-
case "UPDATE_APPLIED":
|
|
8144
|
+
case "UPDATE_APPLIED":
|
|
8145
|
+
case "RECOVERED": return {
|
|
8147
8146
|
...movement(),
|
|
8148
8147
|
type
|
|
8149
8148
|
};
|
|
8150
|
-
case "RECOVERED": {
|
|
8151
|
-
const previousProcessExit = readCode(metadata.previousProcessExit);
|
|
8152
|
-
return {
|
|
8153
|
-
...movement(),
|
|
8154
|
-
type,
|
|
8155
|
-
...previousProcessExit === void 0 ? {} : { metadata: {
|
|
8156
|
-
...httpMetadata,
|
|
8157
|
-
previousProcessExit
|
|
8158
|
-
} }
|
|
8159
|
-
};
|
|
8160
|
-
}
|
|
8161
8149
|
case "UPDATE_FAILED": return {
|
|
8162
8150
|
...movement(),
|
|
8163
8151
|
type,
|
|
@@ -8223,24 +8211,13 @@ function createBundleEventRow(input) {
|
|
|
8223
8211
|
}
|
|
8224
8212
|
};
|
|
8225
8213
|
}
|
|
8226
|
-
case "UPDATE_APPLIED":
|
|
8214
|
+
case "UPDATE_APPLIED":
|
|
8215
|
+
case "RECOVERED": return {
|
|
8227
8216
|
...base,
|
|
8228
8217
|
from_bundle_id: input.fromBundleId,
|
|
8229
8218
|
type: input.type,
|
|
8230
8219
|
metadata
|
|
8231
8220
|
};
|
|
8232
|
-
case "RECOVERED": {
|
|
8233
|
-
const exit = input.metadata?.previousProcessExit;
|
|
8234
|
-
return {
|
|
8235
|
-
...base,
|
|
8236
|
-
from_bundle_id: input.fromBundleId,
|
|
8237
|
-
type: input.type,
|
|
8238
|
-
metadata: {
|
|
8239
|
-
...metadata,
|
|
8240
|
-
...exit === void 0 ? {} : { previous_process_exit: exit }
|
|
8241
|
-
}
|
|
8242
|
-
};
|
|
8243
|
-
}
|
|
8244
8221
|
case "UPDATE_FAILED": {
|
|
8245
8222
|
const { failure } = input.metadata;
|
|
8246
8223
|
return {
|
|
@@ -8257,8 +8234,7 @@ function createBundleEventRow(input) {
|
|
|
8257
8234
|
...failure.resource === void 0 ? {} : { resource: failure.resource },
|
|
8258
8235
|
...failure.httpStatus === void 0 ? {} : { http_status: failure.httpStatus },
|
|
8259
8236
|
...failure.transport === void 0 ? {} : { transport: failure.transport },
|
|
8260
|
-
...failure.originCode === void 0 ? {} : { origin_code: failure.originCode }
|
|
8261
|
-
...failure.previousProcessExit === void 0 ? {} : { previous_process_exit: failure.previousProcessExit }
|
|
8237
|
+
...failure.originCode === void 0 ? {} : { origin_code: failure.originCode }
|
|
8262
8238
|
}
|
|
8263
8239
|
}
|
|
8264
8240
|
};
|
|
@@ -8422,10 +8398,9 @@ const toFailure = (failure) => ({
|
|
|
8422
8398
|
...failure.resource === void 0 ? {} : { resource: failure.resource },
|
|
8423
8399
|
...failure.http_status === void 0 ? {} : { httpStatus: failure.http_status },
|
|
8424
8400
|
...failure.transport === void 0 ? {} : { transport: failure.transport },
|
|
8425
|
-
...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code }
|
|
8426
|
-
...failure.previous_process_exit === void 0 ? {} : { previousProcessExit: failure.previous_process_exit }
|
|
8401
|
+
...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code }
|
|
8427
8402
|
});
|
|
8428
|
-
/** What a row's metadata adds for its type: a failure
|
|
8403
|
+
/** What a row's metadata adds for its type: a failure or a delivery. */
|
|
8429
8404
|
const eventDetails = ({ type, metadata }) => ({
|
|
8430
8405
|
...metadata.http_response !== void 0 ? { httpResponse: {
|
|
8431
8406
|
resource: metadata.http_response.resource,
|
|
@@ -8437,8 +8412,7 @@ const eventDetails = ({ type, metadata }) => ({
|
|
|
8437
8412
|
} } : {},
|
|
8438
8413
|
...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
|
|
8439
8414
|
...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
|
|
8440
|
-
...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
|
|
8441
|
-
...type === "RECOVERED" && metadata.previous_process_exit !== void 0 ? { previousProcessExit: metadata.previous_process_exit } : {}
|
|
8415
|
+
...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
|
|
8442
8416
|
});
|
|
8443
8417
|
const toEventHistoryRow = (row) => ({
|
|
8444
8418
|
...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
|
|
@@ -8861,29 +8835,42 @@ const window = {
|
|
|
8861
8835
|
eq: ["identity"],
|
|
8862
8836
|
sort: ["bucket_start_ms"]
|
|
8863
8837
|
};
|
|
8838
|
+
/** What a release or channel row counts in every period kind. */
|
|
8839
|
+
const WINDOW_COUNTERS = [
|
|
8840
|
+
"downloads",
|
|
8841
|
+
"failed_launches",
|
|
8842
|
+
"patch_downloads",
|
|
8843
|
+
"patch_fallbacks"
|
|
8844
|
+
];
|
|
8864
8845
|
/**
|
|
8865
|
-
*
|
|
8866
|
-
*
|
|
8867
|
-
*
|
|
8868
|
-
* lifetime row counts launches, which the bundle list reads and no windowed
|
|
8869
|
-
* read sums. It also counts the release's failed updates (`failed_updates`),
|
|
8870
|
-
* since no breakdown row outlives the raw period; windowed reads sum
|
|
8871
|
-
* `insights_failures` instead.
|
|
8846
|
+
* Hourly or daily counters: a release or channel row counts downloads,
|
|
8847
|
+
* launches that crashed and recovered (`failed_launches`), and downloads a
|
|
8848
|
+
* patch delivered and those that fell back from one.
|
|
8872
8849
|
*/
|
|
8873
8850
|
const counters = (retention) => defineAggregate(identityFields, {
|
|
8851
|
+
key: ["identity", "bucket_start_ms"],
|
|
8852
|
+
counters: WINDOW_COUNTERS,
|
|
8853
|
+
shards: COUNTER_SHARDS,
|
|
8854
|
+
batched: true,
|
|
8855
|
+
indexes: { window },
|
|
8856
|
+
retention
|
|
8857
|
+
});
|
|
8858
|
+
/**
|
|
8859
|
+
* A release's counters since its first report, at bucket 0: the windowed
|
|
8860
|
+
* counters, its applies, which the bundle list reads, and its failed updates
|
|
8861
|
+
* (`failed_updates`), since no breakdown row outlives the raw period; windowed
|
|
8862
|
+
* reads sum `insights_failures` instead.
|
|
8863
|
+
*/
|
|
8864
|
+
const lifetimeCounters = () => defineAggregate(identityFields, {
|
|
8874
8865
|
key: ["identity", "bucket_start_ms"],
|
|
8875
8866
|
counters: [
|
|
8876
|
-
|
|
8877
|
-
"
|
|
8878
|
-
"
|
|
8879
|
-
"failed_updates",
|
|
8880
|
-
"patch_downloads",
|
|
8881
|
-
"patch_fallbacks"
|
|
8867
|
+
...WINDOW_COUNTERS,
|
|
8868
|
+
"applies",
|
|
8869
|
+
"failed_updates"
|
|
8882
8870
|
],
|
|
8883
8871
|
shards: COUNTER_SHARDS,
|
|
8884
8872
|
batched: true,
|
|
8885
|
-
indexes: { window }
|
|
8886
|
-
...retention === void 0 ? {} : { retention }
|
|
8873
|
+
indexes: { window }
|
|
8887
8874
|
});
|
|
8888
8875
|
/**
|
|
8889
8876
|
* Unique-installation sketches of one period kind: a usage row's active
|
|
@@ -8907,14 +8894,12 @@ const lifetimeSketches = () => defineAggregate(identityFields, {
|
|
|
8907
8894
|
indexes: { window }
|
|
8908
8895
|
});
|
|
8909
8896
|
/**
|
|
8910
|
-
* Update failures by what failed,
|
|
8911
|
-
*
|
|
8912
|
-
*
|
|
8913
|
-
*
|
|
8914
|
-
*
|
|
8915
|
-
*
|
|
8916
|
-
* `launch` and its reason the exit reason, with an empty detail. Every index
|
|
8917
|
-
* is in key order, so its counters stay blind increments.
|
|
8897
|
+
* Update failures by what failed, a row an hour. `release_id` is the failed
|
|
8898
|
+
* update's target release, or empty for a failed check or an unknown target.
|
|
8899
|
+
* `stage` and `reason` say where and why it failed, and `detail` what else
|
|
8900
|
+
* the client knew, as the JSON array `[resource, httpStatus, originCode,
|
|
8901
|
+
* transport]`. Every index is in key order, so its counters stay blind
|
|
8902
|
+
* increments.
|
|
8918
8903
|
*/
|
|
8919
8904
|
const insightsFailures = (retention) => defineAggregate({
|
|
8920
8905
|
platform: {
|
|
@@ -9114,7 +9099,7 @@ const createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTIO
|
|
|
9114
9099
|
insights_overview_daily: counters(daily),
|
|
9115
9100
|
insights_sketches_daily: sketches(daily),
|
|
9116
9101
|
/** A release's counters since its first event, and its failed installations: kept. */
|
|
9117
|
-
insights_overview_lifetime:
|
|
9102
|
+
insights_overview_lifetime: lifetimeCounters(),
|
|
9118
9103
|
insights_sketches_lifetime: lifetimeSketches(),
|
|
9119
9104
|
insights_distribution: insightsDistribution(daily),
|
|
9120
9105
|
insights_latest_by_bundle: insightsLatestByBundle(daily),
|
|
@@ -9157,7 +9142,7 @@ const insightsOverviewDeltas = (event) => {
|
|
|
9157
9142
|
counters.set(key, {
|
|
9158
9143
|
identity,
|
|
9159
9144
|
downloads: (previous?.downloads ?? 0) + values.downloads,
|
|
9160
|
-
|
|
9145
|
+
applies: (previous?.applies ?? 0) + values.applies,
|
|
9161
9146
|
failedLaunches: (previous?.failedLaunches ?? 0) + values.failedLaunches,
|
|
9162
9147
|
activityIdentity: previous?.activityIdentity ?? values.activityIdentity
|
|
9163
9148
|
});
|
|
@@ -9165,7 +9150,7 @@ const insightsOverviewDeltas = (event) => {
|
|
|
9165
9150
|
const metric = (releaseId, values) => {
|
|
9166
9151
|
const windowed = {
|
|
9167
9152
|
...values,
|
|
9168
|
-
|
|
9153
|
+
applies: 0
|
|
9169
9154
|
};
|
|
9170
9155
|
if (releaseId !== null) for (const [periodKind, start] of [["lifetime", 0], ["hour", bucketStartMs]]) append({
|
|
9171
9156
|
scopeKind: "release",
|
|
@@ -9192,21 +9177,19 @@ const insightsOverviewDeltas = (event) => {
|
|
|
9192
9177
|
};
|
|
9193
9178
|
if (event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
|
|
9194
9179
|
downloads: 1,
|
|
9195
|
-
|
|
9180
|
+
applies: 0,
|
|
9196
9181
|
failedLaunches: 0
|
|
9197
9182
|
});
|
|
9198
|
-
else if (event.type
|
|
9199
|
-
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
});
|
|
9209
|
-
}
|
|
9183
|
+
else if (event.type === "UPDATE_APPLIED") metric(event.to_release_id, {
|
|
9184
|
+
downloads: 0,
|
|
9185
|
+
applies: 1,
|
|
9186
|
+
failedLaunches: 0
|
|
9187
|
+
});
|
|
9188
|
+
else if (event.type === "RECOVERED") metric(event.from_release_id, {
|
|
9189
|
+
downloads: 0,
|
|
9190
|
+
applies: 0,
|
|
9191
|
+
failedLaunches: 1
|
|
9192
|
+
});
|
|
9210
9193
|
for (const platform of [event.platform, "all"]) for (const appVersionKind of ["specific", "all"]) append({
|
|
9211
9194
|
scopeKind: "usage",
|
|
9212
9195
|
releaseKind: "all",
|
|
@@ -9219,7 +9202,7 @@ const insightsOverviewDeltas = (event) => {
|
|
|
9219
9202
|
bucketStartMs
|
|
9220
9203
|
}, {
|
|
9221
9204
|
downloads: 0,
|
|
9222
|
-
|
|
9205
|
+
applies: 0,
|
|
9223
9206
|
failedLaunches: 0,
|
|
9224
9207
|
activityIdentity: event.install_id
|
|
9225
9208
|
});
|
|
@@ -9336,7 +9319,7 @@ const countEvent = (tx, event) => {
|
|
|
9336
9319
|
const counters = {
|
|
9337
9320
|
...Object.fromEntries([
|
|
9338
9321
|
["downloads", delta.downloads],
|
|
9339
|
-
["
|
|
9322
|
+
["applies", delta.applies],
|
|
9340
9323
|
["failed_launches", delta.failedLaunches]
|
|
9341
9324
|
].filter(([, value]) => value !== 0)),
|
|
9342
9325
|
...delta.downloads === 0 ? {} : patchCounters(event)
|
|
@@ -9459,23 +9442,6 @@ const countFailure = (tx, event) => {
|
|
|
9459
9442
|
detail: failureDetail(failure)
|
|
9460
9443
|
}, { events: 1 }, { shardBy });
|
|
9461
9444
|
};
|
|
9462
|
-
/**
|
|
9463
|
-
* A recovery's exit reason, which Android 11+ reports, in the breakdown of
|
|
9464
|
-
* the release whose launch failed: stage `launch`, the reason as its reason.
|
|
9465
|
-
*/
|
|
9466
|
-
const countExit = (tx, event) => {
|
|
9467
|
-
const exit = event.metadata.previous_process_exit;
|
|
9468
|
-
if (event.type !== "RECOVERED" || exit === void 0) return;
|
|
9469
|
-
tx.aggregate("insights_failures", {
|
|
9470
|
-
platform: event.platform,
|
|
9471
|
-
channel: event.channel,
|
|
9472
|
-
bucket_start_ms: hourOf(event.received_at_ms),
|
|
9473
|
-
release_id: event.from_release_id ?? "",
|
|
9474
|
-
stage: "launch",
|
|
9475
|
-
reason: exit,
|
|
9476
|
-
detail: ""
|
|
9477
|
-
}, { events: 1 }, { shardBy: event.install_id });
|
|
9478
|
-
};
|
|
9479
9445
|
const isNewer = (event, head) => event.received_at_ms !== head.received_at_ms ? event.received_at_ms > head.received_at_ms : compareUtf8(event.id, head.id) > 0;
|
|
9480
9446
|
/** What an UNCHANGED report must share with its installation's head to repeat it. */
|
|
9481
9447
|
const REPEATED_FIELDS = [
|
|
@@ -9520,7 +9486,6 @@ const recordEvent = (db, event) => {
|
|
|
9520
9486
|
return;
|
|
9521
9487
|
}
|
|
9522
9488
|
countEvent(tx, event);
|
|
9523
|
-
countExit(tx, event);
|
|
9524
9489
|
if (previous !== null && !isNewer(event, previous)) return;
|
|
9525
9490
|
if (previous === null) tx.create("bundle_event_heads", event);
|
|
9526
9491
|
else {
|
|
@@ -9895,7 +9860,7 @@ const lifetimeMetrics = async (db, release) => {
|
|
|
9895
9860
|
})).rows;
|
|
9896
9861
|
return {
|
|
9897
9862
|
downloads: lifetime?.downloads ?? 0,
|
|
9898
|
-
|
|
9863
|
+
applies: lifetime?.applies ?? 0,
|
|
9899
9864
|
failedLaunches: lifetime?.failed_launches ?? 0
|
|
9900
9865
|
};
|
|
9901
9866
|
};
|
|
@@ -10004,21 +9969,18 @@ const isText = (value, maxLength) => typeof value === "string" && value.length >
|
|
|
10004
9969
|
const failed = (...rows) => countDistinct(mergeDistinct(rows.flat().map((row) => row.failed_users)));
|
|
10005
9970
|
/** Entries of a count map, most first, then in key order. */
|
|
10006
9971
|
const mostFirst = (counts) => [...counts].sort(([leftKey, left], [rightKey, right]) => right.events - left.events || compareUtf8(leftKey, rightKey)).map(([, entry]) => entry);
|
|
10007
|
-
/**
|
|
9972
|
+
/** The stages a failure row records: where an update failed. */
|
|
9973
|
+
const FAILURE_STAGES = /* @__PURE__ */ new Set([
|
|
9974
|
+
"check",
|
|
9975
|
+
"download",
|
|
9976
|
+
"install",
|
|
9977
|
+
"unknown"
|
|
9978
|
+
]);
|
|
9979
|
+
/** Failure rows as their breakdown by stage, reason, and detail. */
|
|
10008
9980
|
const breakdownOf = (rows) => {
|
|
10009
9981
|
const failures = /* @__PURE__ */ new Map();
|
|
10010
|
-
const exits = /* @__PURE__ */ new Map();
|
|
10011
9982
|
for (const { stage, reason, detail, events } of rows) {
|
|
10012
9983
|
if (events <= 0) continue;
|
|
10013
|
-
if (stage === "launch") {
|
|
10014
|
-
const exit = exits.get(reason) ?? {
|
|
10015
|
-
value: reason,
|
|
10016
|
-
events: 0
|
|
10017
|
-
};
|
|
10018
|
-
exit.events += events;
|
|
10019
|
-
exits.set(reason, exit);
|
|
10020
|
-
continue;
|
|
10021
|
-
}
|
|
10022
9984
|
const key = JSON.stringify([stage, reason]);
|
|
10023
9985
|
const entry = failures.get(key) ?? {
|
|
10024
9986
|
value: {
|
|
@@ -10037,37 +9999,30 @@ const breakdownOf = (rows) => {
|
|
|
10037
9999
|
entry.details.set(detail, known);
|
|
10038
10000
|
failures.set(key, entry);
|
|
10039
10001
|
}
|
|
10040
|
-
return {
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
}),
|
|
10058
|
-
byExitReason: mostFirst(exits).map(({ value, events }) => ({
|
|
10059
|
-
exitReason: value,
|
|
10060
|
-
events
|
|
10061
|
-
}))
|
|
10062
|
-
};
|
|
10002
|
+
return mostFirst(failures).map(({ value, events }) => {
|
|
10003
|
+
const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
|
|
10004
|
+
return {
|
|
10005
|
+
...value,
|
|
10006
|
+
events,
|
|
10007
|
+
details: mostFirst(details).map(({ value: detail, events }) => {
|
|
10008
|
+
const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
|
|
10009
|
+
return {
|
|
10010
|
+
resource,
|
|
10011
|
+
httpStatus,
|
|
10012
|
+
originCode,
|
|
10013
|
+
transport,
|
|
10014
|
+
events
|
|
10015
|
+
};
|
|
10016
|
+
})
|
|
10017
|
+
};
|
|
10018
|
+
});
|
|
10063
10019
|
};
|
|
10064
10020
|
/**
|
|
10065
10021
|
* A release's or a channel's update failures. Since a release's first
|
|
10066
10022
|
* report: its lifetime counters and failed installations. Over a time range
|
|
10067
|
-
* of at most 30 days: failures, failed checks, the breakdown by stage,
|
|
10068
|
-
* reason, and detail,
|
|
10069
|
-
*
|
|
10070
|
-
* failed installations from their sketches. A release reads hourly rows, a
|
|
10023
|
+
* of at most 30 days: failures, failed checks, and the breakdown by stage,
|
|
10024
|
+
* reason, and detail, all summed from the breakdown's hourly rows; downloads
|
|
10025
|
+
* from the counters; failed installations from their sketches. A release reads hourly rows, a
|
|
10071
10026
|
* channel daily rows past 48 hours like its activity; the breakdown's rows
|
|
10072
10027
|
* are hourly, so its coverage is the raw period's.
|
|
10073
10028
|
*/
|
|
@@ -10154,13 +10109,12 @@ const getUpdateFailures = async (db, input, now, retention) => {
|
|
|
10154
10109
|
}))
|
|
10155
10110
|
]);
|
|
10156
10111
|
const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
|
|
10157
|
-
const scoped =
|
|
10112
|
+
const scoped = rows.filter((row) => FAILURE_STAGES.has(row.stage) && (releaseId === void 0 || row.release_id === releaseId));
|
|
10158
10113
|
const failuresAt = (counted) => scoped.reduce((sum, row) => sum + (counted(row.stage) ? Math.max(0, row.events) : 0), 0);
|
|
10159
|
-
const { breakdown, byExitReason } = breakdownOf(scoped);
|
|
10160
10114
|
return {
|
|
10161
10115
|
coverage: coverageOf(timeRange.start, kept.hour),
|
|
10162
10116
|
measuredAtMs: at,
|
|
10163
|
-
failedUpdates: failuresAt((stage) => stage !== "check"
|
|
10117
|
+
failedUpdates: failuresAt((stage) => stage !== "check"),
|
|
10164
10118
|
failedInstallations: failed(failures),
|
|
10165
10119
|
downloads: total("downloads"),
|
|
10166
10120
|
patchDownloads: total("patch_downloads"),
|
|
@@ -10170,11 +10124,7 @@ const getUpdateFailures = async (db, input, now, retention) => {
|
|
|
10170
10124
|
failedInstallations: failed(checks),
|
|
10171
10125
|
activeInstallations: countDistinct(mergeDistinct(active.map((row) => row.activity_users)))
|
|
10172
10126
|
} } : {},
|
|
10173
|
-
breakdown
|
|
10174
|
-
recoveries: {
|
|
10175
|
-
failedLaunches: total("failed_launches"),
|
|
10176
|
-
byExitReason
|
|
10177
|
-
}
|
|
10127
|
+
breakdown: breakdownOf(scoped)
|
|
10178
10128
|
};
|
|
10179
10129
|
};
|
|
10180
10130
|
//#endregion
|
|
@@ -10437,7 +10387,7 @@ const insights = (options = {}) => {
|
|
|
10437
10387
|
const plugin = definePlugin({
|
|
10438
10388
|
id: "insights",
|
|
10439
10389
|
namespace: false,
|
|
10440
|
-
schemaVersion: "1.
|
|
10390
|
+
schemaVersion: "1.0.0",
|
|
10441
10391
|
schema: createInsightsSchema(retention),
|
|
10442
10392
|
init: ({ db, now }) => {
|
|
10443
10393
|
const api = createInsightsApi(db, now, retention);
|
package/dist/index.d.cts
CHANGED
|
@@ -53,8 +53,6 @@ type BundleEventFailure = DatabaseJsonObject & {
|
|
|
53
53
|
readonly origin_code?: string;
|
|
54
54
|
readonly error_message?: string;
|
|
55
55
|
readonly error_stack?: string;
|
|
56
|
-
/** Android's `ApplicationExitInfo` reason for the process before this one. */
|
|
57
|
-
readonly previous_process_exit?: string;
|
|
58
56
|
};
|
|
59
57
|
type DatabaseHttpResponse = DatabaseJsonObject & {
|
|
60
58
|
readonly resource: "catalog" | "artifact";
|
|
@@ -77,8 +75,6 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
|
|
|
77
75
|
readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
|
|
78
76
|
/** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
|
|
79
77
|
readonly patch_fallback?: boolean;
|
|
80
|
-
/** `RECOVERED`: Android's `ApplicationExitInfo` reason for the crashed process. */
|
|
81
|
-
readonly previous_process_exit?: string;
|
|
82
78
|
};
|
|
83
79
|
type BundleEventRowBase = {
|
|
84
80
|
readonly id: string;
|
|
@@ -203,8 +199,11 @@ type InsightsCoverage = {
|
|
|
203
199
|
* which are kept.
|
|
204
200
|
*/
|
|
205
201
|
interface ReleaseActivityMetrics {
|
|
202
|
+
/** Download reports of the release. */
|
|
206
203
|
readonly downloads: number;
|
|
207
|
-
|
|
204
|
+
/** Apply reports of the release: installations that started running it. */
|
|
205
|
+
readonly applies: number;
|
|
206
|
+
/** Launches that crashed on the release and recovered to the one before. */
|
|
208
207
|
readonly failedLaunches: number;
|
|
209
208
|
}
|
|
210
209
|
interface InsightsGetReleaseActivityInput {
|
|
@@ -304,17 +303,6 @@ interface InsightsUpdateFailures {
|
|
|
304
303
|
};
|
|
305
304
|
/** Over a time range: failures by stage and reason, most first. */
|
|
306
305
|
readonly breakdown?: readonly InsightsFailureBreakdown[];
|
|
307
|
-
/**
|
|
308
|
-
* Over a time range: failed launches, and the recoveries whose crashed
|
|
309
|
-
* process exited for a reason Android 11+ reported, by reason, most first.
|
|
310
|
-
*/
|
|
311
|
-
readonly recoveries?: {
|
|
312
|
-
readonly failedLaunches: number;
|
|
313
|
-
readonly byExitReason: readonly {
|
|
314
|
-
readonly exitReason: string;
|
|
315
|
-
readonly events: number;
|
|
316
|
-
}[];
|
|
317
|
-
};
|
|
318
306
|
}
|
|
319
307
|
//#endregion
|
|
320
308
|
//#region ../api-keys/dist/server/model.d.mts
|
|
@@ -482,7 +470,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
482
470
|
readonly bucket_start_ms: {
|
|
483
471
|
readonly type: "integer";
|
|
484
472
|
};
|
|
485
|
-
}, "downloads" | "
|
|
473
|
+
}, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
|
|
486
474
|
readonly window: {
|
|
487
475
|
readonly eq: readonly ["identity"];
|
|
488
476
|
readonly sort: readonly ["bucket_start_ms"];
|
|
@@ -510,7 +498,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
510
498
|
readonly bucket_start_ms: {
|
|
511
499
|
readonly type: "integer";
|
|
512
500
|
};
|
|
513
|
-
}, "downloads" | "
|
|
501
|
+
}, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
|
|
514
502
|
readonly window: {
|
|
515
503
|
readonly eq: readonly ["identity"];
|
|
516
504
|
readonly sort: readonly ["bucket_start_ms"];
|
|
@@ -538,7 +526,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
538
526
|
readonly bucket_start_ms: {
|
|
539
527
|
readonly type: "integer";
|
|
540
528
|
};
|
|
541
|
-
}, "downloads" | "
|
|
529
|
+
}, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
|
|
542
530
|
readonly window: {
|
|
543
531
|
readonly eq: readonly ["identity"];
|
|
544
532
|
readonly sort: readonly ["bucket_start_ms"];
|
package/dist/index.d.mts
CHANGED
|
@@ -53,8 +53,6 @@ type BundleEventFailure = DatabaseJsonObject & {
|
|
|
53
53
|
readonly origin_code?: string;
|
|
54
54
|
readonly error_message?: string;
|
|
55
55
|
readonly error_stack?: string;
|
|
56
|
-
/** Android's `ApplicationExitInfo` reason for the process before this one. */
|
|
57
|
-
readonly previous_process_exit?: string;
|
|
58
56
|
};
|
|
59
57
|
type DatabaseHttpResponse = DatabaseJsonObject & {
|
|
60
58
|
readonly resource: "catalog" | "artifact";
|
|
@@ -77,8 +75,6 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
|
|
|
77
75
|
readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
|
|
78
76
|
/** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
|
|
79
77
|
readonly patch_fallback?: boolean;
|
|
80
|
-
/** `RECOVERED`: Android's `ApplicationExitInfo` reason for the crashed process. */
|
|
81
|
-
readonly previous_process_exit?: string;
|
|
82
78
|
};
|
|
83
79
|
type BundleEventRowBase = {
|
|
84
80
|
readonly id: string;
|
|
@@ -203,8 +199,11 @@ type InsightsCoverage = {
|
|
|
203
199
|
* which are kept.
|
|
204
200
|
*/
|
|
205
201
|
interface ReleaseActivityMetrics {
|
|
202
|
+
/** Download reports of the release. */
|
|
206
203
|
readonly downloads: number;
|
|
207
|
-
|
|
204
|
+
/** Apply reports of the release: installations that started running it. */
|
|
205
|
+
readonly applies: number;
|
|
206
|
+
/** Launches that crashed on the release and recovered to the one before. */
|
|
208
207
|
readonly failedLaunches: number;
|
|
209
208
|
}
|
|
210
209
|
interface InsightsGetReleaseActivityInput {
|
|
@@ -304,17 +303,6 @@ interface InsightsUpdateFailures {
|
|
|
304
303
|
};
|
|
305
304
|
/** Over a time range: failures by stage and reason, most first. */
|
|
306
305
|
readonly breakdown?: readonly InsightsFailureBreakdown[];
|
|
307
|
-
/**
|
|
308
|
-
* Over a time range: failed launches, and the recoveries whose crashed
|
|
309
|
-
* process exited for a reason Android 11+ reported, by reason, most first.
|
|
310
|
-
*/
|
|
311
|
-
readonly recoveries?: {
|
|
312
|
-
readonly failedLaunches: number;
|
|
313
|
-
readonly byExitReason: readonly {
|
|
314
|
-
readonly exitReason: string;
|
|
315
|
-
readonly events: number;
|
|
316
|
-
}[];
|
|
317
|
-
};
|
|
318
306
|
}
|
|
319
307
|
//#endregion
|
|
320
308
|
//#region ../api-keys/dist/server/model.d.mts
|
|
@@ -482,7 +470,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
482
470
|
readonly bucket_start_ms: {
|
|
483
471
|
readonly type: "integer";
|
|
484
472
|
};
|
|
485
|
-
}, "downloads" | "
|
|
473
|
+
}, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
|
|
486
474
|
readonly window: {
|
|
487
475
|
readonly eq: readonly ["identity"];
|
|
488
476
|
readonly sort: readonly ["bucket_start_ms"];
|
|
@@ -510,7 +498,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
510
498
|
readonly bucket_start_ms: {
|
|
511
499
|
readonly type: "integer";
|
|
512
500
|
};
|
|
513
|
-
}, "downloads" | "
|
|
501
|
+
}, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
|
|
514
502
|
readonly window: {
|
|
515
503
|
readonly eq: readonly ["identity"];
|
|
516
504
|
readonly sort: readonly ["bucket_start_ms"];
|
|
@@ -538,7 +526,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
538
526
|
readonly bucket_start_ms: {
|
|
539
527
|
readonly type: "integer";
|
|
540
528
|
};
|
|
541
|
-
}, "downloads" | "
|
|
529
|
+
}, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
|
|
542
530
|
readonly window: {
|
|
543
531
|
readonly eq: readonly ["identity"];
|
|
544
532
|
readonly sort: readonly ["bucket_start_ms"];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/firebase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.34",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"types": "dist/index.d.cts",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"hono": "4.12.34",
|
|
40
|
-
"@hot-updater/cli-tools": "1.0.0-rc.
|
|
41
|
-
"@hot-updater/
|
|
42
|
-
"@hot-updater/
|
|
43
|
-
"@hot-updater/server": "1.0.0-rc.
|
|
40
|
+
"@hot-updater/cli-tools": "1.0.0-rc.34",
|
|
41
|
+
"@hot-updater/protocol": "1.0.0-rc.30",
|
|
42
|
+
"@hot-updater/plugin-core": "1.0.0-rc.30",
|
|
43
|
+
"@hot-updater/server": "1.0.0-rc.34"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"firebase-tools": "15.29.0",
|
|
56
56
|
"fkill": "^9.0.0",
|
|
57
57
|
"mime": "^4.0.4",
|
|
58
|
-
"@hot-updater/mock": "1.0.0-rc.
|
|
59
|
-
"@hot-updater/test-utils": "1.0.0-rc.
|
|
58
|
+
"@hot-updater/mock": "1.0.0-rc.30",
|
|
59
|
+
"@hot-updater/test-utils": "1.0.0-rc.32"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"firebase-admin": "*",
|