@hot-updater/console 1.0.0-rc.2 → 1.0.0-rc.21
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/.output/nitro.json +1 -1
- package/.output/public/assets/ConsoleFeatureUnavailable-D0LPL1Gy.js +1 -0
- package/.output/public/assets/HashValueDisplay-BmuB4FW-.js +1 -0
- package/.output/public/assets/InsightsInfo--v4SC07c.js +1 -0
- package/.output/public/assets/InsightsPageHeader-dU83CAQY.js +1 -0
- package/.output/public/assets/NotFoundPage-B5mhGG2a.js +1 -0
- package/.output/public/assets/api-D-vOVlQ4.js +1 -0
- package/.output/public/assets/api-keys-Csh8ARKU.js +1 -0
- package/.output/public/assets/api-keys-OMPUoA_A.js +1 -0
- package/.output/public/assets/arrow-left-Bwq9X1eS.js +1 -0
- package/.output/public/assets/arrow-up-right-BkOVn4if.js +1 -0
- package/.output/public/assets/card-C_7d6O9a.js +1 -0
- package/.output/public/assets/console-features-api-C1jRZTGw.js +1 -0
- package/.output/public/assets/dialog-DE8wFN_P.js +1 -0
- package/.output/public/assets/dist-C7ENGzpS.js +1 -0
- package/.output/public/assets/empty-CVd5NlQD.js +1 -0
- package/.output/public/assets/field-C06q1iC9.js +1 -0
- package/.output/public/assets/index-BeWlQtO6.js +11 -0
- package/.output/public/assets/input-group-Bi61biIA.js +1 -0
- package/.output/public/assets/insights-BjnAtC0f.js +2 -0
- package/.output/public/assets/insights-D86m11yH.js +42 -0
- package/.output/public/assets/insights-DpYstMoY.js +1 -0
- package/.output/public/assets/insights-recovery-rpc-BIm5_ALc.js +1 -0
- package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
- package/.output/public/assets/insights-usage-rpc-VHj8vyBt.js +1 -0
- package/.output/public/assets/insights_.distribution-ADcrX48c.js +2 -0
- package/.output/public/assets/insights_.distribution-CuuJUN6Y.js +1 -0
- package/.output/public/assets/insights_.distribution-DJRjuXsO.js +1 -0
- package/.output/public/assets/installations-BSBBzqWs.js +2 -0
- package/.output/public/assets/installations-DqIE6WcO.js +1 -0
- package/.output/public/assets/installations-UsELbDvw.js +1 -0
- package/.output/public/assets/loader-circle-CnCBxzHp.js +1 -0
- package/.output/public/assets/mutation-Csp7xUNh.js +1 -0
- package/.output/public/assets/preload-helper-B-cOmpLH.js +1 -0
- package/.output/public/assets/refresh-cw-FF1NtzlQ.js +1 -0
- package/.output/public/assets/routes-CoN9XHP_.js +2 -0
- package/.output/public/assets/routes-DT1jB0Zi.js +1 -0
- package/.output/public/assets/sidebar-D8mJRF15.js +3 -0
- package/.output/public/assets/styles-Bwt7v6U5.css +2 -0
- package/.output/public/assets/table-WBgfvgXg.js +1 -0
- package/.output/public/assets/useCompositeListItem-CloBmDwn.js +1 -0
- package/.output/public/assets/useMutation-DO8cvsTx.js +1 -0
- package/.output/public/assets/useOpenInteractionType-ReGr9GzJ.js +1 -0
- package/.output/public/assets/valueToPercent-BvPAi4bP.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-DMRD4e4f.mjs} +51 -47
- package/.output/server/_libs/@base-ui/react+[...].mjs +7044 -3622
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +3803 -4023
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +141 -98
- package/.output/server/_libs/d3-array.mjs +231 -0
- package/.output/server/_libs/d3-color.mjs +330 -0
- package/.output/server/_libs/d3-format.mjs +252 -0
- package/.output/server/_libs/d3-interpolate.mjs +207 -0
- package/.output/server/_libs/d3-path.mjs +77 -0
- package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
- package/.output/server/_libs/d3-shape.mjs +818 -0
- package/.output/server/_libs/decimal.js-light.mjs +884 -0
- package/.output/server/_libs/es-toolkit.mjs +1159 -0
- package/.output/server/_libs/eventemitter3.mjs +236 -0
- package/.output/server/_libs/immer.mjs +532 -0
- package/.output/server/_libs/lucide-react.mjs +46 -32
- package/.output/server/_libs/next-themes.mjs +2 -2
- package/.output/server/_libs/react-redux+[...].mjs +205 -0
- package/.output/server/_libs/recharts+[...].mjs +14350 -0
- package/.output/server/_libs/sonner.mjs +2 -2
- package/.output/server/_libs/tanstack__history.mjs +68 -41
- package/.output/server/_libs/tanstack__query-core.mjs +1618 -309
- package/.output/server/_libs/tanstack__react-query.mjs +262 -22
- package/.output/server/_libs/verkit.mjs +99 -1
- package/.output/server/_runtime.mjs +10 -1
- package/.output/server/_ssr/ConsoleFeatureUnavailable-Du7jNPNS.mjs +58 -0
- package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-GQvFkXoi.mjs} +3 -3
- package/.output/server/_ssr/InsightsInfo-Do7QSaFR.mjs +37 -0
- package/.output/server/_ssr/InsightsPageHeader-DSUWUWRc.mjs +154 -0
- package/.output/server/_ssr/NotFoundPage-DsF4Vrhg.mjs +37 -0
- package/.output/server/_ssr/api-BEiVs0H7.mjs +203 -0
- package/.output/server/_ssr/api-keys-BrvBWGRf.mjs +5 -0
- package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-K5v32nf7.mjs} +49 -9
- package/.output/server/_ssr/api-keys-rpc-B96bBllm.mjs +52 -0
- package/.output/server/_ssr/api-rpc-DcUe280B.mjs +296 -0
- package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-C68W56DP.mjs} +6 -6
- package/.output/server/_ssr/auth.server-DM1J6NwR.mjs +5712 -0
- package/.output/server/_ssr/bundleActivity-CpuY9CTP.mjs +19 -0
- package/.output/server/_ssr/card-CPAEKx0w.mjs +49 -0
- package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
- package/.output/server/_ssr/config.server-DDCZk3Sq.mjs +36 -0
- package/.output/server/_ssr/console-access-Da-2Qd7q.mjs +17 -0
- package/.output/server/_ssr/console-features-Dzu2iUGD.mjs +55 -0
- package/.output/server/_ssr/console-features-api-xAsSgFa_.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-qARGpB24.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
- package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
- package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
- package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-q__BJ7mQ.mjs} +2 -2
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
- package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-BW9I-pMQ.mjs} +10 -10
- package/.output/server/_ssr/empty-DcBTAooP.mjs +49 -0
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
- package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-QCDBVpXA.mjs} +63 -51
- package/.output/server/_ssr/getBundleChildren-DIAGCF4a.mjs +22 -0
- package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-hCk2nUoU.mjs} +2 -2
- package/.output/server/_ssr/insights-BfBO0RFr.mjs +1238 -0
- package/.output/server/_ssr/insights-DsAU3-TB.mjs +14 -0
- package/.output/server/_ssr/insights-RIfg447W.mjs +5 -0
- package/.output/server/_ssr/insights-api-CDn-QQHU.mjs +105 -0
- package/.output/server/_ssr/insights-failures-BBGMhuUr.mjs +44 -0
- package/.output/server/_ssr/insights-failures-rpc-rA2dctK2.mjs +21 -0
- package/.output/server/_ssr/insights-recovery-Dpk6Mfb9.mjs +28 -0
- package/.output/server/_ssr/insights-recovery-rpc-BCaF-xSd.mjs +46 -0
- package/.output/server/_ssr/insights-recovery-rpc-CcgYyUHk.mjs +20 -0
- package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-keycmwzw.mjs} +22 -14
- package/.output/server/_ssr/insights-search-BRg5VsNe.mjs +25 -0
- package/.output/server/_ssr/insights-usage-cbw9n2Fq.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-BKgV1QDf.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-D-JYachy.mjs +41 -0
- package/.output/server/_ssr/insightsRecovery-B7oMc8d8.mjs +45 -0
- package/.output/server/_ssr/insightsUsage-Oa7kk5ek.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-BIqOqRW0.mjs +335 -0
- package/.output/server/_ssr/insights_.distribution-CuUrDa7P.mjs +5 -0
- package/.output/server/_ssr/insights_.distribution-RT_6x_ud.mjs +14 -0
- package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-BO5CjX2z.mjs} +517 -54
- package/.output/server/_ssr/installations-BjaHiIfh.mjs +5 -0
- package/.output/server/_ssr/installations-Dt5luA-N.mjs +69 -0
- package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
- package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
- package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-DfVAixxe.mjs} +135 -139
- package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-C6LstNp8.mjs} +176 -455
- package/.output/server/_ssr/routes-Q3Uhmwwz.mjs +292 -0
- package/.output/server/_ssr/runtime.server-fL0o6I2b.mjs +1459 -0
- package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
- package/.output/server/_ssr/select-CUlInmjk.mjs +107 -0
- package/.output/server/_ssr/server-C43wq9ft.mjs +8330 -0
- package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-DKbx2ach.mjs} +117 -63
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
- package/.output/server/_ssr/table-NGWV8B-S.mjs +53 -0
- package/.output/server/_ssr/updateFailures-Jwe2Baab.mjs +24 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-WQ5qGn8n.mjs +124 -0
- package/.output/server/index.mjs +267 -162
- package/LICENSE +13 -14
- package/README.md +82 -72
- package/lib/index.d.mts +23 -11
- package/lib/vite-internal.mjs +13 -3
- package/lib/vite.d.mts +2 -0
- package/package.json +14 -14
- package/src/components/AppSidebar.tsx +100 -71
- package/src/components/ConsoleAccessPage.tsx +5 -1
- package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
- package/src/components/NotFoundPage.tsx +7 -7
- package/src/components/features/FeatureSlots.tsx +99 -0
- package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
- package/src/components/features/bundles/BundleMetadata.tsx +8 -5
- package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
- package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
- package/src/components/features/insights/AppUsage.tsx +370 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +117 -37
- package/src/components/features/insights/EventHistoryCard.tsx +112 -26
- package/src/components/features/insights/EventHistoryList.tsx +2 -2
- package/src/components/features/insights/InsightsControls.tsx +265 -102
- package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
- package/src/components/features/insights/InsightsInfo.tsx +48 -0
- package/src/components/features/insights/InsightsOverview.tsx +176 -173
- package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
- package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
- package/src/components/features/insights/InstallationHistoryCard.tsx +52 -10
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +188 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
- package/src/components/features/insights/UpdateFailures.tsx +319 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -7
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/empty.tsx +101 -0
- package/src/components/ui/scroll-area.tsx +52 -0
- package/src/components/ui/sidebar.tsx +22 -29
- package/src/components/ui/tabs.tsx +81 -0
- package/src/index.ts +26 -19
- package/src/lib/api-keys-api.ts +0 -28
- package/src/lib/api-keys-rpc.ts +27 -38
- package/src/lib/api-rpc.ts +189 -266
- package/src/lib/api.ts +19 -102
- package/src/lib/bundle-activity.ts +31 -0
- package/src/lib/console-access.ts +20 -0
- package/src/lib/console-features-api.ts +68 -0
- package/src/lib/console-features-rpc.ts +13 -0
- package/src/lib/console-features.ts +91 -0
- package/src/lib/insights-api.ts +37 -1
- package/src/lib/insights-failures-rpc.ts +21 -0
- package/src/lib/insights-failures.ts +129 -0
- package/src/lib/insights-recovery-rpc.ts +51 -0
- package/src/lib/insights-recovery.ts +68 -0
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +58 -0
- package/src/lib/insights-usage-rpc.ts +29 -0
- package/src/lib/insights-usage.ts +74 -0
- package/src/lib/insights-view.ts +38 -2
- package/src/lib/scroll-restoration.ts +21 -0
- package/src/lib/server/adminInsights.ts +124 -0
- package/src/lib/server/auth.server.ts +13 -5
- package/src/lib/server/bundleActivity.ts +37 -0
- package/src/lib/server/config.server.ts +21 -74
- package/src/lib/server/console-feature-apis.ts +59 -0
- package/src/lib/server/console-runtime.server.ts +82 -38
- package/src/lib/server/downloadBundle.ts +16 -15
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsRecovery.ts +55 -0
- package/src/lib/server/insightsUsage.ts +63 -0
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseReachability.ts +8 -7
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +31 -0
- package/src/routeTree.gen.ts +44 -44
- package/src/router.tsx +3 -2
- package/src/routes/-installations-search.ts +32 -8
- package/src/routes/-releases-search.ts +76 -29
- package/src/routes/__root.tsx +10 -4
- package/src/routes/api/bundles/$bundleId/download.ts +2 -5
- package/src/routes/api-keys.tsx +5 -2
- package/src/routes/index.tsx +167 -104
- package/src/routes/insights.tsx +100 -59
- package/src/routes/insights_.distribution.tsx +398 -0
- package/src/routes/installations.tsx +98 -41
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
- package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
- package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
- package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
- package/.output/public/assets/card-Dhr_eFYj.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
- package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
- package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
- package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
- package/.output/public/assets/dist-C_NydL0e.js +0 -1
- package/.output/public/assets/index-DdAz-CXv.js +0 -10
- package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
- package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
- package/.output/public/assets/installations-CE33wgbY.js +0 -2
- package/.output/public/assets/installations-CStZja8x.js +0 -1
- package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
- package/.output/public/assets/link-CI8idAUi.js +0 -1
- package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
- package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
- package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
- package/.output/public/assets/routes-DGVXdzpw.js +0 -2
- package/.output/public/assets/routes-DTlwOr8I.js +0 -1
- package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
- package/.output/public/assets/signing-CMf4aqDO.js +0 -1
- package/.output/public/assets/styles-BCvbtKsm.css +0 -2
- package/.output/public/assets/table-2vcV89QB.js +0 -1
- package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
- package/.output/public/assets/x-DCd0j8c6.js +0 -1
- package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
- package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
- package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
- package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
- package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
- package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-BTGwBgzx.mjs +0 -66
- package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
- package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
- package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
- package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
- package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
- package/.output/server/_ssr/server-D_C2rMzf.mjs +0 -7468
- package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
- package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
- package/src/components/features/signing/BundleSigningPage.tsx +0 -139
- package/src/lib/bundle-signing-api.ts +0 -14
- package/src/lib/bundle-signing-rpc.ts +0 -16
- package/src/lib/server/bundle-signing.server.ts +0 -23
- package/src/lib/server/deleteBundle.ts +0 -145
- package/src/routes/signing.tsx +0 -37
|
@@ -1,98 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assertStorageOperations,
|
|
3
|
-
createDatabaseClient,
|
|
4
|
-
type DatabaseClient,
|
|
5
|
-
type StoragePluginWith,
|
|
6
|
-
} from "@hot-updater/plugin-core";
|
|
1
|
+
import type { HotUpdaterCoreApi } from "@hot-updater/plugin-core";
|
|
7
2
|
import { getRequest } from "@tanstack/react-start/server";
|
|
8
3
|
|
|
9
|
-
import type { ResolvedHotUpdaterConsoleConfig } from "../../index";
|
|
10
4
|
import { requireConsoleAccess } from "./auth.server";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
type ResolvedConsoleConfig
|
|
14
|
-
|
|
15
|
-
};
|
|
5
|
+
import {
|
|
6
|
+
resolveConsoleConfig,
|
|
7
|
+
type ResolvedConsoleConfig,
|
|
8
|
+
} from "./console-runtime.server";
|
|
9
|
+
import type { ConsoleRuntime } from "./runtime.server";
|
|
16
10
|
|
|
17
11
|
let configPromise: Promise<ResolvedConsoleConfig> | null = null;
|
|
18
|
-
let
|
|
19
|
-
let
|
|
20
|
-
typeof import("./runtime.server").createRuntimeHotUpdater
|
|
21
|
-
> | null = null;
|
|
22
|
-
let apiKeyStore: ReturnType<
|
|
23
|
-
typeof import("./runtime.server").createApiKeyStore
|
|
24
|
-
> | null = null;
|
|
25
|
-
let apiKeyStoreResolved = false;
|
|
26
|
-
let storagePluginPromise: Promise<
|
|
27
|
-
StoragePluginWith<"get" | "put" | "exists" | "delete">
|
|
28
|
-
> | null = null;
|
|
12
|
+
let core: HotUpdaterCoreApi | null = null;
|
|
13
|
+
let runtime: ConsoleRuntime | null = null;
|
|
29
14
|
|
|
30
15
|
const loadCachedConfig = async (request: Request) => {
|
|
31
16
|
if (!configPromise) {
|
|
32
|
-
configPromise = resolveConsoleConfig(request)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}))
|
|
37
|
-
.catch((error) => {
|
|
38
|
-
configPromise = null;
|
|
39
|
-
throw error;
|
|
40
|
-
});
|
|
17
|
+
configPromise = resolveConsoleConfig(request).catch((error) => {
|
|
18
|
+
configPromise = null;
|
|
19
|
+
throw error;
|
|
20
|
+
});
|
|
41
21
|
}
|
|
42
22
|
|
|
43
23
|
return configPromise;
|
|
44
24
|
};
|
|
45
25
|
|
|
46
|
-
const loadCachedStoragePlugin = async (config: ResolvedConsoleConfig) => {
|
|
47
|
-
if (!storagePluginPromise) {
|
|
48
|
-
storagePluginPromise = Promise.resolve(config.storage)
|
|
49
|
-
.then((storagePlugin) => {
|
|
50
|
-
assertStorageOperations(storagePlugin, [
|
|
51
|
-
"get",
|
|
52
|
-
"put",
|
|
53
|
-
"exists",
|
|
54
|
-
"delete",
|
|
55
|
-
]);
|
|
56
|
-
return storagePlugin;
|
|
57
|
-
})
|
|
58
|
-
.catch((error) => {
|
|
59
|
-
storagePluginPromise = null;
|
|
60
|
-
throw error;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return storagePluginPromise;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
26
|
export const prepareConfig = async (request: Request = getRequest()) => {
|
|
68
27
|
try {
|
|
69
28
|
await requireConsoleAccess(request);
|
|
70
29
|
const config = await loadCachedConfig(request);
|
|
71
30
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!hotUpdater) {
|
|
77
|
-
const { createRuntimeHotUpdater } = await import("./runtime.server");
|
|
78
|
-
hotUpdater = createRuntimeHotUpdater(config);
|
|
79
|
-
}
|
|
31
|
+
// Bundles, releases, catalogs, and channels: core assembled as the
|
|
32
|
+
// server's, on the server's own path, or a self-hosted server's admin API.
|
|
33
|
+
core ??= config.core;
|
|
80
34
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
35
|
+
// The console's features: those of the plugins its config lists.
|
|
36
|
+
if (!runtime) {
|
|
37
|
+
const { createConsoleRuntime } = await import("./runtime.server");
|
|
38
|
+
runtime = createConsoleRuntime(config);
|
|
85
39
|
}
|
|
86
40
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
config,
|
|
91
|
-
databaseClient,
|
|
92
|
-
hotUpdater,
|
|
93
|
-
apiKeyStore,
|
|
94
|
-
storagePlugin,
|
|
95
|
-
};
|
|
41
|
+
// Each bundle file is read and deleted with its protocol's storage.
|
|
42
|
+
return { config, core, runtime, storage: config.storage };
|
|
96
43
|
} catch (error) {
|
|
97
44
|
if (
|
|
98
45
|
!(
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { RemoteDatabase } from "@hot-updater/plugin-core";
|
|
2
|
+
import type { ApiKeyManagementAPI } from "@hot-updater/server/plugins/api-keys";
|
|
3
|
+
import {
|
|
4
|
+
createInsightsModel,
|
|
5
|
+
createInsightsProvider,
|
|
6
|
+
type InsightsApi,
|
|
7
|
+
type InsightsModel,
|
|
8
|
+
} from "@hot-updater/server/plugins/insights";
|
|
9
|
+
|
|
10
|
+
import type { ConsoleFeature } from "../console-features";
|
|
11
|
+
import {
|
|
12
|
+
type ConsoleInsightsReads,
|
|
13
|
+
createAdminInsightsReads,
|
|
14
|
+
} from "./adminInsights";
|
|
15
|
+
|
|
16
|
+
/** A GET on a self-hosted server's admin handler. */
|
|
17
|
+
export type FetchAdmin = RemoteDatabase["fetchAdmin"];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* What serves one console feature: `local` builds it from the API of the
|
|
21
|
+
* feature's plugin, which the console assembles over the database as the
|
|
22
|
+
* server does; `remote` builds it over a self-hosted server's admin API, for
|
|
23
|
+
* a feature that API serves.
|
|
24
|
+
*/
|
|
25
|
+
export interface ConsoleFeatureApi<TPluginApi, TApi> {
|
|
26
|
+
readonly local: (pluginApi: TPluginApi) => TApi;
|
|
27
|
+
readonly remote?: (fetchAdmin: FetchAdmin) => TApi;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const featureApi = <TPluginApi, TApi>(
|
|
31
|
+
api: ConsoleFeatureApi<TPluginApi, TApi>,
|
|
32
|
+
): ConsoleFeatureApi<TPluginApi, TApi> => api;
|
|
33
|
+
|
|
34
|
+
/** What serves each console feature, keyed like `consoleFeatures`. */
|
|
35
|
+
export const consoleFeatureApis = {
|
|
36
|
+
insights: featureApi({
|
|
37
|
+
local: (api: InsightsApi): ConsoleInsightsReads => ({
|
|
38
|
+
...createInsightsProvider(createInsightsModel(api)),
|
|
39
|
+
getRetention: async () => api.retention,
|
|
40
|
+
getUpdateFailures: api.getUpdateFailures,
|
|
41
|
+
}),
|
|
42
|
+
remote: createAdminInsightsReads,
|
|
43
|
+
}),
|
|
44
|
+
insightsAnalytics: featureApi({
|
|
45
|
+
local: (api: InsightsApi): InsightsModel => createInsightsModel(api),
|
|
46
|
+
}),
|
|
47
|
+
apiKeys: featureApi({
|
|
48
|
+
local: (api: ApiKeyManagementAPI) => api,
|
|
49
|
+
}),
|
|
50
|
+
} satisfies {
|
|
51
|
+
readonly [F in ConsoleFeature]: ConsoleFeatureApi<never, unknown>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** What serves each console feature, as its server functions use it. */
|
|
55
|
+
export type ConsoleFeatureApis = {
|
|
56
|
+
readonly [F in ConsoleFeature]: ReturnType<
|
|
57
|
+
(typeof consoleFeatureApis)[F]["local"]
|
|
58
|
+
>;
|
|
59
|
+
};
|
|
@@ -1,42 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyHotUpdaterPlugin,
|
|
3
|
+
type ConfiguredDatabase,
|
|
4
|
+
createMemoryAdapter,
|
|
5
|
+
type EngineDatabase,
|
|
6
|
+
type HotUpdaterCoreApi,
|
|
7
|
+
isRemoteDatabase,
|
|
8
|
+
type StorageAdapter,
|
|
9
|
+
} from "@hot-updater/plugin-core";
|
|
10
|
+
import { createHotUpdater } from "@hot-updater/server";
|
|
11
|
+
|
|
1
12
|
import type {
|
|
2
13
|
ConsoleAuthAdapter,
|
|
3
|
-
ConsoleSigningConfig,
|
|
4
14
|
HotUpdaterConsoleConfigSource,
|
|
5
|
-
ResolvedHotUpdaterConsoleConfig,
|
|
6
15
|
} from "../../index";
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
/** What the console runs on, assembled from its config as the server is. */
|
|
18
|
+
export interface ResolvedConsoleConfig {
|
|
19
|
+
readonly gitUrl?: string;
|
|
20
|
+
/** The server's database, or its admin API. */
|
|
21
|
+
readonly database: ConfiguredDatabase;
|
|
22
|
+
/**
|
|
23
|
+
* Core's API, assembled over the database as the server assembles it, so
|
|
24
|
+
* the console's writes take the server's path, or a self-hosted server's
|
|
25
|
+
* admin API.
|
|
26
|
+
*/
|
|
27
|
+
readonly core: HotUpdaterCoreApi;
|
|
28
|
+
/** The server's storage; each bundle file is read with its protocol's. */
|
|
29
|
+
readonly storage: readonly StorageAdapter[];
|
|
30
|
+
/** The plugins the server runs, as the config lists them. */
|
|
31
|
+
readonly plugins: readonly AnyHotUpdaterPlugin[];
|
|
32
|
+
/**
|
|
33
|
+
* The plugins' APIs by id, which serve their features; absent over a
|
|
34
|
+
* self-hosted server's admin API, whose plugins run on the server.
|
|
35
|
+
*/
|
|
36
|
+
readonly api?: Readonly<Record<string, unknown>>;
|
|
37
|
+
}
|
|
15
38
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
enabled: true,
|
|
37
|
-
provider: getProviderName(signing),
|
|
38
|
-
};
|
|
39
|
-
};
|
|
39
|
+
/**
|
|
40
|
+
* Core and the plugins over `database`, assembled by `createHotUpdater` as
|
|
41
|
+
* the server assembles them, with public client routes unless a plugin
|
|
42
|
+
* provides clientAuth: the console mounts no handlers. cli-tools'
|
|
43
|
+
* `assembleServer` does the same for the CLI; the console keeps its own
|
|
44
|
+
* copy, since a hosted console never loads cli-tools.
|
|
45
|
+
*/
|
|
46
|
+
const assemble = (
|
|
47
|
+
database: EngineDatabase,
|
|
48
|
+
storage: StorageAdapter,
|
|
49
|
+
plugins: readonly AnyHotUpdaterPlugin[],
|
|
50
|
+
) =>
|
|
51
|
+
createHotUpdater({
|
|
52
|
+
database,
|
|
53
|
+
storage: [storage],
|
|
54
|
+
plugins,
|
|
55
|
+
...(plugins.some((plugin) => plugin.provides?.clientAuth)
|
|
56
|
+
? {}
|
|
57
|
+
: { clientAccess: "public" }),
|
|
58
|
+
} as Parameters<typeof createHotUpdater>[0]);
|
|
40
59
|
|
|
41
60
|
export const getConsoleAuthAdapter = async (): Promise<ConsoleAuthAdapter> => {
|
|
42
61
|
const module = await import("virtual:hot-updater-console/auth");
|
|
@@ -45,17 +64,42 @@ export const getConsoleAuthAdapter = async (): Promise<ConsoleAuthAdapter> => {
|
|
|
45
64
|
|
|
46
65
|
export const resolveConsoleConfig = async (
|
|
47
66
|
request: Request,
|
|
48
|
-
): Promise<
|
|
67
|
+
): Promise<ResolvedConsoleConfig> => {
|
|
49
68
|
const { default: source } =
|
|
50
69
|
(await import("virtual:hot-updater-console/config")) as {
|
|
51
70
|
readonly default: HotUpdaterConsoleConfigSource;
|
|
52
71
|
};
|
|
53
72
|
|
|
54
73
|
const config = typeof source === "function" ? await source(request) : source;
|
|
55
|
-
const {
|
|
56
|
-
const
|
|
74
|
+
const { database, storage, plugins = [] } = config;
|
|
75
|
+
const gitUrl =
|
|
76
|
+
config.console?.gitUrl === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: { gitUrl: config.console.gitUrl };
|
|
79
|
+
// A self-hosted server runs the plugins itself: core is its admin API.
|
|
80
|
+
// The plugins are still checked as the server checks them, on a database
|
|
81
|
+
// nothing reads, so one the server would refuse is refused here too.
|
|
82
|
+
if (isRemoteDatabase(database)) {
|
|
83
|
+
const checked = assemble(
|
|
84
|
+
{ name: "memory", adapter: createMemoryAdapter() },
|
|
85
|
+
storage,
|
|
86
|
+
plugins,
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
...gitUrl,
|
|
90
|
+
database,
|
|
91
|
+
core: database.core,
|
|
92
|
+
storage: [storage],
|
|
93
|
+
plugins: checked.plugins,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const server = assemble(database, storage, plugins);
|
|
57
97
|
return {
|
|
58
|
-
...
|
|
59
|
-
|
|
98
|
+
...gitUrl,
|
|
99
|
+
database,
|
|
100
|
+
core: server.core,
|
|
101
|
+
storage: server.storage,
|
|
102
|
+
plugins: server.plugins,
|
|
103
|
+
api: server.api,
|
|
60
104
|
};
|
|
61
105
|
};
|
|
@@ -1,34 +1,35 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
HotUpdaterCoreApi,
|
|
3
|
+
StorageAdapter,
|
|
4
4
|
} from "@hot-updater/plugin-core";
|
|
5
5
|
|
|
6
6
|
interface DownloadBundleDependencies {
|
|
7
|
-
readonly
|
|
8
|
-
|
|
7
|
+
readonly core: Pick<HotUpdaterCoreApi, "getBundle">;
|
|
8
|
+
/** The server's storage; the manifest is read with its protocol's. */
|
|
9
|
+
readonly storage: readonly StorageAdapter[];
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export const downloadBundle = async (
|
|
12
13
|
bundleId: string,
|
|
13
|
-
{
|
|
14
|
+
{ core, storage }: DownloadBundleDependencies,
|
|
14
15
|
): Promise<Response> => {
|
|
15
|
-
const
|
|
16
|
-
if (!
|
|
16
|
+
const detail = await core.getBundle(bundleId);
|
|
17
|
+
if (!detail) return new Response("Bundle not found", { status: 404 });
|
|
17
18
|
|
|
18
|
-
const storageUri = bundle.
|
|
19
|
-
if (!storageUri) {
|
|
20
|
-
return new Response("Bundle has no storage URI", { status: 404 });
|
|
21
|
-
}
|
|
19
|
+
const storageUri = detail.bundle.manifest_storage_uri;
|
|
22
20
|
|
|
23
21
|
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const storageAdapter = storage.find(
|
|
23
|
+
(adapter) => adapter.protocol === protocol,
|
|
24
|
+
);
|
|
25
|
+
if (storageAdapter?.get !== undefined) {
|
|
26
|
+
const { response } = await storageAdapter.get({ storageUri });
|
|
26
27
|
if (!response)
|
|
27
28
|
return new Response("Storage object not found", { status: 404 });
|
|
28
29
|
|
|
29
30
|
const headers = new Headers(response.headers);
|
|
30
31
|
headers.set("cache-control", "private, no-store");
|
|
31
|
-
headers.set("content-disposition",
|
|
32
|
+
headers.set("content-disposition", 'attachment; filename="manifest.json"');
|
|
32
33
|
return new Response(response.body, {
|
|
33
34
|
headers,
|
|
34
35
|
status: response.status,
|
|
@@ -37,7 +38,7 @@ export const downloadBundle = async (
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
if (protocol !== "http" && protocol !== "https") {
|
|
40
|
-
return new Response(`No storage
|
|
41
|
+
return new Response(`No storage adapter for protocol: ${protocol}`, {
|
|
41
42
|
status: 503,
|
|
42
43
|
});
|
|
43
44
|
}
|
|
@@ -1,130 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type CursorPaginationInfo = {
|
|
15
|
-
hasNextPage: boolean;
|
|
16
|
-
nextCursor?: string | null;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
async function collectBundleChildrenByBaseIds(
|
|
20
|
-
baseBundleIds: string[],
|
|
21
|
-
deps: GetBundleChildrenDeps,
|
|
22
|
-
): Promise<Record<string, Bundle[]>> {
|
|
23
|
-
const uniqueBaseBundleIds = [...new Set(baseBundleIds.filter(Boolean))];
|
|
24
|
-
const bundlesByBaseId = Object.fromEntries(
|
|
25
|
-
uniqueBaseBundleIds.map((bundleId) => [bundleId, [] as Bundle[]]),
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const baseBundles = (
|
|
29
|
-
await Promise.all(
|
|
30
|
-
uniqueBaseBundleIds.map((bundleId) =>
|
|
31
|
-
deps.databaseClient.getBundleById(bundleId),
|
|
32
|
-
),
|
|
33
|
-
)
|
|
34
|
-
).filter((bundle): bundle is Bundle => Boolean(bundle));
|
|
35
|
-
|
|
36
|
-
const groupMap = new Map<Bundle["platform"], Set<string>>();
|
|
37
|
-
|
|
38
|
-
for (const baseBundle of baseBundles) {
|
|
39
|
-
const existingGroup = groupMap.get(baseBundle.platform);
|
|
40
|
-
|
|
41
|
-
if (existingGroup) {
|
|
42
|
-
existingGroup.add(baseBundle.id);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
groupMap.set(baseBundle.platform, new Set([baseBundle.id]));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
for (const [platform, bundleIds] of groupMap) {
|
|
50
|
-
const seenBundleIds = new Set<string>();
|
|
51
|
-
const seenCursors = new Set<string>();
|
|
52
|
-
let after: string | undefined;
|
|
53
|
-
|
|
54
|
-
while (true) {
|
|
55
|
-
const page = await deps.databaseClient.getBundles({
|
|
56
|
-
where: {
|
|
57
|
-
platform,
|
|
58
|
-
},
|
|
59
|
-
limit: CHILDREN_QUERY_LIMIT,
|
|
60
|
-
cursor: after ? { after } : undefined,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
for (const bundle of page.data) {
|
|
64
|
-
const parentBundleIds = getBundlePatches(bundle).map(
|
|
65
|
-
(patch) => patch.baseBundleId,
|
|
66
|
-
);
|
|
67
|
-
const matchedParentBundleIds = parentBundleIds.filter((bundleId) =>
|
|
68
|
-
bundleIds.has(bundleId),
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
matchedParentBundleIds.length === 0 ||
|
|
73
|
-
seenBundleIds.has(bundle.id)
|
|
74
|
-
) {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
seenBundleIds.add(bundle.id);
|
|
79
|
-
for (const parentBundleId of matchedParentBundleIds) {
|
|
80
|
-
bundlesByBaseId[parentBundleId]?.push(bundle);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const pagination = page.pagination as CursorPaginationInfo;
|
|
85
|
-
const nextCursor = pagination.nextCursor ?? undefined;
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
!pagination.hasNextPage ||
|
|
89
|
-
!nextCursor ||
|
|
90
|
-
seenCursors.has(nextCursor)
|
|
91
|
-
) {
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
seenCursors.add(nextCursor);
|
|
96
|
-
after = nextCursor;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return bundlesByBaseId;
|
|
101
|
-
}
|
|
102
|
-
|
|
1
|
+
import {
|
|
2
|
+
type Bundle,
|
|
3
|
+
type HotUpdaterCoreApi,
|
|
4
|
+
rowToBundle,
|
|
5
|
+
} from "@hot-updater/plugin-core";
|
|
6
|
+
|
|
7
|
+
/** The most children the lineage panel lists; the count comes from the base bundle's counter. */
|
|
8
|
+
export const BUNDLE_CHILDREN_LIMIT = 100;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The bundles with a patch from this base, newest first: one read of the
|
|
12
|
+
* patches index, then each child bundle.
|
|
13
|
+
*/
|
|
103
14
|
export async function getBundleChildren(
|
|
104
|
-
|
|
105
|
-
|
|
15
|
+
core: Pick<HotUpdaterCoreApi, "getBundle" | "listPatchesFromBase">,
|
|
16
|
+
baseBundleId: string,
|
|
106
17
|
): Promise<Bundle[]> {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
18
|
+
const patches = await core.listPatchesFromBase(baseBundleId, {
|
|
19
|
+
order: "desc",
|
|
20
|
+
limit: BUNDLE_CHILDREN_LIMIT,
|
|
21
|
+
});
|
|
22
|
+
const details = await Promise.all(
|
|
23
|
+
[...new Set(patches.map(({ bundle_id }) => bundle_id))].map((id) =>
|
|
24
|
+
core.getBundle(id),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
return details.flatMap((detail) =>
|
|
28
|
+
detail === null ? [] : [rowToBundle(detail.bundle, detail.patches)],
|
|
110
29
|
);
|
|
111
|
-
|
|
112
|
-
return childrenByBaseId[baseBundleId] ?? [];
|
|
113
30
|
}
|
|
114
31
|
|
|
32
|
+
/** How many bundles patch from each base: each base bundle's reference counter, in one batch read. */
|
|
115
33
|
export async function getBundleChildCounts(
|
|
116
|
-
|
|
117
|
-
|
|
34
|
+
core: Pick<HotUpdaterCoreApi, "countBundleChildren">,
|
|
35
|
+
baseBundleIds: readonly string[],
|
|
118
36
|
): Promise<Record<string, number>> {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
deps,
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
return Object.fromEntries(
|
|
125
|
-
Object.entries(childrenByBaseId).map(([bundleId, bundles]) => [
|
|
126
|
-
bundleId,
|
|
127
|
-
bundles.length,
|
|
128
|
-
]),
|
|
129
|
-
);
|
|
37
|
+
const ids = [...new Set(baseBundleIds.filter(Boolean))];
|
|
38
|
+
return ids.length === 0 ? {} : core.countBundleChildren(ids);
|
|
130
39
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { InsightsModel } from "@hot-updater/server/plugins/insights";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
insightsPeriodEnd,
|
|
5
|
+
readRecoveryInput,
|
|
6
|
+
recoveryWindows,
|
|
7
|
+
type RecoveryInput,
|
|
8
|
+
type RecoveryReport,
|
|
9
|
+
} from "../insights-recovery";
|
|
10
|
+
|
|
11
|
+
export async function getRecoveryReport(
|
|
12
|
+
model: InsightsModel,
|
|
13
|
+
input: RecoveryInput,
|
|
14
|
+
now = Date.now(),
|
|
15
|
+
): Promise<RecoveryReport> {
|
|
16
|
+
readRecoveryInput(input);
|
|
17
|
+
const end = insightsPeriodEnd(now);
|
|
18
|
+
const start = Math.max(0, end - recoveryWindows[input.window].durationMs);
|
|
19
|
+
const result = await model.getReleaseActivity(
|
|
20
|
+
input.releaseId === undefined
|
|
21
|
+
? {
|
|
22
|
+
scope: { platform: input.platform, channel: input.channel },
|
|
23
|
+
timeRange: { start, end },
|
|
24
|
+
}
|
|
25
|
+
: {
|
|
26
|
+
releases: [
|
|
27
|
+
{
|
|
28
|
+
releaseId: input.releaseId,
|
|
29
|
+
platform: input.platform,
|
|
30
|
+
channel: input.channel,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
timeRange: { start, end },
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
const metrics = result.data[0]?.metrics;
|
|
37
|
+
// The launches counter counts each installation once per UTC day it
|
|
38
|
+
// launched, so it reads as active days, and per day as daily active
|
|
39
|
+
// installations.
|
|
40
|
+
return {
|
|
41
|
+
downloads: metrics?.downloads ?? 0,
|
|
42
|
+
activeInstallations: metrics?.uniqueUsers ?? 0,
|
|
43
|
+
activeDays: metrics?.launches ?? 0,
|
|
44
|
+
failedLaunches: metrics?.failedLaunches ?? 0,
|
|
45
|
+
points: (metrics?.series ?? []).map((point) => ({
|
|
46
|
+
startMs: point.startMs,
|
|
47
|
+
dailyActiveInstallations: point.launches,
|
|
48
|
+
failedLaunches: point.failedLaunches,
|
|
49
|
+
})),
|
|
50
|
+
startMs: start,
|
|
51
|
+
endMs: end,
|
|
52
|
+
measuredAtMs: result.measuredAtMs,
|
|
53
|
+
coverage: result.coverage,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { InsightsModel } from "@hot-updater/server/plugins/insights";
|
|
2
|
+
|
|
3
|
+
import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
|
|
4
|
+
import {
|
|
5
|
+
readAppUsageInput,
|
|
6
|
+
type AppUsageInput,
|
|
7
|
+
type AppUsageReport,
|
|
8
|
+
type UsageWindow,
|
|
9
|
+
} from "../insights-usage";
|
|
10
|
+
|
|
11
|
+
const DAY_MS = 86_400_000;
|
|
12
|
+
const WEEK_MS = 7 * DAY_MS;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What a period reads, ending with the current hour. The 12-month period is
|
|
16
|
+
* 52 weeks of whole UTC days, the last one today, so it reads the daily
|
|
17
|
+
* counts: the hourly ones are kept 90 days.
|
|
18
|
+
*/
|
|
19
|
+
const usageRange = (window: UsageWindow, now: number) => {
|
|
20
|
+
const end = insightsPeriodEnd(now);
|
|
21
|
+
if (window === "12m") {
|
|
22
|
+
const lastDay = Math.ceil(end / DAY_MS) * DAY_MS;
|
|
23
|
+
return {
|
|
24
|
+
start: Math.max(0, lastDay - 52 * WEEK_MS),
|
|
25
|
+
end,
|
|
26
|
+
intervalMs: WEEK_MS,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const { durationMs, intervalMs } = recoveryWindows[window];
|
|
30
|
+
return { start: Math.max(0, end - durationMs), end, intervalMs };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export async function getAppUsageReport(
|
|
34
|
+
model: InsightsModel,
|
|
35
|
+
input: AppUsageInput,
|
|
36
|
+
now = Date.now(),
|
|
37
|
+
): Promise<AppUsageReport> {
|
|
38
|
+
readAppUsageInput(input);
|
|
39
|
+
const { start, end, intervalMs } = usageRange(input.window, now);
|
|
40
|
+
const report = await model.getAppUsage({
|
|
41
|
+
channel: input.channel,
|
|
42
|
+
platform: input.platform,
|
|
43
|
+
...(input.appVersion === undefined ? {} : { appVersion: input.appVersion }),
|
|
44
|
+
timeRange: { start, end },
|
|
45
|
+
intervalMs,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
bundleDistribution: report.bundleDistribution,
|
|
49
|
+
activeInstallations: report.activeInstallations,
|
|
50
|
+
sinceMs:
|
|
51
|
+
report.coverage.sinceMs === null
|
|
52
|
+
? start
|
|
53
|
+
: Math.max(start, report.coverage.sinceMs),
|
|
54
|
+
distributionSinceMs: start - (start % DAY_MS),
|
|
55
|
+
beforeReceivedAtMs: end,
|
|
56
|
+
intervalMs,
|
|
57
|
+
truncated: report.coverage.kind === "partial",
|
|
58
|
+
appVersions: report.appVersions,
|
|
59
|
+
versions: report.versions,
|
|
60
|
+
platforms: report.platforms,
|
|
61
|
+
points: report.points,
|
|
62
|
+
};
|
|
63
|
+
}
|