From 42db48487c57fb4a7a045f1a3c3ef4e6e6804ad4 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 01:00:21 +0000 Subject: [PATCH 1/5] Add invite page --- public/images/badge-apple-app-store.svg | 46 +++++++++++++++++++ public/images/badge-google-play.png | Bin 0 -> 4904 bytes rollup.config.js | 3 +- src-11ty/invite.html | 57 ++++++++++++++++++++++++ src/components/my-url-input.ts | 12 ++--- src/entrypoints/my-invite.ts | 31 +++++++++++++ 6 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 public/images/badge-apple-app-store.svg create mode 100644 public/images/badge-google-play.png create mode 100644 src-11ty/invite.html create mode 100644 src/entrypoints/my-invite.ts diff --git a/public/images/badge-apple-app-store.svg b/public/images/badge-apple-app-store.svg new file mode 100644 index 00000000..072b425a --- /dev/null +++ b/public/images/badge-apple-app-store.svg @@ -0,0 +1,46 @@ + + Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/badge-google-play.png b/public/images/badge-google-play.png new file mode 100644 index 0000000000000000000000000000000000000000..131f3acaa252a863c3b694d0f522ea750aebd81c GIT binary patch literal 4904 zcmZu#X*kqj*B?vC^fxJqAzKJz8HNyL8$|XkODM@MO?I+mn~{BA$1;lSlw{2|q>(kd zNMqlPoovtaK40Ds&$+JqKIi(~zjLm$eY)T3YCoU@vw%S$5S_Z3vOWkzi6+~BFHw>2 z%YvR`AP~iruBM^N1C*w*v9YD4rLC>4i;Ihum6f-*cT!SPK|w)ubhMkBo4L7pR#sMM zXlO!0g1x=Hy1F_H24iGoB*)?4;2;yQSgeGEgulN(27{54lQS?dFf}!GbaVuR!DNse znUj-KK|uk9LebOHzj^cK>C>m8qN2LGx-^VnriVOiPvCq3ay;&LSqx$HcUY>bsumU& zR#sNd&(CLOX5!=Hr>3U1x3@bwI@Z?K4i67!XJgwwD_V)Jo_gh+8mY0`(eSLR# zcdyHH{Q2{TL?UrHNTjBw@>p2U&(AkBG$5YW%0yReZEeYTj3|Ze-cP9M>FJSvuT7|6 za512i$=4pKWjJo6p3J9~hf~NFXqN?3aWGTd|DYXUNl6cY);>~OKA@B@(*7DlmE}pL zP^7IROA+Hp85b9qo15E{PE}c1+27xPuh0w8dyslD8GibTPTcb_T*4wKq!--YlsEE~=_}VEV1t$aCU!*Vi@x$yAJQcK#)|bwBR>jI92jI4+!@YWtUy zw}pN}U0Ko4n__Lu=-H|Qy-I7mx|CQ~u;^M~Vk{InTTy%m8?-@5j`_buJ~zFG=}C+0 zNK)eC@`)4b-rmvq&2S znYL+>MWNEpl?(mg_OiwMBs(k}nb*mWUPg0tC|{`x2!y zr?doYs^bD|j#gy5wbbyZbAtK7y`XdA7sF3#h<(zNhKNzC%(*dqpvPW0-X_8SiViAy z?#oc+!=HO3>QVI#$FGD2_Usus0-LVjF`>XvRKs>} zj!S2hKo1QD;<>*6S2fyAH>H@3;zVPnvyb6cq3I zpsU<=E?!Zh?1*wx^rDVE7s0-2YicvaW~$bZ8)jROv;lCag16 za=qp5L-`1v7FG`xG;1fQ1iIxSFeazO$`Nzj;*meZ<-?^I9ceLvQkscm8a7I{Wd1m z;b-2)IVa%Y^gjxAAjs%&;juCau?yHeURI8*YB%+!ZYd4#>%83`ee+Rb8HEYAR72CV z0n2h!f7LM*B>jcTHHED&E|vJm?8ahj_zbmz)a^_&!mZ+1tE86d{1FOgYyhO67Iot_ z8VmFdZksK(8_~3Iy4vVR13RbM*wZ1EGp;=XDvd(b2OR}?{j4w><4p;r5YZu&siNa4 zByAryabA(XC}GH;&?i1;U5nLTF4h7psMNw^ttyMWb$1_sWz1!92nHXn0&%7)z=%?XN?tKZSt zTuk^6i(t%ho6zuMs{VYt@5J^Wqpep&KOfia?QC_t8Pwpsn$5+4UtD~MS@z3Z>5lp| zrZAu`3{0J3pX6Y;|FuC5olV@)Fw=92SuepU#-Fh^v*=5JdY_STt7^QvQ|huE-Z=pm zFWI_VWo5ZZFB%bxvol#2m7lgg(lQOWF7+m{d0lG$UOvSxGt?J73|T*7A6!LST|D{w z%Tme@&OS%!!|aQm_NsO3^@{jOf$9l}n1M)N>|YZn56lfSEbJ_vCD3P1sY=Qg;A?~M zJKO%TUV=W7{k-@ZkaFgq1fjZL;}=||#s2JGlb$6%1x#Bx7409RXT0O&BmZu&LLP3g zHl+Zypq3x~;-#MUaJx~T3bWW!;1&Ap?1IX>DLuUUN zcb%TRrL*d!n3Fzjmu4j1ok)|vX&~7PijaB}m5#Gpl9`j6X3+np52K091A@L7s zi26+ukITIe=bsjO**D+K{6E$7Ky*lU&A~tRX`#C`i%vTt826j#zTgwUX?qpegW$v3 zRP=--|Cx#DH3B`MQ6a=OoGLM|k^U1H4aUrO%a(#eo&SN_#Jmi6m5QjLw6pDRQy;i@?48gM7z zAQ^?d)+fBSCCi_y#FR0ON^%F~ueVg^=e!W^?yPx#<~ruZ%u5Bo7_cJ0;gq=+`rM-> zT6f;oP&}LMyS5~=<@fXHk!R1hIJa;x?-7-7G>hL|#o^w{GRP7ue z?bnZ5cA9GJgHsmh-!4EtL=VR)&u2BH{HxyqG{JZBenSMgDTzj43U!lKqrRICNKEkP zn%qauN0>FIC~Ai45K2SUjz29DK z*WJXf4BrnB7cSL@MrL0?Fw_aDs-1f2b5uz z+U(1|4vFtIA7}CiSPqTrB*;+u2;q;|__%GpH_}r5>WvP_w$yp8XK?em>nY$9b`7&V z%jzO|yJSGz!QlqxJ{NOlzD5B1V_Os76Fs<%y$DCNlimHqrQ615)?0iOM)KrTTW}|$ znG34Pp(1a)K9?<8AL8OK_r(RC=m}eo9Y)$0SZt2-U@Dx?_67WY?};mixni2n1=R=F zpv5c(T`D1Sm!TKHi`AsmH4t~C=&gbIt z;-mUHpobeGas(*4`=LE$!f(Ip6TZ;AGx&6D3NjgOe6HOWS>`AGp-3y8=>9NOD68YP zpL@2I826~tm(+LV8S?1ZC5cy;lish|T>@W#5F1Bl#U-RKAG}*Yk2LPUCA>y$e|L)t za;8|dbmmOog4;6Rk@xo6>2kY2Q?D$>meY1t?TRkcUC<5qdk3#7Y?F33OuDw%0iJr) zsa1vG_)*$!il&nbINDQ##uQ6q4E*hl=VKK48i&Mk1vL2)nzE7W%M}qC%-=Rh5%uD?L!C=QIPG|sWIDb za}gZ9oOLeJmS3BpeEWV^7D(c-TRQe#B(HCx?&7t@Isw=CY?;dnPJ!gNiPLy0#OIxg ze$Y#;Ve}(Wk?T{hbUGE zA(KyqXrcPSqd$bqif#DyE*NQ3+A4J9@6L zLi@t!6XSCl>HDj*yByp`UG&$x+|DVcI4jX{HPOmuC>$U6O~@E8k3lvxjS6VE{rb|7 zZ&`cg!F)FiGxc0lbQob?P9k#GTjlvFO}(o?APXbLJNwrV60T^b!@L|Gli<>p{=@s} z9_K!YJ0r>e*jo(OR7GL2&pz(o5Zl%4S&sl%;ZF;7e}is2J+Cz<4%)fzEVZZ7rug{y zmnz9*e>m{?tWy<@3${^z5N8CvxUlTz z-?(Q7W${jQwBdhrxXoC`j0M$wQhM)N%CU26`hqwhkm6XMWSL{t+04{Xanbf|HNsEW z@NnU78EZ1+beNW{fY*36MH1wQ_d)~srb;^H*pH@JR(LsqCzGvl?$tmTn4>v&c#v&u z+M%@w`QTC%d^h5gQpM-mevO`-tGhX0Ez_-j$O$|xOuJ8Sgt!hfche7tbwz5w;C8bN zC|bKC41b+<7Wv3*KVuPs`xVn81Ef15y!8WqHdq^$0XE>x=DgWvfi3~ z(MZCITSkaIg93x6Q#N-ME$5LxV3hGvspX|&GbPgd@vRzh3aBJILN_0net2EZPy0dS zUc#KiRm6tJr!ZkX5w4em>Dg1)!Z>EfSe?-B_8Nwf9E}biqzEOVL$oUmQDPxEp_~*? zjPe0qa&7Od3pBDXPcxNQD-NkeIBNh7q>d=x3uu}gBjW3uMcU|VVVgMvuVz)7 zUDq$w{8;P&_V^9Fsn9ZD=*;J~5rS?m>5N7uAd2ZESxeKc3Xk;7;DOty$y{eF$H#4M zV_L){ysHb8&>YPP$!=`&#X?-rBfW>Vn36Sq-iAYd-j&PK!5+-pkOw`8AzI4-e?Z>9 zTIuenFFix7?vE=eZj-I9w*+BA_e>zhQ?-Q_@m$MRb_^UpbdPolSD1s9Oz)0vy;~QR zN|S{>sCoDPr%F1%K)lOEKXWYxG^BU3J@fmDCEn+z70){9>bIEizo--?J(LH~Oatzi zH}BLL+$Rc!e2jspRQuCo|E^95NoSh;I_oyWbT-;t!y4T5Es5Eik~n!q=uVp)>Li`Z z2lB!fWjdA|&oqOA0vaUIPnr@^-Ljsu)UpIbh8U$VBK&yBgdm9!k@YsmrIgOL{M8la zpGb0=w8}LD$4x_>hJ(-y7_^C>>}F`LJ!u>C<%|5{qDL~YtW}3yO-dM>QA~xUv$VMc z$v~TYyU8?@D7%~W)c)PV5X<`VBrbpNf1qJyURPnHczrJQj3><*r&}4YJnr9wT{Uc5 zL#DCCWEU?34HL}IEE~tmuj}u3jF}Z&k6*D4KO$``pB}?=P+=08c=Mj9ys|_tTa9u< zuIz()u~K<7+_H4xr-zPL5b*|6+DwO|46;HJy@wHCzsXX}nRPcZUlxyw+~}r1OLWc% zta)rq8$%^VuOR#9mDZRTfIo%Tb!%qjA3$>9>PDyMtt`*z{(H`7N{KVSM(JH(WyF*5 zNo1cCQH)S};#@sPMv z^CwKdmU^#-)~Z&*a{W2wZX34m;pzQu?=^Xf`jUHt~g4A>3^F^`QwuC zwS7#v>^`Zx-vjfi>vDBl7VCIM9b@5<5gGJs-tJL6%sq9 g6#x5Mf{gz51&^3@tr*$xbpPY1t7t2iD?JVV5121yGXMYp literal 0 HcmV?d00001 diff --git a/rollup.config.js b/rollup.config.js index 80fd7d17..8301b5a7 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,4 @@ -import { parse } from 'path'; +import { parse } from "path"; import typescript from "@rollup/plugin-typescript"; import commonjs from "@rollup/plugin-commonjs"; import json from "@rollup/plugin-json"; @@ -27,6 +27,7 @@ const plugins = (latestBuild) => // Each entrypoint a different build to avoid code reuse across builds export default [ "./src/entrypoints/my-index.ts", + "./src/entrypoints/my-invite.ts", "./src/entrypoints/my-redirect.ts", "./src/entrypoints/my-change-url.ts", "./src/entrypoints/my-create-link.ts", diff --git a/src-11ty/invite.html b/src-11ty/invite.html new file mode 100644 index 00000000..eca6433c --- /dev/null +++ b/src-11ty/invite.html @@ -0,0 +1,57 @@ +--- +layout: base_card_with_hero +javascript_source: my-invite.js +permalink: "invite/" +description: Invited to join my Home Assistant instance. +--- + + + +

