|
|
|
|
@@ -13,34 +13,59 @@ server {
|
|
|
|
|
proxy_read_timeout 30m;
|
|
|
|
|
proxy_pass %%protocol%%://backend%%subpath%%;
|
|
|
|
|
|
|
|
|
|
# Tools -> File Size Analyzer (and the other tool views) open a result's
|
|
|
|
|
# parent folder with window.open(<absolute url>, '_blank'), because
|
|
|
|
|
# goToItem() takes its newTab argument from the context menu's
|
|
|
|
|
# showLimitedOptions flag, which those views always set. Behind ingress
|
|
|
|
|
# that popup lands on the raw /api/hassio_ingress/<token>/ url with no
|
|
|
|
|
# Home Assistant frontend around it to keep the ingress session alive,
|
|
|
|
|
# so the new tab answers 401 instead of showing the folder. Turn that
|
|
|
|
|
# popup into a navigation of the panel itself.
|
|
|
|
|
# Two things the ingress panel needs that a plain browser tab does not.
|
|
|
|
|
# Both are injected into the page's existing nonce-carrying inline script
|
|
|
|
|
# rather than next to <div id="app">: FileBrowser sends
|
|
|
|
|
# script-src 'self' 'nonce-<random>', so a standalone inline <script>
|
|
|
|
|
# would be blocked. If upstream ever drops that
|
|
|
|
|
# window.__pwaDeferredPrompt line the filter stops matching and both
|
|
|
|
|
# behaviours revert, which is the state before either fix.
|
|
|
|
|
#
|
|
|
|
|
# The context menu's "go to item" action, which search results and the
|
|
|
|
|
# tool views also offer, hardcodes the same new tab and is fixed too.
|
|
|
|
|
# 1. Opening a folder. The tool views (Tools -> File Size Analyzer and
|
|
|
|
|
# the others) always set the context menu's showLimitedOptions flag, and
|
|
|
|
|
# openParentFolder() hands that same flag to goToItem() as its newTab
|
|
|
|
|
# argument, so the folder is opened with window.open(<url>, '_blank').
|
|
|
|
|
# Behind ingress that popup lands on the raw /api/hassio_ingress/<token>/
|
|
|
|
|
# url with no Home Assistant frontend around it to keep the ingress
|
|
|
|
|
# session alive, so the new tab answers 401 instead of showing the
|
|
|
|
|
# folder. Turn it into a navigation of the panel itself. The context
|
|
|
|
|
# menu's "go to item" action, offered by search results and the tool
|
|
|
|
|
# views, hardcodes the same new tab and is fixed with it.
|
|
|
|
|
#
|
|
|
|
|
# Scoped to the two prefixes goToItem() builds, "files/" and
|
|
|
|
|
# "public/share/", so the window.open calls that download or preview a
|
|
|
|
|
# file (they go to api/resources/download) keep their own tab: sending
|
|
|
|
|
# an inline raw file to location.assign would replace the whole app.
|
|
|
|
|
# A link out of the add-on keeps its own tab as well, unless a user
|
|
|
|
|
# points a sidebar link (or a link inside a file open in the editor) at
|
|
|
|
|
# this same instance's files/ or public/share/ route, which then also
|
|
|
|
|
# opens in the panel. Same shape as the komga add-on's ingress filter.
|
|
|
|
|
# an inline raw file to location.assign would replace the whole app. A
|
|
|
|
|
# link out of the add-on keeps its own tab as well, unless a user points
|
|
|
|
|
# a sidebar link -- or a link inside a file open in the editor -- at this
|
|
|
|
|
# same instance's files/ or public/share/ route. Same shape as the komga
|
|
|
|
|
# add-on's ingress filter.
|
|
|
|
|
#
|
|
|
|
|
# Injected into the page's existing nonce-carrying inline script rather
|
|
|
|
|
# than next to <div id="app">: FileBrowser sends
|
|
|
|
|
# script-src 'self' 'nonce-<random>', so a standalone inline <script>
|
|
|
|
|
# would be blocked. If upstream ever drops that
|
|
|
|
|
# window.__pwaDeferredPrompt line the filter simply stops matching and
|
|
|
|
|
# the popup behaviour returns, which is the pre-fix state.
|
|
|
|
|
sub_filter "window.__pwaDeferredPrompt = null;" "window.__pwaDeferredPrompt = null;(function(){var o=window.open;window.open=function(u,n,f){try{var b=(window.globalVars||{}).baseURL;if(u&&n==='_blank'&&!f&&b){if(b.slice(-1)!=='/')b+='/';var t=new URL(u,location.href);if((t.protocol==='http:'||t.protocol==='https:')&&t.origin===location.origin&&(t.pathname.indexOf(b+'files/')===0||t.pathname.indexOf(b+'public/share/')===0)){location.assign(t.href);return window}}}catch(e){}return o.apply(window,arguments)}})();";
|
|
|
|
|
# 2. Download. FileBrowser downloads a file by building an <a> with no
|
|
|
|
|
# download attribute, clicking it, and letting the attachment response do
|
|
|
|
|
# the rest. The Home Assistant iOS companion app is a WKWebView, and a
|
|
|
|
|
# download happens there only when WebKit turns a navigation *action*
|
|
|
|
|
# into a WKDownload, which is what the download attribute does -- the app
|
|
|
|
|
# hands that to its own download manager
|
|
|
|
|
# (WebViewController+WebKitDelegates.swift, navigationAction:didBecome
|
|
|
|
|
# download:). Its response policy delegate returns .allow for every
|
|
|
|
|
# sub-frame and never returns .download, so a plain attachment navigation
|
|
|
|
|
# inside the ingress panel is just rendered: a text file opens and shows
|
|
|
|
|
# its content with no way to save it. Adding the attribute makes the same
|
|
|
|
|
# click a real download. Desktop browsers already downloaded these and
|
|
|
|
|
# are unaffected, and an empty value keeps the filename the server sends
|
|
|
|
|
# in Content-Disposition. Matched on the two exact download endpoints,
|
|
|
|
|
# minus inline=true: the "no preview available" fallback renders an
|
|
|
|
|
# "Open file" link on the same endpoint with that parameter
|
|
|
|
|
# (views/files/Preview.vue), and it is meant to open, not save. Only
|
|
|
|
|
# programmatic clicks pass through here -- a person clicking a link never
|
|
|
|
|
# calls HTMLAnchorElement.prototype.click -- so this reaches
|
|
|
|
|
# FileBrowser's own hidden download anchor and nothing a user clicks.
|
|
|
|
|
#
|
|
|
|
|
# Not covered: the public-share sidebar downloads with window.open()
|
|
|
|
|
# rather than an anchor, and the app's download manager is gated on
|
|
|
|
|
# iOS 17 (WebViewController+WebKitDelegates.swift).
|
|
|
|
|
sub_filter "window.__pwaDeferredPrompt = null;" "window.__pwaDeferredPrompt = null;(function(){var o=window.open;window.open=function(u,n,f){try{var b=(window.globalVars||{}).baseURL;if(u&&n==='_blank'&&!f&&b){if(b.slice(-1)!=='/')b+='/';var t=new URL(u,location.href);if((t.protocol==='http:'||t.protocol==='https:')&&t.origin===location.origin&&(t.pathname.indexOf(b+'files/')===0||t.pathname.indexOf(b+'public/share/')===0)){location.assign(t.href);return window}}}catch(e){}return o.apply(window,arguments)};var c=HTMLAnchorElement.prototype.click;HTMLAnchorElement.prototype.click=function(){try{var b=(window.globalVars||{}).baseURL;if(b&&this.href&&!this.hasAttribute('download')){if(b.slice(-1)!=='/')b+='/';var t=new URL(this.href,location.href);if(t.origin===location.origin&&t.searchParams.get('inline')!=='true'&&(t.pathname===b+'api/resources/download'||t.pathname===b+'public/api/resources/download')){this.download=''}}}catch(e){}return c.apply(this,arguments)}})();";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|