 function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }import './polyfills'

import hashjs from 'hash.js'
import HmacDRBG from 'hmac-drbg'
import { base64 } from 'rfc4648'
import { makeFetchResponse } from 'serverlet'
import { Bridge, bridgifyObject } from 'yaob'

import {
  addEdgeCorePlugins,
  lockEdgeCorePlugins,
  makeContext,
  makeFakeWorld
} from '../../core/core'






import { hideProperties } from '../hidden-properties'
import { makeNativeBridge } from './native-bridge'
import { YAOB_THROTTLE_MS } from './react-native-types'

// Tracks the status of different URI endpoints for the CORS bouncer:
const endpointCorsState = new Map








()

// Set up the bridges:
const [nativeBridge, reactBridge] =
  window.edgeCore != null
    ? [
        // Android:
        makeNativeBridge((id, name, args) => {
          window.edgeCore.call(id, name, JSON.stringify(args))
        }),
        new Bridge({
          hideProperties,
          sendMessage(message) {
            window.edgeCore.postMessage(JSON.stringify(message))
          },
          throttleMs: YAOB_THROTTLE_MS
        })
      ]
    : [
        // iOS:
        makeNativeBridge((id, name, args) => {
          window.webkit.messageHandlers.edgeCore.postMessage([id, name, args])
        }),
        new Bridge({
          hideProperties,
          sendMessage(message) {
            window.webkit.messageHandlers.edgeCore.postMessage([
              0,
              'postMessage',
              [JSON.stringify(message)]
            ])
          },
          throttleMs: YAOB_THROTTLE_MS
        })
      ]

// Set up global objects:
window.addEdgeCorePlugins = addEdgeCorePlugins
window.nativeBridge = nativeBridge
window.reactBridge = reactBridge

function loadPlugins(pluginUris) {
  const { head } = window.document
  if (head == null || pluginUris.length === 0) {
    lockEdgeCorePlugins()
    return
  }

  let loaded = 0
  const handleLoad = () => {
    if (++loaded >= pluginUris.length) lockEdgeCorePlugins()
  }

  for (const uri of pluginUris) {
    const script = document.createElement('script')
    script.addEventListener('error', handleLoad)
    script.addEventListener('load', handleLoad)
    script.charset = 'utf-8'
    script.defer = true
    script.src = uri
    head.appendChild(script)
  }
}

async function makeIo() {
  const csprng = new HmacDRBG({
    hash: hashjs.sha256,
    entropy: base64.parse(await nativeBridge.call('randomBytes', 32))
  })

  const nativeFetch = async (uri, opts = {}) => {
    const { method = 'GET', headers = {}, body } = opts
    const response = await nativeBridge.call(
      'fetch',
      uri,
      method,
      headers,
      body instanceof ArrayBuffer
        ? base64.stringify(new Uint8Array(body))
        : body,
      body instanceof ArrayBuffer
    )

    return makeFetchResponse({
      status: response.status,
      headers: response.headers,
      body: response.bodyIsBase64 ? base64.parse(response.body) : response.body
    })
  }

  const io = {
    disklet: {
      delete(path) {
        return nativeBridge.call('diskletDelete', normalizePath(path))
      },
      async getData(path) {
        const data = await nativeBridge.call(
          'diskletGetData',
          normalizePath(path)
        )
        return base64.parse(data)
      },
      getText(path) {
        return nativeBridge.call('diskletGetText', normalizePath(path))
      },
      list(path = '') {
        return nativeBridge.call('diskletList', normalizePath(path))
      },
      setData(path, data) {
        return nativeBridge.call(
          'diskletSetData',
          normalizePath(path),
          base64.stringify(data)
        )
      },
      setText(path, text) {
        return nativeBridge.call('diskletSetText', normalizePath(path), text)
      }
    },

    random: bytes => csprng.generate(bytes),

    async scrypt(data, salt, n, r, p, dklen) {
      const hash = await nativeBridge.call(
        'scrypt',
        base64.stringify(data),
        base64.stringify(salt),
        n,
        r,
        p,
        dklen
      )
      return base64.parse(hash)
    },

    // Networking:
    async fetch(
      uri,
      opts
    ) {
      const { corsBypass = 'auto' } = _nullishCoalesce(opts, () => ( {}))

      if (corsBypass === 'always') {
        return await nativeFetch(uri, opts)
      }
      if (corsBypass === 'never') {
        return await window.fetch(uri, opts)
      }

      const { protocol, host, pathname } = new URL(uri)
      const endpoint = `${protocol}//${host}${pathname}`
      const state = _nullishCoalesce(endpointCorsState.get(endpoint), () => ( {
        windowSuccess: false,
        nativeSuccess: false
      }))
      if (!endpointCorsState.has(endpoint)) {
        endpointCorsState.set(endpoint, state)
      }

      // If the native fetch worked,
      // then we can guess that the server has a CORS problem,
      // so don't even bother with `window.fetch`:
      if (!state.nativeSuccess) {
        try {
          const response = await window.fetch(uri, opts)
          state.windowSuccess = true
          return response
        } catch (error) {
          // If `window.fetch` has ever worked,
          // then we know the server has the right CORS headers,
          // so don't even bother with the native fallback:
          if (state.windowSuccess) throw error
        }
      }

      const response = await nativeFetch(uri, opts)
      state.nativeSuccess = true
      return response
    },

    async fetchCors(
      uri,
      opts = {}
    ) {
      return await io.fetch(uri, opts)
    }
  }

  return io
}

/**
 * Interprets a path as a series of folder lookups,
 * handling special components like `.` and `..`.
 */
export function normalizePath(path) {
  if (/^\//.test(path)) throw new Error('Absolute paths are not supported')
  const parts = path.split('/')

  // Shift down good elements, dropping bad ones:
  let i = 0 // Read index
  let j = 0 // Write index
  while (i < parts.length) {
    const part = parts[i++]
    if (part === '..') j--
    else if (part !== '.' && part !== '') parts[j++] = part

    if (j < 0) throw new Error('Path would escape folder')
  }

  // Array items from 0 to j are the path:
  return parts.slice(0, j).join('/')
}

// Send the root object:
const workerApi = bridgifyObject({
  async makeEdgeContext(nativeIo, logBackend, pluginUris, opts) {
    loadPlugins(pluginUris)
    const io = await makeIo()
    return await makeContext({ io, nativeIo }, logBackend, opts)
  },

  async makeFakeEdgeWorld(nativeIo, logBackend, pluginUris, users = []) {
    loadPlugins(pluginUris)
    const io = await makeIo()
    return makeFakeWorld({ io, nativeIo }, logBackend, users)
  }
})
reactBridge.sendRoot(workerApi)
