From a74fbb34c06bae55a216e0f8d1399d6c39832a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20D=C3=ADaz?= Date: Sun, 28 Jun 2020 04:33:08 +0200 Subject: [PATCH] [IMP] web_pwa_oca: Refactor to use OdooClass --- web_pwa_oca/README.rst | 28 +++- web_pwa_oca/__manifest__.py | 6 +- web_pwa_oca/controllers/__init__.py | 2 + web_pwa_oca/controllers/main.py | 130 ++++++++++----- web_pwa_oca/readme/CONTRIBUTORS.rst | 4 + web_pwa_oca/readme/ROADMAP.rst | 23 ++- web_pwa_oca/static/description/index.html | 55 ++++++- web_pwa_oca/static/src/js/pwa_install.js | 71 ++++----- web_pwa_oca/static/src/js/pwa_manager.js | 35 ++++ web_pwa_oca/static/src/js/webclient.js | 30 ++++ .../src/js/worker/base/cache_manager.js | 45 ++++++ .../static/src/js/worker/base/tools.js | 17 ++ .../static/src/js/worker/libs/class.js | 150 ++++++++++++++++++ web_pwa_oca/static/src/js/worker/pwa.js | 62 ++++++++ web_pwa_oca/templates/assets.xml | 23 +++ web_pwa_oca/templates/service_worker.xml | 40 +++++ web_pwa_oca/views/webclient_templates.xml | 105 ------------ 17 files changed, 630 insertions(+), 196 deletions(-) create mode 100644 web_pwa_oca/static/src/js/pwa_manager.js create mode 100644 web_pwa_oca/static/src/js/webclient.js create mode 100644 web_pwa_oca/static/src/js/worker/base/cache_manager.js create mode 100644 web_pwa_oca/static/src/js/worker/base/tools.js create mode 100644 web_pwa_oca/static/src/js/worker/libs/class.js create mode 100644 web_pwa_oca/static/src/js/worker/pwa.js create mode 100644 web_pwa_oca/templates/assets.xml create mode 100644 web_pwa_oca/templates/service_worker.xml delete mode 100644 web_pwa_oca/views/webclient_templates.xml diff --git a/web_pwa_oca/README.rst b/web_pwa_oca/README.rst index bad281d58..22c4ae65d 100644 --- a/web_pwa_oca/README.rst +++ b/web_pwa_oca/README.rst @@ -78,10 +78,31 @@ Known issues / Roadmap ====================== * Evaluate to extend ``FILES_TO_CACHE`` -* Evaluate to use a normal JS file for service worker and download data from a normal JSON controller * Integrate `Notification API `_ * Integrate `Web Share API `_ * Create ``portal_pwa`` module, intended to be used by front-end users (customers, suppliers...) +* Current ``John Resig's inheritance`` doesn't support ``async`` functions. So we need use the following workaround: + - Natural 'async/await' example (This breaks "_super" call) + .. code-block:: javascript + + const MyClass = OdooClass.extend({ + myFunc: async function() { + const mydata = await ...do await stuff... + return mydata; + } + }); + + - Same code with the workaround: + .. code-block:: javascript + + const MyClass = OdooClass.extend({ + myFunc: function() { + return new Promise(async (resolve, reject) => { + const mydata = await ...do await stuff... + resolve(mydata); + }); + } + }); Bug Tracker =========== @@ -100,6 +121,7 @@ Authors ~~~~~~~ * TAKOBI +* Tecnativa Contributors ~~~~~~~~~~~~ @@ -108,6 +130,10 @@ Contributors * Lorenzo Battistini +* `Tecnativa `_: + + * Alexandre D. Díaz + Maintainers ~~~~~~~~~~~ diff --git a/web_pwa_oca/__manifest__.py b/web_pwa_oca/__manifest__.py index d23a0c373..6c83ebf59 100644 --- a/web_pwa_oca/__manifest__.py +++ b/web_pwa_oca/__manifest__.py @@ -1,4 +1,5 @@ # Copyright 2020 Lorenzo Battistini @ TAKOBI +# Copyright 2020 Tecnativa - Alexandre D. Díaz # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). { @@ -8,7 +9,7 @@ "development_status": "Beta", "category": "Website", "website": "https://github.com/OCA/web", - "author": "TAKOBI, Odoo Community Association (OCA)", + "author": "TAKOBI, Tecnativa, Odoo Community Association (OCA)", "maintainers": ["eLBati"], "license": "LGPL-3", "application": True, @@ -18,7 +19,8 @@ 'mail', ], "data": [ - "views/webclient_templates.xml", + "templates/assets.xml", + "templates/service_worker.xml", ], 'qweb': [ 'static/src/xml/pwa_install.xml', diff --git a/web_pwa_oca/controllers/__init__.py b/web_pwa_oca/controllers/__init__.py index 12a7e529b..1f371126f 100644 --- a/web_pwa_oca/controllers/__init__.py +++ b/web_pwa_oca/controllers/__init__.py @@ -1 +1,3 @@ +# Copyright 2020 Lorenzo Battistini @ TAKOBI +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). from . import main diff --git a/web_pwa_oca/controllers/main.py b/web_pwa_oca/controllers/main.py index 3254b648b..7d89de364 100644 --- a/web_pwa_oca/controllers/main.py +++ b/web_pwa_oca/controllers/main.py @@ -1,11 +1,26 @@ +# Copyright 2020 Lorenzo Battistini @ TAKOBI +# Copyright 2020 Tecnativa - Alexandre D. Díaz +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +import json from odoo.http import request, Controller, route class PWA(Controller): - def get_asset_urls(self, asset_xml_id): - qweb = request.env['ir.qweb'].sudo() - assets = qweb._get_asset_nodes(asset_xml_id, {}, True, True) + def _get_pwa_scripts(self): + """ Scripts to be imported in the service worker (Order is important) """ + return [ + '/web/static/lib/underscore/underscore.js', + '/web_pwa_oca/static/src/js/worker/libs/class.js', + '/web_pwa_oca/static/src/js/worker/base/tools.js', + '/web_pwa_oca/static/src/js/worker/base/cache_manager.js', + '/web_pwa_oca/static/src/js/worker/pwa.js', + ] + + def _get_asset_urls(self, asset_xml_id): + """Get all url's that have 'asset_xml_id'""" + qweb_sudo = request.env['ir.qweb'].sudo() + assets = qweb_sudo._get_asset_nodes(asset_xml_id, {}, True, True) urls = [] for asset in assets: if asset[0] == 'link': @@ -14,62 +29,93 @@ class PWA(Controller): urls.append(asset[1]['src']) return urls - @route('/service-worker.js', type='http', auth="public") - def service_worker(self): - qweb = request.env['ir.qweb'].sudo() + def _get_pwa_params(self): + """Get javascript PWA class initialzation params""" urls = [] - urls.extend(self.get_asset_urls("web.assets_common")) - urls.extend(self.get_asset_urls("web.assets_backend")) + urls.extend(self._get_asset_urls("web.assets_common")) + urls.extend(self._get_asset_urls("web.assets_backend")) version_list = [] for url in urls: version_list.append(url.split('/')[3]) cache_version = '-'.join(version_list) - mimetype = 'text/javascript;charset=utf-8' - content = qweb.render('web_pwa_oca.service_worker', { - 'pwa_cache_name': cache_version, - 'pwa_files_to_cache': urls, - }) - return request.make_response(content, [('Content-Type', mimetype)]) + return [ + cache_version, + urls + ] - @route('/web_pwa_oca/manifest.json', type='http', auth="public") - def manifest(self): - qweb = request.env['ir.qweb'].sudo() - config_param = request.env['ir.config_parameter'].sudo() - pwa_name = config_param.get_param("pwa.manifest.name", "Odoo PWA") - pwa_short_name = config_param.get_param("pwa.manifest.short_name", "Odoo PWA") - icon128x128 = config_param.get_param( + @route('/service-worker.js', type='http', auth="public") + def render_service_worker(self): + """Route to register the service worker in the 'main' scope ('/')""" + return request.render('web_pwa_oca.service_worker', { + 'pwa_scripts': self._get_pwa_scripts(), + 'pwa_params': self._get_pwa_params(), + }, headers=[('Content-Type', 'text/javascript;charset=utf-8')]) + + def _get_pwa_manifest(self): + """Webapp manifest""" + config_param_sudo = request.env['ir.config_parameter'].sudo() + pwa_name = config_param_sudo.get_param("pwa.manifest.name", "Odoo PWA") + pwa_short_name = config_param_sudo.get_param( + "pwa.manifest.short_name", "Odoo PWA") + icon128x128 = config_param_sudo.get_param( "pwa.manifest.icon128x128", "/web_pwa_oca/static/img/icons/icon-128x128.png") - icon144x144 = config_param.get_param( + icon144x144 = config_param_sudo.get_param( "pwa.manifest.icon144x144", "/web_pwa_oca/static/img/icons/icon-144x144.png") - icon152x152 = config_param.get_param( + icon152x152 = config_param_sudo.get_param( "pwa.manifest.icon152x152", "/web_pwa_oca/static/img/icons/icon-152x152.png") - icon192x192 = config_param.get_param( + icon192x192 = config_param_sudo.get_param( "pwa.manifest.icon192x192", "/web_pwa_oca/static/img/icons/icon-192x192.png") - icon256x256 = config_param.get_param( + icon256x256 = config_param_sudo.get_param( "pwa.manifest.icon256x256", "/web_pwa_oca/static/img/icons/icon-256x256.png") - icon512x512 = config_param.get_param( + icon512x512 = config_param_sudo.get_param( "pwa.manifest.icon512x512", "/web_pwa_oca/static/img/icons/icon-512x512.png") - background_color = config_param.get_param( + background_color = config_param_sudo.get_param( "pwa.manifest.background_color", "#2E69B5") - theme_color = config_param.get_param( + theme_color = config_param_sudo.get_param( "pwa.manifest.theme_color", "#2E69B5") - mimetype = 'application/json;charset=utf-8' - content = qweb.render('web_pwa_oca.manifest', { - 'pwa_name': pwa_name, - 'pwa_short_name': pwa_short_name, - 'icon128x128': icon128x128, - 'icon144x144': icon144x144, - 'icon152x152': icon152x152, - 'icon192x192': icon192x192, - 'icon256x256': icon256x256, - 'icon512x512': icon512x512, - 'background_color': background_color, - 'theme_color': theme_color, - }) - return request.make_response(content, [('Content-Type', mimetype)]) + return { + "name": pwa_name, + "short_name": pwa_short_name, + "icons": [{ + "src": icon128x128, + "sizes": "128x128", + "type": "image/png" + }, { + "src": icon144x144, + "sizes": "144x144", + "type": "image/png" + }, { + "src": icon152x152, + "sizes": "152x152", + "type": "image/png" + }, { + "src": icon192x192, + "sizes": "192x192", + "type": "image/png" + }, { + "src": icon256x256, + "sizes": "256x256", + "type": "image/png" + }, { + "src": icon512x512, + "sizes": "512x512", + "type": "image/png" + }], + "start_url": "/web", + "display": "standalone", + "background_color": background_color, + "theme_color": theme_color + } + + @route('/web_pwa_oca/manifest.webmanifest', type='http', auth="public") + def pwa_manifest(self): + """Returns the manifest used to install the page as app""" + return request.make_response( + json.dumps(self._get_pwa_manifest()), + headers=[('Content-Type', 'text/javascript;charset=utf-8')]) diff --git a/web_pwa_oca/readme/CONTRIBUTORS.rst b/web_pwa_oca/readme/CONTRIBUTORS.rst index 2b476d752..9df4fda75 100644 --- a/web_pwa_oca/readme/CONTRIBUTORS.rst +++ b/web_pwa_oca/readme/CONTRIBUTORS.rst @@ -1,3 +1,7 @@ * `TAKOBI `_: * Lorenzo Battistini + +* `Tecnativa `_: + + * Alexandre D. Díaz diff --git a/web_pwa_oca/readme/ROADMAP.rst b/web_pwa_oca/readme/ROADMAP.rst index 9e763ae62..ff63d559b 100644 --- a/web_pwa_oca/readme/ROADMAP.rst +++ b/web_pwa_oca/readme/ROADMAP.rst @@ -1,5 +1,26 @@ * Evaluate to extend ``FILES_TO_CACHE`` -* Evaluate to use a normal JS file for service worker and download data from a normal JSON controller * Integrate `Notification API `_ * Integrate `Web Share API `_ * Create ``portal_pwa`` module, intended to be used by front-end users (customers, suppliers...) +* Current ``John Resig's inheritance`` implementation doesn't support ``async`` functions because ``this._super`` can't be called inside a promise. So we need use the following workaround: + - Natural 'async/await' example (This breaks "_super" call) + .. code-block:: javascript + + var MyClass = OdooClass.extend({ + myFunc: async function() { + const mydata = await ...do await stuff... + return mydata; + } + }); + + - Same code with the workaround: + .. code-block:: javascript + + var MyClass = OdooClass.extend({ + myFunc: function() { + return new Promise(async (resolve, reject) => { + const mydata = await ...do await stuff... + resolve(mydata); + }); + } + }); diff --git a/web_pwa_oca/static/description/index.html b/web_pwa_oca/static/description/index.html index 7659b00f8..2cdb54d87 100644 --- a/web_pwa_oca/static/description/index.html +++ b/web_pwa_oca/static/description/index.html @@ -420,12 +420,50 @@ And like all other installed apps, it’s a top level app in the task switcher.<

