mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-31 09:13:33 +02:00
fix(filebrowser_quantum): make Download save the file in the iOS companion app (#3030)
* fix(filebrowser_quantum): make Download save the file in the iOS companion app FileBrowser downloads by clicking an <a> that carries no download attribute and letting the attachment response do the rest. The Home Assistant iOS companion app is a WKWebView, where a download only happens when WebKit turns a navigation action into a WKDownload -- which is what the download attribute does, and the app hands the result 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 inside the ingress panel a plain attachment navigation is simply rendered: a text file opens and shows its content with no way to save it. The ingress filter now adds the attribute, matched on the two exact download endpoints so nothing else in the app is touched. Desktop browsers already downloaded these and are unaffected, and an empty value keeps the filename the server sends in Content-Disposition. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(filebrowser_quantum): keep Open file a navigation, and narrow the claims Review of #3030 found that the 'no preview available' fallback renders an 'Open file' link on the same download endpoint with inline=true (views/files/Preview.vue), so a pathname-only match would have turned opening a file into downloading it. Exclude inline=true. Also narrows two overstated claims: the app's download manager is gated on iOS 17, and the public-share sidebar downloads with window.open() rather than an anchor, so it is not covered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,15 @@
|
||||
|
||||
## 1.5.3.2 (2026-08-30)
|
||||
- Fix Download in the Home Assistant iOS companion app (iOS 17 and later),
|
||||
where a file opened and showed its content with no way to save it.
|
||||
FileBrowser downloads by clicking a link that carries no `download`
|
||||
attribute, and the app's WKWebView only turns a click into a real download
|
||||
when that attribute is present, so inside the ingress panel the file was
|
||||
simply rendered. The ingress filter now adds the attribute to FileBrowser's
|
||||
own download link. "Open file" still opens, and the public-share sidebar's
|
||||
own download button is not covered. Desktop browsers already downloaded
|
||||
these and are unchanged, as is direct access on port 8071.
|
||||
|
||||
## 1.5.3.1 (2026-08-29)
|
||||
- Fix "open parent directory" in Tools -> File Size Analyzer under Home
|
||||
Assistant ingress. FileBrowser opened the parent folder in a new tab, which
|
||||
|
||||
@@ -118,4 +118,4 @@ schema:
|
||||
slug: filebrowser_quantum
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "1.5.3.1"
|
||||
version: "1.5.3.2"
|
||||
|
||||
@@ -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)}})();";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user