/** Tabs, history, bookmarks, about: pages. */ function bareSummonHome(ctx) { var env = ctx && ctx.env && typeof ctx.env === 'object' ? ctx.env : {} if (env.HOME) return String(env.HOME).replace(/\/+$/, '') return '/home/guest' } function bareSummonPaths(ctx) { var h = bareSummonHome(ctx) return { dir: h + '/.summon', config: h + '/.summon/config.json', cookies: h + '/.summon/cookies.json', bookmarks: h + '/.summon/bookmarks.json', history: h + '/.summon/history.jsonl' } } function bareSummonCreateTab(url) { return { url: url || 'about:summon', title: '', loading: false, status: 0, error: '', doc: null, layout: null, source: '', reader: false, find: '', history: { back: [], forward: [] }, scriptsBlocked: 0, stylesBlocked: 0 } } function bareSummonCreateSession(ctx, opts) { opts = opts || {} var jar = bareSummonCreateCookieJar() return { ctx: ctx, jar: jar, tabs: [bareSummonCreateTab(opts.url || 'about:summon')], current: 0, bookmarks: Array.isArray(opts.bookmarks) ? opts.bookmarks.slice() : [], config: opts.config || { javascript: true, images: 'alt', numbers: true }, cols: opts.cols || 80 } } function bareSummonCurrent(session) { return session.tabs[session.current] || session.tabs[0] } function bareSummonAboutBody(kind, session) { if (kind === 'blank') return '' if (kind === 'bookmarks') { var items = (session.bookmarks || []) .map(function (b) { return ( '
  • ' + String(b.title || b.url || '') + '
  • ' ) }) .join('') return ( '

    Bookmarks

    ' ) } if (kind === 'history') { var t = bareSummonCurrent(session) var h = (t.history.back || []) .slice(-20) .map(function (u) { return '
  • ' + u + '
  • ' }) .join('') return ( '

    History

    ' ) } if (kind === 'cookies') { var rows = session.jar .toJSON() .map(function (c) { return '
  • ' + c.name + ' @ ' + c.domain + c.path + '
  • ' }) .join('') return ( '

    Cookies

    ' ) } if (kind === 'net') { return '

    Net

    HTTP via ctx.httpFetch. Allow/deny: BARE_OS_HTTP_ALLOWLIST / DENYLIST.

    ' } return ( '

    summon

    Text browser for Bare OS. Page JS runs in a Bare VM (ctx.bare.bareVm) when present.

    ' + '

    Type a URL (g) or open a bookmark. Try about:bookmarks. Toggle JS with J.

    ' ) } function bareSummonCollectInlineSheets(doc) { var inline = [] function collectStyle(n) { if (n && n.type === 'element' && n.name === 'style') { inline.push(bareSummonParseStylesheet(bareSummonTextContent(n))) } var ch = (n && n.children) || [] var i for (i = 0; i < ch.length; i++) collectStyle(ch[i]) } if (doc) { collectStyle(doc.head) collectStyle(doc.body) } return inline } function bareSummonFinishLayout(session, tab, url) { var sheets = (tab.inlineSheets || []).concat(tab.fetchedSheets || []) tab.sheets = sheets var layout = bareSummonLayout(tab.doc, { cols: session.cols, sheets: sheets, numbers: session.config.numbers !== false }) tab.layout = layout tab.title = layout.title || (tab.doc && tab.doc.title) || url || tab.url return tab } function bareSummonApplyDocument(session, tab, html, url) { var doc = bareSummonParseHtml(html, {}) tab.doc = doc tab.source = html tab.url = url tab.inlineSheets = bareSummonCollectInlineSheets(doc) tab.fetchedSheets = tab.fetchedSheets || [] tab.scriptsBlocked = doc.scriptsBlocked || 0 tab.js = tab.js || { ran: 0, engine: 'off', errors: [], logs: [] } bareSummonFinishLayout(session, tab, url) } async function bareSummonEnhanceDocument(session, tab, url) { if (!tab || !tab.doc) return tab var hrefs = tab.doc.stylesheets || [] var fetched = [] var i for (i = 0; i < hrefs.length && i < 4; i++) { var abs = bareSummonResolveUrl(hrefs[i], url) if (!abs || !bareSummonIsHttp(abs)) continue try { var res = await bareSummonFetch(session.ctx, abs.href, { jar: session.jar }) if (res && res.body) fetched.push(bareSummonParseStylesheet(res.body)) else tab.stylesBlocked = (tab.stylesBlocked || 0) + 1 } catch (e) { tab.stylesBlocked = (tab.stylesBlocked || 0) + 1 } } tab.fetchedSheets = fetched if (session.config && session.config.javascript !== false) { tab.js = await bareSummonRunDocumentJs(session, tab.doc, url) tab.inlineSheets = bareSummonCollectInlineSheets(tab.doc) tab.scriptsBlocked = tab.js.blocked tab.title = (tab.doc && tab.doc.title) || tab.title } else { tab.js = { ran: 0, engine: 'off', blocked: (tab.doc.scripts && tab.doc.scripts.length) || tab.scriptsBlocked || 0, errors: [], logs: [] } } bareSummonFinishLayout(session, tab, url) return tab } function bareSummonRelayout(session) { var tab = bareSummonCurrent(session) if (!tab || !tab.doc) return tab tab.layout = bareSummonLayout(tab.doc, { cols: session.cols, sheets: tab.sheets || [], numbers: session.config.numbers !== false }) return tab } async function bareSummonNavigate(session, urlStr, opts) { opts = opts || {} var tab = bareSummonCurrent(session) var resolved = bareSummonResolveUrl(urlStr, tab.url) if (!resolved || !bareSummonUrlOk(resolved)) { tab.error = 'bad URL' tab.loading = false return tab } if (!opts.replace && tab.url && tab.url !== resolved.href) { tab.history.back.push(tab.url) tab.history.forward = [] } tab.loading = true tab.error = '' tab.reader = false if (resolved.protocol === 'about:') { var kind = resolved.pathname || 'summon' if (kind.charAt(0) === ':') kind = kind.slice(1) bareSummonApplyDocument( session, tab, bareSummonAboutBody(kind, session), resolved.href ) await bareSummonEnhanceDocument(session, tab, resolved.href) tab.status = 200 tab.loading = false return tab } if (resolved.protocol === 'file:') { var ctx = session.ctx var path = resolved.pathname || '/' try { var buf = ctx.vfs && ctx.vfs.readFile ? await ctx.vfs.readFile(path) : null var text = '' if (buf) { text = ctx.b4a && typeof ctx.b4a.toString === 'function' ? ctx.b4a.toString(buf) : String(buf) } bareSummonApplyDocument( session, tab, text || '(empty file)', resolved.href ) await bareSummonEnhanceDocument(session, tab, resolved.href) tab.status = buf ? 200 : 404 } catch (e) { tab.error = (e && e.message) || String(e) tab.status = 0 } tab.loading = false return tab } var fetched = await bareSummonFetch(session.ctx, resolved.href, { jar: session.jar, method: opts.method || 'GET', body: opts.body, headers: opts.headers }) if (!fetched.ok && !fetched.body) { tab.error = fetched.error || 'fetch failed' tab.status = fetched.status || 0 tab.loading = false return tab } tab.status = fetched.status var body = fetched.body || '' var ct = (fetched.headers && fetched.headers['content-type']) || '' if (!/html/i.test(ct) && body && body.charAt(0) !== '<') { body = '
    ' +
          body.replace(/&/g, '&').replace(/'
      }
      bareSummonApplyDocument(session, tab, body, fetched.url || resolved.href)
      await bareSummonEnhanceDocument(session, tab, fetched.url || resolved.href)
      tab.loading = false
      return tab
    }
    
    function bareSummonBack(session) {
      var tab = bareSummonCurrent(session)
      if (!tab.history.back.length) return Promise.resolve(tab)
      tab.history.forward.unshift(tab.url)
      var prev = tab.history.back.pop()
      return bareSummonNavigate(session, prev, { replace: true })
    }
    
    function bareSummonForward(session) {
      var tab = bareSummonCurrent(session)
      if (!tab.history.forward.length) return Promise.resolve(tab)
      var next = tab.history.forward.shift()
      return bareSummonNavigate(session, next, { replace: true })
    }
    
    function bareSummonAddBookmark(session, url, title) {
      session.bookmarks.push({ url: url, title: title || url })
    }
    
    function bareSummonReload(session) {
      var tab = bareSummonCurrent(session)
      return bareSummonNavigate(session, tab.url, { replace: true })
    }
    
    function bareSummonFollowLink(session, id) {
      var tab = bareSummonCurrent(session)
      var links = (tab.layout && tab.layout.links) || []
      var hit = null
      var i
      for (i = 0; i < links.length; i++) {
        if (links[i].id === id) hit = links[i]
      }
      if (!hit) return Promise.resolve(tab)
      return bareSummonNavigate(session, hit.href)
    }
    
    function bareSummonNewTab(session, url) {
      session.tabs.push(bareSummonCreateTab(url || 'about:summon'))
      session.current = session.tabs.length - 1
      return bareSummonCurrent(session)
    }
    
    function bareSummonCloseTab(session) {
      if (session.tabs.length <= 1) {
        session.tabs[0] = bareSummonCreateTab('about:summon')
        session.current = 0
        return session.tabs[0]
      }
      session.tabs.splice(session.current, 1)
      if (session.current >= session.tabs.length)
        session.current = session.tabs.length - 1
      return bareSummonCurrent(session)
    }
    
    function bareSummonSelectTab(session, index) {
      if (index < 0 || index >= session.tabs.length)
        return bareSummonCurrent(session)
      session.current = index
      return bareSummonCurrent(session)
    }
    
    function bareSummonCycleTab(session, dir) {
      var n = session.tabs.length
      if (!n) return bareSummonCurrent(session)
      session.current = (session.current + dir + n) % n
      return bareSummonCurrent(session)
    }
    
    function bareSummonBufToString(ctx, buf) {
      if (buf == null) return ''
      if (typeof buf === 'string') return buf
      if (ctx && ctx.b4a && typeof ctx.b4a.toString === 'function') {
        return ctx.b4a.toString(buf)
      }
      return String(buf)
    }
    
    async function bareSummonReadJson(ctx, path) {
      if (!ctx || !ctx.vfs || typeof ctx.vfs.readFile !== 'function') return null
      try {
        var buf = ctx.vfs.readFile(path)
        if (buf && typeof buf.then === 'function') buf = await buf
        var t = String(bareSummonBufToString(ctx, buf) || '').trim()
        if (!t) return null
        return JSON.parse(t)
      } catch (e) {
        return null
      }
    }
    
    async function bareSummonWriteJson(ctx, path, value) {
      if (!ctx || !ctx.vfs || typeof ctx.vfs.writeFile !== 'function') return false
      try {
        var dir = path.slice(0, path.lastIndexOf('/'))
        if (dir && ctx.vfs.mkdir) {
          try {
            var mk = ctx.vfs.mkdir(dir, { recursive: true })
            if (mk && typeof mk.then === 'function') await mk
          } catch (e) {
            /* ignore */
          }
        }
        var body = JSON.stringify(value, null, 2)
        var w = ctx.vfs.writeFile(path, body)
        if (w && typeof w.then === 'function') await w
        return true
      } catch (e2) {
        return false
      }
    }
    
    async function bareSummonLoadPersisted(session) {
      var ctx = session.ctx
      var paths = bareSummonPaths(ctx)
      var cfg = await bareSummonReadJson(ctx, paths.config)
      if (cfg && typeof cfg === 'object') {
        session.config = Object.assign({}, session.config, cfg)
      }
      var marks = await bareSummonReadJson(ctx, paths.bookmarks)
      if (Array.isArray(marks)) session.bookmarks = marks
      var cookies = await bareSummonReadJson(ctx, paths.cookies)
      if (Array.isArray(cookies)) session.jar.load(cookies)
      return session
    }
    
    async function bareSummonSavePersisted(session) {
      var ctx = session.ctx
      var paths = bareSummonPaths(ctx)
      await bareSummonWriteJson(ctx, paths.bookmarks, session.bookmarks || [])
      await bareSummonWriteJson(ctx, paths.cookies, session.jar.toJSON())
      await bareSummonWriteJson(ctx, paths.config, session.config || {})
    }