|
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