diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 4dc598ff..122bf63c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -279,7 +279,7 @@ jobs: has() { printf '%s\n' "$files" | grep -qE "$1" && echo true || echo false; } { echo "frontend=$(has '^(src/|demo/|test/|dist/|custom_components/houseplan/frontend/|package(-lock)?\.json$|rollup\.config\.mjs$|tsconfig)')" - echo "backend=$(has '^(custom_components/.*\.py$|tests_backend/|pytest\.ini$)')" + echo "backend=$(has '^(custom_components/.*\.py$|tests_backend/|scripts/support-relay/|pytest\.ini$)')" echo "integration=$(has '^(custom_components/houseplan/manifest\.json$|hacs\.json$|custom_components/.*\.py$|custom_components/.*/translations/)')" } >> "$GITHUB_OUTPUT" @@ -828,6 +828,8 @@ jobs: python -m pytest tests_backend/ -q --cov=custom_components/houseplan --cov-branch --cov-report=xml --cov-report=term + - name: Support relay unit tests + run: python -m unittest discover -s scripts/support-relay/tests -q # #42: гейт «не ниже baseline» — пороги 90/95 поднимаются trivial-правкой числа - name: Порог покрытия не ниже baseline run: | diff --git a/custom_components/houseplan/const.py b/custom_components/houseplan/const.py index 922cf62f..21a80cd3 100755 --- a/custom_components/houseplan/const.py +++ b/custom_components/houseplan/const.py @@ -49,6 +49,18 @@ FILES_DIR = "houseplan/files" CONF_ADMIN_ONLY = "admin_only" VERSION = "1.70.0-beta.2" +# #43: the support transport is deliberately not configurable. A user supplied +# URL would turn the integration into an SSRF proxy and make the privacy notice +# false. The relay is deployed and operated by the project; changing it is a +# reviewed release change, not a Home Assistant option. +SUPPORT_RELAY_URL = "https://support.houseplan.tech/v1/reports" +SUPPORT_PREVIEW_TTL_S = 10 * 60 +MAX_SUPPORT_PREVIEWS_PER_USER = 3 +MAX_SUPPORT_PREVIEWS_TOTAL = 3 +MAX_SUPPORT_ATTACHMENT_BYTES = 8 * 1024 * 1024 +MAX_SUPPORT_MESSAGE_CODEPOINTS = 10_000 +MAX_SUPPORT_CONTACT_CODEPOINTS = 320 + # Portable backup format. This is deliberately independent from the Home # Assistant Store version above: storage migrations and files exported by a # user have different compatibility lifecycles. @@ -91,6 +103,9 @@ ERROR_CODES: frozenset[str] = frozenset({ "marker_control_self", "missing_content", "missing_plan", "no_backup", "not_ready", "not_toggleable", "nothing_to_repair", "preview_expired", "preview_owner_mismatch", "space_in_use", "space_not_found", + "support_invalid_message", "support_package_too_large", + "support_preview_expired", "support_rate_limited", "support_rejected", + "support_unavailable", "too_large", "unauthorized", "unsupported_export_version", "value_badge_source_required", "wall_model_client_outdated", "wall_model_migration_blocked", diff --git a/custom_components/houseplan/frontend/houseplan-assets.json b/custom_components/houseplan/frontend/houseplan-assets.json index 3141e104..b295cae1 100644 --- a/custom_components/houseplan/frontend/houseplan-assets.json +++ b/custom_components/houseplan/frontend/houseplan-assets.json @@ -1,125 +1,125 @@ { "schema": 1, - "fingerprint": "2195ace683267664d035370d92e860e463a0a745074964ec60eb91b9460fe406", + "fingerprint": "087d27ff0b95debda974220d6caa06c83df26b0c798e8298eddc1c90ee6353f6", "entry": "houseplan-card.js", "initialViewFiles": [ - "houseplan-assets/houseplan-card-DXakp5oa.js", + "houseplan-assets/houseplan-card-5sP7gmz1.js", "houseplan-card.js" ], - "initialViewGzipBytes": 287369, + "initialViewGzipBytes": 290370, "lazyFiles": [ - "houseplan-assets/backdrop-pick-YVCyO6A0.js", - "houseplan-assets/de-BEGoVhHu.js", - "houseplan-assets/editor-b0lFtnKI.js", - "houseplan-assets/fr-BDUBoPOz.js", - "houseplan-assets/houseplan-editor-runtime-Bf-E2NBt.js", - "houseplan-assets/houseplan-onboarding-runtime-MR4ITSR4.js" + "houseplan-assets/backdrop-pick-DDeYL4Ri.js", + "houseplan-assets/de-CTrgjw4G.js", + "houseplan-assets/editor-m5cOb3Eu.js", + "houseplan-assets/fr-CZcUQIQ6.js", + "houseplan-assets/houseplan-editor-runtime-dBvEi_Y4.js", + "houseplan-assets/houseplan-onboarding-runtime-X9KZW5U_.js" ], - "lazyGzipBytes": 199380, + "lazyGzipBytes": 205776, "lazyEditorFiles": [ - "houseplan-assets/backdrop-pick-YVCyO6A0.js", - "houseplan-assets/editor-b0lFtnKI.js", - "houseplan-assets/houseplan-editor-runtime-Bf-E2NBt.js" + "houseplan-assets/backdrop-pick-DDeYL4Ri.js", + "houseplan-assets/editor-m5cOb3Eu.js", + "houseplan-assets/houseplan-editor-runtime-dBvEi_Y4.js" ], - "lazyEditorGzipBytes": 144690, + "lazyEditorGzipBytes": 148681, "lazyOnboardingFiles": [ - "houseplan-assets/backdrop-pick-YVCyO6A0.js", - "houseplan-assets/houseplan-onboarding-runtime-MR4ITSR4.js" + "houseplan-assets/backdrop-pick-DDeYL4Ri.js", + "houseplan-assets/houseplan-onboarding-runtime-X9KZW5U_.js" ], - "lazyOnboardingGzipBytes": 14029, + "lazyOnboardingGzipBytes": 14026, "lazyLocaleFiles": [ - "houseplan-assets/de-BEGoVhHu.js", - "houseplan-assets/fr-BDUBoPOz.js" + "houseplan-assets/de-CTrgjw4G.js", + "houseplan-assets/fr-CZcUQIQ6.js" ], - "lazyLocaleGzipBytes": 47731, + "lazyLocaleGzipBytes": 50138, "files": [ { - "path": "houseplan-assets/backdrop-pick-YVCyO6A0.js", - "sha256": "7eb697cfff4e5c645e5dc870c020d321bdbcc12a7cbb5fb69ff0fd3eaf8a4827", + "path": "houseplan-assets/backdrop-pick-DDeYL4Ri.js", + "sha256": "999099104d79952b9c9cdc9a71d044e510398366a0526ddb1d091e4697560828", "rawBytes": 20636, - "gzipBytes": 7070, + "gzipBytes": 7069, "isEntry": false, "imports": [ - "houseplan-assets/houseplan-card-DXakp5oa.js" + "houseplan-assets/houseplan-card-5sP7gmz1.js" ], "dynamicImports": [] }, { - "path": "houseplan-assets/de-BEGoVhHu.js", - "sha256": "1e681536e02fb02a2626b3f3d73903bd2ceacdcb7b4310f1ebdabb67e8ea7857", - "rawBytes": 79660, - "gzipBytes": 24116, + "path": "houseplan-assets/de-CTrgjw4G.js", + "sha256": "f91434ee331ed288d117ba5500c0051ece177d99c05615ff2511e610701b2cf8", + "rawBytes": 83981, + "gzipBytes": 25340, "isEntry": false, "imports": [], "dynamicImports": [] }, { - "path": "houseplan-assets/editor-b0lFtnKI.js", - "sha256": "52920f458ae8514f4ab6651a33845aa1b88d3741323f470e0231d2c911ce34dd", + "path": "houseplan-assets/editor-m5cOb3Eu.js", + "sha256": "9dedbdd213bba49452e19f9d1c1c507d0af5d32003d9976eccac889f2cde85be", "rawBytes": 3826, - "gzipBytes": 1581, + "gzipBytes": 1579, "isEntry": false, "imports": [ - "houseplan-assets/houseplan-card-DXakp5oa.js" + "houseplan-assets/houseplan-card-5sP7gmz1.js" ], "dynamicImports": [] }, { - "path": "houseplan-assets/fr-BDUBoPOz.js", - "sha256": "59f029c824f5836d699de2034813e9bfd481c837bae50a81de358ff86c042f3b", - "rawBytes": 81807, - "gzipBytes": 23615, + "path": "houseplan-assets/fr-CZcUQIQ6.js", + "sha256": "0795dff649cec998870462d21df351bfad990f31550f90d2660471b2c01f51cb", + "rawBytes": 86212, + "gzipBytes": 24798, "isEntry": false, "imports": [], "dynamicImports": [] }, { - "path": "houseplan-assets/houseplan-card-DXakp5oa.js", - "sha256": "f7940dd06a85c23dd2d28fd498a7a6305da32111e871f784351f3e0f5d91794b", - "rawBytes": 1017921, - "gzipBytes": 286572, + "path": "houseplan-assets/houseplan-card-5sP7gmz1.js", + "sha256": "45fdd464fac57f508f180fb5e14674425a48e1d32cc374a0482bd684f0962acd", + "rawBytes": 1032271, + "gzipBytes": 289576, "isEntry": false, "imports": [], "dynamicImports": [ - "houseplan-assets/de-BEGoVhHu.js", - "houseplan-assets/editor-b0lFtnKI.js", - "houseplan-assets/fr-BDUBoPOz.js", - "houseplan-assets/houseplan-editor-runtime-Bf-E2NBt.js", - "houseplan-assets/houseplan-onboarding-runtime-MR4ITSR4.js" + "houseplan-assets/de-CTrgjw4G.js", + "houseplan-assets/editor-m5cOb3Eu.js", + "houseplan-assets/fr-CZcUQIQ6.js", + "houseplan-assets/houseplan-editor-runtime-dBvEi_Y4.js", + "houseplan-assets/houseplan-onboarding-runtime-X9KZW5U_.js" ] }, { - "path": "houseplan-assets/houseplan-editor-runtime-Bf-E2NBt.js", - "sha256": "378202552bee2c88535b64244cba36ec1f1de7f21676bf6d177b3ab1931d209c", - "rawBytes": 527211, - "gzipBytes": 136039, + "path": "houseplan-assets/houseplan-editor-runtime-dBvEi_Y4.js", + "sha256": "832daf3acccdf2f18112adba65874123084209bb1e11f54d89553f905cef1338", + "rawBytes": 543198, + "gzipBytes": 140033, "isEntry": false, "imports": [ - "houseplan-assets/backdrop-pick-YVCyO6A0.js", - "houseplan-assets/houseplan-card-DXakp5oa.js" + "houseplan-assets/backdrop-pick-DDeYL4Ri.js", + "houseplan-assets/houseplan-card-5sP7gmz1.js" ], "dynamicImports": [] }, { - "path": "houseplan-assets/houseplan-onboarding-runtime-MR4ITSR4.js", - "sha256": "b26c1bed651ebef10d36bd435119b3a0e194cc5419999ccfc4ecdff9299c260c", + "path": "houseplan-assets/houseplan-onboarding-runtime-X9KZW5U_.js", + "sha256": "d09f3965b43df77d543f03723306d7d3a5c79b325d244ad22213d2860dcda1d7", "rawBytes": 28088, - "gzipBytes": 6959, + "gzipBytes": 6957, "isEntry": false, "imports": [ - "houseplan-assets/backdrop-pick-YVCyO6A0.js", - "houseplan-assets/houseplan-card-DXakp5oa.js" + "houseplan-assets/backdrop-pick-DDeYL4Ri.js", + "houseplan-assets/houseplan-card-5sP7gmz1.js" ], "dynamicImports": [] }, { "path": "houseplan-card.js", - "sha256": "9317a9b6fcb643f24931d79e55a7ed692828058ccb9c9b811559c3854962175e", + "sha256": "9fd734e140645820dacb371bd8adfc8c28ad25b8130588d781639dd926941355", "rawBytes": 1183, - "gzipBytes": 797, + "gzipBytes": 794, "isEntry": true, "imports": [ - "houseplan-assets/houseplan-card-DXakp5oa.js" + "houseplan-assets/houseplan-card-5sP7gmz1.js" ], "dynamicImports": [] } diff --git a/custom_components/houseplan/frontend/houseplan-assets/backdrop-pick-YVCyO6A0.js b/custom_components/houseplan/frontend/houseplan-assets/backdrop-pick-DDeYL4Ri.js similarity index 99% rename from custom_components/houseplan/frontend/houseplan-assets/backdrop-pick-YVCyO6A0.js rename to custom_components/houseplan/frontend/houseplan-assets/backdrop-pick-DDeYL4Ri.js index 04039b84..455cf44f 100644 --- a/custom_components/houseplan/frontend/houseplan-assets/backdrop-pick-YVCyO6A0.js +++ b/custom_components/houseplan/frontend/houseplan-assets/backdrop-pick-DDeYL4Ri.js @@ -1,4 +1,4 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="2195ace683267664d035370d92e860e463a0a745074964ec60eb91b9460fe406";import{d6 as e,c as t,A as i,a as o,b as n,d7 as s}from"./houseplan-card-DXakp5oa.js";function r(e){const t=e.visualViewport;return t&&Number.isFinite(t.width)&&Number.isFinite(t.height)?{left:Number(t.offsetLeft)||0,top:Number(t.offsetTop)||0,width:Math.max(0,Number(t.width)||0),height:Math.max(0,Number(t.height)||0)}:{left:0,top:0,width:e.innerWidth,height:e.innerHeight}}function a(e,t,i,o=7,n=8){const s=i.left+n,r=i.top+n,a=Math.max(s,i.left+i.width-n),l=Math.max(r,i.top+i.height-n),h=Math.max(0,a-s),c=Math.max(0,l-r),d=Math.min(Math.max(0,t.width),h),u=Math.min(Math.max(0,t.height),c);let p=e.left;p+d>a&&(p=e.right-d),p=Math.min(Math.max(s,p),Math.max(s,a-d));const _=e.bottom+o,g=e.top-o-u;let m="bottom",f=_;return _+u>l&&g>=r?(m="top",f=Math.min(Math.max(r,g),Math.max(r,l-u))):f=Math.min(Math.max(r,_),Math.max(r,l-u)),{left:Math.round(p),top:Math.round(f),side:m,maxWidth:Math.round(h),maxHeight:Math.round(c)}}class l{constructor(e,t,i){this.owner=e,this.name=t,this.keydown=i,this.host=null,this.root=null}window(){return this.owner.ownerDocument.defaultView}dialog(){return this.owner.closest("hp-dialog")}usesPopover(e=!1){const t=this.window();return!e&&!!t&&function(e){const t=e.HTMLElement?.prototype;return"function"==typeof t?.showPopover&&"function"==typeof t?.hidePopover}(t)}get hasFallback(){return!!this.host}containsPath(e){return e.includes(this.owner)||!!this.host&&e.includes(this.host)}ownsActiveElement(){let e=this.owner.ownerDocument.activeElement;for(;e?.shadowRoot?.activeElement;)e=e.shadowRoot.activeElement;return!(!e||e!==this.owner&&!this.owner.shadowRoot?.contains(e)&&!this.root?.contains(e))}renderFallback(i,o){const n=this.ensureFallback();return n?(e(t`${i}`,n),n):null}surface(e,t){return t?this.owner.shadowRoot?.querySelector(e)||null:this.root?.querySelector(e)||null}destroy(){this.root&&e(i,this.root),this.host?.remove(),this.host=null,this.root=null}ensureFallback(){if(this.root?.isConnected)return this.root;const e=this.dialog()?.overlayPortal()||this.owner.ownerDocument.body;if(!e)return null;const t=this.owner.ownerDocument.createElement("div");return t.dataset.hpOverlay=this.name,t.style.display="contents",this.keydown&&t.addEventListener("keydown",this.keydown,!0),e.append(t),this.host=t,this.root=t.attachShadow({mode:"open"}),this.root}}let h=0;class c extends n{constructor(){super(...arguments),this.text="",this.ariaLabel="",this.pointerHover=!1,this._open=!1,this._openTimer=0,this._closeTimer=0,this._positionRaf=0,this._forceFallback=!1,this._hoverOwned=!1,this._overlayDispose=null,this._scrollDialog=null,this._descriptionId="hp-help-description-"+ ++h,this._surfacePointerEnter=()=>{const e=this._window();this._closeTimer&&e?.clearTimeout(this._closeTimer),this._closeTimer=0},this._surfacePointerLeave=()=>{this._scheduleClose()},this._outsidePointerDown=e=>{if(!this._open)return;const t=e.composedPath();this._floating.containsPath(t)||this._closeHelp(!1,"outside")},this._dialogScroll=e=>{const t=this._floating.containsPath(e.composedPath()),i=e.target,o=this._dialog(),n=i instanceof Node&&(i===o||i instanceof Element&&i.contains(this));var s;this._open&&(s=n,!t&&s)&&this._closeHelp(!1,"scroll")},this._keyDown=e=>{if(!this._open||e.defaultPrevented)return;if("Escape"===e.key)return e.preventDefault(),e.stopImmediatePropagation(),void this._closeHelp(this._floating.ownsActiveElement(),"escape");const t=this.renderRoot.querySelector(".trigger");if(!t||!e.composedPath().includes(t))return;const i=this._surface();if(!i||i.scrollHeight<=i.clientHeight)return;const o=Math.max(40,.8*i.clientHeight),n={ArrowDown:40,ArrowUp:-40,PageDown:o,PageUp:-o};if("Home"===e.key)i.scrollTop=0;else if("End"===e.key)i.scrollTop=i.scrollHeight;else{if(!(e.key in n))return;i.scrollBy({top:n[e.key],behavior:"auto"})}e.preventDefault(),e.stopImmediatePropagation()},this._floating=new l(this,"help",this._keyDown),this._queuePosition=()=>{if(!this._open||this._positionRaf)return;const e=this._window();e&&(this._positionRaf=e.requestAnimationFrame(()=>{this._positionRaf=0,this._position()||this._closeHelp()}))}}connectedCallback(){super.connectedCallback(),this.addEventListener("keydown",this._keyDown,!0)}willUpdate(){this.toggleAttribute("data-has-content",this._hasContent())}disconnectedCallback(){const e=this.ownerDocument.defaultView;this._unsubscribeOpenListeners(),this.removeEventListener("keydown",this._keyDown,!0),this._clearTimers(),this._positionRaf&&e?.cancelAnimationFrame(this._positionRaf),this._positionRaf=0,this._closeHelp(!1,"disconnect"),super.disconnectedCallback()}updated(e){if(e.has("pointerHover")&&!this.pointerHover){const e=this._window();this._openTimer&&e?.clearTimeout(this._openTimer),this._openTimer=0,this._hoverOwned&&this._closeHelp(!1,"outside")}this._hasContent()||!this._open?this._open&&e.has("text")&&(this._usesPopover()||this._renderFallback(),this._queuePosition()):this._closeHelp()}_window(){return this._floating.window()}_dialog(){return this._floating.dialog()}_usesPopover(){return this._floating.usesPopover(this._forceFallback)}_hasContent(){return e=this.text,t=this.ariaLabel,"string"==typeof e&&e.trim().length>0&&"string"==typeof t&&t.trim().length>0;var e,t}_clearTimers(){const e=this._window();this._openTimer&&e?.clearTimeout(this._openTimer),this._closeTimer&&e?.clearTimeout(this._closeTimer),this._openTimer=0,this._closeTimer=0}_scheduleOpen(){const e=this._window();this.pointerHover&&e&&!this._open&&!this._openTimer&&(this._closeTimer&&e.clearTimeout(this._closeTimer),this._closeTimer=0,this._openTimer=e.setTimeout(()=>{this._openTimer=0,this.isConnected&&(this._hoverOwned=!0,this._openHelp())},300))}_scheduleClose(){const e=this._window(),t=this.renderRoot.querySelector(".trigger");e&&!t?.matches(":focus-visible")&&(this._openTimer&&e.clearTimeout(this._openTimer),this._closeTimer&&e.clearTimeout(this._closeTimer),this._openTimer=0,this._closeTimer=e.setTimeout(()=>{this._closeTimer=0,this.isConnected&&this._closeHelp()},150))}_triggerPointerEnter(e){this.pointerHover&&"mouse"===e.pointerType&&this._scheduleOpen()}_triggerPointerLeave(e){"mouse"===e.pointerType&&this._scheduleClose()}_triggerFocus(){queueMicrotask(()=>{const e=this.renderRoot.querySelector(".trigger");e?.matches(":focus-visible")&&(this._hoverOwned=!1,this._openHelp())})}_triggerBlur(){this._scheduleClose()}_triggerClick(){this._open?this._closeHelp():(this._hoverOwned=!1,this._openHelp())}_subscribeOpenListeners(){this.ownerDocument.addEventListener("pointerdown",this._outsidePointerDown,!0),this.ownerDocument.addEventListener("keydown",this._keyDown,!0);const e=this._window();e?.addEventListener("resize",this._queuePosition),e?.addEventListener("orientationchange",this._queuePosition),e?.visualViewport?.addEventListener("resize",this._queuePosition),e?.visualViewport?.addEventListener("scroll",this._queuePosition),this._scrollDialog=this._dialog(),this._scrollDialog?.addEventListener("scroll",this._dialogScroll,!0)}_unsubscribeOpenListeners(){this.ownerDocument.removeEventListener("pointerdown",this._outsidePointerDown,!0),this.ownerDocument.removeEventListener("keydown",this._keyDown,!0);const e=this._window();e?.removeEventListener("resize",this._queuePosition),e?.removeEventListener("orientationchange",this._queuePosition),e?.visualViewport?.removeEventListener("resize",this._queuePosition),e?.visualViewport?.removeEventListener("scroll",this._queuePosition),this._scrollDialog?.removeEventListener("scroll",this._dialogScroll,!0),this._scrollDialog=null}async _openHelp(){!this._open&&this._hasContent()&&(this._clearTimers(),this._forceFallback=!1,this._open=!0,this._subscribeOpenListeners(),await this.updateComplete,this._open&&(this._usesPopover()||this._renderFallback(),this._position()?this._overlayDispose=this._dialog()?.registerOverlay({owner:this,group:"transient",close:e=>this._closeHelp("escape"===e&&this._floating.ownsActiveElement(),e)})||null:this._closeHelp()))}_closeHelp(e=!1,t="exclusive"){if(!this._open&&!this._floating.hasFallback)return;this._clearTimers();const i=this._overlayDispose;this._overlayDispose=null,i?.();const o=this.renderRoot.querySelector(".tooltip");if(o?.hidePopover)try{o.matches(":popover-open")&&o.hidePopover()}catch{}this._open=!1,this._hoverOwned=!1,this._unsubscribeOpenListeners(),this._floating.destroy(),e&&this.updateComplete.then(()=>this.renderRoot.querySelector(".trigger")?.focus())}_tooltipTemplate(e){return t`