OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / android / app / updater.js

70 lines · javascript ref:
// Self-update from the forge (DESIGN-android.md §6). Android's own HTTPS (fetch,
// UpdaterModule.download), never the worklet: Bare on Android has no DNS.
//   check()        -> { version, apkUrl, shaUrl } | null
//   download(u, p) -> path of an APK whose SHA-256 matched the published .sha256
//   install(path)  -> asks for "install unknown apps" first if needed; Android asks the user
import { NativeModules, DeviceEventEmitter } from 'react-native'
import RNFS from 'react-native-fs'
import { pickUpdate, parseSha256 } from './update-pick'

export const FORGE = 'https://7rnx.net/git'
const native = NativeModules.CthuloidUpdater

export const installedVersion = native ? native.versionName : null

export async function check () {
  if (!native) return null
  // release-index is the anonymous-allowed listing; the other one answers 403 to a phone.
  const res = await fetch(FORGE + '/rpc/release-index', {
    method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}'
  })
  if (!res.ok) throw new Error('release index: HTTP ' + res.status)
  const body = await res.json()
  const files = (body && body.result && body.result.files) || []
  return pickUpdate(files, installedVersion, FORGE)
}

// Resumable: a download cut off (the screen locked, the app was frozen, WiFi
// dropped) leaves its partial file, and the next call continues from it
// (UpdaterModule.download). Only OTHER versions' files are removed.
export async function download (u, onProgress) {
  const dir = native.updatesDir
  await RNFS.mkdir(dir)
  const name = 'cthuloid-lights-' + u.version + '.apk'
  for (const f of await RNFS.readDir(dir)) if (f.name !== name) await RNFS.unlink(f.path).catch(() => {})

  const shaRes = await fetch(u.shaUrl)
  if (!shaRes.ok) throw new Error('checksum: HTTP ' + shaRes.status)
  const want = parseSha256(await shaRes.text())
  if (!want) throw new Error('checksum file is not a SHA-256')

  const path = dir + '/' + name
  const sub = DeviceEventEmitter.addListener('cthuloid-update-progress', (p) => {
    if (onProgress && p.total > 0) onProgress(p.done / p.total)
  })
  try {
    await native.download(u.apkUrl, path)
  } catch (e) {
    e.paused = true   // the partial file is kept; calling download() again resumes
    throw e
  } finally {
    sub.remove()
  }
  const got = (await RNFS.hash(path, 'sha256')).toLowerCase()
  if (got !== want) {
    await RNFS.unlink(path).catch(() => {})
    throw new Error('download corrupted (checksum mismatch), try again')
  }
  return path
}

// true = the installer is open; false = sent to settings to allow installs first.
export async function install (path) {
  if (!(await native.canInstall())) {
    await native.openInstallSettings()
    return false
  }
  await native.install(path)
  return true
}