From 234f7bcac0c9f852da7dea229b2e140c4ce16fe2 Mon Sep 17 00:00:00 2001 From: coderaiser Date: Thu, 17 May 2018 16:08:02 +0300 Subject: [PATCH] feature(cloudcmd) add service worker --- .babelrc | 10 ++++-- .webpack/js.js | 28 +++++++++++++++ client/client.js | 17 +++++++++ client/sw.js | 89 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +++ server/cloudcmd.js | 18 ++++++++++ 6 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 client/sw.js diff --git a/.babelrc b/.babelrc index 66948953..5a844271 100644 --- a/.babelrc +++ b/.babelrc @@ -1,10 +1,16 @@ { "presets": [ - "env" + ["env", { + "exclude": [ + "transform-regenerator" + ] + }] ], "plugins": [ "transform-object-assign", - "transform-object-rest-spread" + "transform-object-rest-spread", + "fast-async", + "babel-plugin-macros", ] } diff --git a/.webpack/js.js b/.webpack/js.js index b5a02ea1..9f88ad1c 100644 --- a/.webpack/js.js +++ b/.webpack/js.js @@ -6,6 +6,12 @@ const { join, } = require('path'); +const { + EnvironmentPlugin, +} = require('webpack'); + +const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin'); + const dir = './client'; const dirModules = './client/modules'; const modules = './modules'; @@ -21,13 +27,33 @@ const devtool = isDev ? 'eval' : 'source-map'; const notEmpty = (a) => a; const clean = (array) => array.filter(notEmpty); +const babelDev = { + babelrc: false, + plugins: [ + 'babel-plugin-macros', + ] +}; + const rules = clean([ !isDev && { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', + }, + isDev && { + test: /sw.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + options: babelDev }]); +const plugins = [ + new EnvironmentPlugin(['NODE_ENV']), + new ServiceWorkerWebpackPlugin({ + entry: join(__dirname, '../client', 'sw.js'), + }), +]; + const splitChunks = { chunks: 'all', name: 'cloudcmd.common', @@ -40,6 +66,7 @@ module.exports = { }, entry: { cloudcmd: `${dir}/cloudcmd.js`, + //[sw]: `${dir}/sw.js`, [modules + '/edit']: `${dirModules}/edit.js`, [modules + '/edit-file']: `${dirModules}/edit-file.js`, [modules + '/edit-file-vim']: `${dirModules}/edit-file-vim.js`, @@ -70,6 +97,7 @@ module.exports = { module: { rules, }, + plugins, }; function externals(context, request, fn) { diff --git a/client/client.js b/client/client.js index 515100f5..3cadee93 100644 --- a/client/client.js +++ b/client/client.js @@ -12,6 +12,8 @@ const join = require('join-io/www/join'); const jonny = require('jonny/legacy'); const currify = require('currify/legacy'); +const runtime = require('serviceworker-webpack-plugin/lib/runtime'); + const bind = (f, ...a) => () => f(...a); const noop = () => {}; @@ -39,6 +41,8 @@ function CloudCmdProto(Util, DOM) { console.log(str); }; + serviceWorker(); + Emitify.call(this); const CloudCmd = this; @@ -607,5 +611,18 @@ function CloudCmdProto(Util, DOM) { }); }); }; + + function serviceWorker() { + if (!navigator.serviceWorker) + return; + + const isHTTPS = location.protocol === 'https:'; + const isLocalhost = location.hostname === 'localhost'; + + if (!isHTTPS && !isLocalhost) + return; + + runtime.register(); + } } diff --git a/client/sw.js b/client/sw.js new file mode 100644 index 00000000..c7cae60a --- /dev/null +++ b/client/sw.js @@ -0,0 +1,89 @@ +'use strict'; + +const preval = require('preval.macro'); +const tryToCatch = require('try-to-catch/legacy'); +const currify = require('currify/legacy'); + +const wait = currify((f, e) => e.waitUntil(f())); +const respondWith = currify((f, e) => e.respondWith(f(e))); +const getPathName = (url) => new URL(url).pathname; + +const date = preval`module.exports = Date()`; +const NAME = `cloudcmd: ${date}`; + +const isGet = (a) => a.method === 'GET'; +const isBasic = (a) => a.type === 'basic'; + +const createRequest = (a) => new Request(a, { + credentials: 'same-origin' +}); + +self.addEventListener('install', wait(onInstall)); +self.addEventListener('fetch', respondWith(onFetch)); +self.addEventListener('activate', wait(onActivate)); + +async function onActivate() { + console.info(`cloudcmd: sw: activate: ${NAME}`); + + await self.clients.claim(); + const keys = await caches.keys(); + const deleteCache = caches.delete.bind(caches); + + await Promise.all(keys.map(deleteCache)); +} + +async function onInstall() { + console.info(`cloudcmd: sw: install: ${NAME}`); + + await self.skipWaiting(); + + const cache = await caches.open(NAME); + + const urls = [ + '/favicon.ico', + ]; + + const requests = urls.map(createRequest); + + return cache.addAll(requests); +} + +async function onFetch(event) { + const {request} = event; + const url = request.url; + const pathname = getPathName(url); + + const cache = await caches.open(NAME); + const response = await cache.match(request); + + if (response) + return response; + + const [e, resp] = await tryToCatch(fetch, request.clone()); + + if (e) + return console.error(e); + + if (!isGet(request) || !resp.ok || !isBasic(resp)) + return resp; + + if (/^\/$/.test(pathname)) + return resp; + + if (/^\/api/.test(pathname)) + return resp; + + if (/^socket.io/.test(pathname)) + return resp; + + await addToCache(request, resp.clone()); + + return resp; +} + +async function addToCache(request, response) { + const cache = await caches.open(NAME); + + cache.put(request, response); +} + diff --git a/package.json b/package.json index 919a4b34..b03e9e66 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,7 @@ "@cloudcmd/clipboard": "^1.0.0", "babel-cli": "^6.18.0", "babel-loader": "^7.0.0", + "babel-plugin-macros": "^2.2.1", "babel-plugin-transform-object-assign": "^6.22.0", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.6.0", @@ -171,6 +172,7 @@ "eslint": "^4.0.0", "eslint-plugin-node": "^6.0.0", "extract-text-webpack-plugin": "^4.0.0-alpha.0", + "fast-async": "^6.3.7", "file-loader": "^1.1.4", "gritty": "^2.2.0", "gunzip-maybe": "^1.3.1", @@ -188,10 +190,12 @@ "nyc": "^11.0.2", "philip": "^2.0.0", "place": "^1.1.4", + "preval.macro": "^1.0.2", "readjson": "^1.1.3", "redrun": "^6.0.0", "request": "^2.76.0", "rimraf": "^2.5.4", + "serviceworker-webpack-plugin": "1.0.0-alpha02", "shortdate": "^1.0.1", "sinon": "^5.0.1", "sinon-called-with-diff": "^2.0.0", diff --git a/server/cloudcmd.js b/server/cloudcmd.js index ad4869d4..ba20da54 100644 --- a/server/cloudcmd.js +++ b/server/cloudcmd.js @@ -39,6 +39,7 @@ const defaultHtml = fs.readFileSync(getIndexPath(isDev), 'utf8'); const auth = currify(_auth); const setUrl = currify(_setUrl); +const setSW = currify(_setSW); const root = () => config('root'); @@ -208,6 +209,7 @@ function cloudcmd(prefix, plugins, modules) { }), setUrl(prefix), + setSW(prefix), logout, authentication(), config.middle, @@ -272,6 +274,22 @@ function _setUrl(pref, req, res, next) { next(); } +function _setSW(pref, req, res, next) { + const prefix = getPrefix(pref); + const is = !req.url.indexOf(prefix); + + if (!is) + return next(); + + const url = replacePrefix(req.url, prefix); + const isSW = /^\/sw\.js(\.map)?$/.test(url); + + if (isSW) + req.url = replaceDist(`/dist${url}`); + + next(); +} + function checkPlugins(plugins) { if (typeof plugins === 'undefined') return;