@graffiti-garden/wrapper-data-guard 0.2.0 → 0.2.2

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.
@@ -1,2 +1,2 @@
1
- import"./chunk-ZI3DQDJT.js";var O=class{};var te={type:"object",properties:{value:{type:"object"},channels:{type:"array",items:{type:"string"}},allowed:{type:"array",items:{type:"string"},nullable:!0},url:{type:"string"},actor:{type:"string"}},additionalProperties:!1,required:["value","channels","actor","url"]},be={...te,required:["value","channels"]};var I=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorForbidden",Object.setPrototypeOf(this,e.prototype)}},R=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorNotFound",Object.setPrototypeOf(this,e.prototype)}},T=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorInvalidSchema",Object.setPrototypeOf(this,e.prototype)}},N=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorSchemaMismatch",Object.setPrototypeOf(this,e.prototype)}},L=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorTooLarge",Object.setPrototypeOf(this,e.prototype)}},P=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorNotAcceptable",Object.setPrototypeOf(this,e.prototype)}},D=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorCursorExpired",Object.setPrototypeOf(this,e.prototype)}};var m=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},re=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof m?e.code:void 0}),se=({name:e,message:t,stack:r,penpalCode:s})=>{let o=s?new m(s,t):new Error(t);return o.name=e,o.stack=r,o},oe=Symbol("Reply"),ne=class{value;transferables;#t=oe;constructor(e,t){this.value=e,this.transferables=t?.transferables}},A="penpal",Y=e=>typeof e=="object"&&e!==null,ie=e=>typeof e=="function",ae=e=>Y(e)&&e.namespace==="penpal",G=e=>e.type==="SYN",K=e=>e.type==="ACK1",k=e=>e.type==="ACK2",ce=e=>e.type==="CALL",de=e=>e.type==="REPLY",le=e=>e.type==="DESTROY",ue=(e,t)=>{let r=e.reduce((s,o)=>{if(!(!Y(s)||!Object.hasOwn(s,o)))return s[o]},t);return ie(r)?r:void 0},w=e=>e.join("."),F=(e,t,r)=>({namespace:A,channel:e,type:"REPLY",callId:t,isError:!0,...r instanceof Error?{value:re(r),isSerializedErrorInstance:!0}:{value:r}}),he=(e,t,r,s)=>{let o=!1,d=async h=>{if(o||!ce(h))return;s?.(`Received ${w(h.methodPath)}() call`,h);let{methodPath:a,args:l,id:f}=h,i,n;try{let c=ue(a,t);if(!c)throw new m("METHOD_NOT_FOUND",`Method \`${w(a)}\` is not found.`);let y=await c(...l);y instanceof ne&&(n=y.transferables,y=await y.value),i={namespace:A,channel:r,type:"REPLY",callId:f,value:y}}catch(c){i=F(r,f,c)}if(!o)try{s?.(`Sending ${w(a)}() reply`,i),e.sendMessage(i,n)}catch(c){throw c.name==="DataCloneError"&&(i=F(r,f,c),s?.(`Sending ${w(a)}() reply`,i),e.sendMessage(i)),c}},p=h=>{d(h).catch(a=>{console.error(a)})};return e.addMessageHandler(p),()=>{o=!0,e.removeMessageHandler(p)}},V=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),fe=Symbol("CallOptions"),H=class{transferables;timeout;#t=fe;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},pe=new Set(["apply","call","bind"]),J=(e,t,r=[])=>new Proxy(r.length?()=>{}:Object.create(null),{get(s,o){if(o!=="then")return r.length&&pe.has(o)?Reflect.get(s,o):J(e,t,[...r,o])},apply(s,o,d){return e(r,d)}}),z=e=>new m("CONNECTION_DESTROYED",`Method call ${w(e)}() failed due to destroyed connection`),me=(e,t,r)=>{let s=!1,o=new Map,d=a=>{if(!de(a))return;let{callId:l,value:f,isError:i,isSerializedErrorInstance:n}=a,c=o.get(l);c&&(o.delete(l),clearTimeout(c.timeoutId),r?.(`Received ${w(c.methodPath)}() reply`,a),i?c.reject(n?se(f):f):c.resolve(f))};return e.addMessageHandler(d),{remoteProxy:J((a,l)=>{if(s)throw z(a);let f=V(),i=l[l.length-1],n=i instanceof H,{timeout:c,transferables:y}=n?i:{},g=n?l.slice(0,-1):l;return new Promise((U,M)=>{let E=c!==void 0?globalThis.setTimeout(()=>{o.delete(f),M(new m("METHOD_CALL_TIMEOUT",`Method call ${w(a)}() timed out after ${c}ms`))},c):void 0;o.set(f,{methodPath:a,resolve:U,reject:M,timeoutId:E});try{let v={namespace:A,channel:t,type:"CALL",id:f,methodPath:a,args:g};r?.(`Sending ${w(a)}() call`,v),e.sendMessage(v,y)}catch(v){o.delete(f),clearTimeout(E),M(new m("TRANSMISSION_FAILED",v.message))}})},r),destroy:()=>{s=!0,e.removeMessageHandler(d);for(let{methodPath:a,reject:l,timeoutId:f}of o.values())clearTimeout(f),l(z(a));o.clear()}}},ye=({messenger:e,methods:t,timeout:r,channel:s,log:o})=>{let d=V(),p,h=[],a=!1,l=!1,{promise:f,resolve:i,reject:n}=Promise.withResolvers(),c=r!==void 0?setTimeout(()=>{g(new m("CONNECTION_TIMEOUT",`Connection timed out after ${r}ms`))},r):void 0,y=()=>{clearTimeout(c);for(let u of h.splice(0))u()},g=u=>{l||(l=!0,y(),n(u))},U=()=>{g(new m("CONNECTION_DESTROYED","Connection destroyed"))},M=()=>{if(l||a)return;h.push(he(e,t,s,o));let{remoteProxy:u,destroy:_}=me(e,s,o);h.push(_),clearTimeout(c),a=!0,i(u)},E=u=>{if(l)return!1;o?.(`Sending handshake ${u.type}`,u);try{return e.sendMessage(u),!0}catch(_){return g(new m("TRANSMISSION_FAILED",_.message)),!1}},v=()=>E({namespace:A,type:"SYN",channel:s,participantId:d}),Q=u=>{o?.("Received handshake SYN",u),u.participantId!==p&&(p=u.participantId,v()&&d>p&&E({namespace:"penpal",channel:s,type:"ACK1"}))},Z=u=>{o?.("Received handshake ACK1",u),E({namespace:"penpal",channel:s,type:"ACK2"})&&M()},ee=u=>{o?.("Received handshake ACK2",u),M()},j=u=>{l||(G(u)?Q(u):K(u)?Z(u):k(u)&&ee(u))};return e.addMessageHandler(j),h.push(()=>e.removeMessageHandler(j)),v(),{promise:f,destroy:U}},ge=e=>{let t=!1,r;return(...s)=>(t||(t=!0,r=e(...s)),r)},$=new WeakSet,W=()=>new m("CONNECTION_DESTROYED","Connection destroyed"),q=({messenger:e,methods:t={},timeout:r,channel:s,log:o})=>{if(!e)throw new m("INVALID_ARGUMENT","messenger must be defined");if($.has(e))throw new m("INVALID_ARGUMENT","A messenger can only be used for a single connection");$.add(e);let d=Promise.withResolvers(),p=!1,h,a=()=>{let i={namespace:A,channel:s,type:"DESTROY"};try{e.sendMessage(i)}catch{}},l=ge((i,n)=>{p=!0,n&&a(),e.destroy(),h?.(),d.reject(i),o?.("Connection destroyed")}),f=i=>ae(i)&&i.channel===s;return(async()=>{try{e.initialize({log:o,validateReceivedMessage:f}),e.addMessageHandler(c=>{le(c)&&l(W(),!1)});let i=ye({messenger:e,methods:t,timeout:r,channel:s,log:o});h=i.destroy;let n=await i.promise;if(p){i.destroy();return}d.resolve(n)}catch(i){l(i,!0)}})(),{promise:d.promise,destroy:()=>{l(W(),!0)}}},B=class{#t;#s;#i;#a;#o;#r=new Set;#e;constructor({remoteWindow:e,allowedOrigins:t}){if(!e)throw new m("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#s=t?.length?t:[window.origin]}initialize=({log:e,validateReceivedMessage:t})=>{this.#i=e,this.#a=t,window.addEventListener("message",this.#l)};sendMessage=(e,t)=>{if(G(e)){let r=this.#c(e);this.#t.postMessage(e,{targetOrigin:r,...t===void 0?{}:{transfer:t}});return}if(K(e)){let r=this.#c(e);this.#t.postMessage(e,{targetOrigin:r,...t===void 0?{}:{transfer:t}});return}if(k(e)){let{port1:r,port2:s}=new MessageChannel;this.#d(r);let o=[s,...t||[]],d=this.#c(e);try{this.#t.postMessage(e,{targetOrigin:d,transfer:o})}catch(p){throw this.#n(),s.close(),p}return}if(this.#e){this.#e.postMessage(e,{...t===void 0?{}:{transfer:t}});return}throw new m("TRANSMISSION_FAILED","Cannot send message because the MessagePort is not connected")};addMessageHandler=e=>{this.#r.add(e)};removeMessageHandler=e=>{this.#r.delete(e)};destroy=()=>{window.removeEventListener("message",this.#l),this.#n(),this.#r.clear()};#h=e=>this.#s.some(t=>t instanceof RegExp?new RegExp(t).test(e):t===e||t==="*");#c=e=>{if(G(e))return"*";if(!this.#o)throw new m("TRANSMISSION_FAILED","Cannot send message because the remote origin is not established");return this.#o==="null"&&this.#s.includes("*")?"*":this.#o};#n=()=>{this.#e?.removeEventListener("message",this.#u),this.#e?.close(),this.#e=void 0};#d=e=>{this.#n(),this.#e=e,this.#e.addEventListener("message",this.#u),this.#e.start()};#l=({source:e,origin:t,ports:r,data:s})=>{if(e===this.#t&&this.#a?.(s)){if(!this.#h(t)){this.#i?.(`Received a message from origin \`${t}\` which did not match allowed origins \`[${this.#s.join(", ")}]\``);return}if(G(s)&&(this.#n(),this.#o=t),k(s)){let o=r[0];if(!o){this.#i?.("Ignoring ACK2 because it did not include a MessagePort");return}this.#d(o)}for(let o of this.#r)o(s)}};#u=({data:e})=>{if(this.#a?.(e))for(let t of this.#r)t(e)}};var we=["post","get","delete","deleteMedia","login","logout","actorToHandle","handleToActor"],ve=[D,I,T,P,R,N,L],C,x=class extends O{sessionEvents=new EventTarget;connection;remote_;remoteClient;sessions=new Map;initialized=!1;destroyed=!1;constructor(t){if(super(),C){let r=C;return window.setTimeout(()=>r.replayInitialization(),0),r}this.connection=q({messenger:new B({remoteWindow:t.remoteWindow,allowedOrigins:t.allowedOrigins??["*"]}),channel:t.channel,timeout:t.timeout,methods:{sessionEvent:(r,s)=>{if(r==="login"){let o=s;o.error||this.sessions.set(o.session.actor,o.session)}else if(r==="logout"){let o=s;o.error||this.sessions.delete(o.actor)}else r==="initialized"&&(this.initialized=!0,s=void 0);this.sessionEvents.dispatchEvent(new CustomEvent(r,{detail:s}))}}}),this.remote_=this.connection.promise.then(r=>{let s=Me(r);return this.remoteClient=s,s}),window.setTimeout(()=>{this.remote_.then(r=>r.initialize()).catch(()=>{})},0);for(let r of we)this[r]=async(...s)=>{let d=(await this.remote())[r];return d(...S(s))};C=this}destroy(){this.destroyed||(this.destroyed=!0,C===this&&(C=void 0),this.remoteClient?.destroy().catch(()=>{}),this.connection.destroy())}replayInitialization(){if(!this.destroyed){for(let t of this.sessions.values())this.sessionEvents.dispatchEvent(new CustomEvent("login",{detail:{session:t}}));this.initialized&&this.sessionEvents.dispatchEvent(new CustomEvent("initialized",{detail:void 0}))}}getMedia=async(...t)=>{let r=await(await this.remote()).getMedia(...S(t));return{...r,data:new Blob([r.data.buffer],{type:r.data.type})}};postMedia=async(t,r)=>{let s=await t.data.arrayBuffer();return(await this.remote()).postMedia({...S(t),data:{buffer:s,type:t.data.type}},S(r),new H({transferables:[s]}))};remoteStream(t){let r=`graffiti-rpc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`,s=this.remote_;return(async function*(){let o=await s;try{for(await t(o,r);;){let d=await o.streamNext(r);if(d.done)return d.value;yield d.value}}finally{await o.streamReturn(r)}})()}discover=(...t)=>this.remoteStream((r,s)=>r.discover(s,...S(t)));continueDiscover=(...t)=>this.remoteStream((r,s)=>r.continueDiscover(s,...S(t)));remote(){if(this.destroyed)throw new Error("Graffiti RPC client has been destroyed.");return this.remote_}};function S(e){return JSON.parse(JSON.stringify(e))}function Me(e){return new Proxy(e,{get(t,r){let s=Reflect.get(t,r);return typeof s=="function"?(...o)=>s(...o).catch(Ee):s}})}function Ee(e){if(e instanceof Error){let t=ve.find(({name:r})=>r===e.name);t&&Object.setPrototypeOf(e,t.prototype)}throw e}var b,Oe=1e4,X=class extends O{sessionEvents;hostUrl;iframe;rpc;onMessage;connectionTimeout;destroyed=!1;constructor(t={}){if(super(),b)return b;let r=new URL(t.hostUrl?.toString()??"https://guard.graffiti.garden/",document.baseURI),s=document.createElement("iframe");s.title="Graffiti Guard",s.allow="storage-access",s.sandbox.add("allow-scripts","allow-same-origin","allow-storage-access-by-user-activation","allow-downloads","allow-forms","allow-popups","allow-top-navigation-by-user-activation"),s.src=r.href,s.setAttribute("aria-hidden","true"),Object.assign(s.style,{background:"transparent",border:"0",display:"none",height:"100vh",inset:"0",position:"fixed",width:"100vw",zIndex:"2147483647"});let o=!1,d=!1;s.addEventListener("load",()=>{s.contentWindow?.postMessage({type:"graffiti-guard:connect"},r.origin)});let p=()=>{o||d||(d=!0,window.alert(`This app could not connect to its data service at ${r.origin}. The service may be offline, or your browser may be blocking its security certificate. Open that address directly, then reload this page.`))};s.addEventListener("error",p),document.body.append(s);let h=s.contentWindow;if(!h)throw s.remove(),new Error("Graffiti Guard iframe did not create a content window.");let a=new x({remoteWindow:h,allowedOrigins:[r.origin],channel:t.channel,timeout:t.timeout}),l=window.setTimeout(p,t.timeout??Oe),f=()=>{o=!0,window.clearTimeout(l)},i=n=>{n.source!==h||n.origin!==r.origin||!n.data||typeof n.data!="object"||(f(),n.data.type!=="graffiti-guard:connected"&&(n.data.type==="graffiti-guard:set-visible"&&typeof n.data.visible=="boolean"?(s.style.display=n.data.visible?"block":"none",s.setAttribute("aria-hidden",String(!n.data.visible)),n.data.visible&&(s.focus(),h.postMessage({type:"graffiti-guard:shown"},r.origin))):n.data.type==="graffiti-guard:open-audit"&&typeof n.data.actor=="string"&&Array.isArray(n.data.source)&&window.location.assign(this.auditUrl(n.data))))};return window.addEventListener("message",i),this.hostUrl=r,this.iframe=s,this.rpc=a,this.sessionEvents=a.sessionEvents,this.onMessage=i,this.connectionTimeout=l,b=new Proxy(this,{get(n,c,y){if(Reflect.has(n,c))return Reflect.get(n,c,y);let g=Reflect.get(a,c,a);return typeof g=="function"?g.bind(a):g}}),b}login=t=>{let r=new URL(this.hostUrl);return r.searchParams.set("guardLogin","1"),r.searchParams.set("redirectUrl",window.location.href),t&&r.searchParams.set("suggestedActor",t),window.location.assign(r.href),Promise.resolve()};auditUrl(t){let r=new URL(this.hostUrl);return r.searchParams.set("redirectUrl",window.location.href),r.searchParams.set("source",JSON.stringify(t?.source??[])),t?.actor&&r.searchParams.set("actor",t.actor),r.href}destroy(){this.destroyed||(this.destroyed=!0,window.clearTimeout(this.connectionTimeout),window.removeEventListener("message",this.onMessage),this.rpc.destroy(),this.iframe.remove(),b===this&&(b=void 0))}};export{X as GraffitiGuarded};
1
+ import"./chunk-ZI3DQDJT.js";var O=class{};var te={type:"object",properties:{value:{type:"object"},channels:{type:"array",items:{type:"string"}},allowed:{type:"array",items:{type:"string"},nullable:!0},url:{type:"string"},actor:{type:"string"}},additionalProperties:!1,required:["value","channels","actor","url"]},be={...te,required:["value","channels"]};var I=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorForbidden",Object.setPrototypeOf(this,e.prototype)}},R=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorNotFound",Object.setPrototypeOf(this,e.prototype)}},T=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorInvalidSchema",Object.setPrototypeOf(this,e.prototype)}},N=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorSchemaMismatch",Object.setPrototypeOf(this,e.prototype)}},L=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorTooLarge",Object.setPrototypeOf(this,e.prototype)}},P=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorNotAcceptable",Object.setPrototypeOf(this,e.prototype)}},D=class e extends Error{constructor(t){super(t),this.name="GraffitiErrorCursorExpired",Object.setPrototypeOf(this,e.prototype)}};var g=class extends Error{code;constructor(e,t){super(t),this.name="PenpalError",this.code=e}},re=e=>({name:e.name,message:e.message,stack:e.stack,penpalCode:e instanceof g?e.code:void 0}),se=({name:e,message:t,stack:r,penpalCode:s})=>{let o=s?new g(s,t):new Error(t);return o.name=e,o.stack=r,o},oe=Symbol("Reply"),ne=class{value;transferables;#t=oe;constructor(e,t){this.value=e,this.transferables=t?.transferables}},A="penpal",Y=e=>typeof e=="object"&&e!==null,ie=e=>typeof e=="function",ae=e=>Y(e)&&e.namespace==="penpal",U=e=>e.type==="SYN",K=e=>e.type==="ACK1",k=e=>e.type==="ACK2",ce=e=>e.type==="CALL",de=e=>e.type==="REPLY",le=e=>e.type==="DESTROY",ue=(e,t)=>{let r=e.reduce((s,o)=>{if(!(!Y(s)||!Object.hasOwn(s,o)))return s[o]},t);return ie(r)?r:void 0},w=e=>e.join("."),F=(e,t,r)=>({namespace:A,channel:e,type:"REPLY",callId:t,isError:!0,...r instanceof Error?{value:re(r),isSerializedErrorInstance:!0}:{value:r}}),he=(e,t,r,s)=>{let o=!1,d=async h=>{if(o||!ce(h))return;s?.(`Received ${w(h.methodPath)}() call`,h);let{methodPath:c,args:l,id:f}=h,a,n;try{let i=ue(c,t);if(!i)throw new g("METHOD_NOT_FOUND",`Method \`${w(c)}\` is not found.`);let m=await i(...l);m instanceof ne&&(n=m.transferables,m=await m.value),a={namespace:A,channel:r,type:"REPLY",callId:f,value:m}}catch(i){a=F(r,f,i)}if(!o)try{s?.(`Sending ${w(c)}() reply`,a),e.sendMessage(a,n)}catch(i){throw i.name==="DataCloneError"&&(a=F(r,f,i),s?.(`Sending ${w(c)}() reply`,a),e.sendMessage(a)),i}},p=h=>{d(h).catch(c=>{console.error(c)})};return e.addMessageHandler(p),()=>{o=!0,e.removeMessageHandler(p)}},V=crypto.randomUUID?.bind(crypto)??(()=>new Array(4).fill(0).map(()=>Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16)).join("-")),fe=Symbol("CallOptions"),H=class{transferables;timeout;#t=fe;constructor(e){this.transferables=e?.transferables,this.timeout=e?.timeout}},pe=new Set(["apply","call","bind"]),J=(e,t,r=[])=>new Proxy(r.length?()=>{}:Object.create(null),{get(s,o){if(o!=="then")return r.length&&pe.has(o)?Reflect.get(s,o):J(e,t,[...r,o])},apply(s,o,d){return e(r,d)}}),z=e=>new g("CONNECTION_DESTROYED",`Method call ${w(e)}() failed due to destroyed connection`),ge=(e,t,r)=>{let s=!1,o=new Map,d=c=>{if(!de(c))return;let{callId:l,value:f,isError:a,isSerializedErrorInstance:n}=c,i=o.get(l);i&&(o.delete(l),clearTimeout(i.timeoutId),r?.(`Received ${w(i.methodPath)}() reply`,c),a?i.reject(n?se(f):f):i.resolve(f))};return e.addMessageHandler(d),{remoteProxy:J((c,l)=>{if(s)throw z(c);let f=V(),a=l[l.length-1],n=a instanceof H,{timeout:i,transferables:m}=n?a:{},y=n?l.slice(0,-1):l;return new Promise((x,E)=>{let v=i!==void 0?globalThis.setTimeout(()=>{o.delete(f),E(new g("METHOD_CALL_TIMEOUT",`Method call ${w(c)}() timed out after ${i}ms`))},i):void 0;o.set(f,{methodPath:c,resolve:x,reject:E,timeoutId:v});try{let M={namespace:A,channel:t,type:"CALL",id:f,methodPath:c,args:y};r?.(`Sending ${w(c)}() call`,M),e.sendMessage(M,m)}catch(M){o.delete(f),clearTimeout(v),E(new g("TRANSMISSION_FAILED",M.message))}})},r),destroy:()=>{s=!0,e.removeMessageHandler(d);for(let{methodPath:c,reject:l,timeoutId:f}of o.values())clearTimeout(f),l(z(c));o.clear()}}},me=({messenger:e,methods:t,timeout:r,channel:s,log:o})=>{let d=V(),p,h=[],c=!1,l=!1,{promise:f,resolve:a,reject:n}=Promise.withResolvers(),i=r!==void 0?setTimeout(()=>{y(new g("CONNECTION_TIMEOUT",`Connection timed out after ${r}ms`))},r):void 0,m=()=>{clearTimeout(i);for(let u of h.splice(0))u()},y=u=>{l||(l=!0,m(),n(u))},x=()=>{y(new g("CONNECTION_DESTROYED","Connection destroyed"))},E=()=>{if(l||c)return;h.push(he(e,t,s,o));let{remoteProxy:u,destroy:_}=ge(e,s,o);h.push(_),clearTimeout(i),c=!0,a(u)},v=u=>{if(l)return!1;o?.(`Sending handshake ${u.type}`,u);try{return e.sendMessage(u),!0}catch(_){return y(new g("TRANSMISSION_FAILED",_.message)),!1}},M=()=>v({namespace:A,type:"SYN",channel:s,participantId:d}),Q=u=>{o?.("Received handshake SYN",u),u.participantId!==p&&(p=u.participantId,M()&&d>p&&v({namespace:"penpal",channel:s,type:"ACK1"}))},Z=u=>{o?.("Received handshake ACK1",u),v({namespace:"penpal",channel:s,type:"ACK2"})&&E()},ee=u=>{o?.("Received handshake ACK2",u),E()},j=u=>{l||(U(u)?Q(u):K(u)?Z(u):k(u)&&ee(u))};return e.addMessageHandler(j),h.push(()=>e.removeMessageHandler(j)),M(),{promise:f,destroy:x}},ye=e=>{let t=!1,r;return(...s)=>(t||(t=!0,r=e(...s)),r)},$=new WeakSet,W=()=>new g("CONNECTION_DESTROYED","Connection destroyed"),q=({messenger:e,methods:t={},timeout:r,channel:s,log:o})=>{if(!e)throw new g("INVALID_ARGUMENT","messenger must be defined");if($.has(e))throw new g("INVALID_ARGUMENT","A messenger can only be used for a single connection");$.add(e);let d=Promise.withResolvers(),p=!1,h,c=()=>{let a={namespace:A,channel:s,type:"DESTROY"};try{e.sendMessage(a)}catch{}},l=ye((a,n)=>{p=!0,n&&c(),e.destroy(),h?.(),d.reject(a),o?.("Connection destroyed")}),f=a=>ae(a)&&a.channel===s;return(async()=>{try{e.initialize({log:o,validateReceivedMessage:f}),e.addMessageHandler(i=>{le(i)&&l(W(),!1)});let a=me({messenger:e,methods:t,timeout:r,channel:s,log:o});h=a.destroy;let n=await a.promise;if(p){a.destroy();return}d.resolve(n)}catch(a){l(a,!0)}})(),{promise:d.promise,destroy:()=>{l(W(),!0)}}},B=class{#t;#s;#i;#a;#o;#r=new Set;#e;constructor({remoteWindow:e,allowedOrigins:t}){if(!e)throw new g("INVALID_ARGUMENT","remoteWindow must be defined");this.#t=e,this.#s=t?.length?t:[window.origin]}initialize=({log:e,validateReceivedMessage:t})=>{this.#i=e,this.#a=t,window.addEventListener("message",this.#l)};sendMessage=(e,t)=>{if(U(e)){let r=this.#c(e);this.#t.postMessage(e,{targetOrigin:r,...t===void 0?{}:{transfer:t}});return}if(K(e)){let r=this.#c(e);this.#t.postMessage(e,{targetOrigin:r,...t===void 0?{}:{transfer:t}});return}if(k(e)){let{port1:r,port2:s}=new MessageChannel;this.#d(r);let o=[s,...t||[]],d=this.#c(e);try{this.#t.postMessage(e,{targetOrigin:d,transfer:o})}catch(p){throw this.#n(),s.close(),p}return}if(this.#e){this.#e.postMessage(e,{...t===void 0?{}:{transfer:t}});return}throw new g("TRANSMISSION_FAILED","Cannot send message because the MessagePort is not connected")};addMessageHandler=e=>{this.#r.add(e)};removeMessageHandler=e=>{this.#r.delete(e)};destroy=()=>{window.removeEventListener("message",this.#l),this.#n(),this.#r.clear()};#h=e=>this.#s.some(t=>t instanceof RegExp?new RegExp(t).test(e):t===e||t==="*");#c=e=>{if(U(e))return"*";if(!this.#o)throw new g("TRANSMISSION_FAILED","Cannot send message because the remote origin is not established");return this.#o==="null"&&this.#s.includes("*")?"*":this.#o};#n=()=>{this.#e?.removeEventListener("message",this.#u),this.#e?.close(),this.#e=void 0};#d=e=>{this.#n(),this.#e=e,this.#e.addEventListener("message",this.#u),this.#e.start()};#l=({source:e,origin:t,ports:r,data:s})=>{if(e===this.#t&&this.#a?.(s)){if(!this.#h(t)){this.#i?.(`Received a message from origin \`${t}\` which did not match allowed origins \`[${this.#s.join(", ")}]\``);return}if(U(s)&&(this.#n(),this.#o=t),k(s)){let o=r[0];if(!o){this.#i?.("Ignoring ACK2 because it did not include a MessagePort");return}this.#d(o)}for(let o of this.#r)o(s)}};#u=({data:e})=>{if(this.#a?.(e))for(let t of this.#r)t(e)}};var we=["post","get","delete","deleteMedia","login","logout","actorToHandle","handleToActor"],Me=[D,I,T,P,R,N,L],C,G=class extends O{sessionEvents=new EventTarget;connection;remote_;remoteClient;sessions=new Map;initialized=!1;destroyed=!1;constructor(t){if(super(),C){let r=C;return window.setTimeout(()=>r.replayInitialization(),0),r}this.connection=q({messenger:new B({remoteWindow:t.remoteWindow,allowedOrigins:t.allowedOrigins??["*"]}),channel:t.channel,timeout:t.timeout,methods:{sessionEvent:(r,s)=>{if(r==="login"){let o=s;o.error||this.sessions.set(o.session.actor,o.session)}else if(r==="logout"){let o=s;o.error||this.sessions.delete(o.actor)}else r==="initialized"&&(this.initialized=!0,s=void 0);this.sessionEvents.dispatchEvent(new CustomEvent(r,{detail:s}))}}}),this.remote_=this.connection.promise.then(r=>{let s=Ee(r);return this.remoteClient=s,s}),window.setTimeout(()=>{this.remote_.then(r=>r.initialize()).catch(()=>{})},0);for(let r of we)this[r]=async(...s)=>{let d=(await this.remote())[r];return d(...S(s))};C=this}destroy(){this.destroyed||(this.destroyed=!0,C===this&&(C=void 0),this.remoteClient?.destroy().catch(()=>{}),this.connection.destroy())}replayInitialization(){if(!this.destroyed){for(let t of this.sessions.values())this.sessionEvents.dispatchEvent(new CustomEvent("login",{detail:{session:t}}));this.initialized&&this.sessionEvents.dispatchEvent(new CustomEvent("initialized",{detail:void 0}))}}getMedia=async(...t)=>{let r=await(await this.remote()).getMedia(...S(t));return{...r,data:new Blob([r.data.buffer],{type:r.data.type})}};postMedia=async(t,r)=>{let s=await t.data.arrayBuffer();return(await this.remote()).postMedia({...S(t),data:{buffer:s,type:t.data.type}},S(r),new H({transferables:[s]}))};remoteStream(t){let r=`graffiti-rpc-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`,s=this.remote_;return(async function*(){let o=await s;try{for(await t(o,r);;){let d=await o.streamNext(r);if(d.done)return d.value;yield d.value}}finally{await o.streamReturn(r)}})()}discover=(...t)=>this.remoteStream((r,s)=>r.discover(s,...S(t)));continueDiscover=(...t)=>this.remoteStream((r,s)=>r.continueDiscover(s,...S(t)));remote(){if(this.destroyed)throw new Error("Graffiti RPC client has been destroyed.");return this.remote_}};function S(e){return JSON.parse(JSON.stringify(e))}function Ee(e){return new Proxy(e,{get(t,r){let s=Reflect.get(t,r);return typeof s=="function"?(...o)=>s(...o).catch(ve):s}})}function ve(e){if(e instanceof Error){let t=Me.find(({name:r})=>r===e.name);t&&Object.setPrototypeOf(e,t.prototype)}throw e}var b,Oe=1e4,X=class extends O{sessionEvents;hostUrl;iframe;rpc;onMessage;connectionTimeout;destroyed=!1;constructor(t={}){if(super(),b)return b;let r=new URL(t.hostUrl?.toString()??"https://guard.graffiti.garden/",document.baseURI),s=document.createElement("iframe");s.title="Graffiti Guard",s.allow="storage-access",s.sandbox.add("allow-scripts","allow-same-origin","allow-storage-access-by-user-activation","allow-downloads","allow-forms","allow-popups","allow-top-navigation-by-user-activation"),s.src=r.href,s.setAttribute("aria-hidden","true"),Object.assign(s.style,{background:"transparent",border:"0",display:"none",height:"100vh",inset:"0",position:"fixed",width:"100vw",zIndex:"2147483647"});let o=!1,d=!1;s.addEventListener("load",()=>{s.contentWindow?.postMessage({type:"graffiti-guard:connect",pageUrl:window.location.href},r.origin)});let p=()=>{o||d||(d=!0,window.alert(`This app could not connect to its data service at ${r.origin}. The service may be offline, or your browser may be blocking its security certificate. Open that address directly, then reload this page.`))};s.addEventListener("error",p),document.body.append(s);let h=s.contentWindow;if(!h)throw s.remove(),new Error("Graffiti Guard iframe did not create a content window.");let c=new G({remoteWindow:h,allowedOrigins:[r.origin],channel:t.channel,timeout:t.timeout}),l=window.setTimeout(p,t.timeout??Oe),f=()=>{o=!0,window.clearTimeout(l)},a=n=>{if(!(n.source!==h||n.origin!==r.origin||!n.data||typeof n.data!="object")&&(f(),n.data.type!=="graffiti-guard:connected"))if(n.data.type==="graffiti-guard:set-visible"&&typeof n.data.visible=="boolean")s.style.display=n.data.visible?"block":"none",s.setAttribute("aria-hidden",String(!n.data.visible)),n.data.visible&&(s.focus(),h.postMessage({type:"graffiti-guard:shown"},r.origin));else if(n.data.type==="graffiti-guard:open-storage-setup"&&typeof n.data.url=="string")try{let i=new URL(n.data.url);if(i.origin!==r.origin||i.searchParams.get("guardStorageSetup")!=="1")return;window.location.assign(i)}catch{}else n.data.type==="graffiti-guard:open-audit"&&typeof n.data.actor=="string"&&Array.isArray(n.data.source)&&window.location.assign(this.auditUrl(n.data))};return window.addEventListener("message",a),this.hostUrl=r,this.iframe=s,this.rpc=c,this.sessionEvents=c.sessionEvents,this.onMessage=a,this.connectionTimeout=l,b=new Proxy(this,{get(n,i,m){if(Reflect.has(n,i))return Reflect.get(n,i,m);let y=Reflect.get(c,i,c);return typeof y=="function"?y.bind(c):y}}),b}login=t=>{let r=new URL(this.hostUrl);return r.searchParams.set("guardLogin","1"),r.searchParams.set("redirectUrl",window.location.href),t&&r.searchParams.set("suggestedActor",t),window.location.assign(r.href),Promise.resolve()};auditUrl(t){let r=new URL(this.hostUrl);return r.searchParams.set("redirectUrl",window.location.href),r.searchParams.set("source",JSON.stringify(t?.source??[])),t?.actor&&r.searchParams.set("actor",t.actor),r.href}destroy(){this.destroyed||(this.destroyed=!0,window.clearTimeout(this.connectionTimeout),window.removeEventListener("message",this.onMessage),this.rpc.destroy(),this.iframe.remove(),b===this&&(b=void 0))}};export{X as GraffitiGuarded};
2
2
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../api/src/1-api.ts", "../../../../../api/src/2-types.ts", "../../../../../api/src/3-errors.ts", "../../../../../node_modules/penpal/src/PenpalError.ts", "../../../../../node_modules/penpal/src/errorSerialization.ts", "../../../../../node_modules/penpal/src/Reply.ts", "../../../../../node_modules/penpal/src/namespace.ts", "../../../../../node_modules/penpal/src/guards.ts", "../../../../../node_modules/penpal/src/methodPath.ts", "../../../../../node_modules/penpal/src/connectCallHandler.ts", "../../../../../node_modules/penpal/src/generateId.ts", "../../../../../node_modules/penpal/src/CallOptions.ts", "../../../../../node_modules/penpal/src/connectRemoteProxy.ts", "../../../../../node_modules/penpal/src/shakeHands.ts", "../../../../../node_modules/penpal/src/once.ts", "../../../../../node_modules/penpal/src/connect.ts", "../../../../../node_modules/penpal/src/messengers/WindowMessenger.ts", "../../../../../node_modules/penpal/src/messengers/WorkerMessenger.ts", "../../../../../node_modules/penpal/src/messengers/PortMessenger.ts", "../../../../../node_modules/penpal/src/ErrorCodeObj.ts", "../../../../../node_modules/penpal/src/debug.ts", "../../../../iframe-rpc/client/index.ts", "../../src/index.ts"],
4
- "sourcesContent": ["import type {\n GraffitiObjectUrl,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiSession,\n GraffitiPostObject,\n GraffitiObjectStream,\n GraffitiMedia,\n GraffitiPostMedia,\n GraffitiMediaAccept,\n} from \"./2-types.js\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\n\n/**\n * This API describes a small but powerful set of methods that\n * can be used to create many different kinds of social applications,\n * from applications like Twitter, to Messenger, to Wikipedia, to many more new designs.\n * See the [Graffiti project website](https://graffiti.garden)\n * for links to example applications. Additionally, apps built on top\n * of the API interoperate with each other so you can seamlessly switch\n * between apps without losing your friends or data.\n *\n * These API methods should satisfy all of an application's needs for\n * the communication, storage, and access management of social data.\n * The rest of the application can be built with standard client-side\n * user interface tools to present and interact with that data\u2014no server code necessary!\n *\n * The TypeScript code for this API is [open source on GitHub](https://github.com/graffiti-garden/graffiti/tree/main/api).\n *\n * There are several different implementations of this Graffiti API available,\n * including a [decentralized implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/decentralized),\n * that lets people choose where their data is stored (you do not need to host your own server)\n * and a [local implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/local)\n * that can be used for testing and development. Different implementations can\n * be swapped-in in the future without changing the API or any of the apps built on\n * top of it. In fact, we're working on an end-to-end encrypted version now!\n * [Follow Theia on BlueSky for updates](https://bsky.app/profile/theias.place).\n *\n * On the other side of the stack, there is [Vue plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * that wraps around this API to provide reactivity. Other plugin frameworks\n * and high-level libraries will be available in the future.\n *\n * ## API Overview\n *\n * The Graffiti API provides applications with methods for {@link login} and {@link logout},\n * methods to interact with data objects using standard database operations ({@link post}, {@link get}, and {@link delete}),\n * and a method to {@link discover} data objects created by others.\n * These data objects have a couple structured properties:\n * - {@link GraffitiObjectBase.url | `url`} (string): A globally unique identifier and locator for the object.\n * - {@link GraffitiObjectBase.actor | `actor`} (string): An unforgeable identifier for the creator of the object.\n * - {@link GraffitiObjectBase.allowed | `allowed`} (string[] | undefined): An array of the actors who are allowed to access the object (undefined for public objects).\n * - {@link GraffitiObjectBase.channels | `channels`} (string[]): An array of the *contexts* in which the object should appear.\n *\n * All other data is stored in the object's unstructured {@link GraffitiObjectBase.value | `value`} property.\n * This data can be used to represent social artifacts (e.g. posts, profiles) and activities (e.g. likes, follows).\n * For example, a post might have the value:\n\n * ```js\n * {\n * title: \"My First Post\",\n * content: \"Hello, world!\",\n * published: 1630483200000\n * }\n * ```\n *\n * a profile might have the value:\n *\n * ```js\n * {\n * name: \"Theia Henderson\",\n * pronouns: \"she/her\",\n * describes: \"did:web:theias.place\" // Theia's actor ID\n * }\n * ```\n *\n * and a \"Like\" might have the value:\n *\n * ```js\n * {\n * activity: \"Like\",\n * target: \"graffiti:remote:pod.graffiti.garden/12345\" // The URL of the graffiti object being liked\n * }\n * ```\n *\n * New social artifacts and activities can be easily created, simply\n * by creating new objects with appropriate properties. Despite the lack of\n * structure, we expect Graffiti object properties to adhere to a \"[folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\",\n * similar to hashtags. Any string can be used as a hashtag on Twitter,\n * but there is social value in using the same hashtags at other people and\n * so a structure naturally emerges. Similarly, Graffiti objects\n * can have arbitrary properties but if people use the same properties as each other,\n * their apps will interoperate, which has social value.\n *\n * For a more complete and detailed overview of Graffiti's design, please\n * refer to [this section of the Graffiti paper](https://dl.acm.org/doi/10.1145/3746059.3747627#sec-3),\n * published in ACM UIST 2025. The paper also overviews {@link GraffitiObjectBase.channels | `channels`},\n * which are Graffiti's means of organizing data contextually, and a concept called \"total reification\",\n * which handles explains how moderation, collaboration, and other interactions are managed.\n *\n * @groupDescription 1 - Single-Object Methods\n * Methods for {@link post | creating}, {@link get | reading},\n * and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.\n * @groupDescription 2 - Multi-Object Methods\n * Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.\n * @groupDescription 3 - Media Methods\n * Methods for {@link postMedia | creating}, {@link getMedia | reading},\n * and {@link deleteMedia | deleting} media data.\n * @groupDescription 4 - Identity Methods\n * Methods and properties for logging in and out.\n */\nexport abstract class Graffiti {\n /**\n * Creates a new {@link GraffitiObjectBase | object}.\n *\n * @returns Returns the object that has been posted, complete with its\n * assigned {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}.\n *\n * @group 1 - Single-Object Methods\n */\n abstract post<Schema extends JSONSchema>(\n /**\n * An object to post, minus its {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}, which will be assigned once posted.\n * This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided\n * as the generic type parameter. It is recommended to use a schema to\n * ensure that the posted object matches subsequent {@link get} or {@link discover}\n * methods.\n */\n partialObject: GraffitiPostObject<Schema>,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Retrieves an object from a given {@link GraffitiObjectBase.url | `url`} matching\n * the provided `schema`.\n *\n * If the retreiving {@link GraffitiObjectBase.actor | `actor`} is not\n * the object's `actor`,\n * the object's {@link GraffitiObjectBase.allowed | `allowed`} and\n * {@link GraffitiObjectBase.channels | `channels`} properties are\n * not revealed, similar to a BCC email.\n *\n * @returns Returns the retrieved object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has been deleted, or the actor is not\n * {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorSchemaMismatch} if the retrieved object does not match the provided schema.\n *\n * @throws {@link GraffitiErrorInvalidSchema} If an invalid schema is provided.\n *\n * @group 1 - Single-Object Methods\n */\n abstract get<Schema extends JSONSchema>(\n /**\n * The location of the object to get.\n */\n url: string | GraffitiObjectUrl,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Deletes an object from a given {@link GraffitiObjectBase.url | `url`}\n * that had previously been {@link post | `post`ed}.\n * The deleting {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has already been deleted,\n * or the actor is not {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * is not the same `actor` as the one who created the object.\n *\n * @group 1 - Single-Object Methods\n */\n abstract delete(\n /**\n * The location of the object to delete.\n */\n url: string | GraffitiObjectUrl,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Discovers objects created by any actor that are contained\n * in at least one of the given {@link GraffitiObjectBase.channels | `channels`}\n * and match the given [JSON Schema](https://json-schema.org).\n *\n * Objects are returned asynchronously as they are discovered but the stream\n * will end once all leads have been exhausted.\n * The {@link GraffitiObjectStream} ends by returning a\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string,\n * that can be passed to {@link continueDiscover | `continueDiscover`} to\n * poll for new objects.\n *\n * `discover` will not return objects that the querying {@link GraffitiObjectBase.actor | `actor`}\n * is not {@link GraffitiObjectBase.allowed | `allowed`} to access.\n * If the `actor` is not the creator of a discovered object,\n * the allowed list will be masked to only contain the querying actor if the\n * allowed list is not `undefined` (public). Additionally, if the actor is not the\n * creator of a discovered object, any {@link GraffitiObjectBase.channels | `channels`}\n * not specified by the `discover` method will not be revealed. This masking happens\n * before the object is validated against the supplied `schema`.\n *\n * Since different implementations may fetch data from multiple sources there is\n * no guarentee on the order that objects are returned in.\n *\n * @throws {@link GraffitiErrorInvalidSchema} if an invalid schema is provided.\n * Discovery is lazy and will not throw until the iterator is consumed.\n *\n * @returns Returns a stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}\n * and [JSON Schema](https://json-schema.org).\n *\n * @group 2 - Multi-Object Methods\n */\n abstract discover<Schema extends JSONSchema>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema>;\n\n /**\n * Continues a {@link GraffitiObjectStream} from a given\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string.\n * The continuation will return new objects that have been {@link post | `post`ed}\n * that match the original stream, and also returns the\n * {@link GraffitiObjectBase.url | `url`}s of objects that\n * have been {@link delete | `delete`d}, as marked by a `tombstone`.\n *\n * @throws {@link GraffitiErrorCursorExpired} upon iteration\n * if the cursor is invalid or expired.\n *\n * @throws {@link GraffitiErrorForbidden} upon iteration\n * if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor`\n * that initiated the original stream.\n *\n * @group 2 - Multi-Object Methods\n */\n abstract continueDiscover<Schema extends JSONSchema>(\n cursor: string,\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema>;\n\n /**\n * Uploads media data, such as an image or video.\n *\n * Unlike structured {@link GraffitiObjectBase | objects},\n * media is not indexed for {@link discover | `discover`y} and\n * must be retrieved by its exact URL using {@link getMedia}\n *\n * @returns The URL that the media was posted to.\n *\n * @group 3 - Media Methods\n */\n abstract postMedia(\n /**\n * The media data to upload, and optionally\n * an {@link GraffitiObjectBase.allowed | `allowed`}\n * list of actors that can view it.\n */\n media: GraffitiPostMedia,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<string>;\n\n /**\n * Deletes media previously {@link postMedia | `post`ed} to a given URL.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor` that {@link postMedia | `post`ed}\n * the media.\n *\n * @group 3 - Media Methods\n */\n abstract deleteMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n url: string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * Retrieves media from the given media URL, adhering to the given requirements.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorTooLarge} if the media exceeds the given `maxBytes`.\n *\n * @throws {@link GraffitiErrorNotAcceptable} if the media does not match the given\n * `accept` specification.\n *\n * @returns The URL of the retrieved media, as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)\n * and the {@link GraffitiObjectBase.actor | `actor`} that posted it.\n *\n * @group 3 - Media Methods\n */\n abstract getMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n mediaUrl: string,\n /**\n * A specification for what types and sizes of media are acceptable.\n */\n accept: GraffitiMediaAccept,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiMedia>;\n\n /**\n * Begins the login process. Depending on the implementation, this may\n * involve redirecting to a login page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * The {@link GraffitiSession | session} object is returned\n * asynchronously via {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLoginEvent} with event type `login`.\n *\n * @group 4 - Identity Methods\n */\n abstract login(\n /**\n * A suggested actor to login as. For example, if a user tries to\n * edit a post but are not logged in, the interface can infer that\n * they might want to log in as the actor who created the post\n * they are attempting to edit.\n *\n * Even if provided, the implementation should allow the user\n * to log in as a different actor if they choose.\n */\n actor?: string | null,\n ): Promise<void>;\n\n /**\n * Begins the logout process for a particular {@link GraffitiSession | session}. Depending on the implementation, this may\n * involve redirecting the user to a logout page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * A confirmation will be returned asynchronously via\n * {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLogoutEvent} as event type `logout`.\n *\n * @group 4 - Identity Methods\n */\n abstract logout(\n /**\n * The {@link GraffitiSession | session} object to logout.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * An event target that can be used to listen for the following\n * events and their corresponding event types:\n * - `login` - {@link GraffitiLoginEvent}\n * - `logout` - {@link GraffitiLogoutEvent}\n * - `initialized` - {@link GraffitiSessionInitializedEvent}\n *\n * @group 4 - Identity Methods\n */\n abstract readonly sessionEvents: EventTarget;\n\n /**\n * Retrieves the human-readable handle associated\n * with the given actor. The handle may change over time\n * and so it should be used for display purposes only.\n *\n * The inverse of {@link handleToActor}.\n *\n * @throws {@link GraffitiErrorNotFound} if a handle cannot be\n * found for the given actor.\n *\n * @returns A human-readable handle for the given actor.\n *\n * @group 4 - Identity Methods\n */\n abstract actorToHandle(actor: string): Promise<string>;\n\n /**\n * Retrieves the actor ID associated with the given handle.\n *\n * The inverse of {@link actorToHandle}.\n *\n * @throws {@link GraffitiErrorNotFound} if there is no actor\n * with the given handle.\n *\n * @returns The actor ID for the given handle.\n *\n * @group 4 - Identity Methods\n */\n abstract handleToActor(handle: string): Promise<string>;\n}\n", "import type { JSONSchema, FromSchema } from \"json-schema-to-ts\";\n\n/**\n * Objects are the atomic unit in Graffiti that can represent both data (*e.g.* a social media post or profile)\n * and activities (*e.g.* a like or follow).\n *\n * Each object embeds the {@link actor | `actor`} that created it.\n * Object content and metadata are static but an object may be deleted by\n * its creating {@link actor | `actor`}.\n *\n * An object's content is stored in its {@link value | `value`} property, which can be any JSON\n * object. However, it is recommended to use properties from the\n * [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/)\n * or properties that emerge in the Graffiti [folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\n * to promote interoperability.\n *\n * Each object is globally addressable via its {@link url | `url`}.\n *\n * An object's {@link channels | `channels`} and {@link allowed | `allowed`} properties\n * are set by an objects creator to shape the visibility of and access to their object.\n */\nexport interface GraffitiObjectBase {\n /**\n * The object's content as freeform JSON. We recommend using properties from the\n * [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/)\n * or properties that emerge in the Graffiti [folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\n * to promote interoperability.\n */\n value: {};\n\n /**\n * An array of URIs the creator associates with the object. Objects can only be found by querying\n * one of the object's channels using the\n * {@link Graffiti.discover} method. This allows creators to express the intended audience of their object\n * which helps to prevent [context collapse](https://en.wikipedia.org/wiki/Context_collapse) even\n * in the highly interoperable ecosystem that Graffiti envisions. For example, channel URIs may be:\n * - A actor's own {@link actor | `actor`} URI. Posting an object to this channel is a way to broadcast\n * the object to the actor's followers, like posting a tweet.\n * - The URL of a Graffiti post. Posting an object to this channel is a way to broadcast to anyone viewing\n * the post, like commenting on a tweet.\n * - A URI representing a topic. Posting an object to this channel is a way to broadcast to anyone interested\n * in that topic, like posting in a subreddit.\n */\n channels: string[];\n\n /**\n * An optional array of {@link actor | `actor`} URIs that the creator allows to access the object.\n * If no `allowed` array is provided, the object can be accessed by anyone (so long as they\n * also know the right {@link channels | `channel` } to look in). An object can always be accessed by its creator, even if\n * the `allowed` array is empty.\n *\n * The `allowed` array is not revealed to actors other than the creator, like\n * a BCC email. An actor may choose to add a `to` property to the object's {@link value | `value`} to indicate\n * other recipients, however this is not enforced by Graffiti and may not accurately reflect the actual `allowed` array.\n *\n * `allowed` can be combined with {@link channels | `channels`}. For example, to send someone a direct message\n * the sender should post their object to the channel of the recipient's {@link actor | `actor`} URI to notify them of the message and also add\n * the recipient's {@link actor | `actor`} URI to the `allowed` array to prevent others from seeing the message.\n */\n allowed?: string[] | null;\n\n /**\n * The URI of the `actor` that {@link Graffiti.post | created } the object.\n * This `actor` has the unique permission to\n * {@link Graffiti.delete | delete} the object.\n *\n * We borrow the term actor from the ActivityPub because\n * [like in ActivityPub](https://www.w3.org/TR/activitypub/#h-note-0)\n * there is not necessarily a one-to-one mapping between actors and people/users.\n * Multiple people can share the same actor or one person can have multiple actors.\n * Actors can also be bots.\n *\n * In Graffiti, actors are always globally unique URIs which\n * allows them to also function as {@link channels | `channels`}.\n */\n actor: string;\n\n /**\n * A globally unique identifier and locator for the object. It can be used to point to\n * an object or to retrieve the object directly with {@link Graffiti.get}.\n *\n * An object's URL is generated when the object is first created and\n * should include sufficient randomness to prevent collisions\n * and guessing. The URL starts with a \"scheme,\" just like web URLs start with `http` or `https`, to indicate\n * to indicate the particular Graffiti implementation. This allows for applications\n * to pull from multiple coexisting Graffiti implementations without collision.\n * Existing schemes include `graffiti:local:` for objects stored locally\n * (see the [local implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/local))\n * and implementation-specific Graffiti URLs for objects stored on web services\n * (see the [decentralized implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/decentralized)).\n * Options available in the future might include `graffiti:solid:` for objects stored on Solid servers\n * or `graffiti:p2p:` for objects stored on a peer-to-peer network.\n */\n url: string;\n}\n\n/**\n * This type constrains the {@link GraffitiObjectBase} type to adhere to a\n * particular [JSON schema](https://json-schema.org/).\n * This allows for static type-checking of an object's {@link GraffitiObjectBase.value | `value`}\n * which is otherwise a freeform JSON object.\n *\n * Schema-aware objects are returned by {@link Graffiti.get} and {@link Graffiti.discover}.\n */\nexport type GraffitiObject<Schema extends JSONSchema> = GraffitiObjectBase &\n FromSchema<Schema & typeof GraffitiPostObjectJSONSchema>;\n\n/**\n * A JSON Schema equivalent to the {@link GraffitiObjectBase} type.\n * Needed internally for type inference of JSON Schemas, but can\n * be used by implementations to validate objects.\n */\nexport const GraffitiObjectJSONSchema = {\n type: \"object\",\n properties: {\n value: { type: \"object\" },\n channels: { type: \"array\", items: { type: \"string\" } },\n allowed: { type: \"array\", items: { type: \"string\" }, nullable: true },\n url: { type: \"string\" },\n actor: { type: \"string\" },\n },\n additionalProperties: false,\n required: [\"value\", \"channels\", \"actor\", \"url\"],\n} as const satisfies JSONSchema;\n\n/**\n * This is an object containing only the {@link GraffitiObjectBase.url | `url`}\n * property of a {@link GraffitiObjectBase | GraffitiObject}.\n * It is used as a utility type so that applications can call\n * {@link Graffiti.delete} directly on an object\n * rather than on `object.url`.\n */\nexport type GraffitiObjectUrl = Pick<GraffitiObjectBase, \"url\">;\n\n/**\n * This object is a subset of {@link GraffitiObjectBase} that must be constructed locally before calling {@link Graffiti.post}.\n * This local copy ignores system-generated properties\n * ({@link GraffitiObjectBase.url | `url`} and {@link GraffitiObjectBase.actor | `actor`}),\n * and may be statically typed with\n * a [JSON schema](https://json-schema.org/) to prevent the accidental creation of erroneous objects.\n *\n * This local object must have a {@link GraffitiObjectBase.value | `value`} and {@link GraffitiObjectBase.channels | `channels`}\n * and may optionally have an {@link GraffitiObjectBase.allowed | `allowed`} property.\n */\nexport type GraffitiPostObject<Schema extends JSONSchema> = Pick<\n GraffitiObjectBase,\n \"value\" | \"channels\" | \"allowed\"\n> &\n FromSchema<Schema & typeof GraffitiPostObjectJSONSchema>;\n\n/**\n * A JSON Schema equivalent to the {@link GraffitiPostObject} type.\n * Needed internally for type inference of JSON Schemas, but can\n * be used by implementations to validate objects.\n */\nexport const GraffitiPostObjectJSONSchema = {\n ...GraffitiObjectJSONSchema,\n required: [\"value\", \"channels\"],\n} as const satisfies JSONSchema;\n\n/**\n * This object contains information that the underlying implementation can\n * use to authenticate a particular {@link GraffitiObjectBase.actor | `actor`}.\n * This object is required of all {@link Graffiti} methods\n * that modify objects or media and is optional for methods that read objects.\n *\n * At a minimum the `session` object must contain the\n * {@link GraffitiSession.actor | `actor`} URI to authenticate with.\n * However it is likely that the `session` object must contain other\n * implementation-specific properties.\n * For example, a Solid implementation might include a\n * [`fetch`](https://docs.inrupt.com/developer-tools/api/javascript/solid-client-authn-browser/functions.html#fetch)\n * function. A distributed implementation may include\n * a cryptographic signature.\n *\n * As to why the `session` object is passed as an argument to every method\n * rather than being an internal property of the {@link Graffiti} instance,\n * this is primarily for type-checking to catch bugs related to login state.\n * Graffiti applications can expose some functionality to people who are not logged in\n * with {@link Graffiti.get} and {@link Graffiti.discover} but without type-checking\n * the `session` it can be easy to forget to hide buttons that trigger\n * other methods that require login.\n *\n * Passing the `session` object per-method also allows for multiple sessions\n * to be used within the same application, like an Email client fetching from\n * multiple accounts.\n */\nexport interface GraffitiSession {\n /**\n * The {@link GraffitiObjectBase.actor | `actor`} to authenticate with.\n */\n actor: string;\n}\n\n/**\n * A stream of data that returned by {@link Graffiti.discover | `discover`}\n * and {@link Graffiti.continueDiscover | `continueDiscover`}.\n *\n * Non-fatal errors are returned within the stream rather than as\n * exceptions that would halt the entire stream. This is because\n * some implementations may pull data from multiple sources\n * including some that may be unreliable. Such errors can generally\n * be ignored, but should be printed for debugging.\n * See {@link GraffitiObjectStreamError}.\n *\n * The stream is an [`AsyncGenerator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)\n * that can be iterated over using `for await` loops or calling `next` on the generator.\n * The stream can be terminated by breaking out of a loop calling `return` on the generator.\n *\n * The stream ends by returning a\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string,\n * that can be passed to {@link Graffiti.continueDiscover | `continueDiscover`} to\n * resume the stream from where it left off.\n */\nexport type GraffitiObjectStream<Schema extends JSONSchema> = AsyncGenerator<\n | GraffitiObjectStreamError\n | GraffitiObjectStreamEntry<Schema>\n | GraffitiObjectStreamTombstone,\n GraffitiObjectStreamReturn\n>;\n\n/**\n * An error that can occur in a {@link GraffitiObjectStream}.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamError {\n /**\n * The error that occurred while streaming data.\n */\n error: Error;\n /**\n * The origin that the error occurred. It will include\n * the scheme of the Graffiti implementation used and other\n * implementation-specific information like a hostname.\n */\n origin: string;\n}\n\n/**\n * A successful result from a {@link GraffitiObjectStream}\n * that includes an object.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamEntry<Schema extends JSONSchema> {\n /**\n * Empty property for compatibility with {@link GraffitiObjectStreamError}\n */\n error?: undefined;\n /**\n * Empty property for compatibility with {@link GraffitiObjectStreamTombstone}\n */\n tombstone?: undefined;\n /**\n * The object returned by the stream.\n */\n object: GraffitiObject<Schema>;\n}\n\n/**\n * A result from a {@link GraffitiObjectStream} that indicated\n * an object has been deleted since the original stream was run.\n * Only sparse metadata about the deleted object is returned to respect\n * the deleting actor's privacy.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamTombstone {\n /**\n * Empty property for compatibility with {@link GraffitiObjectStreamError}\n */\n error?: undefined;\n /**\n * Use this property to differentiate a tombstone from a\n * {@link GraffitiObjectStreamEntry}.\n */\n tombstone: true;\n /**\n * Sparse metadata about the deleted object. The full object is not returned\n * to respect an actor's privacy.\n */\n object: {\n /**\n * The {@link GraffitiObjectBase.url | `url`} of the deleted object.\n */\n url: string;\n };\n}\n\n/**\n * The final return value of a {@link GraffitiObjectStream}.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamReturn {\n /**\n * A string that can be passed to the {@link Graffiti.continueDiscover | `continueDiscover`}\n * to resume the stream. The cursor can be serialized and used between\n * instantiations of {@link Graffiti}\n */\n cursor: string;\n}\n\n/**\n * The event type produced in {@link Graffiti.sessionEvents}\n * when a actor logs in manually from {@link Graffiti.login}\n * or when their session is restored from a previous login.\n * The event name to listen for is `login`.\n */\nexport type GraffitiLoginEvent = CustomEvent<\n | {\n error: Error;\n session?: GraffitiSession;\n }\n | {\n error?: undefined;\n session: GraffitiSession;\n }\n>;\n\n/**\n * The event type produced in {@link Graffiti.sessionEvents}\n * when a actor logs out either manually with {@link Graffiti.logout}\n * or when their session times out or otherwise becomes invalid.\n * The event name to listen for is `logout`.\n */\nexport type GraffitiLogoutEvent = CustomEvent<\n | {\n error: Error;\n actor?: string;\n }\n | {\n error?: undefined;\n actor: string;\n }\n>;\n\n/**\n * The event type produced in {@link Graffiti.sessionEvents}\n * after an application has attempted to complete any login redirects\n * and restore any previously active sessions.\n * Successful session restores will be returned in prior to the\n * initialized event as their own {@link GraffitiLoginEvent} events.\n * The event name to listen for is `initialized`.\n */\nexport type GraffitiSessionInitializedEvent = CustomEvent<\n { error?: Error } | null | undefined\n>;\n\nexport type GraffitiMedia = {\n /**\n * The binary data of the media to be uploaded,\n * along with its [media type](https://www.iana.org/assignments/media-types/media-types.xhtml),\n * formatted as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob).\n */\n data: Blob;\n /**\n * The {@link GraffitiObjectBase.actor | `actor`} that\n * {@link Graffiti.postMedia | `post`ed} the media.\n */\n actor: string;\n /**\n * An optional list, identical in function to an object's\n * {@link GraffitiObjectBase.allowed | `allowed`} property,\n * that specifies the {@link GraffitiObjectBase.actor | `actor`}s\n * who are allowed to access the media. If the list is `undefined`\n * or `null`, anyone with the URL can access the media. If the list\n * is empty, only the {@link GraffitiObjectBase.actor | `actor`}\n * who {@link Graffiti.postMedia | `post`ed} the media can access it.\n */\n allowed?: string[] | null;\n};\n\nexport type GraffitiPostMedia = Pick<GraffitiMedia, \"data\" | \"allowed\">;\n\nexport type GraffitiMediaAccept = {\n /**\n * A list of acceptable media types for the retrieved media.\n * Each type in the list may be of the form `<type>/<subtype>`,\n * `<type>/*`, or `&#42;/*`, just as types are formatted in\n * an [HTTP Accept header](https://httpwg.org/specs/rfc9110.html#field.accept).\n */\n types?: string[];\n /**\n * The maximum acceptable size, in bytes, of the media.\n * Must be a nonnegative safe integer.\n */\n maxBytes?: number;\n};\n", "export class GraffitiErrorForbidden extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorForbidden\";\n Object.setPrototypeOf(this, GraffitiErrorForbidden.prototype);\n }\n}\n\nexport class GraffitiErrorNotFound extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorNotFound\";\n Object.setPrototypeOf(this, GraffitiErrorNotFound.prototype);\n }\n}\n\nexport class GraffitiErrorInvalidSchema extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorInvalidSchema\";\n Object.setPrototypeOf(this, GraffitiErrorInvalidSchema.prototype);\n }\n}\n\nexport class GraffitiErrorSchemaMismatch extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorSchemaMismatch\";\n Object.setPrototypeOf(this, GraffitiErrorSchemaMismatch.prototype);\n }\n}\n\nexport class GraffitiErrorTooLarge extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorTooLarge\";\n Object.setPrototypeOf(this, GraffitiErrorTooLarge.prototype);\n }\n}\n\nexport class GraffitiErrorNotAcceptable extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorNotAcceptable\";\n Object.setPrototypeOf(this, GraffitiErrorNotAcceptable.prototype);\n }\n}\n\nexport class GraffitiErrorCursorExpired extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorCursorExpired\";\n Object.setPrototypeOf(this, GraffitiErrorCursorExpired.prototype);\n }\n}\n", "import type { ErrorCode } from './types.js';\n\nclass PenpalError extends Error {\n public code: ErrorCode;\n\n constructor(code: ErrorCode, message: string) {\n super(message);\n this.name = 'PenpalError';\n this.code = code;\n }\n}\n\nexport default PenpalError;\n", "import type { SerializedError } from './types.js';\nimport PenpalError from './PenpalError.js';\n\n/**\n * Converts an error object into a plain object.\n */\nexport const serializeError = (error: Error): SerializedError => ({\n name: error.name,\n message: error.message,\n stack: error.stack,\n penpalCode: error instanceof PenpalError ? error.code : undefined,\n});\n\n/**\n * Converts a plain object into an error object.\n */\nexport const deserializeError = ({\n name,\n message,\n stack,\n penpalCode,\n}: SerializedError): Error => {\n const deserializedError = penpalCode\n ? new PenpalError(penpalCode, message)\n : new Error(message);\n\n deserializedError.name = name;\n // TypeScript declares Error.stack as `stack?: string`. With\n // exactOptionalPropertyTypes, that permits a missing property but rejects\n // assigning undefined. A serialized error can legitimately have no stack,\n // and Penpal must overwrite the locally generated stack with that undefined\n // value to preserve the remote error, so widen the property for this write.\n (deserializedError as { stack: string | undefined }).stack = stack;\n\n return deserializedError;\n};\n", "const brand: unique symbol = Symbol('Reply');\n\nclass Reply<T = unknown> {\n readonly value: T;\n readonly transferables: Transferable[] | undefined;\n\n // Allows TypeScript to distinguish between an actual instance of this\n // class versus an object that looks structurally similar.\n // eslint-disable-next-line no-unused-private-class-members\n #brand = brand;\n\n constructor(\n value: T,\n options?: {\n transferables?: Transferable[] | undefined;\n },\n ) {\n this.value = value;\n this.transferables = options?.transferables;\n }\n}\n\nexport default Reply;\n", "export default 'penpal' as const;\n", "import namespace from './namespace.js';\nimport type {\n Ack2Message,\n CallMessage,\n Message,\n ReplyMessage,\n Ack1Message,\n SynMessage,\n DestroyMessage,\n} from './types.js';\n\nexport const isObject = (\n value: unknown,\n): value is Record<string | number | symbol, unknown> => {\n return typeof value === 'object' && value !== null;\n};\n\nexport const isFunction = (value: unknown): value is Function => {\n return typeof value === 'function';\n};\n\nexport const isMessage = (data: unknown): data is Message => {\n return isObject(data) && data.namespace === namespace;\n};\n\nexport const isSynMessage = (message: Message): message is SynMessage => {\n return message.type === 'SYN';\n};\n\nexport const isAck1Message = (message: Message): message is Ack1Message => {\n return message.type === 'ACK1';\n};\n\nexport const isAck2Message = (message: Message): message is Ack2Message => {\n return message.type === 'ACK2';\n};\n\nexport const isCallMessage = (message: Message): message is CallMessage => {\n return message.type === 'CALL';\n};\n\nexport const isReplyMessage = (message: Message): message is ReplyMessage => {\n return message.type === 'REPLY';\n};\n\nexport const isDestroyMessage = (\n message: Message,\n): message is DestroyMessage => {\n return message.type === 'DESTROY';\n};\n", "import type { MethodPath, Methods } from './types.js';\nimport { isFunction, isObject } from './guards.js';\n\nexport const getMethodAtMethodPath = (\n methodPath: MethodPath,\n methods: Methods,\n): Function | undefined => {\n const result = methodPath.reduce<Methods | Function | undefined>(\n (acc, pathSegment) => {\n if (!isObject(acc) || !Object.hasOwn(acc, pathSegment)) {\n return undefined;\n }\n\n return acc[pathSegment];\n },\n methods,\n );\n\n return isFunction(result) ? result : undefined;\n};\n\nexport const formatMethodPath = (methodPath: MethodPath): string => {\n return methodPath.join('.');\n};\n", "import { serializeError } from './errorSerialization.js';\nimport type { Message, ReplyMessage, Methods, Log } from './types.js';\nimport Reply from './Reply.js';\nimport type Messenger from './messengers/Messenger.js';\nimport PenpalError from './PenpalError.js';\nimport { formatMethodPath, getMethodAtMethodPath } from './methodPath.js';\nimport { isCallMessage } from './guards.js';\nimport namespace from './namespace.js';\n\nconst createErrorReplyMessage = (\n channel: string | undefined,\n callId: string,\n error: unknown,\n): ReplyMessage => ({\n namespace,\n channel,\n type: 'REPLY',\n callId,\n isError: true,\n ...(error instanceof Error\n ? { value: serializeError(error), isSerializedErrorInstance: true }\n : { value: error }),\n});\n\n/**\n * Listens for \"call\" messages from the remote, executes the corresponding method,\n * and responds with the return value or error.\n */\nconst connectCallHandler = (\n messenger: Messenger,\n methods: Methods,\n channel: string | undefined,\n log: Log | undefined,\n): (() => void) => {\n let isDestroyed = false;\n\n const handleCallMessage = async (message: Message): Promise<void> => {\n if (isDestroyed) {\n // It's possible to throw an error here, but it would only be catchable\n // using window.onerror since we're in an asynchronously-called function.\n // There is no method call the consumer is making that they could wrap in\n // a try-catch. Even if the consumer were to catch the error somehow,\n // the value of doing so is questionable.\n return;\n }\n\n if (!isCallMessage(message)) {\n return;\n }\n\n log?.(`Received ${formatMethodPath(message.methodPath)}() call`, message);\n\n const { methodPath, args, id: callId } = message;\n let replyMessage: ReplyMessage;\n let transferables: Transferable[] | undefined;\n\n try {\n const method = getMethodAtMethodPath(methodPath, methods);\n\n if (!method) {\n throw new PenpalError(\n 'METHOD_NOT_FOUND',\n `Method \\`${formatMethodPath(methodPath)}\\` is not found.`,\n );\n }\n\n let value: unknown = await method(...args);\n\n if (value instanceof Reply) {\n transferables = value.transferables;\n value = await value.value;\n }\n\n replyMessage = {\n namespace,\n channel,\n type: 'REPLY',\n callId,\n value,\n };\n } catch (error) {\n replyMessage = createErrorReplyMessage(channel, callId, error);\n }\n\n // Although we checked this at the beginning of the function, we need to\n // check it again because we've made async calls, and the connection may\n // have been destroyed in the meantime.\n if (isDestroyed) {\n return;\n }\n\n try {\n log?.(`Sending ${formatMethodPath(methodPath)}() reply`, replyMessage);\n messenger.sendMessage(replyMessage, transferables);\n } catch (error) {\n // If a consumer attempts to send an object that's not\n // cloneable (e.g., window), we want to ensure the receiver's promise\n // gets rejected.\n if ((error as Error).name === 'DataCloneError') {\n replyMessage = createErrorReplyMessage(channel, callId, error as Error);\n log?.(`Sending ${formatMethodPath(methodPath)}() reply`, replyMessage);\n messenger.sendMessage(replyMessage);\n // Don't return prematurely here, because we still want the error to\n // hit the console so it's easier for developers to notice and debug.\n }\n\n throw error;\n }\n };\n\n // Messenger dispatch is synchronous, so start the async work without\n // returning its Promise and handle any rejection here.\n const handleMessage = (message: Message): void => {\n void handleCallMessage(message).catch((error: unknown) => {\n console.error(error);\n });\n };\n\n messenger.addMessageHandler(handleMessage);\n\n return () => {\n isDestroyed = true;\n messenger.removeMessageHandler(handleMessage);\n };\n};\n\nexport default connectCallHandler;\n", "/**\n * @return A unique ID\n */\n// crypto.randomUUID is not available in insecure contexts.\nconst generateId: () => string =\n crypto.randomUUID?.bind(crypto) ??\n (() =>\n new Array(4)\n .fill(0)\n .map(() =>\n Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16),\n )\n .join('-'));\n\nexport default generateId;\n", "const brand: unique symbol = Symbol('CallOptions');\n\nclass CallOptions {\n readonly transferables: Transferable[] | undefined;\n readonly timeout: number | undefined;\n\n // Allows TypeScript to distinguish between an actual instance of this\n // class versus an object that looks structurally similar.\n // eslint-disable-next-line no-unused-private-class-members\n #brand = brand;\n\n constructor(options?: {\n transferables?: Transferable[] | undefined;\n timeout?: number | undefined;\n }) {\n this.transferables = options?.transferables;\n this.timeout = options?.timeout;\n }\n}\n\nexport default CallOptions;\n", "import generateId from './generateId.js';\nimport { deserializeError } from './errorSerialization.js';\nimport { formatMethodPath } from './methodPath.js';\nimport type {\n Message,\n RemoteProxy,\n Methods,\n MethodPath,\n CallMessage,\n Log,\n} from './types.js';\nimport CallOptions from './CallOptions.js';\nimport type Messenger from './messengers/Messenger.js';\nimport PenpalError from './PenpalError.js';\nimport { isReplyMessage } from './guards.js';\nimport namespace from './namespace.js';\n\ntype ReplyHandler = {\n methodPath: MethodPath;\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n timeoutId: ReturnType<typeof globalThis.setTimeout> | undefined;\n};\n\ntype ConnectedRemoteProxy<TMethods extends Methods> = {\n remoteProxy: RemoteProxy<TMethods>;\n destroy: () => void;\n};\n\nconst methodsToTreatAsNative = new Set(['apply', 'call', 'bind']);\n\nconst createRemoteProxy = (\n callback: (path: MethodPath, args: unknown[]) => Promise<unknown>,\n log: Log | undefined,\n path: MethodPath = [],\n): Methods => {\n return new Proxy(\n path.length\n ? () => {\n // Intentionally empty\n }\n : Object.create(null),\n {\n get(target, prop: string) {\n // If a promise is resolved with this proxy object, the JavaScript\n // runtime will look for a `then` property on this object to determine\n // if it should be treated as a promise (to support promise chaining).\n // If we don't return undefined here, the JavaScript runtime will treat\n // this object as a promise and attempt to call `then`, which will\n // then send a call message to the remote. This is not what we want.\n if (prop === 'then') {\n return;\n }\n\n // Because we're using a proxy and because Penpal supports developers\n // exposing nested methods, we have a predicament. If a developer\n // calls, for example, remote.auth.apply(), are they\n // attempting to call a nested apply() method that a developer has\n // explicitly exposed from the remote? Could they instead be attempting\n // to call Function.prototype.apply() on the remote.auth() method?\n // Without the remote telling the local Penpal which methods the\n // developer has exposed, it has no way of knowing (and the main reason\n // we use a proxy is so that Penpal doesn't have to communicate which\n // methods are exposed). So, we treat certain methods as native methods\n // and return the native method rather than a proxy. The downside of\n // this is that if a developer has explicitly exposed a nested method\n // with the same name as one of these native method names, the developer\n // will be unable to call the exposed remote method because they will\n // be calling the method on the Function prototype instead.\n if (path.length && methodsToTreatAsNative.has(prop)) {\n return Reflect.get(target, prop);\n }\n\n return createRemoteProxy(callback, log, [...path, prop]);\n },\n apply(target, _thisArg, args) {\n return callback(path, args);\n },\n },\n );\n};\n\nconst getDestroyedConnectionMethodCallError = (methodPath: MethodPath) => {\n return new PenpalError(\n 'CONNECTION_DESTROYED',\n `Method call ${formatMethodPath(\n methodPath,\n )}() failed due to destroyed connection`,\n );\n};\n\n/**\n * Creates a proxy. When methods are called on the proxy, a \"call\" message will\n * be sent to the remote, the remote's corresponding method will be\n * executed, and the method's return value will be returned via a message.\n */\nconst connectRemoteProxy = <TMethods extends Methods>(\n messenger: Messenger,\n channel: string | undefined,\n log: Log | undefined,\n): ConnectedRemoteProxy<TMethods> => {\n let isDestroyed = false;\n const replyHandlers = new Map<string, ReplyHandler>();\n\n const handleMessage = (message: Message) => {\n if (!isReplyMessage(message)) {\n return;\n }\n\n const { callId, value, isError, isSerializedErrorInstance } = message;\n const replyHandler = replyHandlers.get(callId);\n\n if (!replyHandler) {\n return;\n }\n\n replyHandlers.delete(callId);\n clearTimeout(replyHandler.timeoutId);\n log?.(\n `Received ${formatMethodPath(replyHandler.methodPath)}() reply`,\n message,\n );\n\n if (isError) {\n replyHandler.reject(\n isSerializedErrorInstance ? deserializeError(value) : value,\n );\n } else {\n replyHandler.resolve(value);\n }\n };\n\n messenger.addMessageHandler(handleMessage);\n\n const remoteProxy = createRemoteProxy((methodPath, args) => {\n if (isDestroyed) {\n throw getDestroyedConnectionMethodCallError(methodPath);\n }\n\n const callId = generateId();\n const lastArg = args[args.length - 1];\n const lastArgIsOptions = lastArg instanceof CallOptions;\n const { timeout, transferables } = lastArgIsOptions ? lastArg : {};\n const argsWithoutOptions = lastArgIsOptions ? args.slice(0, -1) : args;\n\n return new Promise((resolve, reject) => {\n // Use globalThis so this works in both window and worker contexts.\n const timeoutId =\n timeout !== undefined\n ? globalThis.setTimeout(() => {\n replyHandlers.delete(callId);\n reject(\n new PenpalError(\n 'METHOD_CALL_TIMEOUT',\n `Method call ${formatMethodPath(\n methodPath,\n )}() timed out after ${timeout}ms`,\n ),\n );\n }, timeout)\n : undefined;\n\n replyHandlers.set(callId, { methodPath, resolve, reject, timeoutId });\n\n try {\n const callMessage: CallMessage = {\n namespace,\n channel,\n type: 'CALL',\n id: callId,\n methodPath,\n args: argsWithoutOptions,\n };\n log?.(`Sending ${formatMethodPath(methodPath)}() call`, callMessage);\n messenger.sendMessage(callMessage, transferables);\n } catch (error) {\n replyHandlers.delete(callId);\n clearTimeout(timeoutId);\n reject(\n new PenpalError('TRANSMISSION_FAILED', (error as Error).message),\n );\n }\n });\n }, log) as RemoteProxy<TMethods>;\n\n const destroy = () => {\n isDestroyed = true;\n messenger.removeMessageHandler(handleMessage);\n\n for (const { methodPath, reject, timeoutId } of replyHandlers.values()) {\n clearTimeout(timeoutId);\n reject(getDestroyedConnectionMethodCallError(methodPath));\n }\n\n replyHandlers.clear();\n };\n\n return {\n remoteProxy,\n destroy,\n };\n};\n\nexport default connectRemoteProxy;\n", "import type Messenger from './messengers/Messenger.js';\nimport type {\n Ack2Message,\n Methods,\n Message,\n RemoteProxy,\n Ack1Message,\n SynMessage,\n Log,\n} from './types.js';\nimport PenpalError from './PenpalError.js';\nimport connectCallHandler from './connectCallHandler.js';\nimport connectRemoteProxy from './connectRemoteProxy.js';\nimport { isAck2Message, isAck1Message, isSynMessage } from './guards.js';\nimport generateId from './generateId.js';\nimport namespace from './namespace.js';\n\ntype Options = {\n messenger: Messenger;\n methods: Methods;\n timeout: number | undefined;\n channel: string | undefined;\n log: Log | undefined;\n};\n\ntype Handshake<TMethods extends Methods> = {\n promise: Promise<RemoteProxy<TMethods>>;\n destroy: () => void;\n};\n\n/**\n * Attempts to establish communication with the remote via a handshake protocol.\n * The handshake protocol fulfills a few requirements:\n *\n * 1. One participant in the handshake may not be available when the other\n * participant starts the handshake. For example, a document inside an iframe\n * may not be loaded when the parent window starts a handshake.\n * 2. While #1 could be solved by having the consumer of Penpal specify which\n * participant should initiate the handshake, we'd rather avoid this\n * unnecessary cognitive load.\n * 3. While #1 could be solved by having the consumer of Penpal specify which\n * participant is the \"parent\" or \"child\" and then having Penpal assume\n * the child should initiate the handshake, we'd rather avoid parent-child\n * terminology since Penpal can support communication between two\n * participants where neither would be considered a parent nor child. It may\n * also be too presumptive that the child should always initiate the\n * handshake.\n * 4. For robustness, each participant must know that the other participant is\n * receiving its messages for the handshake to be considered complete.\n * 5. The handshake should support a participant attempting to\n * re-establish the connection. This can occur, for example, if an end user\n * were to right-click within an iframe and click reload.\n * 6. The handshake should allow a Messenger to easily attach something to\n * a handshake message from one participant to the other unidirectionally\n * (rather than from both participants to each other).\n * This is important when a participant needs to be in charge of, for\n * example, creating a MessageChannel and sending one MessagePort from the\n * MessagePort pair to the other participant. If both participants attempted\n * to do this it could lead to confusion.\n * 7. The handshake ideally shouldn't require sending handshake messages on an\n * interval (retrying until the other participant is ready to receive them).\n * Intervals can increase compute resources if the interval is too short\n * or increase latency if the interval is too long. While we could make this\n * configurable, it's additional mental load for the consumer. Additionally,\n * setInterval and setTimeout are not available within some contexts\n * (like AudioWorklet), where a consumer may like to use Penpal.\n *\n * To accomplish these requirements, the handshake protocol is as follows:\n * 1. Each participant generates a random participant ID.\n * 2. As soon as possible, each participant sends a SYN message containing its\n * participant ID to the other participant.\n * 3. When the SYN messages were sent, one of the participants may not have\n * been ready to receive the SYN message from the other. At least one\n * of the participants was ready, however, and should have received a SYN\n * message from the other participant. Each participant that did receive\n * a SYN message knows for sure that the other participant is now ready\n * to receive a SYN message, so it will send another SYN message in case\n * the other participant did not receive the first SYN message. This\n * ultimately results in each participant sending two SYN messages.\n * 4. Each participant now should have received at least one SYN message from\n * the other participant. Each participant compares their own ID with the\n * other participant's ID. Whichever participant has the higher ID\n * (using a simple string comparison) is considered the handshake leader\n * and will send an ACK1 message to the other participant.\n * 5. At this point, the handshake leader does not know whether the other\n * participant is actually receiving messages. The participant receiving\n * the ACK1 message will respond with an ACK2, informing the handshake\n * leader that it is indeed receiving messages.\n * 6. At this point, both participants know the other is receiving messages\n * and the handshake is complete.\n */\nconst shakeHands = <TMethods extends Methods>({\n messenger,\n methods,\n timeout,\n channel,\n log,\n}: Options): Handshake<TMethods> => {\n const participantId = generateId();\n let remoteParticipantId: string;\n const destroyHandlers: (() => void)[] = [];\n let isComplete = false;\n let isDestroyed = false;\n\n const { promise, resolve, reject } =\n Promise.withResolvers<RemoteProxy<TMethods>>();\n\n const timeoutId =\n timeout !== undefined\n ? setTimeout(() => {\n rejectAndCleanUp(\n new PenpalError(\n 'CONNECTION_TIMEOUT',\n `Connection timed out after ${timeout}ms`,\n ),\n );\n }, timeout)\n : undefined;\n\n const cleanUp = () => {\n clearTimeout(timeoutId);\n\n for (const destroyHandler of destroyHandlers.splice(0)) {\n destroyHandler();\n }\n };\n\n const rejectAndCleanUp = (error: PenpalError) => {\n if (isDestroyed) {\n return;\n }\n\n isDestroyed = true;\n cleanUp();\n reject(error);\n };\n\n const destroy = () => {\n rejectAndCleanUp(\n new PenpalError('CONNECTION_DESTROYED', 'Connection destroyed'),\n );\n };\n\n const completeHandshake = () => {\n if (isDestroyed || isComplete) {\n // Ignore messages after cleanup, and keep completion idempotent when\n // the remote attempts to re-connect.\n return;\n }\n\n destroyHandlers.push(connectCallHandler(messenger, methods, channel, log));\n\n const { remoteProxy, destroy: destroyMethodProxies } =\n connectRemoteProxy<TMethods>(messenger, channel, log);\n\n destroyHandlers.push(destroyMethodProxies);\n\n clearTimeout(timeoutId);\n isComplete = true;\n\n resolve(remoteProxy);\n };\n\n const sendHandshakeMessage = (\n message: SynMessage | Ack1Message | Ack2Message,\n ) => {\n if (isDestroyed) {\n return false;\n }\n\n log?.(`Sending handshake ${message.type}`, message);\n\n try {\n messenger.sendMessage(message);\n return true;\n } catch (error) {\n rejectAndCleanUp(\n new PenpalError('TRANSMISSION_FAILED', (error as Error).message),\n );\n return false;\n }\n };\n\n const sendSynMessage = () => {\n const synMessage: SynMessage = {\n namespace,\n type: 'SYN',\n channel,\n participantId: participantId,\n };\n\n return sendHandshakeMessage(synMessage);\n };\n\n const handleSynMessage = (message: SynMessage) => {\n log?.(`Received handshake SYN`, message);\n\n if (message.participantId === remoteParticipantId) {\n return;\n }\n\n remoteParticipantId = message.participantId;\n\n // We send another SYN message in case the other participant was not ready\n // when we sent the first SYN message.\n if (!sendSynMessage()) {\n return;\n }\n\n const isHandshakeLeader = participantId > remoteParticipantId;\n\n if (!isHandshakeLeader) {\n return;\n }\n\n const ack1Message: Ack1Message = {\n namespace,\n channel,\n type: 'ACK1',\n };\n\n if (!sendHandshakeMessage(ack1Message)) {\n return;\n }\n };\n\n const handleAck1Message = (message: Ack1Message) => {\n log?.(`Received handshake ACK1`, message);\n const ack2Message: Ack2Message = {\n namespace,\n channel,\n type: 'ACK2',\n };\n\n if (sendHandshakeMessage(ack2Message)) {\n completeHandshake();\n }\n };\n\n const handleAck2Message = (message: Ack2Message) => {\n log?.(`Received handshake ACK2`, message);\n completeHandshake();\n };\n\n const handleMessage = (message: Message) => {\n if (isDestroyed) {\n return;\n }\n\n if (isSynMessage(message)) {\n handleSynMessage(message);\n } else if (isAck1Message(message)) {\n handleAck1Message(message);\n } else if (isAck2Message(message)) {\n handleAck2Message(message);\n }\n };\n\n messenger.addMessageHandler(handleMessage);\n destroyHandlers.push(() => messenger.removeMessageHandler(handleMessage));\n\n sendSynMessage();\n\n return {\n promise,\n destroy,\n };\n};\n\nexport default shakeHands;\n", "// eslint-disable-next-line typescript/no-explicit-any\nconst once = <T extends (...args: any[]) => any>(\n fn: T,\n): ((...args: Parameters<T>) => ReturnType<T>) => {\n let isCalled = false;\n let result: ReturnType<T>;\n\n return (...args: Parameters<T>): ReturnType<T> => {\n if (!isCalled) {\n isCalled = true;\n result = fn(...args);\n }\n return result;\n };\n};\n\nexport default once;\n", "import type {\n DestroyMessage,\n Connection,\n Log,\n Message,\n Methods,\n RemoteProxy,\n} from './types.js';\nimport PenpalError from './PenpalError.js';\nimport type Messenger from './messengers/Messenger.js';\nimport shakeHands from './shakeHands.js';\nimport { isDestroyMessage, isMessage } from './guards.js';\nimport once from './once.js';\nimport namespace from './namespace.js';\n\ntype Options = {\n /**\n * Messenger in charge of handling communication with the remote.\n */\n messenger: Messenger;\n /**\n * Methods that may be called by the remote.\n */\n methods?: Methods | undefined;\n /**\n * The amount of time, in milliseconds, Penpal should wait\n * for a connection to be established before rejecting the connection promise.\n */\n timeout?: number | undefined;\n /**\n * A string identifier that disambiguates communication when establishing\n * multiple, parallel connections between two participants (e.g., two windows,\n * a window and a worker).\n */\n channel?: string | undefined;\n /**\n * A function for logging debug messages. Debug messages will only be\n * logged when this is defined.\n */\n log?: Log | undefined;\n};\n\nconst usedMessengers = new WeakSet<Messenger>();\n\nconst getConnectionDestroyedError = () => {\n return new PenpalError('CONNECTION_DESTROYED', 'Connection destroyed');\n};\n\n/**\n * Attempts to establish communication with the remote.\n */\nconst connect = <TMethods extends Methods>({\n messenger,\n methods = {},\n timeout,\n channel,\n log,\n}: Options): Connection<TMethods> => {\n if (!messenger) {\n throw new PenpalError('INVALID_ARGUMENT', 'messenger must be defined');\n }\n\n if (usedMessengers.has(messenger)) {\n throw new PenpalError(\n 'INVALID_ARGUMENT',\n 'A messenger can only be used for a single connection',\n );\n }\n\n usedMessengers.add(messenger);\n\n const connectionPromise = Promise.withResolvers<RemoteProxy<TMethods>>();\n let isConnectionDestroyed = false;\n let destroyHandshake: (() => void) | undefined;\n\n const sendDestroyMessage = () => {\n const destroyMessage: DestroyMessage = {\n namespace,\n channel,\n type: 'DESTROY',\n };\n\n try {\n messenger.sendMessage(destroyMessage);\n } catch (_) {\n // We do our best to notify the other participant of the connection,\n // but if there's an error in doing so (e.g., maybe the handshake\n // hasn't completed and a messenger can't send the message), it's\n // probably not worth bothering the consumer with an error.\n }\n };\n\n const destroyConnection = once(\n (\n connectionPromiseRejection: PenpalError,\n notifyOtherParticipant: boolean,\n ) => {\n isConnectionDestroyed = true;\n\n if (notifyOtherParticipant) {\n sendDestroyMessage();\n }\n\n messenger.destroy();\n destroyHandshake?.();\n\n connectionPromise.reject(connectionPromiseRejection);\n\n log?.('Connection destroyed');\n },\n );\n\n const validateReceivedMessage = (data: unknown): data is Message => {\n return isMessage(data) && data.channel === channel;\n };\n\n void (async () => {\n try {\n messenger.initialize({ log, validateReceivedMessage });\n messenger.addMessageHandler((message) => {\n if (isDestroyMessage(message)) {\n destroyConnection(getConnectionDestroyedError(), false);\n }\n });\n\n const handshake = shakeHands<TMethods>({\n messenger,\n methods,\n timeout,\n channel,\n log,\n });\n\n destroyHandshake = handshake.destroy;\n\n const remoteProxy = await handshake.promise;\n\n if (isConnectionDestroyed) {\n handshake.destroy();\n return;\n }\n\n connectionPromise.resolve(remoteProxy);\n } catch (error) {\n destroyConnection(error as PenpalError, true);\n }\n })();\n\n return {\n promise: connectionPromise.promise,\n destroy: () => {\n destroyConnection(getConnectionDestroyedError(), true);\n },\n };\n};\n\nexport default connect;\n", "import type { Log, Message } from '../types.js';\nimport type Messenger from './Messenger.js';\nimport type {\n InitializeMessengerOptions,\n MessageHandler,\n} from './Messenger.js';\nimport { isAck2Message, isAck1Message, isSynMessage } from '../guards.js';\nimport PenpalError from '../PenpalError.js';\n\n// Keep this structural so generated .d.ts files don't reference the DOM-only\n// Window global. Penpal also supports worker-only projects, and those projects\n// should be able to type-check the package without adding \"dom\" to tsconfig libs\n// just because WindowMessenger is one of Penpal's exports.\ntype WindowLike = {\n postMessage: (\n message: unknown,\n options?: {\n targetOrigin?: string;\n transfer?: Transferable[];\n },\n ) => void;\n};\n\ntype Options = {\n /**\n * The window with which the current window will communicate.\n */\n remoteWindow: WindowLike;\n /**\n * An array of strings or regular expressions defining to which origins\n * communication will be allowed. If not provided, communication will be\n * restricted to the origin of the current page. You may specify an allowed\n * origin of `*` to not restrict communication, but beware the risks of\n * doing so.\n */\n allowedOrigins?: (string | RegExp)[] | undefined;\n};\n\n/**\n * Handles the details of communicating with a child window.\n */\nclass WindowMessenger implements Messenger {\n readonly #remoteWindow: WindowLike;\n readonly #allowedOrigins: [string | RegExp, ...(string | RegExp)[]];\n #log: Log | undefined;\n #validateReceivedMessage: ((data: unknown) => data is Message) | undefined;\n #concreteRemoteOrigin: string | undefined;\n #messageCallbacks = new Set<MessageHandler>();\n #port: MessagePort | undefined;\n\n constructor({ remoteWindow, allowedOrigins }: Options) {\n if (!remoteWindow) {\n throw new PenpalError('INVALID_ARGUMENT', 'remoteWindow must be defined');\n }\n\n this.#remoteWindow = remoteWindow;\n this.#allowedOrigins = allowedOrigins?.length\n ? (allowedOrigins as [string | RegExp, ...(string | RegExp)[]])\n : [window.origin];\n }\n\n initialize = ({\n log,\n validateReceivedMessage,\n }: InitializeMessengerOptions): void => {\n this.#log = log;\n this.#validateReceivedMessage = validateReceivedMessage;\n window.addEventListener('message', this.#handleMessageFromRemoteWindow);\n };\n\n sendMessage = (message: Message, transferables?: Transferable[]): void => {\n if (isSynMessage(message)) {\n const originForSending = this.#getOriginForSendingMessage(message);\n this.#remoteWindow.postMessage(message, {\n targetOrigin: originForSending,\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n if (isAck1Message(message)) {\n const originForSending = this.#getOriginForSendingMessage(message);\n this.#remoteWindow.postMessage(message, {\n targetOrigin: originForSending,\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n if (isAck2Message(message)) {\n const { port1, port2 } = new MessageChannel();\n this.#setPort(port1);\n const transferablesToSend = [port2, ...(transferables || [])];\n const originForSending = this.#getOriginForSendingMessage(message);\n\n try {\n this.#remoteWindow.postMessage(message, {\n targetOrigin: originForSending,\n transfer: transferablesToSend,\n });\n } catch (error) {\n this.#destroyPort();\n port2.close();\n throw error;\n }\n\n return;\n }\n\n if (this.#port) {\n this.#port.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n throw new PenpalError(\n 'TRANSMISSION_FAILED',\n 'Cannot send message because the MessagePort is not connected',\n );\n };\n\n addMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.add(callback);\n };\n\n removeMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.delete(callback);\n };\n\n destroy = (): void => {\n window.removeEventListener('message', this.#handleMessageFromRemoteWindow);\n this.#destroyPort();\n this.#messageCallbacks.clear();\n };\n\n #isAllowedOrigin = (origin: string) => {\n return this.#allowedOrigins.some((allowedOrigin) => {\n if (allowedOrigin instanceof RegExp) {\n // RegExp.test() mutates lastIndex for global and sticky expressions,\n // so test a copy to keep repeated handshake messages deterministic.\n return new RegExp(allowedOrigin).test(origin);\n }\n\n return allowedOrigin === origin || allowedOrigin === '*';\n });\n };\n\n #getOriginForSendingMessage = (message: Message) => {\n // It's safe to send the SYN message to any origin because it doesn't contain\n // anything sensitive. When Penpal receives a SYN message, the origin on\n // the message (which we call the concrete origin) is validated against the\n // configured allowed origins. All subsequent messages will be sent to the\n // concrete origin.\n // If you decide to change this, consider https://github.com/Aaronius/penpal/issues/103\n if (isSynMessage(message)) {\n return '*';\n }\n\n if (!this.#concreteRemoteOrigin) {\n throw new PenpalError(\n 'TRANSMISSION_FAILED',\n 'Cannot send message because the remote origin is not established',\n );\n }\n\n // If the concrete remote origin (the origin we received from the remote\n // on a prior message) is 'null', it means the remote is within\n // an \"opaque origin\". The only way to post a message to an\n // opaque origin is by using '*'. This does carry some security risk,\n // so we only do this if the consumer has specifically defined '*' as\n // an allowed origin. Opaque origins occur, for example, when\n // loading an HTML document directly from the filesystem (not a\n // web server) or through a data URI.\n return this.#concreteRemoteOrigin === 'null' &&\n this.#allowedOrigins.includes('*')\n ? '*'\n : this.#concreteRemoteOrigin;\n };\n\n #destroyPort = () => {\n this.#port?.removeEventListener('message', this.#handleMessageFromPort);\n this.#port?.close();\n this.#port = undefined;\n };\n\n #setPort = (port: MessagePort) => {\n this.#destroyPort();\n this.#port = port;\n this.#port.addEventListener('message', this.#handleMessageFromPort);\n this.#port.start();\n };\n\n #handleMessageFromRemoteWindow = ({\n source,\n origin,\n ports,\n data,\n }: MessageEvent): void => {\n if (source !== this.#remoteWindow) {\n return;\n }\n\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n if (!this.#isAllowedOrigin(origin)) {\n this.#log?.(\n `Received a message from origin \\`${origin}\\` which did not match ` +\n `allowed origins \\`[${this.#allowedOrigins.join(', ')}]\\``,\n );\n return;\n }\n\n if (isSynMessage(data)) {\n // If we receive a SYN message and already have a port, it means\n // the child is re-connecting, in which case we'll receive a new port.\n // For this reason, we always make sure we destroy the existing port.\n this.#destroyPort();\n this.#concreteRemoteOrigin = origin;\n }\n\n if (isAck2Message(data)) {\n const port = ports[0];\n\n if (!port) {\n this.#log?.('Ignoring ACK2 because it did not include a MessagePort');\n return;\n }\n\n this.#setPort(port);\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n\n #handleMessageFromPort = ({ data }: MessageEvent): void => {\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n}\n\nexport default WindowMessenger;\n", "import type { Log, Message } from '../types.js';\nimport type Messenger from './Messenger.js';\nimport type {\n InitializeMessengerOptions,\n MessageHandler,\n} from './Messenger.js';\nimport { isAck2Message, isAck1Message, isSynMessage } from '../guards.js';\nimport PenpalError from '../PenpalError.js';\n\n// Keep this structural so generated .d.ts files don't depend on\n// DedicatedWorkerGlobalScope being present in consumer tsconfig libs.\ntype WorkerLike = Pick<\n Worker,\n 'postMessage' | 'addEventListener' | 'removeEventListener'\n>;\n\ntype Options = {\n /**\n * The web worker receiving/sending communication from/to the parent window.\n * If this messenger is being used within the worker, `worker` should\n * typically be set to `self`.\n */\n worker: WorkerLike;\n};\n\n/**\n * Handles the details of communicating with a child web worker.\n */\nclass WorkerMessenger implements Messenger {\n #worker: WorkerLike;\n #log: Log | undefined;\n #validateReceivedMessage: ((data: unknown) => data is Message) | undefined;\n #messageCallbacks = new Set<MessageHandler>();\n #port: MessagePort | undefined;\n\n constructor({ worker }: Options) {\n if (!worker) {\n throw new PenpalError('INVALID_ARGUMENT', 'worker must be defined');\n }\n\n this.#worker = worker;\n }\n\n initialize = ({\n log,\n validateReceivedMessage,\n }: InitializeMessengerOptions): void => {\n this.#log = log;\n this.#validateReceivedMessage = validateReceivedMessage;\n this.#worker.addEventListener('message', this.#handleMessage);\n };\n\n sendMessage = (message: Message, transferables?: Transferable[]): void => {\n if (isSynMessage(message) || isAck1Message(message)) {\n this.#worker.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n if (isAck2Message(message)) {\n const { port1, port2 } = new MessageChannel();\n this.#setPort(port1);\n\n try {\n this.#worker.postMessage(message, {\n transfer: [port2, ...(transferables || [])],\n });\n } catch (error) {\n this.#destroyPort();\n port2.close();\n throw error;\n }\n\n return;\n }\n\n if (this.#port) {\n this.#port.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n throw new PenpalError(\n 'TRANSMISSION_FAILED',\n 'Cannot send message because the MessagePort is not connected',\n );\n };\n\n addMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.add(callback);\n };\n\n removeMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.delete(callback);\n };\n\n destroy = (): void => {\n this.#worker.removeEventListener('message', this.#handleMessage);\n this.#destroyPort();\n this.#messageCallbacks.clear();\n };\n\n #destroyPort = () => {\n this.#port?.removeEventListener('message', this.#handleMessage);\n this.#port?.close();\n this.#port = undefined;\n };\n\n #setPort = (port: MessagePort) => {\n this.#destroyPort();\n this.#port = port;\n this.#port.addEventListener('message', this.#handleMessage);\n this.#port.start();\n };\n\n #handleMessage = ({ ports, data }: MessageEvent): void => {\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n if (isSynMessage(data)) {\n // If we receive a SYN message and already have a port, it means\n // the child is re-connecting, in which case we'll receive a new port.\n // For this reason, we always make sure we destroy the existing port.\n this.#destroyPort();\n }\n\n if (isAck2Message(data)) {\n const port = ports[0];\n\n if (!port) {\n this.#log?.('Ignoring ACK2 because it did not include a MessagePort');\n return;\n }\n\n this.#setPort(port);\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n}\n\nexport default WorkerMessenger;\n", "import type { Message } from '../types.js';\nimport type Messenger from './Messenger.js';\nimport type {\n InitializeMessengerOptions,\n MessageHandler,\n} from './Messenger.js';\nimport PenpalError from '../PenpalError.js';\n\ntype Options = {\n /**\n * The port used to communicate to the other port of the port pair.\n */\n port: MessagePort;\n};\n\n/**\n * Handles the details of communicating on a MessagePort.\n */\nclass PortMessenger implements Messenger {\n #port: MessagePort;\n #validateReceivedMessage: ((data: unknown) => data is Message) | undefined;\n #messageCallbacks = new Set<MessageHandler>();\n\n constructor({ port }: Options) {\n if (!port) {\n throw new PenpalError('INVALID_ARGUMENT', 'port must be defined');\n }\n\n this.#port = port;\n }\n\n initialize = ({\n validateReceivedMessage,\n }: InitializeMessengerOptions): void => {\n this.#validateReceivedMessage = validateReceivedMessage;\n this.#port.addEventListener('message', this.#handleMessage);\n this.#port.start();\n };\n\n sendMessage = (message: Message, transferables?: Transferable[]): void => {\n this.#port?.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n };\n\n addMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.add(callback);\n };\n\n removeMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.delete(callback);\n };\n\n destroy = (): void => {\n this.#port.removeEventListener('message', this.#handleMessage);\n this.#port.close();\n this.#messageCallbacks.clear();\n };\n\n #handleMessage = ({ data }: MessageEvent): void => {\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n}\n\nexport default PortMessenger;\n", "// Not intended to be used internally. Can be useful externally\n// in projects not using TypeScript. It has the `Obj` suffix to disambiguate\n// it from the ErrorCode string union.\nconst ErrorCodeObj = {\n ConnectionDestroyed: 'CONNECTION_DESTROYED',\n ConnectionTimeout: 'CONNECTION_TIMEOUT',\n InvalidArgument: 'INVALID_ARGUMENT',\n MethodCallTimeout: 'METHOD_CALL_TIMEOUT',\n MethodNotFound: 'METHOD_NOT_FOUND',\n TransmissionFailed: 'TRANSMISSION_FAILED',\n} as const;\n\nexport default ErrorCodeObj;\n", "import type { Log } from './types.js';\n\nconst debug = (prefix?: string): Log => {\n return (...args: unknown[]) => {\n console.log(`✍️ %c${prefix}%c`, 'font-weight: bold;', '', ...args);\n };\n};\n\nexport default debug;\n", "import {\n Graffiti,\n GraffitiErrorCursorExpired,\n GraffitiErrorForbidden,\n GraffitiErrorInvalidSchema,\n GraffitiErrorNotAcceptable,\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorTooLarge,\n type GraffitiLoginEvent,\n type GraffitiMedia,\n type GraffitiObjectStream,\n type GraffitiLogoutEvent,\n type GraffitiPostMedia,\n type GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport {\n CallOptions,\n connect,\n type Connection,\n type RemoteProxy,\n WindowMessenger,\n} from \"penpal\";\nexport type * from \"@graffiti-garden/api\";\n\ntype MethodsOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: T[K];\n};\n\ntype SerializedMedia = Omit<GraffitiMedia, \"data\"> & {\n data: { buffer: ArrayBuffer; type: string };\n};\n\ntype SerializedPostMedia = Omit<GraffitiPostMedia, \"data\"> & {\n data: { buffer: ArrayBuffer; type: string };\n};\n\ntype RPCMethods = MethodsOf<Graffiti> & {\n getMedia: (...args: Parameters<Graffiti[\"getMedia\"]>) => Promise<SerializedMedia>;\n postMedia: (\n media: SerializedPostMedia,\n session: GraffitiSession,\n ) => Promise<string>;\n discover: (id: string, ...args: Parameters<Graffiti[\"discover\"]>) => void;\n continueDiscover: (\n id: string,\n ...args: Parameters<Graffiti[\"continueDiscover\"]>\n ) => void;\n streamNext: (id: string) => ReturnType<GraffitiObjectStream<{}>[\"next\"]>;\n streamReturn: (id: string) => void;\n initialize: () => void;\n destroy: () => void;\n};\n\nconst simpleMethods = [\n \"post\",\n \"get\",\n \"delete\",\n \"deleteMedia\",\n \"login\",\n \"logout\",\n \"actorToHandle\",\n \"handleToActor\",\n] as const;\n\nconst graffitiErrors = [\n GraffitiErrorCursorExpired,\n GraffitiErrorForbidden,\n GraffitiErrorInvalidSchema,\n GraffitiErrorNotAcceptable,\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorTooLarge,\n] as const;\n\nlet instance: GraffitiRpcClient | undefined;\n\nexport interface GraffitiRpcClientOptions {\n remoteWindow: Window;\n allowedOrigins?: (string | RegExp)[];\n channel?: string;\n timeout?: number;\n}\n\n/** A Graffiti implementation whose operations run in another iframe. */\n// @ts-ignore Simple methods are attached programmatically in the constructor.\nexport class GraffitiRpcClient extends Graffiti {\n readonly sessionEvents = new EventTarget();\n\n private readonly connection!: Connection<RPCMethods>;\n private readonly remote_!: Promise<RemoteProxy<RPCMethods>>;\n private remoteClient?: RemoteProxy<RPCMethods>;\n private readonly sessions = new Map<string, GraffitiSession>();\n private initialized = false;\n private destroyed = false;\n\n constructor(options: GraffitiRpcClientOptions) {\n super();\n if (instance) {\n const existing = instance;\n window.setTimeout(() => existing.replayInitialization(), 0);\n return existing;\n }\n\n this.connection = connect<RPCMethods>({\n messenger: new WindowMessenger({\n remoteWindow: options.remoteWindow,\n // Sandboxed srcdoc frames have opaque origins.\n allowedOrigins: options.allowedOrigins ?? [\"*\"],\n }),\n channel: options.channel,\n timeout: options.timeout,\n methods: {\n sessionEvent: (type: string, detail: unknown) => {\n if (type === \"login\") {\n const login = detail as GraffitiLoginEvent[\"detail\"];\n if (!login.error) this.sessions.set(login.session.actor, login.session);\n } else if (type === \"logout\") {\n const logout = detail as GraffitiLogoutEvent[\"detail\"];\n if (!logout.error) this.sessions.delete(logout.actor);\n } else if (type === \"initialized\") {\n this.initialized = true;\n detail = undefined;\n }\n this.sessionEvents.dispatchEvent(new CustomEvent(type, { detail }));\n },\n },\n });\n this.remote_ = this.connection.promise.then((remote) => {\n const restored = restoreErrors(remote);\n this.remoteClient = restored;\n return restored;\n });\n window.setTimeout(() => {\n void this.remote_\n .then((remote) => remote.initialize())\n .catch(() => {\n // The iframe may be removed before the connection initializes.\n });\n }, 0);\n\n for (const method of simpleMethods) {\n (this as any)[method] = async (...args: unknown[]) => {\n const remote = await this.remote();\n const call = remote[method] as (...args: unknown[]) => unknown;\n return call(...clone(args));\n };\n }\n\n instance = this;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n if (instance === this) instance = undefined;\n void this.remoteClient?.destroy().catch(() => {});\n this.connection.destroy();\n }\n\n private replayInitialization() {\n if (this.destroyed) return;\n for (const session of this.sessions.values()) {\n this.sessionEvents.dispatchEvent(\n new CustomEvent(\"login\", { detail: { session } }),\n );\n }\n if (this.initialized) {\n this.sessionEvents.dispatchEvent(\n new CustomEvent(\"initialized\", { detail: undefined }),\n );\n }\n }\n\n getMedia: Graffiti[\"getMedia\"] = async (...args) => {\n const result = await (await this.remote()).getMedia(...clone(args));\n return {\n ...result,\n data: new Blob([result.data.buffer], { type: result.data.type }),\n };\n };\n\n postMedia: Graffiti[\"postMedia\"] = async (media, session) => {\n const buffer = await media.data.arrayBuffer();\n return (await this.remote()).postMedia(\n {\n ...clone(media),\n data: { buffer, type: media.data.type },\n },\n clone(session),\n new CallOptions({ transferables: [buffer] }),\n );\n };\n\n protected remoteStream(\n start: (remote: RemoteProxy<RPCMethods>, id: string) => Promise<void>,\n ): GraffitiObjectStream<{}> {\n const id = `graffiti-rpc-${Date.now().toString(36)}-${Math.random()\n .toString(36)\n .slice(2, 10)}`;\n const remote = this.remote_;\n\n return (async function* () {\n const rpc = await remote;\n try {\n await start(rpc, id);\n while (true) {\n const result = await rpc.streamNext(id);\n if (result.done) return result.value;\n yield result.value;\n }\n } finally {\n await rpc.streamReturn(id);\n }\n })();\n }\n\n // @ts-ignore Graffiti schema inference is preserved at the public boundary.\n discover: Graffiti[\"discover\"] = (...args) => {\n return this.remoteStream((remote, id) =>\n remote.discover(id, ...clone(args)),\n ) as any;\n };\n\n // @ts-ignore Graffiti schema inference is preserved at the public boundary.\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n return this.remoteStream((remote, id) =>\n remote.continueDiscover(id, ...clone(args)),\n ) as any;\n };\n\n private remote() {\n if (this.destroyed) throw new Error(\"Graffiti RPC client has been destroyed.\");\n return this.remote_;\n }\n}\n\nfunction clone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nfunction restoreErrors(remote: RemoteProxy<RPCMethods>) {\n return new Proxy(remote, {\n get(target, property) {\n const method = Reflect.get(target, property) as unknown;\n return typeof method === \"function\"\n ? (...args: unknown[]) =>\n (method as (...args: unknown[]) => Promise<unknown>)(...args).catch(\n restoreError,\n )\n : method;\n },\n });\n}\n\nfunction restoreError(error: unknown): never {\n if (error instanceof Error) {\n const ErrorType = graffitiErrors.find(({ name }) => name === error.name);\n if (ErrorType) Object.setPrototypeOf(error, ErrorType.prototype);\n }\n throw error;\n}\n", "import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n height: \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100vw\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage({ type: \"graffiti-guard:connect\" }, hostUrl.origin);\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n loginUrl.searchParams.set(\"guardLogin\", \"1\");\n loginUrl.searchParams.set(\"redirectUrl\", window.location.href);\n if (actor) loginUrl.searchParams.set(\"suggestedActor\", actor);\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n auditUrl.searchParams.set(\"redirectUrl\", window.location.href);\n auditUrl.searchParams.set(\"source\", JSON.stringify(scope?.source ?? []));\n if (scope?.actor) auditUrl.searchParams.set(\"actor\", scope.actor);\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["Graffiti", "GraffitiObjectJSONSchema", "GraffitiPostObjectJSONSchema", "GraffitiErrorForbidden", "_GraffitiErrorForbidden", "message", "GraffitiErrorNotFound", "_GraffitiErrorNotFound", "GraffitiErrorInvalidSchema", "_GraffitiErrorInvalidSchema", "GraffitiErrorSchemaMismatch", "_GraffitiErrorSchemaMismatch", "GraffitiErrorTooLarge", "_GraffitiErrorTooLarge", "GraffitiErrorNotAcceptable", "_GraffitiErrorNotAcceptable", "GraffitiErrorCursorExpired", "_GraffitiErrorCursorExpired", "PenpalError", "code", "message", "serializeError", "error", "deserializeError", "name", "stack", "penpalCode", "deserializedError", "brand", "Reply", "#brand", "value", "options", "namespace_default", "isObject", "isFunction", "isMessage", "data", "isSynMessage", "isAck1Message", "isAck2Message", "isCallMessage", "isReplyMessage", "isDestroyMessage", "getMethodAtMethodPath", "methodPath", "methods", "result", "acc", "pathSegment", "formatMethodPath", "createErrorReplyMessage", "channel", "callId", "connectCallHandler", "messenger", "log", "isDestroyed", "handleCallMessage", "args", "replyMessage", "transferables", "method", "handleMessage", "generateId", "CallOptions", "methodsToTreatAsNative", "createRemoteProxy", "callback", "path", "target", "prop", "_thisArg", "getDestroyedConnectionMethodCallError", "connectRemoteProxy", "replyHandlers", "isError", "isSerializedErrorInstance", "replyHandler", "lastArg", "lastArgIsOptions", "timeout", "argsWithoutOptions", "resolve", "reject", "timeoutId", "callMessage", "shakeHands", "participantId", "remoteParticipantId", "destroyHandlers", "isComplete", "promise", "rejectAndCleanUp", "cleanUp", "destroyHandler", "destroy", "completeHandshake", "remoteProxy", "destroyMethodProxies", "sendHandshakeMessage", "sendSynMessage", "handleSynMessage", "handleAck1Message", "handleAck2Message", "once", "fn", "isCalled", "usedMessengers", "getConnectionDestroyedError", "connect", "connectionPromise", "isConnectionDestroyed", "destroyHandshake", "sendDestroyMessage", "destroyMessage", "destroyConnection", "connectionPromiseRejection", "notifyOtherParticipant", "validateReceivedMessage", "handshake", "WindowMessenger", "#remoteWindow", "#allowedOrigins", "#log", "#validateReceivedMessage", "#concreteRemoteOrigin", "#messageCallbacks", "#port", "remoteWindow", "allowedOrigins", "#handleMessageFromRemoteWindow", "originForSending", "#getOriginForSendingMessage", "port1", "port2", "#setPort", "transferablesToSend", "#destroyPort", "#isAllowedOrigin", "origin", "allowedOrigin", "#handleMessageFromPort", "port", "source", "ports", "simpleMethods", "graffitiErrors", "GraffitiErrorCursorExpired", "GraffitiErrorForbidden", "GraffitiErrorInvalidSchema", "GraffitiErrorNotAcceptable", "GraffitiErrorNotFound", "GraffitiErrorSchemaMismatch", "GraffitiErrorTooLarge", "instance", "GraffitiRpcClient", "Graffiti", "options", "existing", "connect", "WindowMessenger", "type", "detail", "login", "logout", "remote", "restored", "restoreErrors", "method", "args", "call", "clone", "session", "result", "media", "buffer", "CallOptions", "start", "id", "rpc", "value", "target", "property", "restoreError", "error", "ErrorType", "name", "instance", "defaultConnectionTimeout", "GraffitiGuarded", "Graffiti", "options", "hostUrl", "iframe", "connected", "connectionErrorShown", "showConnectionError", "remoteWindow", "rpc", "GraffitiRpcClient", "connectionTimeout", "markConnected", "onMessage", "event", "target", "property", "receiver", "value", "actor", "loginUrl", "scope", "auditUrl"]
4
+ "sourcesContent": ["import type {\n GraffitiObjectUrl,\n GraffitiObject,\n GraffitiObjectBase,\n GraffitiSession,\n GraffitiPostObject,\n GraffitiObjectStream,\n GraffitiMedia,\n GraffitiPostMedia,\n GraffitiMediaAccept,\n} from \"./2-types.js\";\nimport type { JSONSchema } from \"json-schema-to-ts\";\n\n/**\n * This API describes a small but powerful set of methods that\n * can be used to create many different kinds of social applications,\n * from applications like Twitter, to Messenger, to Wikipedia, to many more new designs.\n * See the [Graffiti project website](https://graffiti.garden)\n * for links to example applications. Additionally, apps built on top\n * of the API interoperate with each other so you can seamlessly switch\n * between apps without losing your friends or data.\n *\n * These API methods should satisfy all of an application's needs for\n * the communication, storage, and access management of social data.\n * The rest of the application can be built with standard client-side\n * user interface tools to present and interact with that data\u2014no server code necessary!\n *\n * The TypeScript code for this API is [open source on GitHub](https://github.com/graffiti-garden/graffiti/tree/main/api).\n *\n * There are several different implementations of this Graffiti API available,\n * including a [decentralized implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/decentralized),\n * that lets people choose where their data is stored (you do not need to host your own server)\n * and a [local implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/local)\n * that can be used for testing and development. Different implementations can\n * be swapped-in in the future without changing the API or any of the apps built on\n * top of it. In fact, we're working on an end-to-end encrypted version now!\n * [Follow Theia on BlueSky for updates](https://bsky.app/profile/theias.place).\n *\n * On the other side of the stack, there is [Vue plugin](https://vue.graffiti.garden/variables/GraffitiPlugin.html)\n * that wraps around this API to provide reactivity. Other plugin frameworks\n * and high-level libraries will be available in the future.\n *\n * ## API Overview\n *\n * The Graffiti API provides applications with methods for {@link login} and {@link logout},\n * methods to interact with data objects using standard database operations ({@link post}, {@link get}, and {@link delete}),\n * and a method to {@link discover} data objects created by others.\n * These data objects have a couple structured properties:\n * - {@link GraffitiObjectBase.url | `url`} (string): A globally unique identifier and locator for the object.\n * - {@link GraffitiObjectBase.actor | `actor`} (string): An unforgeable identifier for the creator of the object.\n * - {@link GraffitiObjectBase.allowed | `allowed`} (string[] | undefined): An array of the actors who are allowed to access the object (undefined for public objects).\n * - {@link GraffitiObjectBase.channels | `channels`} (string[]): An array of the *contexts* in which the object should appear.\n *\n * All other data is stored in the object's unstructured {@link GraffitiObjectBase.value | `value`} property.\n * This data can be used to represent social artifacts (e.g. posts, profiles) and activities (e.g. likes, follows).\n * For example, a post might have the value:\n\n * ```js\n * {\n * title: \"My First Post\",\n * content: \"Hello, world!\",\n * published: 1630483200000\n * }\n * ```\n *\n * a profile might have the value:\n *\n * ```js\n * {\n * name: \"Theia Henderson\",\n * pronouns: \"she/her\",\n * describes: \"did:web:theias.place\" // Theia's actor ID\n * }\n * ```\n *\n * and a \"Like\" might have the value:\n *\n * ```js\n * {\n * activity: \"Like\",\n * target: \"graffiti:remote:pod.graffiti.garden/12345\" // The URL of the graffiti object being liked\n * }\n * ```\n *\n * New social artifacts and activities can be easily created, simply\n * by creating new objects with appropriate properties. Despite the lack of\n * structure, we expect Graffiti object properties to adhere to a \"[folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\",\n * similar to hashtags. Any string can be used as a hashtag on Twitter,\n * but there is social value in using the same hashtags at other people and\n * so a structure naturally emerges. Similarly, Graffiti objects\n * can have arbitrary properties but if people use the same properties as each other,\n * their apps will interoperate, which has social value.\n *\n * For a more complete and detailed overview of Graffiti's design, please\n * refer to [this section of the Graffiti paper](https://dl.acm.org/doi/10.1145/3746059.3747627#sec-3),\n * published in ACM UIST 2025. The paper also overviews {@link GraffitiObjectBase.channels | `channels`},\n * which are Graffiti's means of organizing data contextually, and a concept called \"total reification\",\n * which handles explains how moderation, collaboration, and other interactions are managed.\n *\n * @groupDescription 1 - Single-Object Methods\n * Methods for {@link post | creating}, {@link get | reading},\n * and {@link delete | deleting} {@link GraffitiObjectBase | Graffiti objects}.\n * @groupDescription 2 - Multi-Object Methods\n * Methods that retrieve or accumulate information about multiple {@link GraffitiObjectBase | Graffiti objects} at a time.\n * @groupDescription 3 - Media Methods\n * Methods for {@link postMedia | creating}, {@link getMedia | reading},\n * and {@link deleteMedia | deleting} media data.\n * @groupDescription 4 - Identity Methods\n * Methods and properties for logging in and out.\n */\nexport abstract class Graffiti {\n /**\n * Creates a new {@link GraffitiObjectBase | object}.\n *\n * @returns Returns the object that has been posted, complete with its\n * assigned {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}.\n *\n * @group 1 - Single-Object Methods\n */\n abstract post<Schema extends JSONSchema>(\n /**\n * An object to post, minus its {@link GraffitiObjectBase.url | `url`} and\n * {@link GraffitiObjectBase.actor | `actor`}, which will be assigned once posted.\n * This object is statically type-checked against the [JSON schema](https://json-schema.org/) that can be optionally provided\n * as the generic type parameter. It is recommended to use a schema to\n * ensure that the posted object matches subsequent {@link get} or {@link discover}\n * methods.\n */\n partialObject: GraffitiPostObject<Schema>,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Retrieves an object from a given {@link GraffitiObjectBase.url | `url`} matching\n * the provided `schema`.\n *\n * If the retreiving {@link GraffitiObjectBase.actor | `actor`} is not\n * the object's `actor`,\n * the object's {@link GraffitiObjectBase.allowed | `allowed`} and\n * {@link GraffitiObjectBase.channels | `channels`} properties are\n * not revealed, similar to a BCC email.\n *\n * @returns Returns the retrieved object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has been deleted, or the actor is not\n * {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorSchemaMismatch} if the retrieved object does not match the provided schema.\n *\n * @throws {@link GraffitiErrorInvalidSchema} If an invalid schema is provided.\n *\n * @group 1 - Single-Object Methods\n */\n abstract get<Schema extends JSONSchema>(\n /**\n * The location of the object to get.\n */\n url: string | GraffitiObjectUrl,\n /**\n * The JSON schema to validate the retrieved object against.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * the retrieved object's {@link GraffitiObjectBase.allowed | `allowed`}\n * property must be `undefined`.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiObject<Schema>>;\n\n /**\n * Deletes an object from a given {@link GraffitiObjectBase.url | `url`}\n * that had previously been {@link post | `post`ed}.\n * The deleting {@link GraffitiObjectBase.actor | `actor`} must be the same as the\n * `actor` that created the object.\n *\n * @throws {@link GraffitiErrorNotFound} if the object does not exist, has already been deleted,\n * or the actor is not {@link GraffitiObjectBase.allowed | `allowed`} to access it.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * is not the same `actor` as the one who created the object.\n *\n * @group 1 - Single-Object Methods\n */\n abstract delete(\n /**\n * The location of the object to delete.\n */\n url: string | GraffitiObjectUrl,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<GraffitiObjectBase>;\n\n /**\n * Discovers objects created by any actor that are contained\n * in at least one of the given {@link GraffitiObjectBase.channels | `channels`}\n * and match the given [JSON Schema](https://json-schema.org).\n *\n * Objects are returned asynchronously as they are discovered but the stream\n * will end once all leads have been exhausted.\n * The {@link GraffitiObjectStream} ends by returning a\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string,\n * that can be passed to {@link continueDiscover | `continueDiscover`} to\n * poll for new objects.\n *\n * `discover` will not return objects that the querying {@link GraffitiObjectBase.actor | `actor`}\n * is not {@link GraffitiObjectBase.allowed | `allowed`} to access.\n * If the `actor` is not the creator of a discovered object,\n * the allowed list will be masked to only contain the querying actor if the\n * allowed list is not `undefined` (public). Additionally, if the actor is not the\n * creator of a discovered object, any {@link GraffitiObjectBase.channels | `channels`}\n * not specified by the `discover` method will not be revealed. This masking happens\n * before the object is validated against the supplied `schema`.\n *\n * Since different implementations may fetch data from multiple sources there is\n * no guarentee on the order that objects are returned in.\n *\n * @throws {@link GraffitiErrorInvalidSchema} if an invalid schema is provided.\n * Discovery is lazy and will not throw until the iterator is consumed.\n *\n * @returns Returns a stream of objects that match the given {@link GraffitiObjectBase.channels | `channels`}\n * and [JSON Schema](https://json-schema.org).\n *\n * @group 2 - Multi-Object Methods\n */\n abstract discover<Schema extends JSONSchema>(\n /**\n * The {@link GraffitiObjectBase.channels | `channels`} that objects must be associated with.\n */\n channels: string[],\n /**\n * A [JSON Schema](https://json-schema.org) that objects must satisfy.\n */\n schema: Schema,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}. If no `session` is provided,\n * only objects that have no {@link GraffitiObjectBase.allowed | `allowed`}\n * property will be returned.\n */\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema>;\n\n /**\n * Continues a {@link GraffitiObjectStream} from a given\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string.\n * The continuation will return new objects that have been {@link post | `post`ed}\n * that match the original stream, and also returns the\n * {@link GraffitiObjectBase.url | `url`}s of objects that\n * have been {@link delete | `delete`d}, as marked by a `tombstone`.\n *\n * @throws {@link GraffitiErrorCursorExpired} upon iteration\n * if the cursor is invalid or expired.\n *\n * @throws {@link GraffitiErrorForbidden} upon iteration\n * if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor`\n * that initiated the original stream.\n *\n * @group 2 - Multi-Object Methods\n */\n abstract continueDiscover<Schema extends JSONSchema>(\n cursor: string,\n session?: GraffitiSession | null,\n ): GraffitiObjectStream<Schema>;\n\n /**\n * Uploads media data, such as an image or video.\n *\n * Unlike structured {@link GraffitiObjectBase | objects},\n * media is not indexed for {@link discover | `discover`y} and\n * must be retrieved by its exact URL using {@link getMedia}\n *\n * @returns The URL that the media was posted to.\n *\n * @group 3 - Media Methods\n */\n abstract postMedia(\n /**\n * The media data to upload, and optionally\n * an {@link GraffitiObjectBase.allowed | `allowed`}\n * list of actors that can view it.\n */\n media: GraffitiPostMedia,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<string>;\n\n /**\n * Deletes media previously {@link postMedia | `post`ed} to a given URL.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorForbidden} if the {@link GraffitiObjectBase.actor | `actor`}\n * provided in the `session` is not the same as the `actor` that {@link postMedia | `post`ed}\n * the media.\n *\n * @group 3 - Media Methods\n */\n abstract deleteMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n url: string,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * Retrieves media from the given media URL, adhering to the given requirements.\n *\n * @throws {@link GraffitiErrorNotFound} if no media at that URL exists.\n *\n * @throws {@link GraffitiErrorTooLarge} if the media exceeds the given `maxBytes`.\n *\n * @throws {@link GraffitiErrorNotAcceptable} if the media does not match the given\n * `accept` specification.\n *\n * @returns The URL of the retrieved media, as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob)\n * and the {@link GraffitiObjectBase.actor | `actor`} that posted it.\n *\n * @group 3 - Media Methods\n */\n abstract getMedia(\n /**\n * A globally unique identifier and locator for the media.\n */\n mediaUrl: string,\n /**\n * A specification for what types and sizes of media are acceptable.\n */\n accept: GraffitiMediaAccept,\n /**\n * An implementation-specific object with information to authenticate the\n * {@link GraffitiObjectBase.actor | `actor`}.\n */\n session?: GraffitiSession | null,\n ): Promise<GraffitiMedia>;\n\n /**\n * Begins the login process. Depending on the implementation, this may\n * involve redirecting to a login page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * The {@link GraffitiSession | session} object is returned\n * asynchronously via {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLoginEvent} with event type `login`.\n *\n * @group 4 - Identity Methods\n */\n abstract login(\n /**\n * A suggested actor to login as. For example, if a user tries to\n * edit a post but are not logged in, the interface can infer that\n * they might want to log in as the actor who created the post\n * they are attempting to edit.\n *\n * Even if provided, the implementation should allow the user\n * to log in as a different actor if they choose.\n */\n actor?: string | null,\n ): Promise<void>;\n\n /**\n * Begins the logout process for a particular {@link GraffitiSession | session}. Depending on the implementation, this may\n * involve redirecting the user to a logout page or opening a popup,\n * so it should always be called in response to a gesture, such as clicking\n * a button, due to the [feature-gating browser security feature](https://developer.mozilla.org/en-US/docs/Web/Security/User_activation).\n *\n * A confirmation will be returned asynchronously via\n * {@link Graffiti.sessionEvents | sessionEvents}\n * as a {@link GraffitiLogoutEvent} as event type `logout`.\n *\n * @group 4 - Identity Methods\n */\n abstract logout(\n /**\n * The {@link GraffitiSession | session} object to logout.\n */\n session: GraffitiSession,\n ): Promise<void>;\n\n /**\n * An event target that can be used to listen for the following\n * events and their corresponding event types:\n * - `login` - {@link GraffitiLoginEvent}\n * - `logout` - {@link GraffitiLogoutEvent}\n * - `initialized` - {@link GraffitiSessionInitializedEvent}\n *\n * @group 4 - Identity Methods\n */\n abstract readonly sessionEvents: EventTarget;\n\n /**\n * Retrieves the human-readable handle associated\n * with the given actor. The handle may change over time\n * and so it should be used for display purposes only.\n *\n * The inverse of {@link handleToActor}.\n *\n * @throws {@link GraffitiErrorNotFound} if a handle cannot be\n * found for the given actor.\n *\n * @returns A human-readable handle for the given actor.\n *\n * @group 4 - Identity Methods\n */\n abstract actorToHandle(actor: string): Promise<string>;\n\n /**\n * Retrieves the actor ID associated with the given handle.\n *\n * The inverse of {@link actorToHandle}.\n *\n * @throws {@link GraffitiErrorNotFound} if there is no actor\n * with the given handle.\n *\n * @returns The actor ID for the given handle.\n *\n * @group 4 - Identity Methods\n */\n abstract handleToActor(handle: string): Promise<string>;\n}\n", "import type { JSONSchema, FromSchema } from \"json-schema-to-ts\";\n\n/**\n * Objects are the atomic unit in Graffiti that can represent both data (*e.g.* a social media post or profile)\n * and activities (*e.g.* a like or follow).\n *\n * Each object embeds the {@link actor | `actor`} that created it.\n * Object content and metadata are static but an object may be deleted by\n * its creating {@link actor | `actor`}.\n *\n * An object's content is stored in its {@link value | `value`} property, which can be any JSON\n * object. However, it is recommended to use properties from the\n * [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/)\n * or properties that emerge in the Graffiti [folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\n * to promote interoperability.\n *\n * Each object is globally addressable via its {@link url | `url`}.\n *\n * An object's {@link channels | `channels`} and {@link allowed | `allowed`} properties\n * are set by an objects creator to shape the visibility of and access to their object.\n */\nexport interface GraffitiObjectBase {\n /**\n * The object's content as freeform JSON. We recommend using properties from the\n * [Activity Vocabulary](https://www.w3.org/TR/activitystreams-vocabulary/)\n * or properties that emerge in the Graffiti [folksonomy](https://en.wikipedia.org/wiki/Folksonomy)\n * to promote interoperability.\n */\n value: {};\n\n /**\n * An array of URIs the creator associates with the object. Objects can only be found by querying\n * one of the object's channels using the\n * {@link Graffiti.discover} method. This allows creators to express the intended audience of their object\n * which helps to prevent [context collapse](https://en.wikipedia.org/wiki/Context_collapse) even\n * in the highly interoperable ecosystem that Graffiti envisions. For example, channel URIs may be:\n * - A actor's own {@link actor | `actor`} URI. Posting an object to this channel is a way to broadcast\n * the object to the actor's followers, like posting a tweet.\n * - The URL of a Graffiti post. Posting an object to this channel is a way to broadcast to anyone viewing\n * the post, like commenting on a tweet.\n * - A URI representing a topic. Posting an object to this channel is a way to broadcast to anyone interested\n * in that topic, like posting in a subreddit.\n */\n channels: string[];\n\n /**\n * An optional array of {@link actor | `actor`} URIs that the creator allows to access the object.\n * If no `allowed` array is provided, the object can be accessed by anyone (so long as they\n * also know the right {@link channels | `channel` } to look in). An object can always be accessed by its creator, even if\n * the `allowed` array is empty.\n *\n * The `allowed` array is not revealed to actors other than the creator, like\n * a BCC email. An actor may choose to add a `to` property to the object's {@link value | `value`} to indicate\n * other recipients, however this is not enforced by Graffiti and may not accurately reflect the actual `allowed` array.\n *\n * `allowed` can be combined with {@link channels | `channels`}. For example, to send someone a direct message\n * the sender should post their object to the channel of the recipient's {@link actor | `actor`} URI to notify them of the message and also add\n * the recipient's {@link actor | `actor`} URI to the `allowed` array to prevent others from seeing the message.\n */\n allowed?: string[] | null;\n\n /**\n * The URI of the `actor` that {@link Graffiti.post | created } the object.\n * This `actor` has the unique permission to\n * {@link Graffiti.delete | delete} the object.\n *\n * We borrow the term actor from the ActivityPub because\n * [like in ActivityPub](https://www.w3.org/TR/activitypub/#h-note-0)\n * there is not necessarily a one-to-one mapping between actors and people/users.\n * Multiple people can share the same actor or one person can have multiple actors.\n * Actors can also be bots.\n *\n * In Graffiti, actors are always globally unique URIs which\n * allows them to also function as {@link channels | `channels`}.\n */\n actor: string;\n\n /**\n * A globally unique identifier and locator for the object. It can be used to point to\n * an object or to retrieve the object directly with {@link Graffiti.get}.\n *\n * An object's URL is generated when the object is first created and\n * should include sufficient randomness to prevent collisions\n * and guessing. The URL starts with a \"scheme,\" just like web URLs start with `http` or `https`, to indicate\n * to indicate the particular Graffiti implementation. This allows for applications\n * to pull from multiple coexisting Graffiti implementations without collision.\n * Existing schemes include `graffiti:local:` for objects stored locally\n * (see the [local implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/local))\n * and implementation-specific Graffiti URLs for objects stored on web services\n * (see the [decentralized implementation](https://github.com/graffiti-garden/graffiti/tree/main/implementations/decentralized)).\n * Options available in the future might include `graffiti:solid:` for objects stored on Solid servers\n * or `graffiti:p2p:` for objects stored on a peer-to-peer network.\n */\n url: string;\n}\n\n/**\n * This type constrains the {@link GraffitiObjectBase} type to adhere to a\n * particular [JSON schema](https://json-schema.org/).\n * This allows for static type-checking of an object's {@link GraffitiObjectBase.value | `value`}\n * which is otherwise a freeform JSON object.\n *\n * Schema-aware objects are returned by {@link Graffiti.get} and {@link Graffiti.discover}.\n */\nexport type GraffitiObject<Schema extends JSONSchema> = GraffitiObjectBase &\n FromSchema<Schema & typeof GraffitiPostObjectJSONSchema>;\n\n/**\n * A JSON Schema equivalent to the {@link GraffitiObjectBase} type.\n * Needed internally for type inference of JSON Schemas, but can\n * be used by implementations to validate objects.\n */\nexport const GraffitiObjectJSONSchema = {\n type: \"object\",\n properties: {\n value: { type: \"object\" },\n channels: { type: \"array\", items: { type: \"string\" } },\n allowed: { type: \"array\", items: { type: \"string\" }, nullable: true },\n url: { type: \"string\" },\n actor: { type: \"string\" },\n },\n additionalProperties: false,\n required: [\"value\", \"channels\", \"actor\", \"url\"],\n} as const satisfies JSONSchema;\n\n/**\n * This is an object containing only the {@link GraffitiObjectBase.url | `url`}\n * property of a {@link GraffitiObjectBase | GraffitiObject}.\n * It is used as a utility type so that applications can call\n * {@link Graffiti.delete} directly on an object\n * rather than on `object.url`.\n */\nexport type GraffitiObjectUrl = Pick<GraffitiObjectBase, \"url\">;\n\n/**\n * This object is a subset of {@link GraffitiObjectBase} that must be constructed locally before calling {@link Graffiti.post}.\n * This local copy ignores system-generated properties\n * ({@link GraffitiObjectBase.url | `url`} and {@link GraffitiObjectBase.actor | `actor`}),\n * and may be statically typed with\n * a [JSON schema](https://json-schema.org/) to prevent the accidental creation of erroneous objects.\n *\n * This local object must have a {@link GraffitiObjectBase.value | `value`} and {@link GraffitiObjectBase.channels | `channels`}\n * and may optionally have an {@link GraffitiObjectBase.allowed | `allowed`} property.\n */\nexport type GraffitiPostObject<Schema extends JSONSchema> = Pick<\n GraffitiObjectBase,\n \"value\" | \"channels\" | \"allowed\"\n> &\n FromSchema<Schema & typeof GraffitiPostObjectJSONSchema>;\n\n/**\n * A JSON Schema equivalent to the {@link GraffitiPostObject} type.\n * Needed internally for type inference of JSON Schemas, but can\n * be used by implementations to validate objects.\n */\nexport const GraffitiPostObjectJSONSchema = {\n ...GraffitiObjectJSONSchema,\n required: [\"value\", \"channels\"],\n} as const satisfies JSONSchema;\n\n/**\n * This object contains information that the underlying implementation can\n * use to authenticate a particular {@link GraffitiObjectBase.actor | `actor`}.\n * This object is required of all {@link Graffiti} methods\n * that modify objects or media and is optional for methods that read objects.\n *\n * At a minimum the `session` object must contain the\n * {@link GraffitiSession.actor | `actor`} URI to authenticate with.\n * However it is likely that the `session` object must contain other\n * implementation-specific properties.\n * For example, a Solid implementation might include a\n * [`fetch`](https://docs.inrupt.com/developer-tools/api/javascript/solid-client-authn-browser/functions.html#fetch)\n * function. A distributed implementation may include\n * a cryptographic signature.\n *\n * As to why the `session` object is passed as an argument to every method\n * rather than being an internal property of the {@link Graffiti} instance,\n * this is primarily for type-checking to catch bugs related to login state.\n * Graffiti applications can expose some functionality to people who are not logged in\n * with {@link Graffiti.get} and {@link Graffiti.discover} but without type-checking\n * the `session` it can be easy to forget to hide buttons that trigger\n * other methods that require login.\n *\n * Passing the `session` object per-method also allows for multiple sessions\n * to be used within the same application, like an Email client fetching from\n * multiple accounts.\n */\nexport interface GraffitiSession {\n /**\n * The {@link GraffitiObjectBase.actor | `actor`} to authenticate with.\n */\n actor: string;\n}\n\n/**\n * A stream of data that returned by {@link Graffiti.discover | `discover`}\n * and {@link Graffiti.continueDiscover | `continueDiscover`}.\n *\n * Non-fatal errors are returned within the stream rather than as\n * exceptions that would halt the entire stream. This is because\n * some implementations may pull data from multiple sources\n * including some that may be unreliable. Such errors can generally\n * be ignored, but should be printed for debugging.\n * See {@link GraffitiObjectStreamError}.\n *\n * The stream is an [`AsyncGenerator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)\n * that can be iterated over using `for await` loops or calling `next` on the generator.\n * The stream can be terminated by breaking out of a loop calling `return` on the generator.\n *\n * The stream ends by returning a\n * {@link GraffitiObjectStreamReturn.cursor | `cursor`} string,\n * that can be passed to {@link Graffiti.continueDiscover | `continueDiscover`} to\n * resume the stream from where it left off.\n */\nexport type GraffitiObjectStream<Schema extends JSONSchema> = AsyncGenerator<\n | GraffitiObjectStreamError\n | GraffitiObjectStreamEntry<Schema>\n | GraffitiObjectStreamTombstone,\n GraffitiObjectStreamReturn\n>;\n\n/**\n * An error that can occur in a {@link GraffitiObjectStream}.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamError {\n /**\n * The error that occurred while streaming data.\n */\n error: Error;\n /**\n * The origin that the error occurred. It will include\n * the scheme of the Graffiti implementation used and other\n * implementation-specific information like a hostname.\n */\n origin: string;\n}\n\n/**\n * A successful result from a {@link GraffitiObjectStream}\n * that includes an object.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamEntry<Schema extends JSONSchema> {\n /**\n * Empty property for compatibility with {@link GraffitiObjectStreamError}\n */\n error?: undefined;\n /**\n * Empty property for compatibility with {@link GraffitiObjectStreamTombstone}\n */\n tombstone?: undefined;\n /**\n * The object returned by the stream.\n */\n object: GraffitiObject<Schema>;\n}\n\n/**\n * A result from a {@link GraffitiObjectStream} that indicated\n * an object has been deleted since the original stream was run.\n * Only sparse metadata about the deleted object is returned to respect\n * the deleting actor's privacy.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamTombstone {\n /**\n * Empty property for compatibility with {@link GraffitiObjectStreamError}\n */\n error?: undefined;\n /**\n * Use this property to differentiate a tombstone from a\n * {@link GraffitiObjectStreamEntry}.\n */\n tombstone: true;\n /**\n * Sparse metadata about the deleted object. The full object is not returned\n * to respect an actor's privacy.\n */\n object: {\n /**\n * The {@link GraffitiObjectBase.url | `url`} of the deleted object.\n */\n url: string;\n };\n}\n\n/**\n * The final return value of a {@link GraffitiObjectStream}.\n *\n * @internal\n */\nexport interface GraffitiObjectStreamReturn {\n /**\n * A string that can be passed to the {@link Graffiti.continueDiscover | `continueDiscover`}\n * to resume the stream. The cursor can be serialized and used between\n * instantiations of {@link Graffiti}\n */\n cursor: string;\n}\n\n/**\n * The event type produced in {@link Graffiti.sessionEvents}\n * when a actor logs in manually from {@link Graffiti.login}\n * or when their session is restored from a previous login.\n * The event name to listen for is `login`.\n */\nexport type GraffitiLoginEvent = CustomEvent<\n | {\n error: Error;\n session?: GraffitiSession;\n }\n | {\n error?: undefined;\n session: GraffitiSession;\n }\n>;\n\n/**\n * The event type produced in {@link Graffiti.sessionEvents}\n * when a actor logs out either manually with {@link Graffiti.logout}\n * or when their session times out or otherwise becomes invalid.\n * The event name to listen for is `logout`.\n */\nexport type GraffitiLogoutEvent = CustomEvent<\n | {\n error: Error;\n actor?: string;\n }\n | {\n error?: undefined;\n actor: string;\n }\n>;\n\n/**\n * The event type produced in {@link Graffiti.sessionEvents}\n * after an application has attempted to complete any login redirects\n * and restore any previously active sessions.\n * Successful session restores will be returned in prior to the\n * initialized event as their own {@link GraffitiLoginEvent} events.\n * The event name to listen for is `initialized`.\n */\nexport type GraffitiSessionInitializedEvent = CustomEvent<\n { error?: Error } | null | undefined\n>;\n\nexport type GraffitiMedia = {\n /**\n * The binary data of the media to be uploaded,\n * along with its [media type](https://www.iana.org/assignments/media-types/media-types.xhtml),\n * formatted as a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob).\n */\n data: Blob;\n /**\n * The {@link GraffitiObjectBase.actor | `actor`} that\n * {@link Graffiti.postMedia | `post`ed} the media.\n */\n actor: string;\n /**\n * An optional list, identical in function to an object's\n * {@link GraffitiObjectBase.allowed | `allowed`} property,\n * that specifies the {@link GraffitiObjectBase.actor | `actor`}s\n * who are allowed to access the media. If the list is `undefined`\n * or `null`, anyone with the URL can access the media. If the list\n * is empty, only the {@link GraffitiObjectBase.actor | `actor`}\n * who {@link Graffiti.postMedia | `post`ed} the media can access it.\n */\n allowed?: string[] | null;\n};\n\nexport type GraffitiPostMedia = Pick<GraffitiMedia, \"data\" | \"allowed\">;\n\nexport type GraffitiMediaAccept = {\n /**\n * A list of acceptable media types for the retrieved media.\n * Each type in the list may be of the form `<type>/<subtype>`,\n * `<type>/*`, or `&#42;/*`, just as types are formatted in\n * an [HTTP Accept header](https://httpwg.org/specs/rfc9110.html#field.accept).\n */\n types?: string[];\n /**\n * The maximum acceptable size, in bytes, of the media.\n * Must be a nonnegative safe integer.\n */\n maxBytes?: number;\n};\n", "export class GraffitiErrorForbidden extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorForbidden\";\n Object.setPrototypeOf(this, GraffitiErrorForbidden.prototype);\n }\n}\n\nexport class GraffitiErrorNotFound extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorNotFound\";\n Object.setPrototypeOf(this, GraffitiErrorNotFound.prototype);\n }\n}\n\nexport class GraffitiErrorInvalidSchema extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorInvalidSchema\";\n Object.setPrototypeOf(this, GraffitiErrorInvalidSchema.prototype);\n }\n}\n\nexport class GraffitiErrorSchemaMismatch extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorSchemaMismatch\";\n Object.setPrototypeOf(this, GraffitiErrorSchemaMismatch.prototype);\n }\n}\n\nexport class GraffitiErrorTooLarge extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorTooLarge\";\n Object.setPrototypeOf(this, GraffitiErrorTooLarge.prototype);\n }\n}\n\nexport class GraffitiErrorNotAcceptable extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorNotAcceptable\";\n Object.setPrototypeOf(this, GraffitiErrorNotAcceptable.prototype);\n }\n}\n\nexport class GraffitiErrorCursorExpired extends Error {\n constructor(message?: string) {\n super(message);\n this.name = \"GraffitiErrorCursorExpired\";\n Object.setPrototypeOf(this, GraffitiErrorCursorExpired.prototype);\n }\n}\n", "import type { ErrorCode } from './types.js';\n\nclass PenpalError extends Error {\n public code: ErrorCode;\n\n constructor(code: ErrorCode, message: string) {\n super(message);\n this.name = 'PenpalError';\n this.code = code;\n }\n}\n\nexport default PenpalError;\n", "import type { SerializedError } from './types.js';\nimport PenpalError from './PenpalError.js';\n\n/**\n * Converts an error object into a plain object.\n */\nexport const serializeError = (error: Error): SerializedError => ({\n name: error.name,\n message: error.message,\n stack: error.stack,\n penpalCode: error instanceof PenpalError ? error.code : undefined,\n});\n\n/**\n * Converts a plain object into an error object.\n */\nexport const deserializeError = ({\n name,\n message,\n stack,\n penpalCode,\n}: SerializedError): Error => {\n const deserializedError = penpalCode\n ? new PenpalError(penpalCode, message)\n : new Error(message);\n\n deserializedError.name = name;\n // TypeScript declares Error.stack as `stack?: string`. With\n // exactOptionalPropertyTypes, that permits a missing property but rejects\n // assigning undefined. A serialized error can legitimately have no stack,\n // and Penpal must overwrite the locally generated stack with that undefined\n // value to preserve the remote error, so widen the property for this write.\n (deserializedError as { stack: string | undefined }).stack = stack;\n\n return deserializedError;\n};\n", "const brand: unique symbol = Symbol('Reply');\n\nclass Reply<T = unknown> {\n readonly value: T;\n readonly transferables: Transferable[] | undefined;\n\n // Allows TypeScript to distinguish between an actual instance of this\n // class versus an object that looks structurally similar.\n // eslint-disable-next-line no-unused-private-class-members\n #brand = brand;\n\n constructor(\n value: T,\n options?: {\n transferables?: Transferable[] | undefined;\n },\n ) {\n this.value = value;\n this.transferables = options?.transferables;\n }\n}\n\nexport default Reply;\n", "export default 'penpal' as const;\n", "import namespace from './namespace.js';\nimport type {\n Ack2Message,\n CallMessage,\n Message,\n ReplyMessage,\n Ack1Message,\n SynMessage,\n DestroyMessage,\n} from './types.js';\n\nexport const isObject = (\n value: unknown,\n): value is Record<string | number | symbol, unknown> => {\n return typeof value === 'object' && value !== null;\n};\n\nexport const isFunction = (value: unknown): value is Function => {\n return typeof value === 'function';\n};\n\nexport const isMessage = (data: unknown): data is Message => {\n return isObject(data) && data.namespace === namespace;\n};\n\nexport const isSynMessage = (message: Message): message is SynMessage => {\n return message.type === 'SYN';\n};\n\nexport const isAck1Message = (message: Message): message is Ack1Message => {\n return message.type === 'ACK1';\n};\n\nexport const isAck2Message = (message: Message): message is Ack2Message => {\n return message.type === 'ACK2';\n};\n\nexport const isCallMessage = (message: Message): message is CallMessage => {\n return message.type === 'CALL';\n};\n\nexport const isReplyMessage = (message: Message): message is ReplyMessage => {\n return message.type === 'REPLY';\n};\n\nexport const isDestroyMessage = (\n message: Message,\n): message is DestroyMessage => {\n return message.type === 'DESTROY';\n};\n", "import type { MethodPath, Methods } from './types.js';\nimport { isFunction, isObject } from './guards.js';\n\nexport const getMethodAtMethodPath = (\n methodPath: MethodPath,\n methods: Methods,\n): Function | undefined => {\n const result = methodPath.reduce<Methods | Function | undefined>(\n (acc, pathSegment) => {\n if (!isObject(acc) || !Object.hasOwn(acc, pathSegment)) {\n return undefined;\n }\n\n return acc[pathSegment];\n },\n methods,\n );\n\n return isFunction(result) ? result : undefined;\n};\n\nexport const formatMethodPath = (methodPath: MethodPath): string => {\n return methodPath.join('.');\n};\n", "import { serializeError } from './errorSerialization.js';\nimport type { Message, ReplyMessage, Methods, Log } from './types.js';\nimport Reply from './Reply.js';\nimport type Messenger from './messengers/Messenger.js';\nimport PenpalError from './PenpalError.js';\nimport { formatMethodPath, getMethodAtMethodPath } from './methodPath.js';\nimport { isCallMessage } from './guards.js';\nimport namespace from './namespace.js';\n\nconst createErrorReplyMessage = (\n channel: string | undefined,\n callId: string,\n error: unknown,\n): ReplyMessage => ({\n namespace,\n channel,\n type: 'REPLY',\n callId,\n isError: true,\n ...(error instanceof Error\n ? { value: serializeError(error), isSerializedErrorInstance: true }\n : { value: error }),\n});\n\n/**\n * Listens for \"call\" messages from the remote, executes the corresponding method,\n * and responds with the return value or error.\n */\nconst connectCallHandler = (\n messenger: Messenger,\n methods: Methods,\n channel: string | undefined,\n log: Log | undefined,\n): (() => void) => {\n let isDestroyed = false;\n\n const handleCallMessage = async (message: Message): Promise<void> => {\n if (isDestroyed) {\n // It's possible to throw an error here, but it would only be catchable\n // using window.onerror since we're in an asynchronously-called function.\n // There is no method call the consumer is making that they could wrap in\n // a try-catch. Even if the consumer were to catch the error somehow,\n // the value of doing so is questionable.\n return;\n }\n\n if (!isCallMessage(message)) {\n return;\n }\n\n log?.(`Received ${formatMethodPath(message.methodPath)}() call`, message);\n\n const { methodPath, args, id: callId } = message;\n let replyMessage: ReplyMessage;\n let transferables: Transferable[] | undefined;\n\n try {\n const method = getMethodAtMethodPath(methodPath, methods);\n\n if (!method) {\n throw new PenpalError(\n 'METHOD_NOT_FOUND',\n `Method \\`${formatMethodPath(methodPath)}\\` is not found.`,\n );\n }\n\n let value: unknown = await method(...args);\n\n if (value instanceof Reply) {\n transferables = value.transferables;\n value = await value.value;\n }\n\n replyMessage = {\n namespace,\n channel,\n type: 'REPLY',\n callId,\n value,\n };\n } catch (error) {\n replyMessage = createErrorReplyMessage(channel, callId, error);\n }\n\n // Although we checked this at the beginning of the function, we need to\n // check it again because we've made async calls, and the connection may\n // have been destroyed in the meantime.\n if (isDestroyed) {\n return;\n }\n\n try {\n log?.(`Sending ${formatMethodPath(methodPath)}() reply`, replyMessage);\n messenger.sendMessage(replyMessage, transferables);\n } catch (error) {\n // If a consumer attempts to send an object that's not\n // cloneable (e.g., window), we want to ensure the receiver's promise\n // gets rejected.\n if ((error as Error).name === 'DataCloneError') {\n replyMessage = createErrorReplyMessage(channel, callId, error as Error);\n log?.(`Sending ${formatMethodPath(methodPath)}() reply`, replyMessage);\n messenger.sendMessage(replyMessage);\n // Don't return prematurely here, because we still want the error to\n // hit the console so it's easier for developers to notice and debug.\n }\n\n throw error;\n }\n };\n\n // Messenger dispatch is synchronous, so start the async work without\n // returning its Promise and handle any rejection here.\n const handleMessage = (message: Message): void => {\n void handleCallMessage(message).catch((error: unknown) => {\n console.error(error);\n });\n };\n\n messenger.addMessageHandler(handleMessage);\n\n return () => {\n isDestroyed = true;\n messenger.removeMessageHandler(handleMessage);\n };\n};\n\nexport default connectCallHandler;\n", "/**\n * @return A unique ID\n */\n// crypto.randomUUID is not available in insecure contexts.\nconst generateId: () => string =\n crypto.randomUUID?.bind(crypto) ??\n (() =>\n new Array(4)\n .fill(0)\n .map(() =>\n Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16),\n )\n .join('-'));\n\nexport default generateId;\n", "const brand: unique symbol = Symbol('CallOptions');\n\nclass CallOptions {\n readonly transferables: Transferable[] | undefined;\n readonly timeout: number | undefined;\n\n // Allows TypeScript to distinguish between an actual instance of this\n // class versus an object that looks structurally similar.\n // eslint-disable-next-line no-unused-private-class-members\n #brand = brand;\n\n constructor(options?: {\n transferables?: Transferable[] | undefined;\n timeout?: number | undefined;\n }) {\n this.transferables = options?.transferables;\n this.timeout = options?.timeout;\n }\n}\n\nexport default CallOptions;\n", "import generateId from './generateId.js';\nimport { deserializeError } from './errorSerialization.js';\nimport { formatMethodPath } from './methodPath.js';\nimport type {\n Message,\n RemoteProxy,\n Methods,\n MethodPath,\n CallMessage,\n Log,\n} from './types.js';\nimport CallOptions from './CallOptions.js';\nimport type Messenger from './messengers/Messenger.js';\nimport PenpalError from './PenpalError.js';\nimport { isReplyMessage } from './guards.js';\nimport namespace from './namespace.js';\n\ntype ReplyHandler = {\n methodPath: MethodPath;\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n timeoutId: ReturnType<typeof globalThis.setTimeout> | undefined;\n};\n\ntype ConnectedRemoteProxy<TMethods extends Methods> = {\n remoteProxy: RemoteProxy<TMethods>;\n destroy: () => void;\n};\n\nconst methodsToTreatAsNative = new Set(['apply', 'call', 'bind']);\n\nconst createRemoteProxy = (\n callback: (path: MethodPath, args: unknown[]) => Promise<unknown>,\n log: Log | undefined,\n path: MethodPath = [],\n): Methods => {\n return new Proxy(\n path.length\n ? () => {\n // Intentionally empty\n }\n : Object.create(null),\n {\n get(target, prop: string) {\n // If a promise is resolved with this proxy object, the JavaScript\n // runtime will look for a `then` property on this object to determine\n // if it should be treated as a promise (to support promise chaining).\n // If we don't return undefined here, the JavaScript runtime will treat\n // this object as a promise and attempt to call `then`, which will\n // then send a call message to the remote. This is not what we want.\n if (prop === 'then') {\n return;\n }\n\n // Because we're using a proxy and because Penpal supports developers\n // exposing nested methods, we have a predicament. If a developer\n // calls, for example, remote.auth.apply(), are they\n // attempting to call a nested apply() method that a developer has\n // explicitly exposed from the remote? Could they instead be attempting\n // to call Function.prototype.apply() on the remote.auth() method?\n // Without the remote telling the local Penpal which methods the\n // developer has exposed, it has no way of knowing (and the main reason\n // we use a proxy is so that Penpal doesn't have to communicate which\n // methods are exposed). So, we treat certain methods as native methods\n // and return the native method rather than a proxy. The downside of\n // this is that if a developer has explicitly exposed a nested method\n // with the same name as one of these native method names, the developer\n // will be unable to call the exposed remote method because they will\n // be calling the method on the Function prototype instead.\n if (path.length && methodsToTreatAsNative.has(prop)) {\n return Reflect.get(target, prop);\n }\n\n return createRemoteProxy(callback, log, [...path, prop]);\n },\n apply(target, _thisArg, args) {\n return callback(path, args);\n },\n },\n );\n};\n\nconst getDestroyedConnectionMethodCallError = (methodPath: MethodPath) => {\n return new PenpalError(\n 'CONNECTION_DESTROYED',\n `Method call ${formatMethodPath(\n methodPath,\n )}() failed due to destroyed connection`,\n );\n};\n\n/**\n * Creates a proxy. When methods are called on the proxy, a \"call\" message will\n * be sent to the remote, the remote's corresponding method will be\n * executed, and the method's return value will be returned via a message.\n */\nconst connectRemoteProxy = <TMethods extends Methods>(\n messenger: Messenger,\n channel: string | undefined,\n log: Log | undefined,\n): ConnectedRemoteProxy<TMethods> => {\n let isDestroyed = false;\n const replyHandlers = new Map<string, ReplyHandler>();\n\n const handleMessage = (message: Message) => {\n if (!isReplyMessage(message)) {\n return;\n }\n\n const { callId, value, isError, isSerializedErrorInstance } = message;\n const replyHandler = replyHandlers.get(callId);\n\n if (!replyHandler) {\n return;\n }\n\n replyHandlers.delete(callId);\n clearTimeout(replyHandler.timeoutId);\n log?.(\n `Received ${formatMethodPath(replyHandler.methodPath)}() reply`,\n message,\n );\n\n if (isError) {\n replyHandler.reject(\n isSerializedErrorInstance ? deserializeError(value) : value,\n );\n } else {\n replyHandler.resolve(value);\n }\n };\n\n messenger.addMessageHandler(handleMessage);\n\n const remoteProxy = createRemoteProxy((methodPath, args) => {\n if (isDestroyed) {\n throw getDestroyedConnectionMethodCallError(methodPath);\n }\n\n const callId = generateId();\n const lastArg = args[args.length - 1];\n const lastArgIsOptions = lastArg instanceof CallOptions;\n const { timeout, transferables } = lastArgIsOptions ? lastArg : {};\n const argsWithoutOptions = lastArgIsOptions ? args.slice(0, -1) : args;\n\n return new Promise((resolve, reject) => {\n // Use globalThis so this works in both window and worker contexts.\n const timeoutId =\n timeout !== undefined\n ? globalThis.setTimeout(() => {\n replyHandlers.delete(callId);\n reject(\n new PenpalError(\n 'METHOD_CALL_TIMEOUT',\n `Method call ${formatMethodPath(\n methodPath,\n )}() timed out after ${timeout}ms`,\n ),\n );\n }, timeout)\n : undefined;\n\n replyHandlers.set(callId, { methodPath, resolve, reject, timeoutId });\n\n try {\n const callMessage: CallMessage = {\n namespace,\n channel,\n type: 'CALL',\n id: callId,\n methodPath,\n args: argsWithoutOptions,\n };\n log?.(`Sending ${formatMethodPath(methodPath)}() call`, callMessage);\n messenger.sendMessage(callMessage, transferables);\n } catch (error) {\n replyHandlers.delete(callId);\n clearTimeout(timeoutId);\n reject(\n new PenpalError('TRANSMISSION_FAILED', (error as Error).message),\n );\n }\n });\n }, log) as RemoteProxy<TMethods>;\n\n const destroy = () => {\n isDestroyed = true;\n messenger.removeMessageHandler(handleMessage);\n\n for (const { methodPath, reject, timeoutId } of replyHandlers.values()) {\n clearTimeout(timeoutId);\n reject(getDestroyedConnectionMethodCallError(methodPath));\n }\n\n replyHandlers.clear();\n };\n\n return {\n remoteProxy,\n destroy,\n };\n};\n\nexport default connectRemoteProxy;\n", "import type Messenger from './messengers/Messenger.js';\nimport type {\n Ack2Message,\n Methods,\n Message,\n RemoteProxy,\n Ack1Message,\n SynMessage,\n Log,\n} from './types.js';\nimport PenpalError from './PenpalError.js';\nimport connectCallHandler from './connectCallHandler.js';\nimport connectRemoteProxy from './connectRemoteProxy.js';\nimport { isAck2Message, isAck1Message, isSynMessage } from './guards.js';\nimport generateId from './generateId.js';\nimport namespace from './namespace.js';\n\ntype Options = {\n messenger: Messenger;\n methods: Methods;\n timeout: number | undefined;\n channel: string | undefined;\n log: Log | undefined;\n};\n\ntype Handshake<TMethods extends Methods> = {\n promise: Promise<RemoteProxy<TMethods>>;\n destroy: () => void;\n};\n\n/**\n * Attempts to establish communication with the remote via a handshake protocol.\n * The handshake protocol fulfills a few requirements:\n *\n * 1. One participant in the handshake may not be available when the other\n * participant starts the handshake. For example, a document inside an iframe\n * may not be loaded when the parent window starts a handshake.\n * 2. While #1 could be solved by having the consumer of Penpal specify which\n * participant should initiate the handshake, we'd rather avoid this\n * unnecessary cognitive load.\n * 3. While #1 could be solved by having the consumer of Penpal specify which\n * participant is the \"parent\" or \"child\" and then having Penpal assume\n * the child should initiate the handshake, we'd rather avoid parent-child\n * terminology since Penpal can support communication between two\n * participants where neither would be considered a parent nor child. It may\n * also be too presumptive that the child should always initiate the\n * handshake.\n * 4. For robustness, each participant must know that the other participant is\n * receiving its messages for the handshake to be considered complete.\n * 5. The handshake should support a participant attempting to\n * re-establish the connection. This can occur, for example, if an end user\n * were to right-click within an iframe and click reload.\n * 6. The handshake should allow a Messenger to easily attach something to\n * a handshake message from one participant to the other unidirectionally\n * (rather than from both participants to each other).\n * This is important when a participant needs to be in charge of, for\n * example, creating a MessageChannel and sending one MessagePort from the\n * MessagePort pair to the other participant. If both participants attempted\n * to do this it could lead to confusion.\n * 7. The handshake ideally shouldn't require sending handshake messages on an\n * interval (retrying until the other participant is ready to receive them).\n * Intervals can increase compute resources if the interval is too short\n * or increase latency if the interval is too long. While we could make this\n * configurable, it's additional mental load for the consumer. Additionally,\n * setInterval and setTimeout are not available within some contexts\n * (like AudioWorklet), where a consumer may like to use Penpal.\n *\n * To accomplish these requirements, the handshake protocol is as follows:\n * 1. Each participant generates a random participant ID.\n * 2. As soon as possible, each participant sends a SYN message containing its\n * participant ID to the other participant.\n * 3. When the SYN messages were sent, one of the participants may not have\n * been ready to receive the SYN message from the other. At least one\n * of the participants was ready, however, and should have received a SYN\n * message from the other participant. Each participant that did receive\n * a SYN message knows for sure that the other participant is now ready\n * to receive a SYN message, so it will send another SYN message in case\n * the other participant did not receive the first SYN message. This\n * ultimately results in each participant sending two SYN messages.\n * 4. Each participant now should have received at least one SYN message from\n * the other participant. Each participant compares their own ID with the\n * other participant's ID. Whichever participant has the higher ID\n * (using a simple string comparison) is considered the handshake leader\n * and will send an ACK1 message to the other participant.\n * 5. At this point, the handshake leader does not know whether the other\n * participant is actually receiving messages. The participant receiving\n * the ACK1 message will respond with an ACK2, informing the handshake\n * leader that it is indeed receiving messages.\n * 6. At this point, both participants know the other is receiving messages\n * and the handshake is complete.\n */\nconst shakeHands = <TMethods extends Methods>({\n messenger,\n methods,\n timeout,\n channel,\n log,\n}: Options): Handshake<TMethods> => {\n const participantId = generateId();\n let remoteParticipantId: string;\n const destroyHandlers: (() => void)[] = [];\n let isComplete = false;\n let isDestroyed = false;\n\n const { promise, resolve, reject } =\n Promise.withResolvers<RemoteProxy<TMethods>>();\n\n const timeoutId =\n timeout !== undefined\n ? setTimeout(() => {\n rejectAndCleanUp(\n new PenpalError(\n 'CONNECTION_TIMEOUT',\n `Connection timed out after ${timeout}ms`,\n ),\n );\n }, timeout)\n : undefined;\n\n const cleanUp = () => {\n clearTimeout(timeoutId);\n\n for (const destroyHandler of destroyHandlers.splice(0)) {\n destroyHandler();\n }\n };\n\n const rejectAndCleanUp = (error: PenpalError) => {\n if (isDestroyed) {\n return;\n }\n\n isDestroyed = true;\n cleanUp();\n reject(error);\n };\n\n const destroy = () => {\n rejectAndCleanUp(\n new PenpalError('CONNECTION_DESTROYED', 'Connection destroyed'),\n );\n };\n\n const completeHandshake = () => {\n if (isDestroyed || isComplete) {\n // Ignore messages after cleanup, and keep completion idempotent when\n // the remote attempts to re-connect.\n return;\n }\n\n destroyHandlers.push(connectCallHandler(messenger, methods, channel, log));\n\n const { remoteProxy, destroy: destroyMethodProxies } =\n connectRemoteProxy<TMethods>(messenger, channel, log);\n\n destroyHandlers.push(destroyMethodProxies);\n\n clearTimeout(timeoutId);\n isComplete = true;\n\n resolve(remoteProxy);\n };\n\n const sendHandshakeMessage = (\n message: SynMessage | Ack1Message | Ack2Message,\n ) => {\n if (isDestroyed) {\n return false;\n }\n\n log?.(`Sending handshake ${message.type}`, message);\n\n try {\n messenger.sendMessage(message);\n return true;\n } catch (error) {\n rejectAndCleanUp(\n new PenpalError('TRANSMISSION_FAILED', (error as Error).message),\n );\n return false;\n }\n };\n\n const sendSynMessage = () => {\n const synMessage: SynMessage = {\n namespace,\n type: 'SYN',\n channel,\n participantId: participantId,\n };\n\n return sendHandshakeMessage(synMessage);\n };\n\n const handleSynMessage = (message: SynMessage) => {\n log?.(`Received handshake SYN`, message);\n\n if (message.participantId === remoteParticipantId) {\n return;\n }\n\n remoteParticipantId = message.participantId;\n\n // We send another SYN message in case the other participant was not ready\n // when we sent the first SYN message.\n if (!sendSynMessage()) {\n return;\n }\n\n const isHandshakeLeader = participantId > remoteParticipantId;\n\n if (!isHandshakeLeader) {\n return;\n }\n\n const ack1Message: Ack1Message = {\n namespace,\n channel,\n type: 'ACK1',\n };\n\n if (!sendHandshakeMessage(ack1Message)) {\n return;\n }\n };\n\n const handleAck1Message = (message: Ack1Message) => {\n log?.(`Received handshake ACK1`, message);\n const ack2Message: Ack2Message = {\n namespace,\n channel,\n type: 'ACK2',\n };\n\n if (sendHandshakeMessage(ack2Message)) {\n completeHandshake();\n }\n };\n\n const handleAck2Message = (message: Ack2Message) => {\n log?.(`Received handshake ACK2`, message);\n completeHandshake();\n };\n\n const handleMessage = (message: Message) => {\n if (isDestroyed) {\n return;\n }\n\n if (isSynMessage(message)) {\n handleSynMessage(message);\n } else if (isAck1Message(message)) {\n handleAck1Message(message);\n } else if (isAck2Message(message)) {\n handleAck2Message(message);\n }\n };\n\n messenger.addMessageHandler(handleMessage);\n destroyHandlers.push(() => messenger.removeMessageHandler(handleMessage));\n\n sendSynMessage();\n\n return {\n promise,\n destroy,\n };\n};\n\nexport default shakeHands;\n", "// eslint-disable-next-line typescript/no-explicit-any\nconst once = <T extends (...args: any[]) => any>(\n fn: T,\n): ((...args: Parameters<T>) => ReturnType<T>) => {\n let isCalled = false;\n let result: ReturnType<T>;\n\n return (...args: Parameters<T>): ReturnType<T> => {\n if (!isCalled) {\n isCalled = true;\n result = fn(...args);\n }\n return result;\n };\n};\n\nexport default once;\n", "import type {\n DestroyMessage,\n Connection,\n Log,\n Message,\n Methods,\n RemoteProxy,\n} from './types.js';\nimport PenpalError from './PenpalError.js';\nimport type Messenger from './messengers/Messenger.js';\nimport shakeHands from './shakeHands.js';\nimport { isDestroyMessage, isMessage } from './guards.js';\nimport once from './once.js';\nimport namespace from './namespace.js';\n\ntype Options = {\n /**\n * Messenger in charge of handling communication with the remote.\n */\n messenger: Messenger;\n /**\n * Methods that may be called by the remote.\n */\n methods?: Methods | undefined;\n /**\n * The amount of time, in milliseconds, Penpal should wait\n * for a connection to be established before rejecting the connection promise.\n */\n timeout?: number | undefined;\n /**\n * A string identifier that disambiguates communication when establishing\n * multiple, parallel connections between two participants (e.g., two windows,\n * a window and a worker).\n */\n channel?: string | undefined;\n /**\n * A function for logging debug messages. Debug messages will only be\n * logged when this is defined.\n */\n log?: Log | undefined;\n};\n\nconst usedMessengers = new WeakSet<Messenger>();\n\nconst getConnectionDestroyedError = () => {\n return new PenpalError('CONNECTION_DESTROYED', 'Connection destroyed');\n};\n\n/**\n * Attempts to establish communication with the remote.\n */\nconst connect = <TMethods extends Methods>({\n messenger,\n methods = {},\n timeout,\n channel,\n log,\n}: Options): Connection<TMethods> => {\n if (!messenger) {\n throw new PenpalError('INVALID_ARGUMENT', 'messenger must be defined');\n }\n\n if (usedMessengers.has(messenger)) {\n throw new PenpalError(\n 'INVALID_ARGUMENT',\n 'A messenger can only be used for a single connection',\n );\n }\n\n usedMessengers.add(messenger);\n\n const connectionPromise = Promise.withResolvers<RemoteProxy<TMethods>>();\n let isConnectionDestroyed = false;\n let destroyHandshake: (() => void) | undefined;\n\n const sendDestroyMessage = () => {\n const destroyMessage: DestroyMessage = {\n namespace,\n channel,\n type: 'DESTROY',\n };\n\n try {\n messenger.sendMessage(destroyMessage);\n } catch (_) {\n // We do our best to notify the other participant of the connection,\n // but if there's an error in doing so (e.g., maybe the handshake\n // hasn't completed and a messenger can't send the message), it's\n // probably not worth bothering the consumer with an error.\n }\n };\n\n const destroyConnection = once(\n (\n connectionPromiseRejection: PenpalError,\n notifyOtherParticipant: boolean,\n ) => {\n isConnectionDestroyed = true;\n\n if (notifyOtherParticipant) {\n sendDestroyMessage();\n }\n\n messenger.destroy();\n destroyHandshake?.();\n\n connectionPromise.reject(connectionPromiseRejection);\n\n log?.('Connection destroyed');\n },\n );\n\n const validateReceivedMessage = (data: unknown): data is Message => {\n return isMessage(data) && data.channel === channel;\n };\n\n void (async () => {\n try {\n messenger.initialize({ log, validateReceivedMessage });\n messenger.addMessageHandler((message) => {\n if (isDestroyMessage(message)) {\n destroyConnection(getConnectionDestroyedError(), false);\n }\n });\n\n const handshake = shakeHands<TMethods>({\n messenger,\n methods,\n timeout,\n channel,\n log,\n });\n\n destroyHandshake = handshake.destroy;\n\n const remoteProxy = await handshake.promise;\n\n if (isConnectionDestroyed) {\n handshake.destroy();\n return;\n }\n\n connectionPromise.resolve(remoteProxy);\n } catch (error) {\n destroyConnection(error as PenpalError, true);\n }\n })();\n\n return {\n promise: connectionPromise.promise,\n destroy: () => {\n destroyConnection(getConnectionDestroyedError(), true);\n },\n };\n};\n\nexport default connect;\n", "import type { Log, Message } from '../types.js';\nimport type Messenger from './Messenger.js';\nimport type {\n InitializeMessengerOptions,\n MessageHandler,\n} from './Messenger.js';\nimport { isAck2Message, isAck1Message, isSynMessage } from '../guards.js';\nimport PenpalError from '../PenpalError.js';\n\n// Keep this structural so generated .d.ts files don't reference the DOM-only\n// Window global. Penpal also supports worker-only projects, and those projects\n// should be able to type-check the package without adding \"dom\" to tsconfig libs\n// just because WindowMessenger is one of Penpal's exports.\ntype WindowLike = {\n postMessage: (\n message: unknown,\n options?: {\n targetOrigin?: string;\n transfer?: Transferable[];\n },\n ) => void;\n};\n\ntype Options = {\n /**\n * The window with which the current window will communicate.\n */\n remoteWindow: WindowLike;\n /**\n * An array of strings or regular expressions defining to which origins\n * communication will be allowed. If not provided, communication will be\n * restricted to the origin of the current page. You may specify an allowed\n * origin of `*` to not restrict communication, but beware the risks of\n * doing so.\n */\n allowedOrigins?: (string | RegExp)[] | undefined;\n};\n\n/**\n * Handles the details of communicating with a child window.\n */\nclass WindowMessenger implements Messenger {\n readonly #remoteWindow: WindowLike;\n readonly #allowedOrigins: [string | RegExp, ...(string | RegExp)[]];\n #log: Log | undefined;\n #validateReceivedMessage: ((data: unknown) => data is Message) | undefined;\n #concreteRemoteOrigin: string | undefined;\n #messageCallbacks = new Set<MessageHandler>();\n #port: MessagePort | undefined;\n\n constructor({ remoteWindow, allowedOrigins }: Options) {\n if (!remoteWindow) {\n throw new PenpalError('INVALID_ARGUMENT', 'remoteWindow must be defined');\n }\n\n this.#remoteWindow = remoteWindow;\n this.#allowedOrigins = allowedOrigins?.length\n ? (allowedOrigins as [string | RegExp, ...(string | RegExp)[]])\n : [window.origin];\n }\n\n initialize = ({\n log,\n validateReceivedMessage,\n }: InitializeMessengerOptions): void => {\n this.#log = log;\n this.#validateReceivedMessage = validateReceivedMessage;\n window.addEventListener('message', this.#handleMessageFromRemoteWindow);\n };\n\n sendMessage = (message: Message, transferables?: Transferable[]): void => {\n if (isSynMessage(message)) {\n const originForSending = this.#getOriginForSendingMessage(message);\n this.#remoteWindow.postMessage(message, {\n targetOrigin: originForSending,\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n if (isAck1Message(message)) {\n const originForSending = this.#getOriginForSendingMessage(message);\n this.#remoteWindow.postMessage(message, {\n targetOrigin: originForSending,\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n if (isAck2Message(message)) {\n const { port1, port2 } = new MessageChannel();\n this.#setPort(port1);\n const transferablesToSend = [port2, ...(transferables || [])];\n const originForSending = this.#getOriginForSendingMessage(message);\n\n try {\n this.#remoteWindow.postMessage(message, {\n targetOrigin: originForSending,\n transfer: transferablesToSend,\n });\n } catch (error) {\n this.#destroyPort();\n port2.close();\n throw error;\n }\n\n return;\n }\n\n if (this.#port) {\n this.#port.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n throw new PenpalError(\n 'TRANSMISSION_FAILED',\n 'Cannot send message because the MessagePort is not connected',\n );\n };\n\n addMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.add(callback);\n };\n\n removeMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.delete(callback);\n };\n\n destroy = (): void => {\n window.removeEventListener('message', this.#handleMessageFromRemoteWindow);\n this.#destroyPort();\n this.#messageCallbacks.clear();\n };\n\n #isAllowedOrigin = (origin: string) => {\n return this.#allowedOrigins.some((allowedOrigin) => {\n if (allowedOrigin instanceof RegExp) {\n // RegExp.test() mutates lastIndex for global and sticky expressions,\n // so test a copy to keep repeated handshake messages deterministic.\n return new RegExp(allowedOrigin).test(origin);\n }\n\n return allowedOrigin === origin || allowedOrigin === '*';\n });\n };\n\n #getOriginForSendingMessage = (message: Message) => {\n // It's safe to send the SYN message to any origin because it doesn't contain\n // anything sensitive. When Penpal receives a SYN message, the origin on\n // the message (which we call the concrete origin) is validated against the\n // configured allowed origins. All subsequent messages will be sent to the\n // concrete origin.\n // If you decide to change this, consider https://github.com/Aaronius/penpal/issues/103\n if (isSynMessage(message)) {\n return '*';\n }\n\n if (!this.#concreteRemoteOrigin) {\n throw new PenpalError(\n 'TRANSMISSION_FAILED',\n 'Cannot send message because the remote origin is not established',\n );\n }\n\n // If the concrete remote origin (the origin we received from the remote\n // on a prior message) is 'null', it means the remote is within\n // an \"opaque origin\". The only way to post a message to an\n // opaque origin is by using '*'. This does carry some security risk,\n // so we only do this if the consumer has specifically defined '*' as\n // an allowed origin. Opaque origins occur, for example, when\n // loading an HTML document directly from the filesystem (not a\n // web server) or through a data URI.\n return this.#concreteRemoteOrigin === 'null' &&\n this.#allowedOrigins.includes('*')\n ? '*'\n : this.#concreteRemoteOrigin;\n };\n\n #destroyPort = () => {\n this.#port?.removeEventListener('message', this.#handleMessageFromPort);\n this.#port?.close();\n this.#port = undefined;\n };\n\n #setPort = (port: MessagePort) => {\n this.#destroyPort();\n this.#port = port;\n this.#port.addEventListener('message', this.#handleMessageFromPort);\n this.#port.start();\n };\n\n #handleMessageFromRemoteWindow = ({\n source,\n origin,\n ports,\n data,\n }: MessageEvent): void => {\n if (source !== this.#remoteWindow) {\n return;\n }\n\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n if (!this.#isAllowedOrigin(origin)) {\n this.#log?.(\n `Received a message from origin \\`${origin}\\` which did not match ` +\n `allowed origins \\`[${this.#allowedOrigins.join(', ')}]\\``,\n );\n return;\n }\n\n if (isSynMessage(data)) {\n // If we receive a SYN message and already have a port, it means\n // the child is re-connecting, in which case we'll receive a new port.\n // For this reason, we always make sure we destroy the existing port.\n this.#destroyPort();\n this.#concreteRemoteOrigin = origin;\n }\n\n if (isAck2Message(data)) {\n const port = ports[0];\n\n if (!port) {\n this.#log?.('Ignoring ACK2 because it did not include a MessagePort');\n return;\n }\n\n this.#setPort(port);\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n\n #handleMessageFromPort = ({ data }: MessageEvent): void => {\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n}\n\nexport default WindowMessenger;\n", "import type { Log, Message } from '../types.js';\nimport type Messenger from './Messenger.js';\nimport type {\n InitializeMessengerOptions,\n MessageHandler,\n} from './Messenger.js';\nimport { isAck2Message, isAck1Message, isSynMessage } from '../guards.js';\nimport PenpalError from '../PenpalError.js';\n\n// Keep this structural so generated .d.ts files don't depend on\n// DedicatedWorkerGlobalScope being present in consumer tsconfig libs.\ntype WorkerLike = Pick<\n Worker,\n 'postMessage' | 'addEventListener' | 'removeEventListener'\n>;\n\ntype Options = {\n /**\n * The web worker receiving/sending communication from/to the parent window.\n * If this messenger is being used within the worker, `worker` should\n * typically be set to `self`.\n */\n worker: WorkerLike;\n};\n\n/**\n * Handles the details of communicating with a child web worker.\n */\nclass WorkerMessenger implements Messenger {\n #worker: WorkerLike;\n #log: Log | undefined;\n #validateReceivedMessage: ((data: unknown) => data is Message) | undefined;\n #messageCallbacks = new Set<MessageHandler>();\n #port: MessagePort | undefined;\n\n constructor({ worker }: Options) {\n if (!worker) {\n throw new PenpalError('INVALID_ARGUMENT', 'worker must be defined');\n }\n\n this.#worker = worker;\n }\n\n initialize = ({\n log,\n validateReceivedMessage,\n }: InitializeMessengerOptions): void => {\n this.#log = log;\n this.#validateReceivedMessage = validateReceivedMessage;\n this.#worker.addEventListener('message', this.#handleMessage);\n };\n\n sendMessage = (message: Message, transferables?: Transferable[]): void => {\n if (isSynMessage(message) || isAck1Message(message)) {\n this.#worker.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n if (isAck2Message(message)) {\n const { port1, port2 } = new MessageChannel();\n this.#setPort(port1);\n\n try {\n this.#worker.postMessage(message, {\n transfer: [port2, ...(transferables || [])],\n });\n } catch (error) {\n this.#destroyPort();\n port2.close();\n throw error;\n }\n\n return;\n }\n\n if (this.#port) {\n this.#port.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n return;\n }\n\n throw new PenpalError(\n 'TRANSMISSION_FAILED',\n 'Cannot send message because the MessagePort is not connected',\n );\n };\n\n addMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.add(callback);\n };\n\n removeMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.delete(callback);\n };\n\n destroy = (): void => {\n this.#worker.removeEventListener('message', this.#handleMessage);\n this.#destroyPort();\n this.#messageCallbacks.clear();\n };\n\n #destroyPort = () => {\n this.#port?.removeEventListener('message', this.#handleMessage);\n this.#port?.close();\n this.#port = undefined;\n };\n\n #setPort = (port: MessagePort) => {\n this.#destroyPort();\n this.#port = port;\n this.#port.addEventListener('message', this.#handleMessage);\n this.#port.start();\n };\n\n #handleMessage = ({ ports, data }: MessageEvent): void => {\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n if (isSynMessage(data)) {\n // If we receive a SYN message and already have a port, it means\n // the child is re-connecting, in which case we'll receive a new port.\n // For this reason, we always make sure we destroy the existing port.\n this.#destroyPort();\n }\n\n if (isAck2Message(data)) {\n const port = ports[0];\n\n if (!port) {\n this.#log?.('Ignoring ACK2 because it did not include a MessagePort');\n return;\n }\n\n this.#setPort(port);\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n}\n\nexport default WorkerMessenger;\n", "import type { Message } from '../types.js';\nimport type Messenger from './Messenger.js';\nimport type {\n InitializeMessengerOptions,\n MessageHandler,\n} from './Messenger.js';\nimport PenpalError from '../PenpalError.js';\n\ntype Options = {\n /**\n * The port used to communicate to the other port of the port pair.\n */\n port: MessagePort;\n};\n\n/**\n * Handles the details of communicating on a MessagePort.\n */\nclass PortMessenger implements Messenger {\n #port: MessagePort;\n #validateReceivedMessage: ((data: unknown) => data is Message) | undefined;\n #messageCallbacks = new Set<MessageHandler>();\n\n constructor({ port }: Options) {\n if (!port) {\n throw new PenpalError('INVALID_ARGUMENT', 'port must be defined');\n }\n\n this.#port = port;\n }\n\n initialize = ({\n validateReceivedMessage,\n }: InitializeMessengerOptions): void => {\n this.#validateReceivedMessage = validateReceivedMessage;\n this.#port.addEventListener('message', this.#handleMessage);\n this.#port.start();\n };\n\n sendMessage = (message: Message, transferables?: Transferable[]): void => {\n this.#port?.postMessage(message, {\n ...(transferables === undefined ? {} : { transfer: transferables }),\n });\n };\n\n addMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.add(callback);\n };\n\n removeMessageHandler = (callback: MessageHandler): void => {\n this.#messageCallbacks.delete(callback);\n };\n\n destroy = (): void => {\n this.#port.removeEventListener('message', this.#handleMessage);\n this.#port.close();\n this.#messageCallbacks.clear();\n };\n\n #handleMessage = ({ data }: MessageEvent): void => {\n if (!this.#validateReceivedMessage?.(data)) {\n return;\n }\n\n for (const callback of this.#messageCallbacks) {\n callback(data);\n }\n };\n}\n\nexport default PortMessenger;\n", "// Not intended to be used internally. Can be useful externally\n// in projects not using TypeScript. It has the `Obj` suffix to disambiguate\n// it from the ErrorCode string union.\nconst ErrorCodeObj = {\n ConnectionDestroyed: 'CONNECTION_DESTROYED',\n ConnectionTimeout: 'CONNECTION_TIMEOUT',\n InvalidArgument: 'INVALID_ARGUMENT',\n MethodCallTimeout: 'METHOD_CALL_TIMEOUT',\n MethodNotFound: 'METHOD_NOT_FOUND',\n TransmissionFailed: 'TRANSMISSION_FAILED',\n} as const;\n\nexport default ErrorCodeObj;\n", "import type { Log } from './types.js';\n\nconst debug = (prefix?: string): Log => {\n return (...args: unknown[]) => {\n console.log(`✍️ %c${prefix}%c`, 'font-weight: bold;', '', ...args);\n };\n};\n\nexport default debug;\n", "import {\n Graffiti,\n GraffitiErrorCursorExpired,\n GraffitiErrorForbidden,\n GraffitiErrorInvalidSchema,\n GraffitiErrorNotAcceptable,\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorTooLarge,\n type GraffitiLoginEvent,\n type GraffitiMedia,\n type GraffitiObjectStream,\n type GraffitiLogoutEvent,\n type GraffitiPostMedia,\n type GraffitiSession,\n} from \"@graffiti-garden/api\";\nimport {\n CallOptions,\n connect,\n type Connection,\n type RemoteProxy,\n WindowMessenger,\n} from \"penpal\";\nexport type * from \"@graffiti-garden/api\";\n\ntype MethodsOf<T> = {\n [K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: T[K];\n};\n\ntype SerializedMedia = Omit<GraffitiMedia, \"data\"> & {\n data: { buffer: ArrayBuffer; type: string };\n};\n\ntype SerializedPostMedia = Omit<GraffitiPostMedia, \"data\"> & {\n data: { buffer: ArrayBuffer; type: string };\n};\n\ntype RPCMethods = MethodsOf<Graffiti> & {\n getMedia: (...args: Parameters<Graffiti[\"getMedia\"]>) => Promise<SerializedMedia>;\n postMedia: (\n media: SerializedPostMedia,\n session: GraffitiSession,\n ) => Promise<string>;\n discover: (id: string, ...args: Parameters<Graffiti[\"discover\"]>) => void;\n continueDiscover: (\n id: string,\n ...args: Parameters<Graffiti[\"continueDiscover\"]>\n ) => void;\n streamNext: (id: string) => ReturnType<GraffitiObjectStream<{}>[\"next\"]>;\n streamReturn: (id: string) => void;\n initialize: () => void;\n destroy: () => void;\n};\n\nconst simpleMethods = [\n \"post\",\n \"get\",\n \"delete\",\n \"deleteMedia\",\n \"login\",\n \"logout\",\n \"actorToHandle\",\n \"handleToActor\",\n] as const;\n\nconst graffitiErrors = [\n GraffitiErrorCursorExpired,\n GraffitiErrorForbidden,\n GraffitiErrorInvalidSchema,\n GraffitiErrorNotAcceptable,\n GraffitiErrorNotFound,\n GraffitiErrorSchemaMismatch,\n GraffitiErrorTooLarge,\n] as const;\n\nlet instance: GraffitiRpcClient | undefined;\n\nexport interface GraffitiRpcClientOptions {\n remoteWindow: Window;\n allowedOrigins?: (string | RegExp)[];\n channel?: string;\n timeout?: number;\n}\n\n/** A Graffiti implementation whose operations run in another iframe. */\n// @ts-ignore Simple methods are attached programmatically in the constructor.\nexport class GraffitiRpcClient extends Graffiti {\n readonly sessionEvents = new EventTarget();\n\n private readonly connection!: Connection<RPCMethods>;\n private readonly remote_!: Promise<RemoteProxy<RPCMethods>>;\n private remoteClient?: RemoteProxy<RPCMethods>;\n private readonly sessions = new Map<string, GraffitiSession>();\n private initialized = false;\n private destroyed = false;\n\n constructor(options: GraffitiRpcClientOptions) {\n super();\n if (instance) {\n const existing = instance;\n window.setTimeout(() => existing.replayInitialization(), 0);\n return existing;\n }\n\n this.connection = connect<RPCMethods>({\n messenger: new WindowMessenger({\n remoteWindow: options.remoteWindow,\n // Sandboxed srcdoc frames have opaque origins.\n allowedOrigins: options.allowedOrigins ?? [\"*\"],\n }),\n channel: options.channel,\n timeout: options.timeout,\n methods: {\n sessionEvent: (type: string, detail: unknown) => {\n if (type === \"login\") {\n const login = detail as GraffitiLoginEvent[\"detail\"];\n if (!login.error) this.sessions.set(login.session.actor, login.session);\n } else if (type === \"logout\") {\n const logout = detail as GraffitiLogoutEvent[\"detail\"];\n if (!logout.error) this.sessions.delete(logout.actor);\n } else if (type === \"initialized\") {\n this.initialized = true;\n detail = undefined;\n }\n this.sessionEvents.dispatchEvent(new CustomEvent(type, { detail }));\n },\n },\n });\n this.remote_ = this.connection.promise.then((remote) => {\n const restored = restoreErrors(remote);\n this.remoteClient = restored;\n return restored;\n });\n window.setTimeout(() => {\n void this.remote_\n .then((remote) => remote.initialize())\n .catch(() => {\n // The iframe may be removed before the connection initializes.\n });\n }, 0);\n\n for (const method of simpleMethods) {\n (this as any)[method] = async (...args: unknown[]) => {\n const remote = await this.remote();\n const call = remote[method] as (...args: unknown[]) => unknown;\n return call(...clone(args));\n };\n }\n\n instance = this;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n if (instance === this) instance = undefined;\n void this.remoteClient?.destroy().catch(() => {});\n this.connection.destroy();\n }\n\n private replayInitialization() {\n if (this.destroyed) return;\n for (const session of this.sessions.values()) {\n this.sessionEvents.dispatchEvent(\n new CustomEvent(\"login\", { detail: { session } }),\n );\n }\n if (this.initialized) {\n this.sessionEvents.dispatchEvent(\n new CustomEvent(\"initialized\", { detail: undefined }),\n );\n }\n }\n\n getMedia: Graffiti[\"getMedia\"] = async (...args) => {\n const result = await (await this.remote()).getMedia(...clone(args));\n return {\n ...result,\n data: new Blob([result.data.buffer], { type: result.data.type }),\n };\n };\n\n postMedia: Graffiti[\"postMedia\"] = async (media, session) => {\n const buffer = await media.data.arrayBuffer();\n return (await this.remote()).postMedia(\n {\n ...clone(media),\n data: { buffer, type: media.data.type },\n },\n clone(session),\n new CallOptions({ transferables: [buffer] }),\n );\n };\n\n protected remoteStream(\n start: (remote: RemoteProxy<RPCMethods>, id: string) => Promise<void>,\n ): GraffitiObjectStream<{}> {\n const id = `graffiti-rpc-${Date.now().toString(36)}-${Math.random()\n .toString(36)\n .slice(2, 10)}`;\n const remote = this.remote_;\n\n return (async function* () {\n const rpc = await remote;\n try {\n await start(rpc, id);\n while (true) {\n const result = await rpc.streamNext(id);\n if (result.done) return result.value;\n yield result.value;\n }\n } finally {\n await rpc.streamReturn(id);\n }\n })();\n }\n\n // @ts-ignore Graffiti schema inference is preserved at the public boundary.\n discover: Graffiti[\"discover\"] = (...args) => {\n return this.remoteStream((remote, id) =>\n remote.discover(id, ...clone(args)),\n ) as any;\n };\n\n // @ts-ignore Graffiti schema inference is preserved at the public boundary.\n continueDiscover: Graffiti[\"continueDiscover\"] = (...args) => {\n return this.remoteStream((remote, id) =>\n remote.continueDiscover(id, ...clone(args)),\n ) as any;\n };\n\n private remote() {\n if (this.destroyed) throw new Error(\"Graffiti RPC client has been destroyed.\");\n return this.remote_;\n }\n}\n\nfunction clone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\nfunction restoreErrors(remote: RemoteProxy<RPCMethods>) {\n return new Proxy(remote, {\n get(target, property) {\n const method = Reflect.get(target, property) as unknown;\n return typeof method === \"function\"\n ? (...args: unknown[]) =>\n (method as (...args: unknown[]) => Promise<unknown>)(...args).catch(\n restoreError,\n )\n : method;\n },\n });\n}\n\nfunction restoreError(error: unknown): never {\n if (error instanceof Error) {\n const ErrorType = graffitiErrors.find(({ name }) => name === error.name);\n if (ErrorType) Object.setPrototypeOf(error, ErrorType.prototype);\n }\n throw error;\n}\n", "import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n height: \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100vw\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage(\n {\n type: \"graffiti-guard:connect\",\n pageUrl: window.location.href,\n },\n hostUrl.origin,\n );\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-storage-setup\" &&\n typeof event.data.url === \"string\"\n ) {\n try {\n const setupUrl = new URL(event.data.url);\n if (\n setupUrl.origin !== hostUrl.origin ||\n setupUrl.searchParams.get(\"guardStorageSetup\") !== \"1\"\n ) {\n return;\n }\n window.location.assign(setupUrl);\n } catch {}\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n loginUrl.searchParams.set(\"guardLogin\", \"1\");\n loginUrl.searchParams.set(\"redirectUrl\", window.location.href);\n if (actor) loginUrl.searchParams.set(\"suggestedActor\", actor);\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n auditUrl.searchParams.set(\"redirectUrl\", window.location.href);\n auditUrl.searchParams.set(\"source\", JSON.stringify(scope?.source ?? []));\n if (scope?.actor) auditUrl.searchParams.set(\"actor\", scope.actor);\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Graffiti", "GraffitiObjectJSONSchema", "GraffitiPostObjectJSONSchema", "GraffitiErrorForbidden", "_GraffitiErrorForbidden", "message", "GraffitiErrorNotFound", "_GraffitiErrorNotFound", "GraffitiErrorInvalidSchema", "_GraffitiErrorInvalidSchema", "GraffitiErrorSchemaMismatch", "_GraffitiErrorSchemaMismatch", "GraffitiErrorTooLarge", "_GraffitiErrorTooLarge", "GraffitiErrorNotAcceptable", "_GraffitiErrorNotAcceptable", "GraffitiErrorCursorExpired", "_GraffitiErrorCursorExpired", "PenpalError", "code", "message", "serializeError", "error", "deserializeError", "name", "stack", "penpalCode", "deserializedError", "brand", "Reply", "#brand", "value", "options", "namespace_default", "isObject", "isFunction", "isMessage", "data", "isSynMessage", "isAck1Message", "isAck2Message", "isCallMessage", "isReplyMessage", "isDestroyMessage", "getMethodAtMethodPath", "methodPath", "methods", "result", "acc", "pathSegment", "formatMethodPath", "createErrorReplyMessage", "channel", "callId", "connectCallHandler", "messenger", "log", "isDestroyed", "handleCallMessage", "args", "replyMessage", "transferables", "method", "handleMessage", "generateId", "CallOptions", "methodsToTreatAsNative", "createRemoteProxy", "callback", "path", "target", "prop", "_thisArg", "getDestroyedConnectionMethodCallError", "connectRemoteProxy", "replyHandlers", "isError", "isSerializedErrorInstance", "replyHandler", "lastArg", "lastArgIsOptions", "timeout", "argsWithoutOptions", "resolve", "reject", "timeoutId", "callMessage", "shakeHands", "participantId", "remoteParticipantId", "destroyHandlers", "isComplete", "promise", "rejectAndCleanUp", "cleanUp", "destroyHandler", "destroy", "completeHandshake", "remoteProxy", "destroyMethodProxies", "sendHandshakeMessage", "sendSynMessage", "handleSynMessage", "handleAck1Message", "handleAck2Message", "once", "fn", "isCalled", "usedMessengers", "getConnectionDestroyedError", "connect", "connectionPromise", "isConnectionDestroyed", "destroyHandshake", "sendDestroyMessage", "destroyMessage", "destroyConnection", "connectionPromiseRejection", "notifyOtherParticipant", "validateReceivedMessage", "handshake", "WindowMessenger", "#remoteWindow", "#allowedOrigins", "#log", "#validateReceivedMessage", "#concreteRemoteOrigin", "#messageCallbacks", "#port", "remoteWindow", "allowedOrigins", "#handleMessageFromRemoteWindow", "originForSending", "#getOriginForSendingMessage", "port1", "port2", "#setPort", "transferablesToSend", "#destroyPort", "#isAllowedOrigin", "origin", "allowedOrigin", "#handleMessageFromPort", "port", "source", "ports", "simpleMethods", "graffitiErrors", "GraffitiErrorCursorExpired", "GraffitiErrorForbidden", "GraffitiErrorInvalidSchema", "GraffitiErrorNotAcceptable", "GraffitiErrorNotFound", "GraffitiErrorSchemaMismatch", "GraffitiErrorTooLarge", "instance", "GraffitiRpcClient", "Graffiti", "options", "existing", "connect", "WindowMessenger", "type", "detail", "login", "logout", "remote", "restored", "restoreErrors", "method", "args", "call", "clone", "session", "result", "media", "buffer", "CallOptions", "start", "id", "rpc", "value", "target", "property", "restoreError", "error", "ErrorType", "name", "instance", "defaultConnectionTimeout", "GraffitiGuarded", "Graffiti", "options", "hostUrl", "iframe", "connected", "connectionErrorShown", "showConnectionError", "remoteWindow", "rpc", "GraffitiRpcClient", "connectionTimeout", "markConnected", "onMessage", "event", "setupUrl", "target", "property", "receiver", "value", "actor", "loginUrl", "scope", "auditUrl"]
7
7
  }
package/dist/cjs/index.js CHANGED
@@ -67,7 +67,13 @@ class GraffitiGuarded extends import_api.Graffiti {
67
67
  let connected = false;
68
68
  let connectionErrorShown = false;
69
69
  iframe.addEventListener("load", () => {
70
- iframe.contentWindow?.postMessage({ type: "graffiti-guard:connect" }, hostUrl.origin);
70
+ iframe.contentWindow?.postMessage(
71
+ {
72
+ type: "graffiti-guard:connect",
73
+ pageUrl: window.location.href
74
+ },
75
+ hostUrl.origin
76
+ );
71
77
  });
72
78
  const showConnectionError = () => {
73
79
  if (connected || connectionErrorShown) return;
@@ -114,6 +120,15 @@ class GraffitiGuarded extends import_api.Graffiti {
114
120
  hostUrl.origin
115
121
  );
116
122
  }
123
+ } else if (event.data.type === "graffiti-guard:open-storage-setup" && typeof event.data.url === "string") {
124
+ try {
125
+ const setupUrl = new URL(event.data.url);
126
+ if (setupUrl.origin !== hostUrl.origin || setupUrl.searchParams.get("guardStorageSetup") !== "1") {
127
+ return;
128
+ }
129
+ window.location.assign(setupUrl);
130
+ } catch {
131
+ }
117
132
  } else if (event.data.type === "graffiti-guard:open-audit" && typeof event.data.actor === "string" && Array.isArray(event.data.source)) {
118
133
  window.location.assign(this.auditUrl(event.data));
119
134
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n height: \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100vw\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage({ type: \"graffiti-guard:connect\" }, hostUrl.origin);\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n loginUrl.searchParams.set(\"guardLogin\", \"1\");\n loginUrl.searchParams.set(\"redirectUrl\", window.location.href);\n if (actor) loginUrl.searchParams.set(\"suggestedActor\", actor);\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n auditUrl.searchParams.set(\"redirectUrl\", window.location.href);\n auditUrl.searchParams.set(\"source\", JSON.stringify(scope?.source ?? []));\n if (scope?.actor) auditUrl.searchParams.set(\"actor\", scope.actor);\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAA+C;AAC/C,oBAAkC;AAIlC,IAAI;AACJ,MAAM,2BAA2B;AAmB1B,MAAM,wBAAwB,oBAAS;AAAA,EACnC;AAAA,EACQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY;AAAA,EAEpB,YAAY,UAAkC,CAAC,GAAG;AAChD,UAAM;AACN,QAAI,SAAU,QAAO;AAErB,UAAM,UAAU,IAAI;AAAA,MAClB,QAAQ,SAAS,SAAS,KAAK;AAAA,MAC/B,SAAS;AAAA,IACX;AACA,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,QAAQ;AACf,WAAO,QAAQ;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO,MAAM,QAAQ;AACrB,WAAO,aAAa,eAAe,MAAM;AACzC,WAAO,OAAO,OAAO,OAAO;AAAA,MAC1B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,YAAY;AAChB,QAAI,uBAAuB;AAC3B,WAAO,iBAAiB,QAAQ,MAAM;AAGpC,aAAO,eAAe,YAAY,EAAE,MAAM,yBAAyB,GAAG,QAAQ,MAAM;AAAA,IACtF,CAAC;AACD,UAAM,sBAAsB,MAAM;AAChC,UAAI,aAAa,qBAAsB;AACvC,6BAAuB;AACvB,aAAO;AAAA,QACL,qDAAqD,QAAQ,MAAM;AAAA,MAGrE;AAAA,IACF;AACA,WAAO,iBAAiB,SAAS,mBAAmB;AACpD,aAAS,KAAK,OAAO,MAAM;AAE3B,UAAM,eAAe,OAAO;AAC5B,QAAI,CAAC,cAAc;AACjB,aAAO,OAAO;AACd,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,UAAM,MAAM,IAAI,gCAAkB;AAAA,MAChC;AAAA,MACA,gBAAgB,CAAC,QAAQ,MAAM;AAAA,MAC/B,SAAS,QAAQ;AAAA,MACjB,SAAS,QAAQ;AAAA,IACnB,CAAC;AACD,UAAM,oBAAoB,OAAO;AAAA,MAC/B;AAAA,MACA,QAAQ,WAAW;AAAA,IACrB;AACA,UAAM,gBAAgB,MAAM;AAC1B,kBAAY;AACZ,aAAO,aAAa,iBAAiB;AAAA,IACvC;AACA,UAAM,YAAY,CAAC,UAAwB;AACzC,UACE,MAAM,WAAW,gBACjB,MAAM,WAAW,QAAQ,UACzB,CAAC,MAAM,QACP,OAAO,MAAM,SAAS,UACtB;AACA;AAAA,MACF;AACA,oBAAc;AACd,UACE,MAAM,KAAK,SAAS,4BACpB;AACA;AAAA,MACF,WACE,MAAM,KAAK,SAAS,gCACpB,OAAO,MAAM,KAAK,YAAY,WAC9B;AACA,eAAO,MAAM,UAAU,MAAM,KAAK,UAAU,UAAU;AACtD,eAAO,aAAa,eAAe,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC;AAC9D,YAAI,MAAM,KAAK,SAAS;AACtB,iBAAO,MAAM;AACb,uBAAa;AAAA,YACX,EAAE,MAAM,uBAAuB;AAAA,YAC/B,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF,WACE,MAAM,KAAK,SAAS,+BACpB,OAAO,MAAM,KAAK,UAAU,YAC5B,MAAM,QAAQ,MAAM,KAAK,MAAM,GAC/B;AACA,eAAO,SAAS,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAAA,MAClD;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,SAAS;AAE5C,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,gBAAgB,IAAI;AACzB,SAAK,YAAY;AACjB,SAAK,oBAAoB;AAEzB,eAAW,IAAI,MAAM,MAAM;AAAA,MACzB,IAAI,QAAQ,UAAU,UAAU;AAC9B,YAAI,QAAQ,IAAI,QAAQ,QAAQ,GAAG;AACjC,iBAAO,QAAQ,IAAI,QAAQ,UAAU,QAAQ;AAAA,QAC/C;AACA,cAAM,QAAQ,QAAQ,IAAI,KAAK,UAAU,GAAG;AAC5C,eAAO,OAAO,UAAU,aAAa,MAAM,KAAK,GAAG,IAAI;AAAA,MACzD;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,QAA2B,CAAC,UAAU;AACpC,UAAM,WAAW,IAAI,IAAI,KAAK,OAAO;AACrC,aAAS,aAAa,IAAI,cAAc,GAAG;AAC3C,aAAS,aAAa,IAAI,eAAe,OAAO,SAAS,IAAI;AAC7D,QAAI,MAAO,UAAS,aAAa,IAAI,kBAAkB,KAAK;AAC5D,WAAO,SAAS,OAAO,SAAS,IAAI;AACpC,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA;AAAA,EAGA,SAAS,OAGN;AACD,UAAM,WAAW,IAAI,IAAI,KAAK,OAAO;AACrC,aAAS,aAAa,IAAI,eAAe,OAAO,SAAS,IAAI;AAC7D,aAAS,aAAa,IAAI,UAAU,KAAK,UAAU,OAAO,UAAU,CAAC,CAAC,CAAC;AACvE,QAAI,OAAO,MAAO,UAAS,aAAa,IAAI,SAAS,MAAM,KAAK;AAChE,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,UAAU;AACR,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,WAAO,aAAa,KAAK,iBAAiB;AAC1C,WAAO,oBAAoB,WAAW,KAAK,SAAS;AACpD,SAAK,IAAI,QAAQ;AACjB,SAAK,OAAO,OAAO;AACnB,QAAI,aAAa,KAAM,YAAW;AAAA,EACpC;AACF;",
4
+ "sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n height: \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100vw\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage(\n {\n type: \"graffiti-guard:connect\",\n pageUrl: window.location.href,\n },\n hostUrl.origin,\n );\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-storage-setup\" &&\n typeof event.data.url === \"string\"\n ) {\n try {\n const setupUrl = new URL(event.data.url);\n if (\n setupUrl.origin !== hostUrl.origin ||\n setupUrl.searchParams.get(\"guardStorageSetup\") !== \"1\"\n ) {\n return;\n }\n window.location.assign(setupUrl);\n } catch {}\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n loginUrl.searchParams.set(\"guardLogin\", \"1\");\n loginUrl.searchParams.set(\"redirectUrl\", window.location.href);\n if (actor) loginUrl.searchParams.set(\"suggestedActor\", actor);\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n auditUrl.searchParams.set(\"redirectUrl\", window.location.href);\n auditUrl.searchParams.set(\"source\", JSON.stringify(scope?.source ?? []));\n if (scope?.actor) auditUrl.searchParams.set(\"actor\", scope.actor);\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/esm/index.js CHANGED
@@ -44,7 +44,13 @@ class GraffitiGuarded extends Graffiti {
44
44
  let connected = false;
45
45
  let connectionErrorShown = false;
46
46
  iframe.addEventListener("load", () => {
47
- iframe.contentWindow?.postMessage({ type: "graffiti-guard:connect" }, hostUrl.origin);
47
+ iframe.contentWindow?.postMessage(
48
+ {
49
+ type: "graffiti-guard:connect",
50
+ pageUrl: window.location.href
51
+ },
52
+ hostUrl.origin
53
+ );
48
54
  });
49
55
  const showConnectionError = () => {
50
56
  if (connected || connectionErrorShown) return;
@@ -91,6 +97,15 @@ class GraffitiGuarded extends Graffiti {
91
97
  hostUrl.origin
92
98
  );
93
99
  }
100
+ } else if (event.data.type === "graffiti-guard:open-storage-setup" && typeof event.data.url === "string") {
101
+ try {
102
+ const setupUrl = new URL(event.data.url);
103
+ if (setupUrl.origin !== hostUrl.origin || setupUrl.searchParams.get("guardStorageSetup") !== "1") {
104
+ return;
105
+ }
106
+ window.location.assign(setupUrl);
107
+ } catch {
108
+ }
94
109
  } else if (event.data.type === "graffiti-guard:open-audit" && typeof event.data.actor === "string" && Array.isArray(event.data.source)) {
95
110
  window.location.assign(this.auditUrl(event.data));
96
111
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n height: \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100vw\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage({ type: \"graffiti-guard:connect\" }, hostUrl.origin);\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n loginUrl.searchParams.set(\"guardLogin\", \"1\");\n loginUrl.searchParams.set(\"redirectUrl\", window.location.href);\n if (actor) loginUrl.searchParams.set(\"suggestedActor\", actor);\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n auditUrl.searchParams.set(\"redirectUrl\", window.location.href);\n auditUrl.searchParams.set(\"source\", JSON.stringify(scope?.source ?? []));\n if (scope?.actor) auditUrl.searchParams.set(\"actor\", scope.actor);\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,gBAAsC;AAC/C,SAAS,yBAAyB;AAIlC,IAAI;AACJ,MAAM,2BAA2B;AAmB1B,MAAM,wBAAwB,SAAS;AAAA,EACnC;AAAA,EACQ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY;AAAA,EAEpB,YAAY,UAAkC,CAAC,GAAG;AAChD,UAAM;AACN,QAAI,SAAU,QAAO;AAErB,UAAM,UAAU,IAAI;AAAA,MAClB,QAAQ,SAAS,SAAS,KAAK;AAAA,MAC/B,SAAS;AAAA,IACX;AACA,UAAM,SAAS,SAAS,cAAc,QAAQ;AAC9C,WAAO,QAAQ;AACf,WAAO,QAAQ;AACf,WAAO,QAAQ;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO,MAAM,QAAQ;AACrB,WAAO,aAAa,eAAe,MAAM;AACzC,WAAO,OAAO,OAAO,OAAO;AAAA,MAC1B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,UAAU;AAAA,MACV,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AACD,QAAI,YAAY;AAChB,QAAI,uBAAuB;AAC3B,WAAO,iBAAiB,QAAQ,MAAM;AAGpC,aAAO,eAAe,YAAY,EAAE,MAAM,yBAAyB,GAAG,QAAQ,MAAM;AAAA,IACtF,CAAC;AACD,UAAM,sBAAsB,MAAM;AAChC,UAAI,aAAa,qBAAsB;AACvC,6BAAuB;AACvB,aAAO;AAAA,QACL,qDAAqD,QAAQ,MAAM;AAAA,MAGrE;AAAA,IACF;AACA,WAAO,iBAAiB,SAAS,mBAAmB;AACpD,aAAS,KAAK,OAAO,MAAM;AAE3B,UAAM,eAAe,OAAO;AAC5B,QAAI,CAAC,cAAc;AACjB,aAAO,OAAO;AACd,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAEA,UAAM,MAAM,IAAI,kBAAkB;AAAA,MAChC;AAAA,MACA,gBAAgB,CAAC,QAAQ,MAAM;AAAA,MAC/B,SAAS,QAAQ;AAAA,MACjB,SAAS,QAAQ;AAAA,IACnB,CAAC;AACD,UAAM,oBAAoB,OAAO;AAAA,MAC/B;AAAA,MACA,QAAQ,WAAW;AAAA,IACrB;AACA,UAAM,gBAAgB,MAAM;AAC1B,kBAAY;AACZ,aAAO,aAAa,iBAAiB;AAAA,IACvC;AACA,UAAM,YAAY,CAAC,UAAwB;AACzC,UACE,MAAM,WAAW,gBACjB,MAAM,WAAW,QAAQ,UACzB,CAAC,MAAM,QACP,OAAO,MAAM,SAAS,UACtB;AACA;AAAA,MACF;AACA,oBAAc;AACd,UACE,MAAM,KAAK,SAAS,4BACpB;AACA;AAAA,MACF,WACE,MAAM,KAAK,SAAS,gCACpB,OAAO,MAAM,KAAK,YAAY,WAC9B;AACA,eAAO,MAAM,UAAU,MAAM,KAAK,UAAU,UAAU;AACtD,eAAO,aAAa,eAAe,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC;AAC9D,YAAI,MAAM,KAAK,SAAS;AACtB,iBAAO,MAAM;AACb,uBAAa;AAAA,YACX,EAAE,MAAM,uBAAuB;AAAA,YAC/B,QAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF,WACE,MAAM,KAAK,SAAS,+BACpB,OAAO,MAAM,KAAK,UAAU,YAC5B,MAAM,QAAQ,MAAM,KAAK,MAAM,GAC/B;AACA,eAAO,SAAS,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;AAAA,MAClD;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,SAAS;AAE5C,SAAK,UAAU;AACf,SAAK,SAAS;AACd,SAAK,MAAM;AACX,SAAK,gBAAgB,IAAI;AACzB,SAAK,YAAY;AACjB,SAAK,oBAAoB;AAEzB,eAAW,IAAI,MAAM,MAAM;AAAA,MACzB,IAAI,QAAQ,UAAU,UAAU;AAC9B,YAAI,QAAQ,IAAI,QAAQ,QAAQ,GAAG;AACjC,iBAAO,QAAQ,IAAI,QAAQ,UAAU,QAAQ;AAAA,QAC/C;AACA,cAAM,QAAQ,QAAQ,IAAI,KAAK,UAAU,GAAG;AAC5C,eAAO,OAAO,UAAU,aAAa,MAAM,KAAK,GAAG,IAAI;AAAA,MACzD;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,QAA2B,CAAC,UAAU;AACpC,UAAM,WAAW,IAAI,IAAI,KAAK,OAAO;AACrC,aAAS,aAAa,IAAI,cAAc,GAAG;AAC3C,aAAS,aAAa,IAAI,eAAe,OAAO,SAAS,IAAI;AAC7D,QAAI,MAAO,UAAS,aAAa,IAAI,kBAAkB,KAAK;AAC5D,WAAO,SAAS,OAAO,SAAS,IAAI;AACpC,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA;AAAA,EAGA,SAAS,OAGN;AACD,UAAM,WAAW,IAAI,IAAI,KAAK,OAAO;AACrC,aAAS,aAAa,IAAI,eAAe,OAAO,SAAS,IAAI;AAC7D,aAAS,aAAa,IAAI,UAAU,KAAK,UAAU,OAAO,UAAU,CAAC,CAAC,CAAC;AACvE,QAAI,OAAO,MAAO,UAAS,aAAa,IAAI,SAAS,MAAM,KAAK;AAChE,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,UAAU;AACR,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AACjB,WAAO,aAAa,KAAK,iBAAiB;AAC1C,WAAO,oBAAoB,WAAW,KAAK,SAAS;AACpD,SAAK,IAAI,QAAQ;AACjB,SAAK,OAAO,OAAO;AACnB,QAAI,aAAa,KAAM,YAAW;AAAA,EACpC;AACF;",
4
+ "sourcesContent": ["import { Graffiti, type GraffitiSession } from \"@graffiti-garden/api\";\nimport { GraffitiRpcClient } from \"@graffiti-garden/wrapper-iframe-rpc/client\";\n\ndeclare const DATA_GUARD_DEFAULT_HOST_URL: string;\n\nlet instance: GraffitiGuarded | undefined;\nconst defaultConnectionTimeout = 10_000;\n\nexport interface GraffitiGuardedOptions {\n hostUrl?: string | URL;\n channel?: string;\n timeout?: number;\n}\n\nexport interface GraffitiGuardSourceSegment {\n id: string;\n name: string;\n}\n\nexport type GraffitiGuardSession = GraffitiSession & {\n source?: GraffitiGuardSourceSegment[];\n};\n\n/** A Graffiti implementation backed by a sandboxed guard iframe. */\n// @ts-ignore Graffiti methods are delegated through the constructor proxy.\nexport class GraffitiGuarded extends Graffiti {\n readonly sessionEvents!: EventTarget;\n private readonly hostUrl!: URL;\n private readonly iframe!: HTMLIFrameElement;\n private readonly rpc!: GraffitiRpcClient;\n private readonly onMessage!: (event: MessageEvent) => void;\n private readonly connectionTimeout!: number;\n private destroyed = false;\n\n constructor(options: GraffitiGuardedOptions = {}) {\n super();\n if (instance) return instance;\n\n const hostUrl = new URL(\n options.hostUrl?.toString() ?? DATA_GUARD_DEFAULT_HOST_URL,\n document.baseURI,\n );\n const iframe = document.createElement(\"iframe\");\n iframe.title = \"Graffiti Guard\";\n iframe.allow = \"storage-access\";\n iframe.sandbox.add(\n \"allow-scripts\",\n \"allow-same-origin\",\n \"allow-storage-access-by-user-activation\",\n \"allow-downloads\",\n \"allow-forms\",\n \"allow-popups\",\n \"allow-top-navigation-by-user-activation\",\n );\n iframe.src = hostUrl.href;\n iframe.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(iframe.style, {\n background: \"transparent\",\n border: \"0\",\n display: \"none\",\n height: \"100vh\",\n inset: \"0\",\n position: \"fixed\",\n width: \"100vw\",\n zIndex: \"2147483647\",\n });\n let connected = false;\n let connectionErrorShown = false;\n iframe.addEventListener(\"load\", () => {\n // Bootstrap the guard with the browser-reported embedding origin, which\n // the RPC handshake verifies internally but does not expose to the host.\n iframe.contentWindow?.postMessage(\n {\n type: \"graffiti-guard:connect\",\n pageUrl: window.location.href,\n },\n hostUrl.origin,\n );\n });\n const showConnectionError = () => {\n if (connected || connectionErrorShown) return;\n connectionErrorShown = true;\n window.alert(\n `This app could not connect to its data service at ${hostUrl.origin}. ` +\n \"The service may be offline, or your browser may be blocking its security certificate. \" +\n \"Open that address directly, then reload this page.\",\n );\n };\n iframe.addEventListener(\"error\", showConnectionError);\n document.body.append(iframe);\n\n const remoteWindow = iframe.contentWindow;\n if (!remoteWindow) {\n iframe.remove();\n throw new Error(\"Graffiti Guard iframe did not create a content window.\");\n }\n\n const rpc = new GraffitiRpcClient({\n remoteWindow,\n allowedOrigins: [hostUrl.origin],\n channel: options.channel,\n timeout: options.timeout,\n });\n const connectionTimeout = window.setTimeout(\n showConnectionError,\n options.timeout ?? defaultConnectionTimeout,\n );\n const markConnected = () => {\n connected = true;\n window.clearTimeout(connectionTimeout);\n };\n const onMessage = (event: MessageEvent) => {\n if (\n event.source !== remoteWindow ||\n event.origin !== hostUrl.origin ||\n !event.data ||\n typeof event.data !== \"object\"\n ) {\n return;\n }\n markConnected();\n if (\n event.data.type === \"graffiti-guard:connected\"\n ) {\n return;\n } else if (\n event.data.type === \"graffiti-guard:set-visible\" &&\n typeof event.data.visible === \"boolean\"\n ) {\n iframe.style.display = event.data.visible ? \"block\" : \"none\";\n iframe.setAttribute(\"aria-hidden\", String(!event.data.visible));\n if (event.data.visible) {\n iframe.focus();\n remoteWindow.postMessage(\n { type: \"graffiti-guard:shown\" },\n hostUrl.origin,\n );\n }\n } else if (\n event.data.type === \"graffiti-guard:open-storage-setup\" &&\n typeof event.data.url === \"string\"\n ) {\n try {\n const setupUrl = new URL(event.data.url);\n if (\n setupUrl.origin !== hostUrl.origin ||\n setupUrl.searchParams.get(\"guardStorageSetup\") !== \"1\"\n ) {\n return;\n }\n window.location.assign(setupUrl);\n } catch {}\n } else if (\n event.data.type === \"graffiti-guard:open-audit\" &&\n typeof event.data.actor === \"string\" &&\n Array.isArray(event.data.source)\n ) {\n window.location.assign(this.auditUrl(event.data));\n }\n };\n window.addEventListener(\"message\", onMessage);\n\n this.hostUrl = hostUrl;\n this.iframe = iframe;\n this.rpc = rpc;\n this.sessionEvents = rpc.sessionEvents;\n this.onMessage = onMessage;\n this.connectionTimeout = connectionTimeout;\n\n instance = new Proxy(this, {\n get(target, property, receiver) {\n if (Reflect.has(target, property)) {\n return Reflect.get(target, property, receiver);\n }\n const value = Reflect.get(rpc, property, rpc);\n return typeof value === \"function\" ? value.bind(rpc) : value;\n },\n });\n return instance;\n }\n\n login: Graffiti[\"login\"] = (actor) => {\n const loginUrl = new URL(this.hostUrl);\n loginUrl.searchParams.set(\"guardLogin\", \"1\");\n loginUrl.searchParams.set(\"redirectUrl\", window.location.href);\n if (actor) loginUrl.searchParams.set(\"suggestedActor\", actor);\n window.location.assign(loginUrl.href);\n return Promise.resolve();\n };\n\n /** Link to the host-owned audit panel without exposing audit data to this app. */\n auditUrl(scope?: {\n actor?: string;\n source?: GraffitiGuardSourceSegment[];\n }) {\n const auditUrl = new URL(this.hostUrl);\n auditUrl.searchParams.set(\"redirectUrl\", window.location.href);\n auditUrl.searchParams.set(\"source\", JSON.stringify(scope?.source ?? []));\n if (scope?.actor) auditUrl.searchParams.set(\"actor\", scope.actor);\n return auditUrl.href;\n }\n\n destroy() {\n if (this.destroyed) return;\n this.destroyed = true;\n window.clearTimeout(this.connectionTimeout);\n window.removeEventListener(\"message\", this.onMessage);\n this.rpc.destroy();\n this.iframe.remove();\n if (instance === this) instance = undefined;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQtE,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IACnD,MAAM,CAAC,EAAE,0BAA0B,EAAE,CAAC;CACvC,CAAC;AAEF,oEAAoE;AAEpE,qBAAa,eAAgB,SAAQ,QAAQ;IAC3C,QAAQ,CAAC,aAAa,EAAG,WAAW,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAO;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,OAAO,GAAE,sBAA2B,EA6H/C;IAED,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAOtB;IAEF,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,0BAA0B,EAAE,CAAC;KACvC,UAMA;IAED,OAAO,SAQN;CACF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQtE,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IACnD,MAAM,CAAC,EAAE,0BAA0B,EAAE,CAAC;CACvC,CAAC;AAEF,oEAAoE;AAEpE,qBAAa,eAAgB,SAAQ,QAAQ;IAC3C,QAAQ,CAAC,aAAa,EAAG,WAAW,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAO;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiC;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,SAAS,CAAS;IAE1B,YAAY,OAAO,GAAE,sBAA2B,EAiJ/C;IAED,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAOtB;IAEF,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,0BAA0B,EAAE,CAAC;KACvC,UAMA;IAED,OAAO,SAQN;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graffiti-garden/wrapper-data-guard",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Ask for permission before a site accesses Graffiti",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/esm/index.js",
package/src/index.ts CHANGED
@@ -69,7 +69,13 @@ export class GraffitiGuarded extends Graffiti {
69
69
  iframe.addEventListener("load", () => {
70
70
  // Bootstrap the guard with the browser-reported embedding origin, which
71
71
  // the RPC handshake verifies internally but does not expose to the host.
72
- iframe.contentWindow?.postMessage({ type: "graffiti-guard:connect" }, hostUrl.origin);
72
+ iframe.contentWindow?.postMessage(
73
+ {
74
+ type: "graffiti-guard:connect",
75
+ pageUrl: window.location.href,
76
+ },
77
+ hostUrl.origin,
78
+ );
73
79
  });
74
80
  const showConnectionError = () => {
75
81
  if (connected || connectionErrorShown) return;
@@ -130,6 +136,20 @@ export class GraffitiGuarded extends Graffiti {
130
136
  hostUrl.origin,
131
137
  );
132
138
  }
139
+ } else if (
140
+ event.data.type === "graffiti-guard:open-storage-setup" &&
141
+ typeof event.data.url === "string"
142
+ ) {
143
+ try {
144
+ const setupUrl = new URL(event.data.url);
145
+ if (
146
+ setupUrl.origin !== hostUrl.origin ||
147
+ setupUrl.searchParams.get("guardStorageSetup") !== "1"
148
+ ) {
149
+ return;
150
+ }
151
+ window.location.assign(setupUrl);
152
+ } catch {}
133
153
  } else if (
134
154
  event.data.type === "graffiti-guard:open-audit" &&
135
155
  typeof event.data.actor === "string" &&