OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / android / app / worklet-host.js

69 lines · javascript ref:
// Start the worklet and read what it says. The ONLY file that knows
// react-native-bare-kit exists.
//
// Patterns from favela-surface-android/lib/host/worklet.js, where each was paid for:
//   * The bundle is started from a FILE PATH (Worklet.start(path, null, args)), copied
//     out of the APK assets natively. Passing it as a JS string ran the RN heap out of
//     memory on a phone once the bundle grew (ours is ~0.8 MB, but the path costs nothing).
//   * IPC is BYTES both ways; text conversion is ours (./utf8.js), and chunk
//     boundaries are neither message nor character boundaries.
//   * bare-kit suspends the worklet whenever the app goes to the background. favela
//     had to override that; here it is exactly right -- the lamps see the sockets go
//     and free this phone's slots at once.
import { Worklet } from 'react-native-bare-kit'
import RNFS from 'react-native-fs'
import { encodeUtf8, createUtf8Decoder } from './utf8'

const BUNDLE = 'worklet.bundle'
let started = null

// Copy the bundle out of the APK when this APK's build stamp differs from the copy's
// (a new APK). Not by size: two builds can produce bundles of the same size.
async function bundlePath (storage) {
  const dest = storage + '/' + BUNDLE
  const stampFile = dest + '.stamp'
  let want = null
  try { want = (await RNFS.readFileAssets(BUNDLE + '.stamp', 'utf8')).trim() } catch {}
  let have = null
  try { have = (await RNFS.readFile(stampFile, 'utf8')).trim() } catch {}
  if (!want || want !== have || !(await RNFS.exists(dest))) {
    try { await RNFS.unlink(dest) } catch {}
    await RNFS.copyFileAssets(BUNDLE, dest)
    if (want) await RNFS.writeFile(stampFile, want, 'utf8')
  }
  return dest
}

// Resolves { onMessage(fn), send(obj), worklet } once the worklet is started.
export function startWorklet () {
  if (started) return started
  started = (async () => {
    const storage = RNFS.DocumentDirectoryPath
    const path = await bundlePath(storage)
    const worklet = new Worklet()
    // Storage is the one positional argument, ever (see lib/worklet.js).
    worklet.start(path, null, [storage])

    const listeners = []
    const decode = createUtf8Decoder()
    let buf = ''
    worklet.IPC.on('data', (data) => {
      buf += decode(data)
      let i
      while ((i = buf.indexOf('\n')) !== -1) {
        const line = buf.slice(0, i)
        buf = buf.slice(i + 1)
        let msg
        try { msg = JSON.parse(line) } catch { continue }
        for (const fn of listeners) fn(msg)
      }
    })
    return {
      worklet,
      onMessage (fn) { listeners.push(fn) },
      send (obj) { worklet.IPC.write(encodeUtf8(JSON.stringify(obj) + '\n')) }
    }
  })()
  return started
}