From 224e190fa1b30faadd156afc482359244cc0ae05 Mon Sep 17 00:00:00 2001 From: Jens Neuber Date: Mon, 10 Aug 2026 09:28:42 +0200 Subject: [PATCH] Add Home Assistant integration discovery --- README.md | 5 ++ bun.lock | 3 +- package.json | 2 +- solaredge-optimizer-data/CHANGELOG.md | 6 +++ solaredge-optimizer-data/DOCS.md | 17 +++++-- solaredge-optimizer-data/config.yaml | 5 +- solaredge-optimizer-data/dist/server.js | 28 +++++------ solaredge-optimizer-data/package.json | 2 +- .../src/discovery.test.ts | 48 +++++++++++++++++++ solaredge-optimizer-data/src/discovery.ts | 40 ++++++++++++++++ solaredge-optimizer-data/src/index.ts | 10 ++++ 11 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 solaredge-optimizer-data/src/discovery.test.ts create mode 100644 solaredge-optimizer-data/src/discovery.ts diff --git a/README.md b/README.md index 5fa0b70..f3d7b1c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ Voraussetzung ist Home Assistant OS. 5. Hinterlege im Tab **Konfiguration** Benutzername, Passwort und Site-ID. 6. Speichere die Konfiguration und starte die App. +Ab App-Version 0.2.0 meldet sich die App automatisch bei Home Assistant. Mit +der Custom Integration aus +`https://git.jensneuber.de/jens/ha-solaredge-optimizers` werden daraus Geräte +und Sensoren für Tagesenergie und aktuelle Leistung jedes Optimierers. + ## SolarEdge Optimizer Data Die App aktualisiert standardmäßig alle zehn Minuten und liefert für jedes diff --git a/bun.lock b/bun.lock index 094f0a6..00196df 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,7 @@ "workspaces": { "": { "name": "solaredge-optimizers", + "version": "0.2.0", }, "packages/solaredgeapi": { "name": "@solar-dash/solaredgeapi", @@ -19,7 +20,7 @@ }, "solaredge-optimizer-data": { "name": "@solar-dash/solaredge-optimizer-app", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "@solar-dash/solaredgeapi": "workspace:*", }, diff --git a/package.json b/package.json index 0532a6c..9d65e31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solaredge-optimizers", - "version": "0.1.0", + "version": "0.2.0", "private": true, "packageManager": "bun@1.3.14", "workspaces": [ diff --git a/solaredge-optimizer-data/CHANGELOG.md b/solaredge-optimizer-data/CHANGELOG.md index 432fb5c..7cb27b9 100644 --- a/solaredge-optimizer-data/CHANGELOG.md +++ b/solaredge-optimizer-data/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.2.0 + +- Register the internal optimizer API through Home Assistant Supervisor + discovery. +- Enable automatic setup of the SolarEdge Optimizers custom integration. + ## 0.1.0 - Initial Home Assistant App package. diff --git a/solaredge-optimizer-data/DOCS.md b/solaredge-optimizer-data/DOCS.md index 7d76743..b49422c 100644 --- a/solaredge-optimizer-data/DOCS.md +++ b/solaredge-optimizer-data/DOCS.md @@ -83,9 +83,18 @@ im App-Panel erreichbar. ## Zugriff aus Home Assistant Die App veröffentlicht standardmäßig keinen ungeschützten Port im lokalen -Netz. Home Assistant und andere Apps können sie über das interne App-Netzwerk -erreichen. Bei einer lokalen Installation lautet der Hostname typischerweise -`local-solaredge-optimizer-data`: +Netz. Ab Version 0.2.0 meldet sie ihren internen Host und Port automatisch über +die Supervisor-Diensterkennung. Dadurch kann die +[SolarEdge Optimizers Integration](https://git.jensneuber.de/jens/ha-solaredge-optimizers) +ohne manuelle interne URL eingerichtet werden. + +Nach Installation und Start der App erscheint unter **Einstellungen → Geräte & +Dienste** die gefundene Integration **SolarEdge Optimizers**. Installiere die +Custom Integration vorher über HACS und bestätige anschließend den Fund. + +Home Assistant und andere Apps können die API außerdem direkt über das interne +App-Netzwerk erreichen. Bei einer lokalen Installation lautet der Hostname +typischerweise `local-solaredge-optimizer-data`: ```text http://local-solaredge-optimizer-data:8099/api/optimizers @@ -119,7 +128,7 @@ für eine lokale Installation nach: Lade anschließend den App Store neu, installiere **SolarEdge Optimizer Data**, pflege Benutzername, Passwort und Site-ID im Tab **Konfiguration** und starte die App. Der Supervisor lädt dabei das zur App-Version passende Multi-Arch-Image -`git.jensneuber.de/jens/solaredgeoptimizers:0.1.0`. +`git.jensneuber.de/jens/solaredgeoptimizers:0.2.0`. ## Sicherheit und Betrieb diff --git a/solaredge-optimizer-data/config.yaml b/solaredge-optimizer-data/config.yaml index 0cf6e2a..2e9bcc6 100644 --- a/solaredge-optimizer-data/config.yaml +++ b/solaredge-optimizer-data/config.yaml @@ -1,5 +1,5 @@ name: "SolarEdge Optimizer Data" -version: "0.1.0" +version: "0.2.0" slug: "solaredge_optimizer_data" description: >- Cached daily energy and current power data for every SolarEdge optimizer. @@ -7,6 +7,9 @@ arch: - aarch64 - amd64 image: git.jensneuber.de/jens/solaredgeoptimizers +discovery: + - solaredge_optimizers +hassio_api: true startup: application boot: auto init: false diff --git a/solaredge-optimizer-data/dist/server.js b/solaredge-optimizer-data/dist/server.js index 746de7a..65048f8 100644 --- a/solaredge-optimizer-data/dist/server.js +++ b/solaredge-optimizer-data/dist/server.js @@ -1,5 +1,5 @@ // @bun -var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta.require;var $a=x((re,_a)=>{var Ko=/^xn--/,Ho=/[^\0-\x7F]/,Qo=/[\x2E\u3002\uFF0E\uFF61]/g,Wo={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},q=Math.floor,ia=String.fromCharCode;function F(a){throw RangeError(Wo[a])}function Yo(a,o){let i=[],s=a.length;while(s--)i[s]=o(a[s]);return i}function Oa(a,o){let i=a.split("@"),s="";if(i.length>1)s=i[0]+"@",a=i[1];a=a.replace(Qo,".");let e=a.split("."),n=Yo(e,o).join(".");return s+n}function Aa(a){let o=[],i=0,s=a.length;while(i=55296&&e<=56319&&iString.fromCodePoint(...a),Xo=function(a){if(a>=48&&a<58)return 26+(a-48);if(a>=65&&a<91)return a-65;if(a>=97&&a<123)return a-97;return 36},Sa=function(a,o){return a+22+75*(a<26)-((o!=0)<<5)},Ia=function(a,o,i){let s=0;a=i?q(a/700):a>>1,a+=q(a/o);for(;a>455;s+=36)a=q(a/35);return q(s+36*a/(a+38))},Ra=function(a){let o=[],i=a.length,s=0,e=128,n=72,t=a.lastIndexOf("-");if(t<0)t=0;for(let m=0;m=128)F("not-basic");o.push(a.charCodeAt(m))}for(let m=t>0?t+1:0;m=i)F("invalid-input");let h=Xo(a.charCodeAt(m++));if(h>=36)F("invalid-input");if(h>q((2147483647-s)/p))F("overflow");s+=h*p;let b=r<=n?1:r>=n+26?26:r-n;if(hq(2147483647/l))F("overflow");p*=l}let c=o.length+1;if(n=Ia(s-u,c,u==0),q(s/c)>2147483647-e)F("overflow");e+=q(s/c),s%=c,o.splice(s++,0,e)}return String.fromCodePoint(...o)},qa=function(a){let o=[];a=Aa(a);let i=a.length,s=128,e=0,n=72;for(let u of a)if(u<128)o.push(ia(u));let t=o.length,m=t;if(t)o.push("-");while(m=s&&pq((2147483647-e)/c))F("overflow");e+=(u-s)*c,s=u;for(let p of a){if(p2147483647)F("overflow");if(p===s){let r=e;for(let h=36;;h+=36){let b=h<=n?1:h>=n+26?26:h-n;if(r{Ta.exports=function(o,i){if(i=i.split(":")[0],o=+o,!o)return!1;switch(i){case"http":case"ws":return o!==80;case"https":case"wss":return o!==443;case"ftp":return o!==21;case"gopher":return o!==70;case"file":return!1}return o!==0}});var Fa=x((ss)=>{var Uo=Object.prototype.hasOwnProperty,Co;function Da(a){try{return decodeURIComponent(a.replace(/\+/g," "))}catch(o){return null}}function Ga(a){try{return encodeURIComponent(a)}catch(o){return null}}function as(a){var o=/([^=?#&]+)=?([^&]*)/g,i={},s;while(s=o.exec(a)){var e=Da(s[1]),n=Da(s[2]);if(e===null||n===null||e in i)continue;i[e]=n}return i}function os(a,o){o=o||"";var i=[],s,e;if(typeof o!=="string")o="?";for(e in a)if(Uo.call(a,e)){if(s=a[e],!s&&(s===null||s===Co||isNaN(s)))s="";if(e=Ga(e),s=Ga(s),e===null||s===null)continue;i.push(e+"="+s)}return i.length?o+i.join("&"):""}ss.stringify=os;ss.parse=as});var Ya=x((ce,Wa)=>{var Za=La(),J=Fa(),ns=/^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/,Ba=/[\n\r\t]/g,ts=/^[A-Za-z][A-Za-z0-9+-.]*:\/\//,Ka=/:\d+$/,ms=/^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i,rs=/^[a-zA-Z]:/;function na(a){return(a?a:"").toString().replace(ns,"")}var ea=[["#","hash"],["?","query"],function(o,i){return _(i.protocol)?o.replace(/\\/g,"/"):o},["/","pathname"],["@","auth",1],[NaN,"host",void 0,1,1],[/:(\d*)$/,"port",void 0,1],[NaN,"hostname",void 0,1,1]],Na={hash:1,query:1};function Ha(a){var o;if(typeof window<"u")o=window;else if(typeof global<"u")o=global;else if(typeof self<"u")o=self;else o={};var i=o.location||{};a=a||i;var s={},e=typeof a,n;if(a.protocol==="blob:")s=new $(unescape(a.pathname),{});else if(e==="string"){s=new $(a,{});for(n in Na)delete s[n]}else if(e==="object"){for(n in a){if(n in Na)continue;s[n]=a[n]}if(s.slashes===void 0)s.slashes=ts.test(a.href)}return s}function _(a){return a==="file:"||a==="ftp:"||a==="http:"||a==="https:"||a==="ws:"||a==="wss:"}function Qa(a,o){a=na(a),a=a.replace(Ba,""),o=o||{};var i=ms.exec(a),s=i[1]?i[1].toLowerCase():"",e=!!i[2],n=!!i[3],t=0,m;if(e)if(n)m=i[2]+i[3]+i[4],t=i[2].length+i[3].length;else m=i[2]+i[4],t=i[2].length;else if(n)m=i[3]+i[4],t=i[3].length;else m=i[4];if(s==="file:"){if(t>=2)m=m.slice(2)}else if(_(s))m=i[4];else if(s){if(e)m=m.slice(2)}else if(t>=2&&_(o.protocol))m=i[4];return{protocol:s,slashes:e||_(s),slashesCount:t,rest:m}}function ps(a,o){if(a==="")return o;var i=(o||"/").split("/").slice(0,-1).concat(a.split("/")),s=i.length,e=i[s-1],n=!1,t=0;while(s--)if(i[s]===".")i.splice(s,1);else if(i[s]==="..")i.splice(s,1),t++;else if(t){if(s===0)n=!0;i.splice(s,1),t--}if(n)i.unshift("");if(e==="."||e==="..")i.push("");return i.join("/")}function $(a,o,i){if(a=na(a),a=a.replace(Ba,""),!(this instanceof $))return new $(a,o,i);var s,e,n,t,m,u,c=ea.slice(),p=typeof o,r=this,h=0;if(p!=="object"&&p!=="string")i=o,o=null;if(i&&typeof i!=="function")i=J.parse;if(o=Ha(o),e=Qa(a||"",o),s=!e.protocol&&!e.slashes,r.slashes=e.slashes||s&&o.slashes,r.protocol=e.protocol||o.protocol||"",a=e.rest,e.protocol==="file:"&&(e.slashesCount!==2||rs.test(a))||!e.slashes&&(e.protocol||e.slashesCount<2||!_(r.protocol)))c[3]=[/(.*)/,"pathname"];for(;h{Object.defineProperties(Pa,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});function gs(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var ta,Ma;function ks(){if(Ma)return ta;Ma=1;let a=2147483647,o=36,i=1,s=26,e=38,n=700,t=72,m=128,u="-",c=/^xn--/,p=/[^\0-\x7F]/,r=/[\x2E\u3002\uFF0E\uFF61]/g,h={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},b=o-i,l=Math.floor,w=String.fromCharCode;function v(g){throw RangeError(h[g])}function K(g,d){let y=[],j=g.length;for(;j--;)y[j]=d(g[j]);return y}function ba(g,d){let y=g.split("@"),j="";y.length>1&&(j=y[0]+"@",g=y[1]),g=g.replace(r,".");let f=g.split("."),E=K(f,d).join(".");return j+E}function ya(g){let d=[],y=0,j=g.length;for(;y=55296&&f<=56319&&yString.fromCodePoint(...g),Zo=function(g){return g>=48&&g<58?26+(g-48):g>=65&&g<91?g-65:g>=97&&g<123?g-97:o},fa=function(g,d){return g+22+75*(g<26)-((d!=0)<<5)},za=function(g,d,y){let j=0;for(g=y?l(g/n):g>>1,g+=l(g/d);g>b*s>>1;j+=o)g=l(g/b);return l(j+(b+1)*g/(g+e))},va=function(g){let d=[],y=g.length,j=0,f=m,E=t,T=g.lastIndexOf(u);T<0&&(T=0);for(let S=0;S=128&&v("not-basic"),d.push(g.charCodeAt(S));for(let S=T>0?T+1:0;S=y&&v("invalid-input");let D=Zo(g.charCodeAt(S++));D>=o&&v("invalid-input"),D>l((a-j)/A)&&v("overflow"),j+=D*A;let Z=L<=E?i:L>=E+s?s:L-E;if(Dl(a/Q)&&v("overflow"),A*=Q}let G=d.length+1;E=za(j-O,G,O==0),l(j/G)>a-f&&v("overflow"),f+=l(j/G),j%=G,d.splice(j++,0,f)}return String.fromCodePoint(...d)},Ea=function(g){let d=[];g=ya(g);let y=g.length,j=m,f=0,E=t;for(let O of g)O<128&&d.push(w(O));let T=d.length,S=T;for(T&&d.push(u);S=j&&Al((a-f)/G)&&v("overflow"),f+=(O-j)*G,j=O;for(let A of g)if(Aa&&v("overflow"),A===j){let L=f;for(let D=o;;D+=o){let Z=D<=E?i:D>=E+s?s:D-E;if(L{let i=o.replace(/^(\*\.|\!)/,""),s=W.toASCII(i),e=o.charAt(0);if(a.has(s))throw Error(`Multiple rules found for ${o} (${s})`);return a.set(s,{rule:o,suffix:i,punySuffix:s,wildcard:e==="*",exception:e==="!"}),a},new Map),js=(a)=>{let o=W.toASCII(a).split(".");for(let i=0;i{let o=W.toASCII(a);if(o.length<1)return"DOMAIN_TOO_SHORT";if(o.length>255)return"DOMAIN_TOO_LONG";let i=o.split("."),s;for(let e=0;e63)return"LABEL_TOO_LONG";if(s.charAt(0)==="-")return"LABEL_STARTS_WITH_DASH";if(s.charAt(s.length-1)==="-")return"LABEL_ENDS_WITH_DASH";if(!/^[a-z0-9\-_]+$/.test(s))return"LABEL_INVALID_CHARS"}},P=(a)=>{if(typeof a!="string")throw TypeError("Domain name must be a string.");let o=a.slice(0).toLowerCase();o.charAt(o.length-1)==="."&&(o=o.slice(0,o.length-1));let i=ws(o);if(i)return{input:a,error:{message:Xa[i],code:i}};let s={input:a,tld:null,sld:null,domain:null,subdomain:null,listed:!1},e=o.split(".");if(e[e.length-1]==="local")return s;let n=()=>(/xn--/.test(o)&&(s.domain&&(s.domain=W.toASCII(s.domain)),s.subdomain&&(s.subdomain=W.toASCII(s.subdomain))),s),t=js(o);if(!t)return e.length<2?s:(s.tld=e.pop(),s.sld=e.pop(),s.domain=[s.sld,s.tld].join("."),e.length&&(s.subdomain=e.pop()),n());s.listed=!0;let m=t.suffix.split("."),u=e.slice(0,e.length-m.length);return t.exception&&u.push(m.shift()),s.tld=m.join("."),!u.length||(t.wildcard&&(m.unshift(u.pop()),s.tld=m.join(".")),!u.length)||(s.sld=u.pop(),s.domain=[s.sld,s.tld].join("."),u.length&&(s.subdomain=u.join("."))),n()},Va=(a)=>a&&P(a).domain||null,Ja=(a)=>{let o=P(a);return!!(o.domain&&o.listed)},bs={parse:P,get:Va,isValid:Ja};Pa.default=bs;Pa.errorCodes=Xa;Pa.get=Va;Pa.isValid=Ja;Pa.parse=P});var ma=x((As)=>{/*! +var S=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Zo=import.meta.require;var Ta=S((ge,qa)=>{var Qo=/^xn--/,Wo=/[^\0-\x7F]/,Yo=/[\x2E\u3002\uFF0E\uFF61]/g,Mo={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},_=Math.floor,ia=String.fromCharCode;function F(a){throw RangeError(Mo[a])}function Xo(a,o){let i=[],s=a.length;while(s--)i[s]=o(a[s]);return i}function Oa(a,o){let i=a.split("@"),s="";if(i.length>1)s=i[0]+"@",a=i[1];a=a.replace(Yo,".");let e=a.split("."),n=Xo(e,o).join(".");return s+n}function Aa(a){let o=[],i=0,s=a.length;while(i=55296&&e<=56319&&iString.fromCodePoint(...a),Po=function(a){if(a>=48&&a<58)return 26+(a-48);if(a>=65&&a<91)return a-65;if(a>=97&&a<123)return a-97;return 36},xa=function(a,o){return a+22+75*(a<26)-((o!=0)<<5)},Ia=function(a,o,i){let s=0;a=i?_(a/700):a>>1,a+=_(a/o);for(;a>455;s+=36)a=_(a/35);return _(s+36*a/(a+38))},Ra=function(a){let o=[],i=a.length,s=0,e=128,n=72,t=a.lastIndexOf("-");if(t<0)t=0;for(let m=0;m=128)F("not-basic");o.push(a.charCodeAt(m))}for(let m=t>0?t+1:0;m=i)F("invalid-input");let h=Po(a.charCodeAt(m++));if(h>=36)F("invalid-input");if(h>_((2147483647-s)/p))F("overflow");s+=h*p;let b=r<=n?1:r>=n+26?26:r-n;if(h_(2147483647/l))F("overflow");p*=l}let c=o.length+1;if(n=Ia(s-u,c,u==0),_(s/c)>2147483647-e)F("overflow");e+=_(s/c),s%=c,o.splice(s++,0,e)}return String.fromCodePoint(...o)},_a=function(a){let o=[];a=Aa(a);let i=a.length,s=128,e=0,n=72;for(let u of a)if(u<128)o.push(ia(u));let t=o.length,m=t;if(t)o.push("-");while(m=s&&p_((2147483647-e)/c))F("overflow");e+=(u-s)*c,s=u;for(let p of a){if(p2147483647)F("overflow");if(p===s){let r=e;for(let h=36;;h+=36){let b=h<=n?1:h>=n+26?26:h-n;if(r{$a.exports=function(o,i){if(i=i.split(":")[0],o=+o,!o)return!1;switch(i){case"http":case"ws":return o!==80;case"https":case"wss":return o!==443;case"ftp":return o!==21;case"gopher":return o!==70;case"file":return!1}return o!==0}});var Fa=S((es)=>{var as=Object.prototype.hasOwnProperty,os;function Da(a){try{return decodeURIComponent(a.replace(/\+/g," "))}catch(o){return null}}function Ga(a){try{return encodeURIComponent(a)}catch(o){return null}}function ss(a){var o=/([^=?#&]+)=?([^&]*)/g,i={},s;while(s=o.exec(a)){var e=Da(s[1]),n=Da(s[2]);if(e===null||n===null||e in i)continue;i[e]=n}return i}function is(a,o){o=o||"";var i=[],s,e;if(typeof o!=="string")o="?";for(e in a)if(as.call(a,e)){if(s=a[e],!s&&(s===null||s===os||isNaN(s)))s="";if(e=Ga(e),s=Ga(s),e===null||s===null)continue;i.push(e+"="+s)}return i.length?o+i.join("&"):""}es.stringify=is;es.parse=ss});var Ya=S((he,Wa)=>{var Ha=La(),P=Fa(),ms=/^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/,Ba=/[\n\r\t]/g,rs=/^[A-Za-z][A-Za-z0-9+-.]*:\/\//,Ka=/:\d+$/,ps=/^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i,us=/^[a-zA-Z]:/;function na(a){return(a?a:"").toString().replace(ms,"")}var ea=[["#","hash"],["?","query"],function(o,i){return q(i.protocol)?o.replace(/\\/g,"/"):o},["/","pathname"],["@","auth",1],[NaN,"host",void 0,1,1],[/:(\d*)$/,"port",void 0,1],[NaN,"hostname",void 0,1,1]],Na={hash:1,query:1};function Za(a){var o;if(typeof window<"u")o=window;else if(typeof global<"u")o=global;else if(typeof self<"u")o=self;else o={};var i=o.location||{};a=a||i;var s={},e=typeof a,n;if(a.protocol==="blob:")s=new T(unescape(a.pathname),{});else if(e==="string"){s=new T(a,{});for(n in Na)delete s[n]}else if(e==="object"){for(n in a){if(n in Na)continue;s[n]=a[n]}if(s.slashes===void 0)s.slashes=rs.test(a.href)}return s}function q(a){return a==="file:"||a==="ftp:"||a==="http:"||a==="https:"||a==="ws:"||a==="wss:"}function Qa(a,o){a=na(a),a=a.replace(Ba,""),o=o||{};var i=ps.exec(a),s=i[1]?i[1].toLowerCase():"",e=!!i[2],n=!!i[3],t=0,m;if(e)if(n)m=i[2]+i[3]+i[4],t=i[2].length+i[3].length;else m=i[2]+i[4],t=i[2].length;else if(n)m=i[3]+i[4],t=i[3].length;else m=i[4];if(s==="file:"){if(t>=2)m=m.slice(2)}else if(q(s))m=i[4];else if(s){if(e)m=m.slice(2)}else if(t>=2&&q(o.protocol))m=i[4];return{protocol:s,slashes:e||q(s),slashesCount:t,rest:m}}function cs(a,o){if(a==="")return o;var i=(o||"/").split("/").slice(0,-1).concat(a.split("/")),s=i.length,e=i[s-1],n=!1,t=0;while(s--)if(i[s]===".")i.splice(s,1);else if(i[s]==="..")i.splice(s,1),t++;else if(t){if(s===0)n=!0;i.splice(s,1),t--}if(n)i.unshift("");if(e==="."||e==="..")i.push("");return i.join("/")}function T(a,o,i){if(a=na(a),a=a.replace(Ba,""),!(this instanceof T))return new T(a,o,i);var s,e,n,t,m,u,c=ea.slice(),p=typeof o,r=this,h=0;if(p!=="object"&&p!=="string")i=o,o=null;if(i&&typeof i!=="function")i=P.parse;if(o=Za(o),e=Qa(a||"",o),s=!e.protocol&&!e.slashes,r.slashes=e.slashes||s&&o.slashes,r.protocol=e.protocol||o.protocol||"",a=e.rest,e.protocol==="file:"&&(e.slashesCount!==2||us.test(a))||!e.slashes&&(e.protocol||e.slashesCount<2||!q(r.protocol)))c[3]=[/(.*)/,"pathname"];for(;h{Object.defineProperties(Ja,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});function ls(a){return a&&a.__esModule&&Object.prototype.hasOwnProperty.call(a,"default")?a.default:a}var ta,Ma;function hs(){if(Ma)return ta;Ma=1;let a=2147483647,o=36,i=1,s=26,e=38,n=700,t=72,m=128,u="-",c=/^xn--/,p=/[^\0-\x7F]/,r=/[\x2E\u3002\uFF0E\uFF61]/g,h={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},b=o-i,l=Math.floor,w=String.fromCharCode;function v(g){throw RangeError(h[g])}function Z(g,d){let y=[],j=g.length;for(;j--;)y[j]=d(g[j]);return y}function ba(g,d){let y=g.split("@"),j="";y.length>1&&(j=y[0]+"@",g=y[1]),g=g.replace(r,".");let f=g.split("."),E=Z(f,d).join(".");return j+E}function ya(g){let d=[],y=0,j=g.length;for(;y=55296&&f<=56319&&yString.fromCodePoint(...g),Ko=function(g){return g>=48&&g<58?26+(g-48):g>=65&&g<91?g-65:g>=97&&g<123?g-97:o},fa=function(g,d){return g+22+75*(g<26)-((d!=0)<<5)},za=function(g,d,y){let j=0;for(g=y?l(g/n):g>>1,g+=l(g/d);g>b*s>>1;j+=o)g=l(g/b);return l(j+(b+1)*g/(g+e))},va=function(g){let d=[],y=g.length,j=0,f=m,E=t,$=g.lastIndexOf(u);$<0&&($=0);for(let x=0;x<$;++x)g.charCodeAt(x)>=128&&v("not-basic"),d.push(g.charCodeAt(x));for(let x=$>0?$+1:0;x=y&&v("invalid-input");let D=Ko(g.charCodeAt(x++));D>=o&&v("invalid-input"),D>l((a-j)/A)&&v("overflow"),j+=D*A;let H=L<=E?i:L>=E+s?s:L-E;if(Dl(a/W)&&v("overflow"),A*=W}let G=d.length+1;E=za(j-O,G,O==0),l(j/G)>a-f&&v("overflow"),f+=l(j/G),j%=G,d.splice(j++,0,f)}return String.fromCodePoint(...d)},Ea=function(g){let d=[];g=ya(g);let y=g.length,j=m,f=0,E=t;for(let O of g)O<128&&d.push(w(O));let $=d.length,x=$;for($&&d.push(u);x=j&&Al((a-f)/G)&&v("overflow"),f+=(O-j)*G,j=O;for(let A of g)if(Aa&&v("overflow"),A===j){let L=f;for(let D=o;;D+=o){let H=D<=E?i:D>=E+s?s:D-E;if(L{let i=o.replace(/^(\*\.|\!)/,""),s=Y.toASCII(i),e=o.charAt(0);if(a.has(s))throw Error(`Multiple rules found for ${o} (${s})`);return a.set(s,{rule:o,suffix:i,punySuffix:s,wildcard:e==="*",exception:e==="!"}),a},new Map),bs=(a)=>{let o=Y.toASCII(a).split(".");for(let i=0;i{let o=Y.toASCII(a);if(o.length<1)return"DOMAIN_TOO_SHORT";if(o.length>255)return"DOMAIN_TOO_LONG";let i=o.split("."),s;for(let e=0;e63)return"LABEL_TOO_LONG";if(s.charAt(0)==="-")return"LABEL_STARTS_WITH_DASH";if(s.charAt(s.length-1)==="-")return"LABEL_ENDS_WITH_DASH";if(!/^[a-z0-9\-_]+$/.test(s))return"LABEL_INVALID_CHARS"}},J=(a)=>{if(typeof a!="string")throw TypeError("Domain name must be a string.");let o=a.slice(0).toLowerCase();o.charAt(o.length-1)==="."&&(o=o.slice(0,o.length-1));let i=ys(o);if(i)return{input:a,error:{message:Xa[i],code:i}};let s={input:a,tld:null,sld:null,domain:null,subdomain:null,listed:!1},e=o.split(".");if(e[e.length-1]==="local")return s;let n=()=>(/xn--/.test(o)&&(s.domain&&(s.domain=Y.toASCII(s.domain)),s.subdomain&&(s.subdomain=Y.toASCII(s.subdomain))),s),t=bs(o);if(!t)return e.length<2?s:(s.tld=e.pop(),s.sld=e.pop(),s.domain=[s.sld,s.tld].join("."),e.length&&(s.subdomain=e.pop()),n());s.listed=!0;let m=t.suffix.split("."),u=e.slice(0,e.length-m.length);return t.exception&&u.push(m.shift()),s.tld=m.join("."),!u.length||(t.wildcard&&(m.unshift(u.pop()),s.tld=m.join(".")),!u.length)||(s.sld=u.pop(),s.domain=[s.sld,s.tld].join("."),u.length&&(s.subdomain=u.join("."))),n()},Va=(a)=>a&&J(a).domain||null,Pa=(a)=>{let o=J(a);return!!(o.domain&&o.listed)},fs={parse:J,get:Va,isValid:Pa};Ja.default=fs;Ja.errorCodes=Xa;Ja.get=Va;Ja.isValid=Pa;Ja.parse=J});var ma=S((Rs)=>{/*! * Copyright (c) 2018, Salesforce.com, Inc. * All rights reserved. * @@ -28,7 +28,7 @@ var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - */var xs=Ua(),Ca=["local","example","invalid","localhost","test"],Ss=["localhost","invalid"];function Os(a,o={}){let i=a.split("."),s=i[i.length-1],e=!!o.allowSpecialUseDomain,n=!!o.ignoreError;if(e&&Ca.includes(s)){if(i.length>1)return`${i[i.length-2]}.${s}`;else if(Ss.includes(s))return`${s}`}if(!n&&Ca.includes(s))throw Error(`Cookie has domain set to the public suffix "${s}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`);return xs.get(a)}As.getPublicSuffix=Os});var ra=x((Rs)=>{/*! + */var Os=Ua(),Ca=["local","example","invalid","localhost","test"],As=["localhost","invalid"];function Is(a,o={}){let i=a.split("."),s=i[i.length-1],e=!!o.allowSpecialUseDomain,n=!!o.ignoreError;if(e&&Ca.includes(s)){if(i.length>1)return`${i[i.length-2]}.${s}`;else if(As.includes(s))return`${s}`}if(!n&&Ca.includes(s))throw Error(`Cookie has domain set to the public suffix "${s}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`);return Os.get(a)}Rs.getPublicSuffix=Is});var ra=S((qs)=>{/*! * Copyright (c) 2015, Salesforce.com, Inc. * All rights reserved. * @@ -57,7 +57,7 @@ var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - */class ao{constructor(){this.synchronous=!1}findCookie(a,o,i,s){throw Error("findCookie is not implemented")}findCookies(a,o,i,s){throw Error("findCookies is not implemented")}putCookie(a,o){throw Error("putCookie is not implemented")}updateCookie(a,o,i){throw Error("updateCookie is not implemented")}removeCookie(a,o,i,s){throw Error("removeCookie is not implemented")}removeCookies(a,o,i){throw Error("removeCookies is not implemented")}removeAllCookies(a){throw Error("removeAllCookies is not implemented")}getAllCookies(a){throw Error("getAllCookies is not implemented (therefore jar cannot be serialized)")}}Rs.Store=ao});var pa=x((_s)=>{_s.fromCallback=function(a){return Object.defineProperty(function(){if(typeof arguments[arguments.length-1]==="function")a.apply(this,arguments);else return new Promise((o,i)=>{arguments[arguments.length]=(s,e)=>{if(s)return i(s);o(e)},arguments.length++,a.apply(this,arguments)})},"name",{value:a.name})};_s.fromPromise=function(a){return Object.defineProperty(function(){let o=arguments[arguments.length-1];if(typeof o!=="function")return a.apply(this,arguments);else delete arguments[arguments.length-1],arguments.length--,a.apply(this,arguments).then((i)=>o(null,i),o)},"name",{value:a.name})}});var ua=x((Gs)=>{/*! + */class ao{constructor(){this.synchronous=!1}findCookie(a,o,i,s){throw Error("findCookie is not implemented")}findCookies(a,o,i,s){throw Error("findCookies is not implemented")}putCookie(a,o){throw Error("putCookie is not implemented")}updateCookie(a,o,i){throw Error("updateCookie is not implemented")}removeCookie(a,o,i,s){throw Error("removeCookie is not implemented")}removeCookies(a,o,i){throw Error("removeCookies is not implemented")}removeAllCookies(a){throw Error("removeAllCookies is not implemented")}getAllCookies(a){throw Error("getAllCookies is not implemented (therefore jar cannot be serialized)")}}qs.Store=ao});var pa=S(($s)=>{$s.fromCallback=function(a){return Object.defineProperty(function(){if(typeof arguments[arguments.length-1]==="function")a.apply(this,arguments);else return new Promise((o,i)=>{arguments[arguments.length]=(s,e)=>{if(s)return i(s);o(e)},arguments.length++,a.apply(this,arguments)})},"name",{value:a.name})};$s.fromPromise=function(a){return Object.defineProperty(function(){let o=arguments[arguments.length-1];if(typeof o!=="function")return a.apply(this,arguments);else delete arguments[arguments.length-1],arguments.length--,a.apply(this,arguments).then((i)=>o(null,i),o)},"name",{value:a.name})}});var ua=S((Ns)=>{/*! * Copyright (c) 2015, Salesforce.com, Inc. * All rights reserved. * @@ -86,7 +86,7 @@ var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - */var Ls=ma();function Ds(a,o){let i=Ls.getPublicSuffix(a,{allowSpecialUseDomain:o});if(!i)return null;if(i==a)return[a];if(a.slice(-1)==".")a=a.slice(0,-1);let e=a.slice(0,-(i.length+1)).split(".").reverse(),n=i,t=[n];while(e.length)n=`${e.shift()}.${n}`,t.push(n);return t}Gs.permuteDomain=Ds});var ca=x((Zs)=>{/*! + */var Gs=ma();function Fs(a,o){let i=Gs.getPublicSuffix(a,{allowSpecialUseDomain:o});if(!i)return null;if(i==a)return[a];if(a.slice(-1)==".")a=a.slice(0,-1);let e=a.slice(0,-(i.length+1)).split(".").reverse(),n=i,t=[n];while(e.length)n=`${e.shift()}.${n}`,t.push(n);return t}Ns.permuteDomain=Fs});var ca=S((Ks)=>{/*! * Copyright (c) 2015, Salesforce.com, Inc. * All rights reserved. * @@ -115,7 +115,7 @@ var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - */function Ns(a,o){if(o===a)return!0;if(a.indexOf(o)===0){if(o.substr(-1)==="/")return!0;if(a.substr(o.length,1)==="/")return!0}return!1}Zs.pathMatch=Ns});var ga=x((Qs)=>{function oo(){try{return Bo("util")}catch(a){return null}}function Ks(){return Symbol.for("nodejs.util.inspect.custom")}function Hs(a){let i=(a.requireUtil||oo)();return i?i.inspect.custom:null}Qs.getUtilInspect=function(o,i={}){let e=(i.requireUtil||oo)();return function(t,m,u){return e?e.inspect(t,m,u):o(t)}};Qs.getCustomInspectSymbol=function(o={}){return(o.lookupCustomInspectSymbol||Ks)()||Hs(o)}});var io=x((oi)=>{/*! + */function Bs(a,o){if(o===a)return!0;if(a.indexOf(o)===0){if(o.substr(-1)==="/")return!0;if(a.substr(o.length,1)==="/")return!0}return!1}Ks.pathMatch=Bs});var ga=S((Ys)=>{function oo(){try{return Zo("util")}catch(a){return null}}function Qs(){return Symbol.for("nodejs.util.inspect.custom")}function Ws(a){let i=(a.requireUtil||oo)();return i?i.inspect.custom:null}Ys.getUtilInspect=function(o,i={}){let e=(i.requireUtil||oo)();return function(t,m,u){return e?e.inspect(t,m,u):o(t)}};Ys.getCustomInspectSymbol=function(o={}){return(o.lookupCustomInspectSymbol||Qs)()||Ws(o)}});var io=S((ii)=>{/*! * Copyright (c) 2015, Salesforce.com, Inc. * All rights reserved. * @@ -144,13 +144,13 @@ var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - */var{fromCallback:Ms}=pa(),Xs=ra().Store,Vs=ua().permuteDomain,Js=ca().pathMatch,{getCustomInspectSymbol:Ps,getUtilInspect:Us}=ga();class U extends Xs{constructor(){super();this.synchronous=!0,this.idx=Object.create(null);let a=Ps();if(a)this[a]=this.inspect}inspect(){return`{ idx: ${{inspect:Us(so)}.inspect(this.idx,!1,2)} }`}findCookie(a,o,i,s){if(!this.idx[a])return s(null,void 0);if(!this.idx[a][o])return s(null,void 0);return s(null,this.idx[a][o][i]||null)}findCookies(a,o,i,s){let e=[];if(typeof i==="function")s=i,i=!0;if(!a)return s(null,[]);let n;if(!o)n=function(c){for(let p in c){let r=c[p];for(let h in r)e.push(r[h])}};else n=function(c){Object.keys(c).forEach((p)=>{if(Js(o,p)){let r=c[p];for(let h in r)e.push(r[h])}})};let t=Vs(a,i)||[a],m=this.idx;t.forEach((u)=>{let c=m[u];if(!c)return;n(c)}),s(null,e)}putCookie(a,o){if(!this.idx[a.domain])this.idx[a.domain]=Object.create(null);if(!this.idx[a.domain][a.path])this.idx[a.domain][a.path]=Object.create(null);this.idx[a.domain][a.path][a.key]=a,o(null)}updateCookie(a,o,i){this.putCookie(o,i)}removeCookie(a,o,i,s){if(this.idx[a]&&this.idx[a][o]&&this.idx[a][o][i])delete this.idx[a][o][i];s(null)}removeCookies(a,o,i){if(this.idx[a])if(o)delete this.idx[a][o];else delete this.idx[a];return i(null)}removeAllCookies(a){return this.idx=Object.create(null),a(null)}getAllCookies(a){let o=[],i=this.idx;Object.keys(i).forEach((e)=>{Object.keys(i[e]).forEach((t)=>{Object.keys(i[e][t]).forEach((u)=>{if(u!==null)o.push(i[e][t][u])})})}),o.sort((e,n)=>{return(e.creationIndex||0)-(n.creationIndex||0)}),a(null,o)}}["findCookie","findCookies","putCookie","updateCookie","removeCookie","removeCookies","removeAllCookies","getAllCookies"].forEach((a)=>{U.prototype[a]=Ms(U.prototype[a])});oi.MemoryCookieStore=U;function so(a){let o=Object.keys(a);if(o.length===0)return"[Object: null prototype] {}";let i=`[Object: null prototype] { -`;return Object.keys(a).forEach((s,e)=>{if(i+=Cs(s,a[s]),e{if(s+=ai(e,o[e]),n{if(Us(o,p)){let r=c[p];for(let h in r)e.push(r[h])}})};let t=Js(a,i)||[a],m=this.idx;t.forEach((u)=>{let c=m[u];if(!c)return;n(c)}),s(null,e)}putCookie(a,o){if(!this.idx[a.domain])this.idx[a.domain]=Object.create(null);if(!this.idx[a.domain][a.path])this.idx[a.domain][a.path]=Object.create(null);this.idx[a.domain][a.path][a.key]=a,o(null)}updateCookie(a,o,i){this.putCookie(o,i)}removeCookie(a,o,i,s){if(this.idx[a]&&this.idx[a][o]&&this.idx[a][o][i])delete this.idx[a][o][i];s(null)}removeCookies(a,o,i){if(this.idx[a])if(o)delete this.idx[a][o];else delete this.idx[a];return i(null)}removeAllCookies(a){return this.idx=Object.create(null),a(null)}getAllCookies(a){let o=[],i=this.idx;Object.keys(i).forEach((e)=>{Object.keys(i[e]).forEach((t)=>{Object.keys(i[e][t]).forEach((u)=>{if(u!==null)o.push(i[e][t][u])})})}),o.sort((e,n)=>{return(e.creationIndex||0)-(n.creationIndex||0)}),a(null,o)}}["findCookie","findCookies","putCookie","updateCookie","removeCookie","removeCookies","removeAllCookies","getAllCookies"].forEach((a)=>{U.prototype[a]=Vs(U.prototype[a])});ii.MemoryCookieStore=U;function so(a){let o=Object.keys(a);if(o.length===0)return"[Object: null prototype] {}";let i=`[Object: null prototype] { +`;return Object.keys(a).forEach((s,e)=>{if(i+=oi(s,a[s]),e{if(s+=si(e,o[e]),n{let m=o[e];if(s+=` ${e}: ${m.inspect()}`,n{var ei=Object.prototype.toString;function eo(a){return typeof a==="function"}function no(a){return to(a)&&a!==""}function ni(a){return mo(a,Date)&&ri(a.getTime())}function ti(a){return a===""||a instanceof String&&a.toString()===""}function to(a){return typeof a==="string"||a instanceof String}function ka(a){return ei.call(a)==="[object Object]"}function mo(a,o){try{return a instanceof o}catch(i){return!1}}function mi(a){return no(a)||ka(a)&&"hostname"in a&&"pathname"in a&&"protocol"in a||mo(a,URL)}function ri(a){return typeof a==="number"&&a%1===0}function pi(a,o,i){if(!eo(o))i=o,o=null;if(!ka(i))i={Error:"Failed Check"};if(!a)if(o)o(new C(i));else throw new C(i)}class C extends Error{constructor(...a){super(...a)}}ui.ParameterError=C;ui.isFunction=eo;ui.isNonEmptyString=no;ui.isDate=ni;ui.isEmptyString=ti;ui.isString=to;ui.isObject=ka;ui.isUrlStringOrObject=mi;ui.validate=pi});var uo=x((fe,po)=>{po.exports="4.1.4"});/*! +`}),s+=" }",s}ii.inspectFallback=so});var ro=S((gi)=>{var ti=Object.prototype.toString;function eo(a){return typeof a==="function"}function no(a){return to(a)&&a!==""}function mi(a){return mo(a,Date)&&ui(a.getTime())}function ri(a){return a===""||a instanceof String&&a.toString()===""}function to(a){return typeof a==="string"||a instanceof String}function ka(a){return ti.call(a)==="[object Object]"}function mo(a,o){try{return a instanceof o}catch(i){return!1}}function pi(a){return no(a)||ka(a)&&"hostname"in a&&"pathname"in a&&"protocol"in a||mo(a,URL)}function ui(a){return typeof a==="number"&&a%1===0}function ci(a,o,i){if(!eo(o))i=o,o=null;if(!ka(i))i={Error:"Failed Check"};if(!a)if(o)o(new C(i));else throw new C(i)}class C extends Error{constructor(...a){super(...a)}}gi.ParameterError=C;gi.isFunction=eo;gi.isNonEmptyString=no;gi.isDate=mi;gi.isEmptyString=ri;gi.isString=to;gi.isObject=ka;gi.isUrlStringOrObject=pi;gi.validate=ci});var uo=S((Se,po)=>{po.exports="4.1.4"});/*! * Copyright (c) 2015-2020, Salesforce.com, Inc. * All rights reserved. * @@ -179,8 +179,8 @@ var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. - */var co=$a(),yi=Ya(),ha=ma(),fi=ra().Store,zi=io().MemoryCookieStore,vi=ca().pathMatch,k=ro(),Ei=uo(),{fromCallback:yo}=pa(),{getCustomInspectSymbol:xi}=ga(),Si=/^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/,go=/[\x00-\x1F]/,ko=[` -`,"\r","\x00"],Oi=/[\x20-\x3A\x3C-\x7E]+/,Ai=/[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/,Ii={jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11};function lo(a){k.validate(k.isNonEmptyString(a),a);let o=String(a).toLowerCase();if(o==="none"||o==="lax"||o==="strict")return o;else return null}var H=Object.freeze({SILENT:"silent",STRICT:"strict",DISABLED:"unsafe-disabled"}),Ri=/(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/,qi=` + */var co=Ta(),zi=Ya(),ha=ma(),vi=ra().Store,Ei=io().MemoryCookieStore,Si=ca().pathMatch,k=ro(),xi=uo(),{fromCallback:yo}=pa(),{getCustomInspectSymbol:Oi}=ga(),Ai=/^[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+$/,go=/[\x00-\x1F]/,ko=[` +`,"\r","\x00"],Ii=/[\x20-\x3A\x3C-\x7E]+/,Ri=/[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/,_i={jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11};function lo(a){k.validate(k.isNonEmptyString(a),a);let o=String(a).toLowerCase();if(o==="none"||o==="lax"||o==="strict")return o;else return null}var Q=Object.freeze({SILENT:"silent",STRICT:"strict",DISABLED:"unsafe-disabled"}),qi=/(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/,Ti=` \\[?(?: (?:[a-fA-F\\d]{1,4}:){7}(?:[a-fA-F\\d]{1,4}|:)| (?:[a-fA-F\\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|:[a-fA-F\\d]{1,4}|:)| @@ -191,4 +191,4 @@ var x=(a,o)=>()=>(o||a((o={exports:{}}).exports,o),o.exports);var Bo=import.meta (?:[a-fA-F\\d]{1,4}:){1}(?:(?::[a-fA-F\\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,6}|:)| (?::(?:(?::[a-fA-F\\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}|(?::[a-fA-F\\d]{1,4}){1,7}|:)) )(?:%[0-9a-zA-Z]{1,})?\\]? -`.replace(/\s*\/\/.*$/gm,"").replace(/\n/g,"").trim(),fo=new RegExp(`^${qi}$`);function la(a,o,i,s){let e=0;while(e=58)break;e++}if(ei)return null;if(!s&&e!=a.length)return null;return parseInt(a.substr(0,e),10)}function _i(a){let o=a.split(":"),i=[0,0,0];if(o.length!==3)return null;for(let s=0;s<3;s++){let e=s==2,n=la(o[s],1,2,e);if(n===null)return null;i[s]=n}return i}function $i(a){a=String(a).substr(0,3).toLowerCase();let o=Ii[a];return o>=0?o:null}function aa(a){if(!a)return;let o=a.split(Ai);if(!o)return;let i=null,s=null,e=null,n=null,t=null,m=null;for(let u=0;u=70&&m<=99)m+=1900;else if(m>=0&&m<=69)m+=2000}}}if(n===null||t===null||m===null||e===null||n<1||n>31||m<1601||i>23||s>59||e>59)return;return new Date(Date.UTC(m,t,n,i,s,e))}function Ti(a){return k.validate(k.isDate(a),a),a.toUTCString()}function M(a){if(a==null)return null;if(a=a.trim().replace(/^\./,""),fo.test(a))a=a.replace("[","").replace("]","");if(co&&/[^\u0001-\u007f]/.test(a))a=co.toASCII(a);return a.toLowerCase()}function ho(a,o,i){if(a==null||o==null)return null;if(i!==!1)a=M(a),o=M(o);if(a==o)return!0;let s=a.lastIndexOf(o);if(s<=0)return!1;if(a.length!==o.length+s)return!1;if(a.substr(s-1,1)!==".")return!1;if(Ri.test(a))return!1;return!0}function Li(a){if(!a||a.substr(0,1)!=="/")return"/";if(a==="/")return a;let o=a.lastIndexOf("/");if(o===0)return"/";return a.slice(0,o)}function Di(a){if(k.isEmptyString(a))return a;for(let o=0;o{a+=`; ${o}`});return a}TTL(a){if(this.maxAge!=null)return this.maxAge<=0?0:this.maxAge*1000;let o=this.expires;if(o!=1/0){if(!(o instanceof Date))o=aa(o)||1/0;if(o==1/0)return 1/0;return o.getTime()-(a||Date.now())}return 1/0}expiryTime(a){if(this.maxAge!=null){let o=a||this.creation||new Date,i=this.maxAge<=0?-1/0:this.maxAge*1000;return o.getTime()+i}if(this.expires==1/0)return 1/0;return this.expires.getTime()}expiryDate(a){let o=this.expiryTime(a);if(o==1/0)return new Date(2147483647000);else if(o==-1/0)return new Date(0);else return new Date(o)}isPersistent(){return this.maxAge!=null||this.expires!=1/0}canonicalizedDomain(){if(this.domain==null)return null;return M(this.domain)}cdomain(){return this.canonicalizedDomain()}}z.cookiesCreated=0;z.parse=Fi;z.fromJSON=da;z.serializableProperties=Object.keys(Y);z.sameSiteLevel={strict:3,lax:2,none:1};z.sameSiteCanonical={strict:"Strict",lax:"Lax"};function bo(a){if(a!=null){let o=a.toLowerCase();switch(o){case H.STRICT:case H.SILENT:case H.DISABLED:return o}}return H.SILENT}class I{constructor(a,o={rejectPublicSuffixes:!0}){if(typeof o==="boolean")o={rejectPublicSuffixes:o};k.validate(k.isObject(o),o),this.rejectPublicSuffixes=o.rejectPublicSuffixes,this.enableLooseMode=!!o.looseMode,this.allowSpecialUseDomain=typeof o.allowSpecialUseDomain==="boolean"?o.allowSpecialUseDomain:!0,this.store=a||new zi,this.prefixSecurity=bo(o.prefixSecurity),this._cloneSync=N("clone"),this._importCookiesSync=N("_importCookies"),this.getCookiesSync=N("getCookies"),this.getCookieStringSync=N("getCookieString"),this.getSetCookieStringsSync=N("getSetCookieStrings"),this.removeAllCookiesSync=N("removeAllCookies"),this.setCookieSync=N("setCookie"),this.serializeSync=N("serialize")}setCookie(a,o,i,s){k.validate(k.isUrlStringOrObject(o),s,i);let e;if(k.isFunction(o))return s=o,s(Error("No URL was specified"));let n=wo(o);if(k.isFunction(i))s=i,i={};if(k.validate(k.isFunction(s),s),!k.isNonEmptyString(a)&&!k.isObject(a)&&a instanceof String&&a.length==0)return s(null);let t=M(n.hostname),m=i.loose||this.enableLooseMode,u=null;if(i.sameSiteContext){if(u=lo(i.sameSiteContext),!u)return s(Error('Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"'))}if(typeof a==="string"||a instanceof String){if(a=z.parse(a,{loose:m}),!a)return e=Error("Cookie failed to parse"),s(i.ignoreError?null:e)}else if(!(a instanceof z))return e=Error("First argument to setCookie must be a Cookie object or string"),s(i.ignoreError?null:e);let c=i.now||new Date;if(this.rejectPublicSuffixes&&a.domain){if(ha.getPublicSuffix(a.cdomain(),{allowSpecialUseDomain:this.allowSpecialUseDomain,ignoreError:i.ignoreError})==null&&!fo.test(a.domain))return e=Error("Cookie has domain set to a public suffix"),s(i.ignoreError?null:e)}if(a.domain){if(!ho(t,a.cdomain(),!1))return e=Error(`Cookie not in this host's domain. Cookie:${a.cdomain()} Request:${t}`),s(i.ignoreError?null:e);if(a.hostOnly==null)a.hostOnly=!1}else a.hostOnly=!0,a.domain=t;if(!a.path||a.path[0]!=="/")a.path=Li(n.pathname),a.pathIsDefault=!0;if(i.http===!1&&a.httpOnly)return e=Error("Cookie is HttpOnly and this isn't an HTTP API"),s(i.ignoreError?null:e);if(a.sameSite!=="none"&&a.sameSite!==void 0&&u){if(u==="none")return e=Error("Cookie is SameSite but this is a cross-origin request"),s(i.ignoreError?null:e)}let p=this.prefixSecurity===H.SILENT;if(this.prefixSecurity!==H.DISABLED){let l=!1,w;if(!Ni(a))l=!0,w="Cookie has __Secure prefix but Secure attribute is not set";else if(!Zi(a))l=!0,w="Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'";if(l)return s(i.ignoreError||p?null:Error(w))}let h=this.store;if(!h.updateCookie)h.updateCookie=function(l,w,v){this.putCookie(w,v)};function b(l,w){if(l)return s(l);let v=function(K){if(K)return s(K);else s(null,a)};if(w){if(i.http===!1&&w.httpOnly)return l=Error("old Cookie is HttpOnly and this isn't an HTTP API"),s(i.ignoreError?null:l);a.creation=w.creation,a.creationIndex=w.creationIndex,a.lastAccessed=c,h.updateCookie(w,a,v)}else a.creation=a.lastAccessed=c,h.putCookie(a,v)}h.findCookie(a.domain,a.path,a.key,b)}getCookies(a,o,i){k.validate(k.isUrlStringOrObject(a),i,a);let s=wo(a);if(k.isFunction(o))i=o,o={};k.validate(k.isObject(o),i,o),k.validate(k.isFunction(i),i);let e=M(s.hostname),n=s.pathname||"/",t=o.secure;if(t==null&&s.protocol&&(s.protocol=="https:"||s.protocol=="wss:"))t=!0;let m=0;if(o.sameSiteContext){let l=lo(o.sameSiteContext);if(m=z.sameSiteLevel[l],!m)return i(Error('Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"'))}let u=o.http;if(u==null)u=!0;let c=o.now||Date.now(),p=o.expire!==!1,r=!!o.allPaths,h=this.store;function b(l){if(l.hostOnly){if(l.domain!=e)return!1}else if(!ho(e,l.domain,!1))return!1;if(!r&&!vi(n,l.path))return!1;if(l.secure&&!t)return!1;if(l.httpOnly&&!u)return!1;if(m){if(z.sameSiteLevel[l.sameSite||"none"]>m)return!1}if(p&&l.expiryTime()<=c)return h.removeCookie(l.domain,l.path,l.key,()=>{}),!1;return!0}h.findCookies(e,r?null:n,this.allowSpecialUseDomain,(l,w)=>{if(l)return i(l);if(w=w.filter(b),o.sort!==!1)w=w.sort(jo);let v=new Date;for(let K of w)K.lastAccessed=v;i(null,w)})}getCookieString(...a){let o=a.pop();k.validate(k.isFunction(o),o);let i=function(s,e){if(s)o(s);else o(null,e.sort(jo).map((n)=>n.cookieString()).join("; "))};a.push(i),this.getCookies.apply(this,a)}getSetCookieStrings(...a){let o=a.pop();k.validate(k.isFunction(o),o);let i=function(s,e){if(s)o(s);else o(null,e.map((n)=>{return n.toString()}))};a.push(i),this.getCookies.apply(this,a)}serialize(a){k.validate(k.isFunction(a),a);let o=this.store.constructor.name;if(k.isObject(o))o=null;let i={version:`tough-cookie@${Ei}`,storeType:o,rejectPublicSuffixes:!!this.rejectPublicSuffixes,enableLooseMode:!!this.enableLooseMode,allowSpecialUseDomain:!!this.allowSpecialUseDomain,prefixSecurity:bo(this.prefixSecurity),cookies:[]};if(!(this.store.getAllCookies&&typeof this.store.getAllCookies==="function"))return a(Error("store does not support getAllCookies and cannot be serialized"));this.store.getAllCookies((s,e)=>{if(s)return a(s);return i.cookies=e.map((n)=>{return n=n instanceof z?n.toJSON():n,delete n.creationIndex,n}),a(null,i)})}toJSON(){return this.serializeSync()}_importCookies(a,o){let i=a.cookies;if(!i||!Array.isArray(i))return o(Error("serialized jar has no cookies array"));i=i.slice();let s=(e)=>{if(e)return o(e);if(!i.length)return o(e,this);let n;try{n=da(i.shift())}catch(t){return o(t)}if(n===null)return s(null);this.store.putCookie(n,s)};s()}clone(a,o){if(arguments.length===1)o=a,a=null;this.serialize((i,s)=>{if(i)return o(i);I.deserialize(s,a,o)})}cloneSync(a){if(arguments.length===0)return this._cloneSync();if(!a.synchronous)throw Error("CookieJar clone destination store is not synchronous; use async API instead.");return this._cloneSync(a)}removeAllCookies(a){k.validate(k.isFunction(a),a);let o=this.store;if(typeof o.removeAllCookies==="function"&&o.removeAllCookies!==fi.prototype.removeAllCookies)return o.removeAllCookies(a);o.getAllCookies((i,s)=>{if(i)return a(i);if(s.length===0)return a(null);let e=0,n=[];function t(m){if(m)n.push(m);if(e++,e===s.length)return a(n.length?n[0]:null)}s.forEach((m)=>{o.removeCookie(m.domain,m.path,m.key,t)})})}static deserialize(a,o,i){if(arguments.length!==3)i=o,o=null;k.validate(k.isFunction(i),i);let s;if(typeof a==="string"){if(s=zo(a),s instanceof Error)return i(s)}else s=a;let e=new I(o,{rejectPublicSuffixes:s.rejectPublicSuffixes,looseMode:s.enableLooseMode,allowSpecialUseDomain:s.allowSpecialUseDomain,prefixSecurity:s.prefixSecurity});e._importCookies(s,(n)=>{if(n)return i(n);i(null,e)})}static deserializeSync(a,o){let i=typeof a==="string"?JSON.parse(a):a,s=new I(o,{rejectPublicSuffixes:i.rejectPublicSuffixes,looseMode:i.enableLooseMode});if(!s.store.synchronous)throw Error("CookieJar store is not synchronous; use async API instead.");return s._importCookiesSync(i),s}}I.fromJSON=I.deserializeSync;["_importCookies","clone","getCookies","getCookieString","getSetCookieStrings","removeAllCookies","serialize","setCookie"].forEach((a)=>{I.prototype[a]=yo(I.prototype[a])});I.deserialize=yo(I.deserialize);function N(a){return function(...o){if(!this.store.synchronous)throw Error("CookieJar store is not synchronous; use async API instead.");let i,s;if(this[a](...o,(e,n)=>{i=e,s=n}),i)throw i;return s}}var ja=I;var Bi=ha.getPublicSuffix;var Ki=ua().permuteDomain;var Hi=k.ParameterError;var Ro="https://monitoring.solaredge.com",qo="https://login.solaredge.com";var vo="ugfnsujd3384sshcjehaphlh3",Eo="/mfe/auth/callback",Qi=30000,xo=10;class R extends Error{status;code;constructor(a,o){super(a,{cause:o.cause});this.name="SolarEdgePortalAuthError",this.status=o.status??0,this.code=o.code}}class B extends Error{status;constructor(a,o){super(a,{cause:o.cause});this.name="SolarEdgePortalApiError",this.status=o.status}}class oa{username;password;defaultSiteId;portalBaseUrl;loginBaseUrl;fetchImplementation;cookieJar;accessToken;accessTokenExpiresAt=0;authenticationPromise;constructor({username:a,password:o,siteId:i,portalBaseUrl:s=Ro,loginBaseUrl:e=qo,fetch:n=globalThis.fetch,cookieJar:t=new ja}){if(!a.trim())throw Error("SolarEdge portal username must not be empty");if(!o)throw Error("SolarEdge portal password must not be empty");this.username=a,this.password=o,this.defaultSiteId=i,this.portalBaseUrl=s.replace(/\/+$/,""),this.loginBaseUrl=e.replace(/\/+$/,""),this.fetchImplementation=n,this.cookieJar=t}async authenticate(){await this.runSingleAuthentication(async()=>{await this.login()})}async getLogicalLayout(a,o){return this.get(`/services/layout/logical/generic/v2/site/${this.resolveSiteId(a)}`,{"include-optimizers":!0},o)}async listOptimizerSerials(a,o){return(await this.listOptimizerMappings(a,o)).map((s)=>s.serial)}async listOptimizerMappings(a,o){let i=await this.getLogicalLayout(a,o);return Ji(i)}async getOptimizerInformation(a,o){let i=Oo(a);return this.requestJson("/services/layout/information/optimizers",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i),signal:o?.signal})}async getOptimizerEnergy({siteId:a,startDate:o,endDate:i,optimizerSerials:s,chartTimeUnit:e="hours"},n){let t=Oo(s);return this.get(`/services/layout/energy-graph/site/${this.resolveSiteId(a)}/optimizers`,{"chart-time-unit":e,"start-date":Ao(o,"startDate"),"end-date":Ao(i,"endDate"),"optimizer-serials":t.join(",")},n)}async get(a,o={},i={}){let s=this.buildPortalUrl(a);for(let[e,n]of Object.entries(o))if(n!==void 0)s.searchParams.set(e,String(n));return this.requestJson(s,{headers:{Accept:"application/json"},signal:i.signal})}async requestJson(a,o,i=!0){let s=a instanceof URL?a:this.buildPortalUrl(a);if(s.origin!==new URL(this.portalBaseUrl).origin)throw new B("SolarEdge portal requests must use the configured portal origin",{status:0});await this.ensureAuthenticated();let e=new Headers(o.headers);e.set("Accept","application/json"),e.set("Authorization",`Bearer ${this.accessToken}`);let n;try{n=await this.requestWithCookies(s,{...o,headers:e})}catch(t){throw new B("SolarEdge portal request failed",{status:0,cause:t})}if(n.response.status===401&&i)return await this.reauthenticate(),this.requestJson(s,o,!1);if(!n.response.ok)throw new B(`SolarEdge portal request failed (${n.response.status})`,{status:n.response.status});try{return await n.response.json()}catch(t){throw new B("SolarEdge portal returned invalid JSON",{status:n.response.status,cause:t})}}async ensureAuthenticated(){if(this.accessToken&&Date.now(){if(this.accessToken&&await this.refreshSession())return;await this.login()})}async reauthenticate(){this.accessTokenExpiresAt=0,await this.runSingleAuthentication(async()=>{if(await this.refreshSession())return;await this.login()})}async runSingleAuthentication(a){if(!this.authenticationPromise)this.authenticationPromise=a().finally(()=>{this.authenticationPromise=void 0});await this.authenticationPromise}async login(){let a=Wi(),o=await Yi(a),i=`${this.portalBaseUrl}${Eo}`,s=new URL("/login",`${this.loginBaseUrl}/`);s.search=new URLSearchParams({lang:"en",response_type:"code",client_id:vo,scope:"email openid",redirect_uri:i,code_challenge_method:"S256",code_challenge:o}).toString();let e;try{e=await this.requestWithCookies(s,{headers:{Accept:"text/html"}})}catch(p){throw new R("Could not load the SolarEdge login page",{code:"LOGIN_PAGE_FAILED",cause:p})}if(!e.response.ok)throw new R(`Could not load the SolarEdge login page (${e.response.status})`,{code:"LOGIN_PAGE_FAILED",status:e.response.status});let n=Xi(await e.response.text(),"csrf");if(!n)throw new R("SolarEdge login page did not contain a CSRF token",{code:"LOGIN_PAGE_FAILED"});let t=new URLSearchParams({csrf:n,username:this.username,password:this.password,cognitoAsfData:""}),m;try{m=await this.requestWithCookies(s,{method:"POST",headers:{Accept:"text/html","Content-Type":"application/x-www-form-urlencoded",Origin:this.loginBaseUrl,Referer:s.toString()},body:t.toString()},(p)=>p.origin===new URL(this.portalBaseUrl).origin&&p.pathname===Eo)}catch(p){throw new R("SolarEdge login request failed",{code:"AUTH_FAILED",cause:p})}let u=m.url.searchParams.get("code");if(!u){let p=await m.response.text().catch(()=>""),r=/name=["']code["']|mfa|verify(?:Email|Sms|Totp|Password)/i.test(p);throw new R(r?"SolarEdge requires an additional authentication challenge":"SolarEdge portal authentication failed",{code:r?"AUTH_CHALLENGE_REQUIRED":"AUTH_FAILED",status:m.response.status})}let c=await this.exchangeAuthorizationCode(u,a,i);await this.createPortalSession(c),this.setAccessToken(c.access_token,c.expires_in)}async exchangeAuthorizationCode(a,o,i){let s=new URL("/oauth2/token",`${this.loginBaseUrl}/`),e;try{e=await this.requestWithCookies(s,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:vo,redirect_uri:i,code:a,code_verifier:o}).toString()})}catch(t){throw new R("SolarEdge token exchange request failed",{code:"TOKEN_EXCHANGE_FAILED",cause:t})}if(!e.response.ok)throw new R(`SolarEdge token exchange failed (${e.response.status})`,{code:"TOKEN_EXCHANGE_FAILED",status:e.response.status});let n=await Io(e.response);if(!n?.access_token||!Number.isFinite(n.expires_in)||n.expires_in<=0)throw new R("SolarEdge token exchange returned an invalid response",{code:"TOKEN_EXCHANGE_FAILED",status:e.response.status});return n}async createPortalSession(a){let o=new URL("/services/auth/token?legacy=false",`${this.portalBaseUrl}/`),i=await this.requestWithCookies(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id_token:a.id_token,access_token:a.access_token,refresh_token:a.refresh_token,expires_in:a.expires_in,token_type:a.token_type})});if(!i.response.ok)throw new R(`SolarEdge portal session creation failed (${i.response.status})`,{code:"SESSION_CREATION_FAILED",status:i.response.status})}async refreshSession(){let a=new URL("/services/auth/refresh",`${this.portalBaseUrl}/`);try{let o=await this.requestWithCookies(a,{method:"POST",headers:{Accept:"application/json"}});if(!o.response.ok)return!1;let i=await Io(o.response),s=i?.accessToken?.token,e=i?.accessToken?.expiresIn;if(!s||!e||!Number.isFinite(e))return!1;return this.setAccessToken(s,e),!0}catch{return!1}}setAccessToken(a,o){this.accessToken=a,this.accessTokenExpiresAt=Date.now()+o*1000}async requestWithCookies(a,o={},i){let s=new URL(String(a)),e=(o.method??"GET").toUpperCase(),n=o.body,t=new Headers(o.headers);for(let m=0;m<=xo;m+=1){let u=await this.cookieJar.getCookieString(s.toString());if(u)t.set("Cookie",u);else t.delete("Cookie");let c=await this.fetchImplementation(s,{...o,method:e,body:n,headers:t,redirect:"manual"});await this.storeResponseCookies(c,s);let p=c.headers.get("location");if(!p||!Pi(c.status))return{response:c,url:s};let r=new URL(p,s);if(i?.(r))return{response:c,url:r};if(m===xo)throw Error("SolarEdge request exceeded the redirect limit");if(c.status===303||(c.status===301||c.status===302)&&e!=="GET"&&e!=="HEAD")e="GET",n=void 0,t=new Headers(t),t.delete("Content-Type"),t.delete("Content-Length");s=r}throw Error("SolarEdge request exceeded the redirect limit")}async storeResponseCookies(a,o){let i=a.headers,s=i.getSetCookie?.()??[];if(s.length===0){let e=i.get("set-cookie");if(e)s.push(e)}for(let e of s)await this.cookieJar.setCookie(e,o.toString())}resolveSiteId(a){let o=a??this.defaultSiteId;if(o===void 0)throw Error("Missing SolarEdge siteId; pass it to the request or configure SOLAREDGE_SITE_ID");let i=String(o).trim();if(!i)throw Error("SolarEdge siteId must not be empty");return encodeURIComponent(i)}buildPortalUrl(a){if(/^[a-z][a-z\d+.-]*:/i.test(a))throw new B("SolarEdge portal request paths must be relative",{status:0});return new URL(a.replace(/^\/+/,""),`${this.portalBaseUrl}/`)}}function Wi(){let o=crypto.getRandomValues(new Uint8Array(64)),i="";for(let s of o)i+="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"[s%66];return i}async function Yi(a){let o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(a));return Mi(new Uint8Array(o))}function Mi(a){let o="";for(let i of a)o+=String.fromCharCode(i);return btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Xi(a,o){let i=a.match(/]*>/gi)??[];for(let s of i)if(So(s,"name")===o)return So(s,"value");return}function So(a,o){let i=new RegExp(`${o}\\s*=\\s*(["'])(.*?)\\1`,"i"),s=a.match(i)?.[2];return s===void 0?void 0:Vi(s)}function Vi(a){return a.replace(/"/g,'"').replace(/'|'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&")}function Oo(a){let o=[...new Set(a.map((i)=>i.trim()))].filter(Boolean);if(o.length===0)throw Error("At least one SolarEdge optimizer serial is required");return o}function Ji(a){let o=[],i=new Set,s=(e,n)=>{if(!e||typeof e!=="object")return;if(Array.isArray(e)){for(let c of e)s(c,n);return}let t=e,m={displayOrder:typeof t.displayOrder==="string"?t.displayOrder:void 0,order:typeof t.order==="number"?t.order:void 0},u={...n};if(t.type==="INVERTER")u.inverter=m;else if(t.type==="STRING")u.string=m;else if(t.type==="OPTIMIZER"&&typeof t.serial==="string"){let c=t.serial.trim();if(c&&!i.has(c))i.add(c),o.push({serial:c,inverterId:u.inverter?.displayOrder,stringId:u.string?.displayOrder,optimizerId:m.displayOrder,inverterOrder:u.inverter?.order,stringOrder:u.string?.order,optimizerOrder:m.order})}if(Array.isArray(t.children)){s(t.children,u);return}if(t.type===void 0)for(let c of Object.values(t))s(c,u)};return s(a,{}),o}function Ao(a,o){if(!a.trim())throw Error(`SolarEdge ${o} must not be empty`);return a}function Pi(a){return a===301||a===302||a===303||a===307||a===308}async function Io(a){try{return await a.json()}catch{return}}class wa{loadSnapshot;pollIntervalMs;snapshot;nextRefreshAt;lastError;refreshPromise;timer;constructor({loadSnapshot:a,pollIntervalMs:o}){if(!Number.isFinite(o)||o<=0)throw Error("pollIntervalMs must be positive");this.loadSnapshot=a,this.pollIntervalMs=o}start(){this.refresh()}stop(){if(this.timer)clearTimeout(this.timer),this.timer=void 0}async refresh(){if(this.refreshPromise)return this.refreshPromise;return this.stop(),this.refreshPromise=this.performRefresh().finally(()=>{this.refreshPromise=void 0,this.scheduleNextRefresh()}),this.refreshPromise}getPayload(){if(!this.snapshot||!this.nextRefreshAt)return;return{status:this.lastError?"stale":this.snapshot.failedOptimizerCount>0?"partial":"ok",...this.snapshot,nextRefreshAt:this.nextRefreshAt,...this.lastError?{lastError:this.lastError}:{}}}getLastError(){return this.lastError}async performRefresh(){try{this.snapshot=await this.loadSnapshot(),this.lastError=void 0}catch(a){this.lastError=Ui(a)}}scheduleNextRefresh(){this.nextRefreshAt=new Date(Date.now()+this.pollIntervalMs).toISOString(),this.timer=setTimeout(()=>void this.refresh(),this.pollIntervalMs)}}function Ui(a){if(!a||typeof a!=="object")return"Unknown error";let o=a;return[o.name?String(o.name):"Error",o.code===void 0?void 0:`code=${String(o.code)}`,o.status===void 0?void 0:`status=${String(o.status)}`].filter(Boolean).join(" ")}async function $o({reader:a,siteId:o,timeZone:i,concurrency:s=3,now:e=new Date}){let n=Ci(e,i),t=await a.listOptimizerMappings(o);if(t.length===0)throw Error("SolarEdge returned no optimizer mappings");let m=await a.getOptimizerInformation(t.map((p)=>p.serial)),u=await ae(t,s,async(p)=>{let r=m.serialToLiveData[p.serial],h={serial:p.serial,inverterId:p.inverterId??null,stringId:p.stringId??null,optimizerId:p.optimizerId??null,currentPowerW:_o(r?.power_W),lastMeasurement:r?.lastMeasurement??null};try{let b=await a.getOptimizerEnergy({siteId:o,startDate:n,endDate:n,optimizerSerials:[p.serial],chartTimeUnit:"hours"});return{...h,dailyEnergyWh:_o(b.totalEnergy)}}catch(b){return{...h,dailyEnergyWh:null,error:oe(b)}}}),c=u.filter((p)=>p.dailyEnergyWh!==null);return{siteId:o,date:n,timeZone:i,fetchedAt:new Date().toISOString(),optimizerCount:u.length,successfulOptimizerCount:c.length,failedOptimizerCount:u.length-c.length,totalDailyEnergyWh:c.reduce((p,r)=>p+(r.dailyEnergyWh??0),0),totalCurrentPowerW:u.reduce((p,r)=>p+(r.currentPowerW??0),0),optimizers:u}}function Ci(a,o){let i=new Intl.DateTimeFormat("en",{timeZone:o,year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(a),s=Object.fromEntries(i.map((e)=>[e.type,e.value]));return`${s.year}-${s.month}-${s.day}`}async function ae(a,o,i){if(!Number.isInteger(o)||o<1)throw Error("concurrency must be a positive integer");let s=Array(a.length),e=0,n=async()=>{while(e1440)throw Error("poll_interval_minutes must be an integer from 10 to 1440");return{username:i,password:s,siteId:e,pollIntervalMinutes:n}}async function Do(a=process.env.SOLAREDGE_OPTIONS_PATH??"/data/options.json"){let o=Bun.file(a);if(!await o.exists())throw Error(`App options file does not exist: ${a}`);return se(await o.json())}async function Go({fetchImplementation:a=globalThis.fetch,supervisorToken:o=process.env.SUPERVISOR_TOKEN}={}){if(!o){let e=process.env.TZ;if(e)return To(e);throw Error("SUPERVISOR_TOKEN is missing and TZ is not configured")}let i=await a("http://supervisor/info",{headers:{Authorization:`Bearer ${o}`},signal:AbortSignal.timeout(1e4)});if(!i.ok)throw Error(`Could not read Home Assistant time zone (${i.status})`);let s=await i.json();return To(sa(s.data?.timezone,"Supervisor timezone"))}function sa(a,o,i=!0){if(typeof a!=="string")throw Error(`${o} must be a string`);let s=i?a.trim():a;if(!s)throw Error(`${o} must not be empty`);return s}function To(a){try{return new Intl.DateTimeFormat("en",{timeZone:a}).format(),a}catch{throw Error(`Invalid Home Assistant time zone: ${a}`)}}function Fo(a){return(o)=>{if(o.method!=="GET")return X({error:"Method not allowed"},405,{Allow:"GET"});let i=new URL(o.url).pathname.replace(/\/+$/,"")||"/";if(i==="/health"){let s=a.getPayload();return X(s?{status:s.status,fetchedAt:s.fetchedAt,nextRefreshAt:s.nextRefreshAt,optimizerCount:s.optimizerCount,failedOptimizerCount:s.failedOptimizerCount,...s.lastError?{lastError:s.lastError}:{}}:{status:a.getLastError()?"error":"starting",...a.getLastError()?{lastError:a.getLastError()}:{}},s?200:503)}if(i==="/"||i==="/api/optimizers"){let s=a.getPayload();return s?X(s):X({status:a.getLastError()?"error":"starting",message:"No optimizer data is available yet",...a.getLastError()?{lastError:a.getLastError()}:{}},503)}return X({error:"Not found"},404)}}function X(a,o=200,i={}){return Response.json(a,{status:o,headers:{"Cache-Control":"no-store",...i}})}async function ie(){let a=await Do(),o=await Go(),i=new oa({username:a.username,password:a.password,siteId:a.siteId}),s=new wa({pollIntervalMs:a.pollIntervalMinutes*60000,loadSnapshot:async()=>{V("info",`Refreshing optimizer data for site ${a.siteId}`);let m=await $o({reader:i,siteId:a.siteId,timeZone:o});return V(m.failedOptimizerCount===0?"info":"warning",`Refresh finished: ${m.successfulOptimizerCount}/${m.optimizerCount} optimizer energy values`),m}}),e=ee(process.env.PORT),n=Bun.serve({hostname:"0.0.0.0",port:e,fetch:Fo(s)}),t=()=>{V("info","Stopping SolarEdge Optimizer Data App"),s.stop(),n.stop(!0)};process.once("SIGTERM",t),process.once("SIGINT",t),V("info",`Listening on port ${e}; refresh interval ${a.pollIntervalMinutes} minutes; time zone ${o}`),s.start()}function ee(a){let o=a===void 0?Lo:Number(a);if(!Number.isInteger(o)||o<1||o>65535)throw Error("PORT must be an integer from 1 to 65535");return o}function V(a,o){console.log(`${new Date().toISOString()} [${a}] ${o}`)}if(import.meta.main)try{await ie()}catch(a){V("error",ne(a)),process.exit(1)}function ne(a){if(a instanceof Error)return`${a.name}: ${a.message}`;return"Unknown startup error"}export{ie as main}; +`.replace(/\s*\/\/.*$/gm,"").replace(/\n/g,"").trim(),fo=new RegExp(`^${Ti}$`);function la(a,o,i,s){let e=0;while(e=58)break;e++}if(ei)return null;if(!s&&e!=a.length)return null;return parseInt(a.substr(0,e),10)}function $i(a){let o=a.split(":"),i=[0,0,0];if(o.length!==3)return null;for(let s=0;s<3;s++){let e=s==2,n=la(o[s],1,2,e);if(n===null)return null;i[s]=n}return i}function Li(a){a=String(a).substr(0,3).toLowerCase();let o=_i[a];return o>=0?o:null}function aa(a){if(!a)return;let o=a.split(Ri);if(!o)return;let i=null,s=null,e=null,n=null,t=null,m=null;for(let u=0;u=70&&m<=99)m+=1900;else if(m>=0&&m<=69)m+=2000}}}if(n===null||t===null||m===null||e===null||n<1||n>31||m<1601||i>23||s>59||e>59)return;return new Date(Date.UTC(m,t,n,i,s,e))}function Di(a){return k.validate(k.isDate(a),a),a.toUTCString()}function X(a){if(a==null)return null;if(a=a.trim().replace(/^\./,""),fo.test(a))a=a.replace("[","").replace("]","");if(co&&/[^\u0001-\u007f]/.test(a))a=co.toASCII(a);return a.toLowerCase()}function ho(a,o,i){if(a==null||o==null)return null;if(i!==!1)a=X(a),o=X(o);if(a==o)return!0;let s=a.lastIndexOf(o);if(s<=0)return!1;if(a.length!==o.length+s)return!1;if(a.substr(s-1,1)!==".")return!1;if(qi.test(a))return!1;return!0}function Gi(a){if(!a||a.substr(0,1)!=="/")return"/";if(a==="/")return a;let o=a.lastIndexOf("/");if(o===0)return"/";return a.slice(0,o)}function Fi(a){if(k.isEmptyString(a))return a;for(let o=0;o{a+=`; ${o}`});return a}TTL(a){if(this.maxAge!=null)return this.maxAge<=0?0:this.maxAge*1000;let o=this.expires;if(o!=1/0){if(!(o instanceof Date))o=aa(o)||1/0;if(o==1/0)return 1/0;return o.getTime()-(a||Date.now())}return 1/0}expiryTime(a){if(this.maxAge!=null){let o=a||this.creation||new Date,i=this.maxAge<=0?-1/0:this.maxAge*1000;return o.getTime()+i}if(this.expires==1/0)return 1/0;return this.expires.getTime()}expiryDate(a){let o=this.expiryTime(a);if(o==1/0)return new Date(2147483647000);else if(o==-1/0)return new Date(0);else return new Date(o)}isPersistent(){return this.maxAge!=null||this.expires!=1/0}canonicalizedDomain(){if(this.domain==null)return null;return X(this.domain)}cdomain(){return this.canonicalizedDomain()}}z.cookiesCreated=0;z.parse=Hi;z.fromJSON=da;z.serializableProperties=Object.keys(M);z.sameSiteLevel={strict:3,lax:2,none:1};z.sameSiteCanonical={strict:"Strict",lax:"Lax"};function bo(a){if(a!=null){let o=a.toLowerCase();switch(o){case Q.STRICT:case Q.SILENT:case Q.DISABLED:return o}}return Q.SILENT}class I{constructor(a,o={rejectPublicSuffixes:!0}){if(typeof o==="boolean")o={rejectPublicSuffixes:o};k.validate(k.isObject(o),o),this.rejectPublicSuffixes=o.rejectPublicSuffixes,this.enableLooseMode=!!o.looseMode,this.allowSpecialUseDomain=typeof o.allowSpecialUseDomain==="boolean"?o.allowSpecialUseDomain:!0,this.store=a||new Ei,this.prefixSecurity=bo(o.prefixSecurity),this._cloneSync=N("clone"),this._importCookiesSync=N("_importCookies"),this.getCookiesSync=N("getCookies"),this.getCookieStringSync=N("getCookieString"),this.getSetCookieStringsSync=N("getSetCookieStrings"),this.removeAllCookiesSync=N("removeAllCookies"),this.setCookieSync=N("setCookie"),this.serializeSync=N("serialize")}setCookie(a,o,i,s){k.validate(k.isUrlStringOrObject(o),s,i);let e;if(k.isFunction(o))return s=o,s(Error("No URL was specified"));let n=wo(o);if(k.isFunction(i))s=i,i={};if(k.validate(k.isFunction(s),s),!k.isNonEmptyString(a)&&!k.isObject(a)&&a instanceof String&&a.length==0)return s(null);let t=X(n.hostname),m=i.loose||this.enableLooseMode,u=null;if(i.sameSiteContext){if(u=lo(i.sameSiteContext),!u)return s(Error('Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"'))}if(typeof a==="string"||a instanceof String){if(a=z.parse(a,{loose:m}),!a)return e=Error("Cookie failed to parse"),s(i.ignoreError?null:e)}else if(!(a instanceof z))return e=Error("First argument to setCookie must be a Cookie object or string"),s(i.ignoreError?null:e);let c=i.now||new Date;if(this.rejectPublicSuffixes&&a.domain){if(ha.getPublicSuffix(a.cdomain(),{allowSpecialUseDomain:this.allowSpecialUseDomain,ignoreError:i.ignoreError})==null&&!fo.test(a.domain))return e=Error("Cookie has domain set to a public suffix"),s(i.ignoreError?null:e)}if(a.domain){if(!ho(t,a.cdomain(),!1))return e=Error(`Cookie not in this host's domain. Cookie:${a.cdomain()} Request:${t}`),s(i.ignoreError?null:e);if(a.hostOnly==null)a.hostOnly=!1}else a.hostOnly=!0,a.domain=t;if(!a.path||a.path[0]!=="/")a.path=Gi(n.pathname),a.pathIsDefault=!0;if(i.http===!1&&a.httpOnly)return e=Error("Cookie is HttpOnly and this isn't an HTTP API"),s(i.ignoreError?null:e);if(a.sameSite!=="none"&&a.sameSite!==void 0&&u){if(u==="none")return e=Error("Cookie is SameSite but this is a cross-origin request"),s(i.ignoreError?null:e)}let p=this.prefixSecurity===Q.SILENT;if(this.prefixSecurity!==Q.DISABLED){let l=!1,w;if(!Bi(a))l=!0,w="Cookie has __Secure prefix but Secure attribute is not set";else if(!Ki(a))l=!0,w="Cookie has __Host prefix but either Secure or HostOnly attribute is not set or Path is not '/'";if(l)return s(i.ignoreError||p?null:Error(w))}let h=this.store;if(!h.updateCookie)h.updateCookie=function(l,w,v){this.putCookie(w,v)};function b(l,w){if(l)return s(l);let v=function(Z){if(Z)return s(Z);else s(null,a)};if(w){if(i.http===!1&&w.httpOnly)return l=Error("old Cookie is HttpOnly and this isn't an HTTP API"),s(i.ignoreError?null:l);a.creation=w.creation,a.creationIndex=w.creationIndex,a.lastAccessed=c,h.updateCookie(w,a,v)}else a.creation=a.lastAccessed=c,h.putCookie(a,v)}h.findCookie(a.domain,a.path,a.key,b)}getCookies(a,o,i){k.validate(k.isUrlStringOrObject(a),i,a);let s=wo(a);if(k.isFunction(o))i=o,o={};k.validate(k.isObject(o),i,o),k.validate(k.isFunction(i),i);let e=X(s.hostname),n=s.pathname||"/",t=o.secure;if(t==null&&s.protocol&&(s.protocol=="https:"||s.protocol=="wss:"))t=!0;let m=0;if(o.sameSiteContext){let l=lo(o.sameSiteContext);if(m=z.sameSiteLevel[l],!m)return i(Error('Invalid sameSiteContext option for getCookies(); expected one of "strict", "lax", or "none"'))}let u=o.http;if(u==null)u=!0;let c=o.now||Date.now(),p=o.expire!==!1,r=!!o.allPaths,h=this.store;function b(l){if(l.hostOnly){if(l.domain!=e)return!1}else if(!ho(e,l.domain,!1))return!1;if(!r&&!Si(n,l.path))return!1;if(l.secure&&!t)return!1;if(l.httpOnly&&!u)return!1;if(m){if(z.sameSiteLevel[l.sameSite||"none"]>m)return!1}if(p&&l.expiryTime()<=c)return h.removeCookie(l.domain,l.path,l.key,()=>{}),!1;return!0}h.findCookies(e,r?null:n,this.allowSpecialUseDomain,(l,w)=>{if(l)return i(l);if(w=w.filter(b),o.sort!==!1)w=w.sort(jo);let v=new Date;for(let Z of w)Z.lastAccessed=v;i(null,w)})}getCookieString(...a){let o=a.pop();k.validate(k.isFunction(o),o);let i=function(s,e){if(s)o(s);else o(null,e.sort(jo).map((n)=>n.cookieString()).join("; "))};a.push(i),this.getCookies.apply(this,a)}getSetCookieStrings(...a){let o=a.pop();k.validate(k.isFunction(o),o);let i=function(s,e){if(s)o(s);else o(null,e.map((n)=>{return n.toString()}))};a.push(i),this.getCookies.apply(this,a)}serialize(a){k.validate(k.isFunction(a),a);let o=this.store.constructor.name;if(k.isObject(o))o=null;let i={version:`tough-cookie@${xi}`,storeType:o,rejectPublicSuffixes:!!this.rejectPublicSuffixes,enableLooseMode:!!this.enableLooseMode,allowSpecialUseDomain:!!this.allowSpecialUseDomain,prefixSecurity:bo(this.prefixSecurity),cookies:[]};if(!(this.store.getAllCookies&&typeof this.store.getAllCookies==="function"))return a(Error("store does not support getAllCookies and cannot be serialized"));this.store.getAllCookies((s,e)=>{if(s)return a(s);return i.cookies=e.map((n)=>{return n=n instanceof z?n.toJSON():n,delete n.creationIndex,n}),a(null,i)})}toJSON(){return this.serializeSync()}_importCookies(a,o){let i=a.cookies;if(!i||!Array.isArray(i))return o(Error("serialized jar has no cookies array"));i=i.slice();let s=(e)=>{if(e)return o(e);if(!i.length)return o(e,this);let n;try{n=da(i.shift())}catch(t){return o(t)}if(n===null)return s(null);this.store.putCookie(n,s)};s()}clone(a,o){if(arguments.length===1)o=a,a=null;this.serialize((i,s)=>{if(i)return o(i);I.deserialize(s,a,o)})}cloneSync(a){if(arguments.length===0)return this._cloneSync();if(!a.synchronous)throw Error("CookieJar clone destination store is not synchronous; use async API instead.");return this._cloneSync(a)}removeAllCookies(a){k.validate(k.isFunction(a),a);let o=this.store;if(typeof o.removeAllCookies==="function"&&o.removeAllCookies!==vi.prototype.removeAllCookies)return o.removeAllCookies(a);o.getAllCookies((i,s)=>{if(i)return a(i);if(s.length===0)return a(null);let e=0,n=[];function t(m){if(m)n.push(m);if(e++,e===s.length)return a(n.length?n[0]:null)}s.forEach((m)=>{o.removeCookie(m.domain,m.path,m.key,t)})})}static deserialize(a,o,i){if(arguments.length!==3)i=o,o=null;k.validate(k.isFunction(i),i);let s;if(typeof a==="string"){if(s=zo(a),s instanceof Error)return i(s)}else s=a;let e=new I(o,{rejectPublicSuffixes:s.rejectPublicSuffixes,looseMode:s.enableLooseMode,allowSpecialUseDomain:s.allowSpecialUseDomain,prefixSecurity:s.prefixSecurity});e._importCookies(s,(n)=>{if(n)return i(n);i(null,e)})}static deserializeSync(a,o){let i=typeof a==="string"?JSON.parse(a):a,s=new I(o,{rejectPublicSuffixes:i.rejectPublicSuffixes,looseMode:i.enableLooseMode});if(!s.store.synchronous)throw Error("CookieJar store is not synchronous; use async API instead.");return s._importCookiesSync(i),s}}I.fromJSON=I.deserializeSync;["_importCookies","clone","getCookies","getCookieString","getSetCookieStrings","removeAllCookies","serialize","setCookie"].forEach((a)=>{I.prototype[a]=yo(I.prototype[a])});I.deserialize=yo(I.deserialize);function N(a){return function(...o){if(!this.store.synchronous)throw Error("CookieJar store is not synchronous; use async API instead.");let i,s;if(this[a](...o,(e,n)=>{i=e,s=n}),i)throw i;return s}}var ja=I;var Zi=ha.getPublicSuffix;var Qi=ua().permuteDomain;var Wi=k.ParameterError;var Ro="https://monitoring.solaredge.com",_o="https://login.solaredge.com";var vo="ugfnsujd3384sshcjehaphlh3",Eo="/mfe/auth/callback",Yi=30000,So=10;class R extends Error{status;code;constructor(a,o){super(a,{cause:o.cause});this.name="SolarEdgePortalAuthError",this.status=o.status??0,this.code=o.code}}class B extends Error{status;constructor(a,o){super(a,{cause:o.cause});this.name="SolarEdgePortalApiError",this.status=o.status}}class oa{username;password;defaultSiteId;portalBaseUrl;loginBaseUrl;fetchImplementation;cookieJar;accessToken;accessTokenExpiresAt=0;authenticationPromise;constructor({username:a,password:o,siteId:i,portalBaseUrl:s=Ro,loginBaseUrl:e=_o,fetch:n=globalThis.fetch,cookieJar:t=new ja}){if(!a.trim())throw Error("SolarEdge portal username must not be empty");if(!o)throw Error("SolarEdge portal password must not be empty");this.username=a,this.password=o,this.defaultSiteId=i,this.portalBaseUrl=s.replace(/\/+$/,""),this.loginBaseUrl=e.replace(/\/+$/,""),this.fetchImplementation=n,this.cookieJar=t}async authenticate(){await this.runSingleAuthentication(async()=>{await this.login()})}async getLogicalLayout(a,o){return this.get(`/services/layout/logical/generic/v2/site/${this.resolveSiteId(a)}`,{"include-optimizers":!0},o)}async listOptimizerSerials(a,o){return(await this.listOptimizerMappings(a,o)).map((s)=>s.serial)}async listOptimizerMappings(a,o){let i=await this.getLogicalLayout(a,o);return Ui(i)}async getOptimizerInformation(a,o){let i=Oo(a);return this.requestJson("/services/layout/information/optimizers",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(i),signal:o?.signal})}async getOptimizerEnergy({siteId:a,startDate:o,endDate:i,optimizerSerials:s,chartTimeUnit:e="hours"},n){let t=Oo(s);return this.get(`/services/layout/energy-graph/site/${this.resolveSiteId(a)}/optimizers`,{"chart-time-unit":e,"start-date":Ao(o,"startDate"),"end-date":Ao(i,"endDate"),"optimizer-serials":t.join(",")},n)}async get(a,o={},i={}){let s=this.buildPortalUrl(a);for(let[e,n]of Object.entries(o))if(n!==void 0)s.searchParams.set(e,String(n));return this.requestJson(s,{headers:{Accept:"application/json"},signal:i.signal})}async requestJson(a,o,i=!0){let s=a instanceof URL?a:this.buildPortalUrl(a);if(s.origin!==new URL(this.portalBaseUrl).origin)throw new B("SolarEdge portal requests must use the configured portal origin",{status:0});await this.ensureAuthenticated();let e=new Headers(o.headers);e.set("Accept","application/json"),e.set("Authorization",`Bearer ${this.accessToken}`);let n;try{n=await this.requestWithCookies(s,{...o,headers:e})}catch(t){throw new B("SolarEdge portal request failed",{status:0,cause:t})}if(n.response.status===401&&i)return await this.reauthenticate(),this.requestJson(s,o,!1);if(!n.response.ok)throw new B(`SolarEdge portal request failed (${n.response.status})`,{status:n.response.status});try{return await n.response.json()}catch(t){throw new B("SolarEdge portal returned invalid JSON",{status:n.response.status,cause:t})}}async ensureAuthenticated(){if(this.accessToken&&Date.now(){if(this.accessToken&&await this.refreshSession())return;await this.login()})}async reauthenticate(){this.accessTokenExpiresAt=0,await this.runSingleAuthentication(async()=>{if(await this.refreshSession())return;await this.login()})}async runSingleAuthentication(a){if(!this.authenticationPromise)this.authenticationPromise=a().finally(()=>{this.authenticationPromise=void 0});await this.authenticationPromise}async login(){let a=Mi(),o=await Xi(a),i=`${this.portalBaseUrl}${Eo}`,s=new URL("/login",`${this.loginBaseUrl}/`);s.search=new URLSearchParams({lang:"en",response_type:"code",client_id:vo,scope:"email openid",redirect_uri:i,code_challenge_method:"S256",code_challenge:o}).toString();let e;try{e=await this.requestWithCookies(s,{headers:{Accept:"text/html"}})}catch(p){throw new R("Could not load the SolarEdge login page",{code:"LOGIN_PAGE_FAILED",cause:p})}if(!e.response.ok)throw new R(`Could not load the SolarEdge login page (${e.response.status})`,{code:"LOGIN_PAGE_FAILED",status:e.response.status});let n=Pi(await e.response.text(),"csrf");if(!n)throw new R("SolarEdge login page did not contain a CSRF token",{code:"LOGIN_PAGE_FAILED"});let t=new URLSearchParams({csrf:n,username:this.username,password:this.password,cognitoAsfData:""}),m;try{m=await this.requestWithCookies(s,{method:"POST",headers:{Accept:"text/html","Content-Type":"application/x-www-form-urlencoded",Origin:this.loginBaseUrl,Referer:s.toString()},body:t.toString()},(p)=>p.origin===new URL(this.portalBaseUrl).origin&&p.pathname===Eo)}catch(p){throw new R("SolarEdge login request failed",{code:"AUTH_FAILED",cause:p})}let u=m.url.searchParams.get("code");if(!u){let p=await m.response.text().catch(()=>""),r=/name=["']code["']|mfa|verify(?:Email|Sms|Totp|Password)/i.test(p);throw new R(r?"SolarEdge requires an additional authentication challenge":"SolarEdge portal authentication failed",{code:r?"AUTH_CHALLENGE_REQUIRED":"AUTH_FAILED",status:m.response.status})}let c=await this.exchangeAuthorizationCode(u,a,i);await this.createPortalSession(c),this.setAccessToken(c.access_token,c.expires_in)}async exchangeAuthorizationCode(a,o,i){let s=new URL("/oauth2/token",`${this.loginBaseUrl}/`),e;try{e=await this.requestWithCookies(s,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",client_id:vo,redirect_uri:i,code:a,code_verifier:o}).toString()})}catch(t){throw new R("SolarEdge token exchange request failed",{code:"TOKEN_EXCHANGE_FAILED",cause:t})}if(!e.response.ok)throw new R(`SolarEdge token exchange failed (${e.response.status})`,{code:"TOKEN_EXCHANGE_FAILED",status:e.response.status});let n=await Io(e.response);if(!n?.access_token||!Number.isFinite(n.expires_in)||n.expires_in<=0)throw new R("SolarEdge token exchange returned an invalid response",{code:"TOKEN_EXCHANGE_FAILED",status:e.response.status});return n}async createPortalSession(a){let o=new URL("/services/auth/token?legacy=false",`${this.portalBaseUrl}/`),i=await this.requestWithCookies(o,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({id_token:a.id_token,access_token:a.access_token,refresh_token:a.refresh_token,expires_in:a.expires_in,token_type:a.token_type})});if(!i.response.ok)throw new R(`SolarEdge portal session creation failed (${i.response.status})`,{code:"SESSION_CREATION_FAILED",status:i.response.status})}async refreshSession(){let a=new URL("/services/auth/refresh",`${this.portalBaseUrl}/`);try{let o=await this.requestWithCookies(a,{method:"POST",headers:{Accept:"application/json"}});if(!o.response.ok)return!1;let i=await Io(o.response),s=i?.accessToken?.token,e=i?.accessToken?.expiresIn;if(!s||!e||!Number.isFinite(e))return!1;return this.setAccessToken(s,e),!0}catch{return!1}}setAccessToken(a,o){this.accessToken=a,this.accessTokenExpiresAt=Date.now()+o*1000}async requestWithCookies(a,o={},i){let s=new URL(String(a)),e=(o.method??"GET").toUpperCase(),n=o.body,t=new Headers(o.headers);for(let m=0;m<=So;m+=1){let u=await this.cookieJar.getCookieString(s.toString());if(u)t.set("Cookie",u);else t.delete("Cookie");let c=await this.fetchImplementation(s,{...o,method:e,body:n,headers:t,redirect:"manual"});await this.storeResponseCookies(c,s);let p=c.headers.get("location");if(!p||!Ci(c.status))return{response:c,url:s};let r=new URL(p,s);if(i?.(r))return{response:c,url:r};if(m===So)throw Error("SolarEdge request exceeded the redirect limit");if(c.status===303||(c.status===301||c.status===302)&&e!=="GET"&&e!=="HEAD")e="GET",n=void 0,t=new Headers(t),t.delete("Content-Type"),t.delete("Content-Length");s=r}throw Error("SolarEdge request exceeded the redirect limit")}async storeResponseCookies(a,o){let i=a.headers,s=i.getSetCookie?.()??[];if(s.length===0){let e=i.get("set-cookie");if(e)s.push(e)}for(let e of s)await this.cookieJar.setCookie(e,o.toString())}resolveSiteId(a){let o=a??this.defaultSiteId;if(o===void 0)throw Error("Missing SolarEdge siteId; pass it to the request or configure SOLAREDGE_SITE_ID");let i=String(o).trim();if(!i)throw Error("SolarEdge siteId must not be empty");return encodeURIComponent(i)}buildPortalUrl(a){if(/^[a-z][a-z\d+.-]*:/i.test(a))throw new B("SolarEdge portal request paths must be relative",{status:0});return new URL(a.replace(/^\/+/,""),`${this.portalBaseUrl}/`)}}function Mi(){let o=crypto.getRandomValues(new Uint8Array(64)),i="";for(let s of o)i+="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"[s%66];return i}async function Xi(a){let o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(a));return Vi(new Uint8Array(o))}function Vi(a){let o="";for(let i of a)o+=String.fromCharCode(i);return btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Pi(a,o){let i=a.match(/]*>/gi)??[];for(let s of i)if(xo(s,"name")===o)return xo(s,"value");return}function xo(a,o){let i=new RegExp(`${o}\\s*=\\s*(["'])(.*?)\\1`,"i"),s=a.match(i)?.[2];return s===void 0?void 0:Ji(s)}function Ji(a){return a.replace(/"/g,'"').replace(/'|'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(/&/g,"&")}function Oo(a){let o=[...new Set(a.map((i)=>i.trim()))].filter(Boolean);if(o.length===0)throw Error("At least one SolarEdge optimizer serial is required");return o}function Ui(a){let o=[],i=new Set,s=(e,n)=>{if(!e||typeof e!=="object")return;if(Array.isArray(e)){for(let c of e)s(c,n);return}let t=e,m={displayOrder:typeof t.displayOrder==="string"?t.displayOrder:void 0,order:typeof t.order==="number"?t.order:void 0},u={...n};if(t.type==="INVERTER")u.inverter=m;else if(t.type==="STRING")u.string=m;else if(t.type==="OPTIMIZER"&&typeof t.serial==="string"){let c=t.serial.trim();if(c&&!i.has(c))i.add(c),o.push({serial:c,inverterId:u.inverter?.displayOrder,stringId:u.string?.displayOrder,optimizerId:m.displayOrder,inverterOrder:u.inverter?.order,stringOrder:u.string?.order,optimizerOrder:m.order})}if(Array.isArray(t.children)){s(t.children,u);return}if(t.type===void 0)for(let c of Object.values(t))s(c,u)};return s(a,{}),o}function Ao(a,o){if(!a.trim())throw Error(`SolarEdge ${o} must not be empty`);return a}function Ci(a){return a===301||a===302||a===303||a===307||a===308}async function Io(a){try{return await a.json()}catch{return}}class wa{loadSnapshot;pollIntervalMs;snapshot;nextRefreshAt;lastError;refreshPromise;timer;constructor({loadSnapshot:a,pollIntervalMs:o}){if(!Number.isFinite(o)||o<=0)throw Error("pollIntervalMs must be positive");this.loadSnapshot=a,this.pollIntervalMs=o}start(){this.refresh()}stop(){if(this.timer)clearTimeout(this.timer),this.timer=void 0}async refresh(){if(this.refreshPromise)return this.refreshPromise;return this.stop(),this.refreshPromise=this.performRefresh().finally(()=>{this.refreshPromise=void 0,this.scheduleNextRefresh()}),this.refreshPromise}getPayload(){if(!this.snapshot||!this.nextRefreshAt)return;return{status:this.lastError?"stale":this.snapshot.failedOptimizerCount>0?"partial":"ok",...this.snapshot,nextRefreshAt:this.nextRefreshAt,...this.lastError?{lastError:this.lastError}:{}}}getLastError(){return this.lastError}async performRefresh(){try{this.snapshot=await this.loadSnapshot(),this.lastError=void 0}catch(a){this.lastError=ae(a)}}scheduleNextRefresh(){this.nextRefreshAt=new Date(Date.now()+this.pollIntervalMs).toISOString(),this.timer=setTimeout(()=>void this.refresh(),this.pollIntervalMs)}}function ae(a){if(!a||typeof a!=="object")return"Unknown error";let o=a;return[o.name?String(o.name):"Error",o.code===void 0?void 0:`code=${String(o.code)}`,o.status===void 0?void 0:`status=${String(o.status)}`].filter(Boolean).join(" ")}async function To({reader:a,siteId:o,timeZone:i,concurrency:s=3,now:e=new Date}){let n=oe(e,i),t=await a.listOptimizerMappings(o);if(t.length===0)throw Error("SolarEdge returned no optimizer mappings");let m=await a.getOptimizerInformation(t.map((p)=>p.serial)),u=await se(t,s,async(p)=>{let r=m.serialToLiveData[p.serial],h={serial:p.serial,inverterId:p.inverterId??null,stringId:p.stringId??null,optimizerId:p.optimizerId??null,currentPowerW:qo(r?.power_W),lastMeasurement:r?.lastMeasurement??null};try{let b=await a.getOptimizerEnergy({siteId:o,startDate:n,endDate:n,optimizerSerials:[p.serial],chartTimeUnit:"hours"});return{...h,dailyEnergyWh:qo(b.totalEnergy)}}catch(b){return{...h,dailyEnergyWh:null,error:ie(b)}}}),c=u.filter((p)=>p.dailyEnergyWh!==null);return{siteId:o,date:n,timeZone:i,fetchedAt:new Date().toISOString(),optimizerCount:u.length,successfulOptimizerCount:c.length,failedOptimizerCount:u.length-c.length,totalDailyEnergyWh:c.reduce((p,r)=>p+(r.dailyEnergyWh??0),0),totalCurrentPowerW:u.reduce((p,r)=>p+(r.currentPowerW??0),0),optimizers:u}}function oe(a,o){let i=new Intl.DateTimeFormat("en",{timeZone:o,year:"numeric",month:"2-digit",day:"2-digit"}).formatToParts(a),s=Object.fromEntries(i.map((e)=>[e.type,e.value]));return`${s.year}-${s.month}-${s.day}`}async function se(a,o,i){if(!Number.isInteger(o)||o<1)throw Error("concurrency must be a positive integer");let s=Array(a.length),e=0,n=async()=>{while(e1440)throw Error("poll_interval_minutes must be an integer from 10 to 1440");return{username:i,password:s,siteId:e,pollIntervalMinutes:n}}async function Do(a=process.env.SOLAREDGE_OPTIONS_PATH??"/data/options.json"){let o=Bun.file(a);if(!await o.exists())throw Error(`App options file does not exist: ${a}`);return ee(await o.json())}async function Go({fetchImplementation:a=globalThis.fetch,supervisorToken:o=process.env.SUPERVISOR_TOKEN}={}){if(!o){let e=process.env.TZ;if(e)return $o(e);throw Error("SUPERVISOR_TOKEN is missing and TZ is not configured")}let i=await a("http://supervisor/info",{headers:{Authorization:`Bearer ${o}`},signal:AbortSignal.timeout(1e4)});if(!i.ok)throw Error(`Could not read Home Assistant time zone (${i.status})`);let s=await i.json();return $o(sa(s.data?.timezone,"Supervisor timezone"))}function sa(a,o,i=!0){if(typeof a!=="string")throw Error(`${o} must be a string`);let s=i?a.trim():a;if(!s)throw Error(`${o} must not be empty`);return s}function $o(a){try{return new Intl.DateTimeFormat("en",{timeZone:a}).format(),a}catch{throw Error(`Invalid Home Assistant time zone: ${a}`)}}import{hostname as ne}from"os";var te="solaredge_optimizers",me="http://supervisor/discovery";async function Fo({port:a,host:o=process.env.HOSTNAME||ne(),supervisorToken:i=process.env.SUPERVISOR_TOKEN,fetchImpl:s=fetch}){if(!o)throw Error("App hostname is unavailable");if(!i)throw Error("Supervisor token is unavailable");let e=await s(me,{method:"POST",headers:{Accept:"application/json",Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({service:te,config:{host:o,port:a}})});if(!e.ok)throw Error(`Supervisor discovery failed with HTTP ${e.status}`)}function No(a){return(o)=>{if(o.method!=="GET")return V({error:"Method not allowed"},405,{Allow:"GET"});let i=new URL(o.url).pathname.replace(/\/+$/,"")||"/";if(i==="/health"){let s=a.getPayload();return V(s?{status:s.status,fetchedAt:s.fetchedAt,nextRefreshAt:s.nextRefreshAt,optimizerCount:s.optimizerCount,failedOptimizerCount:s.failedOptimizerCount,...s.lastError?{lastError:s.lastError}:{}}:{status:a.getLastError()?"error":"starting",...a.getLastError()?{lastError:a.getLastError()}:{}},s?200:503)}if(i==="/"||i==="/api/optimizers"){let s=a.getPayload();return s?V(s):V({status:a.getLastError()?"error":"starting",message:"No optimizer data is available yet",...a.getLastError()?{lastError:a.getLastError()}:{}},503)}return V({error:"Not found"},404)}}function V(a,o=200,i={}){return Response.json(a,{status:o,headers:{"Cache-Control":"no-store",...i}})}async function re(){let a=await Do(),o=await Go(),i=new oa({username:a.username,password:a.password,siteId:a.siteId}),s=new wa({pollIntervalMs:a.pollIntervalMinutes*60000,loadSnapshot:async()=>{K("info",`Refreshing optimizer data for site ${a.siteId}`);let m=await To({reader:i,siteId:a.siteId,timeZone:o});return K(m.failedOptimizerCount===0?"info":"warning",`Refresh finished: ${m.successfulOptimizerCount}/${m.optimizerCount} optimizer energy values`),m}}),e=pe(process.env.PORT),n=Bun.serve({hostname:"0.0.0.0",port:e,fetch:No(s)}),t=()=>{K("info","Stopping SolarEdge Optimizer Data App"),s.stop(),n.stop(!0)};process.once("SIGTERM",t),process.once("SIGINT",t),K("info",`Listening on port ${e}; refresh interval ${a.pollIntervalMinutes} minutes; time zone ${o}`);try{await Fo({port:e}),K("info","Registered optimizer API discovery with Home Assistant")}catch(m){K("warning",`Home Assistant discovery registration failed: ${Ho(m)}`)}s.start()}function pe(a){let o=a===void 0?Lo:Number(a);if(!Number.isInteger(o)||o<1||o>65535)throw Error("PORT must be an integer from 1 to 65535");return o}function K(a,o){console.log(`${new Date().toISOString()} [${a}] ${o}`)}if(import.meta.main)try{await re()}catch(a){K("error",Ho(a)),process.exit(1)}function Ho(a){if(a instanceof Error)return`${a.name}: ${a.message}`;return"Unknown startup error"}export{re as main}; diff --git a/solaredge-optimizer-data/package.json b/solaredge-optimizer-data/package.json index 68acd52..cdfa4c5 100644 --- a/solaredge-optimizer-data/package.json +++ b/solaredge-optimizer-data/package.json @@ -1,6 +1,6 @@ { "name": "@solar-dash/solaredge-optimizer-app", - "version": "0.1.0", + "version": "0.2.0", "private": true, "type": "module", "scripts": { diff --git a/solaredge-optimizer-data/src/discovery.test.ts b/solaredge-optimizer-data/src/discovery.test.ts new file mode 100644 index 0000000..9b004d7 --- /dev/null +++ b/solaredge-optimizer-data/src/discovery.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from "bun:test"; + +import { registerHomeAssistantDiscovery } from "./discovery"; + +describe("registerHomeAssistantDiscovery", () => { + test("registers the internal App endpoint with Supervisor", async () => { + let requestUrl: string | undefined; + let requestInit: RequestInit | undefined; + const fetchImpl = (async ( + input: string | URL | Request, + init?: RequestInit, + ) => { + requestUrl = String(input); + requestInit = init; + return Response.json({ result: "ok", data: { uuid: "test-uuid" } }); + }) as typeof fetch; + + await registerHomeAssistantDiscovery({ + port: 8099, + host: "abc-solaredge-optimizer-data", + supervisorToken: "secret-token", + fetchImpl, + }); + + expect(requestUrl).toBe("http://supervisor/discovery"); + expect(requestInit?.method).toBe("POST"); + expect(requestInit?.headers).toEqual({ + Accept: "application/json", + Authorization: "Bearer secret-token", + "Content-Type": "application/json", + }); + expect(JSON.parse(String(requestInit?.body))).toEqual({ + service: "solaredge_optimizers", + config: { host: "abc-solaredge-optimizer-data", port: 8099 }, + }); + }); + + test("does not start without a Supervisor token", async () => { + await expect( + registerHomeAssistantDiscovery({ + port: 8099, + host: "abc-solaredge-optimizer-data", + supervisorToken: "", + }), + ).rejects.toThrow("Supervisor token is unavailable"); + }); +}); + diff --git a/solaredge-optimizer-data/src/discovery.ts b/solaredge-optimizer-data/src/discovery.ts new file mode 100644 index 0000000..3c8071a --- /dev/null +++ b/solaredge-optimizer-data/src/discovery.ts @@ -0,0 +1,40 @@ +import { hostname as systemHostname } from "node:os"; + +const DISCOVERY_SERVICE = "solaredge_optimizers"; +const SUPERVISOR_DISCOVERY_URL = "http://supervisor/discovery"; + +export async function registerHomeAssistantDiscovery({ + port, + host = process.env.HOSTNAME || systemHostname(), + supervisorToken = process.env.SUPERVISOR_TOKEN, + fetchImpl = fetch, +}: { + port: number; + host?: string; + supervisorToken?: string; + fetchImpl?: typeof fetch; +}): Promise { + if (!host) { + throw new Error("App hostname is unavailable"); + } + if (!supervisorToken) { + throw new Error("Supervisor token is unavailable"); + } + + const response = await fetchImpl(SUPERVISOR_DISCOVERY_URL, { + method: "POST", + headers: { + Accept: "application/json", + Authorization: `Bearer ${supervisorToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + service: DISCOVERY_SERVICE, + config: { host, port }, + }), + }); + if (!response.ok) { + throw new Error(`Supervisor discovery failed with HTTP ${response.status}`); + } +} + diff --git a/solaredge-optimizer-data/src/index.ts b/solaredge-optimizer-data/src/index.ts index 78cc408..8128da7 100644 --- a/solaredge-optimizer-data/src/index.ts +++ b/solaredge-optimizer-data/src/index.ts @@ -7,6 +7,7 @@ import { readAppOptions, resolveHomeAssistantTimeZone, } from "./config"; +import { registerHomeAssistantDiscovery } from "./discovery"; import { createRequestHandler } from "./http"; export async function main(): Promise { @@ -52,6 +53,15 @@ export async function main(): Promise { "info", `Listening on port ${port}; refresh interval ${options.pollIntervalMinutes} minutes; time zone ${timeZone}`, ); + try { + await registerHomeAssistantDiscovery({ port }); + log("info", "Registered optimizer API discovery with Home Assistant"); + } catch (error) { + log( + "warning", + `Home Assistant discovery registration failed: ${formatStartupError(error)}`, + ); + } cache.start(); }