@netnodeag/kraftwerk 0.78.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/remote.js +1 -1
- package/dist/server/api/workspace.js +6 -4
- package/dist/server/relay.d.ts +7 -1
- package/dist/server/relay.js +13 -4
- package/package.json +2 -2
- package/web/dist/assets/{dist-C90kUzps.js → dist-A0LN_JdM.js} +1 -1
- package/web/dist/assets/dist-B04mMuvH.js +1 -0
- package/web/dist/assets/{dist-B-yVL5lk.js → dist-BTD8k55t.js} +1 -1
- package/web/dist/assets/{dist-D0rjnN8S.js → dist-BasQkRXA.js} +1 -1
- package/web/dist/assets/{dist-CpLdAqFQ.js → dist-D93lSM0a.js} +1 -1
- package/web/dist/assets/{dist-DQbifCX0.js → dist-DdOsafun.js} +1 -1
- package/web/dist/assets/{dist-B9EMB1Ie.js → dist-DgP31lcC.js} +1 -1
- package/web/dist/assets/{dist-DHRhdODt.js → dist-E48sO9Gc.js} +1 -1
- package/web/dist/assets/{dist-Crnb8VVO.js → dist-cNijUfNl.js} +1 -1
- package/web/dist/assets/{dist-D4SDXoGw.js → dist-wtIUmbnn.js} +1 -1
- package/web/dist/assets/{dist-BTc878j7.js → dist-xLZN2eGO.js} +1 -1
- package/web/dist/assets/{editor-DHAdW1g_.js → editor-BQlHN7qT.js} +3 -3
- package/web/dist/assets/{index-D7JVAjll.js → index-C7ddpWNC.js} +3 -3
- package/web/dist/index.html +1 -1
- package/web/dist-remote/assets/index-D6Wznb5o.js +27 -0
- package/web/dist-remote/index.html +2 -2
- package/web/dist-remote/sw.js +1 -1
- package/web/dist/assets/dist-lSJ-O7kT.js +0 -1
- package/web/dist-remote/assets/index-2nAI1HTV.js +0 -13
package/web/dist/index.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
} catch (e) {}
|
|
16
16
|
})();
|
|
17
17
|
</script>
|
|
18
|
-
<script type="module" crossorigin src="/assets/index-
|
|
18
|
+
<script type="module" crossorigin src="/assets/index-C7ddpWNC.js"></script>
|
|
19
19
|
<link rel="stylesheet" crossorigin href="/assets/index-BKDJocDd.css">
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`kraftwerk-remote`;function t(){return new Promise((t,n)=>{let r=indexedDB.open(e,2);r.onupgradeneeded=()=>{let e=r.result.objectStoreNames;e.contains(`machines`)||r.result.createObjectStore(`machines`,{keyPath:`key`}),e.contains(`state`)||r.result.createObjectStore(`state`),e.contains(`joined`)||r.result.createObjectStore(`joined`,{keyPath:`workspaceId`})},r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error)})}async function n(e,n,r){let i=await t();try{return await new Promise((t,a)=>{let o=r(i.transaction(e,n).objectStore(e));o.onsuccess=()=>t(o.result),o.onerror=()=>a(o.error)})}finally{i.close()}}var r=()=>n(`machines`,`readonly`,e=>e.getAll()),i=e=>n(`machines`,`readwrite`,t=>t.delete(e)),a=e=>n(`state`,`readwrite`,t=>t.put(e,`current`)),o=()=>n(`joined`,`readonly`,e=>e.getAll()),s=e=>n(`joined`,`readonly`,t=>t.get(e)),c=e=>n(`joined`,`readwrite`,t=>t.put(e)),l=e=>n(`joined`,`readwrite`,t=>t.delete(e)),u=e=>e.hosts.find(t=>t.publicKey===e.hostKey)??e.hosts.find(e=>e.online)??e.hosts[0];async function d(e){let t=[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e))).subarray(0,4)].map(e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,4)}-${t.slice(4)}`}var f=document.getElementById(`app`),p=e=>e.replace(/[&<>"']/g,e=>({"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`})[e]),m=/^[A-Za-z0-9_-]{43}$/;async function h(){if(!(`serviceWorker`in navigator))throw Error(`this browser has no service workers (a private window?)`);let e=await navigator.serviceWorker.register(`/_kw/sw.js`,{scope:`/`});await navigator.serviceWorker.ready;let t=e.active??e.waiting??e.installing;if(!t)throw Error(`the service worker did not start`);return t}async function g(e,t){let n=await fetch(e,{method:t?`POST`:`GET`,credentials:`same-origin`,headers:t?{"content-type":`application/json`}:{},body:t?JSON.stringify(t):void 0}),r=await n.json().catch(()=>({}));if(!n.ok)throw Object.assign(Error(r.error??`the cloud answered ${n.status}`),{status:n.status});return r}var _=e=>{let t=e.trim(),n=/\/join\/([^/?#\s]+)/.exec(t);return decodeURIComponent(n?n[1]:t)};async function v(e){for(let t of e)await c({...await s(t.id).catch(()=>void 0)??{joinedAt:new Date().toISOString()},workspaceId:t.id,name:t.name,role:t.role,hosts:t.hosts}).catch(()=>{})}async function y(e){let t=await g(`/api/me/workspaces/${encodeURIComponent(e)}/token`,{}),n={...await s(e)??{hosts:[],joinedAt:new Date().toISOString()},workspaceId:e,name:t.workspaceName,role:t.role,token:t.memberToken,error:void 0};return await c(n),n}function b(e,t){return new Promise((n,r)=>{let i=new MessageChannel,a=setTimeout(()=>r(Error(`no answer — is the host awake and remote access on?`)),3e4);i.port1.onmessage=e=>{clearTimeout(a);let t=e.data;t.ok&&t.slug?n(t.slug):r(Error(t.error??`the host did not let this member in`))},e.postMessage({type:`member`,workspaceId:t},[i.port2])})}function x(e,t){return new Promise((n,r)=>{let i=new MessageChannel,a=setTimeout(()=>r(Error(`no answer — is the machine awake and remote access on?`)),3e4);i.port1.onmessage=e=>{clearTimeout(a);let t=e.data;t.ok?n():r(Error(t.error??`pairing failed`))},e.postMessage({type:`pair`,...t},[i.port2])})}async function S(e,t,n){let r=await d(e),i=/iPhone/.test(navigator.userAgent)?`iPhone`:/iPad/.test(navigator.userAgent)?`iPad`:/Android/.test(navigator.userAgent)?`Android phone`:`browser`;f.innerHTML=`
|
|
2
|
+
<h1>Pair with ${p(n)}</h1>
|
|
3
|
+
<p class="dim">This phone reaches ${p(n)} from anywhere, end to end encrypted: the relay in between cannot read along.</p>
|
|
4
|
+
<div class="card">
|
|
5
|
+
<p>Machine key <code>${p(r)}</code><br><span class="dim">The same as <code>kraftwerk remote</code> shows on the machine.</span></p>
|
|
6
|
+
<label for="name">Name this device</label>
|
|
7
|
+
<input id="name" value="${p(i)}" autocomplete="off" />
|
|
8
|
+
<button id="go">Pair</button>
|
|
9
|
+
<p id="msg" class="dim"></p>
|
|
10
|
+
</div>`;let a=document.getElementById(`go`),o=document.getElementById(`msg`);a.onclick=async()=>{a.disabled=!0,o.className=`dim`,o.textContent=`Connecting…`;try{await x(await h(),{key:e,code:t,label:n,name:document.getElementById(`name`).value.trim()}),history.replaceState(null,``,`/_kw/`),location.href=`/`}catch(e){a.disabled=!1,o.className=`err`,o.textContent=e.message}}}async function C(e){let t=await r().catch(()=>[]),n=await Promise.all(t.map(async e=>`<div class="card machine"><a href="/" data-open="${p(e.key)}">${p(e.label)}<br><span class="dim">key <code>${p(await d(e.key))}</code></span></a><button class="quiet" data-forget="${p(e.key)}">Forget</button></div>`));f.innerHTML=`
|
|
11
|
+
<h1>kraftwerk remote</h1>
|
|
12
|
+
${e?`<p class="err">${p(e)}</p>`:``}
|
|
13
|
+
${n.join(``)||`<div class="card"><p>No machine paired on this phone yet.</p><p class="dim">On the Mac: <code>kraftwerk remote on</code>, then <code>kraftwerk remote pair</code> — or scan the QR code under Settings → Devices — and open the link here.</p></div>`}
|
|
14
|
+
<h2>Workspaces you joined</h2>
|
|
15
|
+
<div id="joined"><p class="dim">Loading…</p></div>`,f.querySelectorAll(`[data-open]`).forEach(e=>{e.onclick=async t=>{t.preventDefault(),await h(),await a(e.dataset.open),location.href=`/`}}),f.querySelectorAll(`[data-forget]`).forEach(e=>{e.onclick=async()=>{await i(e.dataset.forget),C()}}),t.length&&h().catch(()=>{}),w()}async function w(e,t=!1){let n=document.getElementById(`joined`);if(!n)return;let r=await g(`/api/auth/me`).catch(()=>({user:null})),i=!!r.user,a=``;i&&await g(`/api/me/workspaces`).then(e=>v(e.workspaces),e=>a=e.message);let d=(await o().catch(()=>[])).sort((e,t)=>e.name.localeCompare(t.name)).map(e=>{let t=u(e),n=t?`${p(t.machineName)} · ${t.online?`online`:`offline`}`:`no host in the cloud`,r=e.error?`<p class="err">${p(e.error)}</p>${i?`<button class="quiet" data-mint="${p(e.workspaceId)}">Get a new token and open</button>`:`<p class="dim">Sign in below for a new token.</p>`}`:``;return`<div class="card"><div class="machine"><a href="/" data-ws="${p(e.workspaceId)}">${p(e.name)}<br><span class="dim">${n} · ${p(e.role)}</span></a><button class="quiet" data-unjoin="${p(e.workspaceId)}">Forget</button></div>${r}</div>`});n.innerHTML=`
|
|
16
|
+
${e?`<p class="${t?`dim`:`err`}">${p(e)}</p>`:``}
|
|
17
|
+
${a?`<p class="err">${p(a)}</p>`:``}
|
|
18
|
+
${d.join(``)||`<div class="card"><p>No workspace joined on this phone yet.</p><p class="dim">A teammate invites you from the workspace's Members settings; accept the invite here.</p></div>`}
|
|
19
|
+
<div class="card">
|
|
20
|
+
${i?`<p>Signed in as <strong>${p(r.user.email)}</strong> <button class="quiet" id="logout">Sign out</button></p>`:`<p class="dim">Sign in to the kraftwerk cloud to join and open workspaces.</p>
|
|
21
|
+
<label for="email">Email</label><input id="email" type="email" autocomplete="username" />
|
|
22
|
+
<label for="password">Password</label><input id="password" type="password" autocomplete="current-password" />`}
|
|
23
|
+
<label for="invite">Invite code or link${i?``:` (optional)`}</label>
|
|
24
|
+
<input id="invite" autocomplete="off" placeholder="XKQ7-2MPD-HH3A" />
|
|
25
|
+
${i?`<button id="join">Join</button>`:`<button id="login">Sign in</button><button class="quiet" id="signup">Create an account instead</button>`}
|
|
26
|
+
<p id="cloudmsg" class="dim"></p>
|
|
27
|
+
</div>`;let f=document.getElementById(`cloudmsg`),m=e=>{f.className=`err`,f.textContent=e.message},x=e=>document.getElementById(e)?.value.trim()??``,S=async()=>{let e=_(x(`invite`));if(!e)return;let t=await g(`/api/invites/accept`,{code:e});return await c({...await s(t.workspaceId)??{hosts:[],joinedAt:new Date().toISOString()},workspaceId:t.workspaceId,name:t.workspaceName,role:t.role,token:t.memberToken,error:void 0}),t},C=e=>async()=>{f.className=`dim`,f.textContent=e===`signup`?`Creating the account…`:`Signing in…`;try{await g(`/api/auth/${e}`,{email:x(`email`),password:document.getElementById(`password`).value});let t;try{t=await S()}catch(e){w(`Signed in, but the invite did not go through: ${e.message}`);return}w(t?`Joined ${t.workspaceName}.`:void 0,!0)}catch(e){m(e)}},T=document.getElementById(`login`);T&&(T.onclick=C(`login`));let E=document.getElementById(`signup`);E&&(E.onclick=C(`signup`));let D=document.getElementById(`logout`);D&&(D.onclick=()=>void g(`/api/auth/logout`,{}).then(()=>w(),m));let O=document.getElementById(`join`);O&&(O.onclick=async()=>{f.className=`dim`,f.textContent=`Joining…`;try{let e=await S();if(!e)throw Error(`paste an invite code or a /join/ link first`);w(`Joined ${e.workspaceName}.`,!0)}catch(e){m(e)}});let k=async(e,t)=>{f.className=`dim`,f.textContent=`Connecting…`;try{let n=await s(e);if(t||!n?.token){if(!i)throw Error(`sign in first — this phone has no member token for that workspace`);n=await y(e)}let r=await b(await h(),e);location.href=`/w/${encodeURIComponent(r)}/`}catch(t){if(t.status===401)return void w(`Your sign-in ran out — sign in again.`);w((await s(e).catch(()=>void 0))?.error===t.message?void 0:t.message)}};n.querySelectorAll(`[data-ws]`).forEach(e=>{e.onclick=t=>{t.preventDefault(),k(e.dataset.ws,!1)}}),n.querySelectorAll(`[data-mint]`).forEach(e=>e.onclick=()=>void k(e.dataset.mint,!0)),n.querySelectorAll(`[data-unjoin]`).forEach(e=>{e.onclick=async()=>{await l(e.dataset.unjoin).catch(()=>{}),w()}})}var T=new URLSearchParams(location.hash.slice(1)),E=T.get(`k`)??``,D=T.get(`c`)??``;m.test(E)&&D?S(E,D,T.get(`n`)||`your machine`):C(m.test(E)?"This link has no pairing code — make a new one with `kraftwerk remote pair`.":void 0);
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
8
8
|
<meta name="apple-mobile-web-app-title" content="kraftwerk" />
|
|
9
9
|
<title>kraftwerk remote</title>
|
|
10
|
-
<style>:root{--bg:#f6f6f4;--card:#fff;--fg:#1d1d1b;--dim:#6b6b66;--line:#e3e3de;--accent:#2f6fde;--bad:#c0392b}@media (prefers-color-scheme:dark){:root{--bg:#151514;--card:#1e1e1c;--fg:#ececea;--dim:#9a9a94;--line:#30302d;--accent:#6b9cf0;--bad:#ef7a6a}}*{box-sizing:border-box}body{background:var(--bg);color:var(--fg);margin:0;font:16px/1.45 system-ui,-apple-system,sans-serif}main{max-width:32rem;padding:max(24px, env(safe-area-inset-top)) 16px 32px;margin:0 auto}h1{margin:8px 0 4px;font-size:22px}p{margin:8px 0}.dim{color:var(--dim);font-size:14px}.card{background:var(--card);border:1px solid var(--line);border-radius:14px;margin:16px 0;padding:16px}label{color:var(--dim);margin:12px 0 4px;font-size:14px;display:block}input{width:100%;font:inherit;border:1px solid var(--line);background:var(--bg);color:var(--fg);border-radius:10px;padding:10px 12px}button{font:inherit;background:var(--accent);color:#fff;border:0;border-radius:10px;width:100%;margin-top:14px;padding:10px 16px;font-weight:600}button.quiet{color:var(--dim);background:0 0;width:auto;margin:0;padding:6px 0;font-weight:400}button:disabled{opacity:.6}.machine{align-items:center;gap:12px;display:flex}.machine a{color:var(--fg);flex:1;font-weight:600;text-decoration:none}.err{color:var(--bad)}code{font:13px ui-monospace,monospace}</style>
|
|
11
|
-
<script type="module" crossorigin src="/_kw/assets/index-
|
|
10
|
+
<style>:root{--bg:#f6f6f4;--card:#fff;--fg:#1d1d1b;--dim:#6b6b66;--line:#e3e3de;--accent:#2f6fde;--bad:#c0392b}@media (prefers-color-scheme:dark){:root{--bg:#151514;--card:#1e1e1c;--fg:#ececea;--dim:#9a9a94;--line:#30302d;--accent:#6b9cf0;--bad:#ef7a6a}}*{box-sizing:border-box}body{background:var(--bg);color:var(--fg);margin:0;font:16px/1.45 system-ui,-apple-system,sans-serif}main{max-width:32rem;padding:max(24px, env(safe-area-inset-top)) 16px 32px;margin:0 auto}h1{margin:8px 0 4px;font-size:22px}h2{margin:28px 0 4px;font-size:17px}p{margin:8px 0}.dim{color:var(--dim);font-size:14px}.card{background:var(--card);border:1px solid var(--line);border-radius:14px;margin:16px 0;padding:16px}label{color:var(--dim);margin:12px 0 4px;font-size:14px;display:block}input{width:100%;font:inherit;border:1px solid var(--line);background:var(--bg);color:var(--fg);border-radius:10px;padding:10px 12px}button{font:inherit;background:var(--accent);color:#fff;border:0;border-radius:10px;width:100%;margin-top:14px;padding:10px 16px;font-weight:600}button.quiet{color:var(--dim);background:0 0;width:auto;margin:0;padding:6px 0;font-weight:400}button:disabled{opacity:.6}.machine{align-items:center;gap:12px;display:flex}.machine a{color:var(--fg);flex:1;font-weight:600;text-decoration:none}.err{color:var(--bad)}code{font:13px ui-monospace,monospace}</style>
|
|
11
|
+
<script type="module" crossorigin src="/_kw/assets/index-D6Wznb5o.js"></script>
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<main id="app"><p class="dim">Loading…</p></main>
|
package/web/dist-remote/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){var e=()=>globalThis.crypto.subtle,t=new TextEncoder,n=new TextDecoder;function r(e){let t=``;for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)}function i(e){let t=atob(e),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return n}var a=e=>r(e).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``),o=e=>i(e.replace(/-/g,`+`).replace(/_/g,`/`)+`===`.slice((e.length+3)%4)),s=(...e)=>{let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t};async function c(){let t=await e().generateKey({name:`X25519`},!0,[`deriveBits`]);return{privateKey:t.privateKey,publicKey:new Uint8Array(await e().exportKey(`raw`,t.publicKey))}}var l=t=>{if(t.length!==32)throw Error(`invalid public key`);return e().importKey(`raw`,t,{name:`X25519`},!1,[])},u=async(t,n)=>new Uint8Array(await e().deriveBits({name:`X25519`,public:await l(n)},t,256));async function d(t){return a(new Uint8Array(await e().digest(`SHA-256`,t)).subarray(0,16))}async function f(n,r){let i=await e().importKey(`raw`,n,`HKDF`,!1,[`deriveKey`]),a=n=>e().deriveKey({name:`HKDF`,hash:`SHA-256`,salt:r,info:t.encode(`kraftwerk-relay-v1 ${n}`)},i,{name:`AES-GCM`,length:256},!1,[`encrypt`,`decrypt`]);return{toDaemon:await a(`to-daemon`),toClient:await a(`to-client`)}}var p=e=>{let t=new Uint8Array(12);return new DataView(t.buffer).setBigUint64(4,BigInt(e)),t};function m(a,o){let s=0,c=0,l=Promise.resolve(),u=Promise.resolve();return{seal(n){let i=l.then(async()=>{let i=p(s++);return r(new Uint8Array(await e().encrypt({name:`AES-GCM`,iv:i},a,t.encode(n))))});return l=i.catch(()=>{}),i},open(t){let r=u.then(async()=>{let r=p(c++);try{return n.decode(await e().decrypt({name:`AES-GCM`,iv:r},o,i(t)))}catch{throw Error(`relay message failed to decrypt — tampered, replayed or out of order`)}});return u=r.catch(()=>{}),r}}}async function h(e){let t=await c();return{hello:{t:`hello`,v:1,key:r(t.publicKey)},async finish(n){if(n?.t!==`ready`||typeof n.key!=`string`||typeof n.box!=`string`)throw Error(`not a relay handshake answer`);let r=i(n.key),a=await f(s(await u(t.privateKey,e),await u(t.privateKey,r)),s(t.publicKey,e,r)),o=m(a.toDaemon,a.toClient);if(await o.open(n.box).catch(()=>``)!==`ready`)throw Error(`the machine did not prove its key — wrong pairing link, or not the machine you paired with`);return o}}}var g=1;async function _(e){let t=await d(e.publicKey),a=`${e.relayUrl}${e.relayUrl.includes(`?`)?`&`:`?`}id=${encodeURIComponent(t)}`,o=await h(e.publicKey),s=e.connect?e.connect(a):new WebSocket(a),c=null,l=``,u,f=new Promise(e=>u=e),p=new Map,m=1,_=null,v=e=>{if(!l){l=e||`the connection closed`;for(let e of p.values()){e.reject(Error(l));try{e.stream?.error(Error(l))}catch{}}p.clear(),u(l)}},y=new Promise((t,r)=>{let i=setTimeout(()=>{r(Error(`the machine did not answer through the relay`)),s.close()},e.timeoutMs??15e3);s.addEventListener(`open`,()=>s.send(JSON.stringify(o.hello))),s.addEventListener(`close`,e=>{clearTimeout(i);let t=e.reason||(e.code===4404?`the machine is not connected to the relay`:`the relay closed the connection`);r(Error(t)),v(t)}),s.addEventListener(`error`,()=>{});let a=!0,l=Promise.resolve();s.addEventListener(`message`,e=>{let u=typeof e.data==`string`?e.data:n.decode(e.data);l=l.then(async()=>{if(a){a=!1;try{c=await o.finish(JSON.parse(u)),clearTimeout(i),t(c)}catch(e){clearTimeout(i),r(e),s.close(4400,`handshake failed`)}return}if(!c)return;let e;try{e=JSON.parse(await c.open(u))}catch(e){s.close(4400,`bad message`),v(e.message);return}b(e)})})}),b=e=>{if(e.t===`auth`){_?.(e),_=null;return}let t=p.get(e.id);if(t){if(e.t===`res`){let n=new ReadableStream({start:e=>{t.stream=e},cancel:()=>{p.delete(e.id),x({t:`abort`,id:e.id})}}),r=e.status,i=r===204||r===304||r>=100&&r<200;t.resolve(new Response(i?null:n,{status:r,headers:e.headers}))}else if(e.t===`data`)try{t.stream?.enqueue(i(e.chunk))}catch{}else if(e.t===`end`){p.delete(e.id);try{t.stream?.close()}catch{}}else if(e.t===`fail`){p.delete(e.id);let n=Error(e.error);t.reject(n);try{t.stream?.error(n)}catch{}}}},x=async e=>{c&&s.readyState===g&&s.send(await c.seal(JSON.stringify(e)))};await y;let S;if(e.member){let t=e.member,n=await new Promise((n,r)=>{let i=setTimeout(()=>r(Error(`the machine did not answer the member's hello`)),e.timeoutMs??15e3);_=e=>{clearTimeout(i),n(e)},f.then(e=>{clearTimeout(i),r(Error(e))}),x({t:`auth`,memberToken:t.token,workspaceId:t.workspaceId}).catch(r)});if(!n.ok)throw s.close(1e3,`bye`),v(n.error),Error(n.error);S={member:n.member,workspace:n.workspace}}return{fetch:async(e,t)=>{if(l)throw Error(l);let n=new Request(typeof e==`string`&&e.startsWith(`/`)?`http://relay${e}`:e,t),i=new URL(n.url),a={};n.headers.forEach((e,t)=>a[t]=e);let o=n.method===`GET`||n.method===`HEAD`?void 0:new Uint8Array(await n.arrayBuffer()),s=m++;return new Promise((e,t)=>{p.set(s,{resolve:e,reject:t,signal:n.signal}),n.signal?.addEventListener(`abort`,()=>{let e=p.get(s);if(!e)return;p.delete(s);let t=new DOMException(`aborted`,`AbortError`);e.reject(t);try{e.stream?.error(t)}catch{}x({t:`abort`,id:s})}),x({t:`req`,id:s,method:n.method,url:i.pathname+i.search,headers:a,...o?.length?{body:r(o)}:{}}).catch(e=>{p.delete(s),t(e)})})},close:()=>{s.close(1e3,`bye`),v(`closed`)},closed:f,...S?{member:S}:{}}}var v=`kraftwerk-remote`;function y(){return new Promise((e,t)=>{let n=indexedDB.open(v,1);n.onupgradeneeded=()=>{n.result.createObjectStore(`machines`,{keyPath:`key`}),n.result.createObjectStore(`state`)},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}async function b(e,t,n){let r=await y();try{return await new Promise((i,a)=>{let o=n(r.transaction(e,t).objectStore(e));o.onsuccess=()=>i(o.result),o.onerror=()=>a(o.error)})}finally{r.close()}}var x=()=>b(`machines`,`readonly`,e=>e.getAll()),S=e=>b(`machines`,`readwrite`,t=>t.put(e)),C=()=>b(`state`,`readonly`,e=>e.get(`current`)),w=e=>b(`state`,`readwrite`,t=>t.put(e,`current`));async function T(){let[e,t]=await Promise.all([C(),x()]);return t.find(t=>t.key===e)??(t.length===1?t[0]:void 0)}var E=e=>`${e.replace(/^http/,`ws`)}/api/relay/client`,D=self;D.addEventListener(`install`,e=>e.waitUntil(D.skipWaiting())),D.addEventListener(`activate`,e=>e.waitUntil(D.clients.claim()));var O=null;function k(e){if(O?.key===e)return O.t;O?.t.then(e=>e.close()).catch(()=>{});let t=_({relayUrl:E(D.location.origin),publicKey:o(e)}),n={key:e,t};return O=n,t.then(e=>void e.closed.then(()=>O===n&&(O=null)),()=>O===n&&(O=null)),t}var A=(e,t,n)=>new Response(`<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${t}</title><body style="font:16px system-ui;padding:24px;max-width:36em;margin:auto"><h1 style="font-size:20px">${t}</h1><p>${n}</p><p><a href="/_kw/">Machines</a> · <a href="" onclick="location.reload();return false">Try again</a></p>`,{status:e,headers:{"content-type":`text/html; charset=utf-8`}});async function j(e,t){let n=new URL(e.url),r=new Headers(e.headers);r.set(`authorization`,`Bearer ${t.token}`),r.set(`host`,n.host);let i=await k(t.key),a=e.method===`GET`||e.method===`HEAD`?void 0:await e.arrayBuffer(),o=await i.fetch(n.pathname+n.search,{method:e.method,headers:r,body:a,signal:e.signal});if(e.method===`POST`&&/^(\/w\/[^/]+)?\/api\/pair$/.test(n.pathname)&&o.ok){let{token:e}=await o.clone().json().catch(()=>({}));e&&await S({...t,token:e,pairedAt:new Date().toISOString()})}return o}D.addEventListener(`fetch`,e=>{let t=new URL(e.request.url);t.origin!==D.location.origin||t.pathname.startsWith(`/_kw/`)||e.respondWith((async()=>{if(e.request.mode===`navigate`&&e.request.method!==`GET`)return A(403,`Refused`,`A form from another page tried to act on your machine.`);let t=await T();if(!t)return e.request.mode===`navigate`?Response.redirect(`/_kw/`,302):new Response(JSON.stringify({error:`no machine paired here`}),{status:503});try{return await j(e.request,t)}catch(n){if(n.name===`AbortError`)throw n;let r=n.message;return e.request.mode===`navigate`?A(502,`${t.label} is not reachable`,`${r}. Is the Mac awake, and is remote access on (<code>kraftwerk remote</code>)?`):new Response(JSON.stringify({error:r}),{status:502,headers:{"content-type":`application/json`}})}})())}),D.addEventListener(`message`,e=>{let t=e.data,n=e.ports[0];if(t?.type!==`pair`||!t.key||!t.code||!n)return;let r=t.key;(async()=>{try{O=null;let e=await(await k(r)).fetch(`/api/pair`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({code:t.code,name:t.name||`phone`})}),i=await e.json().catch(()=>({}));if(!e.ok||!i.token)throw Error(i.error??`pairing failed (${e.status})`);await S({key:r,token:i.token,label:t.label||`your machine`,pairedAt:new Date().toISOString()}),await w(r),n.postMessage({ok:!0})}catch(e){n.postMessage({ok:!1,error:e.message})}})()})})();
|
|
1
|
+
(function(){var e=()=>globalThis.crypto.subtle,t=new TextEncoder,n=new TextDecoder;function r(e){let t=``;for(let n=0;n<e.length;n+=32768)t+=String.fromCharCode(...e.subarray(n,n+32768));return btoa(t)}function i(e){let t=atob(e),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return n}var a=e=>r(e).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``),o=e=>i(e.replace(/-/g,`+`).replace(/_/g,`/`)+`===`.slice((e.length+3)%4)),s=(...e)=>{let t=new Uint8Array(e.reduce((e,t)=>e+t.length,0)),n=0;for(let r of e)t.set(r,n),n+=r.length;return t};async function c(){let t=await e().generateKey({name:`X25519`},!0,[`deriveBits`]);return{privateKey:t.privateKey,publicKey:new Uint8Array(await e().exportKey(`raw`,t.publicKey))}}var l=t=>{if(t.length!==32)throw Error(`invalid public key`);return e().importKey(`raw`,t,{name:`X25519`},!1,[])},u=async(t,n)=>new Uint8Array(await e().deriveBits({name:`X25519`,public:await l(n)},t,256));async function d(t){return a(new Uint8Array(await e().digest(`SHA-256`,t)).subarray(0,16))}async function f(n,r){let i=await e().importKey(`raw`,n,`HKDF`,!1,[`deriveKey`]),a=n=>e().deriveKey({name:`HKDF`,hash:`SHA-256`,salt:r,info:t.encode(`kraftwerk-relay-v1 ${n}`)},i,{name:`AES-GCM`,length:256},!1,[`encrypt`,`decrypt`]);return{toDaemon:await a(`to-daemon`),toClient:await a(`to-client`)}}var p=e=>{let t=new Uint8Array(12);return new DataView(t.buffer).setBigUint64(4,BigInt(e)),t};function m(a,o){let s=0,c=0,l=Promise.resolve(),u=Promise.resolve();return{seal(n){let i=l.then(async()=>{let i=p(s++);return r(new Uint8Array(await e().encrypt({name:`AES-GCM`,iv:i},a,t.encode(n))))});return l=i.catch(()=>{}),i},open(t){let r=u.then(async()=>{let r=p(c++);try{return n.decode(await e().decrypt({name:`AES-GCM`,iv:r},o,i(t)))}catch{throw Error(`relay message failed to decrypt — tampered, replayed or out of order`)}});return u=r.catch(()=>{}),r}}}async function h(e){let t=await c();return{hello:{t:`hello`,v:1,key:r(t.publicKey)},async finish(n){if(n?.t!==`ready`||typeof n.key!=`string`||typeof n.box!=`string`)throw Error(`not a relay handshake answer`);let r=i(n.key),a=await f(s(await u(t.privateKey,e),await u(t.privateKey,r)),s(t.publicKey,e,r)),o=m(a.toDaemon,a.toClient);if(await o.open(n.box).catch(()=>``)!==`ready`)throw Error(`the machine did not prove its key — wrong pairing link, or not the machine you paired with`);return o}}}var g=1;async function _(e){let t=await d(e.publicKey),a=`${e.relayUrl}${e.relayUrl.includes(`?`)?`&`:`?`}id=${encodeURIComponent(t)}`,o=await h(e.publicKey),s=e.connect?e.connect(a):new WebSocket(a),c=null,l=``,u,f=new Promise(e=>u=e),p=new Map,m=1,_=null,v=e=>{if(!l){l=e||`the connection closed`;for(let e of p.values()){e.reject(Error(l));try{e.stream?.error(Error(l))}catch{}}p.clear(),u(l)}},y=new Promise((t,r)=>{let i=setTimeout(()=>{r(Error(`the machine did not answer through the relay`)),s.close()},e.timeoutMs??15e3);s.addEventListener(`open`,()=>s.send(JSON.stringify(o.hello))),s.addEventListener(`close`,e=>{clearTimeout(i);let t=e.reason||(e.code===4404?`the machine is not connected to the relay`:`the relay closed the connection`);r(Error(t)),v(t)}),s.addEventListener(`error`,()=>{});let a=!0,l=Promise.resolve();s.addEventListener(`message`,e=>{let u=typeof e.data==`string`?e.data:n.decode(e.data);l=l.then(async()=>{if(a){a=!1;try{c=await o.finish(JSON.parse(u)),clearTimeout(i),t(c)}catch(e){clearTimeout(i),r(e),s.close(4400,`handshake failed`)}return}if(!c)return;let e;try{e=JSON.parse(await c.open(u))}catch(e){s.close(4400,`bad message`),v(e.message);return}b(e)})})}),b=e=>{if(e.t===`auth`){_?.(e),_=null;return}let t=p.get(e.id);if(t){if(e.t===`res`){let n=new ReadableStream({start:e=>{t.stream=e},cancel:()=>{p.delete(e.id),x({t:`abort`,id:e.id})}}),r=e.status,i=r===204||r===304||r>=100&&r<200;t.resolve(new Response(i?null:n,{status:r,headers:e.headers}))}else if(e.t===`data`)try{t.stream?.enqueue(i(e.chunk))}catch{}else if(e.t===`end`){p.delete(e.id);try{t.stream?.close()}catch{}}else if(e.t===`fail`){p.delete(e.id);let n=Error(e.error);t.reject(n);try{t.stream?.error(n)}catch{}}}},x=async e=>{c&&s.readyState===g&&s.send(await c.seal(JSON.stringify(e)))};await y;let S;if(e.member){let t=e.member,n=await new Promise((n,r)=>{let i=setTimeout(()=>r(Error(`the machine did not answer the member's hello`)),e.timeoutMs??15e3);_=e=>{clearTimeout(i),n(e)},f.then(e=>{clearTimeout(i),r(Error(e))}),x({t:`auth`,memberToken:t.token,workspaceId:t.workspaceId}).catch(r)});if(!n.ok)throw s.close(1e3,`bye`),v(n.error),Error(n.error);S={member:n.member,workspace:n.workspace}}return{fetch:async(e,t)=>{if(l)throw Error(l);let n=new Request(typeof e==`string`&&e.startsWith(`/`)?`http://relay${e}`:e,t),i=new URL(n.url),a={};n.headers.forEach((e,t)=>a[t]=e);let o=n.method===`GET`||n.method===`HEAD`?void 0:new Uint8Array(await n.arrayBuffer()),s=m++;return new Promise((e,t)=>{p.set(s,{resolve:e,reject:t,signal:n.signal}),n.signal?.addEventListener(`abort`,()=>{let e=p.get(s);if(!e)return;p.delete(s);let t=new DOMException(`aborted`,`AbortError`);e.reject(t);try{e.stream?.error(t)}catch{}x({t:`abort`,id:s})}),x({t:`req`,id:s,method:n.method,url:i.pathname+i.search,headers:a,...o?.length?{body:r(o)}:{}}).catch(e=>{p.delete(s),t(e)})})},close:()=>{s.close(1e3,`bye`),v(`closed`)},closed:f,...S?{member:S}:{}}}var v=`kraftwerk-remote`;function y(){return new Promise((e,t)=>{let n=indexedDB.open(v,2);n.onupgradeneeded=()=>{let e=n.result.objectStoreNames;e.contains(`machines`)||n.result.createObjectStore(`machines`,{keyPath:`key`}),e.contains(`state`)||n.result.createObjectStore(`state`),e.contains(`joined`)||n.result.createObjectStore(`joined`,{keyPath:`workspaceId`})},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)})}async function b(e,t,n){let r=await y();try{return await new Promise((i,a)=>{let o=n(r.transaction(e,t).objectStore(e));o.onsuccess=()=>i(o.result),o.onerror=()=>a(o.error)})}finally{r.close()}}var x=()=>b(`machines`,`readonly`,e=>e.getAll()),S=e=>b(`machines`,`readwrite`,t=>t.put(e)),C=()=>b(`state`,`readonly`,e=>e.get(`current`)),w=e=>b(`state`,`readwrite`,t=>t.put(e,`current`)),T=e=>b(`joined`,`readonly`,t=>t.get(e)),E=e=>b(`joined`,`readwrite`,t=>t.put(e)),D=`joined:`,O=e=>w(D+e);async function k(){let[e,t]=await Promise.all([C(),x()]);if(!e?.startsWith(D))return t.find(t=>t.key===e)??(t.length===1?t[0]:void 0)}async function A(){let e=await C();if(e?.startsWith(D)){let t=await T(e.slice(7));if(t)return{joined:t};let n=await x();return n.length===1?{machine:n[0]}:void 0}let t=await k();return t?{machine:t}:void 0}var j=e=>e.hosts.find(t=>t.publicKey===e.hostKey)??e.hosts.find(e=>e.online)??e.hosts[0],M=e=>`${e.replace(/^http/,`ws`)}/api/relay/client`,N=self;N.addEventListener(`install`,e=>e.waitUntil(N.skipWaiting())),N.addEventListener(`activate`,e=>e.waitUntil(N.clients.claim()));var P=null;function F(e,t){let n=t?`${e} ${t.workspaceId} ${t.token}`:e;if(P?.key===n)return P.t;P?.t.then(e=>e.close()).catch(()=>{});let r=_({relayUrl:M(N.location.origin),publicKey:o(e),...t?{member:t}:{}}),i={key:n,t:r};return P=i,r.then(e=>void e.closed.then(()=>P===i&&(P=null)),()=>P===i&&(P=null)),r}var I=e=>e.replace(/[&<>"']/g,e=>({"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`})[e]),L=(e,t,n)=>new Response(`<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${t}</title><body style="font:16px system-ui;padding:24px;max-width:36em;margin:auto"><h1 style="font-size:20px">${t}</h1><p>${n}</p><p><a href="/_kw/">Machines</a> · <a href="" onclick="location.reload();return false">Try again</a></p>`,{status:e,headers:{"content-type":`text/html; charset=utf-8`}});async function R(e,t){let n=new URL(e.url),r=new Headers(e.headers);r.set(`authorization`,`Bearer ${t.token}`),r.set(`host`,n.host);let i=await F(t.key),a=e.method===`GET`||e.method===`HEAD`?void 0:await e.arrayBuffer(),o=await i.fetch(n.pathname+n.search,{method:e.method,headers:r,body:a,signal:e.signal});if(e.method===`POST`&&/^(\/w\/[^/]+)?\/api\/pair$/.test(n.pathname)&&o.ok){let{token:e}=await o.clone().json().catch(()=>({}));e&&await S({...t,token:e,pairedAt:new Date().toISOString()})}return o}async function z(e){let t=j(e);if(!t)throw Error(`no machine hosts ${e.name} in the cloud right now`);if(!e.token)throw Error(`no member token for this workspace on this phone — sign in again`);try{let n=await F(t.publicKey,{token:e.token,workspaceId:e.workspaceId}),r=n.member?.workspace.slug;return(r&&r!==e.slug||e.hostKey!==t.publicKey||e.error)&&await E({...e,slug:r??e.slug,hostKey:t.publicKey,error:void 0}),n}catch(t){throw await E({...e,error:t.message}).catch(()=>{}),t}}async function B(e,t){let n=new URL(e.url),r=await z(t),i=r.member?.workspace.slug??t.slug;if(e.mode===`navigate`&&n.pathname===`/`&&i)return Response.redirect(`/w/${encodeURIComponent(i)}/`,302);let a=i&&!n.pathname.startsWith(`/w/`)&&!n.pathname.startsWith(`/api/`)?`/w/${encodeURIComponent(i)}${n.pathname}`:n.pathname,o=new Headers(e.headers);o.delete(`authorization`),o.set(`host`,n.host);let s=e.method===`GET`||e.method===`HEAD`?void 0:await e.arrayBuffer();return r.fetch(a+n.search,{method:e.method,headers:o,body:s,signal:e.signal})}async function V(e){if(!e.clientId)return!1;let t=await N.clients.get(e.clientId).catch(()=>void 0);return!!t&&new URL(t.url).pathname.startsWith(`/_kw/`)}N.addEventListener(`fetch`,e=>{let t=new URL(e.request.url);t.origin!==N.location.origin||t.pathname.startsWith(`/_kw/`)||e.respondWith((async()=>{if(t.pathname.startsWith(`/api/`)&&await V(e))return fetch(e.request);if(e.request.mode===`navigate`&&e.request.method!==`GET`)return L(403,`Refused`,`A form from another page tried to act on your machine.`);let n=await A();if(!n)return e.request.mode===`navigate`?Response.redirect(`/_kw/`,302):new Response(JSON.stringify({error:`no machine paired here`}),{status:503});try{return`joined`in n?await B(e.request,n.joined):await R(e.request,n.machine)}catch(t){if(t.name===`AbortError`)throw t;let r=t.message;return e.request.mode===`navigate`?`joined`in n?L(502,`${I(n.joined.name)} did not open`,`${I(r)}.`):L(502,`${n.machine.label} is not reachable`,`${r}. Is the Mac awake, and is remote access on (<code>kraftwerk remote</code>)?`):new Response(JSON.stringify({error:r}),{status:502,headers:{"content-type":`application/json`}})}})())});function H(e,t){(async()=>{try{let n=await T(e);if(!n)throw Error(`this workspace is not on this phone`);P=null;let r=await z(n);await O(e),t.postMessage({ok:!0,slug:r.member?.workspace.slug??n.slug})}catch(e){t.postMessage({ok:!1,error:e.message})}})()}N.addEventListener(`message`,e=>{let t=e.data,n=e.ports[0];if(t?.type===`member`&&t.workspaceId&&n)return H(t.workspaceId,n);if(t?.type!==`pair`||!t.key||!t.code||!n)return;let r=t.key;(async()=>{try{P=null;let e=await(await F(r)).fetch(`/api/pair`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({code:t.code,name:t.name||`phone`})}),i=await e.json().catch(()=>({}));if(!e.ok||!i.token)throw Error(i.error??`pairing failed (${e.status})`);await S({key:r,token:i.token,label:t.label||`your machine`,pairedAt:new Date().toISOString()}),await w(r),n.postMessage({ok:!0})}catch(e){n.postMessage({ok:!1,error:e.message})}})()})})();
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e}from"./editor-DHAdW1g_.js";export{e as markdown};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=`kraftwerk-remote`;function t(){return new Promise((t,n)=>{let r=indexedDB.open(e,1);r.onupgradeneeded=()=>{r.result.createObjectStore(`machines`,{keyPath:`key`}),r.result.createObjectStore(`state`)},r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error)})}async function n(e,n,r){let i=await t();try{return await new Promise((t,a)=>{let o=r(i.transaction(e,n).objectStore(e));o.onsuccess=()=>t(o.result),o.onerror=()=>a(o.error)})}finally{i.close()}}var r=()=>n(`machines`,`readonly`,e=>e.getAll()),i=e=>n(`machines`,`readwrite`,t=>t.delete(e)),a=e=>n(`state`,`readwrite`,t=>t.put(e,`current`));async function o(e){let t=[...new Uint8Array(await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e))).subarray(0,4)].map(e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,4)}-${t.slice(4)}`}var s=document.getElementById(`app`),c=e=>e.replace(/[&<>"']/g,e=>({"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`})[e]),l=/^[A-Za-z0-9_-]{43}$/;async function u(){if(!(`serviceWorker`in navigator))throw Error(`this browser has no service workers (a private window?)`);let e=await navigator.serviceWorker.register(`/_kw/sw.js`,{scope:`/`});await navigator.serviceWorker.ready;let t=e.active??e.waiting??e.installing;if(!t)throw Error(`the service worker did not start`);return t}function d(e,t){return new Promise((n,r)=>{let i=new MessageChannel,a=setTimeout(()=>r(Error(`no answer — is the machine awake and remote access on?`)),3e4);i.port1.onmessage=e=>{clearTimeout(a);let t=e.data;t.ok?n():r(Error(t.error??`pairing failed`))},e.postMessage({type:`pair`,...t},[i.port2])})}async function f(e,t,n){let r=await o(e),i=/iPhone/.test(navigator.userAgent)?`iPhone`:/iPad/.test(navigator.userAgent)?`iPad`:/Android/.test(navigator.userAgent)?`Android phone`:`browser`;s.innerHTML=`
|
|
2
|
-
<h1>Pair with ${c(n)}</h1>
|
|
3
|
-
<p class="dim">This phone reaches ${c(n)} from anywhere, end to end encrypted: the relay in between cannot read along.</p>
|
|
4
|
-
<div class="card">
|
|
5
|
-
<p>Machine key <code>${c(r)}</code><br><span class="dim">The same as <code>kraftwerk remote</code> shows on the machine.</span></p>
|
|
6
|
-
<label for="name">Name this device</label>
|
|
7
|
-
<input id="name" value="${c(i)}" autocomplete="off" />
|
|
8
|
-
<button id="go">Pair</button>
|
|
9
|
-
<p id="msg" class="dim"></p>
|
|
10
|
-
</div>`;let a=document.getElementById(`go`),l=document.getElementById(`msg`);a.onclick=async()=>{a.disabled=!0,l.className=`dim`,l.textContent=`Connecting…`;try{await d(await u(),{key:e,code:t,label:n,name:document.getElementById(`name`).value.trim()}),history.replaceState(null,``,`/_kw/`),location.href=`/`}catch(e){a.disabled=!1,l.className=`err`,l.textContent=e.message}}}async function p(e){let t=await r().catch(()=>[]),n=await Promise.all(t.map(async e=>`<div class="card machine"><a href="/" data-open="${c(e.key)}">${c(e.label)}<br><span class="dim">key <code>${c(await o(e.key))}</code></span></a><button class="quiet" data-forget="${c(e.key)}">Forget</button></div>`));s.innerHTML=`
|
|
11
|
-
<h1>kraftwerk remote</h1>
|
|
12
|
-
${e?`<p class="err">${c(e)}</p>`:``}
|
|
13
|
-
${n.join(``)||`<div class="card"><p>No machine paired on this phone yet.</p><p class="dim">On the Mac: <code>kraftwerk remote on</code>, then <code>kraftwerk remote pair</code> — or scan the QR code under Settings → Devices — and open the link here.</p></div>`}`,s.querySelectorAll(`[data-open]`).forEach(e=>{e.onclick=async t=>{t.preventDefault(),await u(),await a(e.dataset.open),location.href=`/`}}),s.querySelectorAll(`[data-forget]`).forEach(e=>{e.onclick=async()=>{await i(e.dataset.forget),p()}}),t.length&&u().catch(()=>{})}var m=new URLSearchParams(location.hash.slice(1)),h=m.get(`k`)??``,g=m.get(`c`)??``;l.test(h)&&g?f(h,g,m.get(`n`)||`your machine`):p(l.test(h)?"This link has no pairing code — make a new one with `kraftwerk remote pair`.":void 0);
|