Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ redirect_to_exact: true
# locales:
# - en
# - ru

# Uncomment to inject extra scripts into replayed pages (place scripts under static/ or your alternate static_dir location)
# inject_scripts:
# - ruffle/ruffle.js
27 changes: 27 additions & 0 deletions docs/manual/configuring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -655,3 +655,30 @@ By default, SSL-Certificates of websites are not verified. To enable verificatio

``ca_cert_dir`` can optionally point to a directory containing the CA certificates that you trust. Most linux distributions provide CA certificates via a package called ``ca-certificates``.
If omitted, the default system CA used by Python is used.

Injecting Scripts
-----------------

Extra JavaScript files can be injected into replayed pages. This can be useful for emulating removed browser features
or applying compatibility tweaks.

For example, to emulate Flash Player using `Ruffle <https://ruffle.rs/>`_, create a subdirectory named ``static/ruffle``
and unzip the `Ruffle self-hosted package <https://ruffle.rs/downloads#website-package>`_ into it. Then add the following
configuration::

inject_scripts:
- ruffle/ruffle.js

Note: Paths listed under ``inject_scripts`` are relative to the ``static_dir`` directory (default ``static/``).

Injected scripts can also be configured per collection::

inject_scripts:
- all.js
- other.js

collections:
mycoll:
inject_scripts:
- all.js # static/all.js
- _/mycoll/tweaks.js # collections/mycoll/static/tweaks.js
12 changes: 11 additions & 1 deletion pywb/apps/rewriterapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,8 @@ def render_content(self, wb_url, kwargs, environ):
replay_mod=self.replay_mod,
metadata=kwargs.get('metadata', {}),
ui=kwargs.get('ui', {}),
config=self.config))
config=self.config,
inject_scripts=self.get_inject_scripts(kwargs)))

cookie_rewriter = None
if self.cookie_tracker and cookie_key:
Expand Down Expand Up @@ -926,6 +927,14 @@ def get_top_frame_params(self, wb_url, kwargs):
'ui': kwargs.get('ui', {})
}

def get_inject_scripts(self, kwargs):
coll = kwargs.get('coll')
coll_config = self.config.get('collections', {}).get(coll, {})
# ignore special collections like live or all
if isinstance(coll_config, str):
coll_config = {}
return coll_config.get('inject_scripts', self.config.get('inject_scripts', []))

def handle_custom_response(self, environ, wb_url, full_prefix, host_prefix, kwargs):
if self.is_framed_replay(wb_url):
extra_params = self.get_top_frame_params(wb_url, kwargs)
Expand All @@ -936,6 +945,7 @@ def handle_custom_response(self, environ, wb_url, full_prefix, host_prefix, kwar
self.frame_mod,
self.replay_mod,
self.client_side_replay,
self.get_inject_scripts(kwargs),
coll=kwargs.get("coll"),
extra_params=extra_params)

Expand Down
4 changes: 3 additions & 1 deletion pywb/rewrite/templateview.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ def get_top_frame(self, wb_url,
frame_mod,
replay_mod,
client_side_replay,
inject_scripts,
coll='',
extra_params=None):
"""
Expand Down Expand Up @@ -429,7 +430,8 @@ def get_top_frame(self, wb_url,
'timestamp': timestamp,
'url': wb_url.get_url(),

'sw_prefix': env.get('pywb.app_prefix', '')
'sw_prefix': env.get('pywb.app_prefix', ''),
'inject_scripts': inject_scripts,
}

if extra_params:
Expand Down
4 changes: 3 additions & 1 deletion pywb/static/loadWabac.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
class WabacReplay
{
constructor(prefix, url, ts, staticPrefix, coll, swScopePrefix) {
constructor(prefix, url, ts, staticPrefix, coll, swScopePrefix, injectScripts) {
this.prefix = prefix;
this.url = url;
this.ts = ts;
this.staticPrefix = staticPrefix;
this.collName = coll;
this.isRoot = coll === "$root";
this.swScope = swScopePrefix;
this.injectScripts = injectScripts;
this.adblockUrl = undefined;

this.queryParams = {"replayPrefix": ""};
Expand Down Expand Up @@ -54,6 +55,7 @@ class WabacReplay
archiveMod: "ir_",
adblockUrl: this.adblockUrl,
noPostToGet: true,
injectScripts: this.injectScripts.map(src => "../" + src),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pywb supports static files from the root static directory or per-collection static directories: https://pywb.readthedocs.io/en/latest/manual/ui-guide.html#static-files. It might be worth applying that same logic to the injected scripts, in case users want e.g. Ruffle enabled but only on one collection?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is exactly what you meant, but I've updated it so you can do this:

inject_scripts:
  - all.js
  - other.js

collections:
  mycoll:
    inject_scripts:
      - all.js                # static/all.js
      - _/mycoll/tweaks.js    # collections/mycoll/static/tweaks.js

Copy link
Collaborator Author

@ato ato Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh and the injectScripts.map(src => "../" + src) on this line is because wabac.js actually adds the prefix /static/proxy/. It looked to me like maybe the original idea was for it to load absolute URLs from the live web, but the serviceworker just returned 404 when I tried that, but ../ worked.

},
};

Expand Down
2 changes: 1 addition & 1 deletion pywb/templates/frame_insert.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@

{% if client_side_replay %}
if (navigator.serviceWorker) {
window.cframe = new WabacReplay("{{ wb_prefix }}", "{{ url }}", "{{ timestamp }}", "{{ static_prefix }}", "{{ coll }}", "{{ sw_prefix }}");
window.cframe = new WabacReplay("{{ wb_prefix }}", "{{ url }}", "{{ timestamp }}", "{{ static_prefix }}", "{{ coll }}", "{{ sw_prefix }}", {{ inject_scripts | tojson }});
window.cframe.init();
}
{% endif %}
Expand Down
4 changes: 4 additions & 0 deletions pywb/templates/head_insert.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
</script>
{% endif %}

{% for script in inject_scripts %}
<script src='{{ static_prefix }}/{{ script }}'></script>
{% endfor %}

{% if config.enable_flash_video_rewrite or config.transclusions_version == 1 %}
<script src='{{ static_prefix }}/vidrw.js'> </script>

Expand Down
3 changes: 3 additions & 0 deletions tests/config_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ enable_memento: true

# enable new transclusion system
transclusions_version: 2

inject_scripts:
- inject.js
1 change: 1 addition & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def test_replay_content(self, fmod):
assert '"20140127171238"' in resp.text, resp.text
assert 'wombat.js' in resp.text
assert 'transclusions.js' in resp.text
assert 'inject.js' in resp.text
assert '_WBWombatInit' in resp.text, resp.text
assert 'wbinfo.enable_auto_fetch = false;' in resp.text
assert '/pywb/20140127171238{0}/http://www.iana.org/time-zones"'.format(fmod) in resp.text
Expand Down