Known issues / Roadmap

-
    -
  • Evaluate to extend FILES_TO_CACHE
  • -
  • Evaluate to use a normal JS file for service worker and download data from a normal JSON controller
  • -
  • Integrate Notification API
  • -
  • Integrate Web Share API
  • -
  • Create portal_pwa module, intended to be used by front-end users (customers, suppliers…)
  • +
      +
    • Evaluate to extend FILES_TO_CACHE

      +
    • +
    • Integrate Notification API

      +
    • +
    • Integrate Web Share API

      +
    • +
    • Create portal_pwa module, intended to be used by front-end users (customers, suppliers…)

      +
    • +
    • +
      Current John Resig's inheritance doesn’t support async functions. So we need use the following workaround:
      +
        +
      • +
        Natural ‘async/await’ example (This breaks “_super” call)
        +
        +const MyClass = OdooClass.extend({
        +    myFunc: async function() {
        +        const mydata = await ...do await stuff...
        +        return mydata;
        +    }
        +});
        +
        +
        +
        +
      • +
      • +
        Same code with the workaround:
        +
        +const MyClass = OdooClass.extend({
        +    myFunc: function() {
        +        return new Promise(async (resolve, reject) => {
        +            const mydata = await ...do await stuff...
        +            resolve(mydata);
        +        });
        +    }
        +});
        +
        +
        +
        +
      • +
      +
      +
      +
@@ -442,6 +480,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

Authors

  • TAKOBI
  • +
  • Tecnativa
@@ -451,6 +490,10 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
  • Lorenzo Battistini
  • +
  • Tecnativa:
      +
    • Alexandre D. Díaz
    • +
    +
  • diff --git a/web_pwa_oca/static/src/js/pwa_install.js b/web_pwa_oca/static/src/js/pwa_install.js index 3a8b741fe..b06d4a21d 100644 --- a/web_pwa_oca/static/src/js/pwa_install.js +++ b/web_pwa_oca/static/src/js/pwa_install.js @@ -1,45 +1,38 @@ -odoo.define('web_pwa_oca.systray.install', function (require) { -"use strict"; +/* Copyright 2020 Lorenzo Battistini @ TAKOBI + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ -var core = require('web.core'); -var session = require('web.session'); -var UserMenu = require('web.UserMenu'); +odoo.define('web_pwa_oca.systray.install', function(require) { + "use strict"; -if ('serviceWorker' in navigator) { - window.addEventListener('load', function () { - navigator.serviceWorker.register('/service-worker.js') - .then(function (reg) { - console.log('Service worker registered.', reg); - }); - }); -} + var UserMenu = require('web.UserMenu'); -var deferredInstallPrompt = null; + var deferredInstallPrompt = null; -UserMenu.include({ - start: function () { - window.addEventListener('beforeinstallprompt', this.saveBeforeInstallPromptEvent); - return this._super.apply(this, arguments); - }, - saveBeforeInstallPromptEvent: function(evt) { - deferredInstallPrompt = evt; - this.$.find('#pwa_install_button')[0].removeAttribute('hidden'); - }, - _onMenuInstallpwa: function () { - deferredInstallPrompt.prompt(); - // Hide the install button, it can't be called twice. - this.el.setAttribute('hidden', true); - // Log user response to prompt. - deferredInstallPrompt.userChoice - .then(function (choice) { - if (choice.outcome === 'accepted') { - console.log('User accepted the A2HS prompt', choice); - } else { - console.log('User dismissed the A2HS prompt', choice); - } - deferredInstallPrompt = null; - }); - }, -}); + + UserMenu.include({ + start: function() { + window.addEventListener('beforeinstallprompt', this.saveBeforeInstallPromptEvent); + return this._super.apply(this, arguments); + }, + saveBeforeInstallPromptEvent: function(evt) { + deferredInstallPrompt = evt; + this.$.find('#pwa_install_button')[0].removeAttribute('hidden'); + }, + _onMenuInstallpwa: function() { + deferredInstallPrompt.prompt(); + // Hide the install button, it can't be called twice. + this.el.setAttribute('hidden', true); + // Log user response to prompt. + deferredInstallPrompt.userChoice + .then(function(choice) { + if (choice.outcome === 'accepted') { + console.log('User accepted the A2HS prompt', choice); + } else { + console.log('User dismissed the A2HS prompt', choice); + } + deferredInstallPrompt = null; + }); + }, + }); }); diff --git a/web_pwa_oca/static/src/js/pwa_manager.js b/web_pwa_oca/static/src/js/pwa_manager.js new file mode 100644 index 000000000..9bdc27b9d --- /dev/null +++ b/web_pwa_oca/static/src/js/pwa_manager.js @@ -0,0 +1,35 @@ +/* Copyright 2020 Tecnativa - Alexandre D. Díaz + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + + odoo.define("web_pwa_oca.PWAManager", function(require) { + "use strict"; + + var Class = require("web.Class"); + var mixins = require("web.mixins"); + + + var PWAManager = Class.extend(mixins.ParentedMixin, { + init: function() { + if (!('serviceWorker' in navigator)) { + throw new Error( + "This browser is not compatible with service workers"); + } + this._service_worker = navigator.serviceWorker; + }, + + /** + * @param {String} sw_script + * @param {Function} success_callback + * @returns {Promise} + */ + registerServiceWorker: function(sw_script, success_callback) { + return this._service_worker.register(sw_script) + .then(success_callback) + .catch(function(error) { + console.log('[ServiceWorker] Registration failed: ', error); + }); + }, + }); + + return PWAManager; +}); diff --git a/web_pwa_oca/static/src/js/webclient.js b/web_pwa_oca/static/src/js/webclient.js new file mode 100644 index 000000000..96ddc29cf --- /dev/null +++ b/web_pwa_oca/static/src/js/webclient.js @@ -0,0 +1,30 @@ +/* Copyright 2020 Tecnativa - Alexandre D. Díaz + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + + odoo.define("web_pwa_oca.webclient", function(require) { + "use strict"; + + var WebClient = require("web.WebClient"); + var PWAManager = require("web_pwa_oca.PWAManager"); + + WebClient.include({ + /** + * @override + */ + show_application: function() { + this.pwa_manager = new PWAManager(); + this.pwa_manager.setParent(this); + this.pwa_manager.registerServiceWorker('/service-worker.js', this._onRegisterServiceWorker); + return this._super.apply(this, arguments); + }, + + /** + * Need register some extra API? override this! + * + * @param {ServiceWorkerRegistration} registration + */ + _onRegisterServiceWorker: function(registration) { + console.log('[ServiceWorker] Registered:', registration); + }, + }); +}); diff --git a/web_pwa_oca/static/src/js/worker/base/cache_manager.js b/web_pwa_oca/static/src/js/worker/base/cache_manager.js new file mode 100644 index 000000000..e8da25808 --- /dev/null +++ b/web_pwa_oca/static/src/js/worker/base/cache_manager.js @@ -0,0 +1,45 @@ +/* Copyright 2020 Tecnativa - Alexandre D. Díaz + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ +"use strict"; + +const CacheManager = OdooClass.extend({ + init: function() { + this._cache = caches; + this._caches = {}; + }, + + /** + * @returns {Promise[CacheStorage]} + */ + initCache: function(cache_name) { + if (_.has(this._caches, cache_name)) { + return Promise.resolve(this._caches[cache_name]); + } + return new Promise(async resolve => { + this._caches[cache_name] = await this._cache.open(cache_name); + resolve(this._caches[cache_name]); + }); + }, + + /** + * @returns {CacheStorage} + */ + get: function(cache_name) { + return this._caches[cache_name]; + }, + + /** + * @returns {Promise} + */ + clean: function() { + return this._cache.keys().then(keyList => { + return Promise.all(keyList.map(key => { + if (!_.has(this._caches, key)) { + console.log('[ServiceWorker] Removing old cache', key); + return this._cache.delete(key); + } + })); + }); + }, + +}); diff --git a/web_pwa_oca/static/src/js/worker/base/tools.js b/web_pwa_oca/static/src/js/worker/base/tools.js new file mode 100644 index 000000000..ccd0adcf8 --- /dev/null +++ b/web_pwa_oca/static/src/js/worker/base/tools.js @@ -0,0 +1,17 @@ +/* Copyright 2020 Tecnativa - Alexandre D. Díaz + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + "use strict"; + + +/** + * Execute various promises in sequential order and return a promise with all results + * Code from: https://decembersoft.com/posts/promises-in-serial-with-array-reduce/ + * + * @param {Array[Promise]} tasks + * @returns {Promise[Array]} + */ +function execSequentialPromises(tasks) { + return tasks.reduce((promiseChain, currentTask) => { + return promiseChain.then(chainResults => currentTask.then(currentResult => [...chainResults, currentResult])); + }, Promise.resolve([])); +} diff --git a/web_pwa_oca/static/src/js/worker/libs/class.js b/web_pwa_oca/static/src/js/worker/libs/class.js new file mode 100644 index 000000000..c44c8e715 --- /dev/null +++ b/web_pwa_oca/static/src/js/worker/libs/class.js @@ -0,0 +1,150 @@ +/** + * Improved John Resig's inheritance, based on: + * + * Simple JavaScript Inheritance + * By John Resig http://ejohn.org/ + * MIT Licensed. + * + * Adds "include()" + * + * Defines The Class object. That object can be used to define and inherit classes using + * the extend() method. + * + * Example:: + * + * var Person = Class.extend({ + * init: function(isDancing){ + * this.dancing = isDancing; + * }, + * dance: function(){ + * return this.dancing; + * } + * }); + * + * The init() method act as a constructor. This class can be instanced this way:: + * + * var person = new Person(true); + * person.dance(); + * + * The Person class can also be extended again: + * + * var Ninja = Person.extend({ + * init: function(){ + * this._super( false ); + * }, + * dance: function(){ + * // Call the inherited version of dance() + * return this._super(); + * }, + * swingSword: function(){ + * return true; + * } + * }); + * + * When extending a class, each re-defined method can use this._super() to call the previous + * implementation of that method. + * + * @class Class + */ +function OdooClass(){} + +var initializing = false; +var fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/; + +/** + * Subclass an existing class + * + * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class + */ +OdooClass.extend = function() { + var _super = this.prototype; + // Support mixins arguments + var args = _.toArray(arguments); + args.unshift({}); + var prop = _.extend.apply(_,args); + + // Instantiate a web class (but only create the instance, + // don't run the init constructor) + initializing = true; + var This = this; + var prototype = new This(); + initializing = false; + + // Copy the properties over onto the new prototype + _.each(prop, function(val, name) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + fnTest.test(prop[name]) ? + (function(name, fn) { + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same + // method but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so + // we remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; + }); + + // The dummy class constructor + function Class() { + if(this.constructor !== OdooClass){ + throw new Error("You can only instanciate objects with the 'new' operator"); + } + // All construction is actually done in the init method + this._super = null; + if (!initializing && this.init) { + var ret = this.init.apply(this, arguments); + if (ret) { return ret; } + } + return this; + } + Class.include = function (properties) { + _.each(properties, function(val, name) { + if (typeof properties[name] !== 'function' + || !fnTest.test(properties[name])) { + prototype[name] = properties[name]; + } else if (typeof prototype[name] === 'function' + && prototype.hasOwnProperty(name)) { + prototype[name] = (function (name, fn, previous) { + return function () { + var tmp = this._super; + this._super = previous; + var ret = fn.apply(this, arguments); + this._super = tmp; + return ret; + }; + })(name, properties[name], prototype[name]); + } else if (typeof _super[name] === 'function') { + prototype[name] = (function (name, fn) { + return function () { + var tmp = this._super; + this._super = _super[name]; + var ret = fn.apply(this, arguments); + this._super = tmp; + return ret; + }; + })(name, properties[name]); + } + }); + }; + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.constructor = Class; + + // And make this class extendable + Class.extend = this.extend; + + return Class; +}; diff --git a/web_pwa_oca/static/src/js/worker/pwa.js b/web_pwa_oca/static/src/js/worker/pwa.js new file mode 100644 index 000000000..12b287567 --- /dev/null +++ b/web_pwa_oca/static/src/js/worker/pwa.js @@ -0,0 +1,62 @@ +/* Copyright 2020 Tecnativa - Alexandre D. Díaz + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ +"use strict"; + +var PWA = OdooClass.extend({ + + /** + * @param {String} cache_name + * @param {Array[String]} prefetched_urls + */ + init: function (cache_name, prefetched_urls) { + this._cache_name = cache_name; + this._prefetched_urls = prefetched_urls; + this._cache = new CacheManager(); + this._cacheLoadPromise = this._cache.initCache(this._cache_name); + }, + + /** + * @returns {Promise} + */ + installWorker: function () { + return this._prefetchData(); + }, + + /** + * @returns {Promise} + */ + activateWorker: function () { + return new Promise(async resolve => { + await this._cacheLoadPromise; + await this._cache.clean(); + return resolve(); + }); + }, + + /** + * @returns {Promise[Response]} + */ + processRequest: function (request) { + if (request.method === 'GET' && (request.cache !== 'only-if-cached' || request.mode === 'same-origin')) { + // Strategy: Cache First + return this._cache.get(this._cache_name).match(request).then(function(response){ + return response || fetch(request); + }); + } + // Default browser behaviour + return fetch(request); + }, + + /** + * @returns {Promise} + */ + _prefetchData: function() { + // Prefetch URL's + return new Promise(resolve => { + this._cacheLoadPromise.then(async () => { + return resolve(await this._cache.get(this._cache_name).addAll(this._prefetched_urls)); + }); + }); + }, + +}); diff --git a/web_pwa_oca/templates/assets.xml b/web_pwa_oca/templates/assets.xml new file mode 100644 index 000000000..cbf96a8fd --- /dev/null +++ b/web_pwa_oca/templates/assets.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/web_pwa_oca/templates/service_worker.xml b/web_pwa_oca/templates/service_worker.xml new file mode 100644 index 000000000..a1113ba8d --- /dev/null +++ b/web_pwa_oca/templates/service_worker.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/web_pwa_oca/views/webclient_templates.xml b/web_pwa_oca/views/webclient_templates.xml deleted file mode 100644 index 14dd5a7a1..000000000 --- a/web_pwa_oca/views/webclient_templates.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - -