Skip to content

patarapolw/webview-server

Repository files navigation

Practical web server in Golang with clean-up function

So, I tried to write a web server in Golang to fit in with zserge/lorca. Focusing on maximize/fullscreen on all platforms as well.

See the original post.

Tested with cURL's

% PORT=3000 ./webview-server
% curl -i -X PUT --data 'hello' http://127.0.0.1:3000/api/file\?filename\=test.txt

This, by default, works with lokijs, by using a custom adaptor.

import Loki from 'lokijs'

class LokiRestAdaptor {
  loadDatabase (dbname: string, callback: (data: string | null | Error) => void) {
    fetch(`/api/file?filename=${encodeURIComponent(dbname)}`)
      .then((r) => r.text())
      .then((r) => callback(r))
      .catch((e) => callback(e))
  }

  saveDatabase (dbname: string, dbstring: string, callback: (e: Error | null) => void) {
    fetch(`/api/file?filename=${encodeURIComponent(dbname)}`, {
      method: 'PUT',
      body: dbstring
    })
      .then(() => callback(null))
      .catch((e) => callback(e))
  }

  deleteDatabase (dbname: string, callback: (data: Error | null) => void) {
    fetch(`/api/file?filename=${encodeURIComponent(dbname)}`, {
      method: 'DELETE'
    })
      .then(() => callback(null))
      .catch((e) => callback(e))
  }
}

// eslint-disable-next-line import/no-mutable-exports
export let loki: Loki

export async function initDatabase () {
  return new Promise((resolve) => {
    loki = new Loki('db.loki', {
      adapter: new LokiRestAdaptor(),
      autoload: true,
      autoloadCallback: () => {
        resolve()
      },
      autosave: true,
      autosaveInterval: 4000
    })
  })
}

window.onbeforeunload = (e: Event) => {
  if (loki) {
    if (loki.autosaveDirty()) {
      loki.saveDatabase()

      e.preventDefault()
      e.returnValue = false
    }
  }
}

And, in you Webpack dev server (e.g. webpack.config.js)

// @ts-check

/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs')
const path = require('path')

const morgan = require('morgan')

const BINARY_DIR = 'release'

module.exports = {
  devServer: {
    /**
     *
     * @param {import('express').Express} app
     * @param {import('http').Server} server
     * @param {*} compiler
     */
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    before (app, server, compiler) {
      app.use('/api', morgan('tiny'))

      app.get('/api/file', (req, res) => {
        /** @type {*} */
        const { filename } = req.query
        const p = path.resolve(__dirname, BINARY_DIR, filename)

        if (fs.existsSync(p)) {
          fs.createReadStream(p).pipe(res)
          return
        }

        res.sendStatus(404)
      })

      app.put('/api/file', (req, res) => {
        /** @type {*} */
        const { filename } = req.query
        const p = path.resolve(__dirname, BINARY_DIR, filename)

        req.pipe(fs.createWriteStream(p))
        res.sendStatus(201)
      })

      app.delete('/api/file', (req, res) => {
        /** @type {*} */
        const { filename } = req.query
        const p = path.resolve(__dirname, BINARY_DIR, filename)

        fs.unlinkSync(p)
        res.sendStatus(201)
      })
    }
  }
}

Web browser in use

Currently, this app doesn't bundle a web browser. Instead, it uses Chrome DevTools Protocol; therefore, either Chrome or Chromium must be installed.

See /deps.md.

Security concerns

I learnt this from pywebview. A major thing about this, is CSRF attack.

Customization

Please see /config/types.go. The easiest way is to create /config.yaml alongside the built webview-server*.

Building

You can also build for your platform, or multiple platforms at once -- take a peek inside robo.yml

Note that executables in macOS can also run in windowed mode (no console), by renaming the extension to *.app. No need to make a folder of *.app/.

I cannot upload *.app directly to GitHub Releases.

darwin binaries can used for macOS, although not built natively on macOS.