How to create Web Server from Browser

In my GIT Web Terminal I use BrowserFS to write files (it’s required by isomorphic-git that I use to create git interface in browsers).
BrowserFS is implementation of node fs module but for browsers, it use few types of storage, I use indexedDB same as example for isomorphic-git.

I thought that i would be cool to edit the project itself and be able to view the files when I edit them in the browser (before I commit), like they where serve from web server. And it turn out it’s possible with Service Worker since you can access IndexedDB from that worker and you can create HTTP response from string or from arrayBuffer (BrowserFS is returning arrayBuffer from its readFile function).

Here is the code for service worker that show pages and directory listing:

self.addEventListener('install', function(evt) {
    self.skipWaiting();
    self.importScripts('https://cdn.jsdelivr.net/npm/browserfs');
    BrowserFS.configure({ fs: 'IndexedDB', options: {} }, function (err) {
        if (err) {
            console.log(err);
        } else {
            self.fs = BrowserFS.BFSRequire('fs');
            self.path = BrowserFS.BFSRequire('path');
        }
    });
});

self.addEventListener('fetch', function (event) {
    event.respondWith(new Promise(function(resolve, reject) {
        function sendFile(path) {
            fs.readFile(path, function(err, buffer) {
                if (err) {
                    err.fn = 'readFile(' + path + ')';
                    return reject(err);
                }
                resolve(new Response(buffer));
            });
        }
        var url = event.request.url;
        var m = url.match(/__browserfs__(.*)/);
        function redirect_dir() {
            return resolve(Response.redirect(url + '/', 301));
        }
        if (m && self.fs) {
            var path = m[1];
            if (path === '') {
                return redirect_dir();
            }
            console.log('serving ' + path + ' from browserfs');
            fs.stat(path, function(err, stat) {
                if (err) {
                    return resolve(textResponse(error404(path)));
                }
                if (stat.isFile()) {
                    sendFile(path);
                } else if (stat.isDirectory()) {
                    if (path.substr(-1, 1) !== '/') {
                        return redirect_dir();
                    }
                    fs.readdir(path, function(err, list) {
                        if (err) {
                            err.fn = 'readdir(' + path + ')';
                            return reject(err);
                        }
                        var len = list.length;
                        if (list.includes('index.html')) {
                            sendFile(path + '/index.html');
                        } else {
                            var output = [
                                '',
                                '',
                                '',
                                '<h1>BrowserFS</h1>',
                                '<ul>'
                            ];
                            if (path.match(/^\/(.*\/)/)) {
                                output.push('<li><a href="..">..</a></li>');
                            }
                            (function loop() {
                                var file = list.shift();
                                if (!file) {
                                    output = output.concat(['</ul>', '', '']);
                                    return resolve(textResponse(output.join('\n')));
                                }
                                fs.stat(path + '/' + file, function(err, stat) {
                                    if (err) {
                                        err.fn = 'stat(' + path + '/' + file + ')';
                                        return reject(err);
                                    }
                                    var name = file + (stat.isDirectory() ? '/' : '');
                                    output.push('<li><a href="' + name + '">' + name + '</a></li>');
                                    loop();
                                });
                            })();
                        }
                    });
                }
            });
        } else {
            fetch(event.request).then(resolve).catch(reject);
        }
    }));
});
function textResponse(string) {
    var blob = new Blob([string], {
        type: 'text/html'
    });
    return new Response(blob);
}

function error404(path) {
    var output = [
        '',
        '',
        '',
        '<h1>404 File Not Found</h1>',
        `File ${path} not found in browserfs`,
        '',
        ''
    ];
    return output.join('\n');
}

Service worker is using __browserfs__ marker to distinguish normal url from urls that are from BrowserFS/IndexedDB. Everything after it it’s serve from BrowserFS. So if you write file foo you can access it using __browserfs__/foo. Here is the code sample that will create a file using browserFS:

fs.writeFile('/foo', 'hello world', function(err) {
   if (err) {
     console.log('Error write file');
   }
});

You can see how this work in my GIT Web Terminal just clone any repo (best is the one that have directories) and view files with url https://jcubic.github.io/git/__browserfs__/repo/path/to/file.

, ,

  1. #1 by wmhilton on May 26, 2018 - 16:37

    Yasss! This is so awesome that you did this too! I am doing this in my NDE and git-apps projects, and even have some extra features like setting the content type. You might love to reuse the `render-index` function in https://github.com/wmhilton/wills-wonderful-service-worker/tree/master/src which is adopted from zeit’s `serve` CLI tool.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: