madoi-client
Version:
A Client library of Madoi Server for Distributed Sharing
2 lines (1 loc) • 15.9 kB
JavaScript
(function(r,y){typeof exports=="object"&&typeof module<"u"?y(exports):typeof define=="function"&&define.amd?define(["exports"],y):(r=typeof globalThis<"u"?globalThis:r||self,y(r.madoi={}))})(this,function(r){"use strict";var Y=Object.defineProperty;var Z=(r,y,M)=>y in r?Y(r,y,{enumerable:!0,configurable:!0,writable:!0,value:M}):r[y]=M;var l=(r,y,M)=>Z(r,typeof y!="symbol"?y+"":y,M);class y extends EventTarget{dispatchCustomEvent(a,e){return super.dispatchEvent(new CustomEvent(a,{detail:e}))}}const M={sender:"__PEER__",castType:"PEERTOSERVER",recipients:void 0},_={sender:"__PEER__",castType:"BROADCAST",recipients:void 0},C={sender:"__PEER__",recipients:void 0};function D(s=void 0){return{type:"Ping",...M,body:s}}function j(s){return{type:"EnterRoom",...M,...s}}function $(s){return{type:"LeaveRoom",...M,...s}}function S(s){return{type:"UpdateRoomProfile",..._,...s}}function b(s){return{type:"UpdatePeerProfile",..._,...s}}function E(s){return{type:"DefineFunction",...M,...s}}function I(s){return{type:"DefineObject",...M,...s}}function T(s,a){return{type:"InvokeFunction",castType:s,...C,...a}}function U(s){return{type:"UpdateObjectState",...M,...s}}function A(s,a){return{type:"InvokeMethod",castType:s,...C,...a}}function F(s={}){return a=>{a.madoiClassConfig_=s}}const w={type:"beforeExec",maxLog:0,allowedTo:["USER"]};function N(s=w){const a=s;return a.type||(a.type="beforeExec"),a.maxLog||(a.maxLog=0),(e,t,o)=>{const n={share:a};e[t].madoiMethodConfig_=n}}const R={type:"beforeExec"};function k(s=R){const a={...s};return(e,t,o)=>{const n={notify:a};e[t].madoiMethodConfig_=n}}const O={maxInterval:5e3};function x(s=O){const a=s;return(e,t,o)=>{const n={getState:a};e[t].madoiMethodConfig_=n}}function H(s={}){const a=s;return(e,t,o)=>{const n={setState:a};e[t].madoiMethodConfig_=n}}function B(s={}){return(a,e,t)=>{const n={hostOnly:s};a[e].madoiMethodConfig_=n}}function J(s={}){const a=s;return(e,t,o)=>{const n={beforeEnterRoom:a};e[t].madoiMethodConfig_=n}}function G(s={}){const a=s;return(e,t,o)=>{const n={enterRoomAllowed:a};e[t].madoiMethodConfig_=n}}function Q(s={}){const a=s;return(e,t,o)=>{const n={enterRoomDenied:a};e[t].madoiMethodConfig_=n}}function z(s={}){const a=s;return(e,t,o)=>{const n={leaveRoomDone:a};e[t].madoiMethodConfig_=n}}function q(s={}){const a=s;return(e,t,o)=>{const n={roomProfileUpdated:a};e[t].madoiMethodConfig_=n}}function V(s={}){const a=s;return(e,t,o)=>{const n={peerEntered:a};e[t].madoiMethodConfig_=n}}function W(s={}){const a=s;return(e,t,o)=>{const n={peerLeaved:a};e[t].madoiMethodConfig_=n}}function K(s={}){const a=s;return(e,t,o)=>{const n={peerProfileUpdated:a};e[t].madoiMethodConfig_=n}}class X extends y{constructor(e,t,o,n){super();l(this,"connecting",!1);l(this,"interimQueue");l(this,"shareOrNotifyFunctions",new Map);l(this,"shareObjects",new Map);l(this,"shareOrNotifyMethods",new Map);l(this,"getStateMethods",new Map);l(this,"setStateMethods",new Map);l(this,"beforeEnterRoomMethods",new Map);l(this,"enterRoomAllowedMethods",new Map);l(this,"enterRoomDeniedMethods",new Map);l(this,"leaveRoomDoneMethods",new Map);l(this,"roomProfileUpdatedMethods",new Map);l(this,"peerEnteredMethods",new Map);l(this,"peerLeavedMethods",new Map);l(this,"peerProfileUpdatedMethods",new Map);l(this,"url");l(this,"ws",null);l(this,"room",{id:"",spec:{maxLog:1e3},profile:{}});l(this,"selfPeer",{id:"",order:-1,profile:{}});l(this,"peers",new Map);l(this,"currentSender",null);l(this,"systemMessageTypes",["Ping","Pong","EnterRoom","EnterRoomAllowed","EnterRoomDenied","LeaveRoom","LeaveRoomDone","UpdateRoomProfile","PeerArrived","PeerLeaved","UpdatePeerProfile","DefineFunction","DefineObject","InvokeFunction","UpdateObjectState","InvokeMethod"]);n&&(this.room={...this.room,...n}),o&&(this.selfPeer={...this.selfPeer,...o,order:-1}),this.interimQueue=new Array;const i=e.indexOf("?")!=-1?"&":"?";if(e.match(/^wss?:\/\//))this.url=`${e}${i}authToken=${t}`,this.room.id=e.split("rooms/")[1].split("?")[0];else{const d=document.querySelector("script[src$='madoi.js']").src.split("/",5),u=(d[0]=="http:"?"ws:":"wss:")+"//"+d[2]+"/"+d[3];this.url=`${u}/rooms/${e}${i}authToken=${t}`,this.room.id=e}this.ws=new WebSocket(this.url),this.ws.onopen=d=>this.handleOnOpen(d),this.ws.onclose=d=>this.handleOnClose(d),this.ws.onerror=d=>this.handleOnError(d),this.ws.onmessage=d=>this.handleOnMessage(d),setInterval(()=>{this.saveStates()},1e3),setInterval(()=>{this.sendPing()},3e4)}getRoomId(){return this.room.id}getRoomProfile(){var e;return(e=this.room)==null?void 0:e.profile}setRoomProfile(e,t){const o={};o[e]=t,this.sendMessage(S({updates:o}))}removeRoomProfile(e){this.sendMessage(S({deletes:[e]}))}getSelfPeerId(){var e;return(e=this.selfPeer)==null?void 0:e.id}getSelfPeerProfile(){return this.selfPeer.profile}updateSelfPeerProfile(e,t){this.selfPeer.profile[e]=t;const o={};o[e]=t,this.sendMessage(b({updates:o}));const n={updates:o,peerId:this.selfPeer.id};for(const[i,d]of this.peerProfileUpdatedMethods)d(n,this);this.dispatchCustomEvent("peerProfileUpdated",n)}removeSelfPeerProfile(e){delete this.selfPeer.profile[e],this.sendMessage(b({deletes:[e]}));const t={deletes:[e],peerId:this.selfPeer.id};for(const[o,n]of this.peerProfileUpdatedMethods)n(t,this);this.dispatchCustomEvent("peerProfileUpdated",t)}getCurrentSender(){return this.currentSender?this.peers.get(this.currentSender):null}isCurrentSenderSelf(){return this.currentSender===this.selfPeer.id}close(){var e;(e=this.ws)==null||e.close(),this.ws=null}sendPing(){var e;(e=this.ws)==null||e.send(JSON.stringify(D()))}handleOnOpen(e){var t;this.connecting=!0;for(const[o,n]of this.beforeEnterRoomMethods)n(this.selfPeer.profile,this);this.doSendMessage(j({room:this.room,selfPeer:this.selfPeer}));for(let o of this.interimQueue)(t=this.ws)==null||t.send(JSON.stringify(o));this.interimQueue=[]}handleOnClose(e){console.debug(`websocket closed because: ${e.reason}.`),this.connecting=!1,this.ws=null}handleOnError(e){}handleOnMessage(e){const t=JSON.parse(e.data);this.currentSender=t.sender,this.data(t)}data(e){if(e.type!="Pong")if(e.type==="EnterRoomAllowed"){const t=e;for(const[o,n]of this.enterRoomAllowedMethods)n(t,this);this.room=e.room,this.selfPeer.order=e.selfPeer.order,this.peers.set(t.selfPeer.id,{...t.selfPeer,profile:this.selfPeer.profile});for(const o of t.otherPeers)this.peers.set(o.id,o);if(this.dispatchCustomEvent("enterRoomAllowed",t),e.histories)for(const o of e.histories)this.data(o)}else if(e.type==="EnterRoomDenied"){const o=e;for(const[n,i]of this.enterRoomDeniedMethods)i(o,this);this.dispatchCustomEvent("enterRoomDenied",o)}else if(e.type=="LeaveRoomDone"){for(const[t,o]of this.leaveRoomDoneMethods)o(this);this.dispatchCustomEvent("leaveRoomDone")}else if(e.type==="UpdateRoomProfile"){const t=e;if(e.updates)for(const[o,n]of Object.entries(e.updates))this.room.profile[o]=n;if(e.deletes)for(const o of e.deletes)delete this.room.profile[o];for(const[o,n]of this.roomProfileUpdatedMethods)n(t,this);this.dispatchCustomEvent("roomProfileUpdated",t)}else if(e.type==="PeerEntered"){const t=e;this.peers.set(t.peer.id,t.peer);for(const[o,n]of this.peerEnteredMethods)n(t,this);this.dispatchCustomEvent("peerEntered",t)}else if(e.type==="PeerLeaved"){const t=e;this.peers.delete(e.peerId);for(const[o,n]of this.peerLeavedMethods)n(t,this);this.dispatchCustomEvent("peerLeaved",t)}else if(e.type==="UpdatePeerProfile"){const t=this.peers.get(e.sender);if(e.sender&&t){if(e.updates)for(const[n,i]of Object.entries(e.updates))t.profile[n]=i;if(e.deletes)for(const n of e.deletes)delete t.profile[n];const o={...e,peerId:e.sender};for(const[n,i]of this.peerProfileUpdatedMethods)i(o,this);this.dispatchCustomEvent("peerProfileUpdated",o)}}else if(e.type==="InvokeFunction"){const t=`${e.funcId}`,o=this.shareOrNotifyFunctions.get(t);if(o===void 0){console.warn("no suitable function for ",e);return}const n=this.applyInvocation(o.original,e.args);n instanceof Promise&&n.then(()=>{var i;(i=o.resolve)==null||i.apply(null,arguments)}).catch(()=>{var i;(i=o.reject)==null||i.apply(null,arguments)})}else if(e.type==="UpdateObjectState"){const t=this.setStateMethods.get(e.objId);t&&t(e.state,e.objRevision);const o=this.shareObjects.get(e.objId);o&&(o.revision=e.objRevision)}else if(e.type==="InvokeMethod"){const t=this.shareObjects.get(e.objId);if(t===void 0){console.error(`Object not found for id: ${e.objId}.`,e);return}const o=`${e.objId}:${e.methodId}`,n=this.shareOrNotifyMethods.get(o);if(n===void 0){console.error(`Method not found for id: ${o}.`,e);return}n.config.share&&(t.revision+1!==e.serverObjRevision&&console.error(`Found inconsistency. serverObjRevision must be ${t.revision+1} but ${e.serverObjRevision}.`,e),t.revision=e.serverObjRevision);const i=this.applyInvocation(n.original,e.args);i instanceof Promise&&i.then(()=>{var d;(d=n.resolve)==null||d.apply(null,arguments)}).catch(()=>{var d;(d=n.reject)==null||d.apply(null,arguments)})}else e.type?this.dispatchEvent(new CustomEvent(e.type,{detail:e})):console.warn("Unknown message type.",e)}isSystemMessageType(e){return e in this.systemMessageTypes}send(e,t,o="BROADCAST"){this.ws&&this.sendMessage({type:e,sender:this.selfPeer.id,castType:o,recipients:void 0,content:t})}unicast(e,t,o){this.sendMessage({type:e,sender:this.selfPeer.id,castType:"UNICAST",recipients:[o],content:t})}multicast(e,t,o){this.sendMessage({type:e,sender:this.selfPeer.id,castType:"MULTICAST",recipients:o,content:t})}broadcast(e,t){this.sendMessage({type:e,sender:this.selfPeer.id,castType:"BROADCAST",recipients:void 0,content:t})}othercast(e,t){this.sendMessage({type:e,sender:this.selfPeer.id,castType:"OTHERCAST",recipients:void 0,content:t})}sendMessage(e){if(this.isSystemMessageType(e.type))throw new Error("システムメッセージは送信できません。");this.doSendMessage(e)}addReceiver(e,t){if(this.isSystemMessageType(e))throw new Error("システムメッセージのレシーバは登録できません。");this.addEventListener(e,t)}removeReceiver(e,t){this.removeEventListener(e,t)}replacer(e,t){return t instanceof Map?Object.fromEntries(t):t}doSendMessage(e){var t;this.connecting?(t=this.ws)==null||t.send(JSON.stringify(e,this.replacer)):this.interimQueue.push(e)}registerFunction(e,t={share:{}}){if("hostOnly"in t)return this.addHostOnlyFunction(e,t);if("notify"in t){t.notify.type||(t.notify.type=R.type);const o=e.name,n=this.shareOrNotifyFunctions.size,i=this.createFunctionProxy(e,{notify:t.notify},n),d=function(){return i.apply(null,arguments)};return this.doSendMessage(E({definition:{funcId:n,name:o,config:t}})),d}else if("share"in t){t.share.type||(t.share.type=w.type),t.share.maxLog||(t.share.maxLog=w.maxLog);const o=e.name,n=this.shareOrNotifyFunctions.size,i=this.createFunctionProxy(e,{share:t.share},n),d=function(){return i.apply(null,arguments)};return this.doSendMessage(E({definition:{funcId:n,name:o,config:t}})),d}return e}register(e,t=[]){if(!this.ws)return e;const o=e;if(o.madoiObjectId_)return console.warn("Ignore object registration because it's already registered."),e;let n=o.constructor.name;o.__proto__.constructor.madoiClassConfig_&&(n=o.__proto__.constructor.madoiClassConfig_.className);const i=this.shareObjects.size,d={instance:o,revision:0,modification:0};this.shareObjects.set(i,d),o.madoiObjectId_=i;const u=new Array,m=new Array,v=new Map;Object.getOwnPropertyNames(Object.getPrototypeOf(o)).forEach(p=>{const h=o[p];if(typeof h!="function"||!h.madoiMethodConfig_)return;const f=h.madoiMethodConfig_,c=u.length;v.set(p,c),u.push(h),m.push({methodId:c,name:p,config:f}),console.debug(`add config ${n}.${p}=${JSON.stringify(f)} from decorator`)});for(const p of t){const h=p.method,f=p,c=h.name;if("share"in f)f.share.type||(f.share.type=w.type),f.share.maxLog||(f.share.maxLog=w.maxLog);else if("notify"in f)f.notify.type||(f.notify.type=R.type);else if(!("hostOnly"in f)){if("getState"in f)f.getState.maxInterval||(f.getState.maxInterval=O.maxInterval);else if(!("setState"in f)){if(!("enterRoomAllowed"in f)){if(!("enterRoomDenied"in f)){if(!("leaveRoomDone"in f)){if(!("peerEntered"in f)){if(!("peerLeaved"in f))continue}}}}}}const g=v.get(c);if(typeof g>"u"){const L=u.length;v.set(c,L),u.push(h),m.push({methodId:L,name:p.method.name,config:f}),console.debug(`add config ${n}.${c}=${JSON.stringify(p)} from argument`)}else m[g].config=p,console.debug(`replace config ${n}.${c}=${JSON.stringify(p)} from argument`)}for(let p=0;p<u.length;p++){const h=u[p],f=m[p],c=f.config;if("share"in c){const g=this.createMethodProxy(h.bind(o),{share:c.share},i,f.methodId);o[f.name]=function(){return d.modification++,g.apply(null,arguments)}}else if("notify"in c){const g=this.createMethodProxy(h.bind(o),{notify:c.notify},i,f.methodId);o[f.name]=function(){return g.apply(null,arguments)}}else if("hostOnly"in c){const g=this.addHostOnlyFunction(h.bind(o),c.hostOnly);o[f.name]=function(){return d.modification++,g.apply(null,arguments)}}else"getState"in c?this.getStateMethods.set(i,{method:h.bind(o),config:c.getState,lastGet:0}):"setState"in c?this.setStateMethods.set(i,h.bind(o)):"beforeEnterRoom"in c?this.beforeEnterRoomMethods.set(i,h.bind(o)):"enterRoomAllowed"in c?this.enterRoomAllowedMethods.set(i,h.bind(o)):"enterRoomDenied"in c?this.enterRoomDeniedMethods.set(i,h.bind(o)):"leaveRoomDone"in c?this.leaveRoomDoneMethods.set(i,h.bind(o)):"peerEntered"in c?this.peerEnteredMethods.set(i,h.bind(o)):"peerProfileUpdated"in c?this.peerProfileUpdatedMethods.set(i,h.bind(o)):"peerLeaved"in c&&this.peerLeavedMethods.set(i,h.bind(o))}const P=I({definition:{objId:i,className:n,methods:m}});return this.doSendMessage(P),e}createFunctionProxy(e,t,o){const n=`${o}`,i={original:e,config:t};this.shareOrNotifyFunctions.set(n,i),i.promise=new Promise((u,m)=>{i.resolve=u,i.reject=m});const d=this;return function(){var u,m;if(d.ws===null){if(e)return e.apply(null,arguments)}else{let v=null,P="BROADCAST";return(((u=t.share)==null?void 0:u.type)==="afterExec"||((m=t.notify)==null?void 0:m.type)==="afterExec")&&(v=e.apply(null,arguments),P="OTHERCAST"),d.sendMessage(T(P,{funcId:o,args:Array.from(arguments)})),v??i.promise}}}createMethodProxy(e,t,o,n){const i=`${o}:${n}`,d={original:e,config:t};this.shareOrNotifyMethods.set(i,d),d.promise=new Promise((m,v)=>{d.resolve=m,d.reject=v});const u=this;return function(){var m,v;if(u.ws===null){if(e)return e.apply(null,[...arguments,u])}else{let P=null,p="BROADCAST";return(((m=t.share)==null?void 0:m.type)==="afterExec"||((v=t.notify)==null?void 0:v.type)==="afterExec")&&(P=e.apply(null,[...arguments,u]),p="OTHERCAST"),u.sendMessage(A(p,{objId:o,objRevision:u.shareObjects.get(o).revision,methodId:n,args:Array.from(arguments)})),P??d.promise}}}addHostOnlyFunction(e,t){const o=this;return function(){let n=o.selfPeer.order;for(const i of o.peers.values())n>i.order&&(n=i.order);o.selfPeer.order===n&&e.apply(null,[...arguments,o])}}saveStates(){if(!(!this.ws||!this.connecting))for(let[e,t]of this.shareObjects){if(t.modification==0)continue;const o=this.getStateMethods.get(e);if(!o)continue;const n=performance.now();(o.config.maxUpdates&&o.config.maxUpdates<=t.modification||o.config.maxInterval&&o.config.maxInterval<=n-o.lastGet)&&(this.doSendMessage(U({objId:e,objRevision:t.revision,state:o.method(this)})),o.lastGet=n,t.modification=0,console.debug(`state saved: ${e}`))}}applyInvocation(e,t){return e.apply(null,t)}}r.BeforeEnterRoom=J,r.EnterRoomAllowed=G,r.EnterRoomDenied=Q,r.GetState=x,r.HostOnly=B,r.LeaveRoomDone=z,r.Madoi=X,r.Notify=k,r.PeerEntered=V,r.PeerLeaved=W,r.PeerProfileUpdated=K,r.RoomProfileUpdated=q,r.SetState=H,r.Share=N,r.ShareClass=F,r.getStateConfigDefault=O,r.newDefineFunction=E,r.newDefineObject=I,r.newEnterRoom=j,r.newInvokeFunction=T,r.newInvokeMethod=A,r.newLeaveRoom=$,r.newPing=D,r.newUpdateObjectState=U,r.newUpdatePeerProfile=b,r.newUpdateRoomProfile=S,r.notifyConfigDefault=R,r.shareConfigDefault=w,Object.defineProperty(r,Symbol.toStringTag,{value:"Module"})});