@chatpanel/events 0.358.2 → 0.358.3

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.
Files changed (2) hide show
  1. package/link-fetch.js +1 -1
  2. package/package.json +1 -1
package/link-fetch.js CHANGED
@@ -1 +1 @@
1
- import{createLinkEndpoint as M}from"./link-connect.js";import{noiseCrypto as Q}from"./link-noise.js";import{parsePairingCode as X,admissionToken as Y,toB64url as _,fromB64url as D,linkPathAllowed as Z}from"./link-protocol.js";import{relaySocketUrl as ee,tunnelSocketUrl as te}from"./link-routes.js";const I=1,re="chatpanel-link.v1",i=(e,s)=>Object.assign(new TypeError(`link: ${e}`),{code:s}),L=()=>{try{return new DOMException("The operation was aborted.","AbortError")}catch{return Object.assign(new Error("aborted"),{name:"AbortError"})}},U=e=>{try{return new URL(e).host}catch{return"the relay"}};function ne(e){if(!(e&&typeof e=="object"&&e.v===I&&typeof e.room=="string"&&typeof e.token=="string"&&typeof e.deviceKey=="string"&&typeof e.gatewayKey=="string"&&(typeof e.relay=="string"||typeof e.gw=="string")))throw i("the stored device state is not a ChatPanel Link device (pair again with a new code)","bad_state");return e}function oe(e){const s=e.gw&&te(e.gw,e.room),f=e.relay&&ee(e.relay,e.room);return e.route==="tailscale"||e.route==="cloudflare"?[s].filter(Boolean):e.route==="link"||e.route==="relay"?[f].filter(Boolean):[f,s].filter(Boolean)}const V=oe;function ae(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):new Uint8Array(0)}function ie(e){const s={};if(!e)return s;if(typeof e.forEach=="function"&&!Array.isArray(e))return e.forEach((f,g)=>{s[g]=f}),s;for(const[f,g]of Array.isArray(e)?e:Object.entries(e))g!=null&&(s[f]=String(g));return s}async function se(e){return e==null?null:typeof e=="string"||e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):typeof URLSearchParams<"u"&&e instanceof URLSearchParams?e.toString():new Uint8Array(await new Response(e).arrayBuffer())}async function de({pairing:e=null,state:s=null,store:f=null,name:g="Partner server",WebSocketImpl:T=globalThis.WebSocket,crypto:P=Q(),now:$=()=>Date.now(),connectTimeoutMs:F=15e3,keepaliveMs:x=25e3,setTimer:O=setTimeout,clearTimer:R=clearTimeout}={}){if(typeof T!="function")throw i("no WebSocket here — pass WebSocketImpl (the `ws` package on Node 20)","no_websocket");let b=s||(f?await f.load():null)||null;function C(n,t,{keyPair:r,psk:o=null,peerKey:c=null,laptopKey:k=null,onClose:d=()=>{}}){return new Promise((h,K)=>{let y=null,m=!1;const v=O(()=>l(i(`the gateway did not answer through ${U(n)} — is it running and online?`,"unreachable")),F);function l(a,p){if(!m)if(m=!0,R(v),a){try{y?.close(1e3,"done")}catch{}K(a)}else h(p)}const u=M({side:"phone",keyPair:r,psk:o,peerKey:c,laptopKey:k,name:String(g||"").slice(0,60),crypto:P,sendBytes:a=>{try{y.send(a)}catch{}},onSession:a=>l(null,{ws:y,endpoint:u,session:a}),onEnd:a=>{/^handshake/.test(String(a))&&l(i(`the gateway refused this device (${String(a).replace(/^handshake: /,"")})`,"refused"))}});try{y=new T(n,[re,t])}catch(a){l(i(`cannot open ${U(n)}: ${a?.message||a}`,"unreachable"));return}y.binaryType="arraybuffer",y.onmessage=a=>typeof a.data=="string"?u.onText(a.data):u.onBinary(ae(a.data)),y.onerror=()=>{},y.onclose=a=>{u.close("socket closed");const p=a?.code;d(p),l(p===4003||p===4004?i("the computer’s owner removed this device — pair again with a new code","revoked"):p===1008?i(`${U(n)} did not admit this device — the code expired or was used, or the device was removed`,"refused"):i(`${U(n)} closed the connection (${p||"no code"}) — is the gateway online?`,"unreachable"))}})}async function N(n){const t=X(n,{now:$()}),r=await P.generateKeyPair(),o=_(t.room),c=await Y(t.secret,t.room,"phone");let k=null;for(const d of V({relay:t.relay,gw:t.gw,room:o,route:t.route}))try{const h=await C(d,c,{keyPair:r,psk:t.secret,laptopKey:t.key});h.session.close("paired");try{h.ws.close(1e3,"paired")}catch{}return{v:I,route:t.route,relay:t.relay,gw:t.gw,room:o,token:c,deviceKey:_(r.raw),gatewayKey:_(t.key),pairedAt:$()}}catch(h){if(k=h,h?.code==="refused")break}throw k||i("the code names no way to reach the gateway","bad_code")}if(!b){if(!e)throw i("pass `pairing` (a cplink1. code from the computer’s owner) or a `store` holding an earlier pairing","not_paired");b=await N(e),f&&await f.save(b)}ne(b);const A=b;let j=null,w=null,E=null,q=!1;const W=n=>!!n&&!n.dead&&!n.session.closed;async function G(){j||=await P.keyPairFromPrivate(D(A.deviceKey));let n=null;for(const t of V(A)){const r={dead:!1};try{Object.assign(r,await C(t,A.token,{keyPair:j,peerKey:D(A.gatewayKey),onClose:c=>{r.dead=!0,R(r.keepalive),(c===4003||c===4004)&&(E=i("the computer’s owner removed this device — pair again with a new code","revoked"))}}));const o=()=>{if(!r.dead){try{r.ws.send("ping")}catch{}r.keepalive=O(o,x),r.keepalive?.unref?.()}};return r.keepalive=O(o,x),r.keepalive?.unref?.(),r}catch(o){if(n=o,o?.code==="revoked")throw E=o,o;if(o?.code==="refused")throw o}}throw n}async function H(n){if(q)throw i("this Link fetch was closed","closed");if(E)throw E;if(w){const r=await w.catch(()=>null);if(W(r))return r;w=null}const t=G();if(w=t,t.catch(()=>{w===t&&(w=null)}),!n)return t;if(n.aborted)throw L();return Promise.race([t,new Promise((r,o)=>n.addEventListener("abort",()=>o(L()),{once:!0}))])}async function z(n,t={}){const r=typeof Request<"u"&&n instanceof Request?n:null,o=new URL(r?r.url:String(n),"http://127.0.0.1"),c=String(t.method||r?.method||"GET").toUpperCase(),k=ie(t.headers??r?.headers),d=t.signal??r?.signal??null;if(d?.aborted)throw L();const h=o.pathname+o.search;if(!Z(h))throw i(`only the gateway's /v1/ API crosses Link (asked for ${o.pathname})`,"path");const K=t.body!==void 0?t.body:r&&c!=="GET"&&c!=="HEAD"?await r.arrayBuffer():null,y=await se(K),m=await H(d),v=new AbortController,l=()=>v.abort();d?.addEventListener?.("abort",l,{once:!0});let u;try{u=await m.session.request({method:c,path:h,headers:k,body:y,signal:v.signal})}catch(S){throw d?.removeEventListener?.("abort",l),d?.aborted?L():(m.session.closed&&(m.dead=!0),i(S?.message?.replace(/^link: /,"")||"the request failed","request"))}const a=u.body[Symbol.asyncIterator](),J=u.status===204||u.status===205||u.status===304||c==="HEAD"?null:new ReadableStream({async pull(S){try{const B=await a.next();B.done?(d?.removeEventListener?.("abort",l),S.close()):S.enqueue(B.value)}catch(B){d?.removeEventListener?.("abort",l),S.error(v.signal.aborted?L():i(B?.message?.replace(/^link: /,"")||"the response was cut off","stream"))}},cancel(){v.abort()}});return new Response(J,{status:u.status,headers:u.headers})}return Object.defineProperties(z,{deviceId:{value:A.room,enumerable:!0},revoked:{get:()=>E,enumerable:!0},close:{value:()=>{q=!0;const n=w;w=null,n?.then(t=>{t.dead=!0,R(t.keepalive),t.endpoint.close("closed");try{t.ws.close(1e3,"closed")}catch{}},()=>{})}}})}export{I as LINK_DEVICE_STATE_VERSION,ne as checkLinkDeviceState,de as createLinkFetch,oe as linkSocketUrls};
1
+ import{createLinkEndpoint as M}from"./link-connect.js";import{noiseCrypto as Q}from"./link-noise.js";import{parsePairingCode as X,admissionToken as Y,toB64url as _,fromB64url as D,linkPathAllowed as Z}from"./link-protocol.js";import{relaySocketUrl as ee,tunnelSocketUrl as te}from"./link-routes.js";const I=1,re="chatpanel-link.v1",i=(e,s)=>Object.assign(new TypeError(`link: ${e}`),{code:s}),L=()=>{try{return new DOMException("The operation was aborted.","AbortError")}catch{return Object.assign(new Error("aborted"),{name:"AbortError"})}},B=e=>{try{return new URL(e).host}catch{return"the relay"}};function ne(e){if(!(e&&typeof e=="object"&&e.v===I&&typeof e.room=="string"&&typeof e.token=="string"&&typeof e.deviceKey=="string"&&typeof e.gatewayKey=="string"&&(typeof e.relay=="string"||typeof e.gw=="string")))throw i("the stored device state is not a ChatPanel Link device (pair again with a new code)","bad_state");return e}function oe(e){const s=e.gw&&te(e.gw,e.room),f=e.relay&&ee(e.relay,e.room);return e.route==="tailscale"||e.route==="cloudflare"?[s].filter(Boolean):e.route==="link"||e.route==="relay"?[f].filter(Boolean):[f,s].filter(Boolean)}const V=oe;function ae(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):new Uint8Array(0)}function ie(e){const s={};if(!e)return s;if(typeof e.forEach=="function"&&!Array.isArray(e))return e.forEach((f,g)=>{s[g]=f}),s;for(const[f,g]of Array.isArray(e)?e:Object.entries(e))g!=null&&(s[f]=String(g));return s}async function se(e){return e==null?null:typeof e=="string"||e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):typeof URLSearchParams<"u"&&e instanceof URLSearchParams?e.toString():new Uint8Array(await new Response(e).arrayBuffer())}async function de({pairing:e=null,state:s=null,store:f=null,name:g="Partner server",WebSocketImpl:$=globalThis.WebSocket,crypto:O=Q(),now:x=()=>Date.now(),connectTimeoutMs:F=15e3,keepaliveMs:C=25e3,setTimer:U=setTimeout,clearTimer:R=clearTimeout}={}){if(typeof $!="function")throw i("no WebSocket here — pass WebSocketImpl (the `ws` package on Node 20)","no_websocket");let b=s||(f?await f.load():null)||null;function T(n,t,{keyPair:r,psk:o=null,peerKey:l=null,laptopKey:k=null,onClose:d=()=>{}}){return new Promise((h,K)=>{let y=null,m=!1;const v=U(()=>c(i(`the gateway did not answer through ${B(n)} — is it running and online?`,"unreachable")),F);function c(a,p){if(!m)if(m=!0,R(v),a){try{y?.close(1e3,"done")}catch{}K(a)}else h(p)}const u=M({side:"phone",keyPair:r,psk:o,peerKey:l,laptopKey:k,name:String(g||"").slice(0,60),crypto:O,sendBytes:a=>{try{y.send(a)}catch{}},onSession:a=>c(null,{ws:y,endpoint:u,session:a}),onEnd:a=>{/^handshake/.test(String(a))&&c(i(`the gateway refused this device (${String(a).replace(/^handshake: /,"")})`,"refused"))}});try{y=new $(n,[re,t])}catch(a){c(i(`cannot open ${B(n)}: ${a?.message||a}`,"unreachable"));return}y.binaryType="arraybuffer",y.onmessage=a=>typeof a.data=="string"?u.onText(a.data):u.onBinary(ae(a.data)),y.onerror=()=>{U(()=>c(i(`${B(n)} refused the connection — is the gateway online, and this device still paired?`,"unreachable")),100)},y.onclose=a=>{u.close("socket closed");const p=a?.code;d(p),c(p===4003||p===4004?i("the computer’s owner removed this device — pair again with a new code","revoked"):p===1008?i(`${B(n)} did not admit this device — the code expired or was used, or the device was removed`,"refused"):i(`${B(n)} closed the connection (${p||"no code"}) — is the gateway online?`,"unreachable"))}})}async function N(n){const t=X(n,{now:x()}),r=await O.generateKeyPair(),o=_(t.room),l=await Y(t.secret,t.room,"phone");let k=null;for(const d of V({relay:t.relay,gw:t.gw,room:o,route:t.route}))try{const h=await T(d,l,{keyPair:r,psk:t.secret,laptopKey:t.key});h.session.close("paired");try{h.ws.close(1e3,"paired")}catch{}return{v:I,route:t.route,relay:t.relay,gw:t.gw,room:o,token:l,deviceKey:_(r.raw),gatewayKey:_(t.key),pairedAt:x()}}catch(h){if(k=h,h?.code==="refused")break}throw k||i("the code names no way to reach the gateway","bad_code")}if(!b){if(!e)throw i("pass `pairing` (a cplink1. code from the computer’s owner) or a `store` holding an earlier pairing","not_paired");b=await N(e),f&&await f.save(b)}ne(b);const A=b;let j=null,w=null,E=null,q=!1;const W=n=>!!n&&!n.dead&&!n.session.closed;async function G(){j||=await O.keyPairFromPrivate(D(A.deviceKey));let n=null;for(const t of V(A)){const r={dead:!1};try{Object.assign(r,await T(t,A.token,{keyPair:j,peerKey:D(A.gatewayKey),onClose:l=>{r.dead=!0,R(r.keepalive),(l===4003||l===4004)&&(E=i("the computer’s owner removed this device — pair again with a new code","revoked"))}}));const o=()=>{if(!r.dead){try{r.ws.send("ping")}catch{}r.keepalive=U(o,C),r.keepalive?.unref?.()}};return r.keepalive=U(o,C),r.keepalive?.unref?.(),r}catch(o){if(n=o,o?.code==="revoked")throw E=o,o;if(o?.code==="refused")throw o}}throw n}async function H(n){if(q)throw i("this Link fetch was closed","closed");if(E)throw E;if(w){const r=await w.catch(()=>null);if(W(r))return r;w=null}const t=G();if(w=t,t.catch(()=>{w===t&&(w=null)}),!n)return t;if(n.aborted)throw L();return Promise.race([t,new Promise((r,o)=>n.addEventListener("abort",()=>o(L()),{once:!0}))])}async function z(n,t={}){const r=typeof Request<"u"&&n instanceof Request?n:null,o=new URL(r?r.url:String(n),"http://127.0.0.1"),l=String(t.method||r?.method||"GET").toUpperCase(),k=ie(t.headers??r?.headers),d=t.signal??r?.signal??null;if(d?.aborted)throw L();const h=o.pathname+o.search;if(!Z(h))throw i(`only the gateway's /v1/ API crosses Link (asked for ${o.pathname})`,"path");const K=t.body!==void 0?t.body:r&&l!=="GET"&&l!=="HEAD"?await r.arrayBuffer():null,y=await se(K),m=await H(d),v=new AbortController,c=()=>v.abort();d?.addEventListener?.("abort",c,{once:!0});let u;try{u=await m.session.request({method:l,path:h,headers:k,body:y,signal:v.signal})}catch(S){throw d?.removeEventListener?.("abort",c),d?.aborted?L():(m.session.closed&&(m.dead=!0),i(S?.message?.replace(/^link: /,"")||"the request failed","request"))}const a=u.body[Symbol.asyncIterator](),J=u.status===204||u.status===205||u.status===304||l==="HEAD"?null:new ReadableStream({async pull(S){try{const P=await a.next();P.done?(d?.removeEventListener?.("abort",c),S.close()):S.enqueue(P.value)}catch(P){d?.removeEventListener?.("abort",c),S.error(v.signal.aborted?L():i(P?.message?.replace(/^link: /,"")||"the response was cut off","stream"))}},cancel(){v.abort()}});return new Response(J,{status:u.status,headers:u.headers})}return Object.defineProperties(z,{deviceId:{value:A.room,enumerable:!0},revoked:{get:()=>E,enumerable:!0},close:{value:()=>{q=!0;const n=w;w=null,n?.then(t=>{t.dead=!0,R(t.keepalive),t.endpoint.close("closed");try{t.ws.close(1e3,"closed")}catch{}},()=>{})}}})}export{I as LINK_DEVICE_STATE_VERSION,ne as checkLinkDeviceState,de as createLinkFetch,oe as linkSocketUrls};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/events",
3
- "version": "0.358.2",
3
+ "version": "0.358.3",
4
4
  "description": "The canonical ChatPanel event-log and capability contracts — typed durable facts, clock-free deterministic linearization, schema upcasting, and the invariants the replay harness asserts. Pure, dependency-free ESM shared by the ChatPanel extension, gateway and bridge.",
5
5
  "type": "module",
6
6
  "main": "index.js",