|
1 | 1 | import { OpenAPIHono, createRoute } from "@hono/zod-openapi";
|
2 | 2 | import { compare, compareVersions, satisfies } from "compare-versions";
|
3 |
| -import { z } from "zod"; |
| 3 | +import qs from "qs"; |
| 4 | +import type { z } from "zod"; |
4 | 5 | import { getStorageProvider } from "../storage/factory";
|
5 | 6 | import type { Env } from "../types/env";
|
6 | 7 | import { isStorageError } from "../types/error";
|
@@ -206,6 +207,11 @@ router.openapi(routes.updateCheck, async (c) => {
|
206 | 207 | // Check app version compatibility
|
207 | 208 | if (!query.isCompanion && packageEntry.appVersion) {
|
208 | 209 | if (!satisfies(sanitizedAppVersion, packageEntry.appVersion)) {
|
| 210 | + // Special handling for pre-release versions |
| 211 | + if (sanitizedAppVersion.includes("-")) { |
| 212 | + // For pre-release versions, we should make the update available |
| 213 | + latestSatisfyingEnabledPackage ||= packageEntry; |
| 214 | + } |
209 | 215 | continue;
|
210 | 216 | }
|
211 | 217 | }
|
@@ -287,7 +293,7 @@ router.openapi(routes.updateCheck, async (c) => {
|
287 | 293 |
|
288 | 294 | // Handle rollout if specified
|
289 | 295 | if (
|
290 |
| - latestSatisfyingEnabledPackage.rollout && |
| 296 | + typeof latestSatisfyingEnabledPackage.rollout === "number" && |
291 | 297 | latestSatisfyingEnabledPackage.rollout < 100
|
292 | 298 | ) {
|
293 | 299 | if (!query.clientUniqueId) {
|
@@ -333,188 +339,63 @@ router.openapi(routes.updateCheck, async (c) => {
|
333 | 339 | } satisfies UpdateCheckResponse);
|
334 | 340 | });
|
335 | 341 |
|
336 |
| -// Legacy v1 endpoint implementations remain the same |
337 | 342 | router.openapi(routes.updateCheckV1, async (c) => {
|
338 |
| - const storage = getStorageProvider(c); |
339 | 343 | const query = c.req.valid("query");
|
340 | 344 |
|
341 |
| - const { deployment_key: deploymentKey, app_version: receivedAppVersion } = |
342 |
| - query; |
343 |
| - |
344 | 345 | try {
|
345 |
| - const deploymentInfo = await storage.getDeploymentInfo( |
346 |
| - deploymentKey.trim(), |
347 |
| - ); |
348 |
| - const history = await storage.getPackageHistory( |
349 |
| - "", // accountId not needed |
350 |
| - deploymentInfo.appId, |
351 |
| - deploymentInfo.deploymentId, |
352 |
| - ); |
353 |
| - |
354 |
| - // Handle empty package history |
355 |
| - if (!history || history.length === 0) { |
356 |
| - return c.json({ |
357 |
| - update_info: { |
358 |
| - is_available: false, |
359 |
| - is_mandatory: false, |
360 |
| - app_version: receivedAppVersion, |
361 |
| - should_run_binary_version: true, |
362 |
| - }, |
363 |
| - } satisfies LegacyUpdateCheckResponse); |
364 |
| - } |
365 |
| - |
366 |
| - // Find appropriate package using original CodePush logic |
367 |
| - let foundRequestPackageInHistory = false; |
368 |
| - let latestSatisfyingEnabledPackage: Package | undefined; |
369 |
| - let latestEnabledPackage: Package | undefined; |
370 |
| - let shouldMakeUpdateMandatory = false; |
371 |
| - |
372 |
| - // Iterate history backwards to find appropriate package |
373 |
| - for (let i = history.length - 1; i >= 0; i--) { |
374 |
| - const packageEntry = history[i]; |
375 |
| - |
376 |
| - foundRequestPackageInHistory = |
377 |
| - foundRequestPackageInHistory || |
378 |
| - (!query.label && !query.package_hash) || |
379 |
| - (query.label && packageEntry.label === query.label) || |
380 |
| - (!query.label && packageEntry.packageHash === query.package_hash); |
381 |
| - |
382 |
| - if (packageEntry.isDisabled) { |
383 |
| - continue; |
384 |
| - } |
385 |
| - |
386 |
| - latestEnabledPackage ||= packageEntry; |
387 |
| - |
388 |
| - if (!query.is_companion && packageEntry.appVersion) { |
389 |
| - if (!satisfies(receivedAppVersion, packageEntry.appVersion)) { |
390 |
| - continue; |
391 |
| - } |
392 |
| - } |
393 |
| - |
394 |
| - latestSatisfyingEnabledPackage ||= packageEntry; |
395 |
| - |
396 |
| - if (foundRequestPackageInHistory) { |
397 |
| - break; |
398 |
| - } |
399 |
| - if (packageEntry.isMandatory) { |
400 |
| - shouldMakeUpdateMandatory = true; |
401 |
| - break; |
402 |
| - } |
403 |
| - } |
404 |
| - |
405 |
| - if (!latestEnabledPackage) { |
406 |
| - return c.json({ |
407 |
| - update_info: { |
408 |
| - is_available: false, |
409 |
| - is_mandatory: false, |
410 |
| - app_version: receivedAppVersion, |
411 |
| - }, |
412 |
| - } satisfies LegacyUpdateCheckResponse); |
413 |
| - } |
414 |
| - |
415 |
| - if (!latestSatisfyingEnabledPackage) { |
416 |
| - return c.json({ |
417 |
| - update_info: { |
418 |
| - is_available: false, |
419 |
| - is_mandatory: false, |
420 |
| - app_version: receivedAppVersion, |
421 |
| - should_run_binary_version: true, |
422 |
| - }, |
423 |
| - } satisfies LegacyUpdateCheckResponse); |
424 |
| - } |
425 |
| - |
426 |
| - if (latestSatisfyingEnabledPackage.packageHash === query.package_hash) { |
427 |
| - const response: LegacyUpdateCheckResponse = { |
428 |
| - update_info: { |
429 |
| - is_available: false, |
430 |
| - is_mandatory: false, |
431 |
| - app_version: receivedAppVersion, |
432 |
| - }, |
433 |
| - }; |
434 |
| - |
435 |
| - if ( |
436 |
| - compareVersions( |
437 |
| - receivedAppVersion, |
438 |
| - latestEnabledPackage.appVersion, |
439 |
| - ">", |
440 |
| - ) |
441 |
| - ) { |
442 |
| - response.update_info.app_version = latestEnabledPackage.appVersion; |
443 |
| - } else if ( |
444 |
| - !satisfies(receivedAppVersion, latestEnabledPackage.appVersion) |
445 |
| - ) { |
446 |
| - response.update_info.update_app_version = true; |
447 |
| - response.update_info.app_version = latestEnabledPackage.appVersion; |
448 |
| - } |
449 |
| - |
450 |
| - return c.json(response); |
451 |
| - } |
452 |
| - |
453 |
| - let downloadUrl = latestSatisfyingEnabledPackage.blobUrl; |
454 |
| - let packageSize = latestSatisfyingEnabledPackage.size; |
455 |
| - |
456 |
| - if ( |
457 |
| - query.package_hash && |
458 |
| - latestSatisfyingEnabledPackage.diffPackageMap?.[query.package_hash] |
459 |
| - ) { |
460 |
| - const diff = |
461 |
| - latestSatisfyingEnabledPackage.diffPackageMap[query.package_hash]; |
462 |
| - downloadUrl = diff.url; |
463 |
| - packageSize = diff.size; |
464 |
| - } |
| 346 | + // Transform snake_case query to camelCase for reuse |
| 347 | + const camelCaseQuery = { |
| 348 | + deploymentKey: query.deployment_key, |
| 349 | + appVersion: query.app_version, |
| 350 | + packageHash: query.package_hash, |
| 351 | + label: query.label, |
| 352 | + clientUniqueId: query.client_unique_id, |
| 353 | + isCompanion: query.is_companion, |
| 354 | + } satisfies z.infer<typeof UpdateCheckParams>; |
| 355 | + |
| 356 | + // Create a new context with transformed query |
| 357 | + const transformedContext = { |
| 358 | + ...c, |
| 359 | + req: { ...c.req, query: camelCaseQuery }, |
| 360 | + }; |
465 | 361 |
|
466 |
| - if ( |
467 |
| - latestSatisfyingEnabledPackage.rollout && |
468 |
| - latestSatisfyingEnabledPackage.rollout < 100 |
469 |
| - ) { |
470 |
| - if (!query.client_unique_id) { |
471 |
| - return c.json({ |
472 |
| - update_info: { |
473 |
| - is_available: false, |
474 |
| - is_mandatory: false, |
475 |
| - app_version: receivedAppVersion, |
476 |
| - }, |
477 |
| - } satisfies LegacyUpdateCheckResponse); |
478 |
| - } |
| 362 | + // Reuse updateCheck logic |
| 363 | + const response = await router.fetch( |
| 364 | + new Request( |
| 365 | + `${c.req.url.split("?")[0].replace("/v0.1/public/codepush/update_check", "/updateCheck")}?${qs.stringify( |
| 366 | + camelCaseQuery, |
| 367 | + )}`, |
| 368 | + { headers: c.req.raw.headers }, |
| 369 | + ), |
| 370 | + transformedContext.env, |
| 371 | + ); |
479 | 372 |
|
480 |
| - const isInRollout = rolloutStrategy( |
481 |
| - query.client_unique_id, |
482 |
| - latestSatisfyingEnabledPackage.rollout, |
483 |
| - latestSatisfyingEnabledPackage.packageHash, |
484 |
| - ); |
485 |
| - |
486 |
| - if (!isInRollout) { |
487 |
| - return c.json({ |
488 |
| - update_info: { |
489 |
| - is_available: false, |
490 |
| - is_mandatory: false, |
491 |
| - app_version: receivedAppVersion, |
492 |
| - }, |
493 |
| - } satisfies LegacyUpdateCheckResponse); |
494 |
| - } |
495 |
| - } |
| 373 | + const result = UpdateCheckResponseSchema.parse(await response.json()); |
496 | 374 |
|
497 |
| - return c.json({ |
| 375 | + // Transform camelCase response to snake_case for legacy endpoint |
| 376 | + const legacyResponse: LegacyUpdateCheckResponse = { |
498 | 377 | update_info: {
|
499 |
| - is_available: true, |
500 |
| - is_mandatory: |
501 |
| - shouldMakeUpdateMandatory || |
502 |
| - latestSatisfyingEnabledPackage.isMandatory, |
503 |
| - app_version: receivedAppVersion, |
504 |
| - package_hash: latestSatisfyingEnabledPackage.packageHash, |
505 |
| - label: latestSatisfyingEnabledPackage.label, |
506 |
| - package_size: packageSize, |
507 |
| - description: latestSatisfyingEnabledPackage.description, |
508 |
| - download_url: downloadUrl, |
| 378 | + is_available: result.updateInfo.isAvailable, |
| 379 | + is_mandatory: result.updateInfo.isMandatory, |
| 380 | + app_version: result.updateInfo.appVersion, |
| 381 | + should_run_binary_version: result.updateInfo.shouldRunBinaryVersion, |
| 382 | + update_app_version: result.updateInfo.updateAppVersion, |
| 383 | + package_hash: result.updateInfo.packageHash, |
| 384 | + label: result.updateInfo.label, |
| 385 | + package_size: result.updateInfo.packageSize, |
| 386 | + description: result.updateInfo.description, |
| 387 | + download_url: result.updateInfo.downloadURL, |
509 | 388 | },
|
510 |
| - } satisfies LegacyUpdateCheckResponse); |
| 389 | + }; |
| 390 | + |
| 391 | + return c.json(legacyResponse); |
511 | 392 | } catch (error) {
|
512 | 393 | if (isStorageError(error)) {
|
513 | 394 | return c.json({
|
514 | 395 | update_info: {
|
515 | 396 | is_available: false,
|
516 | 397 | is_mandatory: false,
|
517 |
| - app_version: receivedAppVersion, |
| 398 | + app_version: query.app_version, |
518 | 399 | },
|
519 | 400 | } satisfies LegacyUpdateCheckResponse);
|
520 | 401 | }
|
|
0 commit comments