UNPKG

@rtco/client

Version:
2 lines (1 loc) 15.3 kB
import{EventEmitter as O}from"eventemitter3";class r{#i=0;#h;constructor(i="[artico]",h=1){this.#h=i,this.#i=h}get logLevel(){return this.#i}set logLevel(i){this.#i=i}debug(...i){if(this.#i>=4)this.#n(4,...i)}log(...i){if(this.#i>=3)this.#n(4,...i)}warn(...i){if(this.#i>=2)this.#n(2,...i)}error(...i){if(this.#i>=1)this.#n(1,...i)}#n(i,...h){let n=[this.#h,...h];if(n.forEach((t,s)=>{if(t instanceof Error)n[s]=`(${t.name}) ${t.message}`}),i>=4)console.debug(...n);else if(i>=3)console.log(...n);else if(i>=2)console.warn("WARNING",...n);else if(i>=1)console.error("ERROR",...n)}}import{EventEmitter as x}from"eventemitter3";import{EventEmitter as k}from"eventemitter3";class S{#i=0;#h;constructor(i="[artico]",h=1){this.#h=i,this.#i=h}get logLevel(){return this.#i}set logLevel(i){this.#i=i}debug(...i){if(this.#i>=4)this.#n(4,...i)}log(...i){if(this.#i>=3)this.#n(4,...i)}warn(...i){if(this.#i>=2)this.#n(2,...i)}error(...i){if(this.#i>=1)this.#n(1,...i)}#n(i,...h){let n=[this.#h,...h];if(n.forEach((t,s)=>{if(t instanceof Error)n[s]=`(${t.name}) ${t.message}`}),i>=4)console.debug(...n);else if(i>=3)console.log(...n);else if(i>=2)console.warn("WARNING",...n);else if(i>=1)console.error("ERROR",...n)}}var $=()=>{return Math.random().toString(36).slice(2)};class f extends k{#i;#h;#n;#d=!1;#t=!1;#s;#r={iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:"stun:stun1.l.google.com:19302"}]};#e;#o;constructor(i){super();this.#i=new S("[peer]",i?.debug??1),this.#i.debug("new Peer:",i),this.#s=i?.initiator??!1,this.#r={...this.#r,...i?.config},this.#e=i?.channelName??`dc_${$()}`,this.#o=i?.channelConfig??{};try{this.#h=new RTCPeerConnection(this.#r),this.#c()}catch{throw new Error("WebRTC is not supported by this browser")}if(this.#s)this.#n=this.#h.createDataChannel(this.#e,this.#o),this.#b()}get ready(){return this.#n?.readyState==="open"}get#u(){return!this.#s}destroy=()=>{if(this.#i.debug("destroy()"),this.#n)this.#S(),this.#n.close(),this.#n=void 0;this.#f(),this.#h.close(),this.emit("close"),this.removeAllListeners()};signal=async(i)=>{this.#i.debug(`signal(${i.type})`);try{if(i.type==="candidate")try{await this.#h.addIceCandidate(i.data)}catch(h){if(!this.#t)throw h}else if(i.type==="sdp"){let h=i.data,n=h.type==="offer"&&(this.#d||this.#h.signalingState!=="stable");if(this.#t=!this.#u&&n,this.#t){this.#i.debug("ignoring offer");return}if(await this.#h.setRemoteDescription(h),h.type==="offer")await this.#h.setLocalDescription(),this.emit("signal",{type:"sdp",data:this.#h.localDescription})}}catch(h){this.emit("error",h)}};send(i){if(this.#i.debug(`send(${i})`),!this.#n||!this.ready)throw new Error("Connection is not established yet.");this.#n.send(i)}addStream=(i)=>{this.#i.debug(`addStream(${i.id})`);for(let h of i.getTracks())this.#h.addTrack(h,i)};removeStream=(i)=>{this.#i.debug(`removeStream(${i.id})`);for(let h of i.getTracks())this.removeTrack(h)};addTrack=(i,h)=>{this.#i.debug(`addTrack(${i.id}, ${h.id})`),this.#h.addTrack(i,h)};removeTrack=(i)=>{this.#i.debug(`removeTrack(${i.id})`);let h=this.#h.getSenders().find((n)=>n.track===i);if(h)this.#i.debug("removeTrack(sender)"),this.#h.removeTrack(h)};replaceTrack=async(i,h)=>{this.#i.debug(`replaceTrack(${i.id}, ${h.id})`);let n=this.#h.getSenders().find((t)=>t.track===i);if(n){this.#i.debug("replaceTrack(sender)");try{await n.replaceTrack(h),this.emit("replacetrack",i,h)}catch(t){throw new Error(`Failed to replace track: ${t}`)}}else throw new Error("Failed to replace track: sender not found")};#c=()=>{this.#h.onnegotiationneeded=this.#w,this.#h.onicecandidate=this.#y,this.#h.onicecandidateerror=(i)=>{this.#i.debug("onicecandidateerror:",i)},this.#h.oniceconnectionstatechange=this.#E,this.#h.ontrack=this.#$,this.#h.ondatachannel=this.#j,this.#h.onicegatheringstatechange=this.#k};#f=()=>{this.#h.onnegotiationneeded=null,this.#h.onicecandidate=null,this.#h.onicecandidateerror=null,this.#h.oniceconnectionstatechange=null,this.#h.ontrack=null,this.#h.ondatachannel=null,this.#h.onicegatheringstatechange=null};#b=()=>{if(!this.#n)return this.emit("error",new Error("Tried to setup undefined data channel.")),this.destroy();this.#e=this.#n.label,this.#n.onopen=()=>{this.#x()},this.#n.onclose=()=>{this.#T()},this.#n.onerror=(i)=>{let h=i,n=h.error.message,t=h.error instanceof Error?h.error:new Error(`Datachannel error: ${n}`);this.emit("error",t),this.destroy()},this.#n.onmessage=this.#M};#S=()=>{if(!this.#n)return;this.#n.onopen=null,this.#n.onclose=null,this.#n.onerror=null,this.#n.onmessage=null};#w=async()=>{this.#i.debug("onNegotiationNeeded()");try{this.#d=!0,await this.#h.setLocalDescription(),this.emit("signal",{type:"sdp",data:this.#h.localDescription})}catch(i){this.emit("error",new Error(`Failed to create offer: ${i}`))}finally{this.#d=!1}};#y=(i)=>{if(this.#i.debug(`onIceCandidate(${i.candidate?.candidate})`),i.candidate)this.emit("signal",{type:"candidate",data:i.candidate})};#E=()=>{switch(this.#i.debug(`onIceConnectionStateChange(${this.#h.iceConnectionState})`),this.#h.iceConnectionState){case"disconnected":this.destroy();break;case"failed":this.#h.restartIce();break;default:break}};#k=()=>{this.#i.debug(`onIceGatheringStateChange(${this.#h.iceGatheringState})`)};#$=(i)=>{this.#i.debug("onTrack",i);let h=i.streams[0]??new MediaStream;if(h.onremovetrack=(n)=>{if(this.emit("removetrack",n.track,h),h.getTracks().length===0)this.emit("removestream",h)},i.streams.length)this.emit("stream",h);else h.addTrack(i.track),this.emit("stream",h);this.emit("track",i.track,h)};#j=(i)=>{this.#i.debug("onDataChannel",i),this.#n=i.channel,this.#b()};#x=()=>{this.#i.debug("onChannelOpen"),this.emit("connect")};#T=()=>{this.#i.debug("onChannelClose"),this.destroy()};#M=(i)=>{this.#i.debug("onChannelMessage",i.data);let{data:h}=i;this.emit("data",h)}}import{customAlphabet as j}from"nanoid";var w=j("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",24),u=()=>{return w(10)},y=()=>{return w()};function T(i){return i.target!==void 0}class e extends x{static SESSION_PREFIX="call:";#i;#h;#n;#d;#t;#s;#r;#e;#o=[];#u=new Map;constructor(i){super();if(this.#i=new r("[call]",i.debug??1),this.#i.debug("new Call:",i),T(i))this.#n=i.session??`${e.SESSION_PREFIX}${u()}`,this.#d=i.target,this.#t=!0;else this.#n=i.signal.session,this.#d=i.signal.source,this.#t=!1,this.#o.push(i.signal.signal);if(this.#r=i.rtcConfig,this.#s=i.metadata,this.#h=i.signaling,this.#h.on("signal",this.#c),this.#t)this.#f(!0)}get session(){return this.#n}get metadata(){return this.#s}get initiator(){return this.#t}get ready(){return this.#e?.ready??!1}get target(){return this.#d}hangup=()=>{this.#i.debug("hangup"),this.#e?.destroy(),this.removeAllListeners()};answer=()=>{if(this.#i.debug("answer"),this.initiator)throw new Error("Only non-initiators can answer calls");this.#f()};send=(i)=>{this.#i.debug("send:",i),this.#e?.send(i)};addStream=(i,h)=>{this.#i.debug("addStream:",i.id,h);let n={type:"[artico]",data:{cmd:"stream-meta",payload:{streamId:i.id,metadata:h??""}}};this.#e?.send(JSON.stringify(n)),this.#e?.addStream(i)};removeStream=(i)=>{this.#i.debug("removeStream:",i.id),this.#e?.removeStream(i)};addTrack=(i,h)=>{this.#i.debug("addTrack:",i.id,h.id),this.#e?.addTrack(i,h)};removeTrack=(i)=>{this.#i.debug("removeTrack:",i.id),this.#e?.removeTrack(i)};#c=(i)=>{if(i.session!==this.#n)return;if(this.#i.debug("signal:",i),this.#e)this.#e.signal(i.signal);else this.#o.push(i.signal)};#f=(i=!1)=>{let h=new f({debug:this.#i.logLevel,config:this.#r,initiator:i});this.#e=h;while(this.#o.length>0){let n=this.#o.shift();if(n)h.signal(n)}h.on("signal",(n)=>{let t={target:this.#d,session:this.#n,metadata:this.#s,signal:n};this.#h.signal(t)}),h.on("connect",()=>{this.#i.debug("open:",this.session),this.emit("open")}),h.on("data",(n)=>{if(this.#i.debug("data:",{session:this.session,data:n}),typeof n!=="string"){this.#i.warn("received non-string data:",{session:this.session,data:n});return}try{let t=JSON.parse(n);if(t.type==="[artico]"){let{cmd:s,payload:d}=t.data;switch(s){case"stream-meta":if(this.#i.debug("adding stream metadata:",{session:this.session,streamId:d.streamId,metadata:d.metadata}),d.metadata)this.#u.set(d.streamId,d.metadata);break;default:this.#i.warn("unknown artico command:",{session:this.session,cmd:s})}}else this.emit("data",n)}catch{this.emit("data",n)}}),h.on("stream",(n)=>{let t=this.#u.get(n.id);this.#i.debug("stream:",{session:this.session,stream:n,metadata:t}),this.emit("stream",n,t)}),h.on("removestream",(n)=>{let t=this.#u.get(n.id);this.emit("removestream",n,t),this.#u.delete(n.id)}),h.on("track",(n,t)=>{let s=this.#u.get(t.id);this.#i.debug("track:",{session:this.session,track:n,stream:t,metadata:s}),this.emit("track",n,t,s)}),h.on("removetrack",(n,t)=>{let s=this.#u.get(t.id);this.emit("removetrack",n,t,s)}),h.on("close",()=>{this.#i.log("close:",this.session),this.emit("close")}),h.on("error",(n)=>{this.#i.warn("error:",{session:this.session,error:n}),this.emit("error",n)})}}import{EventEmitter as M}from"eventemitter3";class o extends M{static SESSION_PREFIX="room:";#i;#h;#n;#d;#t;#s=new Map;constructor(i){super();this.#i=new r("[room]",i.debug??1),this.#i.debug("new Room:",i),this.#h=i.roomId,this.#d=o.SESSION_PREFIX+this.#h,this.#n=i.rtcConfig,this.#t=i.signaling,this.#e(),this.#t.join(this.#h,i.metadata)}get id(){return this.#h}get session(){return this.#d}get peers(){return Array.from(this.#s.keys())}leave(){this.#i.debug("leaving room:",this.#h),this.#r()}send(i,h){let n=h?Array.isArray(h)?h:[h]:null;this.#s.forEach((t,s)=>{if(!n||n.includes(s))t.send(i)})}addStream(i,h,n){let t=n?Array.isArray(n)?n:[n]:null;this.#s.forEach((s,d)=>{if(!t||t.includes(d))s.addStream(i,h)})}removeStream(i,h){let n=h?Array.isArray(h)?h:[h]:null;this.#s.forEach((t,s)=>{if(!n||n.includes(s))t.removeStream(i)})}addTrack(i,h,n){let t=n?Array.isArray(n)?n:[n]:null;this.#s.forEach((s,d)=>{if(!t||t.includes(d))s.addTrack(i,h)})}removeTrack(i,h){let n=h?Array.isArray(h)?h:[h]:null;this.#s.forEach((t,s)=>{if(!n||n.includes(s))t.removeTrack(i)})}#r=()=>{this.#o(),this.#s.forEach((i)=>{this.#b(i),i.hangup()}),this.emit("close"),this.removeAllListeners()};#e(){this.#t.on("disconnect",this.#r),this.#t.on("signal",this.#u),this.#t.on("join",this.#c)}#o(){this.#t.off("disconnect",this.#r),this.#t.off("signal",this.#u),this.#t.off("join",this.#c)}#u=(i)=>{if(!i.session.startsWith(this.#d))return;let h=!1;if(this.#s.forEach((n)=>{if(n.session===i.session)h=!0}),!h){let n=new e({debug:this.#i.logLevel,signaling:this.#t,rtcConfig:this.#n,signal:i});this.#s.set(n.target,n),n.answer(),this.#f(n)}};#c=(i,h,n)=>{if(i!==this.#h)return;this.#i.debug("onJoin:",i,h,n);let t=new e({debug:this.#i.logLevel,signaling:this.#t,rtcConfig:this.#n,session:`${this.#d}:${e.SESSION_PREFIX}${u()}`,target:h,metadata:n});this.#s.set(t.target,t),this.#f(t)};#f=(i)=>{i.on("open",this.#S.bind(this,i)),i.on("close",this.#w.bind(this,i)),i.on("data",this.#y.bind(this,i)),i.on("stream",this.#E.bind(this,i)),i.on("removestream",this.#k.bind(this,i)),i.on("track",this.#$.bind(this,i)),i.on("removetrack",this.#j.bind(this,i))};#b=(i)=>{i.off("open",this.#S.bind(this,i)),i.off("close",this.#w.bind(this,i)),i.off("data",this.#y.bind(this,i)),i.off("stream",this.#E.bind(this,i)),i.off("removestream",this.#k.bind(this,i)),i.off("track",this.#$.bind(this,i)),i.off("removetrack",this.#j.bind(this,i))};#S=(i)=>{this.emit("join",i.target,i.metadata)};#w=(i)=>{this.#b(i),this.#s.delete(i.target),this.emit("leave",i.target)};#y=(i,h)=>{this.emit("message",h,i.target)};#E=(i,h,n)=>{this.emit("stream",h,i.target,n)};#k=(i,h,n)=>{this.emit("removestream",h,i.target,n)};#$=(i,h,n,t)=>{this.emit("track",h,n,i.target,t)};#j=(i,h,n,t)=>{this.emit("removetrack",h,n,i.target,t)}}import{EventEmitter as A}from"eventemitter3";import{io as N}from"socket.io-client";class b extends A{#i;#h="disconnected";#n;#d;#t;constructor(i){super();this.#i=new r("[io]",i?.debug??1),this.#i.debug("new SocketSignaling:",i),this.#d=i?.url??"https://0.artico.dev:443",this.#t=i?.id??y(),this.#n=N(this.#d,{autoConnect:!1,transports:["websocket"],query:{id:this.#t}})}get id(){return this.#t}get state(){return this.#h}connect(){this.#i.debug(`connect(${this.#t})`),this.#h="connecting",this.#s(),this.#n.connect()}disconnect(){this.#i.debug("disconnect()"),this.#r(),this.#n.disconnect(),this.#h="disconnected"}signal(i){if(this.#h!=="ready"){this.emit("error",new Error("Cannot send message until signaling is ready"));return}this.#i.debug("tx signal:",i),this.#n.emit("signal",i)}join(i,h){if(this.#h!=="ready"){this.emit("error",new Error("Cannot join room until signaling is ready"));return}this.#i.debug(`join(${i}, ${h})`),this.#n.emit("join",i,h)}#s(){this.#n.on("connect",this.#e.bind(this)),this.#n.on("disconnect",this.#o.bind(this)),this.#n.on("connect_error",this.#u.bind(this)),this.#n.on("open",this.#c.bind(this)),this.#n.on("error",this.#f.bind(this)),this.#n.on("signal",this.#b.bind(this)),this.#n.on("join",this.#S.bind(this))}#r(){this.#n.removeAllListeners()}#e(){this.#i.debug("connect"),this.#h="connected"}#o(){this.#i.debug("disconnect"),this.#h="disconnected",this.emit("disconnect"),this.#r()}#u(i){this.#i.debug("connect_error:",i.message),this.emit("error",new Error("connect-error"))}#c(i){this.#i.debug("open:",i),this.#h="ready",this.emit("connect",i)}#f(i){this.#i.debug("error:",i),this.emit("error",new Error(i)),this.#r(),this.#n.disconnect(),this.#h="disconnected",this.emit("disconnect")}#b(i){this.#i.debug("rx signal:",i),this.emit("signal",i)}#S(i,h,n){this.#i.debug("join:",{roomId:i,peerId:h,metadata:n}),this.emit("join",i,h,n)}}class E extends O{#i;#h;#n=new Map;#d;constructor(i){super();this.#i=new r("[artico]",i?.debug??1),this.#i.debug("new Artico:",i),this.#h=i?.signaling??new b({debug:this.#i.logLevel,id:i?.id}),this.#d=i?.rtcConfig,this.#t(),this.#h.connect()}get id(){return this.#h.id}get state(){return this.#h.state}call=(i,h)=>{if(this.#i.debug(`call(${i}, ${h})`),this.#h.state!=="ready")throw new Error("Cannot call peers until signaling is ready.");let n=new e({signaling:this.#h,debug:this.#i.logLevel,target:i,metadata:h,rtcConfig:this.#d});return this.#n.set(n.session,n),n};join=(i,h)=>{if(this.#i.debug("join:",i,h),this.#h.state!=="ready")throw new Error("Cannot join room until signaling is ready.");return new o({debug:this.#i.logLevel,signaling:this.#h,roomId:i,metadata:h})};close=()=>{this.#i.debug("close"),this.removeAllListeners(),this.#s(),this.#h.disconnect(),this.emit("close")};#t(){this.#h.on("error",this.#r.bind(this)),this.#h.on("connect",this.#e.bind(this)),this.#h.on("disconnect",this.#o.bind(this)),this.#h.on("signal",this.#u.bind(this))}#s(){this.#h.off("error",this.#r.bind(this)),this.#h.off("connect",this.#e.bind(this)),this.#h.off("disconnect",this.#o.bind(this)),this.#h.off("signal",this.#u.bind(this))}#r(i){this.emit("error",i)}#e(i){this.emit("open",i)}#o(){this.emit("close"),this.removeAllListeners(),this.#s()}#u(i){if(i.session.startsWith(e.SESSION_PREFIX)&&!this.#n.has(i.session)){this.#i.debug("call from",i.source,i.signal);let h=new e({debug:this.#i.logLevel,signaling:this.#h,metadata:i.metadata,signal:i,rtcConfig:this.#d});this.#n.set(h.session,h),this.emit("call",h)}}}export{E as default,b as SocketSignaling,E as Artico};