From ec5bc02b231d87b57211835d9ad2f915fe4c2482 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 5 Nov 2026 13:00:35 +0000 Subject: [PATCH 1/2] fix(signin): say why a sign-in couldn't start in plain words MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the server didn't answer, Android's sign-in screen showed the raw Go error ("start login flow: Post …: context deadline exceeded (Client.Timeout exceeded while awaiting headers)") and the desktop window showed the same behind "error:". Starting the login flow now returns a short message naming the server for the common cases: no answer in time, address not found, connection refused, untrusted certificate, a plain-http server typed without http://, a 404 (with a hint about Nextcloud in a subfolder), a server error, and an answer that isn't Nextcloud. The raw error still goes to the log and stays wrapped for errors.Is. Both apps get it, they show the text as it comes. d907 --- .../{index-B_IgXu9_.js => index-DtAKrFT6.js} | 2 +- cmd/nimbo-gui/frontend/dist/index.html | 2 +- cmd/nimbo-gui/frontend/src/Login.svelte | 2 +- internal/account/login.go | 13 ++- internal/account/login_test.go | 3 +- internal/account/loginerr.go | 98 +++++++++++++++++ internal/account/loginerr_test.go | 100 ++++++++++++++++++ 7 files changed, 212 insertions(+), 8 deletions(-) rename cmd/nimbo-gui/frontend/dist/assets/{index-B_IgXu9_.js => index-DtAKrFT6.js} (99%) create mode 100644 internal/account/loginerr.go create mode 100644 internal/account/loginerr_test.go diff --git a/cmd/nimbo-gui/frontend/dist/assets/index-B_IgXu9_.js b/cmd/nimbo-gui/frontend/dist/assets/index-DtAKrFT6.js similarity index 99% rename from cmd/nimbo-gui/frontend/dist/assets/index-B_IgXu9_.js rename to cmd/nimbo-gui/frontend/dist/assets/index-DtAKrFT6.js index 0480144..fad402b 100644 --- a/cmd/nimbo-gui/frontend/dist/assets/index-B_IgXu9_.js +++ b/cmd/nimbo-gui/frontend/dist/assets/index-DtAKrFT6.js @@ -36,6 +36,6 @@ Expires: ${H(It).expires??``} SHA-256: ${H(It).fingerprint??``}`),s.disabled=H(Ft)}),U(`click`,s,()=>Lt(H(It)?.fingerprint??``)),G(e,t)},a=e=>{var t=Pg(),n=I(t,!0);O(t),z(()=>K(n,H(It).message)),G(e,t)};q(n,e=>{H(It).result===`ok`?e(r):H(It).result===`untrusted`?e(i,1):e(a,-1)}),G(e,t)},h=e=>{var t=x_(),n=I(t,!0),r=R(n),i=e=>{var t=Br();t.nodeValue=` (certificate pinned)`,G(e,t)};q(r,e=>{H(wt).localPinned&&e(i)}),O(t),z(e=>K(n,e),[()=>Bt(H(B))]),G(e,t)};q(p,e=>{H(It)?e(m):H(wt).localAddress&&e(h,1)}),O(t),z(e=>{K(i,`Sync directly with your server when you're on the same network; ${H(n)??``} uses the internet address whenever the local one isn't reachable. Needs HTTPS on that address — a self-signed certificate is fine. Leave empty if your server isn't on your network. Your local address must serve the same site, including the /push/ws notification socket.`),o.disabled=H(Ft),s.disabled=e,K(c,H(Ft)?`Testing…`:`Test`)},[()=>H(Ft)||!H(Nt).trim()]),U(`input`,o,()=>{P(It,null),P(Pt,``)}),U(`keydown`,o,e=>{e.key===`Enter`&&H(Nt).trim()&&Lt()}),Ti(o,()=>H(Nt),e=>P(Nt,e)),U(`click`,s,()=>Lt()),G(e,t)},_=M(()=>H(wt).server.toLowerCase().startsWith(`https://`));q(h,e=>{H(_)&&e(g)}),z(()=>{K(o,H(wt).user),K(c,H(wt).server)}),G(e,t)},f=e=>{var t=w_(),n=R(I(t));O(t),U(`click`,n,()=>vl()),G(e,t)};q(u,e=>{H(wt).signedIn?e(d):e(f,-1)});var p=R(u,2),m=e=>{var t=A_();J(R(L(t),2),17,()=>H(Ot),Yr,(e,t)=>{var n=k_(),r=I(n),i=I(r),a=I(i,!0);O(i);var o=R(i,2),s=I(o,!0);O(o);var c=R(o,2),l=e=>{G(e,T_())};q(c,e=>{H(t).active&&e(l)}),O(r);var u=R(r,2),d=I(u),f=e=>{var n=E_();U(`click`,n,()=>Rd(H(t).id)),G(e,n)},p=e=>{var n=D_(),r=I(n,!0);O(n),z(()=>{Z(n,`title`,H(t).status),K(r,H(t).status)}),G(e,n)};q(d,e=>{H(t).status===`Sign in again`?e(f):H(t).status&&!H(t).active&&e(p,1)});var m=R(d,2),h=e=>{var n=O_(),r=L(n),i=R(r,2);z(()=>r.disabled=H(kt)),U(`click`,r,()=>Vt(H(t).id)),U(`click`,i,()=>Ht(H(t).id)),G(e,n)};q(m,e=>{H(t).active||e(h)}),O(u),O(n),z(()=>{K(a,H(t).user),Z(o,`title`,H(t).server),K(s,H(t).server)}),G(e,n)}),G(e,t)};q(p,e=>{H(Ot).length>1&&e(m)});var h=R(p,2),g=I(h);O(h);var v=R(h,4),y=R(I(v),4),b=I(y);X(b);var x=R(b),S=e=>{G(e,Jg())};q(x,e=>{H(Ue)===`base`&&e(S)}),O(y),O(v);var C=R(v,2),w=R(I(C),2),ee=I(w);ee.value=ee.__value=`system`;var te=R(ee);te.value=te.__value=`light`;var ne=R(te);ne.value=ne.__value=`dark`,O(w),O(C);var re=R(C,2),ie=I(re);X(ie),k(),O(re);var ae=R(re,4),oe=e=>{var t=j_(),r=I(t);X(r);var i=R(r);O(t),z(()=>{xi(r,H(Wt)),K(i,` Start ${H(n)??``} when I log in`)}),U(`change`,r,Gt),G(e,t)},se=e=>{G(e,M_())};q(ae,e=>{H(Ut)?e(oe):e(se,-1)});var ce=R(ae,2),le=e=>{var t=j_(),r=I(t);X(r);var i=R(r);O(t),z(()=>{xi(r,H(qt)),K(i,` Add “Share with ${H(n)??``}” to the Explorer right-click menu`)}),U(`change`,r,Jt),G(e,t)};q(ce,e=>{H(Kt)&&e(le)});var T=R(ce,2),ue=e=>{var t=I_(),r=L(t),i=I(r);X(i);var a=R(i);O(r);var o=R(r,2);J(o,17,()=>H(Ot),e=>e.id,(e,t)=>{var n=N_(),r=I(n),i=R(I(r)),a=e=>{var n=Br();z(e=>K(n,` for ${H(t).user??``} on ${e??``}`),[()=>Xt(H(t).server)]),G(e,n)};q(i,e=>{H(Ot).length>1&&e(a)}),O(r);var o=R(r,2);X(o),O(n),z(()=>{Z(r,`for`,`navname-`+H(t).id),Z(o,`id`,`navname-`+H(t).id),Z(o,`placeholder`,H(t).sidebarAuto),bi(o,H(t).sidebarName)}),U(`change`,o,e=>Yt(H(t).id,e.currentTarget.value)),G(e,n)});var s=R(o,2),c=e=>{G(e,P_())};q(s,e=>{H(Ot).length&&e(c)});var l=R(s,2),u=e=>{G(e,F_())};q(l,e=>{H(Zt)&&e(u)}),z(()=>{xi(i,H($t)),i.disabled=H(en),K(a,` Show ${H(n)??``} in the Explorer sidebar (points at your default sync location)`)}),U(`change`,i,tn),G(e,t)};q(T,e=>{H(Qt)&&e(ue)});var de=R(T,2),fe=R(I(de)),pe=e=>{var t=L_();U(`click`,t,()=>Iu(c(`troubleshooting`))),G(e,t)};q(fe,e=>{H(o)&&e(pe)}),O(de);var me=R(de,2),he=I(me);X(he),k(),O(me);var ge=R(me,4),_e=e=>{var t=V_(),r=R(I(t),2),i=I(r,!0),a=R(i),o=e=>{var t=Br();z(()=>K(t,`· Nextcloud ${H(B).serverVersion??``}`)),G(e,t)};q(a,e=>{H(B).serverVersion&&e(o)}),O(r);var s=R(r,4),c=I(s,!0);O(s);var l=R(s,4),u=I(l),d=e=>{var t=R_(),r=L(t),i=I(r);O(r);var a=R(r,2);z(()=>K(i,`not set up on this server, so ${H(n)??``} checks for changes every 30 seconds instead. It needs the Client Push app on the server (restart ${H(n)??``} once it is)`)),U(`click`,a,()=>Iu(`https://github.com/nextcloud/notify_push#readme`)),G(e,t)},f=e=>{var t=z_(),n=R(L(t)),r=e=>{var t=Br();z(()=>K(t,`· up ${H(B).pushUptime??``}`)),G(e,t)};q(n,e=>{H(B).pushUptime&&e(r)}),G(e,t)},p=e=>{G(e,B_())};q(u,e=>{H(B).pushAvailable?H(B).pushConnected?e(f,1):e(p,-1):e(d)}),O(l);var m=R(l,4),h=I(m,!0),g=R(h),_=e=>{var t=Br();z(()=>K(t,`· ${H(B).lastStatus??``}`)),G(e,t)};q(g,e=>{H(B).lastStatus&&e(_)}),O(m),O(t),z(()=>{K(i,H(B).serverURL||`—`),K(c,H(B).account||`—`),K(h,H(B).lastSync)}),G(e,t)};q(ge,e=>{H(B)&&e(_e)});var ve=R(ge,2),ye=I(ve),be=R(ye,2),xe=R(be,2),Se=e=>{var t=H_(),n=I(t,!0);O(t),z(()=>K(n,H(vr))),G(e,t)};q(xe,e=>{H(vr)&&e(Se)}),O(ve);var Ce=R(ve,4),we=I(Ce),Te=I(we);O(we);var Ee=R(we,2),De=e=>{var t=G_(),n=L(t),r=R(n,2),i=e=>{var t=U_(),n=I(t,!0);O(t),z(()=>K(n,H(br))),G(e,t)};q(r,e=>{H(br)&&e(i)});var a=R(r,2),o=e=>{var t=W_();z(()=>t.disabled=H(wr)),U(`click`,t,Mr),G(e,t)};q(a,e=>{H(Cr)&&e(o)}),z(()=>n.disabled=H(wr)),U(`click`,n,jr),G(e,t)};q(Ee,e=>{H(Tr)&&e(De)}),O(Ce);var Oe=R(Ce,2),ke=e=>{var t=K_(),n=L(t),r=I(n);X(r),k(),O(n),k(2),z(()=>xi(r,H(Er))),U(`change`,r,Or),G(e,t)};q(Oe,e=>{H(Tr)&&e(ke)});var Ae=R(Oe,2),je=e=>{var t=q_(),n=I(t),r=R(I(n),6),i=I(r),a=R(i,2);O(r),O(n),O(t),U(`click`,i,kr),U(`click`,a,()=>P(Dr,!1)),G(e,t)};q(Ae,e=>{H(Dr)&&e(je)});var E=R(Ae,2),Me=e=>{var t=J_(),n=I(t,!0);O(t),z(()=>K(n,H(Sr))),G(e,t)};q(E,e=>{H(Sr)&&e(Me)});var D=R(E,4),Ne=e=>{var t=Y_(),n=L(t),r=R(I(n));O(n);var i=R(n,2),a=I(i);O(i),z(()=>{K(r,` — ${H(ur).customer??``}${H(ur).seats?` · ${H(ur).seats} seats`:``}${H(ur).expires?` · expires ${H(ur).expires}`:` · perpetual`}.`),a.disabled=H(fr)}),U(`click`,a,gr),G(e,t)},Pe=e=>{var t=Z_(),r=L(t),a=I(r),o=R(a,3),s=R(o,2),c=e=>{var t=X_(),n=R(L(t)),r=I(n);O(n),z(()=>K(r,`Installed licence: ${H(ur).err??``}.`)),G(e,t)};q(s,e=>{H(ur).hasLicence&&H(ur).err&&e(c)}),O(r);var l=R(r,2),u=I(l);X(u);var d=R(u,2);O(l),z(()=>{K(a,`${H(n)??``} is `),d.disabled=H(fr)}),U(`click`,o,()=>Iu(H(i)+`/business.html`)),U(`keydown`,u,e=>e.key===`Enter`&&hr()),Ti(u,()=>H(dr),e=>P(dr,e)),U(`click`,d,hr),G(e,t)};q(D,e=>{H(ur).licensed?e(Ne):e(Pe,-1)});var Fe=R(D,2),Ie=e=>{var t=Q_(),n=I(t,!0);O(t),z(()=>K(n,H(pr))),G(e,t)};q(Fe,e=>{H(pr)&&e(Ie)});var Le=R(Fe,2),Re=I(Le),ze=R(Re),Be=I(ze,!0);O(ze);var Ve=R(ze,2),He=e=>{var t=$_();U(`click`,R(L(t)),()=>Iu(H(o))),G(e,t)};q(Ve,e=>{H(o)&&e(He)});var We=R(Ve,2),Ge=I(We,!0);O(We),O(Le),z((e,t)=>{xi(ie,H(nn)),xi(he,H(Xn)),be.disabled=H(_r),K(Te,`${H(n)??``} ${H(lr)??``}`),K(Re,`© ${e??``} ${H(r)??``} · `),K(Be,t),K(Ge,H(a))},[()=>new Date().getFullYear(),()=>s(H(i))]),U(`click`,g,()=>vl()),U(`change`,b,qe),Ti(b,()=>H(_),e=>P(_,e)),U(`change`,w,fn),mi(w,()=>H(dn),e=>P(dn,e)),U(`change`,ie,rn),U(`change`,he,Zn),U(`click`,ye,()=>ju()),U(`click`,be,yr),U(`click`,ze,()=>Iu(H(i))),U(`click`,We,()=>Iu(`mailto:`+H(a))),G(e,t)};q(Zr,e=>{H(f)===`folders`?e(Qr):H(f)===`sync`?e($r,1):H(f)===`exclusions`?e(ei,2):H(f)===`appearance`?e(ti,3):e(ni,-1)}),O(Kr),O(Nr),z(()=>{Ir=Y(Fr,1,`svelte-1v8xn86`,null,Ir,{active:H(f)===`folders`}),W=Y(Lr,1,`svelte-1v8xn86`,null,W,{active:H(f)===`sync`}),zr=Y(Rr,1,`svelte-1v8xn86`,null,zr,{active:H(f)===`exclusions`}),Ur=Y(Hr,1,`svelte-1v8xn86`,null,Ur,{active:H(f)===`appearance`}),Gr=Y(Wr,1,`svelte-1v8xn86`,null,Gr,{active:H(f)===`general`})}),U(`click`,Fr,()=>P(f,`folders`)),U(`click`,Lr,()=>P(f,`sync`)),U(`click`,Rr,()=>P(f,`exclusions`)),U(`click`,Hr,()=>P(f,`appearance`)),U(`click`,Wr,()=>P(f,`general`)),G(e,Nr),Ye()}jr([`click`,`change`,`keydown`,`input`]);var rv=W(` `),iv=W(`Downloads first: `,1),av=W(``),ov=W(`
`),sv=W(`

Files appear instantly but download only when you open them. - Anything you add or change syncs back to your server as usual.

`),cv=W(`

`),lv=W(`
`),uv=W(`

Loading…

`),dv=W(`

No folders here.

`),fv=W(`
`),pv=W(``),mv=W(`
`),hv=W(`
  • `),gv=W(`

    `),_v=W(`
    `),vv=W(`
    ↔
    📁
    Local Folder
    `,1),yv=W(`

    Loading account…

    `),bv=W(`
    `);function xv(e,t){Je(t,!0);let n=[],r=N(null),i=N(``),a=N(0),o=N(`everything`),s=N(!1),c=N(``);(async()=>{P(r,await lu(),!0),P(i,H(r).defaultDir,!0),P(a,H(r).freeBytes,!0),!H(r).onDemandSupport&&H(o)===`ondemand`&&P(o,`everything`)})();function l(e){if(!e||e<0)return`—`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=e,r=0;for(;n>=1024&&r0))} ${t[r]}`}async function u(){let e=await Vu(H(i));e&&(P(i,e,!0),P(a,await tu(e),!0))}let d=N(null),f=N(!1),p=N(0),m=!1,h=null;async function g(){P(s,!0),P(c,``),H(o)===`ondemand`&&(P(f,!0),m=!1,P(p,0),h=setInterval(async()=>{P(p,(await Yl())?.adoptScanDirs||0,!0)},1500));let e=H(o)===`everything`&&H(x).length?`everything:`+JSON.stringify(H(x)):H(o),n=await Ll(H(i),e);if(h&&=(clearInterval(h),null),P(f,!1),P(s,!1),m){m=!1;return}if(n){if(H(o)===`ondemand`&&n.startsWith(`{`))try{let e=JSON.parse(n);if(e.error){P(c,e.error,!0);return}P(d,e,!0);return}catch{}P(c,n,!0);return}H(o)===`choose`&&await Mu(),t.done()}async function _(){m=!0,P(f,!1),await Dd(`ondemand-cancel`)}async function v(){P(d,null),P(s,!0);let e=await Dd(`ondemand-adopt`);if(P(s,!1),e){P(c,e,!0);return}t.done()}async function y(){P(d,null),await Dd(`ondemand-cancel`)}function b(){t.done()}let x=N(F([])),S=N(!1),C=N(``),w=N(F([])),ee=N(!1);async function te(e){P(C,e.replace(/^\/+|\/+$/g,``),!0),P(ee,!0),P(w,(await jl(H(C))??[]).filter(e=>e.isDir),!0),P(ee,!1)}function ne(){P(S,!0),te(``)}function re(){H(C)&&te(H(C).split(`/`).slice(0,-1).join(`/`))}function ie(e){P(x,H(x).includes(e)?H(x).filter(t=>t!==e):[...H(x),e],!0)}let ae=M(()=>H(x).length<=3?H(x).join(`, `):`${H(x).slice(0,3).join(`, `)} and ${H(x).length-3} more`);var oe=bv(),se=I(oe),ce=e=>{var t=vv(),m=L(t),h=I(m),oe=I(h),se=I(oe,!0);O(oe);var ce=R(oe,2),le=I(ce,!0);O(ce);var T=R(ce,2),ue=I(T,!0);O(T),O(h);var de=R(h,4),fe=R(I(de),4),pe=I(fe,!0);O(fe);var me=R(fe,2),he=I(me);O(me);var ge=R(me,2);O(de),O(m);var _e=R(m,2),ve=I(_e);let ye;var be=I(ve);X(be),be.value=be.__value=`everything`;var xe=R(be,2),Se=R(I(xe)),Ce=e=>{var t=rv(),n=I(t);O(t),z(e=>K(n,`(${e??``})`),[()=>l(H(r).accountBytes)]),G(e,t)};q(Se,e=>{H(r).accountBytes>0&&e(Ce)}),O(xe),O(ve);var we=R(ve,2),Te=e=>{var t=ov(),n=I(t),r=e=>{var t=iv(),n=L(t),r=R(I(n)),i=I(r,!0);O(r),O(n);var a=R(n,2);z(e=>{Z(n,`title`,e),K(i,H(ae)),a.disabled=H(s)},[()=>H(x).join(`, `)]),U(`click`,a,ne),G(e,t)},i=e=>{var t=av();z(()=>t.disabled=H(s)),U(`click`,t,ne),G(e,t)};q(n,e=>{H(x).length?e(r):e(i,-1)}),O(t),G(e,t)};q(we,e=>{H(o)===`everything`&&e(Te)});var Ee=R(we,2);let De;var Oe=I(Ee);X(Oe),Oe.value=Oe.__value=`choose`,k(2),O(Ee);var ke=R(Ee,2);let Ae;var je=I(ke);X(je),je.value=je.__value=`ondemand`,k(2),O(ke);var E=R(ke,2),Me=e=>{G(e,sv())};q(E,e=>{H(o)===`ondemand`&&e(Me)}),O(_e);var D=R(_e,2),Ne=e=>{var t=cv(),n=I(t,!0);O(t),z(()=>K(n,H(c))),G(e,t)};q(D,e=>{H(c)&&e(Ne)});var Pe=R(D,2),Fe=I(Pe),Ie=R(Fe,2),Le=I(Ie,!0);O(Ie),O(Pe);var Re=R(Pe,2),ze=e=>{var t=lv(),n=I(t),r=R(I(n),2),i=I(r);O(r);var a=R(r,2),o=I(a),s=I(o,!0);O(o),k(),O(a);var c=R(a,2),l=I(c);O(c),O(n),O(t),z(()=>{K(i,`${Jp.name??``} is comparing everything in this folder with your server before setting up. On a large account this can take several minutes. Nothing is changed until you confirm.`),K(s,H(p))}),U(`click`,l,_),G(e,t)};q(Re,e=>{H(f)&&e(ze)});var Be=R(Re,2),Ve=e=>{var t=mv(),n=I(t),r=R(I(n),4),i=I(r),a=R(i,2),o=I(a);O(a),O(r);var s=R(r,2),c=I(s),l=e=>{G(e,uv())},u=e=>{G(e,dv())};q(c,e=>{H(ee)?e(l):H(w).length===0&&e(u,1)}),J(R(c,2),17,()=>H(w),e=>e.path,(e,t)=>{var n=fv(),r=I(n);X(r);var i=R(r,2),a=I(i);O(i),O(n),z(e=>{xi(r,e),Z(i,`title`,`Open ${H(t).name??``}`),K(a,`📁 ${H(t).name??``}`)},[()=>H(x).includes(H(t).path)]),U(`change`,r,()=>ie(H(t).path)),U(`click`,i,()=>te(H(t).path)),G(e,n)}),O(s);var d=R(s,2),f=I(d),p=I(f);O(f);var m=R(f,2),h=e=>{var t=pv();U(`click`,t,()=>P(x,[],!0)),G(e,t)};q(m,e=>{H(x).length&&e(h)});var g=R(m,2);O(d),O(n),O(t),z(()=>{i.disabled=!H(C),Z(a,`title`,`/`+H(C)),K(o,`/${H(C)??``}`),K(p,`${H(x).length??``} chosen`)}),U(`click`,i,re),U(`click`,g,()=>P(S,!1)),G(e,t)};q(Be,e=>{H(S)&&e(Ve)});var He=R(Be,2),Ue=e=>{var t=_v(),n=I(t),r=R(I(n),2),a=R(I(r)),o=I(a,!0);O(a);var s=R(a);O(r);var c=R(r,2),u=I(c),f=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).keep),K(i,` already ${H(d).keep===1?`matches`:`match`} your server — kept on this PC.`)}),G(e,t)};q(u,e=>{H(d).keep&&e(f)});var p=R(u,2),m=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).replace),K(i,` ${H(d).replace===1?`is an online-only leftover`:`are online-only leftovers`} from another app — replaced with ${H(d).replace===1?`a ${Jp.name} placeholder`:`${Jp.name} placeholders`}.`)}),G(e,t)};q(p,e=>{H(d).replace&&e(m)});var h=R(p,2),g=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).upload),K(i,` ${H(d).upload===1?`isn’t`:`aren’t`} on your server yet — uploaded.`)}),G(e,t)};q(h,e=>{H(d).upload&&e(g)});var _=R(h,2),b=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).conflict),K(i,` ${H(d).conflict===1?`differs`:`differ`} from your server — both versions kept.`)}),G(e,t)};q(_,e=>{H(d).conflict&&e(b)}),O(c);var x=R(c,2),S=e=>{var t=gv(),n=I(t);O(t),z(e=>K(n,`Upload size: ${e??``}`),[()=>l(H(d).uploadBytes)]),G(e,t)};q(x,e=>{H(d).uploadBytes&&e(S)});var C=R(x,2),w=I(C),ee=R(w,2);O(C),O(n),O(t),z(()=>{K(o,H(i)),K(s,`. ${Jp.name??``} can keep them where they are instead of downloading everything again.`)}),U(`click`,w,y),U(`click`,ee,v),G(e,t)};q(He,e=>{H(d)&&e(Ue)}),z((e,t,n)=>{K(se,e),K(le,H(r).user),K(ue,t),Z(fe,`title`,H(i)),K(pe,H(i)),K(he,`${n??``} free`),ge.disabled=H(s),ye=Y(ve,1,`opt svelte-1wxc8fs`,null,ye,{sel:H(o)===`everything`}),be.disabled=H(s),De=Y(Ee,1,`opt svelte-1wxc8fs`,null,De,{sel:H(o)===`choose`}),Oe.disabled=H(s),Ae=Y(ke,1,`opt svelte-1wxc8fs`,null,Ae,{sel:H(o)===`ondemand`,off:!H(r).onDemandSupport}),je.disabled=H(s)||!H(r).onDemandSupport,Fe.disabled=H(s),Ie.disabled=H(s)||!H(i),K(Le,H(s)?`Setting up…`:`Connect`)},[()=>(H(r).user||`?`).charAt(0).toUpperCase(),()=>H(r).server.replace(/^https?:\/\//,``),()=>l(H(a))]),U(`click`,ge,u),Di(n,[],be,()=>H(o),e=>P(o,e)),Di(n,[],Oe,()=>H(o),e=>P(o,e)),Di(n,[],je,()=>H(o),e=>P(o,e)),U(`click`,Fe,b),U(`click`,Ie,g),G(e,t)},le=e=>{G(e,yv())};q(se,e=>{H(r)?e(ce):e(le,-1)}),O(oe),G(e,oe),Ye()}jr([`click`,`change`]);var Sv=W(`

    Sign in again

    `,1),Cv=W(`

    Sign in to Nextcloud

    `,1),wv=W(``),Tv=W(`

    `),Ev=W(``),Dv=W(``);function Ov(e,t){Je(t,!0);let n=`Enter your Nextcloud server address to begin.`,r=N(``),i=N(n),a=N(!1),o=N(`signin`),s=N(null),c=N(!1),l=M(()=>!!H(s)&&!H(c));async function u(){let e=await Bd();P(s,e?.id?e:null,!0),P(c,!1),H(a)||(P(r,H(s)?H(s).server:``,!0),P(i,H(s)?``:n,!0))}u(),ns(`login:target`,u),ns(`login:error`,e=>{H(a)&&(P(i,`Sign-in failed: `+e.data),P(a,!1))}),ns(`login:done`,async e=>{if(P(a,!1),e.data===!0){P(i,`Signed in again.`),await Il();return}await $l(),P(o,`setup`)});async function d(){if(!H(r).trim())return;P(a,!0),P(i,`Connecting…`);let e=await Tl(H(r));if(e.startsWith(`error:`)){P(i,e,!0),P(a,!1);return}P(i,`Finish signing in in the browser that just opened. This window steps aside until you have.`)}function f(){P(a,!1),P(i,H(l)?``:n,!0)}function p(){zd(),P(c,!0),P(r,``),P(i,n)}var m=Vr(),h=L(m),g=e=>{xv(e,{done:()=>Il()})},_=e=>{var t=Dv(),n=I(t),o=e=>{var t=Sv(),n=R(L(t),2),r=I(n,!0);O(n);var i=R(n,2),o=I(i);O(i);var c=R(i,2);z(()=>{K(r,H(s).address),K(o,`${Jp.name??``} can't sign in to this account any more, for example because its access was + Anything you add or change syncs back to your server as usual.

    `),cv=W(`

    `),lv=W(`
    `),uv=W(`

    Loading…

    `),dv=W(`

    No folders here.

    `),fv=W(`
    `),pv=W(``),mv=W(`
    `),hv=W(`
  • `),gv=W(`

    `),_v=W(`
    `),vv=W(`
    ↔
    📁
    Local Folder
    `,1),yv=W(`

    Loading account…

    `),bv=W(`
    `);function xv(e,t){Je(t,!0);let n=[],r=N(null),i=N(``),a=N(0),o=N(`everything`),s=N(!1),c=N(``);(async()=>{P(r,await lu(),!0),P(i,H(r).defaultDir,!0),P(a,H(r).freeBytes,!0),!H(r).onDemandSupport&&H(o)===`ondemand`&&P(o,`everything`)})();function l(e){if(!e||e<0)return`—`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=e,r=0;for(;n>=1024&&r0))} ${t[r]}`}async function u(){let e=await Vu(H(i));e&&(P(i,e,!0),P(a,await tu(e),!0))}let d=N(null),f=N(!1),p=N(0),m=!1,h=null;async function g(){P(s,!0),P(c,``),H(o)===`ondemand`&&(P(f,!0),m=!1,P(p,0),h=setInterval(async()=>{P(p,(await Yl())?.adoptScanDirs||0,!0)},1500));let e=H(o)===`everything`&&H(x).length?`everything:`+JSON.stringify(H(x)):H(o),n=await Ll(H(i),e);if(h&&=(clearInterval(h),null),P(f,!1),P(s,!1),m){m=!1;return}if(n){if(H(o)===`ondemand`&&n.startsWith(`{`))try{let e=JSON.parse(n);if(e.error){P(c,e.error,!0);return}P(d,e,!0);return}catch{}P(c,n,!0);return}H(o)===`choose`&&await Mu(),t.done()}async function _(){m=!0,P(f,!1),await Dd(`ondemand-cancel`)}async function v(){P(d,null),P(s,!0);let e=await Dd(`ondemand-adopt`);if(P(s,!1),e){P(c,e,!0);return}t.done()}async function y(){P(d,null),await Dd(`ondemand-cancel`)}function b(){t.done()}let x=N(F([])),S=N(!1),C=N(``),w=N(F([])),ee=N(!1);async function te(e){P(C,e.replace(/^\/+|\/+$/g,``),!0),P(ee,!0),P(w,(await jl(H(C))??[]).filter(e=>e.isDir),!0),P(ee,!1)}function ne(){P(S,!0),te(``)}function re(){H(C)&&te(H(C).split(`/`).slice(0,-1).join(`/`))}function ie(e){P(x,H(x).includes(e)?H(x).filter(t=>t!==e):[...H(x),e],!0)}let ae=M(()=>H(x).length<=3?H(x).join(`, `):`${H(x).slice(0,3).join(`, `)} and ${H(x).length-3} more`);var oe=bv(),se=I(oe),ce=e=>{var t=vv(),m=L(t),h=I(m),oe=I(h),se=I(oe,!0);O(oe);var ce=R(oe,2),le=I(ce,!0);O(ce);var T=R(ce,2),ue=I(T,!0);O(T),O(h);var de=R(h,4),fe=R(I(de),4),pe=I(fe,!0);O(fe);var me=R(fe,2),he=I(me);O(me);var ge=R(me,2);O(de),O(m);var _e=R(m,2),ve=I(_e);let ye;var be=I(ve);X(be),be.value=be.__value=`everything`;var xe=R(be,2),Se=R(I(xe)),Ce=e=>{var t=rv(),n=I(t);O(t),z(e=>K(n,`(${e??``})`),[()=>l(H(r).accountBytes)]),G(e,t)};q(Se,e=>{H(r).accountBytes>0&&e(Ce)}),O(xe),O(ve);var we=R(ve,2),Te=e=>{var t=ov(),n=I(t),r=e=>{var t=iv(),n=L(t),r=R(I(n)),i=I(r,!0);O(r),O(n);var a=R(n,2);z(e=>{Z(n,`title`,e),K(i,H(ae)),a.disabled=H(s)},[()=>H(x).join(`, `)]),U(`click`,a,ne),G(e,t)},i=e=>{var t=av();z(()=>t.disabled=H(s)),U(`click`,t,ne),G(e,t)};q(n,e=>{H(x).length?e(r):e(i,-1)}),O(t),G(e,t)};q(we,e=>{H(o)===`everything`&&e(Te)});var Ee=R(we,2);let De;var Oe=I(Ee);X(Oe),Oe.value=Oe.__value=`choose`,k(2),O(Ee);var ke=R(Ee,2);let Ae;var je=I(ke);X(je),je.value=je.__value=`ondemand`,k(2),O(ke);var E=R(ke,2),Me=e=>{G(e,sv())};q(E,e=>{H(o)===`ondemand`&&e(Me)}),O(_e);var D=R(_e,2),Ne=e=>{var t=cv(),n=I(t,!0);O(t),z(()=>K(n,H(c))),G(e,t)};q(D,e=>{H(c)&&e(Ne)});var Pe=R(D,2),Fe=I(Pe),Ie=R(Fe,2),Le=I(Ie,!0);O(Ie),O(Pe);var Re=R(Pe,2),ze=e=>{var t=lv(),n=I(t),r=R(I(n),2),i=I(r);O(r);var a=R(r,2),o=I(a),s=I(o,!0);O(o),k(),O(a);var c=R(a,2),l=I(c);O(c),O(n),O(t),z(()=>{K(i,`${Jp.name??``} is comparing everything in this folder with your server before setting up. On a large account this can take several minutes. Nothing is changed until you confirm.`),K(s,H(p))}),U(`click`,l,_),G(e,t)};q(Re,e=>{H(f)&&e(ze)});var Be=R(Re,2),Ve=e=>{var t=mv(),n=I(t),r=R(I(n),4),i=I(r),a=R(i,2),o=I(a);O(a),O(r);var s=R(r,2),c=I(s),l=e=>{G(e,uv())},u=e=>{G(e,dv())};q(c,e=>{H(ee)?e(l):H(w).length===0&&e(u,1)}),J(R(c,2),17,()=>H(w),e=>e.path,(e,t)=>{var n=fv(),r=I(n);X(r);var i=R(r,2),a=I(i);O(i),O(n),z(e=>{xi(r,e),Z(i,`title`,`Open ${H(t).name??``}`),K(a,`📁 ${H(t).name??``}`)},[()=>H(x).includes(H(t).path)]),U(`change`,r,()=>ie(H(t).path)),U(`click`,i,()=>te(H(t).path)),G(e,n)}),O(s);var d=R(s,2),f=I(d),p=I(f);O(f);var m=R(f,2),h=e=>{var t=pv();U(`click`,t,()=>P(x,[],!0)),G(e,t)};q(m,e=>{H(x).length&&e(h)});var g=R(m,2);O(d),O(n),O(t),z(()=>{i.disabled=!H(C),Z(a,`title`,`/`+H(C)),K(o,`/${H(C)??``}`),K(p,`${H(x).length??``} chosen`)}),U(`click`,i,re),U(`click`,g,()=>P(S,!1)),G(e,t)};q(Be,e=>{H(S)&&e(Ve)});var He=R(Be,2),Ue=e=>{var t=_v(),n=I(t),r=R(I(n),2),a=R(I(r)),o=I(a,!0);O(a);var s=R(a);O(r);var c=R(r,2),u=I(c),f=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).keep),K(i,` already ${H(d).keep===1?`matches`:`match`} your server — kept on this PC.`)}),G(e,t)};q(u,e=>{H(d).keep&&e(f)});var p=R(u,2),m=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).replace),K(i,` ${H(d).replace===1?`is an online-only leftover`:`are online-only leftovers`} from another app — replaced with ${H(d).replace===1?`a ${Jp.name} placeholder`:`${Jp.name} placeholders`}.`)}),G(e,t)};q(p,e=>{H(d).replace&&e(m)});var h=R(p,2),g=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).upload),K(i,` ${H(d).upload===1?`isn’t`:`aren’t`} on your server yet — uploaded.`)}),G(e,t)};q(h,e=>{H(d).upload&&e(g)});var _=R(h,2),b=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).conflict),K(i,` ${H(d).conflict===1?`differs`:`differ`} from your server — both versions kept.`)}),G(e,t)};q(_,e=>{H(d).conflict&&e(b)}),O(c);var x=R(c,2),S=e=>{var t=gv(),n=I(t);O(t),z(e=>K(n,`Upload size: ${e??``}`),[()=>l(H(d).uploadBytes)]),G(e,t)};q(x,e=>{H(d).uploadBytes&&e(S)});var C=R(x,2),w=I(C),ee=R(w,2);O(C),O(n),O(t),z(()=>{K(o,H(i)),K(s,`. ${Jp.name??``} can keep them where they are instead of downloading everything again.`)}),U(`click`,w,y),U(`click`,ee,v),G(e,t)};q(He,e=>{H(d)&&e(Ue)}),z((e,t,n)=>{K(se,e),K(le,H(r).user),K(ue,t),Z(fe,`title`,H(i)),K(pe,H(i)),K(he,`${n??``} free`),ge.disabled=H(s),ye=Y(ve,1,`opt svelte-1wxc8fs`,null,ye,{sel:H(o)===`everything`}),be.disabled=H(s),De=Y(Ee,1,`opt svelte-1wxc8fs`,null,De,{sel:H(o)===`choose`}),Oe.disabled=H(s),Ae=Y(ke,1,`opt svelte-1wxc8fs`,null,Ae,{sel:H(o)===`ondemand`,off:!H(r).onDemandSupport}),je.disabled=H(s)||!H(r).onDemandSupport,Fe.disabled=H(s),Ie.disabled=H(s)||!H(i),K(Le,H(s)?`Setting up…`:`Connect`)},[()=>(H(r).user||`?`).charAt(0).toUpperCase(),()=>H(r).server.replace(/^https?:\/\//,``),()=>l(H(a))]),U(`click`,ge,u),Di(n,[],be,()=>H(o),e=>P(o,e)),Di(n,[],Oe,()=>H(o),e=>P(o,e)),Di(n,[],je,()=>H(o),e=>P(o,e)),U(`click`,Fe,b),U(`click`,Ie,g),G(e,t)},le=e=>{G(e,yv())};q(se,e=>{H(r)?e(ce):e(le,-1)}),O(oe),G(e,oe),Ye()}jr([`click`,`change`]);var Sv=W(`

    Sign in again

    `,1),Cv=W(`

    Sign in to Nextcloud

    `,1),wv=W(``),Tv=W(`

    `),Ev=W(``),Dv=W(``);function Ov(e,t){Je(t,!0);let n=`Enter your Nextcloud server address to begin.`,r=N(``),i=N(n),a=N(!1),o=N(`signin`),s=N(null),c=N(!1),l=M(()=>!!H(s)&&!H(c));async function u(){let e=await Bd();P(s,e?.id?e:null,!0),P(c,!1),H(a)||(P(r,H(s)?H(s).server:``,!0),P(i,H(s)?``:n,!0))}u(),ns(`login:target`,u),ns(`login:error`,e=>{H(a)&&(P(i,`Sign-in failed: `+e.data),P(a,!1))}),ns(`login:done`,async e=>{if(P(a,!1),e.data===!0){P(i,`Signed in again.`),await Il();return}await $l(),P(o,`setup`)});async function d(){if(!H(r).trim())return;P(a,!0),P(i,`Connecting…`);let e=await Tl(H(r));if(e.startsWith(`error:`)){P(i,`Sign-in failed: `+e.slice(6).trim()),P(a,!1);return}P(i,`Finish signing in in the browser that just opened. This window steps aside until you have.`)}function f(){P(a,!1),P(i,H(l)?``:n,!0)}function p(){zd(),P(c,!0),P(r,``),P(i,n)}var m=Vr(),h=L(m),g=e=>{xv(e,{done:()=>Il()})},_=e=>{var t=Dv(),n=I(t),o=e=>{var t=Sv(),n=R(L(t),2),r=I(n,!0);O(n);var i=R(n,2),o=I(i);O(i);var c=R(i,2);z(()=>{K(r,H(s).address),K(o,`${Jp.name??``} can't sign in to this account any more, for example because its access was removed in Nextcloud. Sign in again and it carries on where it left off: your folders and settings are kept, and nothing needs setting up again.`),c.disabled=H(a)}),U(`click`,c,d),G(e,t)},c=e=>{var t=Cv(),n=R(L(t),2);X(n);var i=R(n,2);z(()=>{n.disabled=H(a),i.disabled=H(a)}),U(`keydown`,n,e=>e.key===`Enter`&&d()),Ti(n,()=>H(r),e=>P(r,e)),U(`click`,i,d),G(e,t)};q(n,e=>{H(l)&&H(s)?e(o):e(c,-1)});var u=R(n,2),m=e=>{var t=wv();U(`click`,t,f),G(e,t)};q(u,e=>{H(a)&&e(m)});var h=R(u,2),g=e=>{var t=Tv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(h,e=>{H(i)&&e(g)});var _=R(h,2),v=e=>{var t=Ev();U(`click`,t,p),G(e,t)};q(_,e=>{H(l)&&!H(a)&&e(v)}),O(t),G(e,t)};q(h,e=>{H(o)===`setup`?e(g):e(_,-1)}),G(e,m),Ye()}jr([`click`,`keydown`]);var kv=W(`

    `),Av=W(`

    Not shared yet.

    `),jv=W(`
    `),Mv=W(`
    `),Nv=W(``),Pv=W(`
    Sharing

    Shares

    Create public link

    Share with a user

    `);function Fv(e,t){Je(t,!0);let n=N(``),r=N(F([])),i=N(``),a=N(!1),o=N(``),s=N(``),c=N(``),l=N(!1),u=N(``),d=N(!1);async function f(){P(n,await jd(),!0),P(r,await Ad()??[],!0)}f(),ns(`share:target`,()=>{P(i,``),P(o,``),f()});let p=e=>e.replace(/\/+$/,``).split(`/`).pop()||e,m=e=>(e&2)!=0,h=e=>e===3;async function g(e){P(i,``),P(a,!0);let t=await e();return P(a,!1),t?(P(i,t,!0),!1):(await f(),!0)}async function _(){await g(()=>Vl(H(s).trim(),H(c).trim(),H(l)))&&(P(s,``),P(c,``),P(l,!1))}async function v(){await g(()=>Hl(H(u),H(d)))&&(P(u,``),P(d,!1))}let y=e=>g(()=>Gl(e));async function b(e,t){await Bl(e),P(o,t,!0),setTimeout(()=>{H(o)===t&&P(o,``)},1500)}let x=e=>Iu(e);var S=Pv(),C=I(S),w=R(I(C),2),ee=I(w);O(w);var te=R(w,2),ne=I(te);O(te),O(C);var re=R(C,2),ie=I(re),ae=e=>{var t=kv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(ie,e=>{H(i)&&e(ae)});var oe=R(ie,4),se=e=>{G(e,Av())},ce=e=>{var t=Vr();J(L(t),17,()=>H(r),Yr,(e,t)=>{var n=Nv(),r=I(n),i=I(r),s=I(i),c=e=>{G(e,Br(`🔗 Public link`))},l=M(()=>h(H(t).type)),u=e=>{var n=Br();z(()=>K(n,`👤 ${H(t).shareWith??``}`)),G(e,n)};q(s,e=>{H(l)?e(c):e(u,-1)}),O(i);var d=R(i,2),f=I(d,!0);O(d);var p=R(d,2);O(r);var g=R(r,2),_=e=>{var n=jv(),r=I(n);X(r);var i=R(r,2),a=I(i,!0);O(i);var s=R(i,2);O(n),z(()=>{bi(r,H(t).url),K(a,H(o)===H(t).id?`Copied!`:`Copy`)}),U(`click`,i,()=>b(H(t).url,H(t).id)),U(`click`,s,()=>x(H(t).url)),G(e,n)},v=M(()=>h(H(t).type)&&H(t).url);q(g,e=>{H(v)&&e(_)});var S=R(g,2),C=e=>{var n=Mv(),r=I(n);O(n),z(e=>K(r,`Expires ${e??``}`),[()=>H(t).expiration.slice(0,10)]),G(e,n)};q(S,e=>{H(t).expiration&&e(C)}),O(n),z(e=>{K(f,e),p.disabled=H(a)},[()=>m(H(t).permissions)?`Can edit`:`View only`]),U(`click`,p,()=>y(H(t).id)),G(e,n)}),G(e,t)};q(oe,e=>{H(r).length===0?e(se):e(ce,-1)});var le=R(oe,4),T=I(le),ue=R(I(T));X(ue),O(T);var de=R(T,2),fe=R(I(de));X(fe),O(de);var pe=R(de,2),me=I(pe);X(me),k(),O(pe);var he=R(pe,2);O(le);var ge=R(le,4),_e=I(ge),ve=R(I(_e));X(ve),O(_e);var ye=R(_e,2),be=I(ye);X(be),k(),O(ye);var xe=R(ye,2);O(ge),O(re),O(S),z(e=>{Z(w,`title`,H(n)),K(ee,`📄 ${e??``}`),K(ne,`/${H(n)??``}`),he.disabled=H(a),xe.disabled=H(a)},[()=>p(H(n))]),Ti(ue,()=>H(s),e=>P(s,e)),Ti(fe,()=>H(c),e=>P(c,e)),Oi(me,()=>H(l),e=>P(l,e)),U(`click`,he,_),U(`keydown`,ve,e=>e.key===`Enter`&&v()),Ti(ve,()=>H(u),e=>P(u,e)),Oi(be,()=>H(d),e=>P(d,e)),U(`click`,xe,v),G(e,S),Ye()}jr([`click`,`keydown`]);var Iv=W(`

    `),Lv=W(`

    Loading…

    `),Rv=W(`

    No previous versions. Nextcloud keeps versions as a file changes over time.

    `),zv=W(`
    `),Bv=W(`

    Restoring makes that version the current file (the present copy is kept as a version too).

    `,1),Vv=W(`
    Version history
    `);function Hv(e,t){Je(t,!0);let n=N(``),r=N(F([])),i=N(``),a=N(!1),o=N(!0);async function s(){P(o,!0),P(n,await ef(),!0),P(r,await $d()??[],!0),P(o,!1)}s(),ns(`versions:target`,()=>{P(i,``),s()});let c=e=>e.replace(/\/+$/,``).split(`/`).pop()||e;function l(e){if(!e||e<0)return`—`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=0;for(;e>=1024&&n0?e.toFixed(1):Math.round(e)} ${t[n]}`}async function u(e){P(i,``),P(a,!0);let t=await rd(e);if(P(a,!1),t){P(i,t,!0);return}await s()}var d=Vv(),f=I(d),p=R(I(f),2),m=I(p);O(p);var h=R(p,2),g=I(h);O(h),O(f);var _=R(f,2),v=I(_),y=e=>{var t=Iv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(v,e=>{H(i)&&e(y)});var b=R(v,2),x=e=>{G(e,Lv())},S=e=>{G(e,Rv())},C=e=>{var t=Bv();J(R(L(t),2),17,()=>H(r),Yr,(e,t)=>{var n=zv(),r=I(n),i=I(r),o=I(i,!0);O(i);var s=R(i,2),c=I(s,!0);O(s),O(r);var d=R(r,2);O(n),z(e=>{K(o,H(t).modified),K(c,e),d.disabled=H(a)},[()=>l(H(t).size)]),U(`click`,d,()=>u(H(t).href)),G(e,n)}),G(e,t)};q(b,e=>{H(o)?e(x):H(r).length===0?e(S,1):e(C,-1)}),O(_),O(d),z(e=>{Z(p,`title`,H(n)),K(m,`📄 ${e??``}`),K(g,`/${H(n)??``}`)},[()=>c(H(n))]),G(e,d),Ye()}jr([`click`]);var Uv=W(`
    Logs
     
    `);function Wv(e,t){Je(t,!0);let n=N(``),r=N(``),i=N(!1),a=N(!1),o;async function s(e=!0){P(n,await Gd(),!0),P(r,await bu(),!0),P(i,await Zd(),!0),e&&requestAnimationFrame(()=>{o&&(o.scrollTop=o.scrollHeight)})}s();async function c(){await Bl(H(n)),P(a,!0),setTimeout(()=>P(a,!1),1500)}function l(){P(i,!H(i)),kd(H(i))}var u=Uv(),d=I(u),f=R(I(d),2),p=I(f,!0);O(f);var m=R(f,2),h=I(m),g=R(h,2),_=I(g,!0);O(g);var v=R(g,2),y=R(v,2),b=I(y);X(b),k(),O(y),O(m),O(d);var x=R(d,2),S=I(x,!0);O(x),Ni(x,e=>o=e,()=>o),O(u),z(()=>{Z(f,`title`,H(r)),K(p,H(r)),K(_,H(a)?`Copied!`:`Copy`),xi(b,H(i)),K(S,H(n)||`(no log yet)`)}),U(`click`,h,()=>s()),U(`click`,g,c),U(`click`,v,()=>Au()),U(`change`,b,l),G(e,u),Ye()}jr([`click`,`change`]),He();var Gv=W(``),Kv=W(`🗂`),qv=W(`
    Loading from your server…
    `);function Jv(e,t){Je(t,!1);let n=window.location.hash.slice(1),r=n.startsWith(`appsplash/`)?n.slice(10):``,i=r.indexOf(`?`),a=decodeURIComponent(i>=0?r.slice(0,i):r)||`App`,o=i>=0&&new URLSearchParams(r.slice(i)).get(`icon`)||``;Pi();var s=qv(),c=I(s),l=I(c),u=e=>{var t=Gv();z(()=>Z(t,`src`,o)),G(e,t)},d=e=>{G(e,Kv())};q(l,e=>{o?e(u):e(d,-1)}),O(c);var f=R(c,2),p=I(f,!0);O(f),k(4),O(s),z(()=>K(p,a)),G(e,s),Ye()}var Yv=W(`

    `),Xv=W(``),Zv=W(``),Qv=W(`
    `);function $v(e,t){Je(t,!0);let n=N(``),r=N(null),i=N(null);Dn(()=>{let e=Yp.current;e&&(P(n,e.value,!0),queueMicrotask(()=>{e.kind===`text`&&H(i)?(H(i).focus(),H(i).select()):H(r)?.focus()}))});function a(){let e=Yp.current;e&&Qp(e.kind===`ask`?!0:e.kind===`text`?H(n):void 0)}function o(){let e=Yp.current;e&&Qp(e.kind===`ask`?!1:e.kind===`text`?null:void 0)}function s(e){Yp.current&&(e.key===`Escape`?(e.preventDefault(),o()):e.key===`Enter`&&Yp.current.kind===`text`&&(e.preventDefault(),a()))}var c=Vr();Ar(`keydown`,on,s);var l=L(c),u=e=>{let t=M(()=>Yp.current);var s=Qv(),c=I(s),l=I(c),u=I(l,!0);O(l);var d=R(l,2),f=e=>{var n=Yv(),r=I(n,!0);O(n),z(()=>K(r,H(t).message)),G(e,n)};q(d,e=>{H(t).message&&e(f)});var p=R(d,2),m=e=>{var t=Xv();X(t),Ni(t,e=>P(i,e),()=>H(i)),Ti(t,()=>H(n),e=>P(n,e)),G(e,t)};q(p,e=>{H(t).kind===`text`&&e(m)});var h=R(p,2),g=I(h),_=e=>{var n=Zv(),r=I(n,!0);O(n),z(()=>K(r,H(t).cancel)),U(`click`,n,o),G(e,n)};q(g,e=>{H(t).kind!==`tell`&&e(_)});var v=R(g,2);let y;var b=I(v,!0);O(v),Ni(v,e=>P(r,e),()=>H(r)),O(h),O(c),O(s),z(()=>{K(u,H(t).title),y=Y(v,1,`primary svelte-11qie5r`,null,y,{danger:H(t).danger}),K(b,H(t).ok)}),U(`click`,v,a),G(e,s)};q(l,e=>{Yp.current&&e(u)}),G(e,c),Ye()}jr([`click`]);var ey=W(` `,1);function ty(e,t){Je(t,!0);let n=N(F(window.location.hash.replace(`#`,``)));window.addEventListener(`hashchange`,()=>{P(n,window.location.hash.replace(`#`,``),!0)});let r=window.matchMedia(`(prefers-color-scheme: dark)`),i=`system`,a=``;function o(){let e;e=i===`dark`?!0:i===`light`?!1:a===`dark`?!0:a===`light`?!1:r.matches,e?document.documentElement.setAttribute(`data-theme`,`dark`):document.documentElement.removeAttribute(`data-theme`)}r.addEventListener(`change`,o),ns(`theme`,e=>{i=e.data||`system`,o()}),(async()=>{i=await du(),o()})();let s=0;async function c(){let e=++s;for(let t=0;t<12&&e===s;t++){let e=await wu();if(e){a=e,o();return}await new Promise(e=>setTimeout(e,1e3))}}c();function l(e,t){let n=e.replace(`#`,``);return n.length===6?`#`+[0,2,4].map(e=>Math.max(0,Math.round(parseInt(n.slice(e,e+2),16)*t))).map(e=>e.toString(16).padStart(2,`0`)).join(``):e}function u(e){let t=document.documentElement;e&&/^#[0-9a-fA-F]{6}$/.test(e)?(t.style.setProperty(`--accent`,e),t.style.setProperty(`--accent-dark`,l(e,.88))):(t.style.removeProperty(`--accent`),t.style.removeProperty(`--accent-dark`))}ns(`accent`,e=>{u(e.data||``),c()}),(async()=>{for(let e=0;e<12;e++){try{let e=await qd();if(e&&/^#[0-9a-fA-F]{6}$/.test(e)){u(e);return}}catch{}await new Promise(e=>setTimeout(e,1e3))}})();var d=ey(),f=L(d),p=e=>{Jv(e,{})},m=M(()=>H(n).startsWith(`appsplash`)),h=e=>{yh(e,{})},g=e=>{nv(e,{})},_=M(()=>H(n).startsWith(`settings`)),v=e=>{Ov(e,{})},y=e=>{Fv(e,{})},b=e=>{Hv(e,{})},x=e=>{Wv(e,{})},S=e=>{qp(e,{})};q(f,e=>{H(m)?e(p):H(n)===`status`?e(h,1):H(_)?e(g,2):H(n)===`login`?e(v,3):H(n)===`share`?e(y,4):H(n)===`versions`?e(b,5):H(n)===`logs`?e(x,6):e(S,-1)}),$v(R(f,2),{}),G(e,d),Ye()}Hr(ty,{target:document.getElementById(`app`)}); \ No newline at end of file diff --git a/cmd/nimbo-gui/frontend/dist/index.html b/cmd/nimbo-gui/frontend/dist/index.html index 570dd7f..eada591 100644 --- a/cmd/nimbo-gui/frontend/dist/index.html +++ b/cmd/nimbo-gui/frontend/dist/index.html @@ -6,7 +6,7 @@ Wails + Svelte + TS - + diff --git a/cmd/nimbo-gui/frontend/src/Login.svelte b/cmd/nimbo-gui/frontend/src/Login.svelte index 23cb077..f6a3d12 100644 --- a/cmd/nimbo-gui/frontend/src/Login.svelte +++ b/cmd/nimbo-gui/frontend/src/Login.svelte @@ -48,7 +48,7 @@ busy = true; msg = "Connecting…"; const url = await App.BeginLogin(server); - if (url.startsWith("error:")) { msg = url; busy = false; return; } + if (url.startsWith("error:")) { msg = "Sign-in failed: " + url.slice(6).trim(); busy = false; return; } // The window minimises itself meanwhile (it covered the browser's Grant // access button) and comes back for setup or an error. msg = "Finish signing in in the browser that just opened. This window steps aside until you have."; diff --git a/internal/account/login.go b/internal/account/login.go index de49704..6ee0ffe 100644 --- a/internal/account/login.go +++ b/internal/account/login.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "log/slog" "net/http" "net/url" "strings" @@ -65,12 +66,14 @@ func InitLogin(ctx context.Context, serverURL string) (*Flow, error) { hc := &http.Client{Timeout: 30 * time.Second} resp, err := hc.Do(req) if err != nil { - return nil, fmt.Errorf("start login flow: %w", err) + slog.Warn("start login flow failed", "server", serverURL, "err", err) + return nil, explainLoginStart(serverURL, fmt.Errorf("start login flow: %w", err)) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<12)) - return nil, fmt.Errorf("start login flow: server returned %s: %s", resp.Status, strings.TrimSpace(string(body))) + slog.Warn("start login flow refused", "server", serverURL, "status", resp.Status, "body", strings.TrimSpace(string(body))) + return nil, explainLoginStatus(serverURL, resp.StatusCode, resp.Status, strings.TrimSpace(string(body))) } var out struct { @@ -81,10 +84,12 @@ func InitLogin(ctx context.Context, serverURL string) (*Flow, error) { Login string `json:"login"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { - return nil, fmt.Errorf("decode login flow response: %w", err) + slog.Warn("start login flow: not a login-flow answer", "server", serverURL, "err", err) + return nil, notNextcloud(serverURL, fmt.Errorf("decode login flow response: %w", err)) } if out.Login == "" || out.Poll.Token == "" || out.Poll.Endpoint == "" { - return nil, fmt.Errorf("start login flow: incomplete response from server") + slog.Warn("start login flow: incomplete answer", "server", serverURL) + return nil, notNextcloud(serverURL, errors.New("start login flow: incomplete response from server")) } return &Flow{ diff --git a/internal/account/login_test.go b/internal/account/login_test.go index 027c309..9cb203a 100644 --- a/internal/account/login_test.go +++ b/internal/account/login_test.go @@ -3,6 +3,7 @@ package account import ( "context" "encoding/json" + "errors" "net/http" "net/http/httptest" "os" @@ -37,7 +38,7 @@ func TestInitLoginDefaultsToHTTPS(t *testing.T) { defer srv.Close() host := strings.TrimPrefix(srv.URL, "http://") _, err := InitLogin(context.Background(), host) - if err == nil || !strings.Contains(err.Error(), "HTTPS client") { + if err == nil || !strings.Contains(errors.Unwrap(err).Error(), "HTTPS client") { t.Fatalf("want https-vs-http transport error proving scheme default, got: %v", err) } } diff --git a/internal/account/loginerr.go b/internal/account/loginerr.go new file mode 100644 index 0000000..6f815d8 --- /dev/null +++ b/internal/account/loginerr.go @@ -0,0 +1,98 @@ +package account + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "net" + "net/url" + "strings" + "syscall" +) + +// loginError is what InitLogin returns when it can't start a sign-in: a short +// sentence a user can act on, wrapping the transport or server error so logs +// and errors.Is keep the detail. The sign-in screens show Error() as it is, and +// the raw Go error ("start login flow: Post …: context deadline exceeded") +// told nobody anything (Deck d907). +type loginError struct { + msg string + err error +} + +func (e *loginError) Error() string { return e.msg } +func (e *loginError) Unwrap() error { return e.err } + +// hostOf names the server the way the user typed it, without the scheme. +func hostOf(serverURL string) string { + if u, err := url.Parse(serverURL); err == nil && u.Host != "" { + return u.Host + strings.TrimRight(u.Path, "/") + } + return serverURL +} + +// wsaConnRefused is WSAECONNREFUSED: Windows reports a refused connection with +// its own code, which syscall.ECONNREFUSED doesn't match there. +const wsaConnRefused = syscall.Errno(10061) + +// explainLoginStart turns a failed request to start the login flow into a +// plain message. A cancelled attempt is passed through untouched: nothing went +// wrong that the user needs telling about. +func explainLoginStart(serverURL string, err error) error { + if errors.Is(err, context.Canceled) { + return err + } + host := hostOf(serverURL) + var ( + certErr *tls.CertificateVerificationError + unknownCA x509.UnknownAuthorityError + badHost x509.HostnameError + invalidCert x509.CertificateInvalidError + dnsErr *net.DNSError + netErr net.Error + ) + var msg string + switch { + case errors.As(err, &certErr), errors.As(err, &unknownCA), errors.As(err, &badHost), errors.As(err, &invalidCert): + msg = fmt.Sprintf("Can't connect securely to %s: its certificate isn't trusted. An expired or self-signed certificate is the usual cause.", host) + case strings.Contains(err.Error(), "server gave HTTP response to HTTPS client"): + msg = fmt.Sprintf("%s doesn't answer on https. If your server really has no https, type the address starting with http://", host) + case errors.As(err, &dnsErr): + msg = fmt.Sprintf("Can't find %s. Check the address, and that this device is online.", host) + case errors.Is(err, syscall.ECONNREFUSED), errors.Is(err, wsaConnRefused): + msg = fmt.Sprintf("%s turned the connection down. Check the address (and the port, if it has one), then try again.", host) + case errors.Is(err, context.DeadlineExceeded), errors.As(err, &netErr) && netErr.Timeout(): + msg = fmt.Sprintf("%s didn't answer in time. Check your connection and the address, then try again.", host) + default: + msg = fmt.Sprintf("Can't reach %s. Check your connection and the address, then try again.", host) + } + return &loginError{msg: msg, err: err} +} + +// explainLoginStatus is the plain message for a server that answered the +// login-flow request with something other than 200. The body (often an HTML +// error page) only goes in the wrapped error. +func explainLoginStatus(serverURL string, status int, statusText, body string) error { + host := hostOf(serverURL) + raw := fmt.Errorf("start login flow: server returned %s: %s", statusText, body) + var msg string + switch { + case status == 404: + msg = fmt.Sprintf("No Nextcloud sign-in found at %s. Check the address; if Nextcloud isn't at the top of the site, include its folder, e.g. example.com/nextcloud.", host) + case status >= 500: + msg = fmt.Sprintf("%s had a problem starting the sign-in (%s). Try again in a moment.", host, statusText) + default: + msg = fmt.Sprintf("%s wouldn't start a sign-in (%s).", host, statusText) + } + return &loginError{msg: msg, err: raw} +} + +// notNextcloud is the plain message for a 200 answer that isn't a login flow. +func notNextcloud(serverURL string, err error) error { + return &loginError{ + msg: fmt.Sprintf("%s answered, but not like a Nextcloud server. Check the address.", hostOf(serverURL)), + err: err, + } +} diff --git a/internal/account/loginerr_test.go b/internal/account/loginerr_test.go new file mode 100644 index 0000000..f97c4e1 --- /dev/null +++ b/internal/account/loginerr_test.go @@ -0,0 +1,100 @@ +package account + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "syscall" + "testing" +) + +// Each common way a sign-in can fail to start reads as a short plain sentence +// naming the server, never the raw Go error, and still unwraps to the original +// so logs and errors.Is keep the detail (Deck d907). +func TestExplainLoginStart(t *testing.T) { + wrap := func(err error) error { + return &url.Error{Op: "Post", URL: "https://cloud.example.com/index.php/login/v2", Err: err} + } + op := func(err error) error { return wrap(&net.OpError{Op: "dial", Net: "tcp", Err: err}) } + cases := []struct { + name string + err error + want string + }{ + {"timeout", wrap(context.DeadlineExceeded), "didn't answer in time"}, + {"dns", op(&net.DNSError{Err: "no such host", Name: "cloud.example.com", IsNotFound: true}), "Can't find cloud.example.com"}, + {"refused", op(os.NewSyscallError("connect", syscall.ECONNREFUSED)), "turned the connection down"}, + {"refused-windows", op(os.NewSyscallError("connectex", syscall.Errno(10061))), "turned the connection down"}, + {"unknown-ca", wrap(&tls.CertificateVerificationError{Err: x509.UnknownAuthorityError{}}), "certificate"}, + {"hostname", wrap(x509.HostnameError{Host: "cloud.example.com"}), "certificate"}, + {"plain-http", wrap(errors.New("http: server gave HTTP response to HTTPS client")), "http://"}, + {"other-net", op(errors.New("network is unreachable")), "Can't reach cloud.example.com"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := explainLoginStart("https://cloud.example.com", c.err) + if !strings.Contains(got.Error(), c.want) { + t.Errorf("message %q, want it to contain %q", got.Error(), c.want) + } + if strings.Contains(got.Error(), "login/v2") || strings.Contains(got.Error(), "Post ") { + t.Errorf("message %q leaks the raw error", got.Error()) + } + if !errors.Is(got, c.err) { + t.Errorf("message does not unwrap to the original error") + } + }) + } +} + +// A caller cancelling the attempt is not a failure to explain. +func TestExplainLoginStartKeepsCancel(t *testing.T) { + err := &url.Error{Op: "Post", URL: "https://x/index.php/login/v2", Err: context.Canceled} + if got := explainLoginStart("https://x", err); got != err { + t.Errorf("got %v, want the original cancel error", got) + } +} + +// A server that isn't Nextcloud (or the wrong path) answers 404: say so plainly +// instead of dumping its HTML error page. +func TestInitLoginNotFoundIsPlain(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Not here", http.StatusNotFound) + })) + defer srv.Close() + _, err := InitLogin(context.Background(), srv.URL) + if err == nil { + t.Fatal("want an error") + } + if !strings.Contains(err.Error(), "No Nextcloud sign-in") || strings.Contains(err.Error(), "") { + t.Errorf("got %q", err.Error()) + } +} + +func TestInitLoginServerErrorIsPlain(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "stack trace…", http.StatusServiceUnavailable) + })) + defer srv.Close() + _, err := InitLogin(context.Background(), srv.URL) + if err == nil || !strings.Contains(err.Error(), "503") || strings.Contains(err.Error(), "stack trace") { + t.Errorf("got %v", err) + } +} + +func TestInitLoginNotNextcloudIsPlain(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("welcome to my blog")) + })) + defer srv.Close() + _, err := InitLogin(context.Background(), srv.URL) + if err == nil || !strings.Contains(err.Error(), "not like a Nextcloud server") { + t.Errorf("got %v", err) + } +} From d2c13d713c5bace1d34a28e4d56ade729075eb29 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 5 Nov 2026 13:13:21 +0000 Subject: [PATCH 2/2] feat(white-label): bake a server address and lock into brand.json A partner wants its clients' installs locked to its own server, but the only lock so far was the HKLM admin policy, which needs a business licence and a registry step on every PC, and a partner can't push either onto its clients' machines. brand.json now takes serverUrl and lockServer, which feed the same lock the policy uses, so a partner build signs in to its server out of the box. An admin policy's own address still wins. The sign-in window fills the preset in and, when it's locked, greys the field out instead of quietly swapping whatever was typed (this also covers the existing admin lock). build-partner.ps1 refuses a lock without an https address, and the app drops a bad preset rather than lock to it. The stock build sets neither. d1010 --- cmd/nimbo-gui/brandserver_test.go | 35 +++++++++++++++++++ .../{index-B_IgXu9_.js => index-DZbRDFCU.js} | 4 +-- cmd/nimbo-gui/frontend/dist/index.html | 2 +- cmd/nimbo-gui/frontend/src/Login.svelte | 20 +++++++++-- cmd/nimbo-gui/service.go | 17 ++++++++- internal/brand/brand.go | 32 ++++++++++++++++- internal/brand/brand_test.go | 32 +++++++++++++++++ packaging/whitelabel/README.md | 2 ++ packaging/whitelabel/build-partner.ps1 | 9 +++++ 9 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 cmd/nimbo-gui/brandserver_test.go rename cmd/nimbo-gui/frontend/dist/assets/{index-B_IgXu9_.js => index-DZbRDFCU.js} (94%) diff --git a/cmd/nimbo-gui/brandserver_test.go b/cmd/nimbo-gui/brandserver_test.go new file mode 100644 index 0000000..5774147 --- /dev/null +++ b/cmd/nimbo-gui/brandserver_test.go @@ -0,0 +1,35 @@ +package main + +import ( + "testing" + + "github.com/otherworld/nimbo/internal/brand" + "github.com/otherworld/nimbo/internal/policy" +) + +// A partner build's baked-in server lock applies with no licence or registry +// policy; an admin policy's own address wins; the stock build changes nothing +// (Deck d1010). +func TestWithBrandServer(t *testing.T) { + acme := brand.Brand{ServerURL: "https://cloud.acme.example", LockServer: true} + + p := withBrandServer(policy.Policy{AllowSignOut: true}, acme) + if !p.LockServer || p.ServerURL != "https://cloud.acme.example" || !p.AllowSignOut { + t.Errorf("partner lock not applied: %+v", p) + } + + p = withBrandServer(policy.Policy{ServerURL: "https://admin.example"}, acme) + if p.ServerURL != "https://admin.example" || !p.LockServer { + t.Errorf("admin address should win, lock should hold: %+v", p) + } + + p = withBrandServer(policy.Policy{AllowSignOut: true}, brand.Brand{ServerURL: "https://preset.example"}) + if p.ServerURL != "https://preset.example" || p.LockServer { + t.Errorf("a preset without lockServer must not lock: %+v", p) + } + + in := policy.Policy{AllowSignOut: true} + if p := withBrandServer(in, brand.Brand{Name: "OtherSync"}); p != in { + t.Errorf("stock brand changed the policy: %+v", p) + } +} diff --git a/cmd/nimbo-gui/frontend/dist/assets/index-B_IgXu9_.js b/cmd/nimbo-gui/frontend/dist/assets/index-DZbRDFCU.js similarity index 94% rename from cmd/nimbo-gui/frontend/dist/assets/index-B_IgXu9_.js rename to cmd/nimbo-gui/frontend/dist/assets/index-DZbRDFCU.js index 0480144..20573cc 100644 --- a/cmd/nimbo-gui/frontend/dist/assets/index-B_IgXu9_.js +++ b/cmd/nimbo-gui/frontend/dist/assets/index-DZbRDFCU.js @@ -36,6 +36,6 @@ Expires: ${H(It).expires??``} SHA-256: ${H(It).fingerprint??``}`),s.disabled=H(Ft)}),U(`click`,s,()=>Lt(H(It)?.fingerprint??``)),G(e,t)},a=e=>{var t=Pg(),n=I(t,!0);O(t),z(()=>K(n,H(It).message)),G(e,t)};q(n,e=>{H(It).result===`ok`?e(r):H(It).result===`untrusted`?e(i,1):e(a,-1)}),G(e,t)},h=e=>{var t=x_(),n=I(t,!0),r=R(n),i=e=>{var t=Br();t.nodeValue=` (certificate pinned)`,G(e,t)};q(r,e=>{H(wt).localPinned&&e(i)}),O(t),z(e=>K(n,e),[()=>Bt(H(B))]),G(e,t)};q(p,e=>{H(It)?e(m):H(wt).localAddress&&e(h,1)}),O(t),z(e=>{K(i,`Sync directly with your server when you're on the same network; ${H(n)??``} uses the internet address whenever the local one isn't reachable. Needs HTTPS on that address — a self-signed certificate is fine. Leave empty if your server isn't on your network. Your local address must serve the same site, including the /push/ws notification socket.`),o.disabled=H(Ft),s.disabled=e,K(c,H(Ft)?`Testing…`:`Test`)},[()=>H(Ft)||!H(Nt).trim()]),U(`input`,o,()=>{P(It,null),P(Pt,``)}),U(`keydown`,o,e=>{e.key===`Enter`&&H(Nt).trim()&&Lt()}),Ti(o,()=>H(Nt),e=>P(Nt,e)),U(`click`,s,()=>Lt()),G(e,t)},_=M(()=>H(wt).server.toLowerCase().startsWith(`https://`));q(h,e=>{H(_)&&e(g)}),z(()=>{K(o,H(wt).user),K(c,H(wt).server)}),G(e,t)},f=e=>{var t=w_(),n=R(I(t));O(t),U(`click`,n,()=>vl()),G(e,t)};q(u,e=>{H(wt).signedIn?e(d):e(f,-1)});var p=R(u,2),m=e=>{var t=A_();J(R(L(t),2),17,()=>H(Ot),Yr,(e,t)=>{var n=k_(),r=I(n),i=I(r),a=I(i,!0);O(i);var o=R(i,2),s=I(o,!0);O(o);var c=R(o,2),l=e=>{G(e,T_())};q(c,e=>{H(t).active&&e(l)}),O(r);var u=R(r,2),d=I(u),f=e=>{var n=E_();U(`click`,n,()=>Rd(H(t).id)),G(e,n)},p=e=>{var n=D_(),r=I(n,!0);O(n),z(()=>{Z(n,`title`,H(t).status),K(r,H(t).status)}),G(e,n)};q(d,e=>{H(t).status===`Sign in again`?e(f):H(t).status&&!H(t).active&&e(p,1)});var m=R(d,2),h=e=>{var n=O_(),r=L(n),i=R(r,2);z(()=>r.disabled=H(kt)),U(`click`,r,()=>Vt(H(t).id)),U(`click`,i,()=>Ht(H(t).id)),G(e,n)};q(m,e=>{H(t).active||e(h)}),O(u),O(n),z(()=>{K(a,H(t).user),Z(o,`title`,H(t).server),K(s,H(t).server)}),G(e,n)}),G(e,t)};q(p,e=>{H(Ot).length>1&&e(m)});var h=R(p,2),g=I(h);O(h);var v=R(h,4),y=R(I(v),4),b=I(y);X(b);var x=R(b),S=e=>{G(e,Jg())};q(x,e=>{H(Ue)===`base`&&e(S)}),O(y),O(v);var C=R(v,2),w=R(I(C),2),ee=I(w);ee.value=ee.__value=`system`;var te=R(ee);te.value=te.__value=`light`;var ne=R(te);ne.value=ne.__value=`dark`,O(w),O(C);var re=R(C,2),ie=I(re);X(ie),k(),O(re);var ae=R(re,4),oe=e=>{var t=j_(),r=I(t);X(r);var i=R(r);O(t),z(()=>{xi(r,H(Wt)),K(i,` Start ${H(n)??``} when I log in`)}),U(`change`,r,Gt),G(e,t)},se=e=>{G(e,M_())};q(ae,e=>{H(Ut)?e(oe):e(se,-1)});var ce=R(ae,2),le=e=>{var t=j_(),r=I(t);X(r);var i=R(r);O(t),z(()=>{xi(r,H(qt)),K(i,` Add “Share with ${H(n)??``}” to the Explorer right-click menu`)}),U(`change`,r,Jt),G(e,t)};q(ce,e=>{H(Kt)&&e(le)});var T=R(ce,2),ue=e=>{var t=I_(),r=L(t),i=I(r);X(i);var a=R(i);O(r);var o=R(r,2);J(o,17,()=>H(Ot),e=>e.id,(e,t)=>{var n=N_(),r=I(n),i=R(I(r)),a=e=>{var n=Br();z(e=>K(n,` for ${H(t).user??``} on ${e??``}`),[()=>Xt(H(t).server)]),G(e,n)};q(i,e=>{H(Ot).length>1&&e(a)}),O(r);var o=R(r,2);X(o),O(n),z(()=>{Z(r,`for`,`navname-`+H(t).id),Z(o,`id`,`navname-`+H(t).id),Z(o,`placeholder`,H(t).sidebarAuto),bi(o,H(t).sidebarName)}),U(`change`,o,e=>Yt(H(t).id,e.currentTarget.value)),G(e,n)});var s=R(o,2),c=e=>{G(e,P_())};q(s,e=>{H(Ot).length&&e(c)});var l=R(s,2),u=e=>{G(e,F_())};q(l,e=>{H(Zt)&&e(u)}),z(()=>{xi(i,H($t)),i.disabled=H(en),K(a,` Show ${H(n)??``} in the Explorer sidebar (points at your default sync location)`)}),U(`change`,i,tn),G(e,t)};q(T,e=>{H(Qt)&&e(ue)});var de=R(T,2),fe=R(I(de)),pe=e=>{var t=L_();U(`click`,t,()=>Iu(c(`troubleshooting`))),G(e,t)};q(fe,e=>{H(o)&&e(pe)}),O(de);var me=R(de,2),he=I(me);X(he),k(),O(me);var ge=R(me,4),_e=e=>{var t=V_(),r=R(I(t),2),i=I(r,!0),a=R(i),o=e=>{var t=Br();z(()=>K(t,`· Nextcloud ${H(B).serverVersion??``}`)),G(e,t)};q(a,e=>{H(B).serverVersion&&e(o)}),O(r);var s=R(r,4),c=I(s,!0);O(s);var l=R(s,4),u=I(l),d=e=>{var t=R_(),r=L(t),i=I(r);O(r);var a=R(r,2);z(()=>K(i,`not set up on this server, so ${H(n)??``} checks for changes every 30 seconds instead. It needs the Client Push app on the server (restart ${H(n)??``} once it is)`)),U(`click`,a,()=>Iu(`https://github.com/nextcloud/notify_push#readme`)),G(e,t)},f=e=>{var t=z_(),n=R(L(t)),r=e=>{var t=Br();z(()=>K(t,`· up ${H(B).pushUptime??``}`)),G(e,t)};q(n,e=>{H(B).pushUptime&&e(r)}),G(e,t)},p=e=>{G(e,B_())};q(u,e=>{H(B).pushAvailable?H(B).pushConnected?e(f,1):e(p,-1):e(d)}),O(l);var m=R(l,4),h=I(m,!0),g=R(h),_=e=>{var t=Br();z(()=>K(t,`· ${H(B).lastStatus??``}`)),G(e,t)};q(g,e=>{H(B).lastStatus&&e(_)}),O(m),O(t),z(()=>{K(i,H(B).serverURL||`—`),K(c,H(B).account||`—`),K(h,H(B).lastSync)}),G(e,t)};q(ge,e=>{H(B)&&e(_e)});var ve=R(ge,2),ye=I(ve),be=R(ye,2),xe=R(be,2),Se=e=>{var t=H_(),n=I(t,!0);O(t),z(()=>K(n,H(vr))),G(e,t)};q(xe,e=>{H(vr)&&e(Se)}),O(ve);var Ce=R(ve,4),we=I(Ce),Te=I(we);O(we);var Ee=R(we,2),De=e=>{var t=G_(),n=L(t),r=R(n,2),i=e=>{var t=U_(),n=I(t,!0);O(t),z(()=>K(n,H(br))),G(e,t)};q(r,e=>{H(br)&&e(i)});var a=R(r,2),o=e=>{var t=W_();z(()=>t.disabled=H(wr)),U(`click`,t,Mr),G(e,t)};q(a,e=>{H(Cr)&&e(o)}),z(()=>n.disabled=H(wr)),U(`click`,n,jr),G(e,t)};q(Ee,e=>{H(Tr)&&e(De)}),O(Ce);var Oe=R(Ce,2),ke=e=>{var t=K_(),n=L(t),r=I(n);X(r),k(),O(n),k(2),z(()=>xi(r,H(Er))),U(`change`,r,Or),G(e,t)};q(Oe,e=>{H(Tr)&&e(ke)});var Ae=R(Oe,2),je=e=>{var t=q_(),n=I(t),r=R(I(n),6),i=I(r),a=R(i,2);O(r),O(n),O(t),U(`click`,i,kr),U(`click`,a,()=>P(Dr,!1)),G(e,t)};q(Ae,e=>{H(Dr)&&e(je)});var E=R(Ae,2),Me=e=>{var t=J_(),n=I(t,!0);O(t),z(()=>K(n,H(Sr))),G(e,t)};q(E,e=>{H(Sr)&&e(Me)});var D=R(E,4),Ne=e=>{var t=Y_(),n=L(t),r=R(I(n));O(n);var i=R(n,2),a=I(i);O(i),z(()=>{K(r,` — ${H(ur).customer??``}${H(ur).seats?` · ${H(ur).seats} seats`:``}${H(ur).expires?` · expires ${H(ur).expires}`:` · perpetual`}.`),a.disabled=H(fr)}),U(`click`,a,gr),G(e,t)},Pe=e=>{var t=Z_(),r=L(t),a=I(r),o=R(a,3),s=R(o,2),c=e=>{var t=X_(),n=R(L(t)),r=I(n);O(n),z(()=>K(r,`Installed licence: ${H(ur).err??``}.`)),G(e,t)};q(s,e=>{H(ur).hasLicence&&H(ur).err&&e(c)}),O(r);var l=R(r,2),u=I(l);X(u);var d=R(u,2);O(l),z(()=>{K(a,`${H(n)??``} is `),d.disabled=H(fr)}),U(`click`,o,()=>Iu(H(i)+`/business.html`)),U(`keydown`,u,e=>e.key===`Enter`&&hr()),Ti(u,()=>H(dr),e=>P(dr,e)),U(`click`,d,hr),G(e,t)};q(D,e=>{H(ur).licensed?e(Ne):e(Pe,-1)});var Fe=R(D,2),Ie=e=>{var t=Q_(),n=I(t,!0);O(t),z(()=>K(n,H(pr))),G(e,t)};q(Fe,e=>{H(pr)&&e(Ie)});var Le=R(Fe,2),Re=I(Le),ze=R(Re),Be=I(ze,!0);O(ze);var Ve=R(ze,2),He=e=>{var t=$_();U(`click`,R(L(t)),()=>Iu(H(o))),G(e,t)};q(Ve,e=>{H(o)&&e(He)});var We=R(Ve,2),Ge=I(We,!0);O(We),O(Le),z((e,t)=>{xi(ie,H(nn)),xi(he,H(Xn)),be.disabled=H(_r),K(Te,`${H(n)??``} ${H(lr)??``}`),K(Re,`© ${e??``} ${H(r)??``} · `),K(Be,t),K(Ge,H(a))},[()=>new Date().getFullYear(),()=>s(H(i))]),U(`click`,g,()=>vl()),U(`change`,b,qe),Ti(b,()=>H(_),e=>P(_,e)),U(`change`,w,fn),mi(w,()=>H(dn),e=>P(dn,e)),U(`change`,ie,rn),U(`change`,he,Zn),U(`click`,ye,()=>ju()),U(`click`,be,yr),U(`click`,ze,()=>Iu(H(i))),U(`click`,We,()=>Iu(`mailto:`+H(a))),G(e,t)};q(Zr,e=>{H(f)===`folders`?e(Qr):H(f)===`sync`?e($r,1):H(f)===`exclusions`?e(ei,2):H(f)===`appearance`?e(ti,3):e(ni,-1)}),O(Kr),O(Nr),z(()=>{Ir=Y(Fr,1,`svelte-1v8xn86`,null,Ir,{active:H(f)===`folders`}),W=Y(Lr,1,`svelte-1v8xn86`,null,W,{active:H(f)===`sync`}),zr=Y(Rr,1,`svelte-1v8xn86`,null,zr,{active:H(f)===`exclusions`}),Ur=Y(Hr,1,`svelte-1v8xn86`,null,Ur,{active:H(f)===`appearance`}),Gr=Y(Wr,1,`svelte-1v8xn86`,null,Gr,{active:H(f)===`general`})}),U(`click`,Fr,()=>P(f,`folders`)),U(`click`,Lr,()=>P(f,`sync`)),U(`click`,Rr,()=>P(f,`exclusions`)),U(`click`,Hr,()=>P(f,`appearance`)),U(`click`,Wr,()=>P(f,`general`)),G(e,Nr),Ye()}jr([`click`,`change`,`keydown`,`input`]);var rv=W(` `),iv=W(`Downloads first: `,1),av=W(``),ov=W(`
    `),sv=W(`

    Files appear instantly but download only when you open them. - Anything you add or change syncs back to your server as usual.

    `),cv=W(`

    `),lv=W(`
    `),uv=W(`

    Loading…

    `),dv=W(`

    No folders here.

    `),fv=W(`
    `),pv=W(``),mv=W(`
    `),hv=W(`
  • `),gv=W(`

    `),_v=W(`
    `),vv=W(`
    ↔
    📁
    Local Folder
    `,1),yv=W(`

    Loading account…

    `),bv=W(`
    `);function xv(e,t){Je(t,!0);let n=[],r=N(null),i=N(``),a=N(0),o=N(`everything`),s=N(!1),c=N(``);(async()=>{P(r,await lu(),!0),P(i,H(r).defaultDir,!0),P(a,H(r).freeBytes,!0),!H(r).onDemandSupport&&H(o)===`ondemand`&&P(o,`everything`)})();function l(e){if(!e||e<0)return`—`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=e,r=0;for(;n>=1024&&r0))} ${t[r]}`}async function u(){let e=await Vu(H(i));e&&(P(i,e,!0),P(a,await tu(e),!0))}let d=N(null),f=N(!1),p=N(0),m=!1,h=null;async function g(){P(s,!0),P(c,``),H(o)===`ondemand`&&(P(f,!0),m=!1,P(p,0),h=setInterval(async()=>{P(p,(await Yl())?.adoptScanDirs||0,!0)},1500));let e=H(o)===`everything`&&H(x).length?`everything:`+JSON.stringify(H(x)):H(o),n=await Ll(H(i),e);if(h&&=(clearInterval(h),null),P(f,!1),P(s,!1),m){m=!1;return}if(n){if(H(o)===`ondemand`&&n.startsWith(`{`))try{let e=JSON.parse(n);if(e.error){P(c,e.error,!0);return}P(d,e,!0);return}catch{}P(c,n,!0);return}H(o)===`choose`&&await Mu(),t.done()}async function _(){m=!0,P(f,!1),await Dd(`ondemand-cancel`)}async function v(){P(d,null),P(s,!0);let e=await Dd(`ondemand-adopt`);if(P(s,!1),e){P(c,e,!0);return}t.done()}async function y(){P(d,null),await Dd(`ondemand-cancel`)}function b(){t.done()}let x=N(F([])),S=N(!1),C=N(``),w=N(F([])),ee=N(!1);async function te(e){P(C,e.replace(/^\/+|\/+$/g,``),!0),P(ee,!0),P(w,(await jl(H(C))??[]).filter(e=>e.isDir),!0),P(ee,!1)}function ne(){P(S,!0),te(``)}function re(){H(C)&&te(H(C).split(`/`).slice(0,-1).join(`/`))}function ie(e){P(x,H(x).includes(e)?H(x).filter(t=>t!==e):[...H(x),e],!0)}let ae=M(()=>H(x).length<=3?H(x).join(`, `):`${H(x).slice(0,3).join(`, `)} and ${H(x).length-3} more`);var oe=bv(),se=I(oe),ce=e=>{var t=vv(),m=L(t),h=I(m),oe=I(h),se=I(oe,!0);O(oe);var ce=R(oe,2),le=I(ce,!0);O(ce);var T=R(ce,2),ue=I(T,!0);O(T),O(h);var de=R(h,4),fe=R(I(de),4),pe=I(fe,!0);O(fe);var me=R(fe,2),he=I(me);O(me);var ge=R(me,2);O(de),O(m);var _e=R(m,2),ve=I(_e);let ye;var be=I(ve);X(be),be.value=be.__value=`everything`;var xe=R(be,2),Se=R(I(xe)),Ce=e=>{var t=rv(),n=I(t);O(t),z(e=>K(n,`(${e??``})`),[()=>l(H(r).accountBytes)]),G(e,t)};q(Se,e=>{H(r).accountBytes>0&&e(Ce)}),O(xe),O(ve);var we=R(ve,2),Te=e=>{var t=ov(),n=I(t),r=e=>{var t=iv(),n=L(t),r=R(I(n)),i=I(r,!0);O(r),O(n);var a=R(n,2);z(e=>{Z(n,`title`,e),K(i,H(ae)),a.disabled=H(s)},[()=>H(x).join(`, `)]),U(`click`,a,ne),G(e,t)},i=e=>{var t=av();z(()=>t.disabled=H(s)),U(`click`,t,ne),G(e,t)};q(n,e=>{H(x).length?e(r):e(i,-1)}),O(t),G(e,t)};q(we,e=>{H(o)===`everything`&&e(Te)});var Ee=R(we,2);let De;var Oe=I(Ee);X(Oe),Oe.value=Oe.__value=`choose`,k(2),O(Ee);var ke=R(Ee,2);let Ae;var je=I(ke);X(je),je.value=je.__value=`ondemand`,k(2),O(ke);var E=R(ke,2),Me=e=>{G(e,sv())};q(E,e=>{H(o)===`ondemand`&&e(Me)}),O(_e);var D=R(_e,2),Ne=e=>{var t=cv(),n=I(t,!0);O(t),z(()=>K(n,H(c))),G(e,t)};q(D,e=>{H(c)&&e(Ne)});var Pe=R(D,2),Fe=I(Pe),Ie=R(Fe,2),Le=I(Ie,!0);O(Ie),O(Pe);var Re=R(Pe,2),ze=e=>{var t=lv(),n=I(t),r=R(I(n),2),i=I(r);O(r);var a=R(r,2),o=I(a),s=I(o,!0);O(o),k(),O(a);var c=R(a,2),l=I(c);O(c),O(n),O(t),z(()=>{K(i,`${Jp.name??``} is comparing everything in this folder with your server before setting up. On a large account this can take several minutes. Nothing is changed until you confirm.`),K(s,H(p))}),U(`click`,l,_),G(e,t)};q(Re,e=>{H(f)&&e(ze)});var Be=R(Re,2),Ve=e=>{var t=mv(),n=I(t),r=R(I(n),4),i=I(r),a=R(i,2),o=I(a);O(a),O(r);var s=R(r,2),c=I(s),l=e=>{G(e,uv())},u=e=>{G(e,dv())};q(c,e=>{H(ee)?e(l):H(w).length===0&&e(u,1)}),J(R(c,2),17,()=>H(w),e=>e.path,(e,t)=>{var n=fv(),r=I(n);X(r);var i=R(r,2),a=I(i);O(i),O(n),z(e=>{xi(r,e),Z(i,`title`,`Open ${H(t).name??``}`),K(a,`📁 ${H(t).name??``}`)},[()=>H(x).includes(H(t).path)]),U(`change`,r,()=>ie(H(t).path)),U(`click`,i,()=>te(H(t).path)),G(e,n)}),O(s);var d=R(s,2),f=I(d),p=I(f);O(f);var m=R(f,2),h=e=>{var t=pv();U(`click`,t,()=>P(x,[],!0)),G(e,t)};q(m,e=>{H(x).length&&e(h)});var g=R(m,2);O(d),O(n),O(t),z(()=>{i.disabled=!H(C),Z(a,`title`,`/`+H(C)),K(o,`/${H(C)??``}`),K(p,`${H(x).length??``} chosen`)}),U(`click`,i,re),U(`click`,g,()=>P(S,!1)),G(e,t)};q(Be,e=>{H(S)&&e(Ve)});var He=R(Be,2),Ue=e=>{var t=_v(),n=I(t),r=R(I(n),2),a=R(I(r)),o=I(a,!0);O(a);var s=R(a);O(r);var c=R(r,2),u=I(c),f=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).keep),K(i,` already ${H(d).keep===1?`matches`:`match`} your server — kept on this PC.`)}),G(e,t)};q(u,e=>{H(d).keep&&e(f)});var p=R(u,2),m=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).replace),K(i,` ${H(d).replace===1?`is an online-only leftover`:`are online-only leftovers`} from another app — replaced with ${H(d).replace===1?`a ${Jp.name} placeholder`:`${Jp.name} placeholders`}.`)}),G(e,t)};q(p,e=>{H(d).replace&&e(m)});var h=R(p,2),g=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).upload),K(i,` ${H(d).upload===1?`isn’t`:`aren’t`} on your server yet — uploaded.`)}),G(e,t)};q(h,e=>{H(d).upload&&e(g)});var _=R(h,2),b=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).conflict),K(i,` ${H(d).conflict===1?`differs`:`differ`} from your server — both versions kept.`)}),G(e,t)};q(_,e=>{H(d).conflict&&e(b)}),O(c);var x=R(c,2),S=e=>{var t=gv(),n=I(t);O(t),z(e=>K(n,`Upload size: ${e??``}`),[()=>l(H(d).uploadBytes)]),G(e,t)};q(x,e=>{H(d).uploadBytes&&e(S)});var C=R(x,2),w=I(C),ee=R(w,2);O(C),O(n),O(t),z(()=>{K(o,H(i)),K(s,`. ${Jp.name??``} can keep them where they are instead of downloading everything again.`)}),U(`click`,w,y),U(`click`,ee,v),G(e,t)};q(He,e=>{H(d)&&e(Ue)}),z((e,t,n)=>{K(se,e),K(le,H(r).user),K(ue,t),Z(fe,`title`,H(i)),K(pe,H(i)),K(he,`${n??``} free`),ge.disabled=H(s),ye=Y(ve,1,`opt svelte-1wxc8fs`,null,ye,{sel:H(o)===`everything`}),be.disabled=H(s),De=Y(Ee,1,`opt svelte-1wxc8fs`,null,De,{sel:H(o)===`choose`}),Oe.disabled=H(s),Ae=Y(ke,1,`opt svelte-1wxc8fs`,null,Ae,{sel:H(o)===`ondemand`,off:!H(r).onDemandSupport}),je.disabled=H(s)||!H(r).onDemandSupport,Fe.disabled=H(s),Ie.disabled=H(s)||!H(i),K(Le,H(s)?`Setting up…`:`Connect`)},[()=>(H(r).user||`?`).charAt(0).toUpperCase(),()=>H(r).server.replace(/^https?:\/\//,``),()=>l(H(a))]),U(`click`,ge,u),Di(n,[],be,()=>H(o),e=>P(o,e)),Di(n,[],Oe,()=>H(o),e=>P(o,e)),Di(n,[],je,()=>H(o),e=>P(o,e)),U(`click`,Fe,b),U(`click`,Ie,g),G(e,t)},le=e=>{G(e,yv())};q(se,e=>{H(r)?e(ce):e(le,-1)}),O(oe),G(e,oe),Ye()}jr([`click`,`change`]);var Sv=W(`

    Sign in again

    `,1),Cv=W(`

    Sign in to Nextcloud

    `,1),wv=W(``),Tv=W(`

    `),Ev=W(``),Dv=W(``);function Ov(e,t){Je(t,!0);let n=`Enter your Nextcloud server address to begin.`,r=N(``),i=N(n),a=N(!1),o=N(`signin`),s=N(null),c=N(!1),l=M(()=>!!H(s)&&!H(c));async function u(){let e=await Bd();P(s,e?.id?e:null,!0),P(c,!1),H(a)||(P(r,H(s)?H(s).server:``,!0),P(i,H(s)?``:n,!0))}u(),ns(`login:target`,u),ns(`login:error`,e=>{H(a)&&(P(i,`Sign-in failed: `+e.data),P(a,!1))}),ns(`login:done`,async e=>{if(P(a,!1),e.data===!0){P(i,`Signed in again.`),await Il();return}await $l(),P(o,`setup`)});async function d(){if(!H(r).trim())return;P(a,!0),P(i,`Connecting…`);let e=await Tl(H(r));if(e.startsWith(`error:`)){P(i,e,!0),P(a,!1);return}P(i,`Finish signing in in the browser that just opened. This window steps aside until you have.`)}function f(){P(a,!1),P(i,H(l)?``:n,!0)}function p(){zd(),P(c,!0),P(r,``),P(i,n)}var m=Vr(),h=L(m),g=e=>{xv(e,{done:()=>Il()})},_=e=>{var t=Dv(),n=I(t),o=e=>{var t=Sv(),n=R(L(t),2),r=I(n,!0);O(n);var i=R(n,2),o=I(i);O(i);var c=R(i,2);z(()=>{K(r,H(s).address),K(o,`${Jp.name??``} can't sign in to this account any more, for example because its access was + Anything you add or change syncs back to your server as usual.

    `),cv=W(`

    `),lv=W(`
    `),uv=W(`

    Loading…

    `),dv=W(`

    No folders here.

    `),fv=W(`
    `),pv=W(``),mv=W(`
    `),hv=W(`
  • `),gv=W(`

    `),_v=W(`
    `),vv=W(`
    ↔
    📁
    Local Folder
    `,1),yv=W(`

    Loading account…

    `),bv=W(`
    `);function xv(e,t){Je(t,!0);let n=[],r=N(null),i=N(``),a=N(0),o=N(`everything`),s=N(!1),c=N(``);(async()=>{P(r,await lu(),!0),P(i,H(r).defaultDir,!0),P(a,H(r).freeBytes,!0),!H(r).onDemandSupport&&H(o)===`ondemand`&&P(o,`everything`)})();function l(e){if(!e||e<0)return`—`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=e,r=0;for(;n>=1024&&r0))} ${t[r]}`}async function u(){let e=await Vu(H(i));e&&(P(i,e,!0),P(a,await tu(e),!0))}let d=N(null),f=N(!1),p=N(0),m=!1,h=null;async function g(){P(s,!0),P(c,``),H(o)===`ondemand`&&(P(f,!0),m=!1,P(p,0),h=setInterval(async()=>{P(p,(await Yl())?.adoptScanDirs||0,!0)},1500));let e=H(o)===`everything`&&H(x).length?`everything:`+JSON.stringify(H(x)):H(o),n=await Ll(H(i),e);if(h&&=(clearInterval(h),null),P(f,!1),P(s,!1),m){m=!1;return}if(n){if(H(o)===`ondemand`&&n.startsWith(`{`))try{let e=JSON.parse(n);if(e.error){P(c,e.error,!0);return}P(d,e,!0);return}catch{}P(c,n,!0);return}H(o)===`choose`&&await Mu(),t.done()}async function _(){m=!0,P(f,!1),await Dd(`ondemand-cancel`)}async function v(){P(d,null),P(s,!0);let e=await Dd(`ondemand-adopt`);if(P(s,!1),e){P(c,e,!0);return}t.done()}async function y(){P(d,null),await Dd(`ondemand-cancel`)}function b(){t.done()}let x=N(F([])),S=N(!1),C=N(``),w=N(F([])),ee=N(!1);async function te(e){P(C,e.replace(/^\/+|\/+$/g,``),!0),P(ee,!0),P(w,(await jl(H(C))??[]).filter(e=>e.isDir),!0),P(ee,!1)}function ne(){P(S,!0),te(``)}function re(){H(C)&&te(H(C).split(`/`).slice(0,-1).join(`/`))}function ie(e){P(x,H(x).includes(e)?H(x).filter(t=>t!==e):[...H(x),e],!0)}let ae=M(()=>H(x).length<=3?H(x).join(`, `):`${H(x).slice(0,3).join(`, `)} and ${H(x).length-3} more`);var oe=bv(),se=I(oe),ce=e=>{var t=vv(),m=L(t),h=I(m),oe=I(h),se=I(oe,!0);O(oe);var ce=R(oe,2),le=I(ce,!0);O(ce);var T=R(ce,2),ue=I(T,!0);O(T),O(h);var de=R(h,4),fe=R(I(de),4),pe=I(fe,!0);O(fe);var me=R(fe,2),he=I(me);O(me);var ge=R(me,2);O(de),O(m);var _e=R(m,2),ve=I(_e);let ye;var be=I(ve);X(be),be.value=be.__value=`everything`;var xe=R(be,2),Se=R(I(xe)),Ce=e=>{var t=rv(),n=I(t);O(t),z(e=>K(n,`(${e??``})`),[()=>l(H(r).accountBytes)]),G(e,t)};q(Se,e=>{H(r).accountBytes>0&&e(Ce)}),O(xe),O(ve);var we=R(ve,2),Te=e=>{var t=ov(),n=I(t),r=e=>{var t=iv(),n=L(t),r=R(I(n)),i=I(r,!0);O(r),O(n);var a=R(n,2);z(e=>{Z(n,`title`,e),K(i,H(ae)),a.disabled=H(s)},[()=>H(x).join(`, `)]),U(`click`,a,ne),G(e,t)},i=e=>{var t=av();z(()=>t.disabled=H(s)),U(`click`,t,ne),G(e,t)};q(n,e=>{H(x).length?e(r):e(i,-1)}),O(t),G(e,t)};q(we,e=>{H(o)===`everything`&&e(Te)});var Ee=R(we,2);let De;var Oe=I(Ee);X(Oe),Oe.value=Oe.__value=`choose`,k(2),O(Ee);var ke=R(Ee,2);let Ae;var je=I(ke);X(je),je.value=je.__value=`ondemand`,k(2),O(ke);var E=R(ke,2),Me=e=>{G(e,sv())};q(E,e=>{H(o)===`ondemand`&&e(Me)}),O(_e);var D=R(_e,2),Ne=e=>{var t=cv(),n=I(t,!0);O(t),z(()=>K(n,H(c))),G(e,t)};q(D,e=>{H(c)&&e(Ne)});var Pe=R(D,2),Fe=I(Pe),Ie=R(Fe,2),Le=I(Ie,!0);O(Ie),O(Pe);var Re=R(Pe,2),ze=e=>{var t=lv(),n=I(t),r=R(I(n),2),i=I(r);O(r);var a=R(r,2),o=I(a),s=I(o,!0);O(o),k(),O(a);var c=R(a,2),l=I(c);O(c),O(n),O(t),z(()=>{K(i,`${Jp.name??``} is comparing everything in this folder with your server before setting up. On a large account this can take several minutes. Nothing is changed until you confirm.`),K(s,H(p))}),U(`click`,l,_),G(e,t)};q(Re,e=>{H(f)&&e(ze)});var Be=R(Re,2),Ve=e=>{var t=mv(),n=I(t),r=R(I(n),4),i=I(r),a=R(i,2),o=I(a);O(a),O(r);var s=R(r,2),c=I(s),l=e=>{G(e,uv())},u=e=>{G(e,dv())};q(c,e=>{H(ee)?e(l):H(w).length===0&&e(u,1)}),J(R(c,2),17,()=>H(w),e=>e.path,(e,t)=>{var n=fv(),r=I(n);X(r);var i=R(r,2),a=I(i);O(i),O(n),z(e=>{xi(r,e),Z(i,`title`,`Open ${H(t).name??``}`),K(a,`📁 ${H(t).name??``}`)},[()=>H(x).includes(H(t).path)]),U(`change`,r,()=>ie(H(t).path)),U(`click`,i,()=>te(H(t).path)),G(e,n)}),O(s);var d=R(s,2),f=I(d),p=I(f);O(f);var m=R(f,2),h=e=>{var t=pv();U(`click`,t,()=>P(x,[],!0)),G(e,t)};q(m,e=>{H(x).length&&e(h)});var g=R(m,2);O(d),O(n),O(t),z(()=>{i.disabled=!H(C),Z(a,`title`,`/`+H(C)),K(o,`/${H(C)??``}`),K(p,`${H(x).length??``} chosen`)}),U(`click`,i,re),U(`click`,g,()=>P(S,!1)),G(e,t)};q(Be,e=>{H(S)&&e(Ve)});var He=R(Be,2),Ue=e=>{var t=_v(),n=I(t),r=R(I(n),2),a=R(I(r)),o=I(a,!0);O(a);var s=R(a);O(r);var c=R(r,2),u=I(c),f=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).keep),K(i,` already ${H(d).keep===1?`matches`:`match`} your server — kept on this PC.`)}),G(e,t)};q(u,e=>{H(d).keep&&e(f)});var p=R(u,2),m=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).replace),K(i,` ${H(d).replace===1?`is an online-only leftover`:`are online-only leftovers`} from another app — replaced with ${H(d).replace===1?`a ${Jp.name} placeholder`:`${Jp.name} placeholders`}.`)}),G(e,t)};q(p,e=>{H(d).replace&&e(m)});var h=R(p,2),g=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).upload),K(i,` ${H(d).upload===1?`isn’t`:`aren’t`} on your server yet — uploaded.`)}),G(e,t)};q(h,e=>{H(d).upload&&e(g)});var _=R(h,2),b=e=>{var t=hv(),n=I(t),r=I(n,!0);O(n);var i=R(n);O(t),z(()=>{K(r,H(d).conflict),K(i,` ${H(d).conflict===1?`differs`:`differ`} from your server — both versions kept.`)}),G(e,t)};q(_,e=>{H(d).conflict&&e(b)}),O(c);var x=R(c,2),S=e=>{var t=gv(),n=I(t);O(t),z(e=>K(n,`Upload size: ${e??``}`),[()=>l(H(d).uploadBytes)]),G(e,t)};q(x,e=>{H(d).uploadBytes&&e(S)});var C=R(x,2),w=I(C),ee=R(w,2);O(C),O(n),O(t),z(()=>{K(o,H(i)),K(s,`. ${Jp.name??``} can keep them where they are instead of downloading everything again.`)}),U(`click`,w,y),U(`click`,ee,v),G(e,t)};q(He,e=>{H(d)&&e(Ue)}),z((e,t,n)=>{K(se,e),K(le,H(r).user),K(ue,t),Z(fe,`title`,H(i)),K(pe,H(i)),K(he,`${n??``} free`),ge.disabled=H(s),ye=Y(ve,1,`opt svelte-1wxc8fs`,null,ye,{sel:H(o)===`everything`}),be.disabled=H(s),De=Y(Ee,1,`opt svelte-1wxc8fs`,null,De,{sel:H(o)===`choose`}),Oe.disabled=H(s),Ae=Y(ke,1,`opt svelte-1wxc8fs`,null,Ae,{sel:H(o)===`ondemand`,off:!H(r).onDemandSupport}),je.disabled=H(s)||!H(r).onDemandSupport,Fe.disabled=H(s),Ie.disabled=H(s)||!H(i),K(Le,H(s)?`Setting up…`:`Connect`)},[()=>(H(r).user||`?`).charAt(0).toUpperCase(),()=>H(r).server.replace(/^https?:\/\//,``),()=>l(H(a))]),U(`click`,ge,u),Di(n,[],be,()=>H(o),e=>P(o,e)),Di(n,[],Oe,()=>H(o),e=>P(o,e)),Di(n,[],je,()=>H(o),e=>P(o,e)),U(`click`,Fe,b),U(`click`,Ie,g),G(e,t)},le=e=>{G(e,yv())};q(se,e=>{H(r)?e(ce):e(le,-1)}),O(oe),G(e,oe),Ye()}jr([`click`,`change`]);var Sv=W(`

    Sign in again

    `,1),Cv=W(`

    Sign in to Nextcloud

    `,1),wv=W(``),Tv=W(`

    `),Ev=W(``),Dv=W(``);function Ov(e,t){Je(t,!0);let n=`Enter your Nextcloud server address to begin.`,r=N(``),i=N(n),a=N(!1),o=N(`signin`),s=N(null),c=N(!1),l=M(()=>!!H(s)&&!H(c)),u=N(``),d=N(!1);async function f(){let e=await Bd();P(s,e?.id?e:null,!0),P(c,!1),H(a)||(P(r,H(s)?H(s).server:H(u),!0),P(i,H(s)?``:n,!0))}f(),(async()=>{let e=await Uu();e?.serverUrl&&(P(u,e.serverUrl,!0),P(d,e.lockServer,!0),!H(a)&&!H(s)&&(H(d)||!H(r))&&P(r,H(u),!0))})(),ns(`login:target`,f),ns(`login:error`,e=>{H(a)&&(P(i,`Sign-in failed: `+e.data),P(a,!1))}),ns(`login:done`,async e=>{if(P(a,!1),e.data===!0){P(i,`Signed in again.`),await Il();return}await $l(),P(o,`setup`)});async function p(){if(!H(r).trim())return;P(a,!0),P(i,`Connecting…`);let e=await Tl(H(r));if(e.startsWith(`error:`)){P(i,e,!0),P(a,!1);return}P(i,`Finish signing in in the browser that just opened. This window steps aside until you have.`)}function m(){P(a,!1),P(i,H(l)?``:n,!0)}function h(){zd(),P(c,!0),P(r,H(u),!0),P(i,n)}var g=Vr(),_=L(g),v=e=>{xv(e,{done:()=>Il()})},y=e=>{var t=Dv(),n=I(t),o=e=>{var t=Sv(),n=R(L(t),2),r=I(n,!0);O(n);var i=R(n,2),o=I(i);O(i);var c=R(i,2);z(()=>{K(r,H(s).address),K(o,`${Jp.name??``} can't sign in to this account any more, for example because its access was removed in Nextcloud. Sign in again and it carries on where it left off: your folders and - settings are kept, and nothing needs setting up again.`),c.disabled=H(a)}),U(`click`,c,d),G(e,t)},c=e=>{var t=Cv(),n=R(L(t),2);X(n);var i=R(n,2);z(()=>{n.disabled=H(a),i.disabled=H(a)}),U(`keydown`,n,e=>e.key===`Enter`&&d()),Ti(n,()=>H(r),e=>P(r,e)),U(`click`,i,d),G(e,t)};q(n,e=>{H(l)&&H(s)?e(o):e(c,-1)});var u=R(n,2),m=e=>{var t=wv();U(`click`,t,f),G(e,t)};q(u,e=>{H(a)&&e(m)});var h=R(u,2),g=e=>{var t=Tv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(h,e=>{H(i)&&e(g)});var _=R(h,2),v=e=>{var t=Ev();U(`click`,t,p),G(e,t)};q(_,e=>{H(l)&&!H(a)&&e(v)}),O(t),G(e,t)};q(h,e=>{H(o)===`setup`?e(g):e(_,-1)}),G(e,m),Ye()}jr([`click`,`keydown`]);var kv=W(`

    `),Av=W(`

    Not shared yet.

    `),jv=W(`
    `),Mv=W(`
    `),Nv=W(``),Pv=W(`
    Sharing

    Shares

    Create public link

    Share with a user

    `);function Fv(e,t){Je(t,!0);let n=N(``),r=N(F([])),i=N(``),a=N(!1),o=N(``),s=N(``),c=N(``),l=N(!1),u=N(``),d=N(!1);async function f(){P(n,await jd(),!0),P(r,await Ad()??[],!0)}f(),ns(`share:target`,()=>{P(i,``),P(o,``),f()});let p=e=>e.replace(/\/+$/,``).split(`/`).pop()||e,m=e=>(e&2)!=0,h=e=>e===3;async function g(e){P(i,``),P(a,!0);let t=await e();return P(a,!1),t?(P(i,t,!0),!1):(await f(),!0)}async function _(){await g(()=>Vl(H(s).trim(),H(c).trim(),H(l)))&&(P(s,``),P(c,``),P(l,!1))}async function v(){await g(()=>Hl(H(u),H(d)))&&(P(u,``),P(d,!1))}let y=e=>g(()=>Gl(e));async function b(e,t){await Bl(e),P(o,t,!0),setTimeout(()=>{H(o)===t&&P(o,``)},1500)}let x=e=>Iu(e);var S=Pv(),C=I(S),w=R(I(C),2),ee=I(w);O(w);var te=R(w,2),ne=I(te);O(te),O(C);var re=R(C,2),ie=I(re),ae=e=>{var t=kv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(ie,e=>{H(i)&&e(ae)});var oe=R(ie,4),se=e=>{G(e,Av())},ce=e=>{var t=Vr();J(L(t),17,()=>H(r),Yr,(e,t)=>{var n=Nv(),r=I(n),i=I(r),s=I(i),c=e=>{G(e,Br(`🔗 Public link`))},l=M(()=>h(H(t).type)),u=e=>{var n=Br();z(()=>K(n,`👤 ${H(t).shareWith??``}`)),G(e,n)};q(s,e=>{H(l)?e(c):e(u,-1)}),O(i);var d=R(i,2),f=I(d,!0);O(d);var p=R(d,2);O(r);var g=R(r,2),_=e=>{var n=jv(),r=I(n);X(r);var i=R(r,2),a=I(i,!0);O(i);var s=R(i,2);O(n),z(()=>{bi(r,H(t).url),K(a,H(o)===H(t).id?`Copied!`:`Copy`)}),U(`click`,i,()=>b(H(t).url,H(t).id)),U(`click`,s,()=>x(H(t).url)),G(e,n)},v=M(()=>h(H(t).type)&&H(t).url);q(g,e=>{H(v)&&e(_)});var S=R(g,2),C=e=>{var n=Mv(),r=I(n);O(n),z(e=>K(r,`Expires ${e??``}`),[()=>H(t).expiration.slice(0,10)]),G(e,n)};q(S,e=>{H(t).expiration&&e(C)}),O(n),z(e=>{K(f,e),p.disabled=H(a)},[()=>m(H(t).permissions)?`Can edit`:`View only`]),U(`click`,p,()=>y(H(t).id)),G(e,n)}),G(e,t)};q(oe,e=>{H(r).length===0?e(se):e(ce,-1)});var le=R(oe,4),T=I(le),ue=R(I(T));X(ue),O(T);var de=R(T,2),fe=R(I(de));X(fe),O(de);var pe=R(de,2),me=I(pe);X(me),k(),O(pe);var he=R(pe,2);O(le);var ge=R(le,4),_e=I(ge),ve=R(I(_e));X(ve),O(_e);var ye=R(_e,2),be=I(ye);X(be),k(),O(ye);var xe=R(ye,2);O(ge),O(re),O(S),z(e=>{Z(w,`title`,H(n)),K(ee,`📄 ${e??``}`),K(ne,`/${H(n)??``}`),he.disabled=H(a),xe.disabled=H(a)},[()=>p(H(n))]),Ti(ue,()=>H(s),e=>P(s,e)),Ti(fe,()=>H(c),e=>P(c,e)),Oi(me,()=>H(l),e=>P(l,e)),U(`click`,he,_),U(`keydown`,ve,e=>e.key===`Enter`&&v()),Ti(ve,()=>H(u),e=>P(u,e)),Oi(be,()=>H(d),e=>P(d,e)),U(`click`,xe,v),G(e,S),Ye()}jr([`click`,`keydown`]);var Iv=W(`

    `),Lv=W(`

    Loading…

    `),Rv=W(`

    No previous versions. Nextcloud keeps versions as a file changes over time.

    `),zv=W(`
    `),Bv=W(`

    Restoring makes that version the current file (the present copy is kept as a version too).

    `,1),Vv=W(`
    Version history
    `);function Hv(e,t){Je(t,!0);let n=N(``),r=N(F([])),i=N(``),a=N(!1),o=N(!0);async function s(){P(o,!0),P(n,await ef(),!0),P(r,await $d()??[],!0),P(o,!1)}s(),ns(`versions:target`,()=>{P(i,``),s()});let c=e=>e.replace(/\/+$/,``).split(`/`).pop()||e;function l(e){if(!e||e<0)return`—`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=0;for(;e>=1024&&n0?e.toFixed(1):Math.round(e)} ${t[n]}`}async function u(e){P(i,``),P(a,!0);let t=await rd(e);if(P(a,!1),t){P(i,t,!0);return}await s()}var d=Vv(),f=I(d),p=R(I(f),2),m=I(p);O(p);var h=R(p,2),g=I(h);O(h),O(f);var _=R(f,2),v=I(_),y=e=>{var t=Iv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(v,e=>{H(i)&&e(y)});var b=R(v,2),x=e=>{G(e,Lv())},S=e=>{G(e,Rv())},C=e=>{var t=Bv();J(R(L(t),2),17,()=>H(r),Yr,(e,t)=>{var n=zv(),r=I(n),i=I(r),o=I(i,!0);O(i);var s=R(i,2),c=I(s,!0);O(s),O(r);var d=R(r,2);O(n),z(e=>{K(o,H(t).modified),K(c,e),d.disabled=H(a)},[()=>l(H(t).size)]),U(`click`,d,()=>u(H(t).href)),G(e,n)}),G(e,t)};q(b,e=>{H(o)?e(x):H(r).length===0?e(S,1):e(C,-1)}),O(_),O(d),z(e=>{Z(p,`title`,H(n)),K(m,`📄 ${e??``}`),K(g,`/${H(n)??``}`)},[()=>c(H(n))]),G(e,d),Ye()}jr([`click`]);var Uv=W(`
    Logs
     
    `);function Wv(e,t){Je(t,!0);let n=N(``),r=N(``),i=N(!1),a=N(!1),o;async function s(e=!0){P(n,await Gd(),!0),P(r,await bu(),!0),P(i,await Zd(),!0),e&&requestAnimationFrame(()=>{o&&(o.scrollTop=o.scrollHeight)})}s();async function c(){await Bl(H(n)),P(a,!0),setTimeout(()=>P(a,!1),1500)}function l(){P(i,!H(i)),kd(H(i))}var u=Uv(),d=I(u),f=R(I(d),2),p=I(f,!0);O(f);var m=R(f,2),h=I(m),g=R(h,2),_=I(g,!0);O(g);var v=R(g,2),y=R(v,2),b=I(y);X(b),k(),O(y),O(m),O(d);var x=R(d,2),S=I(x,!0);O(x),Ni(x,e=>o=e,()=>o),O(u),z(()=>{Z(f,`title`,H(r)),K(p,H(r)),K(_,H(a)?`Copied!`:`Copy`),xi(b,H(i)),K(S,H(n)||`(no log yet)`)}),U(`click`,h,()=>s()),U(`click`,g,c),U(`click`,v,()=>Au()),U(`change`,b,l),G(e,u),Ye()}jr([`click`,`change`]),He();var Gv=W(``),Kv=W(`🗂`),qv=W(`
    Loading from your server…
    `);function Jv(e,t){Je(t,!1);let n=window.location.hash.slice(1),r=n.startsWith(`appsplash/`)?n.slice(10):``,i=r.indexOf(`?`),a=decodeURIComponent(i>=0?r.slice(0,i):r)||`App`,o=i>=0&&new URLSearchParams(r.slice(i)).get(`icon`)||``;Pi();var s=qv(),c=I(s),l=I(c),u=e=>{var t=Gv();z(()=>Z(t,`src`,o)),G(e,t)},d=e=>{G(e,Kv())};q(l,e=>{o?e(u):e(d,-1)}),O(c);var f=R(c,2),p=I(f,!0);O(f),k(4),O(s),z(()=>K(p,a)),G(e,s),Ye()}var Yv=W(`

    `),Xv=W(``),Zv=W(``),Qv=W(`
    `);function $v(e,t){Je(t,!0);let n=N(``),r=N(null),i=N(null);Dn(()=>{let e=Yp.current;e&&(P(n,e.value,!0),queueMicrotask(()=>{e.kind===`text`&&H(i)?(H(i).focus(),H(i).select()):H(r)?.focus()}))});function a(){let e=Yp.current;e&&Qp(e.kind===`ask`?!0:e.kind===`text`?H(n):void 0)}function o(){let e=Yp.current;e&&Qp(e.kind===`ask`?!1:e.kind===`text`?null:void 0)}function s(e){Yp.current&&(e.key===`Escape`?(e.preventDefault(),o()):e.key===`Enter`&&Yp.current.kind===`text`&&(e.preventDefault(),a()))}var c=Vr();Ar(`keydown`,on,s);var l=L(c),u=e=>{let t=M(()=>Yp.current);var s=Qv(),c=I(s),l=I(c),u=I(l,!0);O(l);var d=R(l,2),f=e=>{var n=Yv(),r=I(n,!0);O(n),z(()=>K(r,H(t).message)),G(e,n)};q(d,e=>{H(t).message&&e(f)});var p=R(d,2),m=e=>{var t=Xv();X(t),Ni(t,e=>P(i,e),()=>H(i)),Ti(t,()=>H(n),e=>P(n,e)),G(e,t)};q(p,e=>{H(t).kind===`text`&&e(m)});var h=R(p,2),g=I(h),_=e=>{var n=Zv(),r=I(n,!0);O(n),z(()=>K(r,H(t).cancel)),U(`click`,n,o),G(e,n)};q(g,e=>{H(t).kind!==`tell`&&e(_)});var v=R(g,2);let y;var b=I(v,!0);O(v),Ni(v,e=>P(r,e),()=>H(r)),O(h),O(c),O(s),z(()=>{K(u,H(t).title),y=Y(v,1,`primary svelte-11qie5r`,null,y,{danger:H(t).danger}),K(b,H(t).ok)}),U(`click`,v,a),G(e,s)};q(l,e=>{Yp.current&&e(u)}),G(e,c),Ye()}jr([`click`]);var ey=W(` `,1);function ty(e,t){Je(t,!0);let n=N(F(window.location.hash.replace(`#`,``)));window.addEventListener(`hashchange`,()=>{P(n,window.location.hash.replace(`#`,``),!0)});let r=window.matchMedia(`(prefers-color-scheme: dark)`),i=`system`,a=``;function o(){let e;e=i===`dark`?!0:i===`light`?!1:a===`dark`?!0:a===`light`?!1:r.matches,e?document.documentElement.setAttribute(`data-theme`,`dark`):document.documentElement.removeAttribute(`data-theme`)}r.addEventListener(`change`,o),ns(`theme`,e=>{i=e.data||`system`,o()}),(async()=>{i=await du(),o()})();let s=0;async function c(){let e=++s;for(let t=0;t<12&&e===s;t++){let e=await wu();if(e){a=e,o();return}await new Promise(e=>setTimeout(e,1e3))}}c();function l(e,t){let n=e.replace(`#`,``);return n.length===6?`#`+[0,2,4].map(e=>Math.max(0,Math.round(parseInt(n.slice(e,e+2),16)*t))).map(e=>e.toString(16).padStart(2,`0`)).join(``):e}function u(e){let t=document.documentElement;e&&/^#[0-9a-fA-F]{6}$/.test(e)?(t.style.setProperty(`--accent`,e),t.style.setProperty(`--accent-dark`,l(e,.88))):(t.style.removeProperty(`--accent`),t.style.removeProperty(`--accent-dark`))}ns(`accent`,e=>{u(e.data||``),c()}),(async()=>{for(let e=0;e<12;e++){try{let e=await qd();if(e&&/^#[0-9a-fA-F]{6}$/.test(e)){u(e);return}}catch{}await new Promise(e=>setTimeout(e,1e3))}})();var d=ey(),f=L(d),p=e=>{Jv(e,{})},m=M(()=>H(n).startsWith(`appsplash`)),h=e=>{yh(e,{})},g=e=>{nv(e,{})},_=M(()=>H(n).startsWith(`settings`)),v=e=>{Ov(e,{})},y=e=>{Fv(e,{})},b=e=>{Hv(e,{})},x=e=>{Wv(e,{})},S=e=>{qp(e,{})};q(f,e=>{H(m)?e(p):H(n)===`status`?e(h,1):H(_)?e(g,2):H(n)===`login`?e(v,3):H(n)===`share`?e(y,4):H(n)===`versions`?e(b,5):H(n)===`logs`?e(x,6):e(S,-1)}),$v(R(f,2),{}),G(e,d),Ye()}Hr(ty,{target:document.getElementById(`app`)}); \ No newline at end of file + settings are kept, and nothing needs setting up again.`),c.disabled=H(a)}),U(`click`,c,p),G(e,t)},c=e=>{var t=Cv(),n=R(L(t),2);X(n);var i=R(n,2);z(()=>{n.disabled=H(a)||H(d),Z(n,`title`,H(d)?`This server is set for you`:``),i.disabled=H(a)}),U(`keydown`,n,e=>e.key===`Enter`&&p()),Ti(n,()=>H(r),e=>P(r,e)),U(`click`,i,p),G(e,t)};q(n,e=>{H(l)&&H(s)?e(o):e(c,-1)});var u=R(n,2),f=e=>{var t=wv();U(`click`,t,m),G(e,t)};q(u,e=>{H(a)&&e(f)});var g=R(u,2),_=e=>{var t=Tv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(g,e=>{H(i)&&e(_)});var v=R(g,2),y=e=>{var t=Ev();U(`click`,t,h),G(e,t)};q(v,e=>{H(l)&&!H(a)&&e(y)}),O(t),G(e,t)};q(_,e=>{H(o)===`setup`?e(v):e(y,-1)}),G(e,g),Ye()}jr([`click`,`keydown`]);var kv=W(`

    `),Av=W(`

    Not shared yet.

    `),jv=W(`
    `),Mv=W(`
    `),Nv=W(``),Pv=W(`
    Sharing

    Shares

    Create public link

    Share with a user

    `);function Fv(e,t){Je(t,!0);let n=N(``),r=N(F([])),i=N(``),a=N(!1),o=N(``),s=N(``),c=N(``),l=N(!1),u=N(``),d=N(!1);async function f(){P(n,await jd(),!0),P(r,await Ad()??[],!0)}f(),ns(`share:target`,()=>{P(i,``),P(o,``),f()});let p=e=>e.replace(/\/+$/,``).split(`/`).pop()||e,m=e=>(e&2)!=0,h=e=>e===3;async function g(e){P(i,``),P(a,!0);let t=await e();return P(a,!1),t?(P(i,t,!0),!1):(await f(),!0)}async function _(){await g(()=>Vl(H(s).trim(),H(c).trim(),H(l)))&&(P(s,``),P(c,``),P(l,!1))}async function v(){await g(()=>Hl(H(u),H(d)))&&(P(u,``),P(d,!1))}let y=e=>g(()=>Gl(e));async function b(e,t){await Bl(e),P(o,t,!0),setTimeout(()=>{H(o)===t&&P(o,``)},1500)}let x=e=>Iu(e);var S=Pv(),C=I(S),w=R(I(C),2),ee=I(w);O(w);var te=R(w,2),ne=I(te);O(te),O(C);var re=R(C,2),ie=I(re),ae=e=>{var t=kv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(ie,e=>{H(i)&&e(ae)});var oe=R(ie,4),se=e=>{G(e,Av())},ce=e=>{var t=Vr();J(L(t),17,()=>H(r),Yr,(e,t)=>{var n=Nv(),r=I(n),i=I(r),s=I(i),c=e=>{G(e,Br(`🔗 Public link`))},l=M(()=>h(H(t).type)),u=e=>{var n=Br();z(()=>K(n,`👤 ${H(t).shareWith??``}`)),G(e,n)};q(s,e=>{H(l)?e(c):e(u,-1)}),O(i);var d=R(i,2),f=I(d,!0);O(d);var p=R(d,2);O(r);var g=R(r,2),_=e=>{var n=jv(),r=I(n);X(r);var i=R(r,2),a=I(i,!0);O(i);var s=R(i,2);O(n),z(()=>{bi(r,H(t).url),K(a,H(o)===H(t).id?`Copied!`:`Copy`)}),U(`click`,i,()=>b(H(t).url,H(t).id)),U(`click`,s,()=>x(H(t).url)),G(e,n)},v=M(()=>h(H(t).type)&&H(t).url);q(g,e=>{H(v)&&e(_)});var S=R(g,2),C=e=>{var n=Mv(),r=I(n);O(n),z(e=>K(r,`Expires ${e??``}`),[()=>H(t).expiration.slice(0,10)]),G(e,n)};q(S,e=>{H(t).expiration&&e(C)}),O(n),z(e=>{K(f,e),p.disabled=H(a)},[()=>m(H(t).permissions)?`Can edit`:`View only`]),U(`click`,p,()=>y(H(t).id)),G(e,n)}),G(e,t)};q(oe,e=>{H(r).length===0?e(se):e(ce,-1)});var le=R(oe,4),T=I(le),ue=R(I(T));X(ue),O(T);var de=R(T,2),fe=R(I(de));X(fe),O(de);var pe=R(de,2),me=I(pe);X(me),k(),O(pe);var he=R(pe,2);O(le);var ge=R(le,4),_e=I(ge),ve=R(I(_e));X(ve),O(_e);var ye=R(_e,2),be=I(ye);X(be),k(),O(ye);var xe=R(ye,2);O(ge),O(re),O(S),z(e=>{Z(w,`title`,H(n)),K(ee,`📄 ${e??``}`),K(ne,`/${H(n)??``}`),he.disabled=H(a),xe.disabled=H(a)},[()=>p(H(n))]),Ti(ue,()=>H(s),e=>P(s,e)),Ti(fe,()=>H(c),e=>P(c,e)),Oi(me,()=>H(l),e=>P(l,e)),U(`click`,he,_),U(`keydown`,ve,e=>e.key===`Enter`&&v()),Ti(ve,()=>H(u),e=>P(u,e)),Oi(be,()=>H(d),e=>P(d,e)),U(`click`,xe,v),G(e,S),Ye()}jr([`click`,`keydown`]);var Iv=W(`

    `),Lv=W(`

    Loading…

    `),Rv=W(`

    No previous versions. Nextcloud keeps versions as a file changes over time.

    `),zv=W(`
    `),Bv=W(`

    Restoring makes that version the current file (the present copy is kept as a version too).

    `,1),Vv=W(`
    Version history
    `);function Hv(e,t){Je(t,!0);let n=N(``),r=N(F([])),i=N(``),a=N(!1),o=N(!0);async function s(){P(o,!0),P(n,await ef(),!0),P(r,await $d()??[],!0),P(o,!1)}s(),ns(`versions:target`,()=>{P(i,``),s()});let c=e=>e.replace(/\/+$/,``).split(`/`).pop()||e;function l(e){if(!e||e<0)return`—`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=0;for(;e>=1024&&n0?e.toFixed(1):Math.round(e)} ${t[n]}`}async function u(e){P(i,``),P(a,!0);let t=await rd(e);if(P(a,!1),t){P(i,t,!0);return}await s()}var d=Vv(),f=I(d),p=R(I(f),2),m=I(p);O(p);var h=R(p,2),g=I(h);O(h),O(f);var _=R(f,2),v=I(_),y=e=>{var t=Iv(),n=I(t,!0);O(t),z(()=>K(n,H(i))),G(e,t)};q(v,e=>{H(i)&&e(y)});var b=R(v,2),x=e=>{G(e,Lv())},S=e=>{G(e,Rv())},C=e=>{var t=Bv();J(R(L(t),2),17,()=>H(r),Yr,(e,t)=>{var n=zv(),r=I(n),i=I(r),o=I(i,!0);O(i);var s=R(i,2),c=I(s,!0);O(s),O(r);var d=R(r,2);O(n),z(e=>{K(o,H(t).modified),K(c,e),d.disabled=H(a)},[()=>l(H(t).size)]),U(`click`,d,()=>u(H(t).href)),G(e,n)}),G(e,t)};q(b,e=>{H(o)?e(x):H(r).length===0?e(S,1):e(C,-1)}),O(_),O(d),z(e=>{Z(p,`title`,H(n)),K(m,`📄 ${e??``}`),K(g,`/${H(n)??``}`)},[()=>c(H(n))]),G(e,d),Ye()}jr([`click`]);var Uv=W(`
    Logs
     
    `);function Wv(e,t){Je(t,!0);let n=N(``),r=N(``),i=N(!1),a=N(!1),o;async function s(e=!0){P(n,await Gd(),!0),P(r,await bu(),!0),P(i,await Zd(),!0),e&&requestAnimationFrame(()=>{o&&(o.scrollTop=o.scrollHeight)})}s();async function c(){await Bl(H(n)),P(a,!0),setTimeout(()=>P(a,!1),1500)}function l(){P(i,!H(i)),kd(H(i))}var u=Uv(),d=I(u),f=R(I(d),2),p=I(f,!0);O(f);var m=R(f,2),h=I(m),g=R(h,2),_=I(g,!0);O(g);var v=R(g,2),y=R(v,2),b=I(y);X(b),k(),O(y),O(m),O(d);var x=R(d,2),S=I(x,!0);O(x),Ni(x,e=>o=e,()=>o),O(u),z(()=>{Z(f,`title`,H(r)),K(p,H(r)),K(_,H(a)?`Copied!`:`Copy`),xi(b,H(i)),K(S,H(n)||`(no log yet)`)}),U(`click`,h,()=>s()),U(`click`,g,c),U(`click`,v,()=>Au()),U(`change`,b,l),G(e,u),Ye()}jr([`click`,`change`]),He();var Gv=W(``),Kv=W(`🗂`),qv=W(`
    Loading from your server…
    `);function Jv(e,t){Je(t,!1);let n=window.location.hash.slice(1),r=n.startsWith(`appsplash/`)?n.slice(10):``,i=r.indexOf(`?`),a=decodeURIComponent(i>=0?r.slice(0,i):r)||`App`,o=i>=0&&new URLSearchParams(r.slice(i)).get(`icon`)||``;Pi();var s=qv(),c=I(s),l=I(c),u=e=>{var t=Gv();z(()=>Z(t,`src`,o)),G(e,t)},d=e=>{G(e,Kv())};q(l,e=>{o?e(u):e(d,-1)}),O(c);var f=R(c,2),p=I(f,!0);O(f),k(4),O(s),z(()=>K(p,a)),G(e,s),Ye()}var Yv=W(`

    `),Xv=W(``),Zv=W(``),Qv=W(`
    `);function $v(e,t){Je(t,!0);let n=N(``),r=N(null),i=N(null);Dn(()=>{let e=Yp.current;e&&(P(n,e.value,!0),queueMicrotask(()=>{e.kind===`text`&&H(i)?(H(i).focus(),H(i).select()):H(r)?.focus()}))});function a(){let e=Yp.current;e&&Qp(e.kind===`ask`?!0:e.kind===`text`?H(n):void 0)}function o(){let e=Yp.current;e&&Qp(e.kind===`ask`?!1:e.kind===`text`?null:void 0)}function s(e){Yp.current&&(e.key===`Escape`?(e.preventDefault(),o()):e.key===`Enter`&&Yp.current.kind===`text`&&(e.preventDefault(),a()))}var c=Vr();Ar(`keydown`,on,s);var l=L(c),u=e=>{let t=M(()=>Yp.current);var s=Qv(),c=I(s),l=I(c),u=I(l,!0);O(l);var d=R(l,2),f=e=>{var n=Yv(),r=I(n,!0);O(n),z(()=>K(r,H(t).message)),G(e,n)};q(d,e=>{H(t).message&&e(f)});var p=R(d,2),m=e=>{var t=Xv();X(t),Ni(t,e=>P(i,e),()=>H(i)),Ti(t,()=>H(n),e=>P(n,e)),G(e,t)};q(p,e=>{H(t).kind===`text`&&e(m)});var h=R(p,2),g=I(h),_=e=>{var n=Zv(),r=I(n,!0);O(n),z(()=>K(r,H(t).cancel)),U(`click`,n,o),G(e,n)};q(g,e=>{H(t).kind!==`tell`&&e(_)});var v=R(g,2);let y;var b=I(v,!0);O(v),Ni(v,e=>P(r,e),()=>H(r)),O(h),O(c),O(s),z(()=>{K(u,H(t).title),y=Y(v,1,`primary svelte-11qie5r`,null,y,{danger:H(t).danger}),K(b,H(t).ok)}),U(`click`,v,a),G(e,s)};q(l,e=>{Yp.current&&e(u)}),G(e,c),Ye()}jr([`click`]);var ey=W(` `,1);function ty(e,t){Je(t,!0);let n=N(F(window.location.hash.replace(`#`,``)));window.addEventListener(`hashchange`,()=>{P(n,window.location.hash.replace(`#`,``),!0)});let r=window.matchMedia(`(prefers-color-scheme: dark)`),i=`system`,a=``;function o(){let e;e=i===`dark`?!0:i===`light`?!1:a===`dark`?!0:a===`light`?!1:r.matches,e?document.documentElement.setAttribute(`data-theme`,`dark`):document.documentElement.removeAttribute(`data-theme`)}r.addEventListener(`change`,o),ns(`theme`,e=>{i=e.data||`system`,o()}),(async()=>{i=await du(),o()})();let s=0;async function c(){let e=++s;for(let t=0;t<12&&e===s;t++){let e=await wu();if(e){a=e,o();return}await new Promise(e=>setTimeout(e,1e3))}}c();function l(e,t){let n=e.replace(`#`,``);return n.length===6?`#`+[0,2,4].map(e=>Math.max(0,Math.round(parseInt(n.slice(e,e+2),16)*t))).map(e=>e.toString(16).padStart(2,`0`)).join(``):e}function u(e){let t=document.documentElement;e&&/^#[0-9a-fA-F]{6}$/.test(e)?(t.style.setProperty(`--accent`,e),t.style.setProperty(`--accent-dark`,l(e,.88))):(t.style.removeProperty(`--accent`),t.style.removeProperty(`--accent-dark`))}ns(`accent`,e=>{u(e.data||``),c()}),(async()=>{for(let e=0;e<12;e++){try{let e=await qd();if(e&&/^#[0-9a-fA-F]{6}$/.test(e)){u(e);return}}catch{}await new Promise(e=>setTimeout(e,1e3))}})();var d=ey(),f=L(d),p=e=>{Jv(e,{})},m=M(()=>H(n).startsWith(`appsplash`)),h=e=>{yh(e,{})},g=e=>{nv(e,{})},_=M(()=>H(n).startsWith(`settings`)),v=e=>{Ov(e,{})},y=e=>{Fv(e,{})},b=e=>{Hv(e,{})},x=e=>{Wv(e,{})},S=e=>{qp(e,{})};q(f,e=>{H(m)?e(p):H(n)===`status`?e(h,1):H(_)?e(g,2):H(n)===`login`?e(v,3):H(n)===`share`?e(y,4):H(n)===`versions`?e(b,5):H(n)===`logs`?e(x,6):e(S,-1)}),$v(R(f,2),{}),G(e,d),Ye()}Hr(ty,{target:document.getElementById(`app`)}); \ No newline at end of file diff --git a/cmd/nimbo-gui/frontend/dist/index.html b/cmd/nimbo-gui/frontend/dist/index.html index 570dd7f..de0cefb 100644 --- a/cmd/nimbo-gui/frontend/dist/index.html +++ b/cmd/nimbo-gui/frontend/dist/index.html @@ -6,7 +6,7 @@ Wails + Svelte + TS - + diff --git a/cmd/nimbo-gui/frontend/src/Login.svelte b/cmd/nimbo-gui/frontend/src/Login.svelte index 23cb077..c7fb315 100644 --- a/cmd/nimbo-gui/frontend/src/Login.svelte +++ b/cmd/nimbo-gui/frontend/src/Login.svelte @@ -18,16 +18,29 @@ let different = $state(false); const again = $derived(!!target && !different); + // A server preset (admin policy, or a partner build's brand.json) fills the + // address in; a locked one also stops it being changed, since sign-in goes + // to that server whatever is typed (Deck d1010). + let preset = $state(""); + let locked = $state(false); + async function loadTarget() { const t = await App.SignInTarget(); target = t?.id ? t : null; different = false; if (!busy) { - server = target ? target.server : ""; + server = target ? target.server : preset; msg = target ? "" : startMsg; } } loadTarget(); + (async () => { + const p = await App.PolicyInfo(); + if (!p?.serverUrl) return; + preset = p.serverUrl; + locked = p.lockServer; + if (!busy && !target && (locked || !server)) server = preset; + })(); Events.On("login:target", loadTarget); Events.On("login:error", (e: any) => { if (!busy) return; msg = "Sign-in failed: " + e.data; busy = false; }); @@ -65,7 +78,7 @@ function signInSomeoneElse() { App.SignInSomeoneElse(); // the grant may now be for any account different = true; - server = ""; + server = preset; msg = startMsg; } @@ -83,7 +96,8 @@ {:else}

    Sign in to Nextcloud

    - e.key === "Enter" && login()} /> {/if} diff --git a/cmd/nimbo-gui/service.go b/cmd/nimbo-gui/service.go index 81cd1eb..4b1d2ae 100644 --- a/cmd/nimbo-gui/service.go +++ b/cmd/nimbo-gui/service.go @@ -778,14 +778,29 @@ func (a *App) refreshLicence() { if info.Allows(license.TierBusiness) { pol = policy.Load() // admin policy is a business-tier capability } + pol = withBrandServer(pol, brand.Current) a.licMu.Lock() a.lic = info a.pol = pol a.licMu.Unlock() } +// withBrandServer adds a partner build's baked-in server preset and lock to +// the policy (Deck d1010). It needs no licence: the partner paid for the build. +// An admin policy's own server address wins over the brand's; a lock from +// either side holds. +func withBrandServer(p policy.Policy, b brand.Brand) policy.Policy { + if p.ServerURL == "" { + p.ServerURL = b.ServerURL + } + if b.LockServer && p.ServerURL != "" { + p.LockServer = true + } + return p +} + // policyNow returns the effective admin policy (empty/permissive unless a -// business licence is active). +// business licence is active), plus a partner build's server lock. func (a *App) policyNow() policy.Policy { a.licMu.Lock() defer a.licMu.Unlock() diff --git a/internal/brand/brand.go b/internal/brand/brand.go index ab3fc88..d1a3133 100644 --- a/internal/brand/brand.go +++ b/internal/brand/brand.go @@ -11,6 +11,7 @@ import ( "encoding/json" "fmt" "log/slog" + "net/url" "strings" ) @@ -31,6 +32,12 @@ type Brand struct { APIBase string `json:"apiBase"` // GitHub releases API root for the update check AccentHex string `json:"accentHex"` // brand accent (tray badge, UI accent fallback) AppID string `json:"appId"` // MSIX Application Id (AUMID) + self-update task name + the config/data dir name (slugged) + // ServerURL presets the Nextcloud server the sign-in window offers, and + // LockServer pins sign-in to it, the same lock the HKLM admin policy gives + // but baked into a partner build, so it needs no licence key or registry + // step on a client's PC (Deck d1010). Both are empty in the stock build. + ServerURL string `json:"serverUrl,omitempty"` + LockServer bool `json:"lockServer,omitempty"` } // Current is the active brand for this build. @@ -66,14 +73,37 @@ func (b Brand) Validate() error { return nil } +// ValidateServer checks the server preset: an https address with a host, and +// a lock only together with one. build-partner.ps1 refuses a brand that fails +// it, so a broken one should never ship. +func (b Brand) ValidateServer() error { + if b.ServerURL == "" { + if b.LockServer { + return fmt.Errorf("brand.json: lockServer is set but serverUrl is empty") + } + return nil + } + u, err := url.Parse(b.ServerURL) + if err != nil || u.Scheme != "https" || u.Host == "" { + return fmt.Errorf("brand.json: serverUrl %q must be an https:// address", b.ServerURL) + } + return nil +} + // sanitize drops a former name that fails Validate, so such a build runs as a // brand that was never renamed: its own Start-menu folder and labels, no -// rename notice. +// rename notice. A server preset that fails ValidateServer is dropped with its +// lock: a lock to an unusable address would leave nobody able to sign in. func sanitize(b Brand) Brand { if err := b.Validate(); err != nil { slog.Error("ignoring formerName", "err", err) b.FormerName = "" } + if err := b.ValidateServer(); err != nil { + slog.Error("ignoring serverUrl and lockServer", "err", err) + b.ServerURL, b.LockServer = "", false + } + b.ServerURL = strings.TrimRight(b.ServerURL, "/") return b } diff --git a/internal/brand/brand_test.go b/internal/brand/brand_test.go index 7779dee..e26df95 100644 --- a/internal/brand/brand_test.go +++ b/internal/brand/brand_test.go @@ -104,6 +104,38 @@ func TestValidateFormerName(t *testing.T) { } } +func TestValidateServer(t *testing.T) { + for _, c := range []struct { + b Brand + ok bool + }{ + {Brand{}, true}, + {Brand{ServerURL: "https://cloud.acme.example"}, true}, + {Brand{ServerURL: "https://acme.example/nextcloud", LockServer: true}, true}, + {Brand{LockServer: true}, false}, + {Brand{ServerURL: "http://cloud.acme.example", LockServer: true}, false}, + {Brand{ServerURL: "cloud.acme.example"}, false}, + } { + if err := c.b.ValidateServer(); (err == nil) != c.ok { + t.Errorf("%+v: ValidateServer() = %v, want ok=%v", c.b, err, c.ok) + } + } +} + +// A lock to an unusable address is dropped with its address, not kept; a good +// preset loses only a trailing slash. +func TestSanitizeServer(t *testing.T) { + if b := sanitize(Brand{Name: "Acme", ServerURL: "http://x.example", LockServer: true}); b.ServerURL != "" || b.LockServer { + t.Errorf("bad preset kept: %+v", b) + } + if b := sanitize(Brand{Name: "Acme", ServerURL: "https://x.example/", LockServer: true}); b.ServerURL != "https://x.example" || !b.LockServer { + t.Errorf("good preset changed: %+v", b) + } + if s := stockBrand(); s.ServerURL != "" || s.LockServer { + t.Errorf("stock brand must not preset a server: %+v", s) + } +} + // A brand that fails the check runs as never renamed rather than wearing the // stock build's former name. func TestSanitizeDropsBadFormerName(t *testing.T) { diff --git a/packaging/whitelabel/README.md b/packaging/whitelabel/README.md index 1a7d797..a2499a4 100644 --- a/packaging/whitelabel/README.md +++ b/packaging/whitelabel/README.md @@ -45,6 +45,8 @@ user-visible identity value: | `feedUrl`, `apiBase` | the in-app update check + "Update now" target — point these at the partner's OWN release feed | | `accentHex` | brand accent — **drives the app icon** (tile + cloud, auto-derived), plus the tray badge and UI accent fallback | | `appId` | the MSIX Application Id used in the AUMID and the self-update task name — must match the manifest's `` | +| `serverUrl` | optional: the partner's Nextcloud (`https://…`, a subfolder is fine). The sign-in window fills it in | +| `lockServer` | optional, needs `serverUrl`: sign-in always goes to `serverUrl` and the address can't be changed. The same lock as the `ServerURL`/`LockServer` admin policy, but baked in, so it needs no business licence or registry step on each PC (for a partner's clients). If an admin policy on the PC also sets a server, the policy's address wins. `build-partner.ps1` refuses a lock without an https `serverUrl` | | `formerName` | **Stock only: leave it out for partners.** The stock build's name before the rename to OtherSync ("Nimbo"). It keeps what existing stock installs made under the old name (the "Nimbo Apps" Start-menu folder, the `%LOCALAPPDATA%\Nimbo` sidebar icon folder, "Nimbo - " sidebar labels) and triggers the one-time "Nimbo is now OtherSync" notice. A partner brand carrying it would share those with stock and tell its users "Nimbo is now ". `build-partner.ps1` refuses a partner `brand.json` that sets it, unless `partner.json` has `"renamedFrom"` equal to it (a partner that really renamed its own product) | ## Steps to build a branded copy diff --git a/packaging/whitelabel/build-partner.ps1 b/packaging/whitelabel/build-partner.ps1 index cce2226..d6dcaf1 100644 --- a/packaging/whitelabel/build-partner.ps1 +++ b/packaging/whitelabel/build-partner.ps1 @@ -46,6 +46,15 @@ $formerName = (Get-Content $brandSrc -Raw | ConvertFrom-Json).formerName if ($formerName -and $formerName -ne $cfg.renamedFrom) { throw "brand.json sets formerName '$formerName', which is the stock build's. Remove it from the partner's brand.json, or, if this partner really renamed its product from '$formerName', add `"renamedFrom`": `"$formerName`" to partner.json." } +# serverUrl / lockServer: the app drops a preset it can't use (and its lock), +# which would ship a partner build that signs in anywhere. Refuse it here. +$brandCfg = Get-Content $brandSrc -Raw | ConvertFrom-Json +if ($brandCfg.lockServer -and -not $brandCfg.serverUrl) { + throw "brand.json sets lockServer but no serverUrl." +} +if ($brandCfg.serverUrl -and $brandCfg.serverUrl -notmatch '^https://[^/\s]+') { + throw "brand.json serverUrl '$($brandCfg.serverUrl)' must be an https:// address." +} # Make Go + windres reachable even if not on PATH. function Ensure-Tool($exe, $candidates) {