@hot-updater/server 0.36.6 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/databaseConstraintErrors.cjs +23 -0
- package/dist/adapters/databaseConstraintErrors.mjs +21 -0
- package/dist/adapters/databasePluginUtils.cjs +117 -0
- package/dist/adapters/databasePluginUtils.mjs +106 -0
- package/dist/adapters/drizzle.cjs +53 -176
- package/dist/adapters/drizzle.d.cts +6 -4
- package/dist/adapters/drizzle.d.mts +6 -4
- package/dist/adapters/drizzle.mjs +54 -177
- package/dist/adapters/drizzleCrud.cjs +250 -0
- package/dist/adapters/drizzleCrud.mjs +249 -0
- package/dist/adapters/drizzleLazyDB.cjs +84 -36
- package/dist/adapters/drizzleLazyDB.mjs +84 -36
- package/dist/adapters/drizzleQuery.cjs +68 -0
- package/dist/adapters/drizzleQuery.mjs +67 -0
- package/dist/adapters/kysely.cjs +18 -138
- package/dist/adapters/kysely.d.cts +3 -12
- package/dist/adapters/kysely.d.mts +3 -12
- package/dist/adapters/kysely.mjs +19 -139
- package/dist/adapters/kyselyCrud.cjs +254 -0
- package/dist/adapters/kyselyCrud.mjs +253 -0
- package/dist/adapters/kyselyQuery.cjs +56 -0
- package/dist/adapters/kyselyQuery.mjs +55 -0
- package/dist/adapters/mongodb.cjs +21 -174
- package/dist/adapters/mongodb.d.cts +3 -2
- package/dist/adapters/mongodb.d.mts +3 -2
- package/dist/adapters/mongodb.mjs +21 -174
- package/dist/adapters/mongodbCollections.cjs +36 -0
- package/dist/adapters/mongodbCollections.mjs +30 -0
- package/dist/adapters/mongodbQuery.cjs +77 -0
- package/dist/adapters/mongodbQuery.mjs +70 -0
- package/dist/adapters/mongodbReads.cjs +148 -0
- package/dist/adapters/mongodbReads.mjs +147 -0
- package/dist/adapters/mongodbWrites.cjs +134 -0
- package/dist/adapters/mongodbWrites.mjs +133 -0
- package/dist/adapters/prisma.cjs +205 -180
- package/dist/adapters/prisma.d.cts +3 -3
- package/dist/adapters/prisma.d.mts +3 -3
- package/dist/adapters/prisma.mjs +206 -181
- package/dist/adapters/prismaQuery.cjs +58 -0
- package/dist/adapters/prismaQuery.mjs +56 -0
- package/dist/adapters/prismaRows.cjs +198 -0
- package/dist/adapters/prismaRows.mjs +188 -0
- package/dist/analytics/bounded/activeOverview.cjs +87 -0
- package/dist/analytics/bounded/activeOverview.mjs +85 -0
- package/dist/analytics/bounded/installationSearch.cjs +42 -0
- package/dist/analytics/bounded/installationSearch.mjs +42 -0
- package/dist/analytics/bounded/persistence.cjs +39 -0
- package/dist/analytics/bounded/persistence.mjs +38 -0
- package/dist/analytics/bounded/provider.cjs +170 -0
- package/dist/analytics/bounded/provider.d.cts +7 -0
- package/dist/analytics/bounded/provider.d.mts +7 -0
- package/dist/analytics/bounded/provider.mjs +170 -0
- package/dist/analytics/bounded/scan.cjs +144 -0
- package/dist/analytics/bounded/scan.mjs +138 -0
- package/dist/analytics/domain.d.cts +121 -0
- package/dist/analytics/domain.d.mts +121 -0
- package/dist/analytics/errors.cjs +22 -0
- package/dist/analytics/errors.mjs +20 -0
- package/dist/analytics/eventInput.cjs +111 -0
- package/dist/analytics/eventInput.mjs +111 -0
- package/dist/analytics/persistence.d.cts +6 -0
- package/dist/analytics/persistence.d.mts +6 -0
- package/dist/analytics/queryInput.cjs +58 -0
- package/dist/analytics/queryInput.mjs +55 -0
- package/dist/analytics/routes.cjs +63 -0
- package/dist/analytics/routes.mjs +61 -0
- package/dist/analytics/types.d.cts +17 -0
- package/dist/analytics/types.d.mts +17 -0
- package/dist/apiKeys.cjs +139 -0
- package/dist/apiKeys.d.cts +50 -0
- package/dist/apiKeys.d.mts +50 -0
- package/dist/apiKeys.mjs +133 -0
- package/dist/createHotUpdaterCore.cjs +48 -26
- package/dist/createHotUpdaterCore.d.cts +49 -17
- package/dist/createHotUpdaterCore.d.mts +49 -17
- package/dist/createHotUpdaterCore.mjs +51 -29
- package/dist/db/createBundleDiff.cjs +45 -68
- package/dist/db/createBundleDiff.d.cts +3 -3
- package/dist/db/createBundleDiff.d.mts +3 -3
- package/dist/db/createBundleDiff.mjs +47 -68
- package/dist/db/databasePluginCore.cjs +138 -0
- package/dist/db/databasePluginCore.mjs +137 -0
- package/dist/db/fixedMigrator.cjs +36 -93
- package/dist/db/fixedMigrator.mjs +36 -93
- package/dist/db/fixedMigratorMongo.cjs +319 -0
- package/dist/db/fixedMigratorMongo.mjs +319 -0
- package/dist/db/fixedMigratorShared.cjs +23 -0
- package/dist/db/fixedMigratorShared.mjs +18 -0
- package/dist/db/mongoMigrationExecution.cjs +11 -0
- package/dist/db/mongoMigrationExecution.mjs +11 -0
- package/dist/db/releaseCatalog.cjs +66 -0
- package/dist/db/releaseCatalog.d.cts +17 -0
- package/dist/db/releaseCatalog.d.mts +17 -0
- package/dist/db/releaseCatalog.mjs +64 -0
- package/dist/db/schema/mongodb.cjs +14 -6
- package/dist/db/schema/mongodb.mjs +14 -6
- package/dist/db/schema/registry.cjs +0 -2
- package/dist/db/schema/registry.mjs +1 -2
- package/dist/db/schema/sql.cjs +31 -14
- package/dist/db/schema/sql.mjs +32 -13
- package/dist/db/schema/sqlOperations.cjs +4 -4
- package/dist/db/schema/sqlOperations.mjs +5 -5
- package/dist/db/schemaGenerators.cjs +52 -18
- package/dist/db/schemaGenerators.mjs +52 -18
- package/dist/db/schemaReadiness.cjs +4 -3
- package/dist/db/schemaReadiness.mjs +4 -3
- package/dist/db/sqlMigrationExecution.cjs +67 -0
- package/dist/db/sqlMigrationExecution.mjs +66 -0
- package/dist/db/types.cjs +2 -6
- package/dist/db/types.d.cts +60 -16
- package/dist/db/types.d.mts +60 -16
- package/dist/db/types.mjs +3 -6
- package/dist/db/updateArtifacts.cjs +114 -60
- package/dist/db/updateArtifacts.mjs +117 -62
- package/dist/handler.cjs +108 -282
- package/dist/handler.d.cts +3 -46
- package/dist/handler.d.mts +3 -46
- package/dist/handler.mjs +107 -281
- package/dist/handlerBundleRoutes.cjs +155 -0
- package/dist/handlerBundleRoutes.mjs +155 -0
- package/dist/handlerErrors.cjs +9 -0
- package/dist/handlerErrors.mjs +9 -0
- package/dist/handlerParameters.cjs +39 -0
- package/dist/handlerParameters.mjs +34 -0
- package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
- package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
- package/dist/handlerReleaseManagementRoutes.cjs +125 -0
- package/dist/handlerReleaseManagementRoutes.mjs +124 -0
- package/dist/handlerTypes.d.cts +63 -0
- package/dist/handlerTypes.d.mts +63 -0
- package/dist/handlerVersionRoutes.cjs +10 -0
- package/dist/handlerVersionRoutes.d.cts +4 -0
- package/dist/handlerVersionRoutes.d.mts +4 -0
- package/dist/handlerVersionRoutes.mjs +9 -0
- package/dist/index.cjs +10 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.mts +9 -3
- package/dist/index.mjs +5 -2
- package/dist/node.cjs +19 -12
- package/dist/node.d.cts +15 -10
- package/dist/node.d.mts +15 -10
- package/dist/node.mjs +19 -13
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/schema/dsl.cjs +19 -2
- package/dist/schema/dsl.mjs +17 -3
- package/dist/schema/index.cjs +1 -8
- package/dist/schema/index.mjs +2 -8
- package/dist/schema/types.cjs +3 -1
- package/dist/schema/types.mjs +3 -2
- package/dist/schema/v1_0_0.cjs +364 -0
- package/dist/schema/v1_0_0.mjs +357 -0
- package/dist/storageAccess.cjs +69 -25
- package/dist/storageAccess.mjs +68 -25
- package/dist/types/index.d.cts +4 -3
- package/dist/types/index.d.mts +5 -4
- package/package.json +8 -7
- package/src/adapters/databaseConstraintErrors.ts +25 -0
- package/src/adapters/databasePluginTestDatabase.ts +106 -0
- package/src/adapters/databasePluginUtils.spec.ts +29 -0
- package/src/adapters/databasePluginUtils.ts +214 -0
- package/src/adapters/drizzle.spec.ts +208 -47
- package/src/adapters/drizzle.ts +106 -293
- package/src/adapters/drizzleCrud.ts +419 -0
- package/src/adapters/drizzleLazyDB.ts +206 -100
- package/src/adapters/drizzleQuery.ts +164 -0
- package/src/adapters/kysely.spec.ts +126 -88
- package/src/adapters/kysely.ts +76 -334
- package/src/adapters/kyselyCrud.ts +588 -0
- package/src/adapters/kyselyQuery.ts +128 -0
- package/src/adapters/mongodb.spec.ts +141 -0
- package/src/adapters/mongodb.ts +60 -288
- package/src/adapters/mongodbCollections.ts +62 -0
- package/src/adapters/mongodbQuery.ts +226 -0
- package/src/adapters/mongodbReads.ts +311 -0
- package/src/adapters/mongodbRows.spec.ts +64 -0
- package/src/adapters/mongodbRows.ts +118 -0
- package/src/adapters/mongodbTestClient.ts +342 -0
- package/src/adapters/mongodbTestFilter.ts +166 -0
- package/src/adapters/mongodbWrites.ts +260 -0
- package/src/adapters/prisma.spec.ts +196 -0
- package/src/adapters/prisma.ts +417 -292
- package/src/adapters/prismaQuery.ts +129 -0
- package/src/adapters/prismaRows.spec.ts +98 -0
- package/src/adapters/prismaRows.ts +301 -0
- package/src/adapters/prismaTestClient.ts +396 -0
- package/src/analytics/analytics.integration.spec.ts +144 -0
- package/src/analytics/bounded/activeOverview.spec.ts +189 -0
- package/src/analytics/bounded/activeOverview.ts +148 -0
- package/src/analytics/bounded/installationSearch.spec.ts +130 -0
- package/src/analytics/bounded/installationSearch.ts +69 -0
- package/src/analytics/bounded/persistence.ts +43 -0
- package/src/analytics/bounded/provider.spec.ts +219 -0
- package/src/analytics/bounded/provider.ts +229 -0
- package/src/analytics/bounded/scan.ts +235 -0
- package/src/analytics/domain.ts +139 -0
- package/src/analytics/errors.ts +18 -0
- package/src/analytics/eventInput.ts +168 -0
- package/src/analytics/persistence.ts +10 -0
- package/src/analytics/queryInput.ts +102 -0
- package/src/analytics/routes.ts +123 -0
- package/src/analytics/types.ts +44 -0
- package/src/apiKeys.integration.spec.ts +192 -0
- package/src/apiKeys.spec.ts +162 -0
- package/src/apiKeys.ts +220 -0
- package/src/createHotUpdaterCore.ts +164 -100
- package/src/db/bundleRows.spec.ts +164 -0
- package/src/db/bundleRows.ts +10 -140
- package/src/db/createBundleDiff.spec.ts +172 -127
- package/src/db/createBundleDiff.ts +91 -110
- package/src/db/databasePluginCore.spec.ts +62 -0
- package/src/db/databasePluginCore.testFixtures.ts +76 -0
- package/src/db/databasePluginCore.ts +185 -0
- package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
- package/src/db/fixedMigrator.spec.ts +184 -42
- package/src/db/fixedMigrator.ts +64 -205
- package/src/db/fixedMigratorMongo.spec.ts +192 -0
- package/src/db/fixedMigratorMongo.ts +425 -0
- package/src/db/fixedMigratorShared.ts +36 -0
- package/src/db/hotUpdaterSchema.ts +0 -1
- package/src/db/index.spec.ts +333 -1025
- package/src/db/mongoMigrationExecution.ts +23 -0
- package/src/db/node-sqlite.d.ts +25 -0
- package/src/db/releaseCatalog.ts +148 -0
- package/src/db/schema/mongodb.ts +20 -6
- package/src/db/schema/sql.ts +52 -11
- package/src/db/schema/sqlOperations.ts +5 -4
- package/src/db/schemaGenerators.ts +107 -23
- package/src/db/schemaReadiness.ts +2 -1
- package/src/db/sqlMigrationExecution.spec.ts +65 -0
- package/src/db/sqlMigrationExecution.ts +108 -0
- package/src/db/types.spec.ts +55 -0
- package/src/db/types.ts +129 -65
- package/src/db/updateArtifacts.spec.ts +359 -0
- package/src/db/updateArtifacts.ts +290 -126
- package/src/handler-standalone.integration.spec.ts +401 -364
- package/src/handler.spec.ts +137 -582
- package/src/handler.testFixtures.ts +30 -0
- package/src/handler.ts +218 -616
- package/src/handlerAuth.integration.spec.ts +33 -0
- package/src/handlerBundleRoutes.ts +259 -0
- package/src/handlerErrors.ts +7 -0
- package/src/handlerManagement.spec.ts +262 -0
- package/src/handlerParameters.ts +105 -0
- package/src/handlerReleaseCatalogRoutes.ts +154 -0
- package/src/handlerReleaseManagementRoutes.ts +213 -0
- package/src/handlerTypes.ts +96 -0
- package/src/handlerVersionRoutes.ts +12 -0
- package/src/index.ts +22 -2
- package/src/node.spec.ts +79 -10
- package/src/node.ts +20 -17
- package/src/releaseCatalogRoutes.integration.spec.ts +238 -0
- package/src/runtime.spec.ts +126 -959
- package/src/runtime.testFixtures.ts +71 -0
- package/src/schema/dsl-all-versions.spec.ts +26 -17
- package/src/schema/dsl.ts +41 -2
- package/src/schema/index.ts +3 -9
- package/src/schema/types.ts +9 -3
- package/src/schema/v1_0_0.spec.ts +146 -0
- package/src/schema/v1_0_0.ts +465 -0
- package/src/storageAccess.spec.ts +127 -32
- package/src/storageAccess.ts +106 -55
- package/src/types/index.ts +2 -1
- package/dist/db/bundleRows.cjs +0 -82
- package/dist/db/bundleRows.d.cts +0 -33
- package/dist/db/bundleRows.d.mts +0 -33
- package/dist/db/bundleRows.mjs +0 -79
- package/dist/db/pluginCore.cjs +0 -242
- package/dist/db/pluginCore.mjs +0 -241
- package/dist/db/schema/sqlMigrations.cjs +0 -74
- package/dist/db/schema/sqlMigrations.mjs +0 -73
- package/dist/db/schemaEnhancements.cjs +0 -20
- package/dist/db/schemaEnhancements.mjs +0 -19
- package/dist/route.cjs +0 -7
- package/dist/route.mjs +0 -7
- package/dist/schema/v0_21_0.cjs +0 -38
- package/dist/schema/v0_21_0.mjs +0 -37
- package/dist/schema/v0_29_0.cjs +0 -45
- package/dist/schema/v0_29_0.mjs +0 -44
- package/dist/schema/v0_31_0.cjs +0 -82
- package/dist/schema/v0_31_0.mjs +0 -80
- package/src/db/pluginCore.spec.ts +0 -1095
- package/src/db/pluginCore.ts +0 -475
- package/src/db/pluginUpdateCheck.bench.ts +0 -254
- package/src/db/schema/sqlMigrations.ts +0 -219
- package/src/db/schemaEnhancements.ts +0 -55
- package/src/route.ts +0 -7
- package/src/schema/v0_21_0.ts +0 -52
- package/src/schema/v0_29_0.ts +0 -62
- package/src/schema/v0_31_0.spec.ts +0 -73
- package/src/schema/v0_31_0.ts +0 -120
package/src/handler.ts
CHANGED
|
@@ -1,643 +1,245 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAnalyticsRouteHandlers,
|
|
3
|
+
registerAnalyticsAdminRoutes,
|
|
4
|
+
registerAnalyticsClientRoutes,
|
|
5
|
+
} from "./analytics/routes";
|
|
6
|
+
import type { AnalyticsProvider } from "./analytics/types";
|
|
7
|
+
import { createBundleRouteHandlers } from "./handlerBundleRoutes";
|
|
8
|
+
import { HandlerBadRequestError } from "./handlerErrors";
|
|
9
|
+
import { createReleaseCatalogRouteHandlers } from "./handlerReleaseCatalogRoutes";
|
|
10
|
+
import { createReleaseManagementRouteHandlers } from "./handlerReleaseManagementRoutes";
|
|
1
11
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "@hot-updater/core";
|
|
9
|
-
import type {
|
|
10
|
-
DatabaseBundleQueryOptions,
|
|
11
|
-
HotUpdaterContext,
|
|
12
|
-
} from "@hot-updater/plugin-core";
|
|
13
|
-
import { isGreaterOrEqual, normalize } from "verkit";
|
|
14
|
-
|
|
12
|
+
HandlerAPI,
|
|
13
|
+
HotUpdaterHandler,
|
|
14
|
+
HotUpdaterHandlers,
|
|
15
|
+
RouteHandler,
|
|
16
|
+
} from "./handlerTypes";
|
|
17
|
+
import { createVersionRouteHandlers } from "./handlerVersionRoutes";
|
|
15
18
|
import { addRoute, createRouter, findRoute } from "./internalRouter";
|
|
16
|
-
import type { ChannelsResponse, PaginatedResult } from "./types";
|
|
17
|
-
import { HOT_UPDATER_SERVER_VERSION } from "./version";
|
|
18
|
-
|
|
19
|
-
// Narrow API surface needed by the handler to avoid circular types
|
|
20
|
-
export interface HandlerAPI<TContext = unknown> {
|
|
21
|
-
getAppUpdateInfo: (
|
|
22
|
-
args: AppVersionGetBundlesArgs | FingerprintGetBundlesArgs,
|
|
23
|
-
context?: HotUpdaterContext<TContext>,
|
|
24
|
-
) => Promise<AppUpdateAvailableInfo | null>;
|
|
25
|
-
getBundleById: (
|
|
26
|
-
id: string,
|
|
27
|
-
context?: HotUpdaterContext<TContext>,
|
|
28
|
-
) => Promise<Bundle | null>;
|
|
29
|
-
getBundles: (
|
|
30
|
-
options: DatabaseBundleQueryOptions,
|
|
31
|
-
context?: HotUpdaterContext<TContext>,
|
|
32
|
-
) => Promise<PaginatedResult>;
|
|
33
|
-
insertBundle: (
|
|
34
|
-
bundle: Bundle,
|
|
35
|
-
context?: HotUpdaterContext<TContext>,
|
|
36
|
-
) => Promise<void>;
|
|
37
|
-
updateBundleById: (
|
|
38
|
-
bundleId: string,
|
|
39
|
-
bundle: Partial<Bundle>,
|
|
40
|
-
context?: HotUpdaterContext<TContext>,
|
|
41
|
-
) => Promise<void>;
|
|
42
|
-
deleteBundleById: (
|
|
43
|
-
bundleId: string,
|
|
44
|
-
context?: HotUpdaterContext<TContext>,
|
|
45
|
-
) => Promise<void>;
|
|
46
|
-
getChannels: (context?: HotUpdaterContext<TContext>) => Promise<string[]>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface HandlerOptions {
|
|
50
|
-
/**
|
|
51
|
-
* Base path for all routes
|
|
52
|
-
* @default "/api"
|
|
53
|
-
*/
|
|
54
|
-
basePath?: string;
|
|
55
|
-
/**
|
|
56
|
-
* Route groups to mount. Omit this option to use the default route groups.
|
|
57
|
-
* When provided, both route groups must be specified explicitly.
|
|
58
|
-
* The `/version` endpoint is always mounted for diagnostics.
|
|
59
|
-
*/
|
|
60
|
-
routes?: HandlerRoutes;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface HandlerRoutes {
|
|
64
|
-
/**
|
|
65
|
-
* Controls whether update-check routes are mounted.
|
|
66
|
-
* Defaults to `true` only when `routes` is omitted.
|
|
67
|
-
*/
|
|
68
|
-
updateCheck: boolean;
|
|
69
|
-
/**
|
|
70
|
-
* Controls whether bundle management routes are mounted.
|
|
71
|
-
* This includes `/api/bundles*`, which are used by the
|
|
72
|
-
* CLI `standaloneRepository` plugin.
|
|
73
|
-
* Defaults to `false` only when `routes` is omitted.
|
|
74
|
-
*/
|
|
75
|
-
bundles: boolean;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
type RouteHandler<TContext = unknown> = (
|
|
79
|
-
params: Record<string, string>,
|
|
80
|
-
request: Request,
|
|
81
|
-
api: HandlerAPI<TContext>,
|
|
82
|
-
context?: HotUpdaterContext<TContext>,
|
|
83
|
-
) => Promise<Response>;
|
|
84
|
-
|
|
85
|
-
class HandlerBadRequestError extends Error {
|
|
86
|
-
constructor(message: string) {
|
|
87
|
-
super(message);
|
|
88
|
-
this.name = "HandlerBadRequestError";
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
19
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
normalizedSdkVersion !== null &&
|
|
106
|
-
isGreaterOrEqual(normalizedSdkVersion, EXPLICIT_NO_UPDATE_MIN_SDK_VERSION)
|
|
107
|
-
);
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const serializeUpdateInfo = (
|
|
111
|
-
updateInfo: AppUpdateAvailableInfo | null,
|
|
112
|
-
request: Request,
|
|
113
|
-
): string => {
|
|
114
|
-
if (updateInfo) {
|
|
115
|
-
return JSON.stringify(updateInfo satisfies AppUpdateInfo);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (supportsExplicitNoUpdateResponse(request)) {
|
|
119
|
-
return JSON.stringify({ status: "UP_TO_DATE" } satisfies AppUpdateInfo);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return JSON.stringify(null);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// Route handlers
|
|
126
|
-
const handleVersion: RouteHandler = async () => {
|
|
127
|
-
return new Response(JSON.stringify({ version: HOT_UPDATER_SERVER_VERSION }), {
|
|
128
|
-
status: 200,
|
|
129
|
-
headers: { "Content-Type": "application/json" },
|
|
20
|
+
export type {
|
|
21
|
+
HandlerAPI,
|
|
22
|
+
HotUpdaterHandler,
|
|
23
|
+
HotUpdaterHandlers,
|
|
24
|
+
} from "./handlerTypes";
|
|
25
|
+
|
|
26
|
+
const withPrivateNoStore = (response: Response): Response => {
|
|
27
|
+
const headers = new Headers(response.headers);
|
|
28
|
+
headers.set("cache-control", "private, no-store");
|
|
29
|
+
return new Response(response.body, {
|
|
30
|
+
headers,
|
|
31
|
+
status: response.status,
|
|
32
|
+
statusText: response.statusText,
|
|
130
33
|
});
|
|
131
34
|
};
|
|
132
35
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return decodeURIComponent(value);
|
|
137
|
-
} catch {
|
|
138
|
-
return value;
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const isPlatform = (value: string): value is Platform => {
|
|
143
|
-
return value === "ios" || value === "android";
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const requireRouteParam = (
|
|
147
|
-
params: Record<string, string>,
|
|
148
|
-
key: string,
|
|
149
|
-
): string => {
|
|
150
|
-
const value = params[key];
|
|
151
|
-
if (!value) {
|
|
152
|
-
throw new HandlerBadRequestError(`Missing route parameter: ${key}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return value;
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const parseBooleanSearchParam = (
|
|
159
|
-
url: URL,
|
|
160
|
-
key: string,
|
|
161
|
-
): boolean | undefined => {
|
|
162
|
-
const value = url.searchParams.get(key);
|
|
163
|
-
if (value === null) {
|
|
164
|
-
return undefined;
|
|
165
|
-
}
|
|
166
|
-
if (value === "true") {
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
if (value === "false") {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
throw new HandlerBadRequestError(
|
|
174
|
-
`The '${key}' query parameter must be 'true' or 'false'.`,
|
|
175
|
-
);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const parseNullableStringSearchParam = (
|
|
179
|
-
url: URL,
|
|
180
|
-
key: string,
|
|
181
|
-
): string | null | undefined => {
|
|
182
|
-
const value = url.searchParams.get(key);
|
|
183
|
-
if (value === null) {
|
|
184
|
-
return undefined;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return value === "null" ? null : value;
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const parseStringArraySearchParam = (url: URL, key: string) => {
|
|
191
|
-
const values = url.searchParams.getAll(key);
|
|
192
|
-
return values.length > 0 ? values : undefined;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const parsePositiveIntegerSearchParam = (
|
|
196
|
-
url: URL,
|
|
197
|
-
key: string,
|
|
198
|
-
defaultValue: number,
|
|
199
|
-
maxValue: number,
|
|
200
|
-
): number => {
|
|
201
|
-
const value = url.searchParams.get(key);
|
|
202
|
-
if (value === null) {
|
|
203
|
-
return defaultValue;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const parsed = Number(value);
|
|
207
|
-
if (!Number.isInteger(parsed) || parsed < 1 || parsed > maxValue) {
|
|
208
|
-
throw new HandlerBadRequestError(
|
|
209
|
-
`The '${key}' query parameter must be a positive integer between 1 and ${maxValue}.`,
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
return parsed;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const requirePlatformParam = (params: Record<string, string>): Platform => {
|
|
217
|
-
const platform = requireRouteParam(params, "platform");
|
|
218
|
-
|
|
219
|
-
if (!isPlatform(platform)) {
|
|
220
|
-
throw new HandlerBadRequestError(
|
|
221
|
-
`Invalid platform: ${platform}. Expected 'ios' or 'android'.`,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return platform;
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
type BundlePatchPayload = Partial<Bundle> & {
|
|
229
|
-
id?: string;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const requireBundlePatchPayload = (
|
|
233
|
-
payload: unknown,
|
|
234
|
-
bundleId: string,
|
|
235
|
-
): Partial<Bundle> => {
|
|
236
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
237
|
-
throw new HandlerBadRequestError("Invalid bundle payload");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const bundlePatch = payload as BundlePatchPayload;
|
|
241
|
-
if (bundlePatch.id !== undefined && bundlePatch.id !== bundleId) {
|
|
242
|
-
throw new HandlerBadRequestError("Bundle id mismatch");
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const { id: _ignoredId, ...rest } = bundlePatch;
|
|
246
|
-
return rest;
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const handleFingerprintUpdateWithCohort: RouteHandler = async (
|
|
250
|
-
params,
|
|
251
|
-
request,
|
|
252
|
-
api,
|
|
253
|
-
context,
|
|
254
|
-
) => {
|
|
255
|
-
const platform = requirePlatformParam(params);
|
|
256
|
-
const fingerprintHash = requireRouteParam(params, "fingerprintHash");
|
|
257
|
-
const channel = requireRouteParam(params, "channel");
|
|
258
|
-
const minBundleId = requireRouteParam(params, "minBundleId");
|
|
259
|
-
const bundleId = requireRouteParam(params, "bundleId");
|
|
260
|
-
|
|
261
|
-
const updateInfo = await api.getAppUpdateInfo(
|
|
36
|
+
const errorResponse = (error: string, status: number): Response =>
|
|
37
|
+
Response.json(
|
|
38
|
+
{ error },
|
|
262
39
|
{
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
fingerprintHash,
|
|
266
|
-
channel,
|
|
267
|
-
minBundleId,
|
|
268
|
-
bundleId,
|
|
269
|
-
cohort: decodeMaybe(params.cohort),
|
|
40
|
+
headers: { "cache-control": "private, no-store" },
|
|
41
|
+
status,
|
|
270
42
|
},
|
|
271
|
-
context,
|
|
272
43
|
);
|
|
273
44
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
channel,
|
|
298
|
-
minBundleId,
|
|
299
|
-
bundleId,
|
|
300
|
-
cohort: decodeMaybe(params.cohort),
|
|
301
|
-
},
|
|
302
|
-
context,
|
|
303
|
-
);
|
|
304
|
-
|
|
305
|
-
return new Response(serializeUpdateInfo(updateInfo, request), {
|
|
306
|
-
status: 200,
|
|
307
|
-
headers: { "Content-Type": "application/json" },
|
|
308
|
-
});
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const handleGetBundle: RouteHandler = async (
|
|
312
|
-
params,
|
|
313
|
-
_request,
|
|
314
|
-
api,
|
|
315
|
-
context,
|
|
316
|
-
) => {
|
|
317
|
-
const bundleId = requireRouteParam(params, "id");
|
|
318
|
-
const bundle = await api.getBundleById(bundleId, context);
|
|
319
|
-
|
|
320
|
-
if (!bundle) {
|
|
321
|
-
return new Response(JSON.stringify({ error: "Bundle not found" }), {
|
|
322
|
-
status: 404,
|
|
323
|
-
headers: { "Content-Type": "application/json" },
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
return new Response(JSON.stringify(bundle), {
|
|
328
|
-
status: 200,
|
|
329
|
-
headers: { "Content-Type": "application/json" },
|
|
330
|
-
});
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const handleGetBundles: RouteHandler = async (
|
|
334
|
-
_params,
|
|
335
|
-
request,
|
|
336
|
-
api,
|
|
337
|
-
context,
|
|
338
|
-
) => {
|
|
339
|
-
const url = new URL(request.url);
|
|
340
|
-
const channel = url.searchParams.get("channel") ?? undefined;
|
|
341
|
-
const platform = url.searchParams.get("platform");
|
|
342
|
-
const limit = parsePositiveIntegerSearchParam(
|
|
343
|
-
url,
|
|
344
|
-
"limit",
|
|
345
|
-
DEFAULT_BUNDLE_LIST_LIMIT,
|
|
346
|
-
MAX_BUNDLE_LIST_LIMIT,
|
|
347
|
-
);
|
|
348
|
-
const pageParam = url.searchParams.get("page");
|
|
349
|
-
const offset = url.searchParams.get("offset");
|
|
350
|
-
const after = url.searchParams.get("after") ?? undefined;
|
|
351
|
-
const before = url.searchParams.get("before") ?? undefined;
|
|
352
|
-
const enabled = parseBooleanSearchParam(url, "enabled");
|
|
353
|
-
const targetAppVersion = parseNullableStringSearchParam(
|
|
354
|
-
url,
|
|
355
|
-
"targetAppVersion",
|
|
356
|
-
);
|
|
357
|
-
const targetAppVersionIn = parseStringArraySearchParam(
|
|
358
|
-
url,
|
|
359
|
-
"targetAppVersionIn",
|
|
360
|
-
);
|
|
361
|
-
const targetAppVersionNotNull = parseBooleanSearchParam(
|
|
362
|
-
url,
|
|
363
|
-
"targetAppVersionNotNull",
|
|
364
|
-
);
|
|
365
|
-
const fingerprintHash = parseNullableStringSearchParam(
|
|
366
|
-
url,
|
|
367
|
-
"fingerprintHash",
|
|
368
|
-
);
|
|
369
|
-
const idEq = url.searchParams.get("idEq") ?? undefined;
|
|
370
|
-
const idGt = url.searchParams.get("idGt") ?? undefined;
|
|
371
|
-
const idGte = url.searchParams.get("idGte") ?? undefined;
|
|
372
|
-
const idLt = url.searchParams.get("idLt") ?? undefined;
|
|
373
|
-
const idLte = url.searchParams.get("idLte") ?? undefined;
|
|
374
|
-
const idIn = parseStringArraySearchParam(url, "idIn");
|
|
375
|
-
const page =
|
|
376
|
-
pageParam === null
|
|
377
|
-
? undefined
|
|
378
|
-
: Number.isInteger(Number(pageParam)) && Number(pageParam) > 0
|
|
379
|
-
? Number(pageParam)
|
|
380
|
-
: null;
|
|
381
|
-
|
|
382
|
-
if (offset !== null) {
|
|
383
|
-
throw new HandlerBadRequestError(
|
|
384
|
-
"The 'offset' query parameter has been removed. Use 'after' or 'before' cursor pagination instead.",
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (page === null) {
|
|
389
|
-
throw new HandlerBadRequestError(
|
|
390
|
-
"The 'page' query parameter must be a positive integer.",
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (platform !== null && !isPlatform(platform)) {
|
|
395
|
-
throw new HandlerBadRequestError(
|
|
396
|
-
`Invalid platform: ${platform}. Expected 'ios' or 'android'.`,
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const result = await api.getBundles(
|
|
401
|
-
{
|
|
402
|
-
where: {
|
|
403
|
-
...(channel && { channel }),
|
|
404
|
-
...(platform && { platform }),
|
|
405
|
-
...(enabled !== undefined && { enabled }),
|
|
406
|
-
...(idEq || idGt || idGte || idLt || idLte || (idIn && idIn.length > 0)
|
|
407
|
-
? {
|
|
408
|
-
id: {
|
|
409
|
-
...(idEq && { eq: idEq }),
|
|
410
|
-
...(idGt && { gt: idGt }),
|
|
411
|
-
...(idGte && { gte: idGte }),
|
|
412
|
-
...(idLt && { lt: idLt }),
|
|
413
|
-
...(idLte && { lte: idLte }),
|
|
414
|
-
...(idIn && idIn.length > 0 && { in: idIn }),
|
|
415
|
-
},
|
|
416
|
-
}
|
|
417
|
-
: {}),
|
|
418
|
-
...(targetAppVersion !== undefined && { targetAppVersion }),
|
|
419
|
-
...(targetAppVersionIn && { targetAppVersionIn }),
|
|
420
|
-
...(targetAppVersionNotNull !== undefined && {
|
|
421
|
-
targetAppVersionNotNull,
|
|
422
|
-
}),
|
|
423
|
-
...(fingerprintHash !== undefined && { fingerprintHash }),
|
|
424
|
-
},
|
|
425
|
-
limit,
|
|
426
|
-
page,
|
|
427
|
-
cursor:
|
|
428
|
-
after || before
|
|
429
|
-
? {
|
|
430
|
-
after,
|
|
431
|
-
before,
|
|
432
|
-
}
|
|
433
|
-
: undefined,
|
|
434
|
-
},
|
|
435
|
-
context,
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
return new Response(JSON.stringify(result), {
|
|
439
|
-
status: 200,
|
|
440
|
-
headers: { "Content-Type": "application/json" },
|
|
441
|
-
});
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
const handleCreateBundles: RouteHandler = async (
|
|
445
|
-
_params,
|
|
446
|
-
request,
|
|
447
|
-
api,
|
|
448
|
-
context,
|
|
449
|
-
) => {
|
|
450
|
-
const body = await request.json();
|
|
451
|
-
const bundles = Array.isArray(body) ? body : [body];
|
|
452
|
-
|
|
453
|
-
for (const bundle of bundles) {
|
|
454
|
-
await api.insertBundle(bundle as Bundle, context);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return new Response(JSON.stringify({ success: true }), {
|
|
458
|
-
status: 201,
|
|
459
|
-
headers: { "Content-Type": "application/json" },
|
|
460
|
-
});
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
const handleUpdateBundle: RouteHandler = async (
|
|
464
|
-
params,
|
|
465
|
-
request,
|
|
466
|
-
api,
|
|
467
|
-
context,
|
|
468
|
-
) => {
|
|
469
|
-
const bundleId = requireRouteParam(params, "id");
|
|
470
|
-
const body = await request.json();
|
|
471
|
-
const payload = Array.isArray(body) ? body[0] : body;
|
|
472
|
-
const bundlePatch = requireBundlePatchPayload(payload, bundleId);
|
|
473
|
-
await api.updateBundleById(bundleId, bundlePatch, context);
|
|
474
|
-
|
|
475
|
-
return new Response(JSON.stringify({ success: true }), {
|
|
476
|
-
status: 200,
|
|
477
|
-
headers: { "Content-Type": "application/json" },
|
|
478
|
-
});
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
const handleDeleteBundle: RouteHandler = async (
|
|
482
|
-
params,
|
|
483
|
-
_request,
|
|
484
|
-
api,
|
|
485
|
-
context,
|
|
486
|
-
) => {
|
|
487
|
-
const bundleId = requireRouteParam(params, "id");
|
|
488
|
-
await api.deleteBundleById(bundleId, context);
|
|
489
|
-
|
|
490
|
-
return new Response(JSON.stringify({ success: true }), {
|
|
491
|
-
status: 200,
|
|
492
|
-
headers: { "Content-Type": "application/json" },
|
|
493
|
-
});
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
const handleGetChannels: RouteHandler = async (
|
|
497
|
-
_params,
|
|
498
|
-
_request,
|
|
499
|
-
api,
|
|
500
|
-
context,
|
|
501
|
-
) => {
|
|
502
|
-
const channels = await api.getChannels(context);
|
|
503
|
-
const response: ChannelsResponse = {
|
|
504
|
-
data: {
|
|
505
|
-
channels,
|
|
506
|
-
},
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
return new Response(JSON.stringify(response), {
|
|
510
|
-
status: 200,
|
|
511
|
-
headers: { "Content-Type": "application/json" },
|
|
512
|
-
});
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
// Route handlers map
|
|
516
|
-
const routes: Record<string, RouteHandler<any>> = {
|
|
517
|
-
version: handleVersion,
|
|
518
|
-
fingerprintUpdateWithCohort: handleFingerprintUpdateWithCohort,
|
|
519
|
-
appVersionUpdateWithCohort: handleAppVersionUpdateWithCohort,
|
|
520
|
-
getBundle: handleGetBundle,
|
|
521
|
-
getBundles: handleGetBundles,
|
|
522
|
-
createBundles: handleCreateBundles,
|
|
523
|
-
updateBundle: handleUpdateBundle,
|
|
524
|
-
deleteBundle: handleDeleteBundle,
|
|
525
|
-
getChannels: handleGetChannels,
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Creates a Web Standard Request handler for Hot Updater API
|
|
530
|
-
* This handler is framework-agnostic and works with any runtime that
|
|
531
|
-
* supports standard Request/Response objects.
|
|
532
|
-
*/
|
|
533
|
-
export function createHandler<TContext = unknown>(
|
|
534
|
-
api: HandlerAPI<TContext>,
|
|
535
|
-
options: HandlerOptions = {},
|
|
536
|
-
): (
|
|
537
|
-
request: Request,
|
|
538
|
-
context?: HotUpdaterContext<TContext>,
|
|
539
|
-
) => Promise<Response> {
|
|
540
|
-
const basePath = options.basePath ?? "/api";
|
|
541
|
-
const routeOptions = {
|
|
542
|
-
updateCheck: options.routes?.updateCheck ?? true,
|
|
543
|
-
bundles: options.routes?.bundles ?? false,
|
|
544
|
-
};
|
|
545
|
-
|
|
546
|
-
// Create and configure router
|
|
547
|
-
const router = createRouter();
|
|
548
|
-
|
|
549
|
-
// Register routes
|
|
550
|
-
addRoute(router, "GET", "/version", "version");
|
|
551
|
-
|
|
552
|
-
if (routeOptions.updateCheck) {
|
|
553
|
-
addRoute(
|
|
554
|
-
router,
|
|
555
|
-
"GET",
|
|
556
|
-
"/fingerprint/:platform/:fingerprintHash/:channel/:minBundleId/:bundleId",
|
|
557
|
-
"fingerprintUpdateWithCohort",
|
|
558
|
-
);
|
|
559
|
-
addRoute(
|
|
560
|
-
router,
|
|
561
|
-
"GET",
|
|
562
|
-
"/fingerprint/:platform/:fingerprintHash/:channel/:minBundleId/:bundleId/:cohort",
|
|
563
|
-
"fingerprintUpdateWithCohort",
|
|
564
|
-
);
|
|
565
|
-
addRoute(
|
|
566
|
-
router,
|
|
567
|
-
"GET",
|
|
568
|
-
"/app-version/:platform/:appVersion/:channel/:minBundleId/:bundleId",
|
|
569
|
-
"appVersionUpdateWithCohort",
|
|
570
|
-
);
|
|
571
|
-
addRoute(
|
|
572
|
-
router,
|
|
573
|
-
"GET",
|
|
574
|
-
"/app-version/:platform/:appVersion/:channel/:minBundleId/:bundleId/:cohort",
|
|
575
|
-
"appVersionUpdateWithCohort",
|
|
576
|
-
);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (routeOptions.bundles) {
|
|
580
|
-
addRoute(router, "GET", "/api/bundles/channels", "getChannels");
|
|
581
|
-
addRoute(router, "GET", "/api/bundles/:id", "getBundle");
|
|
582
|
-
addRoute(router, "GET", "/api/bundles", "getBundles");
|
|
583
|
-
addRoute(router, "POST", "/api/bundles", "createBundles");
|
|
584
|
-
addRoute(router, "PATCH", "/api/bundles/:id", "updateBundle");
|
|
585
|
-
addRoute(router, "DELETE", "/api/bundles/:id", "deleteBundle");
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
return async (
|
|
589
|
-
request: Request,
|
|
590
|
-
context?: HotUpdaterContext<TContext>,
|
|
591
|
-
): Promise<Response> => {
|
|
45
|
+
const requiresApiKey = (handlerName: string): boolean =>
|
|
46
|
+
handlerName === "appVersionReleaseCatalog" ||
|
|
47
|
+
handlerName === "fingerprintReleaseCatalog" ||
|
|
48
|
+
handlerName === "artifact" ||
|
|
49
|
+
handlerName === "appendBundleEvent";
|
|
50
|
+
|
|
51
|
+
const createRequestHandler =
|
|
52
|
+
({
|
|
53
|
+
api,
|
|
54
|
+
apiKeyAuth,
|
|
55
|
+
privateResponses = false,
|
|
56
|
+
routeHandlers,
|
|
57
|
+
router,
|
|
58
|
+
}: {
|
|
59
|
+
readonly api: HandlerAPI;
|
|
60
|
+
readonly apiKeyAuth?: {
|
|
61
|
+
readonly authenticate: (request: Request) => Promise<boolean>;
|
|
62
|
+
};
|
|
63
|
+
readonly privateResponses?: boolean;
|
|
64
|
+
readonly routeHandlers: Record<string, RouteHandler>;
|
|
65
|
+
readonly router: ReturnType<typeof createRouter<string>>;
|
|
66
|
+
}): HotUpdaterHandler =>
|
|
67
|
+
async (request): Promise<Response> => {
|
|
592
68
|
try {
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const routePath = path.startsWith(basePath)
|
|
599
|
-
? path.slice(basePath.length)
|
|
600
|
-
: path;
|
|
601
|
-
|
|
602
|
-
// Find matching route
|
|
603
|
-
const match = findRoute(router, method, routePath);
|
|
604
|
-
|
|
69
|
+
const match = findRoute(
|
|
70
|
+
router,
|
|
71
|
+
request.method,
|
|
72
|
+
new URL(request.url).pathname,
|
|
73
|
+
);
|
|
605
74
|
if (!match) {
|
|
606
|
-
return
|
|
607
|
-
status: 404,
|
|
608
|
-
headers: { "Content-Type": "application/json" },
|
|
609
|
-
});
|
|
75
|
+
return errorResponse("Not found", 404);
|
|
610
76
|
}
|
|
611
77
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
78
|
+
if (apiKeyAuth !== undefined && requiresApiKey(match.data)) {
|
|
79
|
+
let authenticated: boolean;
|
|
80
|
+
try {
|
|
81
|
+
authenticated = await apiKeyAuth.authenticate(request);
|
|
82
|
+
} catch {
|
|
83
|
+
return errorResponse("Service unavailable", 503);
|
|
84
|
+
}
|
|
85
|
+
if (!authenticated) {
|
|
86
|
+
return errorResponse("Unauthorized", 401);
|
|
87
|
+
}
|
|
619
88
|
}
|
|
620
89
|
|
|
621
|
-
|
|
90
|
+
const handler = routeHandlers[match.data];
|
|
91
|
+
if (!handler) {
|
|
92
|
+
return errorResponse("Handler not found", 500);
|
|
93
|
+
}
|
|
94
|
+
const response = await handler(match.params, request, api);
|
|
95
|
+
return privateResponses ? withPrivateNoStore(response) : response;
|
|
622
96
|
} catch (error) {
|
|
623
97
|
if (error instanceof HandlerBadRequestError) {
|
|
624
|
-
return
|
|
625
|
-
status: 400,
|
|
626
|
-
headers: { "Content-Type": "application/json" },
|
|
627
|
-
});
|
|
98
|
+
return errorResponse(error.message, 400);
|
|
628
99
|
}
|
|
629
|
-
|
|
630
100
|
console.error("Hot Updater handler error:", error);
|
|
631
|
-
return
|
|
632
|
-
|
|
633
|
-
error: "Internal server error",
|
|
634
|
-
message: error instanceof Error ? error.message : "Unknown error",
|
|
635
|
-
}),
|
|
101
|
+
return Response.json(
|
|
102
|
+
{ error: "Internal server error" },
|
|
636
103
|
{
|
|
104
|
+
headers: { "cache-control": "private, no-store" },
|
|
637
105
|
status: 500,
|
|
638
|
-
headers: { "Content-Type": "application/json" },
|
|
639
106
|
},
|
|
640
107
|
);
|
|
641
108
|
}
|
|
642
109
|
};
|
|
110
|
+
|
|
111
|
+
const createDownloadStorageRouteHandler =
|
|
112
|
+
(
|
|
113
|
+
downloadStorageObject: (
|
|
114
|
+
token: string,
|
|
115
|
+
signature: string,
|
|
116
|
+
) => Promise<Response | null>,
|
|
117
|
+
): RouteHandler =>
|
|
118
|
+
async (params) => {
|
|
119
|
+
const token = params.token;
|
|
120
|
+
const signature = params.signature;
|
|
121
|
+
if (!token || !signature) {
|
|
122
|
+
return errorResponse("Not found", 404);
|
|
123
|
+
}
|
|
124
|
+
const response = await downloadStorageObject(token, signature);
|
|
125
|
+
if (!response) {
|
|
126
|
+
return errorResponse("Not found", 404);
|
|
127
|
+
}
|
|
128
|
+
const headers = new Headers(response.headers);
|
|
129
|
+
if (!headers.has("cache-control")) {
|
|
130
|
+
headers.set("cache-control", "public, max-age=31536000, immutable");
|
|
131
|
+
}
|
|
132
|
+
return new Response(response.body, {
|
|
133
|
+
headers,
|
|
134
|
+
status: response.status,
|
|
135
|
+
statusText: response.statusText,
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export function createHandlers(api: HandlerAPI): HotUpdaterHandlers {
|
|
140
|
+
return createHotUpdaterHandlers(api);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function createHotUpdaterHandlers(
|
|
144
|
+
api: HandlerAPI,
|
|
145
|
+
analytics?: AnalyticsProvider,
|
|
146
|
+
apiKeyAuth?: {
|
|
147
|
+
readonly authenticate: (request: Request) => Promise<boolean>;
|
|
148
|
+
readonly headerName: string;
|
|
149
|
+
},
|
|
150
|
+
downloadStorageObject?: (
|
|
151
|
+
token: string,
|
|
152
|
+
signature: string,
|
|
153
|
+
) => Promise<Response | null>,
|
|
154
|
+
): HotUpdaterHandlers {
|
|
155
|
+
const routeHandlers: Record<string, RouteHandler> = {
|
|
156
|
+
...createVersionRouteHandlers(),
|
|
157
|
+
...createReleaseCatalogRouteHandlers(apiKeyAuth?.headerName),
|
|
158
|
+
...createReleaseManagementRouteHandlers(),
|
|
159
|
+
...createBundleRouteHandlers(),
|
|
160
|
+
...(analytics === undefined ? {} : createAnalyticsRouteHandlers(analytics)),
|
|
161
|
+
...(downloadStorageObject === undefined
|
|
162
|
+
? {}
|
|
163
|
+
: {
|
|
164
|
+
downloadStorageObject: createDownloadStorageRouteHandler(
|
|
165
|
+
downloadStorageObject,
|
|
166
|
+
),
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const clientRouter = createRouter<string>();
|
|
171
|
+
const addClientRoute = (
|
|
172
|
+
method: string,
|
|
173
|
+
path: string,
|
|
174
|
+
handler: string,
|
|
175
|
+
): void => addRoute(clientRouter, method, path, handler);
|
|
176
|
+
addClientRoute("GET", "/version", "version");
|
|
177
|
+
if (downloadStorageObject !== undefined) {
|
|
178
|
+
addClientRoute(
|
|
179
|
+
"GET",
|
|
180
|
+
"/storage/:token/:signature",
|
|
181
|
+
"downloadStorageObject",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
addClientRoute(
|
|
185
|
+
"GET",
|
|
186
|
+
"/release-catalogs/app-version/:platform/:channelKey/:appVersion",
|
|
187
|
+
"appVersionReleaseCatalog",
|
|
188
|
+
);
|
|
189
|
+
addClientRoute(
|
|
190
|
+
"GET",
|
|
191
|
+
"/release-catalogs/fingerprint/:platform/:channelKey/:fingerprintHash",
|
|
192
|
+
"fingerprintReleaseCatalog",
|
|
193
|
+
);
|
|
194
|
+
addClientRoute(
|
|
195
|
+
"GET",
|
|
196
|
+
"/artifacts/:targetBundleId/from/:currentBundleId",
|
|
197
|
+
"artifact",
|
|
198
|
+
);
|
|
199
|
+
if (analytics !== undefined) {
|
|
200
|
+
registerAnalyticsClientRoutes(addClientRoute);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const adminRouter = createRouter<string>();
|
|
204
|
+
const addAdminRoute = (method: string, path: string, handler: string): void =>
|
|
205
|
+
addRoute(adminRouter, method, path, handler);
|
|
206
|
+
addAdminRoute("GET", "/releases/:id", "getRelease");
|
|
207
|
+
addAdminRoute("GET", "/releases", "getReleases");
|
|
208
|
+
addAdminRoute("PATCH", "/releases/:id", "updateRelease");
|
|
209
|
+
addAdminRoute("POST", "/releases/:id/preflight", "preflightRelease");
|
|
210
|
+
addAdminRoute("DELETE", "/releases/:id", "deleteRelease");
|
|
211
|
+
addAdminRoute("GET", "/release-catalogs/:scopeKey", "getReleaseCatalogRow");
|
|
212
|
+
addAdminRoute("GET", "/release-catalogs", "getReleaseCatalogs");
|
|
213
|
+
addAdminRoute(
|
|
214
|
+
"POST",
|
|
215
|
+
"/release-catalogs/:scopeKey/rebuild",
|
|
216
|
+
"rebuildReleaseCatalog",
|
|
217
|
+
);
|
|
218
|
+
addAdminRoute("POST", "/database/commit", "commitDatabase");
|
|
219
|
+
addAdminRoute("GET", "/channels", "getChannels");
|
|
220
|
+
addAdminRoute("POST", "/channels", "createChannel");
|
|
221
|
+
addAdminRoute("DELETE", "/channels/:id", "deleteChannel");
|
|
222
|
+
addAdminRoute("GET", "/bundles/:id", "getBundle");
|
|
223
|
+
addAdminRoute("GET", "/bundles", "getBundles");
|
|
224
|
+
addAdminRoute("POST", "/bundles", "createBundles");
|
|
225
|
+
addAdminRoute("PATCH", "/bundles/:id", "updateBundle");
|
|
226
|
+
addAdminRoute("DELETE", "/bundles/:id", "deleteBundle");
|
|
227
|
+
if (analytics !== undefined) {
|
|
228
|
+
registerAnalyticsAdminRoutes(addAdminRoute);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return Object.freeze({
|
|
232
|
+
client: createRequestHandler({
|
|
233
|
+
api,
|
|
234
|
+
apiKeyAuth,
|
|
235
|
+
routeHandlers,
|
|
236
|
+
router: clientRouter,
|
|
237
|
+
}),
|
|
238
|
+
admin: createRequestHandler({
|
|
239
|
+
api,
|
|
240
|
+
privateResponses: true,
|
|
241
|
+
routeHandlers,
|
|
242
|
+
router: adminRouter,
|
|
243
|
+
}),
|
|
244
|
+
});
|
|
643
245
|
}
|