From 8707918269a87c978796bb39a1ca692a9b0a3579 Mon Sep 17 00:00:00 2001 From: Tessa Walsh Date: Thu, 6 Mar 2025 15:23:08 -0500 Subject: [PATCH] WIP: Add client-side replay option --- config.yaml | 4 ++ pywb/apps/frontendapp.py | 16 +++++++ pywb/apps/rewriterapp.py | 3 ++ pywb/rewrite/templateview.py | 3 ++ pywb/static/loadWabac.js | 80 ++++++++++++++++++++++++++++++++ pywb/templates/frame_insert.html | 11 +++++ 6 files changed, 117 insertions(+) create mode 100644 pywb/static/loadWabac.js diff --git a/config.yaml b/config.yaml index 5bdcf544..9c4be0e9 100644 --- a/config.yaml +++ b/config.yaml @@ -29,6 +29,10 @@ framed_replay: true redirect_to_exact: true +# Use wabac.js-style client-side replay system +client_side_replay: false + + # Uncomment and change to set default locale # default_locale: en diff --git a/pywb/apps/frontendapp.py b/pywb/apps/frontendapp.py index ab57a701..5459b980 100644 --- a/pywb/apps/frontendapp.py +++ b/pywb/apps/frontendapp.py @@ -81,6 +81,8 @@ class FrontEndApp(object): self.debug = config.get('debug', False) + self.client_side_replay = config.get('client_side_replay', False) + self.warcserver_server = GeventServer(self.warcserver, port=0) self.proxy_prefix = None # the URL prefix to be used for the collection with proxy mode (e.g. /coll/id_/) @@ -130,6 +132,9 @@ class FrontEndApp(object): coll_prefix = '/' self.url_map.add(Rule('/', endpoint=self.serve_home)) + if self.client_side_replay: + self.url_map.add(Rule('/static/sw.js', endpoint=self.serve_wabac_service_worker)) + self._init_coll_routes(coll_prefix) if self.proxy_prefix is not None: @@ -818,6 +823,17 @@ class FrontEndApp(object): response.add_access_control_headers(env=env) return response + def serve_wabac_service_worker(self, env): + """Serve wabac.js service worker. + + :param dict env: The WSGI environment dictionary + :return: WbResponse with service worker + :rtype: WbResponse + """ + response = self.serve_static(env, coll='', filepath='wabacWorker.js') + response.status_headers['Service-Worker-Allowed'] = '/' + return response + # ============================================================================ class MetadataCache(object): diff --git a/pywb/apps/rewriterapp.py b/pywb/apps/rewriterapp.py index 59b7d0ea..442747b1 100644 --- a/pywb/apps/rewriterapp.py +++ b/pywb/apps/rewriterapp.py @@ -84,6 +84,8 @@ class RewriterApp(object): self._html_templ('head_insert_html'), self.custom_banner_view) + self.client_side_replay = self.config.get('client_side_replay', False) + self.frame_insert_view = TopFrameView(self.jinja_env, self._html_templ('frame_insert_html'), self.banner_view) @@ -933,6 +935,7 @@ class RewriterApp(object): environ, self.frame_mod, self.replay_mod, + self.client_side_replay, coll='', extra_params=extra_params) diff --git a/pywb/rewrite/templateview.py b/pywb/rewrite/templateview.py index c323a999..07b867f3 100644 --- a/pywb/rewrite/templateview.py +++ b/pywb/rewrite/templateview.py @@ -388,6 +388,7 @@ class TopFrameView(BaseInsertView): env, frame_mod, replay_mod, + client_side_replay, coll='', extra_params=None): """ @@ -397,6 +398,7 @@ class TopFrameView(BaseInsertView): :param dict env: The WSGI environment dictionary for the request this template is being rendered for :param str frame_mod: The modifier to be used for framing (e.g. if_) :param str replay_mod: The modifier to be used in the URL of the page being replayed (e.g. mp_) + :param bool client_side_replay: Boolean indicating whether to use wabac.js-based client side replay :param str coll: The name of the collection this template is being rendered for :param dict extra_params: Additional parameters to be supplied to the Jninja template render method :return: The frame insert string @@ -423,6 +425,7 @@ class TopFrameView(BaseInsertView): 'embed_url': embed_url, 'is_proxy': is_proxy, + 'client_side_replay': client_side_replay, 'timestamp': timestamp, 'url': wb_url.get_url() } diff --git a/pywb/static/loadWabac.js b/pywb/static/loadWabac.js new file mode 100644 index 00000000..8f36506e --- /dev/null +++ b/pywb/static/loadWabac.js @@ -0,0 +1,80 @@ +class WabacReplay +{ + constructor(prefix, url, ts) { + this.prefix = prefix; + this.url = url; + this.ts = ts; + this.collName = new URL(prefix, "http://dummy").pathname.split('/')[1]; + this.adblockUrl = undefined; + + this.queryParams = {}; + } + + async init() { + const scope = '/'; + + await navigator.serviceWorker.register("/static/sw.js?" + new URLSearchParams(this.queryParams).toString(), {scope}); + + let initedResolve = null; + + const inited = new Promise((resolve) => initedResolve = resolve); + + navigator.serviceWorker.addEventListener("message", (event) => { + if (event.data.msg_type === "collAdded") { + // the replay is ready to be loaded when this message is received + initedResolve(); + } + }); + + const baseUrl = new URL(window.location); + baseUrl.hash = ""; + + const proxyPrefix = ""; + + const msg = { + msg_type: "addColl", + name: this.collName, + type: "live", + file: {"sourceUrl": `proxy:${proxyPrefix}`}, + skipExisting: false, + extraConfig: { + prefix: proxyPrefix, + isLive: false, + baseUrl: baseUrl.href, + baseUrlHashReplay: true, + noPostToGet: false, + archivePrefix: `/${this.collName}/`, + adblockUrl: this.adblockUrl + }, + }; + + if (!navigator.serviceWorker.controller) { + navigator.serviceWorker.addEventListener("controllerchange", () => { + navigator.serviceWorker.controller.postMessage(msg); + }); + } else { + navigator.serviceWorker.controller.postMessage(msg); + } + + window.addEventListener('message', event => { + let data = event.data; + if (data.wb_type !== 'load') return; + history.replaceState({}, data.title, this.prefix + data.ts + '/' + data.url); + window.WBBanner.onMessage(event); + }); + + window.cframe = this; + + if (inited) { + await inited; + } + + this.load_url(this.url, this.ts); + } + + // called by the Vue banner when the timeline is clicked + load_url(url, ts) { + const iframe = document.querySelector('#replay_iframe'); + iframe.src = `/w/${this.collName}/${ts}mp_/${url}`; + } +} diff --git a/pywb/templates/frame_insert.html b/pywb/templates/frame_insert.html index 189eed71..8403395c 100644 --- a/pywb/templates/frame_insert.html +++ b/pywb/templates/frame_insert.html @@ -12,7 +12,15 @@ html, body } + +{% if client_side_replay %} + + +{% else %} +{% endif %} {% autoescape false %} @@ -45,6 +53,8 @@ html, body
+ +{% if not client_side_replay %} +{% endif %} {% endautoescape %}