@sanity/comlink
Advanced tools
Comparing version 1.0.0 to 1.0.1-canary.0
@@ -1,1 +0,979 @@ | ||
import{v4 as e}from"uuid";import{fromEventObservable as t,setup as s,sendTo as n,assign as a,fromCallback as r,enqueueActions as o,assertEvent as i,raise as c,stopChild as d,createActor as u,emit as p}from"xstate";import{defer as l,fromEvent as g,map as f,pipe as m,filter as h,bufferCount as b,concatMap as y,take as v,EMPTY as q,takeUntil as k}from"rxjs";const x=e=>({context:t})=>{const{count:s,include:n,exclude:a,responseType:r="message.received"}=e;return{count:s,domain:t.domain,from:t.connectTo,include:n?Array.isArray(n)?n:[n]:[],exclude:a?Array.isArray(a)?a:[a]:[],responseType:r,target:t.target,to:t.name}},T=l((()=>g(window,"message"))),E=e=>t((({input:t})=>{return T.pipe(e?f(e):m(),h((e=>t=>{const{data:s}=t;return(!e.include.length||e.include.includes(s.type))&&(!e.exclude.length||!e.exclude.includes(s.type))&&s.domain===e.domain&&s.from===e.from&&s.to===e.to&&(!e.target||t.source===e.target)})(t)),f((s=t.responseType,e=>({type:s,message:e}))),t.count?m(b(t.count),y((e=>e)),v(t.count)):m());var s})),I="sanity/comlink",w=1e4,S=1e3,R=500,O="comlink/response",$="comlink/heartbeat",A="comlink/disconnect",B="comlink/handshake/syn",M="comlink/handshake/syn-ack",j="comlink/handshake/ack",_=[B,M,j],z=[O,A,$,..._],D=()=>s({types:{},actors:{listen:t((({input:e})=>{const t=e.signal?g(e.signal,"abort").pipe((s=`Request ${e.requestId} aborted`,e=>e.pipe(v(1),f((()=>{throw new Error(s)}))))):q;var s;return g(window,"message").pipe(h((t=>t.data?.type===O&&t.data?.responseTo===e.requestId&&!!t.source&&e.sources.has(t.source))),v(e.sources.size),k(t))}))},actions:{"send message":({context:e},t)=>{const{sources:s,targetOrigin:n}=e,{message:a}=t;s.forEach((e=>{e.postMessage(a,{targetOrigin:n})}))},"on success":n((({context:e})=>e.parentRef),(({context:e,self:t})=>(e.response&&e.resolvable?.resolve(e.response),{type:"request.success",requestId:t.id,response:e.response,responseTo:e.responseTo}))),"on fail":n((({context:e})=>e.parentRef),(({context:e,self:t})=>(console.warn(`Received no response to message '${e.type}' on client '${e.from}' (ID: '${e.id}').`),e.resolvable?.reject(new Error("No response received")),{type:"request.failed",requestId:t.id}))),"on abort":n((({context:e})=>e.parentRef),(({context:e,self:t})=>(e.resolvable?.reject(new Error("Request aborted")),{type:"request.aborted",requestId:t.id})))},guards:{expectsResponse:({context:e})=>e.expectResponse},delays:{initialTimeout:0,responseTimeout:1e4}}).createMachine({context:({input:t})=>({connectionId:t.connectionId,data:t.data,domain:t.domain,expectResponse:t.expectResponse??!1,from:t.from,id:`msg-${e()}`,parentRef:t.parentRef,resolvable:t.resolvable,response:null,responseTo:t.responseTo,signal:t.signal,sources:t.sources instanceof Set?t.sources:new Set([t.sources]),targetOrigin:t.targetOrigin,to:t.to,type:t.type}),initial:"idle",on:{abort:".aborted"},states:{idle:{after:{initialTimeout:[{target:"sending"}]}},sending:{entry:{type:"send message",params:({context:e})=>{const{connectionId:t,data:s,domain:n,from:a,id:r,responseTo:o,to:i,type:c}=e;return{message:{connectionId:t,data:s,domain:n,from:a,id:r,to:i,type:c,responseTo:o}}}},always:[{guard:"expectsResponse",target:"awaiting"},"success"]},awaiting:{invoke:{id:"listen for response",src:"listen",input:({context:e})=>({requestId:e.id,sources:e.sources,signal:e.signal}),onError:"aborted"},after:{responseTimeout:"failed"},on:{message:{actions:a({response:({event:e})=>e.data.data,responseTo:({event:e})=>e.data.responseTo}),target:"success"}}},failed:{type:"final",entry:"on fail"},success:{type:"final",entry:"on success"},aborted:{type:"final",entry:"on abort"}},output:({context:e,self:t})=>({requestId:t.id,response:e.response,responseTo:e.responseTo})}),C=r((({sendBack:e,input:t})=>{const s=()=>{e(t.event)};t.immediate&&s();const n=setInterval(s,t.interval);return()=>{clearInterval(n)}})),N=()=>s({types:{},actors:{requestMachine:D(),listen:E(),sendBackAtInterval:C},actions:{"buffer message":o((({enqueue:e})=>{e.assign({buffer:({event:e,context:t})=>(i(e,"post"),[...t.buffer,e.data])}),e.emit((({event:e})=>(i(e,"post"),{type:"_buffer.added",message:e.data})))})),"create request":a({requests:({context:t,event:s,self:n,spawn:a})=>{i(s,"request");const r=(Array.isArray(s.data)?s.data:[s.data]).map((s=>{const r=`req-${e()}`;return a("requestMachine",{id:r,input:{connectionId:t.connectionId,data:s.data,domain:t.domain,expectResponse:s.expectResponse,from:t.name,parentRef:n,responseTo:s.responseTo,sources:t.target,targetOrigin:t.targetOrigin,to:t.connectTo,type:s.type}})}));return[...t.requests,...r]}}),"emit received message":o((({enqueue:e})=>{e.emit((({event:e})=>(i(e,"message.received"),{type:"_message",message:e.message.data}))),e.emit((({event:e})=>(i(e,"message.received"),{type:e.message.data.type,message:e.message.data})))})),"flush buffer":o((({enqueue:e})=>{e.raise((({context:e})=>({type:"request",data:e.buffer.map((({data:e,type:t})=>({data:e,type:t})))}))),e.emit((({context:e})=>({type:"_buffer.flushed",messages:e.buffer}))),e.assign({buffer:[]})})),post:c((({event:e})=>(i(e,"post"),{type:"request",data:{data:e.data.data,expectResponse:!0,type:e.data.type}}))),"remove request":o((({context:e,enqueue:t,event:s})=>{i(s,["request.success","request.failed","request.aborted"]),d(s.requestId),t.assign({requests:e.requests.filter((({id:e})=>e!==s.requestId))})})),respond:c((({event:e})=>(i(e,"response"),{type:"request",data:{data:e.data,type:O,responseTo:e.respondTo}}))),"send handshake ack":c({type:"request",data:{type:j}}),"send disconnect":c((()=>({type:"request",data:{type:A}}))),"send handshake syn":c({type:"request",data:{type:B}}),"set target":a({target:({event:e})=>(i(e,"target.set"),e.target)})},guards:{"has target":({context:e})=>!!e.target,"should send heartbeats":({context:e})=>e.heartbeat}}).createMachine({id:"channel",context:({input:t})=>({id:t.id||`${t.name}-${e()}`,buffer:[],connectionId:`cnx-${e()}`,connectTo:t.connectTo,domain:t.domain??I,heartbeat:t.heartbeat??!1,name:t.name,requests:[],target:t.target,targetOrigin:t.targetOrigin}),on:{"target.set":{actions:"set target"},"request.success":{actions:"remove request"},"request.failed":{actions:"remove request"}},initial:"idle",states:{idle:{on:{connect:{target:"handshaking",guard:"has target"},post:{actions:"buffer message"}}},handshaking:{id:"handshaking",invoke:[{id:"send syn",src:"sendBackAtInterval",input:()=>({event:{type:"syn"},interval:500,immediate:!0})},{id:"listen for handshake",src:"listen",input:e=>x({include:M,count:1})(e)}],on:{syn:{actions:"send handshake syn"},request:{actions:"create request"},post:{actions:"buffer message"},"message.received":{target:"connected"},disconnect:{target:"disconnected"}},exit:"send handshake ack"},connected:{entry:"flush buffer",invoke:{id:"listen for messages",src:"listen",input:x({exclude:[O,$]})},on:{post:{actions:"post"},request:{actions:"create request"},response:{actions:"respond"},"message.received":{actions:"emit received message"},disconnect:{target:"disconnected"}},initial:"heartbeat",states:{heartbeat:{initial:"checking",states:{checking:{always:{guard:"should send heartbeats",target:"sending"}},sending:{on:{"request.failed":{target:"#handshaking"}},invoke:{id:"send heartbeat",src:"sendBackAtInterval",input:()=>({event:{type:"post",data:{type:$,data:void 0}},interval:2e3,immediate:!1})}}}}}},disconnected:{id:"disconnected",entry:"send disconnect",on:{request:{actions:"create request"},post:{actions:"buffer message"},connect:{target:"handshaking",guard:"has target"}}}}}),P=(t,s=N())=>{const n=t.id||`${t.name}-${e()}`,a=u(s,{input:{...t,id:n}}),r=()=>{a.stop()};return{actor:a,connect:()=>{a.send({type:"connect"})},disconnect:()=>{a.send({type:"disconnect"})},id:n,name:t.name,machine:s,on:(e,t)=>{const{unsubscribe:s}=a.on(e,(async e=>{const s=await t(e.message.data);s&&a.send({type:"response",respondTo:e.message.id,data:s})}));return s},onStatus:e=>{const t=a.getSnapshot();let s="string"==typeof t.value?t.value:Object.keys(t.value)[0];const{unsubscribe:n}=a.subscribe((t=>{const n="string"==typeof t.value?t.value:Object.keys(t.value)[0];s!==n&&(s=n,e(n))}));return n},post:e=>{a.send({type:"post",data:e})},setTarget:e=>{a.send({type:"target.set",target:e})},start:()=>(a.start(),r),stop:r,get target(){return a.getSnapshot().context.target}}},F=()=>{},G=e=>{const{targetOrigin:t}=e,s=new Set,n=new Set,a=e=>{e.disconnect(),setTimeout((()=>{e.stop()}),0)};return{addTarget:e=>{if(s.has(e))return F;if(!s.size||!n.size)return s.add(e),n.forEach((t=>{t.channels.forEach((t=>{t.setTarget(e),t.connect()}))})),()=>{s.delete(e),n.forEach((t=>{t.channels.forEach((t=>{t.target===e&&t.disconnect()}))}))};s.add(e);const r=new Set;return n.forEach((s=>{const n=P({...s.input,target:e,targetOrigin:t},s.machine);r.add(n),s.channels.add(n),s.subscribers.forEach((({type:e,handler:t,unsubscribers:s})=>{s.push(n.on(e,t))})),s.internalEventSubscribers.forEach((({type:e,handler:t,unsubscribers:s})=>{s.push(n.actor.on(e,t).unsubscribe)})),s.statusSubscribers.forEach((({handler:e,unsubscribers:t})=>{t.push(n.onStatus((t=>e({channel:n.id,status:t}))))})),n.start(),n.connect()})),()=>{s.delete(e),r.forEach((e=>{a(e),n.forEach((t=>{t.channels.delete(e)}))}))}},createConnection:(e,a=N())=>{const r={channels:new Set,input:e,internalEventSubscribers:new Set,machine:a,statusSubscribers:new Set,subscribers:new Set};n.add(r);const{channels:o,internalEventSubscribers:i,statusSubscribers:c,subscribers:d}=r;if(s.size)s.forEach((s=>{const n=P({...e,target:s,targetOrigin:t},a);o.add(n)}));else{const s=P({...e,targetOrigin:t},a);o.add(s)}const u=()=>{o.forEach((e=>{e.disconnect(),e.stop()}))};return{on:(e,t)=>{const s=[];o.forEach((n=>{s.push(n.on(e,t))}));const n={type:e,handler:t,unsubscribers:s};return d.add(n),()=>{s.forEach((e=>e())),d.delete(n)}},onInternalEvent:(e,t)=>{const s=[];o.forEach((n=>{s.push(n.actor.on(e,t).unsubscribe)}));const n={type:e,handler:t,unsubscribers:s};return i.add(n),()=>{s.forEach((e=>e())),i.delete(n)}},onStatus:e=>{const t=[];o.forEach((s=>{t.push(s.onStatus((t=>e({channel:s.id,status:t}))))}));const s={handler:e,unsubscribers:t};return c.add(s),()=>{t.forEach((e=>e())),c.delete(s)}},post:e=>{o.forEach((t=>{t.post(e)}))},start:()=>(o.forEach((e=>{e.start(),e.connect()})),u),stop:u}},destroy:()=>{n.forEach((({channels:e})=>{e.forEach((t=>{a(t),e.delete(t)}))}))}}},H=()=>s({types:{},actors:{requestMachine:D(),listen:E()},actions:{"buffer incoming message":a({handshakeBuffer:({event:e,context:t})=>(i(e,"message.received"),[...t.handshakeBuffer,e])}),"buffer message":o((({enqueue:e})=>{e.assign({buffer:({event:e,context:t})=>(i(e,"post"),[...t.buffer,{data:e.data,resolvable:e.resolvable,signal:e.signal}])}),e.emit((({event:e})=>(i(e,"post"),{type:"_buffer.added",message:e.data})))})),"create request":a({requests:({context:t,event:s,self:n,spawn:a})=>{i(s,"request");const r=(Array.isArray(s.data)?s.data:[s.data]).map((s=>{const r=`req-${e()}`;return a("requestMachine",{id:r,input:{connectionId:t.connectionId,data:s.data,domain:t.domain,expectResponse:s.expectResponse,from:t.name,parentRef:n,resolvable:s.resolvable,responseTo:s.responseTo,sources:t.target,targetOrigin:t.targetOrigin,to:t.connectTo,type:s.type,signal:s.signal}})}));return[...t.requests,...r]}}),"emit heartbeat":p((()=>({type:"_heartbeat"}))),"emit received message":o((({enqueue:e})=>{e.emit((({event:e})=>(i(e,"message.received"),{type:"_message",message:e.message.data}))),e.emit((({event:e})=>(i(e,"message.received"),{type:e.message.data.type,message:e.message.data})))})),"flush buffer":o((({enqueue:e})=>{e.raise((({context:e})=>({type:"request",data:e.buffer.map((({data:e,resolvable:t,signal:s})=>({data:e.data,type:e.type,expectResponse:!!t,resolvable:t,signal:s})))}))),e.emit((({context:e})=>({type:"_buffer.flushed",messages:e.buffer.map((({data:e})=>e))}))),e.assign({buffer:[]})})),"flush handshake buffer":o((({context:e,enqueue:t})=>{e.handshakeBuffer.forEach((e=>t.raise(e))),t.assign({handshakeBuffer:[]})})),post:c((({event:e})=>(i(e,"post"),{type:"request",data:{data:e.data.data,expectResponse:!!e.resolvable,type:e.data.type,resolvable:e.resolvable,signal:e.signal}}))),"remove request":o((({context:e,enqueue:t,event:s})=>{i(s,["request.success","request.failed","request.aborted"]),d(s.requestId),t.assign({requests:e.requests.filter((({id:e})=>e!==s.requestId))})})),"send response":c((({event:e})=>(i(e,["message.received","heartbeat.received"]),{type:"request",data:{type:O,responseTo:e.message.data.id,data:void 0}}))),"send handshake syn ack":c({type:"request",data:{type:M}}),"set connection config":a({connectionId:({event:e})=>(i(e,"message.received"),e.message.data.connectionId),target:({event:e})=>(i(e,"message.received"),e.message.source||void 0),targetOrigin:({event:e})=>(i(e,"message.received"),e.message.origin)})},guards:{hasSource:({context:e})=>null!==e.target}}).createMachine({id:"node",context:({input:e})=>({buffer:[],connectionId:null,connectTo:e.connectTo,domain:e.domain??I,handshakeBuffer:[],name:e.name,requests:[],target:void 0,targetOrigin:null}),on:{"request.success":{actions:"remove request"},"request.failed":{actions:"remove request"},"request.aborted":{actions:"remove request"}},initial:"idle",states:{idle:{invoke:{id:"listen for handshake syn",src:"listen",input:x({include:B,count:1}),onDone:{target:"handshaking",guard:"hasSource"}},on:{"message.received":{actions:"set connection config"},post:{actions:"buffer message"}}},handshaking:{entry:"send handshake syn ack",invoke:[{id:"listen for handshake ack",src:"listen",input:x({include:j,count:1,responseType:"handshake.complete"}),onDone:"connected"},{id:"listen for disconnect",src:"listen",input:x({include:A,count:1,responseType:"disconnect"})},{id:"listen for messages",src:"listen",input:x({exclude:[A,j,$,O]})}],on:{request:{actions:"create request"},post:{actions:"buffer message"},"message.received":{actions:"buffer incoming message"},disconnect:{target:"idle"}}},connected:{entry:["flush handshake buffer","flush buffer"],invoke:[{id:"listen for messages",src:"listen",input:x({exclude:[O,$]})},{id:"listen for heartbeat",src:"listen",input:x({include:$,responseType:"heartbeat.received"})},{id:"listen for disconnect",src:"listen",input:x({include:A,count:1,responseType:"disconnect"})}],on:{request:{actions:"create request"},post:{actions:"post"},disconnect:{target:"idle"},"message.received":{actions:["send response","emit received message"]},"heartbeat.received":{actions:["send response","emit heartbeat"]}}}}}),J=(e,t=H())=>{const s=u(t,{input:e}),n=()=>{s.stop()};return{actor:s,fetch:(e,t)=>{const n=Promise.withResolvers();return s.send({type:"post",data:e,resolvable:n,signal:t?.signal}),n.promise},machine:t,on:(e,t)=>{const{unsubscribe:n}=s.on(e,(e=>{t(e.message.data)}));return n},onStatus:e=>{const t=s.getSnapshot();let n="string"==typeof t.value?t.value:Object.keys(t.value)[0];const{unsubscribe:a}=s.subscribe((t=>{const s="string"==typeof t.value?t.value:Object.keys(t.value)[0];n!==s&&(n=s,e(s))}));return a},post:e=>{s.send({type:"post",data:e})},start:()=>(s.start(),n),stop:n}};export{I as DOMAIN,R as HANDSHAKE_INTERVAL,_ as HANDSHAKE_MSG_TYPES,S as HEARTBEAT_INTERVAL,z as INTERNAL_MSG_TYPES,A as MSG_DISCONNECT,j as MSG_HANDSHAKE_ACK,B as MSG_HANDSHAKE_SYN,M as MSG_HANDSHAKE_SYN_ACK,$ as MSG_HEARTBEAT,O as MSG_RESPONSE,w as RESPONSE_TIMEOUT,P as createChannel,N as createChannelMachine,G as createController,E as createListenLogic,J as createNode,H as createNodeMachine,D as createRequestMachine};//# sourceMappingURL=index.js.map | ||
import { v4 } from "uuid"; | ||
import { fromEventObservable, setup, sendTo, assign, fromCallback, enqueueActions, assertEvent, raise, stopChild, createActor, emit } from "xstate"; | ||
import { defer, fromEvent, map, pipe, filter, bufferCount, concatMap, take, EMPTY, takeUntil } from "rxjs"; | ||
const listenInputFromContext = (config) => ({ | ||
context | ||
}) => { | ||
const { count, include, exclude, responseType = "message.received" } = config; | ||
return { | ||
count, | ||
domain: context.domain, | ||
from: context.connectTo, | ||
include: include ? Array.isArray(include) ? include : [include] : [], | ||
exclude: exclude ? Array.isArray(exclude) ? exclude : [exclude] : [], | ||
responseType, | ||
target: context.target, | ||
to: context.name | ||
}; | ||
}, listenFilter = (input) => (event) => { | ||
const { data } = event; | ||
return (input.include.length ? input.include.includes(data.type) : !0) && (input.exclude.length ? !input.exclude.includes(data.type) : !0) && data.domain === input.domain && data.from === input.from && data.to === input.to && (!input.target || event.source === input.target); | ||
}, eventToMessage = (type) => (event) => ({ | ||
type, | ||
message: event | ||
}), messageEvents$ = defer( | ||
() => fromEvent(window, "message") | ||
), createListenLogic = (compatMap) => fromEventObservable(({ input }) => messageEvents$.pipe( | ||
compatMap ? map(compatMap) : pipe(), | ||
filter(listenFilter(input)), | ||
map(eventToMessage(input.responseType)), | ||
input.count ? pipe( | ||
bufferCount(input.count), | ||
concatMap((arr) => arr), | ||
take(input.count) | ||
) : pipe() | ||
)), DOMAIN = "sanity/comlink", RESPONSE_TIMEOUT = 1e4, HEARTBEAT_INTERVAL = 1e3, HANDSHAKE_INTERVAL = 500, MSG_RESPONSE = "comlink/response", MSG_HEARTBEAT = "comlink/heartbeat", MSG_DISCONNECT = "comlink/disconnect", MSG_HANDSHAKE_SYN = "comlink/handshake/syn", MSG_HANDSHAKE_SYN_ACK = "comlink/handshake/syn-ack", MSG_HANDSHAKE_ACK = "comlink/handshake/ack", HANDSHAKE_MSG_TYPES = [ | ||
MSG_HANDSHAKE_SYN, | ||
MSG_HANDSHAKE_SYN_ACK, | ||
MSG_HANDSHAKE_ACK | ||
], INTERNAL_MSG_TYPES = [ | ||
MSG_RESPONSE, | ||
MSG_DISCONNECT, | ||
MSG_HEARTBEAT, | ||
...HANDSHAKE_MSG_TYPES | ||
], throwOnEvent = (message) => (source) => source.pipe( | ||
take(1), | ||
map(() => { | ||
throw new Error(message); | ||
}) | ||
), createRequestMachine = () => setup({ | ||
types: {}, | ||
actors: { | ||
listen: fromEventObservable( | ||
({ | ||
input | ||
}) => { | ||
const abortSignal$ = input.signal ? fromEvent(input.signal, "abort").pipe( | ||
throwOnEvent(`Request ${input.requestId} aborted`) | ||
) : EMPTY, messageFilter = (event) => event.data?.type === MSG_RESPONSE && event.data?.responseTo === input.requestId && !!event.source && input.sources.has(event.source); | ||
return fromEvent(window, "message").pipe( | ||
filter(messageFilter), | ||
take(input.sources.size), | ||
takeUntil(abortSignal$) | ||
); | ||
} | ||
) | ||
}, | ||
actions: { | ||
"send message": ({ context }, params) => { | ||
const { sources, targetOrigin } = context, { message } = params; | ||
sources.forEach((source) => { | ||
source.postMessage(message, { targetOrigin }); | ||
}); | ||
}, | ||
"on success": sendTo( | ||
({ context }) => context.parentRef, | ||
({ context, self }) => (context.response && context.resolvable?.resolve(context.response), { | ||
type: "request.success", | ||
requestId: self.id, | ||
response: context.response, | ||
responseTo: context.responseTo | ||
}) | ||
), | ||
"on fail": sendTo( | ||
({ context }) => context.parentRef, | ||
({ context, self }) => (console.warn( | ||
`Received no response to message '${context.type}' on client '${context.from}' (ID: '${context.id}').` | ||
), context.resolvable?.reject(new Error("No response received")), { type: "request.failed", requestId: self.id }) | ||
), | ||
"on abort": sendTo( | ||
({ context }) => context.parentRef, | ||
({ context, self }) => (context.resolvable?.reject(new Error("Request aborted")), { type: "request.aborted", requestId: self.id }) | ||
) | ||
}, | ||
guards: { | ||
expectsResponse: ({ context }) => context.expectResponse | ||
}, | ||
delays: { | ||
initialTimeout: 0, | ||
responseTimeout: RESPONSE_TIMEOUT | ||
} | ||
}).createMachine({ | ||
/** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOlwgBswBiAD1gBd0GwT0AzFgJ2QNwdzoKAFVyowAewCuDItTRY8hUuSoBtAAwBdRKAAOE2P1wT8ukLUQBGAEwBWEgBYAnK+eOAzB7sB2DzY8rABoQAE9rDQc3V0cNTw8fAA4NHwBfVJCFHAJiElgwfAgCKGpNHSQQAyMBU3NLBDsrDxI7DTaAjQA2OOcNDxDwhHsNJx9Ou0TOq2cJxP9HdMyMbOU8gqL8ErUrcv1DY1qK+sbm1vaPLp6+gcRnGydo9wDGycWQLKVc9AB3dGNN6jiWCwdAwMrmKoHMxHRCJRKOEiJHwuZKBZwXKzBMKIGyYkhtAkXOweTqOHw2RJvD45Ug-P4CAH0JgsNicMA8LhwAz4fKicTSWTyZafWm-f5QcEVSE1aGgepwhFIlF9aYYrGDC4+JzEppjGzOUkeGbpDIgfASCBwczU5QQ-YyuqIAC0nRuCBd+IJXu9KSpwppZEoYDt1RMsosiEcNjdVjiJEeGisiSTHkcVgWpptuXyhWKIahjqGzi1BqRJINnVcdkcbuTLS9VYC8ISfsUAbp4vzDphCHJIyjBvJNlxNmRNexQ3sJGH43GPj8jWJrZWuXYfyoEC7YcLsbrgRsjkcvkmdgNbopVhIPhVfnsh8ClMz-tWsCkmEwcHgUvt257u8v+6Hse4xnhOdZnImVidPqCRNB4JqpEAA */ | ||
context: ({ input }) => ({ | ||
connectionId: input.connectionId, | ||
data: input.data, | ||
domain: input.domain, | ||
expectResponse: input.expectResponse ?? !1, | ||
from: input.from, | ||
id: `msg-${v4()}`, | ||
parentRef: input.parentRef, | ||
resolvable: input.resolvable, | ||
response: null, | ||
responseTo: input.responseTo, | ||
signal: input.signal, | ||
sources: input.sources instanceof Set ? input.sources : /* @__PURE__ */ new Set([input.sources]), | ||
targetOrigin: input.targetOrigin, | ||
to: input.to, | ||
type: input.type | ||
}), | ||
initial: "idle", | ||
on: { | ||
abort: ".aborted" | ||
}, | ||
states: { | ||
idle: { | ||
after: { | ||
initialTimeout: [ | ||
{ | ||
target: "sending" | ||
} | ||
] | ||
} | ||
}, | ||
sending: { | ||
entry: { | ||
type: "send message", | ||
params: ({ context }) => { | ||
const { connectionId, data, domain, from, id, responseTo, to, type } = context; | ||
return { message: { | ||
connectionId, | ||
data, | ||
domain, | ||
from, | ||
id, | ||
to, | ||
type, | ||
responseTo | ||
} }; | ||
} | ||
}, | ||
always: [ | ||
{ | ||
guard: "expectsResponse", | ||
target: "awaiting" | ||
}, | ||
"success" | ||
] | ||
}, | ||
awaiting: { | ||
invoke: { | ||
id: "listen for response", | ||
src: "listen", | ||
input: ({ context }) => ({ | ||
requestId: context.id, | ||
sources: context.sources, | ||
signal: context.signal | ||
}), | ||
onError: "aborted" | ||
}, | ||
after: { | ||
responseTimeout: "failed" | ||
}, | ||
on: { | ||
message: { | ||
actions: assign({ | ||
response: ({ event }) => event.data.data, | ||
responseTo: ({ event }) => event.data.responseTo | ||
}), | ||
target: "success" | ||
} | ||
} | ||
}, | ||
failed: { | ||
type: "final", | ||
entry: "on fail" | ||
}, | ||
success: { | ||
type: "final", | ||
entry: "on success" | ||
}, | ||
aborted: { | ||
type: "final", | ||
entry: "on abort" | ||
} | ||
}, | ||
output: ({ context, self }) => ({ | ||
requestId: self.id, | ||
response: context.response, | ||
responseTo: context.responseTo | ||
}) | ||
}), sendBackAtInterval = fromCallback(({ sendBack, input }) => { | ||
const send = () => { | ||
sendBack(input.event); | ||
}; | ||
input.immediate && send(); | ||
const interval = setInterval(send, input.interval); | ||
return () => { | ||
clearInterval(interval); | ||
}; | ||
}), createChannelMachine = () => setup({ | ||
types: {}, | ||
actors: { | ||
requestMachine: createRequestMachine(), | ||
listen: createListenLogic(), | ||
sendBackAtInterval | ||
}, | ||
actions: { | ||
"buffer message": enqueueActions(({ enqueue }) => { | ||
enqueue.assign({ | ||
buffer: ({ event, context }) => (assertEvent(event, "post"), [...context.buffer, event.data]) | ||
}), enqueue.emit(({ event }) => (assertEvent(event, "post"), { | ||
type: "_buffer.added", | ||
message: event.data | ||
})); | ||
}), | ||
"create request": assign({ | ||
requests: ({ context, event, self, spawn }) => { | ||
assertEvent(event, "request"); | ||
const requests = (Array.isArray(event.data) ? event.data : [event.data]).map((request) => { | ||
const id = `req-${v4()}`; | ||
return spawn("requestMachine", { | ||
id, | ||
input: { | ||
connectionId: context.connectionId, | ||
data: request.data, | ||
domain: context.domain, | ||
expectResponse: request.expectResponse, | ||
from: context.name, | ||
parentRef: self, | ||
responseTo: request.responseTo, | ||
sources: context.target, | ||
targetOrigin: context.targetOrigin, | ||
to: context.connectTo, | ||
type: request.type | ||
} | ||
}); | ||
}); | ||
return [...context.requests, ...requests]; | ||
} | ||
}), | ||
"emit received message": enqueueActions(({ enqueue }) => { | ||
enqueue.emit(({ event }) => (assertEvent(event, "message.received"), { | ||
type: "_message", | ||
message: event.message.data | ||
})), enqueue.emit(({ event }) => (assertEvent(event, "message.received"), { | ||
type: event.message.data.type, | ||
message: event.message.data | ||
})); | ||
}), | ||
"flush buffer": enqueueActions(({ enqueue }) => { | ||
enqueue.raise(({ context }) => ({ | ||
type: "request", | ||
data: context.buffer.map(({ data, type }) => ({ data, type })) | ||
})), enqueue.emit(({ context }) => ({ | ||
type: "_buffer.flushed", | ||
messages: context.buffer | ||
})), enqueue.assign({ | ||
buffer: [] | ||
}); | ||
}), | ||
post: raise(({ event }) => (assertEvent(event, "post"), { | ||
type: "request", | ||
data: { | ||
data: event.data.data, | ||
expectResponse: !0, | ||
type: event.data.type | ||
} | ||
})), | ||
"remove request": enqueueActions(({ context, enqueue, event }) => { | ||
assertEvent(event, ["request.success", "request.failed", "request.aborted"]), stopChild(event.requestId), enqueue.assign({ requests: context.requests.filter(({ id }) => id !== event.requestId) }); | ||
}), | ||
respond: raise(({ event }) => (assertEvent(event, "response"), { | ||
type: "request", | ||
data: { | ||
data: event.data, | ||
type: MSG_RESPONSE, | ||
responseTo: event.respondTo | ||
} | ||
})), | ||
"send handshake ack": raise({ | ||
type: "request", | ||
data: { type: MSG_HANDSHAKE_ACK } | ||
}), | ||
"send disconnect": raise(() => ({ | ||
type: "request", | ||
data: { type: MSG_DISCONNECT } | ||
})), | ||
"send handshake syn": raise({ | ||
type: "request", | ||
data: { type: MSG_HANDSHAKE_SYN } | ||
}), | ||
"set target": assign({ | ||
target: ({ event }) => (assertEvent(event, "target.set"), event.target) | ||
}) | ||
}, | ||
guards: { | ||
"has target": ({ context }) => !!context.target, | ||
"should send heartbeats": ({ context }) => context.heartbeat | ||
} | ||
}).createMachine({ | ||
/** @xstate-layout N4IgpgJg5mDOIC5QGMAWBDAdpsAbAxAC7oBOMhAdLGIQNoAMAuoqAA4D2sAloV+5ixAAPRAHZRAJgoAWABz0ArHICMy2QGZZCgJwAaEAE9EE+tIrb6ANgkLl46fTuj1AXxf60WHARJgAjgCucJSwAcjIcLAMzEggHNy8-IIiCKLS2hQS6qb2yurisrL6RgjK9LIyCuqq0g7WstZuHhjYePi+gcEUAGboXLiQ0YLxPHwCsSmiCgoykpayDtqS6trqxYjKEk0gnq24FFwQA-jI-DjIdEzDnKNJExuOZpZ12eq29OrSCuupypYUojUaTKCnm5Wk2123gORzA+HilxibBuiXGoBSGnUAIU4gU9FWamUtR+lmUM1EllBEkslMUEnpkJa0JaEFgGAA1lxMFB8LADJghrERqjkhtshk3mTtNo5OpqpYfqCKhTptoqpY1WUtu4dky8BQWWz0Jzue1-EFYIjrgkxqLSupqRRPpoPqJtLI0hIioZENJJE7NnJ8ZYHVk1YyvPrDRyuTyEYLkTa7uixVlMh81KGFhS1j6EPkZlpVjTphr8mkI3sDVhWTHTQBbSLoGAUXwRLgAN0GVyFKNt91KimUFEKXvKC2s9R+6X+jipnzJeSqEJ1UKjNaNJp5EC4sFOrQuCbifeTwg2cgoym0RPxDtqkj0eaB9Ao8zSolMEivZVcq71+33c5CEgeFOCtXskzRM8EDxKRpmkSw3QJbQsmpH5tHmV8JHSbJpDsakV2aSMALOMALhAjoLXAxNbiglI-SxWw1Vw0QNDw0Qfg9KQ7EJSxHHxApK2hQCyOAiAzVgDhMGoI9hX7FMEHSF8cWkelpHURCbBsb481xAEgT9BQJCmWQsiE-URPI8TG1gWBmzAVsyLATtuyRY9ILtWoKmlL82Kqd0tAVJ91LMHFZDKIkVlkNVZHMkiDzE-Adz3UjDx7GiRQHCKnheD53k+HSSkDDIwpBVTqQwuKKEssSDTAUhCAAI3qyg0DIrd8Fkk86MQUMnVM+RynoegTDJH48hGp0vR-FDRqqKqasgOqGua9AQjATAd1NSiul6fpXOtWi7Wy19cslD4vnG7IX3oVjVDUVYEJQqrksW8SdstLqPKy0wKgG1RhtMWogqKhoMjkWp6XxUyFBe3c3tAz70vco6fq+V8PTkGUFzdQqNnELEM2yClrwwzQ4ZShKQJqr7UYU98AS0W9pT4z5pHG0yXwMkNNTyGk3B1TB2AgOBBDXXBDsyhSFG9EovQqN5i1JeRcKqw4Bkl+ToMx8x0j+EaqQ9XMSkBURMgMkEwQWKro2NWNNdPFJAzN0lJGM4slDxhBEJfXyplBd03wW1KxIdnrBxBh4JAyW75C8rJpmDqmIGWkgmpasPjqUcaHooMLHA0uU1UkJOgKW1B6rT1bWor5At0zgcTAkK7hrz1irB0D8cW0UvRPLyv07WqgNq2qAG+l9SnXUz0UOXD5xuMs3Y4+DVJBX7UiKrV6Q8gcfoJO54rFefLLqfJYX1WKYNLxL4NO1NwgA */ | ||
id: "channel", | ||
context: ({ input }) => ({ | ||
id: input.id || `${input.name}-${v4()}`, | ||
buffer: [], | ||
connectionId: `cnx-${v4()}`, | ||
connectTo: input.connectTo, | ||
domain: input.domain ?? DOMAIN, | ||
heartbeat: input.heartbeat ?? !1, | ||
name: input.name, | ||
requests: [], | ||
target: input.target, | ||
targetOrigin: input.targetOrigin | ||
}), | ||
on: { | ||
"target.set": { | ||
actions: "set target" | ||
}, | ||
"request.success": { | ||
actions: "remove request" | ||
}, | ||
"request.failed": { | ||
actions: "remove request" | ||
} | ||
}, | ||
initial: "idle", | ||
states: { | ||
idle: { | ||
on: { | ||
connect: { | ||
target: "handshaking", | ||
guard: "has target" | ||
}, | ||
post: { | ||
actions: "buffer message" | ||
} | ||
} | ||
}, | ||
handshaking: { | ||
id: "handshaking", | ||
invoke: [ | ||
{ | ||
id: "send syn", | ||
src: "sendBackAtInterval", | ||
input: () => ({ | ||
event: { type: "syn" }, | ||
interval: HANDSHAKE_INTERVAL, | ||
immediate: !0 | ||
}) | ||
}, | ||
{ | ||
id: "listen for handshake", | ||
src: "listen", | ||
input: (input) => listenInputFromContext({ | ||
include: MSG_HANDSHAKE_SYN_ACK, | ||
count: 1 | ||
})(input) | ||
/* Below would maybe be more readable than transitioning to | ||
'connected' on 'message', and 'ack' on exit but having onDone when | ||
using passing invocations currently breaks XState Editor */ | ||
// onDone: { | ||
// target: 'connected', | ||
// actions: 'ack', | ||
// }, | ||
} | ||
], | ||
on: { | ||
syn: { | ||
actions: "send handshake syn" | ||
}, | ||
request: { | ||
actions: "create request" | ||
}, | ||
post: { | ||
actions: "buffer message" | ||
}, | ||
"message.received": { | ||
target: "connected" | ||
}, | ||
disconnect: { | ||
target: "disconnected" | ||
} | ||
}, | ||
exit: "send handshake ack" | ||
}, | ||
connected: { | ||
entry: "flush buffer", | ||
invoke: { | ||
id: "listen for messages", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
exclude: [MSG_RESPONSE, MSG_HEARTBEAT] | ||
}) | ||
}, | ||
on: { | ||
post: { | ||
actions: "post" | ||
}, | ||
request: { | ||
actions: "create request" | ||
}, | ||
response: { | ||
actions: "respond" | ||
}, | ||
"message.received": { | ||
actions: "emit received message" | ||
}, | ||
disconnect: { | ||
target: "disconnected" | ||
} | ||
}, | ||
initial: "heartbeat", | ||
states: { | ||
heartbeat: { | ||
initial: "checking", | ||
states: { | ||
checking: { | ||
always: { | ||
guard: "should send heartbeats", | ||
target: "sending" | ||
} | ||
}, | ||
sending: { | ||
on: { | ||
"request.failed": { | ||
target: "#handshaking" | ||
} | ||
}, | ||
invoke: { | ||
id: "send heartbeat", | ||
src: "sendBackAtInterval", | ||
input: () => ({ | ||
event: { type: "post", data: { type: MSG_HEARTBEAT, data: void 0 } }, | ||
interval: 2e3, | ||
immediate: !1 | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
disconnected: { | ||
id: "disconnected", | ||
entry: "send disconnect", | ||
on: { | ||
request: { | ||
actions: "create request" | ||
}, | ||
post: { | ||
actions: "buffer message" | ||
}, | ||
connect: { | ||
target: "handshaking", | ||
guard: "has target" | ||
} | ||
} | ||
} | ||
} | ||
}), createChannel = (input, machine = createChannelMachine()) => { | ||
const id = input.id || `${input.name}-${v4()}`, actor = createActor(machine, { | ||
input: { ...input, id } | ||
}), on = (type, handler) => { | ||
const { unsubscribe } = actor.on( | ||
// @ts-expect-error @todo `type` typing | ||
type, | ||
async (event) => { | ||
const response = await handler(event.message.data); | ||
response && actor.send({ type: "response", respondTo: event.message.id, data: response }); | ||
} | ||
); | ||
return unsubscribe; | ||
}, connect = () => { | ||
actor.send({ type: "connect" }); | ||
}, disconnect = () => { | ||
actor.send({ type: "disconnect" }); | ||
}, onStatus = (handler) => { | ||
const currentSnapshot = actor.getSnapshot(); | ||
let currentStatus = typeof currentSnapshot.value == "string" ? currentSnapshot.value : Object.keys(currentSnapshot.value)[0]; | ||
const { unsubscribe } = actor.subscribe((state) => { | ||
const status = typeof state.value == "string" ? state.value : Object.keys(state.value)[0]; | ||
currentStatus !== status && (currentStatus = status, handler(status)); | ||
}); | ||
return unsubscribe; | ||
}, setTarget = (target) => { | ||
actor.send({ type: "target.set", target }); | ||
}, post = (data) => { | ||
actor.send({ type: "post", data }); | ||
}, stop = () => { | ||
actor.stop(); | ||
}, start = () => (actor.start(), stop); | ||
return { | ||
actor, | ||
connect, | ||
disconnect, | ||
id, | ||
name: input.name, | ||
machine, | ||
on, | ||
onStatus, | ||
post, | ||
setTarget, | ||
start, | ||
stop, | ||
get target() { | ||
return actor.getSnapshot().context.target; | ||
} | ||
}; | ||
}, noop = () => { | ||
}, createController = (input) => { | ||
const { targetOrigin } = input, targets = /* @__PURE__ */ new Set(), connections = /* @__PURE__ */ new Set(), addTarget = (target) => { | ||
if (targets.has(target)) | ||
return noop; | ||
if (!targets.size || !connections.size) | ||
return targets.add(target), connections.forEach((connection) => { | ||
connection.channels.forEach((channel) => { | ||
channel.setTarget(target), channel.connect(); | ||
}); | ||
}), () => { | ||
targets.delete(target), connections.forEach((connection) => { | ||
connection.channels.forEach((channel) => { | ||
channel.target === target && channel.disconnect(); | ||
}); | ||
}); | ||
}; | ||
targets.add(target); | ||
const targetChannels = /* @__PURE__ */ new Set(); | ||
return connections.forEach((connection) => { | ||
const channel = createChannel( | ||
{ | ||
...connection.input, | ||
target, | ||
targetOrigin | ||
}, | ||
connection.machine | ||
); | ||
targetChannels.add(channel), connection.channels.add(channel), connection.subscribers.forEach(({ type, handler, unsubscribers }) => { | ||
unsubscribers.push(channel.on(type, handler)); | ||
}), connection.internalEventSubscribers.forEach(({ type, handler, unsubscribers }) => { | ||
unsubscribers.push(channel.actor.on(type, handler).unsubscribe); | ||
}), connection.statusSubscribers.forEach(({ handler, unsubscribers }) => { | ||
unsubscribers.push(channel.onStatus((status) => handler({ channel: channel.id, status }))); | ||
}), channel.start(), channel.connect(); | ||
}), () => { | ||
targets.delete(target), targetChannels.forEach((channel) => { | ||
cleanupChannel(channel), connections.forEach((connection) => { | ||
connection.channels.delete(channel); | ||
}); | ||
}); | ||
}; | ||
}, cleanupChannel = (channel) => { | ||
channel.disconnect(), setTimeout(() => { | ||
channel.stop(); | ||
}, 0); | ||
}; | ||
return { | ||
addTarget, | ||
createConnection: (input2, machine = createChannelMachine()) => { | ||
const connection = { | ||
channels: /* @__PURE__ */ new Set(), | ||
input: input2, | ||
internalEventSubscribers: /* @__PURE__ */ new Set(), | ||
machine, | ||
statusSubscribers: /* @__PURE__ */ new Set(), | ||
subscribers: /* @__PURE__ */ new Set() | ||
}; | ||
connections.add(connection); | ||
const { channels, internalEventSubscribers, statusSubscribers, subscribers } = connection; | ||
if (targets.size) | ||
targets.forEach((target) => { | ||
const channel = createChannel( | ||
{ | ||
...input2, | ||
target, | ||
targetOrigin | ||
}, | ||
machine | ||
); | ||
channels.add(channel); | ||
}); | ||
else { | ||
const channel = createChannel({ ...input2, targetOrigin }, machine); | ||
channels.add(channel); | ||
} | ||
const post = (data) => { | ||
channels.forEach((channel) => { | ||
channel.post(data); | ||
}); | ||
}, on = (type, handler) => { | ||
const unsubscribers = []; | ||
channels.forEach((channel) => { | ||
unsubscribers.push(channel.on(type, handler)); | ||
}); | ||
const subscriber = { type, handler, unsubscribers }; | ||
return subscribers.add(subscriber), () => { | ||
unsubscribers.forEach((unsub) => unsub()), subscribers.delete(subscriber); | ||
}; | ||
}, onInternalEvent = (type, handler) => { | ||
const unsubscribers = []; | ||
channels.forEach((channel) => { | ||
unsubscribers.push(channel.actor.on(type, handler).unsubscribe); | ||
}); | ||
const subscriber = { type, handler, unsubscribers }; | ||
return internalEventSubscribers.add(subscriber), () => { | ||
unsubscribers.forEach((unsub) => unsub()), internalEventSubscribers.delete(subscriber); | ||
}; | ||
}, onStatus = (handler) => { | ||
const unsubscribers = []; | ||
channels.forEach((channel) => { | ||
unsubscribers.push(channel.onStatus((status) => handler({ channel: channel.id, status }))); | ||
}); | ||
const subscriber = { handler, unsubscribers }; | ||
return statusSubscribers.add(subscriber), () => { | ||
unsubscribers.forEach((unsub) => unsub()), statusSubscribers.delete(subscriber); | ||
}; | ||
}, stop = () => { | ||
channels.forEach((channel) => { | ||
channel.disconnect(), channel.stop(); | ||
}); | ||
}; | ||
return { | ||
on, | ||
onInternalEvent, | ||
onStatus, | ||
post, | ||
start: () => (channels.forEach((channel) => { | ||
channel.start(), channel.connect(); | ||
}), stop), | ||
stop | ||
}; | ||
}, | ||
destroy: () => { | ||
connections.forEach(({ channels }) => { | ||
channels.forEach((channel) => { | ||
cleanupChannel(channel), channels.delete(channel); | ||
}); | ||
}); | ||
} | ||
}; | ||
}, createNodeMachine = () => setup({ | ||
types: {}, | ||
actors: { | ||
requestMachine: createRequestMachine(), | ||
listen: createListenLogic() | ||
}, | ||
actions: { | ||
"buffer incoming message": assign({ | ||
handshakeBuffer: ({ event, context }) => (assertEvent(event, "message.received"), [...context.handshakeBuffer, event]) | ||
}), | ||
"buffer message": enqueueActions(({ enqueue }) => { | ||
enqueue.assign({ | ||
buffer: ({ event, context }) => (assertEvent(event, "post"), [ | ||
...context.buffer, | ||
{ | ||
data: event.data, | ||
resolvable: event.resolvable, | ||
signal: event.signal | ||
} | ||
]) | ||
}), enqueue.emit(({ event }) => (assertEvent(event, "post"), { | ||
type: "_buffer.added", | ||
message: event.data | ||
})); | ||
}), | ||
"create request": assign({ | ||
requests: ({ context, event, self, spawn }) => { | ||
assertEvent(event, "request"); | ||
const requests = (Array.isArray(event.data) ? event.data : [event.data]).map((request) => { | ||
const id = `req-${v4()}`; | ||
return spawn("requestMachine", { | ||
id, | ||
input: { | ||
connectionId: context.connectionId, | ||
data: request.data, | ||
domain: context.domain, | ||
expectResponse: request.expectResponse, | ||
from: context.name, | ||
parentRef: self, | ||
resolvable: request.resolvable, | ||
responseTo: request.responseTo, | ||
sources: context.target, | ||
targetOrigin: context.targetOrigin, | ||
to: context.connectTo, | ||
type: request.type, | ||
signal: request.signal | ||
} | ||
}); | ||
}); | ||
return [...context.requests, ...requests]; | ||
} | ||
}), | ||
"emit heartbeat": emit(() => ({ | ||
type: "_heartbeat" | ||
})), | ||
"emit received message": enqueueActions(({ enqueue }) => { | ||
enqueue.emit(({ event }) => (assertEvent(event, "message.received"), { | ||
type: "_message", | ||
message: event.message.data | ||
})), enqueue.emit(({ event }) => (assertEvent(event, "message.received"), { | ||
type: event.message.data.type, | ||
message: event.message.data | ||
})); | ||
}), | ||
"flush buffer": enqueueActions(({ enqueue }) => { | ||
enqueue.raise(({ context }) => ({ | ||
type: "request", | ||
data: context.buffer.map(({ data, resolvable, signal }) => ({ | ||
data: data.data, | ||
type: data.type, | ||
expectResponse: !!resolvable, | ||
resolvable, | ||
signal | ||
})) | ||
})), enqueue.emit(({ context }) => ({ | ||
type: "_buffer.flushed", | ||
messages: context.buffer.map(({ data }) => data) | ||
})), enqueue.assign({ | ||
buffer: [] | ||
}); | ||
}), | ||
"flush handshake buffer": enqueueActions(({ context, enqueue }) => { | ||
context.handshakeBuffer.forEach((event) => enqueue.raise(event)), enqueue.assign({ | ||
handshakeBuffer: [] | ||
}); | ||
}), | ||
post: raise(({ event }) => (assertEvent(event, "post"), { | ||
type: "request", | ||
data: { | ||
data: event.data.data, | ||
expectResponse: !!event.resolvable, | ||
type: event.data.type, | ||
resolvable: event.resolvable, | ||
signal: event.signal | ||
} | ||
})), | ||
"remove request": enqueueActions(({ context, enqueue, event }) => { | ||
assertEvent(event, ["request.success", "request.failed", "request.aborted"]), stopChild(event.requestId), enqueue.assign({ requests: context.requests.filter(({ id }) => id !== event.requestId) }); | ||
}), | ||
"send response": raise(({ event }) => (assertEvent(event, ["message.received", "heartbeat.received"]), { | ||
type: "request", | ||
data: { | ||
type: MSG_RESPONSE, | ||
responseTo: event.message.data.id, | ||
data: void 0 | ||
} | ||
})), | ||
"send handshake syn ack": raise({ | ||
type: "request", | ||
data: { type: MSG_HANDSHAKE_SYN_ACK } | ||
}), | ||
"set connection config": assign({ | ||
connectionId: ({ event }) => (assertEvent(event, "message.received"), event.message.data.connectionId), | ||
target: ({ event }) => (assertEvent(event, "message.received"), event.message.source || void 0), | ||
targetOrigin: ({ event }) => (assertEvent(event, "message.received"), event.message.origin) | ||
}) | ||
}, | ||
guards: { | ||
hasSource: ({ context }) => context.target !== null | ||
} | ||
}).createMachine({ | ||
/** @xstate-layout N4IgpgJg5mDOIC5QDsD2EwGIBOYCOArnAC4B0sBAxpXLANoAMAuoqAA6qwCWxXqyrEAA9EAVgYAWUgEYJDUQA4JAZmUSJC0coDsAGhABPRNIYLSErdOkBOAGzbx227YUBfV-rQYc+IrDIAZgCGXAA2kIwsSCAc3Lz8giIIoiakqgBMDKbp2tYS0srp+kYI0ununuhgpFwQ4ZgQ-NVcyABuqADW1V7NdWAILe2UQfHIkZGCsTx8AtFJ6aKipAzWOtrpC7Z5BUWGiNoK6aS26RLW2tLaqkqqFSA9NX2YALa0QTCkuDRcrRHMk5xpgk5ogJLZSNZIVDoVCFLZiohbIVSLkXLZRHZDgxbHcHrV6rFiBNolNRolEVJbCsdGUzsoyhiEcllOC1DowelVmVrOUPPcqqQABZBZAQWDCjotKANJo1NqdboC4Wi8VBSXIKADeXDUbjf4kwFkkEILbg8RZMHKOzWKzKJkHJa086Xa4qZS4pUisUSqU+QgkYnsQ0zcnJaRLDbpZwKNQSBYspm2MEyC5KTnaDSSd18h7K71q32EwMxYPA0BJFLKY5yZxIrKSURM0RnFHSBTrQqQ9babQejBCr2q9XSiBcWCUfjIMCUIn6oNxEPGtTWFFR0RUy7iGzt+3Ip0XURXVZKPvVCfIKczyB+vyzqLzoGzcuIG0MGTyCztjRtjaJjbHVMNAUTdu1PUhz0vYhryLOcSwXMthBfK0ZGsLQGBZekCi0Jso1IdI23WG04zOE4wIg6coIgBox3Imdi1JRdnxNOxSHNSQkWtW0mTjMxMQ7fDzgcbNKn7WjKJeN4Pi+MAfj+e84MfUMFHbZZwxOHZNDyO09gQOQjmAhZJCM9IMjIycKOvQUwCCbBiAAI2sshpNkiB6NLJ9EIQBQbWOdJlMhYCUjbJkchXGsFmsJQMVsWl3BzKp4GiHoAXgjykgAWmkZZ6xy3LZF2EobCy6xsQWJQ42kE4FjA-EwBSxTjSRUhDgqkzgO2BxdykU4AvXFQ-KjMC8yHKV6qNJi6WOdcypcZsXGxe0JG0XySKjM5lKsMyLwsiAxsYzylDfONznUEqrmi+1ThkHqXDONbULi1wgA */ | ||
id: "node", | ||
context: ({ input }) => ({ | ||
buffer: [], | ||
connectionId: null, | ||
connectTo: input.connectTo, | ||
domain: input.domain ?? DOMAIN, | ||
handshakeBuffer: [], | ||
name: input.name, | ||
requests: [], | ||
target: void 0, | ||
targetOrigin: null | ||
}), | ||
on: { | ||
"request.success": { | ||
actions: "remove request" | ||
}, | ||
"request.failed": { | ||
actions: "remove request" | ||
}, | ||
"request.aborted": { | ||
actions: "remove request" | ||
} | ||
}, | ||
initial: "idle", | ||
states: { | ||
idle: { | ||
invoke: { | ||
id: "listen for handshake syn", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
include: MSG_HANDSHAKE_SYN, | ||
count: 1 | ||
}), | ||
onDone: { | ||
target: "handshaking", | ||
guard: "hasSource" | ||
} | ||
}, | ||
on: { | ||
"message.received": { | ||
actions: "set connection config" | ||
}, | ||
post: { | ||
actions: "buffer message" | ||
} | ||
} | ||
}, | ||
handshaking: { | ||
entry: "send handshake syn ack", | ||
invoke: [ | ||
{ | ||
id: "listen for handshake ack", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
include: MSG_HANDSHAKE_ACK, | ||
count: 1, | ||
// Override the default `message.received` responseType to prevent | ||
// buffering the ack message. We transition to the connected state | ||
// using onDone instead of listening to this event using `on` | ||
responseType: "handshake.complete" | ||
}), | ||
onDone: "connected" | ||
}, | ||
{ | ||
id: "listen for disconnect", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
include: MSG_DISCONNECT, | ||
count: 1, | ||
responseType: "disconnect" | ||
}) | ||
}, | ||
{ | ||
id: "listen for messages", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
exclude: [MSG_DISCONNECT, MSG_HANDSHAKE_ACK, MSG_HEARTBEAT, MSG_RESPONSE] | ||
}) | ||
} | ||
], | ||
on: { | ||
request: { | ||
actions: "create request" | ||
}, | ||
post: { | ||
actions: "buffer message" | ||
}, | ||
"message.received": { | ||
actions: "buffer incoming message" | ||
}, | ||
disconnect: { | ||
target: "idle" | ||
} | ||
} | ||
}, | ||
connected: { | ||
entry: ["flush handshake buffer", "flush buffer"], | ||
invoke: [ | ||
{ | ||
id: "listen for messages", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
exclude: [MSG_RESPONSE, MSG_HEARTBEAT] | ||
}) | ||
}, | ||
{ | ||
id: "listen for heartbeat", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
include: MSG_HEARTBEAT, | ||
responseType: "heartbeat.received" | ||
}) | ||
}, | ||
{ | ||
id: "listen for disconnect", | ||
src: "listen", | ||
input: listenInputFromContext({ | ||
include: MSG_DISCONNECT, | ||
count: 1, | ||
responseType: "disconnect" | ||
}) | ||
} | ||
], | ||
on: { | ||
request: { | ||
actions: "create request" | ||
}, | ||
post: { | ||
actions: "post" | ||
}, | ||
disconnect: { | ||
target: "idle" | ||
}, | ||
"message.received": { | ||
actions: ["send response", "emit received message"] | ||
}, | ||
"heartbeat.received": { | ||
actions: ["send response", "emit heartbeat"] | ||
} | ||
} | ||
} | ||
} | ||
}), createNode = (input, machine = createNodeMachine()) => { | ||
const actor = createActor(machine, { | ||
input | ||
}), on = (type, handler) => { | ||
const { unsubscribe } = actor.on( | ||
// @ts-expect-error @todo `type` typing | ||
type, | ||
(event) => { | ||
handler(event.message.data); | ||
} | ||
); | ||
return unsubscribe; | ||
}, onStatus = (handler) => { | ||
const snapshot = actor.getSnapshot(); | ||
let currentStatus = typeof snapshot.value == "string" ? snapshot.value : Object.keys(snapshot.value)[0]; | ||
const { unsubscribe } = actor.subscribe((state) => { | ||
const status = typeof state.value == "string" ? state.value : Object.keys(state.value)[0]; | ||
currentStatus !== status && (currentStatus = status, handler(status)); | ||
}); | ||
return unsubscribe; | ||
}, post = (data) => { | ||
actor.send({ type: "post", data }); | ||
}, fetch = (data, options) => { | ||
const resolvable = Promise.withResolvers(); | ||
return actor.send({ | ||
type: "post", | ||
data, | ||
resolvable, | ||
signal: options?.signal | ||
}), resolvable.promise; | ||
}, stop = () => { | ||
actor.stop(); | ||
}; | ||
return { | ||
actor, | ||
fetch, | ||
machine, | ||
on, | ||
onStatus, | ||
post, | ||
start: () => (actor.start(), stop), | ||
stop | ||
}; | ||
}; | ||
export { | ||
DOMAIN, | ||
HANDSHAKE_INTERVAL, | ||
HANDSHAKE_MSG_TYPES, | ||
HEARTBEAT_INTERVAL, | ||
INTERNAL_MSG_TYPES, | ||
MSG_DISCONNECT, | ||
MSG_HANDSHAKE_ACK, | ||
MSG_HANDSHAKE_SYN, | ||
MSG_HANDSHAKE_SYN_ACK, | ||
MSG_HEARTBEAT, | ||
MSG_RESPONSE, | ||
RESPONSE_TIMEOUT, | ||
createChannel, | ||
createChannelMachine, | ||
createController, | ||
createListenLogic, | ||
createNode, | ||
createNodeMachine, | ||
createRequestMachine | ||
}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@sanity/comlink", | ||
"version": "1.0.0", | ||
"version": "1.0.1-canary.0", | ||
"keywords": [ | ||
@@ -5,0 +5,0 @@ "sanity.io", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
746813
10586
63
92
6
92
50797
2