@hot-updater/azure 1.0.5 → 1.0.6

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.
@@ -7998,7 +7998,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
7998
7998
  };
7999
7999
  //#endregion
8000
8000
  //#region ../../packages/server/dist/version.mjs
8001
- const HOT_UPDATER_SERVER_VERSION = "1.0.5";
8001
+ const HOT_UPDATER_SERVER_VERSION = "1.0.6";
8002
8002
  //#endregion
8003
8003
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
8004
8004
  const createVersionRouteHandlers = (plugins = []) => {
@@ -8287,6 +8287,85 @@ async function resolveManifestArtifacts({ currentBundle, resolveFileUrl, readSto
8287
8287
  ...archiveUrl ? { archiveUrl } : {}
8288
8288
  };
8289
8289
  }
8290
+ async function loadTargetPlan({ currentBundle, readStorageText, targetBundle }) {
8291
+ const manifestStorageUri = getManifestStorageUri(targetBundle);
8292
+ const manifestFileHash = getManifestFileHash(targetBundle);
8293
+ const assetBaseStorageUri = getAssetBaseStorageUri(targetBundle);
8294
+ if (!manifestStorageUri || !manifestFileHash || !assetBaseStorageUri) return null;
8295
+ const targetManifest = await fetchBundleManifest(manifestStorageUri, readStorageText);
8296
+ if (!targetManifest) return null;
8297
+ return {
8298
+ manifestFileHash,
8299
+ manifestStorageUri,
8300
+ plan: createManifestArtifactPlan({
8301
+ assetBaseStorageUri,
8302
+ currentBundle,
8303
+ targetBundle,
8304
+ targetManifest
8305
+ }),
8306
+ targetManifest
8307
+ };
8308
+ }
8309
+ async function resolveManifestArtifactsV2({ currentBundle, resolveFileUrl, readStorageText, targetBundle }) {
8310
+ const target = await loadTargetPlan({
8311
+ currentBundle,
8312
+ readStorageText,
8313
+ targetBundle
8314
+ });
8315
+ if (!target) return null;
8316
+ const manifestUrl = await resolveFileUrl(target.manifestStorageUri);
8317
+ if (!manifestUrl) return null;
8318
+ const patched = target.plan.assets.find((asset) => asset.patch !== null);
8319
+ let patch;
8320
+ if (patched?.patch && !(patched.file.byteSize !== null && patched.patch.byteSize !== null && patched.patch.byteSize >= patched.file.byteSize)) try {
8321
+ const patchUrl = await resolveFileUrl(patched.patch.storageUri);
8322
+ if (patchUrl) patch = {
8323
+ algorithm: "bsdiff",
8324
+ assetPath: patched.assetPath,
8325
+ baseBundleId: patched.patch.baseBundleId,
8326
+ baseFileHash: patched.patch.baseFileHash,
8327
+ patchFileHash: patched.patch.patchFileHash,
8328
+ patchUrl,
8329
+ ...patched.patch.byteSize === null ? {} : { byteSize: patched.patch.byteSize }
8330
+ };
8331
+ } catch {}
8332
+ let archiveUrl = null;
8333
+ const archive = target.targetManifest.archive;
8334
+ if (archive && isContentAddressedAssetFileHash(archive.downloadFileHash) && (asByteSize(archive.downloadByteSize) ?? 0) > 0 && (asByteSize(archive.tarByteSize) ?? 0) > 0) try {
8335
+ archiveUrl = await resolveFileUrl(getBundleArchiveStorageUri({
8336
+ manifestStorageUri: target.manifestStorageUri,
8337
+ bundleId: targetBundle.id
8338
+ }));
8339
+ } catch {}
8340
+ return {
8341
+ artifactProtocolVersion: 2,
8342
+ manifestFileHash: target.manifestFileHash,
8343
+ manifestUrl,
8344
+ ...archiveUrl ? { archiveUrl } : {},
8345
+ ...patch ? { patch } : {}
8346
+ };
8347
+ }
8348
+ async function resolveManifestArtifactFiles({ paths, resolveFileUrl, readStorageText, targetBundle }) {
8349
+ if (paths.length === 0 || paths.length > 100 || new Set(paths).size !== paths.length) return null;
8350
+ const target = await loadTargetPlan({
8351
+ currentBundle: null,
8352
+ readStorageText,
8353
+ targetBundle
8354
+ });
8355
+ if (!target) return null;
8356
+ const planned = new Map(target.plan.assets.map((asset) => [asset.assetPath, asset]));
8357
+ const requested = paths.map((path) => planned.get(path));
8358
+ if (requested.some((asset) => asset === void 0)) return null;
8359
+ const files = await Promise.all(requested.map(async (asset) => {
8360
+ const url = await resolveFileUrl(asset.file.storageUri);
8361
+ if (!url) return null;
8362
+ const file = { url };
8363
+ if (asset.file.compression) file.compression = asset.file.compression;
8364
+ return [asset.assetPath, file];
8365
+ }));
8366
+ if (files.some((file) => file === null)) return null;
8367
+ return { files: Object.fromEntries(files.filter((file) => file !== null)) };
8368
+ }
8290
8369
  //#endregion
8291
8370
  //#region ../../node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/comparison-DenM3wCn.js
8292
8371
  const LETTER_DASH_NUMBER = "[a-zA-Z0-9-]";
@@ -8748,20 +8827,33 @@ const createCoreReads = (db, storage) => {
8748
8827
  const row = await db.findOne("release_catalogs", { scope_key: scopeKey });
8749
8828
  return projectReleaseCatalogRow(row === null ? null : toCatalogRow(row), input, scopeKey);
8750
8829
  },
8751
- async getArtifactInfo(targetBundleId, currentBundleId, artifactProtocolVersion) {
8830
+ getArtifactInfo: (async (targetBundleId, currentBundleId, artifactProtocolVersion) => {
8752
8831
  const { readStorageText } = storage;
8753
- if (artifactProtocolVersion !== 1 || readStorageText === void 0) return null;
8832
+ if (artifactProtocolVersion !== 1 && artifactProtocolVersion !== 2 || readStorageText === void 0) return null;
8754
8833
  const withCurrent = currentBundleId !== NIL_UUID;
8755
8834
  const [[target, current], patch] = await Promise.all([db.findByKeys("bundles", withCurrent ? [{ id: targetBundleId }, { id: currentBundleId }] : [{ id: targetBundleId }]), withCurrent ? db.findOne("bundle_patches", {
8756
8835
  bundle_id: targetBundleId,
8757
8836
  base_bundle_id: currentBundleId
8758
8837
  }) : null]);
8759
8838
  if (target === null || target === void 0) return null;
8760
- return resolveManifestArtifacts({
8839
+ const input = {
8761
8840
  currentBundle: current ? rowToBundle(toBundleRow(current)) : null,
8762
8841
  readStorageText,
8763
8842
  resolveFileUrl: storage.resolveFileUrl,
8764
8843
  targetBundle: rowToBundle(toBundleRow(target), patch === null ? [] : [toPatchRow(patch)])
8844
+ };
8845
+ return artifactProtocolVersion === 2 ? resolveManifestArtifactsV2(input) : resolveManifestArtifacts(input);
8846
+ }),
8847
+ async getArtifactFiles(bundleId, paths) {
8848
+ const { readStorageText } = storage;
8849
+ if (readStorageText === void 0) return null;
8850
+ const row = await db.findOne("bundles", { id: bundleId });
8851
+ if (row === null) return null;
8852
+ return resolveManifestArtifactFiles({
8853
+ paths,
8854
+ readStorageText,
8855
+ resolveFileUrl: storage.resolveFileUrl,
8856
+ targetBundle: rowToBundle(toBundleRow(row))
8765
8857
  });
8766
8858
  },
8767
8859
  async getBundle(id) {
@@ -10038,11 +10130,23 @@ const ADMIN_ROUTES = [
10038
10130
  //#region ../../packages/server/dist/handlerReleaseCatalogRoutes.mjs
10039
10131
  const CATALOG_CONTENT_TYPE = "application/vnd.hot-updater.release-catalog+json; version=1";
10040
10132
  const ARTIFACT_CONTENT_TYPE = "application/vnd.hot-updater.artifact+json; version=1";
10133
+ const ARTIFACT_V2_CONTENT_TYPE = "application/vnd.hot-updater.artifact+json; version=2";
10041
10134
  const ORIGIN_CACHE_TTL_MS = 5e3;
10042
10135
  const ORIGIN_CACHE_MAX_ENTRIES = 128;
10043
10136
  const NO_CATALOG_HEADER = "x-hot-updater-catalog";
10044
10137
  const CATALOG_CACHE_CONTROL = "public, max-age=0, s-maxage=5";
10045
10138
  const CATALOG_CDN_CACHE_CONTROL = "public, max-age=5, stale-while-revalidate=5, stale-if-error=0";
10139
+ const readArtifactFilePaths = async (request) => {
10140
+ let body;
10141
+ try {
10142
+ body = await request.json();
10143
+ } catch {
10144
+ return null;
10145
+ }
10146
+ const paths = body?.paths;
10147
+ if (!Array.isArray(paths) || paths.length === 0 || paths.length > 100 || !paths.every((path) => typeof path === "string" && path.length > 0) || new Set(paths).size !== paths.length) return null;
10148
+ return paths;
10149
+ };
10046
10150
  const privateNotFound = () => Response.json({ error: "Not found" }, {
10047
10151
  status: 404,
10048
10152
  headers: { "cache-control": "private, no-store" }
@@ -10152,6 +10256,30 @@ const createReleaseCatalogRouteHandlers = () => {
10152
10256
  if (invalid) return invalid;
10153
10257
  return catalogResponse(await loadCatalog(`fingerprint:${JSON.stringify(input)}`, () => core.getReleaseCatalog(input)), request);
10154
10258
  },
10259
+ artifactV2: async (params, _request, { core }) => {
10260
+ const info = await core.getArtifactInfo(requireRouteParam(params, "targetBundleId"), requireRouteParam(params, "currentBundleId"), 2);
10261
+ if (info === null) return privateNotFound();
10262
+ return new Response(JSON.stringify(info), {
10263
+ status: 200,
10264
+ headers: {
10265
+ "cache-control": "private, no-store",
10266
+ "content-type": ARTIFACT_V2_CONTENT_TYPE
10267
+ }
10268
+ });
10269
+ },
10270
+ artifactFilesV2: async (params, request, { core }) => {
10271
+ const paths = await readArtifactFilePaths(request);
10272
+ if (paths === null) return badRequest(`paths must be 1 to 100 unique strings`);
10273
+ const files = await core.getArtifactFiles(requireRouteParam(params, "targetBundleId"), paths);
10274
+ if (files === null) return privateNotFound();
10275
+ return new Response(JSON.stringify(files), {
10276
+ status: 200,
10277
+ headers: {
10278
+ "cache-control": "private, no-store",
10279
+ "content-type": ARTIFACT_V2_CONTENT_TYPE
10280
+ }
10281
+ });
10282
+ },
10155
10283
  artifactV1: async (params, _request, { core }) => {
10156
10284
  const info = await core.getArtifactInfo(requireRouteParam(params, "targetBundleId"), requireRouteParam(params, "currentBundleId"), 1);
10157
10285
  if (info === null) return privateNotFound();
@@ -10302,6 +10430,8 @@ function createHotUpdaterHandlers({ api, clientPolicy, endpoints = [], plugins =
10302
10430
  addClientRoute("GET", "/release-catalogs/app-version/:platform/:channelKey/:appVersion", "appVersionReleaseCatalog");
10303
10431
  addClientRoute("GET", "/release-catalogs/fingerprint/:platform/:channelKey/:fingerprintHash", "fingerprintReleaseCatalog");
10304
10432
  addClientRoute("GET", "/artifacts/v1/:targetBundleId/from/:currentBundleId", "artifactV1");
10433
+ addClientRoute("GET", "/artifacts/v2/:targetBundleId/from/:currentBundleId", "artifactV2");
10434
+ addClientRoute("POST", "/artifacts/v2/:targetBundleId/files", "artifactFilesV2");
10305
10435
  const adminRouter = createRouter();
10306
10436
  const addAdminRoute = mount(adminRouter, true);
10307
10437
  addAdminRoute("GET", "/version", "adminVersion");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/azure",
3
3
  "type": "module",
4
- "version": "1.0.5",
4
+ "version": "1.0.6",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "engines": {
7
7
  "node": ">=22"
@@ -42,10 +42,10 @@
42
42
  "@azure/core-auth": "1.11.0",
43
43
  "@azure/cosmos": "4.10.1",
44
44
  "@azure/storage-blob": "12.34.0",
45
- "@hot-updater/plugin-core": "1.0.5",
46
- "@hot-updater/cli-tools": "1.0.5",
47
- "@hot-updater/server": "1.0.5",
48
- "@hot-updater/protocol": "1.0.5"
45
+ "@hot-updater/cli-tools": "1.0.6",
46
+ "@hot-updater/plugin-core": "1.0.6",
47
+ "@hot-updater/protocol": "1.0.6",
48
+ "@hot-updater/server": "1.0.6"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@azure/functions": "4.16.5",
@@ -53,7 +53,7 @@
53
53
  "execa": "9.5.2",
54
54
  "jszip": "^3.10.1",
55
55
  "vitest": "4.1.4",
56
- "@hot-updater/test-utils": "1.0.5"
56
+ "@hot-updater/test-utils": "1.0.6"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsdown",