@dobuki/hello-worker 1.0.84 → 1.0.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/enter-world.d.ts +2 -1
- package/dist/enter-world.d.ts.map +1 -1
- package/dist/enter-world.js +780 -2
- package/dist/enter-world.js.map +5 -5
- package/dist/index.js +1010 -3
- package/dist/index.js.map +8 -7
- package/dist/listeners.js +22 -2
- package/dist/listeners.js.map +2 -2
- package/dist/sample/index.d.ts.map +1 -1
- package/dist/signal/impl/signal-room.d.ts +1 -0
- package/dist/signal/impl/signal-room.d.ts.map +1 -1
- package/dist/signal/signal-room.d.ts +2 -1
- package/dist/signal/signal-room.d.ts.map +1 -1
- package/dist/signal/signal-room.worker.d.ts +1 -0
- package/dist/signal/signal-room.worker.d.ts.map +1 -1
- package/dist/web-components/sync-button.d.ts +1 -0
- package/dist/web-components/sync-button.d.ts.map +1 -1
- package/dist/webrtc-peer-collector.d.ts +2 -1
- package/dist/webrtc-peer-collector.d.ts.map +1 -1
- package/dist/webrtc-peer-collector.js +632 -2
- package/dist/webrtc-peer-collector.js.map +5 -5
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,944 @@
|
|
|
1
|
-
class N{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(c,F){this.icePromiseResolve?.({url:c,expiration:F}),this.icePromiseResolve=void 0,this.icePromise=void 0}addRequester(c){return this.sendToServerFunctions.push(c),()=>{this.removeRequester(c)}}removeRequester(c){this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(c),1)}sendToServer(c){this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length*Math.random())](c)}async requestIce(){if(!this.icePromise)this.icePromise=new Promise((c)=>{this.icePromiseResolve=c,this.sendToServer("request-ice")});return await this.icePromise}}function r(c){let{userId:F,worldId:R,room:W,host:$,autoRejoin:v=!0,logLine:k}=c,Q=!1,X=0,E,w,V=!0,S=new Map,x=`wss://${$}/room/${R}/${W}?userId=${encodeURIComponent(F)}`,D=[],K=0;function Y(q,h,T){if(!E)return k?.("\uD83D\uDC64 ➡️ ❌","no ws available"),!1;let C={type:q,to:h,payload:T};if(D.push(C),k?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️",C),clearTimeout(K),Q||E.readyState!==WebSocket.OPEN)return k?.("\uD83D\uDC64 ➡️ ❌","Not in opened state: "+E.readyState),!1;return K=setTimeout(()=>{E.send(JSON.stringify(D)),D.length=0}),!0}function Z(){if(Q)return;E=new WebSocket(x),E.onopen=()=>{if(V)c.onOpen?.(),V=!1;X=0},E.onmessage=(q)=>{try{let h=JSON.parse(q.data);(Array.isArray(h)?h:[h]).forEach((C)=>{if(k?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64",C),C.type==="peer-joined"||C.type==="peer-left")u(C.users,C);else if(C.type==="ice-server")c.onIceUrl?.(C.url,C.expiration);else if(C.userId)c.onMessage(C.type,C.payload,C.userId)})}catch{k?.("⚠️ ERROR",{error:"invalid-json"})}},E.onclose=(q)=>{let T=[1001,1006,1011,1012,1013].includes(q.code);if(v&&!Q&&T){let C=Math.min(Math.pow(2,X)*1000,15000),H=Math.random()*1000,G=C+H;k?.("\uD83D\uDD04 RECONNECTING",{attempt:X+1,delayMs:Math.round(G)}),X++,w=setTimeout(Z,G)}else c.onClose?.({code:q.code,reason:q.reason,wasClean:q.wasClean})},E.onerror=(q)=>{console.error("WS Error",q),c.onError?.()}}function u(q,h){let T=[],C=[],H=new Set,G=q.filter((M)=>M.userId===F)[0];if(!G){k?.("⚠️","Cannot find self in updated users");return}let z=G.joined;q.forEach(({userId:M,joined:O})=>{if(M===F)return;if(!S.has(M)||h.type==="peer-joined"&&M===h.userId){let n={userId:M,joined:O};S.set(M,n),T.push(n)}H.add(M)});for(let M of S.keys())if(!H.has(M)||h.type==="peer-left"&&M===h.userId)S.delete(M),C.push({userId:M});if(T.length)c.onPeerJoined(T,z);if(C.length)c.onPeerLeft(C)}return Z(),{send(q,h,T){Y(q,h,T)},exitRoom:()=>{Q=!0,clearTimeout(w),E.close()}}}function P({userId:c,worldId:F,room:R,host:W,autoRejoin:$=!0,onOpen:v,onClose:k,onError:Q,onPeerJoined:X,onPeerLeft:E,onIceUrl:w,onMessage:V,logLine:S,workerUrl:x}){if(!x)return console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:","https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js"),r({userId:c,worldId:F,room:R,host:W,autoRejoin:$,onOpen:v,onClose:k,onError:Q,onPeerJoined:X,onPeerLeft:E,onIceUrl:w,onMessage:V});let D=new Worker(x,{type:"module"}),K=!1,Y=(Z)=>{let u=Z.data;if(u.kind==="open")v?.();else if(u.kind==="close")D.terminate(),k?.(u.ev);else if(u.kind==="error")Q?.();else if(u.kind==="peer-joined")X(u.users.map((q)=>({userId:q.userId,joined:q.joined})),u.joined);else if(u.kind==="peer-left")E(u.users);else if(u.kind==="ice-server")w?.(u.url,u.expiration);else if(u.kind==="message")V(u.type,u.payload,u.fromUserId);else if(u.kind==="log")S?.(u.direction,u.obj)};return D.addEventListener("message",Y),D.postMessage({cmd:"enter",userId:c,worldId:F,room:R,host:W,autoRejoin:$}),{exitRoom:()=>{K=!0,D.removeEventListener("message",Y),D.postMessage({cmd:"exit"})},send:(Z,u,q)=>{D.postMessage({cmd:"send",toUserId:u,host:W,room:R,type:Z,payload:q})}}}var j={iceServers:[{urls:"stun:stun.l.google.com:19302"}]};class J{iceUrlProvider;constructor(c){this.iceUrlProvider=c}rtcConfig={...j,timestamp:Date.now()};rtcConfigPromise;async getRtcConfig(){if(Date.now()-(this.rtcConfig?.timestamp??0)<1e4)return this.rtcConfig;if(!this.rtcConfigPromise)this.rtcConfigPromise=new Promise(async(F)=>{let R=3;for(let W=0;W<R;W++)try{let $=(await this.iceUrlProvider.requestIce()).url,v=await fetch($);if(!v.ok)throw Error(`ICE endpoint failed: ${v.status}`);let k=await v.json();F(k);return}catch($){console.warn("Failed fetching iceUrl")}}),this.rtcConfig=await this.rtcConfigPromise,this.rtcConfigPromise=void 0;return this.rtcConfig}}var s=P;function _({userId:c,worldId:F,receivePeerConnection:R,peerlessUserExpiration:W=5000,enterRoomFunction:$=s,logLine:v,onLeaveUser:k,workerUrl:Q,onRoomReady:X,onRoomClose:E,onBroadcastMessage:w}){let V=c??`user-${crypto.randomUUID()}`,S=new Map,x=new Map;function D(h){k?.(h);let T=S.get(h);if(!T)return;S.delete(h);try{T.close()}catch{}}async function K(h){if(!h.connection?.pc?.remoteDescription)return;let T=h.connection.pendingRemoteIce;h.connection.pendingRemoteIce=[];for(let C of T)try{await h.connection.pc.addIceCandidate(C)}catch(H){v?.("⚠️ ERROR",{error:"add-ice-failed",userId:h.peer,detail:String(H)})}}let Y=new N,Z=new J(Y);function u({room:h,host:T}){let C=`${T}/room/${h}`,H=x.get(C);if(H)H.exitRoom(),x.delete(C)}function q({room:h,host:T}){return new Promise(async(C,H)=>{async function G(i){if(i.connectionPromise)return i.connectionPromise;let b=new Promise(async(t)=>{i.connection={id:`conn-${crypto.randomUUID()}`,pc:new RTCPeerConnection(await Z.getRtcConfig()),pendingRemoteIce:[]},i.connection.pc.onicecandidate=(f)=>{if(!f.candidate)return;n("ice",i.peer,{connectionId:i.connection?.id,ice:f.candidate.toJSON()})},i.connection.pc.onconnectionstatechange=async()=>{if(v?.("\uD83D\uDCAC",{event:"pc-state",userId:i.peer,state:i.connection?.pc?.connectionState}),i.connection?.pc?.connectionState==="failed"){i.close();let f=await z(i.peer,!0);if(f.connection?.pc)R({pc:f.connection?.pc,userId:f.peer,restart:()=>f.close()});else v?.("\uD83D\uDC64ℹ️","no pc: "+f.peer);return}},t(i.connection)});return i.connectionPromise=b,await b,i.connectionPromise=void 0,b}async function z(i,b){let t=S.get(i);if(!t||b){let f={peer:i,close(){if(this.connection)this.connection.pc.close(),this.connection=void 0;S.delete(i)},async reset(){f.close(),setTimeout(async()=>{let B=await z(i,!0);if(!B.connection?.pc){v?.("⚠️","no pc");return}R({pc:B.connection?.pc,userId:B.peer,restart:()=>B.close()}),await M(B.peer)},500)}};t=f,await G(f),S.set(t.peer,t)}else if(t){if(clearTimeout(t.expirationTimeout),t.expirationTimeout=0,!t.connection?.pc||t.connection?.pc.signalingState==="closed")await G(t)}return t.peer=i,t}async function M(i){let b=await z(i),t=b.connection?.pc,f=await t?.createOffer();await t?.setLocalDescription(f),n("offer",i,{connectionId:b.connection?.id,offer:t?.localDescription?.toJSON()})}let{exitRoom:O,send:n}=$({userId:V,worldId:F,room:h,host:T,logLine:v,workerUrl:Q,autoRejoin:!0,onOpen(){X?.({room:h,host:T}),C()},onError(){console.error("onError"),H()},onClose(i){E?.({room:h,host:T,ev:i})},onPeerJoined(i,b){i.forEach(async(t)=>{let f=await z(t.userId,!0);f.joined=t.joined;let B=f.connection?.pc;if(!B){v?.("\uD83D\uDC64ℹ️","no pc: "+t.userId);return}if(R({pc:B,userId:t.userId,restart:()=>f.close()}),t.joined>b||t.joined===b&&t.userId.localeCompare(V)>0)await M(t.userId)})},onPeerLeft(i){i.forEach(({userId:b})=>{let t=S.get(b);if(!t)return;t.expirationTimeout=setTimeout(()=>D(b),W??0)})},onIceUrl(i,b){Y.receiveIce(i,b)},async onMessage(i,b,t){if(i==="offer"&&b.offer){let f=await z(t,!1),B=!f.connection||f.connection.pc.signalingState==="stable"?await G(f):f.connection;v?.("\uD83D\uDCAC",{type:i,signalingState:B.pc.signalingState}),B.peerConnectionId=b.connectionId,R({pc:B.pc,userId:t,restart:()=>f.close()}),await B.pc.setRemoteDescription(b.offer);let d=await B.pc.createAnswer();await B.pc.setLocalDescription(d),n("answer",t,{connectionId:B.id,answer:B.pc.localDescription?.toJSON()}),await K(f);return}if(i==="answer"&&b.answer){let f=await z(t,!1),B=f.connection&&f.connection.pc.signalingState!=="closed"?f.connection:await G(f);v?.("\uD83D\uDCAC",{type:i,signalingState:B.pc.signalingState}),await B.pc.setRemoteDescription(b.answer),B.peerConnectionId=b.connectionId,await K(f);return}if(i==="ice"&&b.ice){let f=await z(t,!1),B=f.connection??await f.connectionPromise;if(!B){v?.("⚠️","No connection");return}if(v?.("\uD83D\uDCAC",{type:i,signalingState:B.pc.signalingState}),B.peerConnectionId&&b.connectionId!==B.peerConnectionId){v?.("⚠️","Mismatch peerConnectionID"+b.connectionId+"vs"+B.peerConnectionId);return}if(!B.pc.remoteDescription||!B.peerConnectionId){B.peerConnectionId=b.connectionId,B.pendingRemoteIce.push(b.ice);return}try{await B.pc.addIceCandidate(b.ice)}catch(d){v?.("⚠️ ERROR",{error:"add-ice-failed",userId:f.peer,detail:String(d)})}return}if(i==="broadcast")w?.(b,t)}}),A=Y.addRequester((i)=>n(i,"server"));x.set(`${T}/room/${h}`,{exitRoom:()=>{O(),A()},room:h,host:T,broadcast:(i)=>n("broadcast","server",i)})})}return{userId:V,enterRoom:q,exitRoom:u,leaveUser:D,async reset(h){S.get(h)?.reset()},broadcast(h){x.forEach((T)=>T.broadcast(h))},end(){x.forEach(({exitRoom:h})=>h()),x.clear(),S.forEach(({peer:h})=>D(h)),S.clear()}}}function g({userId:c,worldId:F,logLine:R,enterRoomFunction:W=P,peerlessUserExpiration:$,workerUrl:v,onRoomReady:k,onRoomClose:Q,dataChannelOptions:X}){let E=new Set,w=new Map,V=new Set,S=new Set;function x(n,A,i){function b(f){let B=f.channel;K(A,B,i),w.set(A,B)}n.addEventListener("datachannel",b);let t=n.createDataChannel("data",X);return K(A,t,i),w.set(A,t),()=>{n.removeEventListener("datachannel",b)}}function D(n,A){S.forEach((i)=>i(n,A))}function K(n,A,i){A.onopen=()=>{R?.("\uD83D\uDCAC",{event:"dc-open",userId:n}),E.add(n),V.forEach((t)=>t(n,"join",[...E]))};let b=({data:t})=>{D(t,n)};A.addEventListener("message",b),A.onclose=()=>{R?.("\uD83D\uDCAC",{event:"dc-close",userId:n}),E.delete(n),V.forEach((t)=>t(n,"leave",[...E])),A.removeEventListener("message",b),A.onopen=null,A.onclose=null,A.onerror=null,A.close(),i?.()},A.onerror=()=>R?.("⚠️ ERROR",{error:"dc-error",userId:n})}let{userId:Y,enterRoom:Z,exitRoom:u,leaveUser:q,broadcast:h,end:T,reset:C}=_({userId:c,worldId:F,enterRoomFunction:W,logLine:R,workerUrl:v,peerlessUserExpiration:$,onRoomReady:k,onRoomClose:Q,onLeaveUser(n){let A=w.get(n);try{A?.close()}catch{}w.delete(n)},receivePeerConnection({pc:n,userId:A,restart:i}){x(n,A,i)},onBroadcastMessage(n,A){D(n,A),R?.("\uD83D\uDCE2",{event:"broadcast",userId:Y,data:n})}});function H(n,A){w.forEach((i,b)=>{if(A&&b!==A)return;if(i.readyState==="open")i.send(n)})}function G(n){S.delete(n)}function z(n){return S.add(n),()=>{G(n)}}function M(n){V.delete(n)}function O(n){return V.add(n),()=>{M(n)}}return{userId:Y,send:H,broadcast:h,enterRoom:Z,exitRoom:u,leaveUser:q,getUsers:()=>[...E],addMessageListener:z,removeMessageListener:G,addUserListener:O,removeUserListener:M,reset(){E.forEach((n)=>{w.get(n)?.close(),w.delete(n),C(n)})},end(){w.forEach((n)=>{try{n.close()}catch{}}),w.clear(),T(),V.clear(),E.clear()}}}var l=g({worldId:"sync-buttons",dataChannelOptions:{ordered:!1},logLine:console.log});l.enterRoom({room:"sync-button",host:"hello.dobuki.net"});class L extends HTMLElement{static observedAttributes=["id","disabled"];shadowButton;slotEl;constructor(){super();this.attachShadow({mode:"open"}),this.onMessage=this.onMessage.bind(this),this.onClickButton=this.onClickButton.bind(this),this.shadowRoot.innerHTML=`
|
|
1
|
+
// src/browser/utils/ice-url-provider.ts
|
|
2
|
+
class IceUrlProvider {
|
|
3
|
+
sendToServerFunctions = new Array;
|
|
4
|
+
icePromiseResolve;
|
|
5
|
+
icePromise;
|
|
6
|
+
receiveIce(url, expiration) {
|
|
7
|
+
this.icePromiseResolve?.({ url, expiration });
|
|
8
|
+
this.icePromiseResolve = undefined;
|
|
9
|
+
this.icePromise = undefined;
|
|
10
|
+
}
|
|
11
|
+
addRequester(requester) {
|
|
12
|
+
this.sendToServerFunctions.push(requester);
|
|
13
|
+
return () => {
|
|
14
|
+
this.removeRequester(requester);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
removeRequester(requester) {
|
|
18
|
+
this.sendToServerFunctions.splice(this.sendToServerFunctions.indexOf(requester), 1);
|
|
19
|
+
}
|
|
20
|
+
sendToServer(command) {
|
|
21
|
+
this.sendToServerFunctions[Math.floor(this.sendToServerFunctions.length * Math.random())](command);
|
|
22
|
+
}
|
|
23
|
+
async requestIce() {
|
|
24
|
+
if (!this.icePromise) {
|
|
25
|
+
this.icePromise = new Promise((resolve) => {
|
|
26
|
+
this.icePromiseResolve = resolve;
|
|
27
|
+
this.sendToServer("request-ice");
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return await this.icePromise;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/browser/signal/impl/signal-room.ts
|
|
35
|
+
function enterRoom(params) {
|
|
36
|
+
const {
|
|
37
|
+
userId,
|
|
38
|
+
worldId,
|
|
39
|
+
room,
|
|
40
|
+
host,
|
|
41
|
+
protocol,
|
|
42
|
+
autoRejoin = true,
|
|
43
|
+
logLine
|
|
44
|
+
} = params;
|
|
45
|
+
let exited = false;
|
|
46
|
+
let retryCount = 0;
|
|
47
|
+
let ws;
|
|
48
|
+
let timeoutId;
|
|
49
|
+
let initialConnection = true;
|
|
50
|
+
const peers = new Map;
|
|
51
|
+
const wsUrl = `${protocol ?? "wss"}://${host}/room/${worldId}/${room}?userId=${encodeURIComponent(userId)}`;
|
|
52
|
+
const accumulatedMessages = [];
|
|
53
|
+
let timeout = 0;
|
|
54
|
+
function send(type, to, payload) {
|
|
55
|
+
if (!ws) {
|
|
56
|
+
logLine?.("\uD83D\uDC64 ➡️ ❌", "no ws available");
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const obj = { type, to, payload };
|
|
60
|
+
accumulatedMessages.push(obj);
|
|
61
|
+
logLine?.("\uD83D\uDC64 ➡️ \uD83D\uDDA5️", obj);
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
if (exited || ws.readyState !== WebSocket.OPEN) {
|
|
64
|
+
logLine?.("\uD83D\uDC64 ➡️ ❌", "Not in opened state: " + ws.readyState);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
timeout = setTimeout(() => {
|
|
68
|
+
ws.send(JSON.stringify(accumulatedMessages));
|
|
69
|
+
accumulatedMessages.length = 0;
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function connect() {
|
|
74
|
+
if (exited)
|
|
75
|
+
return;
|
|
76
|
+
ws = new WebSocket(wsUrl);
|
|
77
|
+
ws.onopen = () => {
|
|
78
|
+
if (initialConnection) {
|
|
79
|
+
params.onOpen?.();
|
|
80
|
+
initialConnection = false;
|
|
81
|
+
}
|
|
82
|
+
retryCount = 0;
|
|
83
|
+
};
|
|
84
|
+
ws.onmessage = (e) => {
|
|
85
|
+
try {
|
|
86
|
+
const result = JSON.parse(e.data);
|
|
87
|
+
const msgs = Array.isArray(result) ? result : [result];
|
|
88
|
+
msgs.forEach((msg) => {
|
|
89
|
+
logLine?.("\uD83D\uDDA5️ ➡️ \uD83D\uDC64", msg);
|
|
90
|
+
if (msg.type === "peer-joined" || msg.type === "peer-left") {
|
|
91
|
+
updatePeers(msg.users, msg);
|
|
92
|
+
} else if (msg.type === "ice-server") {
|
|
93
|
+
params.onIceUrl?.(msg.url, msg.expiration);
|
|
94
|
+
} else if (msg.userId) {
|
|
95
|
+
params.onMessage(msg.type, msg.payload, msg.userId);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
} catch {
|
|
99
|
+
logLine?.("⚠️ ERROR", { error: "invalid-json" });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
ws.onclose = (ev) => {
|
|
103
|
+
const recoverableCodes = [1001, 1006, 1011, 1012, 1013];
|
|
104
|
+
const isRecoverable = recoverableCodes.includes(ev.code);
|
|
105
|
+
if (autoRejoin && !exited && isRecoverable) {
|
|
106
|
+
const backoff = Math.min(Math.pow(2, retryCount) * 1000, 15000);
|
|
107
|
+
const jitter = Math.random() * 1000;
|
|
108
|
+
const delay = backoff + jitter;
|
|
109
|
+
logLine?.("\uD83D\uDD04 RECONNECTING", {
|
|
110
|
+
attempt: retryCount + 1,
|
|
111
|
+
delayMs: Math.round(delay)
|
|
112
|
+
});
|
|
113
|
+
retryCount++;
|
|
114
|
+
timeoutId = setTimeout(connect, delay);
|
|
115
|
+
} else {
|
|
116
|
+
params.onClose?.({
|
|
117
|
+
code: ev.code,
|
|
118
|
+
reason: ev.reason,
|
|
119
|
+
wasClean: ev.wasClean
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
ws.onerror = (ev) => {
|
|
124
|
+
console.error("WS Error", ev);
|
|
125
|
+
params.onError?.();
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function updatePeers(updatedUsers, msg) {
|
|
129
|
+
const joined = [];
|
|
130
|
+
const left = [];
|
|
131
|
+
const updatedPeerSet = new Set;
|
|
132
|
+
const selfPeer = updatedUsers.filter((peer) => peer.userId === userId)[0];
|
|
133
|
+
if (!selfPeer) {
|
|
134
|
+
logLine?.("⚠️", "Cannot find self in updated users");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const selfJoined = selfPeer.joined;
|
|
138
|
+
updatedUsers.forEach(({ userId: pUserId, joined: joinedTime }) => {
|
|
139
|
+
if (pUserId === userId)
|
|
140
|
+
return;
|
|
141
|
+
if (!peers.has(pUserId) || msg.type === "peer-joined" && pUserId === msg.userId) {
|
|
142
|
+
const newPeer = {
|
|
143
|
+
userId: pUserId,
|
|
144
|
+
joined: joinedTime
|
|
145
|
+
};
|
|
146
|
+
peers.set(pUserId, newPeer);
|
|
147
|
+
joined.push(newPeer);
|
|
148
|
+
}
|
|
149
|
+
updatedPeerSet.add(pUserId);
|
|
150
|
+
});
|
|
151
|
+
for (const pUserId of peers.keys()) {
|
|
152
|
+
if (!updatedPeerSet.has(pUserId) || msg.type === "peer-left" && pUserId === msg.userId) {
|
|
153
|
+
peers.delete(pUserId);
|
|
154
|
+
left.push({ userId: pUserId });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (joined.length)
|
|
158
|
+
params.onPeerJoined(joined, selfJoined);
|
|
159
|
+
if (left.length)
|
|
160
|
+
params.onPeerLeft(left);
|
|
161
|
+
}
|
|
162
|
+
connect();
|
|
163
|
+
return {
|
|
164
|
+
send(type, userId2, payload) {
|
|
165
|
+
send(type, userId2, payload);
|
|
166
|
+
},
|
|
167
|
+
exitRoom: () => {
|
|
168
|
+
exited = true;
|
|
169
|
+
clearTimeout(timeoutId);
|
|
170
|
+
ws.close();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/browser/signal/signal-room.ts
|
|
176
|
+
function enterRoom2({
|
|
177
|
+
userId,
|
|
178
|
+
worldId,
|
|
179
|
+
room,
|
|
180
|
+
protocol = "wss",
|
|
181
|
+
host,
|
|
182
|
+
autoRejoin = true,
|
|
183
|
+
onOpen,
|
|
184
|
+
onClose,
|
|
185
|
+
onError,
|
|
186
|
+
onPeerJoined,
|
|
187
|
+
onPeerLeft,
|
|
188
|
+
onIceUrl,
|
|
189
|
+
onMessage,
|
|
190
|
+
logLine,
|
|
191
|
+
workerUrl
|
|
192
|
+
}) {
|
|
193
|
+
if (!workerUrl) {
|
|
194
|
+
const CDN_WORKER_URL = `https://cdn.jsdelivr.net/npm/@dobuki/hello-worker/dist/signal-room.worker.min.js`;
|
|
195
|
+
console.warn("Warning: enterRoom called without workerUrl; this may cause issues in some environments. You should pass workerUrl explicitly. Use:", CDN_WORKER_URL);
|
|
196
|
+
return enterRoom({
|
|
197
|
+
userId,
|
|
198
|
+
worldId,
|
|
199
|
+
room,
|
|
200
|
+
protocol,
|
|
201
|
+
host,
|
|
202
|
+
autoRejoin,
|
|
203
|
+
onOpen,
|
|
204
|
+
onClose,
|
|
205
|
+
onError,
|
|
206
|
+
onPeerJoined,
|
|
207
|
+
onPeerLeft,
|
|
208
|
+
onIceUrl,
|
|
209
|
+
onMessage
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
let worker;
|
|
213
|
+
const res = fetch(workerUrl).then(async (res2) => {
|
|
214
|
+
if (!res2.ok) {
|
|
215
|
+
throw new Error(`Failed to load worker script: ${res2.status}`);
|
|
216
|
+
}
|
|
217
|
+
const source = await res2.text();
|
|
218
|
+
const blob = new Blob([source], { type: "text/javascript" });
|
|
219
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
220
|
+
worker = new Worker(blobUrl, { type: "module" });
|
|
221
|
+
worker.addEventListener("message", onWorkerMessage);
|
|
222
|
+
worker.postMessage({
|
|
223
|
+
cmd: "enter",
|
|
224
|
+
userId,
|
|
225
|
+
worldId,
|
|
226
|
+
room,
|
|
227
|
+
protocol,
|
|
228
|
+
host,
|
|
229
|
+
autoRejoin
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
let exited = false;
|
|
233
|
+
const onWorkerMessage = (e) => {
|
|
234
|
+
const ev = e.data;
|
|
235
|
+
if (ev.kind === "open")
|
|
236
|
+
onOpen?.();
|
|
237
|
+
else if (ev.kind === "close") {
|
|
238
|
+
worker?.terminate();
|
|
239
|
+
onClose?.(ev.ev);
|
|
240
|
+
} else if (ev.kind === "error")
|
|
241
|
+
onError?.();
|
|
242
|
+
else if (ev.kind === "peer-joined")
|
|
243
|
+
onPeerJoined(ev.users.map((ev2) => ({ userId: ev2.userId, joined: ev2.joined })), ev.joined);
|
|
244
|
+
else if (ev.kind === "peer-left")
|
|
245
|
+
onPeerLeft(ev.users);
|
|
246
|
+
else if (ev.kind === "ice-server")
|
|
247
|
+
onIceUrl?.(ev.url, ev.expiration);
|
|
248
|
+
else if (ev.kind === "message")
|
|
249
|
+
onMessage(ev.type, ev.payload, ev.fromUserId);
|
|
250
|
+
else if (ev.kind === "log")
|
|
251
|
+
logLine?.(ev.direction, ev.obj);
|
|
252
|
+
};
|
|
253
|
+
return {
|
|
254
|
+
exitRoom: () => {
|
|
255
|
+
exited = true;
|
|
256
|
+
worker?.removeEventListener("message", onWorkerMessage);
|
|
257
|
+
worker?.postMessage({ cmd: "exit" });
|
|
258
|
+
},
|
|
259
|
+
send: (type, toUserId, payload) => {
|
|
260
|
+
worker?.postMessage({
|
|
261
|
+
cmd: "send",
|
|
262
|
+
toUserId,
|
|
263
|
+
host,
|
|
264
|
+
room,
|
|
265
|
+
type,
|
|
266
|
+
payload
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/browser/utils/rtc-config.ts
|
|
273
|
+
var FALLBACK_RTC_CONFIG = {
|
|
274
|
+
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
class RTCConfigProvider {
|
|
278
|
+
iceUrlProvider;
|
|
279
|
+
constructor(iceUrlProvider) {
|
|
280
|
+
this.iceUrlProvider = iceUrlProvider;
|
|
281
|
+
}
|
|
282
|
+
rtcConfig = {
|
|
283
|
+
...FALLBACK_RTC_CONFIG,
|
|
284
|
+
timestamp: Date.now()
|
|
285
|
+
};
|
|
286
|
+
rtcConfigPromise;
|
|
287
|
+
async getRtcConfig() {
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
if (now - (this.rtcConfig?.timestamp ?? 0) < 1e4) {
|
|
290
|
+
return this.rtcConfig;
|
|
291
|
+
}
|
|
292
|
+
if (!this.rtcConfigPromise) {
|
|
293
|
+
this.rtcConfigPromise = new Promise(async (resolve) => {
|
|
294
|
+
let retries = 3;
|
|
295
|
+
for (let r = 0;r < retries; r++) {
|
|
296
|
+
try {
|
|
297
|
+
const iceUrl = (await this.iceUrlProvider.requestIce()).url;
|
|
298
|
+
const r2 = await fetch(iceUrl);
|
|
299
|
+
if (!r2.ok)
|
|
300
|
+
throw new Error(`ICE endpoint failed: ${r2.status}`);
|
|
301
|
+
const rtcConfig = await r2.json();
|
|
302
|
+
resolve(rtcConfig);
|
|
303
|
+
return;
|
|
304
|
+
} catch (e) {
|
|
305
|
+
console.warn("Failed fetching iceUrl");
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
this.rtcConfig = await this.rtcConfigPromise;
|
|
310
|
+
this.rtcConfigPromise = undefined;
|
|
311
|
+
}
|
|
312
|
+
return this.rtcConfig;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/browser/webrtc-peer-collector.ts
|
|
317
|
+
var DEFAULT_ENTER_ROOM = enterRoom2;
|
|
318
|
+
function collectPeerConnections({
|
|
319
|
+
userId: passedUserId,
|
|
320
|
+
worldId,
|
|
321
|
+
receivePeerConnection,
|
|
322
|
+
peerlessUserExpiration = 5000,
|
|
323
|
+
enterRoomFunction: enterRoom3 = DEFAULT_ENTER_ROOM,
|
|
324
|
+
logLine,
|
|
325
|
+
onLeaveUser,
|
|
326
|
+
workerUrl,
|
|
327
|
+
onRoomReady,
|
|
328
|
+
onRoomClose,
|
|
329
|
+
onBroadcastMessage
|
|
330
|
+
}) {
|
|
331
|
+
const userId = passedUserId ?? `user-${crypto.randomUUID()}`;
|
|
332
|
+
const users = new Map;
|
|
333
|
+
const roomsEntered = new Map;
|
|
334
|
+
function leaveUser(userId2) {
|
|
335
|
+
onLeaveUser?.(userId2);
|
|
336
|
+
const p = users.get(userId2);
|
|
337
|
+
if (!p)
|
|
338
|
+
return;
|
|
339
|
+
users.delete(userId2);
|
|
340
|
+
try {
|
|
341
|
+
p.close();
|
|
342
|
+
} catch {}
|
|
343
|
+
}
|
|
344
|
+
async function flushRemoteIce(state) {
|
|
345
|
+
if (!state.connection?.pc?.remoteDescription)
|
|
346
|
+
return;
|
|
347
|
+
const queued = state.connection.pendingRemoteIce;
|
|
348
|
+
state.connection.pendingRemoteIce = [];
|
|
349
|
+
for (const ice of queued) {
|
|
350
|
+
try {
|
|
351
|
+
await state.connection.pc.addIceCandidate(ice);
|
|
352
|
+
} catch (e) {
|
|
353
|
+
logLine?.("⚠️ ERROR", {
|
|
354
|
+
error: "add-ice-failed",
|
|
355
|
+
userId: state.peer,
|
|
356
|
+
detail: String(e)
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const iceUrlProvider = new IceUrlProvider;
|
|
362
|
+
const rtcConfigProvider = new RTCConfigProvider(iceUrlProvider);
|
|
363
|
+
function exit({ room, host }) {
|
|
364
|
+
const key = `${host}/room/${room}`;
|
|
365
|
+
const session = roomsEntered.get(key);
|
|
366
|
+
if (session) {
|
|
367
|
+
session.exitRoom();
|
|
368
|
+
roomsEntered.delete(key);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function enter({
|
|
372
|
+
room,
|
|
373
|
+
host,
|
|
374
|
+
protocol
|
|
375
|
+
}) {
|
|
376
|
+
return new Promise(async (resolve, reject) => {
|
|
377
|
+
async function setupConnection(state) {
|
|
378
|
+
if (state.connectionPromise) {
|
|
379
|
+
return state.connectionPromise;
|
|
380
|
+
}
|
|
381
|
+
const promise = new Promise(async (resolve2) => {
|
|
382
|
+
state.connection = {
|
|
383
|
+
id: `conn-${crypto.randomUUID()}`,
|
|
384
|
+
pc: new RTCPeerConnection(await rtcConfigProvider.getRtcConfig()),
|
|
385
|
+
pendingRemoteIce: []
|
|
386
|
+
};
|
|
387
|
+
state.connection.pc.onicecandidate = (ev) => {
|
|
388
|
+
if (!ev.candidate)
|
|
389
|
+
return;
|
|
390
|
+
send("ice", state.peer, {
|
|
391
|
+
connectionId: state.connection?.id,
|
|
392
|
+
ice: ev.candidate.toJSON()
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
state.connection.pc.onconnectionstatechange = async () => {
|
|
396
|
+
logLine?.("\uD83D\uDCAC", {
|
|
397
|
+
event: "pc-state",
|
|
398
|
+
userId: state.peer,
|
|
399
|
+
state: state.connection?.pc?.connectionState
|
|
400
|
+
});
|
|
401
|
+
if (state.connection?.pc?.connectionState === "failed") {
|
|
402
|
+
state.close();
|
|
403
|
+
const userState = await getPeer(state.peer, true);
|
|
404
|
+
if (userState.connection?.pc) {
|
|
405
|
+
receivePeerConnection({
|
|
406
|
+
pc: userState.connection?.pc,
|
|
407
|
+
userId: userState.peer,
|
|
408
|
+
restart: () => userState.close()
|
|
409
|
+
});
|
|
410
|
+
} else {
|
|
411
|
+
logLine?.("\uD83D\uDC64ℹ️", "no pc: " + userState.peer);
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
resolve2(state.connection);
|
|
417
|
+
});
|
|
418
|
+
state.connectionPromise = promise;
|
|
419
|
+
await promise;
|
|
420
|
+
state.connectionPromise = undefined;
|
|
421
|
+
return promise;
|
|
422
|
+
}
|
|
423
|
+
async function getPeer(peer, forceReset) {
|
|
424
|
+
let state = users.get(peer);
|
|
425
|
+
if (!state || forceReset) {
|
|
426
|
+
const newState = {
|
|
427
|
+
peer,
|
|
428
|
+
close() {
|
|
429
|
+
if (this.connection) {
|
|
430
|
+
this.connection.pc.close();
|
|
431
|
+
this.connection = undefined;
|
|
432
|
+
}
|
|
433
|
+
users.delete(peer);
|
|
434
|
+
},
|
|
435
|
+
async reset() {
|
|
436
|
+
newState.close();
|
|
437
|
+
setTimeout(async () => {
|
|
438
|
+
const userState = await getPeer(peer, true);
|
|
439
|
+
if (!userState.connection?.pc) {
|
|
440
|
+
logLine?.("⚠️", "no pc");
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
receivePeerConnection({
|
|
444
|
+
pc: userState.connection?.pc,
|
|
445
|
+
userId: userState.peer,
|
|
446
|
+
restart: () => userState.close()
|
|
447
|
+
});
|
|
448
|
+
await makeOffer(userState.peer);
|
|
449
|
+
}, 500);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
state = newState;
|
|
453
|
+
await setupConnection(newState);
|
|
454
|
+
users.set(state.peer, state);
|
|
455
|
+
} else if (state) {
|
|
456
|
+
clearTimeout(state.expirationTimeout);
|
|
457
|
+
state.expirationTimeout = 0;
|
|
458
|
+
if (!state.connection?.pc || state.connection?.pc.signalingState === "closed") {
|
|
459
|
+
await setupConnection(state);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
state.peer = peer;
|
|
463
|
+
return state;
|
|
464
|
+
}
|
|
465
|
+
async function makeOffer(userId2) {
|
|
466
|
+
const state = await getPeer(userId2);
|
|
467
|
+
const pc = state.connection?.pc;
|
|
468
|
+
const offer = await pc?.createOffer();
|
|
469
|
+
await pc?.setLocalDescription(offer);
|
|
470
|
+
send("offer", userId2, {
|
|
471
|
+
connectionId: state.connection?.id,
|
|
472
|
+
offer: pc?.localDescription?.toJSON()
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
const { exitRoom, send } = enterRoom3({
|
|
476
|
+
userId,
|
|
477
|
+
worldId,
|
|
478
|
+
room,
|
|
479
|
+
protocol,
|
|
480
|
+
host,
|
|
481
|
+
logLine,
|
|
482
|
+
workerUrl,
|
|
483
|
+
autoRejoin: true,
|
|
484
|
+
onOpen() {
|
|
485
|
+
onRoomReady?.({ room, host });
|
|
486
|
+
resolve();
|
|
487
|
+
},
|
|
488
|
+
onError() {
|
|
489
|
+
console.error("onError");
|
|
490
|
+
reject();
|
|
491
|
+
},
|
|
492
|
+
onClose(ev) {
|
|
493
|
+
onRoomClose?.({ room, host, ev });
|
|
494
|
+
},
|
|
495
|
+
onPeerJoined(joiningUsers, selfJoined) {
|
|
496
|
+
joiningUsers.forEach(async (user) => {
|
|
497
|
+
const state = await getPeer(user.userId, true);
|
|
498
|
+
state.joined = user.joined;
|
|
499
|
+
const pc = state.connection?.pc;
|
|
500
|
+
if (!pc) {
|
|
501
|
+
logLine?.("\uD83D\uDC64ℹ️", "no pc: " + user.userId);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
receivePeerConnection({
|
|
505
|
+
pc,
|
|
506
|
+
userId: user.userId,
|
|
507
|
+
restart: () => state.close()
|
|
508
|
+
});
|
|
509
|
+
if (user.joined > selfJoined || user.joined === selfJoined && user.userId.localeCompare(userId) > 0) {
|
|
510
|
+
await makeOffer(user.userId);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
},
|
|
514
|
+
onPeerLeft(leavingUsers) {
|
|
515
|
+
leavingUsers.forEach(({ userId: userId2 }) => {
|
|
516
|
+
const state = users.get(userId2);
|
|
517
|
+
if (!state)
|
|
518
|
+
return;
|
|
519
|
+
state.expirationTimeout = setTimeout(() => leaveUser(userId2), peerlessUserExpiration ?? 0);
|
|
520
|
+
});
|
|
521
|
+
},
|
|
522
|
+
onIceUrl(url, expiration) {
|
|
523
|
+
iceUrlProvider.receiveIce(url, expiration);
|
|
524
|
+
},
|
|
525
|
+
async onMessage(type, payload, from) {
|
|
526
|
+
if (type === "offer" && payload.offer) {
|
|
527
|
+
const state = await getPeer(from, false);
|
|
528
|
+
const connection = !state.connection || state.connection.pc.signalingState === "stable" ? await setupConnection(state) : state.connection;
|
|
529
|
+
logLine?.("\uD83D\uDCAC", {
|
|
530
|
+
type,
|
|
531
|
+
signalingState: connection.pc.signalingState
|
|
532
|
+
});
|
|
533
|
+
connection.peerConnectionId = payload.connectionId;
|
|
534
|
+
receivePeerConnection({
|
|
535
|
+
pc: connection.pc,
|
|
536
|
+
userId: from,
|
|
537
|
+
restart: () => state.close()
|
|
538
|
+
});
|
|
539
|
+
await connection.pc.setRemoteDescription(payload.offer);
|
|
540
|
+
const answer = await connection.pc.createAnswer();
|
|
541
|
+
await connection.pc.setLocalDescription(answer);
|
|
542
|
+
send("answer", from, {
|
|
543
|
+
connectionId: connection.id,
|
|
544
|
+
answer: connection.pc.localDescription?.toJSON()
|
|
545
|
+
});
|
|
546
|
+
await flushRemoteIce(state);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (type === "answer" && payload.answer) {
|
|
550
|
+
const state = await getPeer(from, false);
|
|
551
|
+
const connection = state.connection && state.connection.pc.signalingState !== "closed" ? state.connection : await setupConnection(state);
|
|
552
|
+
logLine?.("\uD83D\uDCAC", {
|
|
553
|
+
type,
|
|
554
|
+
signalingState: connection.pc.signalingState
|
|
555
|
+
});
|
|
556
|
+
await connection.pc.setRemoteDescription(payload.answer);
|
|
557
|
+
connection.peerConnectionId = payload.connectionId;
|
|
558
|
+
await flushRemoteIce(state);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (type === "ice" && payload.ice) {
|
|
562
|
+
const state = await getPeer(from, false);
|
|
563
|
+
const connection = state.connection ?? await state.connectionPromise;
|
|
564
|
+
if (!connection) {
|
|
565
|
+
logLine?.("⚠️", "No connection");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
logLine?.("\uD83D\uDCAC", {
|
|
569
|
+
type,
|
|
570
|
+
signalingState: connection.pc.signalingState
|
|
571
|
+
});
|
|
572
|
+
if (connection.peerConnectionId && payload.connectionId !== connection.peerConnectionId) {
|
|
573
|
+
logLine?.("⚠️", "Mismatch peerConnectionID" + payload.connectionId + "vs" + connection.peerConnectionId);
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (!connection.pc.remoteDescription || !connection.peerConnectionId) {
|
|
577
|
+
connection.peerConnectionId = payload.connectionId;
|
|
578
|
+
connection.pendingRemoteIce.push(payload.ice);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
try {
|
|
582
|
+
await connection.pc.addIceCandidate(payload.ice);
|
|
583
|
+
} catch (e) {
|
|
584
|
+
logLine?.("⚠️ ERROR", {
|
|
585
|
+
error: "add-ice-failed",
|
|
586
|
+
userId: state.peer,
|
|
587
|
+
detail: String(e)
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (type === "broadcast") {
|
|
593
|
+
onBroadcastMessage?.(payload, from);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
const removeRequester = iceUrlProvider.addRequester((command) => send(command, "server"));
|
|
598
|
+
roomsEntered.set(`${host}/room/${room}`, {
|
|
599
|
+
exitRoom: () => {
|
|
600
|
+
exitRoom();
|
|
601
|
+
removeRequester();
|
|
602
|
+
},
|
|
603
|
+
room,
|
|
604
|
+
host,
|
|
605
|
+
broadcast: (payload) => send("broadcast", "server", payload)
|
|
606
|
+
});
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
userId,
|
|
611
|
+
enterRoom: enter,
|
|
612
|
+
exitRoom: exit,
|
|
613
|
+
leaveUser,
|
|
614
|
+
async reset(userId2) {
|
|
615
|
+
const userState = users.get(userId2);
|
|
616
|
+
userState?.reset();
|
|
617
|
+
},
|
|
618
|
+
broadcast(payload) {
|
|
619
|
+
roomsEntered.forEach((room) => room.broadcast(payload));
|
|
620
|
+
},
|
|
621
|
+
end() {
|
|
622
|
+
roomsEntered.forEach(({ exitRoom }) => exitRoom());
|
|
623
|
+
roomsEntered.clear();
|
|
624
|
+
users.forEach(({ peer }) => leaveUser(peer));
|
|
625
|
+
users.clear();
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/browser/enter-world.ts
|
|
631
|
+
function enterWorld({
|
|
632
|
+
userId: passedUserId,
|
|
633
|
+
worldId,
|
|
634
|
+
logLine,
|
|
635
|
+
enterRoomFunction = enterRoom2,
|
|
636
|
+
peerlessUserExpiration,
|
|
637
|
+
workerUrl,
|
|
638
|
+
onRoomReady,
|
|
639
|
+
onRoomClose,
|
|
640
|
+
dataChannelOptions
|
|
641
|
+
}) {
|
|
642
|
+
const userIds = new Set;
|
|
643
|
+
const dataChannels = new Map;
|
|
644
|
+
const userListeners = new Set;
|
|
645
|
+
const messagesListeners = new Set;
|
|
646
|
+
function createDataChannel(pc, peerUserId, restart) {
|
|
647
|
+
function listener(ev) {
|
|
648
|
+
const dc2 = ev.channel;
|
|
649
|
+
wireDataChannel(peerUserId, dc2, restart);
|
|
650
|
+
dataChannels.set(peerUserId, dc2);
|
|
651
|
+
}
|
|
652
|
+
pc.addEventListener("datachannel", listener);
|
|
653
|
+
const dc = pc.createDataChannel("data", dataChannelOptions);
|
|
654
|
+
wireDataChannel(peerUserId, dc, restart);
|
|
655
|
+
dataChannels.set(peerUserId, dc);
|
|
656
|
+
return () => {
|
|
657
|
+
pc.removeEventListener("datachannel", listener);
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function conveyMessage(data, userId2) {
|
|
661
|
+
messagesListeners.forEach((listener) => listener(data, userId2));
|
|
662
|
+
}
|
|
663
|
+
function wireDataChannel(userId2, dc, restart) {
|
|
664
|
+
dc.onopen = () => {
|
|
665
|
+
logLine?.("\uD83D\uDCAC", { event: "dc-open", userId: userId2 });
|
|
666
|
+
userIds.add(userId2);
|
|
667
|
+
userListeners.forEach((listener) => listener(userId2, "join", [...userIds]));
|
|
668
|
+
};
|
|
669
|
+
const onmessage = ({ data }) => {
|
|
670
|
+
conveyMessage(data, userId2);
|
|
671
|
+
};
|
|
672
|
+
dc.addEventListener("message", onmessage);
|
|
673
|
+
dc.onclose = () => {
|
|
674
|
+
logLine?.("\uD83D\uDCAC", { event: "dc-close", userId: userId2 });
|
|
675
|
+
userIds.delete(userId2);
|
|
676
|
+
userListeners.forEach((listener) => listener(userId2, "leave", [...userIds]));
|
|
677
|
+
dc.removeEventListener("message", onmessage);
|
|
678
|
+
dc.onopen = null;
|
|
679
|
+
dc.onclose = null;
|
|
680
|
+
dc.onerror = null;
|
|
681
|
+
dc.close();
|
|
682
|
+
restart?.();
|
|
683
|
+
};
|
|
684
|
+
dc.onerror = () => logLine?.("⚠️ ERROR", { error: "dc-error", userId: userId2 });
|
|
685
|
+
}
|
|
686
|
+
const {
|
|
687
|
+
userId,
|
|
688
|
+
enterRoom: enterRoom3,
|
|
689
|
+
exitRoom,
|
|
690
|
+
leaveUser,
|
|
691
|
+
broadcast,
|
|
692
|
+
end: endPeerCollection,
|
|
693
|
+
reset: resetPeerCollection
|
|
694
|
+
} = collectPeerConnections({
|
|
695
|
+
userId: passedUserId,
|
|
696
|
+
worldId,
|
|
697
|
+
enterRoomFunction,
|
|
698
|
+
logLine,
|
|
699
|
+
workerUrl,
|
|
700
|
+
peerlessUserExpiration,
|
|
701
|
+
onRoomReady,
|
|
702
|
+
onRoomClose,
|
|
703
|
+
onLeaveUser(userId2) {
|
|
704
|
+
const dc = dataChannels.get(userId2);
|
|
705
|
+
try {
|
|
706
|
+
dc?.close();
|
|
707
|
+
} catch {}
|
|
708
|
+
dataChannels.delete(userId2);
|
|
709
|
+
},
|
|
710
|
+
receivePeerConnection({ pc, userId: userId2, restart }) {
|
|
711
|
+
createDataChannel(pc, userId2, restart);
|
|
712
|
+
},
|
|
713
|
+
onBroadcastMessage(payload, from) {
|
|
714
|
+
conveyMessage(payload, from);
|
|
715
|
+
logLine?.("\uD83D\uDCE2", { event: "broadcast", userId, data: payload });
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
function send(data, userId2) {
|
|
719
|
+
dataChannels.forEach((dataChannel, pUserId) => {
|
|
720
|
+
if (userId2 && pUserId !== userId2)
|
|
721
|
+
return;
|
|
722
|
+
if (dataChannel.readyState === "open") {
|
|
723
|
+
dataChannel.send(data);
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
function removeMessageListener(listener) {
|
|
728
|
+
messagesListeners.delete(listener);
|
|
729
|
+
}
|
|
730
|
+
function addMessageListener(listener) {
|
|
731
|
+
messagesListeners.add(listener);
|
|
732
|
+
return () => {
|
|
733
|
+
removeMessageListener(listener);
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function removeUserListener(listener) {
|
|
737
|
+
userListeners.delete(listener);
|
|
738
|
+
}
|
|
739
|
+
function addUserListener(listener) {
|
|
740
|
+
userListeners.add(listener);
|
|
741
|
+
return () => {
|
|
742
|
+
removeUserListener(listener);
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
userId,
|
|
747
|
+
send,
|
|
748
|
+
broadcast,
|
|
749
|
+
enterRoom: enterRoom3,
|
|
750
|
+
exitRoom,
|
|
751
|
+
leaveUser,
|
|
752
|
+
getUsers: () => [...userIds],
|
|
753
|
+
addMessageListener,
|
|
754
|
+
removeMessageListener,
|
|
755
|
+
addUserListener,
|
|
756
|
+
removeUserListener,
|
|
757
|
+
reset() {
|
|
758
|
+
userIds.forEach((userId2) => {
|
|
759
|
+
dataChannels.get(userId2)?.close();
|
|
760
|
+
dataChannels.delete(userId2);
|
|
761
|
+
resetPeerCollection(userId2);
|
|
762
|
+
});
|
|
763
|
+
},
|
|
764
|
+
end() {
|
|
765
|
+
dataChannels.forEach((dataChannel) => {
|
|
766
|
+
try {
|
|
767
|
+
dataChannel.close();
|
|
768
|
+
} catch {}
|
|
769
|
+
});
|
|
770
|
+
dataChannels.clear();
|
|
771
|
+
endPeerCollection();
|
|
772
|
+
userListeners.clear();
|
|
773
|
+
userIds.clear();
|
|
774
|
+
}
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
// node_modules/ts-md5/dist/index.es.js
|
|
778
|
+
var c = new Int32Array(4);
|
|
779
|
+
|
|
780
|
+
class h {
|
|
781
|
+
static hashStr(i, a = false) {
|
|
782
|
+
return this.onePassHasher.start().appendStr(i).end(a);
|
|
783
|
+
}
|
|
784
|
+
static hashAsciiStr(i, a = false) {
|
|
785
|
+
return this.onePassHasher.start().appendAsciiStr(i).end(a);
|
|
786
|
+
}
|
|
787
|
+
static stateIdentity = new Int32Array([
|
|
788
|
+
1732584193,
|
|
789
|
+
-271733879,
|
|
790
|
+
-1732584194,
|
|
791
|
+
271733878
|
|
792
|
+
]);
|
|
793
|
+
static buffer32Identity = new Int32Array([
|
|
794
|
+
0,
|
|
795
|
+
0,
|
|
796
|
+
0,
|
|
797
|
+
0,
|
|
798
|
+
0,
|
|
799
|
+
0,
|
|
800
|
+
0,
|
|
801
|
+
0,
|
|
802
|
+
0,
|
|
803
|
+
0,
|
|
804
|
+
0,
|
|
805
|
+
0,
|
|
806
|
+
0,
|
|
807
|
+
0,
|
|
808
|
+
0,
|
|
809
|
+
0
|
|
810
|
+
]);
|
|
811
|
+
static hexChars = "0123456789abcdef";
|
|
812
|
+
static hexOut = [];
|
|
813
|
+
static onePassHasher = new h;
|
|
814
|
+
static _hex(i) {
|
|
815
|
+
const { hexChars: a, hexOut: t } = h;
|
|
816
|
+
let e, s, r, n;
|
|
817
|
+
for (n = 0;n < 4; n += 1)
|
|
818
|
+
for (s = n * 8, e = i[n], r = 0;r < 8; r += 2)
|
|
819
|
+
t[s + 1 + r] = a.charAt(e & 15), e >>>= 4, t[s + 0 + r] = a.charAt(e & 15), e >>>= 4;
|
|
820
|
+
return t.join("");
|
|
821
|
+
}
|
|
822
|
+
static _md5cycle(i, a) {
|
|
823
|
+
let t = i[0], e = i[1], s = i[2], r = i[3];
|
|
824
|
+
t += (e & s | ~e & r) + a[0] - 680876936 | 0, t = (t << 7 | t >>> 25) + e | 0, r += (t & e | ~t & s) + a[1] - 389564586 | 0, r = (r << 12 | r >>> 20) + t | 0, s += (r & t | ~r & e) + a[2] + 606105819 | 0, s = (s << 17 | s >>> 15) + r | 0, e += (s & r | ~s & t) + a[3] - 1044525330 | 0, e = (e << 22 | e >>> 10) + s | 0, t += (e & s | ~e & r) + a[4] - 176418897 | 0, t = (t << 7 | t >>> 25) + e | 0, r += (t & e | ~t & s) + a[5] + 1200080426 | 0, r = (r << 12 | r >>> 20) + t | 0, s += (r & t | ~r & e) + a[6] - 1473231341 | 0, s = (s << 17 | s >>> 15) + r | 0, e += (s & r | ~s & t) + a[7] - 45705983 | 0, e = (e << 22 | e >>> 10) + s | 0, t += (e & s | ~e & r) + a[8] + 1770035416 | 0, t = (t << 7 | t >>> 25) + e | 0, r += (t & e | ~t & s) + a[9] - 1958414417 | 0, r = (r << 12 | r >>> 20) + t | 0, s += (r & t | ~r & e) + a[10] - 42063 | 0, s = (s << 17 | s >>> 15) + r | 0, e += (s & r | ~s & t) + a[11] - 1990404162 | 0, e = (e << 22 | e >>> 10) + s | 0, t += (e & s | ~e & r) + a[12] + 1804603682 | 0, t = (t << 7 | t >>> 25) + e | 0, r += (t & e | ~t & s) + a[13] - 40341101 | 0, r = (r << 12 | r >>> 20) + t | 0, s += (r & t | ~r & e) + a[14] - 1502002290 | 0, s = (s << 17 | s >>> 15) + r | 0, e += (s & r | ~s & t) + a[15] + 1236535329 | 0, e = (e << 22 | e >>> 10) + s | 0, t += (e & r | s & ~r) + a[1] - 165796510 | 0, t = (t << 5 | t >>> 27) + e | 0, r += (t & s | e & ~s) + a[6] - 1069501632 | 0, r = (r << 9 | r >>> 23) + t | 0, s += (r & e | t & ~e) + a[11] + 643717713 | 0, s = (s << 14 | s >>> 18) + r | 0, e += (s & t | r & ~t) + a[0] - 373897302 | 0, e = (e << 20 | e >>> 12) + s | 0, t += (e & r | s & ~r) + a[5] - 701558691 | 0, t = (t << 5 | t >>> 27) + e | 0, r += (t & s | e & ~s) + a[10] + 38016083 | 0, r = (r << 9 | r >>> 23) + t | 0, s += (r & e | t & ~e) + a[15] - 660478335 | 0, s = (s << 14 | s >>> 18) + r | 0, e += (s & t | r & ~t) + a[4] - 405537848 | 0, e = (e << 20 | e >>> 12) + s | 0, t += (e & r | s & ~r) + a[9] + 568446438 | 0, t = (t << 5 | t >>> 27) + e | 0, r += (t & s | e & ~s) + a[14] - 1019803690 | 0, r = (r << 9 | r >>> 23) + t | 0, s += (r & e | t & ~e) + a[3] - 187363961 | 0, s = (s << 14 | s >>> 18) + r | 0, e += (s & t | r & ~t) + a[8] + 1163531501 | 0, e = (e << 20 | e >>> 12) + s | 0, t += (e & r | s & ~r) + a[13] - 1444681467 | 0, t = (t << 5 | t >>> 27) + e | 0, r += (t & s | e & ~s) + a[2] - 51403784 | 0, r = (r << 9 | r >>> 23) + t | 0, s += (r & e | t & ~e) + a[7] + 1735328473 | 0, s = (s << 14 | s >>> 18) + r | 0, e += (s & t | r & ~t) + a[12] - 1926607734 | 0, e = (e << 20 | e >>> 12) + s | 0, t += (e ^ s ^ r) + a[5] - 378558 | 0, t = (t << 4 | t >>> 28) + e | 0, r += (t ^ e ^ s) + a[8] - 2022574463 | 0, r = (r << 11 | r >>> 21) + t | 0, s += (r ^ t ^ e) + a[11] + 1839030562 | 0, s = (s << 16 | s >>> 16) + r | 0, e += (s ^ r ^ t) + a[14] - 35309556 | 0, e = (e << 23 | e >>> 9) + s | 0, t += (e ^ s ^ r) + a[1] - 1530992060 | 0, t = (t << 4 | t >>> 28) + e | 0, r += (t ^ e ^ s) + a[4] + 1272893353 | 0, r = (r << 11 | r >>> 21) + t | 0, s += (r ^ t ^ e) + a[7] - 155497632 | 0, s = (s << 16 | s >>> 16) + r | 0, e += (s ^ r ^ t) + a[10] - 1094730640 | 0, e = (e << 23 | e >>> 9) + s | 0, t += (e ^ s ^ r) + a[13] + 681279174 | 0, t = (t << 4 | t >>> 28) + e | 0, r += (t ^ e ^ s) + a[0] - 358537222 | 0, r = (r << 11 | r >>> 21) + t | 0, s += (r ^ t ^ e) + a[3] - 722521979 | 0, s = (s << 16 | s >>> 16) + r | 0, e += (s ^ r ^ t) + a[6] + 76029189 | 0, e = (e << 23 | e >>> 9) + s | 0, t += (e ^ s ^ r) + a[9] - 640364487 | 0, t = (t << 4 | t >>> 28) + e | 0, r += (t ^ e ^ s) + a[12] - 421815835 | 0, r = (r << 11 | r >>> 21) + t | 0, s += (r ^ t ^ e) + a[15] + 530742520 | 0, s = (s << 16 | s >>> 16) + r | 0, e += (s ^ r ^ t) + a[2] - 995338651 | 0, e = (e << 23 | e >>> 9) + s | 0, t += (s ^ (e | ~r)) + a[0] - 198630844 | 0, t = (t << 6 | t >>> 26) + e | 0, r += (e ^ (t | ~s)) + a[7] + 1126891415 | 0, r = (r << 10 | r >>> 22) + t | 0, s += (t ^ (r | ~e)) + a[14] - 1416354905 | 0, s = (s << 15 | s >>> 17) + r | 0, e += (r ^ (s | ~t)) + a[5] - 57434055 | 0, e = (e << 21 | e >>> 11) + s | 0, t += (s ^ (e | ~r)) + a[12] + 1700485571 | 0, t = (t << 6 | t >>> 26) + e | 0, r += (e ^ (t | ~s)) + a[3] - 1894986606 | 0, r = (r << 10 | r >>> 22) + t | 0, s += (t ^ (r | ~e)) + a[10] - 1051523 | 0, s = (s << 15 | s >>> 17) + r | 0, e += (r ^ (s | ~t)) + a[1] - 2054922799 | 0, e = (e << 21 | e >>> 11) + s | 0, t += (s ^ (e | ~r)) + a[8] + 1873313359 | 0, t = (t << 6 | t >>> 26) + e | 0, r += (e ^ (t | ~s)) + a[15] - 30611744 | 0, r = (r << 10 | r >>> 22) + t | 0, s += (t ^ (r | ~e)) + a[6] - 1560198380 | 0, s = (s << 15 | s >>> 17) + r | 0, e += (r ^ (s | ~t)) + a[13] + 1309151649 | 0, e = (e << 21 | e >>> 11) + s | 0, t += (s ^ (e | ~r)) + a[4] - 145523070 | 0, t = (t << 6 | t >>> 26) + e | 0, r += (e ^ (t | ~s)) + a[11] - 1120210379 | 0, r = (r << 10 | r >>> 22) + t | 0, s += (t ^ (r | ~e)) + a[2] + 718787259 | 0, s = (s << 15 | s >>> 17) + r | 0, e += (r ^ (s | ~t)) + a[9] - 343485551 | 0, e = (e << 21 | e >>> 11) + s | 0, i[0] = t + i[0] | 0, i[1] = e + i[1] | 0, i[2] = s + i[2] | 0, i[3] = r + i[3] | 0;
|
|
825
|
+
}
|
|
826
|
+
_dataLength = 0;
|
|
827
|
+
_bufferLength = 0;
|
|
828
|
+
_state = new Int32Array(4);
|
|
829
|
+
_buffer = new ArrayBuffer(68);
|
|
830
|
+
_buffer8;
|
|
831
|
+
_buffer32;
|
|
832
|
+
constructor() {
|
|
833
|
+
this._buffer8 = new Uint8Array(this._buffer, 0, 68), this._buffer32 = new Uint32Array(this._buffer, 0, 17), this.start();
|
|
834
|
+
}
|
|
835
|
+
start() {
|
|
836
|
+
return this._dataLength = 0, this._bufferLength = 0, this._state.set(h.stateIdentity), this;
|
|
837
|
+
}
|
|
838
|
+
appendStr(i) {
|
|
839
|
+
const a = this._buffer8, t = this._buffer32;
|
|
840
|
+
let e = this._bufferLength, s, r;
|
|
841
|
+
for (r = 0;r < i.length; r += 1) {
|
|
842
|
+
if (s = i.charCodeAt(r), s < 128)
|
|
843
|
+
a[e++] = s;
|
|
844
|
+
else if (s < 2048)
|
|
845
|
+
a[e++] = (s >>> 6) + 192, a[e++] = s & 63 | 128;
|
|
846
|
+
else if (s < 55296 || s > 56319)
|
|
847
|
+
a[e++] = (s >>> 12) + 224, a[e++] = s >>> 6 & 63 | 128, a[e++] = s & 63 | 128;
|
|
848
|
+
else {
|
|
849
|
+
if (s = (s - 55296) * 1024 + (i.charCodeAt(++r) - 56320) + 65536, s > 1114111)
|
|
850
|
+
throw new Error("Unicode standard supports code points up to U+10FFFF");
|
|
851
|
+
a[e++] = (s >>> 18) + 240, a[e++] = s >>> 12 & 63 | 128, a[e++] = s >>> 6 & 63 | 128, a[e++] = s & 63 | 128;
|
|
852
|
+
}
|
|
853
|
+
e >= 64 && (this._dataLength += 64, h._md5cycle(this._state, t), e -= 64, t[0] = t[16]);
|
|
854
|
+
}
|
|
855
|
+
return this._bufferLength = e, this;
|
|
856
|
+
}
|
|
857
|
+
appendAsciiStr(i) {
|
|
858
|
+
const a = this._buffer8, t = this._buffer32;
|
|
859
|
+
let e = this._bufferLength, s, r = 0;
|
|
860
|
+
for (;; ) {
|
|
861
|
+
for (s = Math.min(i.length - r, 64 - e);s--; )
|
|
862
|
+
a[e++] = i.charCodeAt(r++);
|
|
863
|
+
if (e < 64)
|
|
864
|
+
break;
|
|
865
|
+
this._dataLength += 64, h._md5cycle(this._state, t), e = 0;
|
|
866
|
+
}
|
|
867
|
+
return this._bufferLength = e, this;
|
|
868
|
+
}
|
|
869
|
+
appendByteArray(i) {
|
|
870
|
+
const a = this._buffer8, t = this._buffer32;
|
|
871
|
+
let e = this._bufferLength, s, r = 0;
|
|
872
|
+
for (;; ) {
|
|
873
|
+
for (s = Math.min(i.length - r, 64 - e);s--; )
|
|
874
|
+
a[e++] = i[r++];
|
|
875
|
+
if (e < 64)
|
|
876
|
+
break;
|
|
877
|
+
this._dataLength += 64, h._md5cycle(this._state, t), e = 0;
|
|
878
|
+
}
|
|
879
|
+
return this._bufferLength = e, this;
|
|
880
|
+
}
|
|
881
|
+
getState() {
|
|
882
|
+
const i = this._state;
|
|
883
|
+
return {
|
|
884
|
+
buffer: String.fromCharCode.apply(null, Array.from(this._buffer8)),
|
|
885
|
+
buflen: this._bufferLength,
|
|
886
|
+
length: this._dataLength,
|
|
887
|
+
state: [i[0], i[1], i[2], i[3]]
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
setState(i) {
|
|
891
|
+
const { buffer: a, state: t } = i, e = this._state;
|
|
892
|
+
let s;
|
|
893
|
+
for (this._dataLength = i.length, this._bufferLength = i.buflen, e[0] = t[0], e[1] = t[1], e[2] = t[2], e[3] = t[3], s = 0;s < a.length; s += 1)
|
|
894
|
+
this._buffer8[s] = a.charCodeAt(s);
|
|
895
|
+
}
|
|
896
|
+
end(i = false) {
|
|
897
|
+
const a = this._bufferLength, t = this._buffer8, e = this._buffer32, s = (a >> 2) + 1;
|
|
898
|
+
this._dataLength += a;
|
|
899
|
+
const r = this._dataLength * 8;
|
|
900
|
+
if (t[a] = 128, t[a + 1] = t[a + 2] = t[a + 3] = 0, e.set(h.buffer32Identity.subarray(s), s), a > 55 && (h._md5cycle(this._state, e), e.set(h.buffer32Identity)), r <= 4294967295)
|
|
901
|
+
e[14] = r;
|
|
902
|
+
else {
|
|
903
|
+
const n = r.toString(16).match(/(.*?)(.{0,8})$/);
|
|
904
|
+
if (n === null)
|
|
905
|
+
return i ? c : "";
|
|
906
|
+
const o = parseInt(n[2], 16), _ = parseInt(n[1], 16) || 0;
|
|
907
|
+
e[14] = o, e[15] = _;
|
|
908
|
+
}
|
|
909
|
+
return h._md5cycle(this._state, e), i ? this._state : h._hex(this._state);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (h.hashStr("hello") !== "5d41402abc4b2a76b9719d911017c592")
|
|
913
|
+
throw new Error("Md5 self test failed.");
|
|
914
|
+
|
|
915
|
+
// src/browser/web-components/sync-button.ts
|
|
916
|
+
class SyncButton extends HTMLElement {
|
|
917
|
+
static observedAttributes = ["id", "disabled"];
|
|
918
|
+
static session;
|
|
919
|
+
shadowButton;
|
|
920
|
+
slotEl;
|
|
921
|
+
constructor() {
|
|
922
|
+
super();
|
|
923
|
+
if (!SyncButton.session) {
|
|
924
|
+
const session = enterWorld({
|
|
925
|
+
worldId: "sync-buttons",
|
|
926
|
+
dataChannelOptions: {
|
|
927
|
+
ordered: false
|
|
928
|
+
},
|
|
929
|
+
workerUrl: new URL("https://hello.dobuki.net/signal/signal-room.worker.js"),
|
|
930
|
+
logLine: console.log
|
|
931
|
+
});
|
|
932
|
+
session.enterRoom({
|
|
933
|
+
room: `sync-button-${h.hashStr(`${location.origin}-${location.pathname}`)}`,
|
|
934
|
+
host: "hello.dobuki.net"
|
|
935
|
+
});
|
|
936
|
+
SyncButton.session = session;
|
|
937
|
+
}
|
|
938
|
+
this.attachShadow({ mode: "open" });
|
|
939
|
+
this.onMessage = this.onMessage.bind(this);
|
|
940
|
+
this.onClickButton = this.onClickButton.bind(this);
|
|
941
|
+
this.shadowRoot.innerHTML = `
|
|
2
942
|
<style>
|
|
3
943
|
:host {
|
|
4
944
|
display: inline-block;
|
|
@@ -22,7 +962,74 @@ class N{sendToServerFunctions=[];icePromiseResolve;icePromise;receiveIce(c,F){th
|
|
|
22
962
|
<button part="button" type="button">
|
|
23
963
|
<slot></slot>
|
|
24
964
|
</button>
|
|
25
|
-
|
|
965
|
+
`;
|
|
966
|
+
this.shadowButton = this.shadowRoot.querySelector("button");
|
|
967
|
+
this.slotEl = this.shadowRoot.querySelector("slot");
|
|
968
|
+
}
|
|
969
|
+
connectedCallback() {
|
|
970
|
+
SyncButton.session.addMessageListener(this.onMessage);
|
|
971
|
+
this.shadowButton.addEventListener("click", this.onClickButton);
|
|
972
|
+
this.syncToInnerButton();
|
|
973
|
+
this.upgradeProperty("disabled");
|
|
974
|
+
}
|
|
975
|
+
disconnectedCallback() {
|
|
976
|
+
SyncButton.session.removeMessageListener(this.onMessage);
|
|
977
|
+
this.shadowButton.removeEventListener("click", this.onClickButton);
|
|
978
|
+
}
|
|
979
|
+
attributeChangedCallback(_name, _oldValue, _newValue) {
|
|
980
|
+
this.syncToInnerButton();
|
|
981
|
+
}
|
|
982
|
+
get disabled() {
|
|
983
|
+
return this.hasAttribute("disabled");
|
|
984
|
+
}
|
|
985
|
+
set disabled(value) {
|
|
986
|
+
this.toggleAttribute("disabled", Boolean(value));
|
|
987
|
+
}
|
|
988
|
+
focus(options) {
|
|
989
|
+
this.shadowButton.focus(options);
|
|
990
|
+
}
|
|
991
|
+
click() {
|
|
992
|
+
this.shadowButton.click();
|
|
993
|
+
}
|
|
994
|
+
syncToInnerButton() {
|
|
995
|
+
this.shadowButton.disabled = this.disabled;
|
|
996
|
+
if (this.id) {
|
|
997
|
+
this.shadowButton.id = this.id;
|
|
998
|
+
} else {
|
|
999
|
+
this.shadowButton.removeAttribute("id");
|
|
1000
|
+
}
|
|
1001
|
+
this.setAttribute("role", "button");
|
|
1002
|
+
this.setAttribute("tabindex", this.disabled ? "-1" : "0");
|
|
1003
|
+
this.setAttribute("aria-disabled", String(this.disabled));
|
|
1004
|
+
}
|
|
1005
|
+
upgradeProperty(prop) {
|
|
1006
|
+
if (Object.prototype.hasOwnProperty.call(this, prop)) {
|
|
1007
|
+
const value = this[prop];
|
|
1008
|
+
delete this[prop];
|
|
1009
|
+
this[prop] = value;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
onMessage(data) {
|
|
1013
|
+
try {
|
|
1014
|
+
const { action, id } = JSON.parse(String(data));
|
|
1015
|
+
if (action === "click" && this.id === id) {
|
|
1016
|
+
this.shadowButton.removeEventListener("click", this.onClickButton);
|
|
1017
|
+
this.shadowButton.click();
|
|
1018
|
+
this.shadowButton.addEventListener("click", this.onClickButton);
|
|
1019
|
+
}
|
|
1020
|
+
} catch {}
|
|
1021
|
+
}
|
|
1022
|
+
onClickButton() {
|
|
1023
|
+
SyncButton.session.send(JSON.stringify({ action: "click", id: this.id }));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
customElements.define("sync-button", SyncButton);
|
|
1027
|
+
export {
|
|
1028
|
+
enterWorld,
|
|
1029
|
+
enterRoom,
|
|
1030
|
+
collectPeerConnections,
|
|
1031
|
+
SyncButton
|
|
1032
|
+
};
|
|
26
1033
|
|
|
27
|
-
//# debugId=
|
|
1034
|
+
//# debugId=84CAC7ECF570725764756E2164756E21
|
|
28
1035
|
//# sourceMappingURL=index.js.map
|