From 4c23a902c19e62289e26ec22c473cca0fba0aaa3 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Mon, 31 Jan 2022 15:28:12 +0100 Subject: [PATCH] [HTML5] Implement JavaScript PWA update callbacks. Allows detecting when a new version of the progressive web app service worker is waiting (i.e. an update is pending), along a function to force the update and reload all clients. --- doc/classes/JavaScript.xml | 22 ++++++++ platform/javascript/api/api.cpp | 9 +++ .../javascript/api/javascript_singleton.h | 2 + platform/javascript/godot_js.h | 2 + platform/javascript/javascript_singleton.cpp | 8 +++ .../javascript/js/libs/library_godot_os.js | 55 +++++++++++++++++++ platform/javascript/os_javascript.cpp | 16 ++++++ platform/javascript/os_javascript.h | 4 ++ 8 files changed, 118 insertions(+) diff --git a/doc/classes/JavaScript.xml b/doc/classes/JavaScript.xml index bc3f8145900..ec89b5b409d 100644 --- a/doc/classes/JavaScript.xml +++ b/doc/classes/JavaScript.xml @@ -54,7 +54,29 @@ Returns an interface to a JavaScript object that can be used by scripts. The [code]interface[/code] must be a valid property of the JavaScript [code]window[/code]. The callback must accept a single [Array] argument, which will contain the JavaScript [code]arguments[/code]. See [JavaScriptObject] for usage. + + + + Returns [code]true[/code] if a new version of the progressive web app is waiting to be activated. + [b]Note:[/b] Only relevant when exported as a Progressive Web App. + + + + + + Performs the live update of the progressive web app. Forcing the new version to be installed and the page to be reloaded. + [b]Note:[/b] Your application will be [b]reloaded in all browser tabs[/b]. + [b]Note:[/b] Only relevant when exported as a Progressive Web App and [method pwa_needs_update] returns [code]true[/code]. + + + + + + Emitted when an update for this progressive web app has been detected but is waiting to be activated because a previous version is active. See [method pwa_update] to force the update to take place immediately. + + + diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 4297f0d3860..aa05f5898a2 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -71,6 +71,9 @@ void JavaScript::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "create_object", &JavaScript::_create_object_bind, mi); } ClassDB::bind_method(D_METHOD("download_buffer", "buffer", "name", "mime"), &JavaScript::download_buffer, DEFVAL("application/octet-stream")); + ClassDB::bind_method(D_METHOD("pwa_needs_update"), &JavaScript::pwa_needs_update); + ClassDB::bind_method(D_METHOD("pwa_update"), &JavaScript::pwa_update); + ADD_SIGNAL(MethodInfo("pwa_update_available")); } #if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED) @@ -102,6 +105,12 @@ Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, } #endif #if !defined(JAVASCRIPT_ENABLED) +bool JavaScript::pwa_needs_update() const { + return false; +} +Error JavaScript::pwa_update() { + return ERR_UNAVAILABLE; +} void JavaScript::download_buffer(Vector p_arr, const String &p_name, const String &p_mime) { } #endif diff --git a/platform/javascript/api/javascript_singleton.h b/platform/javascript/api/javascript_singleton.h index 27410f5bad4..d346a1ce90f 100644 --- a/platform/javascript/api/javascript_singleton.h +++ b/platform/javascript/api/javascript_singleton.h @@ -59,6 +59,8 @@ public: Ref create_callback(Object *p_ref, const StringName &p_method); Variant _create_object_bind(const Variant **p_args, int p_argcount, Variant::CallError &r_error); void download_buffer(Vector p_arr, const String &p_name, const String &p_mime = "application/octet-stream"); + bool pwa_needs_update() const; + Error pwa_update(); static JavaScript *get_singleton(); JavaScript(); diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h index 15de8b5dc77..2cb5c3025c9 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -49,6 +49,8 @@ extern void godot_js_os_fs_sync(void (*p_callback)()); extern int godot_js_os_execute(const char *p_json); extern void godot_js_os_shell_open(const char *p_uri); extern int godot_js_os_hw_concurrency_get(); +extern int godot_js_pwa_cb(void (*p_callback)()); +extern int godot_js_pwa_update(); // Input extern void godot_js_input_mouse_button_cb(int (*p_callback)(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers)); diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp index c5b8cc8e0c0..6ef80b9f22b 100644 --- a/platform/javascript/javascript_singleton.cpp +++ b/platform/javascript/javascript_singleton.cpp @@ -30,6 +30,7 @@ #include "api/javascript_singleton.h" #include "emscripten.h" +#include "os_javascript.h" extern "C" { extern void godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime); @@ -346,3 +347,10 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { void JavaScript::download_buffer(Vector p_arr, const String &p_name, const String &p_mime) { godot_js_os_download_buffer(p_arr.ptr(), p_arr.size(), p_name.utf8().get_data(), p_mime.utf8().get_data()); } + +bool JavaScript::pwa_needs_update() const { + return OS_JavaScript::get_singleton()->pwa_needs_update(); +} +Error JavaScript::pwa_update() { + return OS_JavaScript::get_singleton()->pwa_update(); +} diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 662d2154438..12d06a8d514 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -368,3 +368,58 @@ const GodotEventListeners = { }, }; mergeInto(LibraryManager.library, GodotEventListeners); + +const GodotPWA = { + + $GodotPWA__deps: ['$GodotRuntime', '$GodotEventListeners'], + $GodotPWA: { + hasUpdate: false, + + updateState: function (cb, reg) { + if (!reg) { + return; + } + if (!reg.active) { + return; + } + if (reg.waiting) { + GodotPWA.hasUpdate = true; + cb(); + } + GodotEventListeners.add(reg, 'updatefound', function () { + const installing = reg.installing; + GodotEventListeners.add(installing, 'statechange', function () { + if (installing.state === 'installed') { + GodotPWA.hasUpdate = true; + cb(); + } + }); + }); + }, + }, + + godot_js_pwa_cb__sig: 'vi', + godot_js_pwa_cb: function (p_update_cb) { + if ('serviceWorker' in navigator) { + const cb = GodotRuntime.get_func(p_update_cb); + navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb)); + } + }, + + godot_js_pwa_update__sig: 'i', + godot_js_pwa_update: function () { + if ('serviceWorker' in navigator && GodotPWA.hasUpdate) { + navigator.serviceWorker.getRegistration().then(function (reg) { + if (!reg || !reg.waiting) { + return; + } + reg.waiting.postMessage('update'); + }); + return 0; + } + return 1; + }, +}; + +autoAddDeps(GodotPWA, '$GodotPWA'); +mergeInto(LibraryManager.library, GodotPWA); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index a8081d95694..cb1ee68cac4 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -46,6 +46,7 @@ #include #include +#include "api/javascript_singleton.h" #include "dom_keys.inc" #include "godot_js.h" @@ -1035,6 +1036,19 @@ void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags } } +void OS_JavaScript::update_pwa_state_callback() { + if (OS_JavaScript::get_singleton()) { + OS_JavaScript::get_singleton()->pwa_is_waiting = true; + } + if (JavaScript::get_singleton()) { + JavaScript::get_singleton()->emit_signal("pwa_update_available"); + } +} + +Error OS_JavaScript::pwa_update() { + return godot_js_pwa_update() ? FAILED : OK; +} + bool OS_JavaScript::is_userfs_persistent() const { return idb_available; } @@ -1072,6 +1086,8 @@ OS_JavaScript::OS_JavaScript() { idb_available = godot_js_os_fs_is_persistent() != 0; idb_needs_sync = false; idb_is_syncing = false; + pwa_is_waiting = false; + godot_js_pwa_cb(&OS_JavaScript::update_pwa_state_callback); if (AudioDriverJavaScript::is_available()) { #ifdef NO_THREADS diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 75a0abcf423..50f0e8776e3 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -78,6 +78,7 @@ private: bool idb_available; bool idb_needs_sync; bool idb_is_syncing; + bool pwa_is_waiting; static void fullscreen_change_callback(int p_fullscreen); static int mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers); @@ -98,6 +99,7 @@ private: static void send_notification_callback(int p_notification); static void fs_sync_callback(); static void update_clipboard_callback(const char *p_text); + static void update_pwa_state_callback(); protected: void resume_audio(); @@ -116,6 +118,8 @@ protected: public: bool check_size_force_redraw(); + bool pwa_needs_update() const { return pwa_is_waiting; } + Error pwa_update(); // Override return type to make writing static callbacks less tedious. static OS_JavaScript *get_singleton();