Welcome to Home Assistant

+ + +
+

+ You have been invited to join a Home Assistant instance. To accept the + invitation, download the Home Assistant application to continue. +

+

+ Get it on Google Play + Download on the App Store +

+

When the application is installed, click below to accept the invite.

+

+ Loading... +

+

+ Invitations are currently only supported on the Home Assistant apps. +

+
+
diff --git a/src/components/my-url-input.ts b/src/components/my-url-input.ts index 61e77b9b..4fa80b16 100644 --- a/src/components/my-url-input.ts +++ b/src/components/my-url-input.ts @@ -1,5 +1,5 @@ -import "@material/web/button/filled-button" -import "@material/web/textfield/filled-text-field" +import "@material/web/button/filled-button"; +import "@material/web/textfield/filled-text-field"; import type { MdFilledTextField } from "@material/web/textfield/filled-text-field"; import { css, CSSResult, html, LitElement, TemplateResult } from "lit"; import { customElement, state, query, property } from "lit/decorators.js"; @@ -29,9 +29,9 @@ export class MyUrlInputMain extends LitElement { .value=${this.value || DEFAULT_HASS_URL} @keydown=${this._handleInputKeyDown} > - ${this.value ? "Update" : "Save"} + ${this.value ? "Update" : "Save"} `; } @@ -54,7 +54,7 @@ export class MyUrlInputMain extends LitElement { if (value.indexOf("://") === -1) { this._textfield.setCustomValidity( - "Please enter your full URL, including the protocol part (https://)." + "Please enter your full URL, including the protocol part (https://).", ); this._textfield.reportValidity(); return; diff --git a/src/entrypoints/my-invite.ts b/src/entrypoints/my-invite.ts new file mode 100644 index 00000000..532f8cda --- /dev/null +++ b/src/entrypoints/my-invite.ts @@ -0,0 +1,31 @@ +import "@material/web/button/filled-button"; +import { LitElement, TemplateResult, html } from "lit"; +import { customElement } from "lit/decorators.js"; +import { extractSearchParamsObject } from "../util/search-params"; + +const SUPPORTED_PARAMS = ["url"]; + +@customElement("my-invite") +export class MyUrlInputMain extends LitElement { + protected render(): TemplateResult { + // Craft a new URL homeassistant://invite? with supported params appended + const url = new URL("homeassistant://invite"); + for (const [key, value] of Object.entries(extractSearchParamsObject())) { + if (SUPPORTED_PARAMS.includes(key)) { + url.searchParams.append(key, value); + } + } + + return html` + + Accept Invite + + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "my-invite": MyUrlInputMain; + } +} From 696e938516a908db7f0c8cab800db84f381daf3d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 01:27:33 +0000 Subject: [PATCH 2/5] Move URL generation to top --- src/entrypoints/my-invite.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/entrypoints/my-invite.ts b/src/entrypoints/my-invite.ts index 532f8cda..fb6b3eec 100644 --- a/src/entrypoints/my-invite.ts +++ b/src/entrypoints/my-invite.ts @@ -5,19 +5,19 @@ import { extractSearchParamsObject } from "../util/search-params"; const SUPPORTED_PARAMS = ["url"]; +// Craft a new URL homeassistant://invite? with supported params appended +const INVITE_URL = new URL("homeassistant://invite"); +for (const [key, value] of Object.entries(extractSearchParamsObject())) { + if (SUPPORTED_PARAMS.includes(key)) { + INVITE_URL.searchParams.append(key, value); + } +} + @customElement("my-invite") export class MyUrlInputMain extends LitElement { protected render(): TemplateResult { - // Craft a new URL homeassistant://invite? with supported params appended - const url = new URL("homeassistant://invite"); - for (const [key, value] of Object.entries(extractSearchParamsObject())) { - if (SUPPORTED_PARAMS.includes(key)) { - url.searchParams.append(key, value); - } - } - return html` - + Accept Invite `; From c204c65e8a7e4d35f2f859c57c874729fa9a9160 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 02:29:10 +0000 Subject: [PATCH 3/5] remove unused tag --- src-11ty/invite.html | 64 +++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/src-11ty/invite.html b/src-11ty/invite.html index eca6433c..2ebd672e 100644 --- a/src-11ty/invite.html +++ b/src-11ty/invite.html @@ -21,37 +21,33 @@

Welcome to Home Assistant

- -
-

- You have been invited to join a Home Assistant instance. To accept the - invitation, download the Home Assistant application to continue. -

-

- Get it on Google Play - Download on the App Store -

-

When the application is installed, click below to accept the invite.

-

- Loading... -

-

- Invitations are currently only supported on the Home Assistant apps. -

-
-
+
+

+ You have been invited to join a Home Assistant instance. To accept the + invitation, download the Home Assistant application to continue. +

+

+ Get it on Google Play + Download on the App Store +

+

When the application is installed, click below to accept the invite.

+

+ Loading... +

+

+ Invitations are currently only supported on the Home Assistant apps. +

+
From 7778596a882bdcd76eb358b44cf57b65e6801129 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 02:46:41 +0000 Subject: [PATCH 4/5] Read data from hash --- src/entrypoints/my-invite.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/entrypoints/my-invite.ts b/src/entrypoints/my-invite.ts index fb6b3eec..3bd70441 100644 --- a/src/entrypoints/my-invite.ts +++ b/src/entrypoints/my-invite.ts @@ -1,13 +1,15 @@ import "@material/web/button/filled-button"; import { LitElement, TemplateResult, html } from "lit"; import { customElement } from "lit/decorators.js"; -import { extractSearchParamsObject } from "../util/search-params"; const SUPPORTED_PARAMS = ["url"]; -// Craft a new URL homeassistant://invite? with supported params appended const INVITE_URL = new URL("homeassistant://invite"); -for (const [key, value] of Object.entries(extractSearchParamsObject())) { + +// We read params from location.hash instead of location.search, as they +// won't be sent to the server when the user visits the link. +const hashParams = new URLSearchParams(location.hash.substring(1)); +for (const [key, value] of hashParams.entries()) { if (SUPPORTED_PARAMS.includes(key)) { INVITE_URL.searchParams.append(key, value); } From 4e8eab025cb5031722fa38227db18c24e36c01fc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 5 May 2025 03:00:29 +0000 Subject: [PATCH 5/5] Hash all the way --- src/entrypoints/my-invite.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/entrypoints/my-invite.ts b/src/entrypoints/my-invite.ts index 3bd70441..3b0cc749 100644 --- a/src/entrypoints/my-invite.ts +++ b/src/entrypoints/my-invite.ts @@ -5,15 +5,18 @@ import { customElement } from "lit/decorators.js"; const SUPPORTED_PARAMS = ["url"]; const INVITE_URL = new URL("homeassistant://invite"); - // We read params from location.hash instead of location.search, as they // won't be sent to the server when the user visits the link. const hashParams = new URLSearchParams(location.hash.substring(1)); +const inviteHashParams = new URLSearchParams(); for (const [key, value] of hashParams.entries()) { if (SUPPORTED_PARAMS.includes(key)) { - INVITE_URL.searchParams.append(key, value); + inviteHashParams.append(key, value); } } +if (inviteHashParams.size > 0) { + INVITE_URL.hash = inviteHashParams.toString(); +} @customElement("my-invite") export class MyUrlInputMain extends LitElement {