UNPKG

madoi-client

Version:
718 lines (717 loc) 21.6 kB
var C = Object.defineProperty; var j = (s, r, e) => r in s ? C(s, r, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[r] = e; var f = (s, r, e) => j(s, typeof r != "symbol" ? r + "" : r, e); class D extends EventTarget { dispatchCustomEvent(r, e) { return super.dispatchEvent(new CustomEvent(r, { detail: e })); } } const M = { sender: "__PEER__", castType: "PEERTOSERVER", recipients: void 0 }, O = { sender: "__PEER__", castType: "BROADCAST", recipients: void 0 }, E = { sender: "__PEER__", recipients: void 0 }; function x(s = void 0) { return { type: "Ping", ...M, body: s }; } function I(s) { return { type: "EnterRoom", ...M, ...s }; } function F(s) { return { type: "LeaveRoom", ...M, ...s }; } function R(s) { return { type: "UpdateRoomProfile", ...O, ...s }; } function S(s) { return { type: "UpdatePeerProfile", ...O, ...s }; } function b(s) { return { type: "DefineFunction", ...M, ...s }; } function T(s) { return { type: "DefineObject", ...M, ...s }; } function A(s, r) { return { type: "InvokeFunction", castType: s, ...E, ...r }; } function U(s) { return { type: "UpdateObjectState", ...M, ...s }; } function L(s, r) { return { type: "InvokeMethod", castType: s, ...E, ...r }; } function N(s = {}) { return (r) => { r.madoiClassConfig_ = s; }; } const P = { type: "beforeExec", maxLog: 0, allowedTo: ["USER"] }; function k(s = P) { const r = s; return r.type || (r.type = "beforeExec"), r.maxLog || (r.maxLog = 0), (e, t, o) => { const n = { share: r }; e[t].madoiMethodConfig_ = n; }; } const g = { type: "beforeExec" }; function H(s = g) { const r = { ...s }; return (e, t, o) => { const n = { notify: r }; e[t].madoiMethodConfig_ = n; }; } const _ = { maxInterval: 5e3 }; function J(s = _) { const r = s; return (e, t, o) => { const n = { getState: r }; e[t].madoiMethodConfig_ = n; }; } function B(s = {}) { const r = s; return (e, t, o) => { const n = { setState: r }; e[t].madoiMethodConfig_ = n; }; } function Q(s = {}) { return (r, e, t) => { const n = { hostOnly: s }; r[e].madoiMethodConfig_ = n; }; } function G(s = {}) { const r = s; return (e, t, o) => { const n = { beforeEnterRoom: r }; e[t].madoiMethodConfig_ = n; }; } function z(s = {}) { const r = s; return (e, t, o) => { const n = { enterRoomAllowed: r }; e[t].madoiMethodConfig_ = n; }; } function q(s = {}) { const r = s; return (e, t, o) => { const n = { enterRoomDenied: r }; e[t].madoiMethodConfig_ = n; }; } function V(s = {}) { const r = s; return (e, t, o) => { const n = { leaveRoomDone: r }; e[t].madoiMethodConfig_ = n; }; } function W(s = {}) { const r = s; return (e, t, o) => { const n = { roomProfileUpdated: r }; e[t].madoiMethodConfig_ = n; }; } function K(s = {}) { const r = s; return (e, t, o) => { const n = { peerEntered: r }; e[t].madoiMethodConfig_ = n; }; } function X(s = {}) { const r = s; return (e, t, o) => { const n = { peerLeaved: r }; e[t].madoiMethodConfig_ = n; }; } function Y(s = {}) { const r = s; return (e, t, o) => { const n = { peerProfileUpdated: r }; e[t].madoiMethodConfig_ = n; }; } class Z extends D { constructor(e, t, o, n) { super(); f(this, "connecting", !1); f(this, "interimQueue"); f(this, "shareOrNotifyFunctions", /* @__PURE__ */ new Map()); f(this, "shareObjects", /* @__PURE__ */ new Map()); f(this, "shareOrNotifyMethods", /* @__PURE__ */ new Map()); // annotated methods f(this, "getStateMethods", /* @__PURE__ */ new Map()); f(this, "setStateMethods", /* @__PURE__ */ new Map()); // objectId -> @SetState method f(this, "beforeEnterRoomMethods", /* @__PURE__ */ new Map()); f(this, "enterRoomAllowedMethods", /* @__PURE__ */ new Map()); f(this, "enterRoomDeniedMethods", /* @__PURE__ */ new Map()); f(this, "leaveRoomDoneMethods", /* @__PURE__ */ new Map()); f(this, "roomProfileUpdatedMethods", /* @__PURE__ */ new Map()); f(this, "peerEnteredMethods", /* @__PURE__ */ new Map()); f(this, "peerLeavedMethods", /* @__PURE__ */ new Map()); f(this, "peerProfileUpdatedMethods", /* @__PURE__ */ new Map()); f(this, "url"); f(this, "ws", null); f(this, "room", { id: "", spec: { maxLog: 1e3 }, profile: {} }); f(this, "selfPeer", { id: "", order: -1, profile: {} }); f(this, "peers", /* @__PURE__ */ new Map()); f(this, "currentSender", null); f(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 a = document.querySelector("script[src$='madoi.js']").src.split("/", 5), h = (a[0] == "http:" ? "ws:" : "wss:") + "//" + a[2] + "/" + a[3]; this.url = `${h}/rooms/${e}${i}authToken=${t}`, this.room.id = e; } this.ws = new WebSocket(this.url), this.ws.onopen = (a) => this.handleOnOpen(a), this.ws.onclose = (a) => this.handleOnClose(a), this.ws.onerror = (a) => this.handleOnError(a), this.ws.onmessage = (a) => this.handleOnMessage(a), 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(R( { updates: o } )); } removeRoomProfile(e) { this.sendMessage(R( { 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(S( { updates: o } )); const n = { updates: o, peerId: this.selfPeer.id }; for (const [i, a] of this.peerProfileUpdatedMethods) a(n, this); this.dispatchCustomEvent("peerProfileUpdated", n); } removeSelfPeerProfile(e) { delete this.selfPeer.profile[e], this.sendMessage(S( { 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(x())); } handleOnOpen(e) { var t; this.connecting = !0; for (const [o, n] of this.beforeEnterRoomMethods) n(this.selfPeer.profile, this); this.doSendMessage(I({ 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 a; (a = n.resolve) == null || a.apply(null, arguments); }).catch(() => { var a; (a = n.reject) == null || a.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 = g.type); const o = e.name, n = this.shareOrNotifyFunctions.size, i = this.createFunctionProxy(e, { notify: t.notify }, n), a = function() { return i.apply(null, arguments); }; return this.doSendMessage(b({ definition: { funcId: n, name: o, config: t } })), a; } else if ("share" in t) { t.share.type || (t.share.type = P.type), t.share.maxLog || (t.share.maxLog = P.maxLog); const o = e.name, n = this.shareOrNotifyFunctions.size, i = this.createFunctionProxy(e, { share: t.share }, n), a = function() { return i.apply(null, arguments); }; return this.doSendMessage(b({ definition: { funcId: n, name: o, config: t } })), a; } 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, a = { instance: o, revision: 0, modification: 0 }; this.shareObjects.set(i, a), o.madoiObjectId_ = i; const h = new Array(), u = new Array(), m = /* @__PURE__ */ new Map(); Object.getOwnPropertyNames(Object.getPrototypeOf(o)).forEach((p) => { const l = o[p]; if (typeof l != "function" || !l.madoiMethodConfig_) return; const d = l.madoiMethodConfig_, c = h.length; m.set(p, c), h.push(l), u.push({ methodId: c, name: p, config: d }), console.debug(`add config ${n}.${p}=${JSON.stringify(d)} from decorator`); }); for (const p of t) { const l = p.method, d = p, c = l.name; if ("share" in d) d.share.type || (d.share.type = P.type), d.share.maxLog || (d.share.maxLog = P.maxLog); else if ("notify" in d) d.notify.type || (d.notify.type = g.type); else if (!("hostOnly" in d)) { if ("getState" in d) d.getState.maxInterval || (d.getState.maxInterval = _.maxInterval); else if (!("setState" in d)) { if (!("enterRoomAllowed" in d)) { if (!("enterRoomDenied" in d)) { if (!("leaveRoomDone" in d)) { if (!("peerEntered" in d)) { if (!("peerLeaved" in d)) continue; } } } } } } const v = m.get(c); if (typeof v > "u") { const w = h.length; m.set(c, w), h.push(l), u.push({ methodId: w, name: p.method.name, config: d }), console.debug(`add config ${n}.${c}=${JSON.stringify(p)} from argument`); } else u[v].config = p, console.debug(`replace config ${n}.${c}=${JSON.stringify(p)} from argument`); } for (let p = 0; p < h.length; p++) { const l = h[p], d = u[p], c = d.config; if ("share" in c) { const v = this.createMethodProxy( l.bind(o), { share: c.share }, i, d.methodId ); o[d.name] = function() { return a.modification++, v.apply(null, arguments); }; } else if ("notify" in c) { const v = this.createMethodProxy( l.bind(o), { notify: c.notify }, i, d.methodId ); o[d.name] = function() { return v.apply(null, arguments); }; } else if ("hostOnly" in c) { const v = this.addHostOnlyFunction( l.bind(o), c.hostOnly ); o[d.name] = function() { return a.modification++, v.apply(null, arguments); }; } else "getState" in c ? this.getStateMethods.set(i, { method: l.bind(o), config: c.getState, lastGet: 0 }) : "setState" in c ? this.setStateMethods.set(i, l.bind(o)) : "beforeEnterRoom" in c ? this.beforeEnterRoomMethods.set(i, l.bind(o)) : "enterRoomAllowed" in c ? this.enterRoomAllowedMethods.set(i, l.bind(o)) : "enterRoomDenied" in c ? this.enterRoomDeniedMethods.set(i, l.bind(o)) : "leaveRoomDone" in c ? this.leaveRoomDoneMethods.set(i, l.bind(o)) : "peerEntered" in c ? this.peerEnteredMethods.set(i, l.bind(o)) : "peerProfileUpdated" in c ? this.peerProfileUpdatedMethods.set(i, l.bind(o)) : "peerLeaved" in c && this.peerLeavedMethods.set(i, l.bind(o)); } const y = T({ definition: { objId: i, className: n, methods: u } }); return this.doSendMessage(y), e; } createFunctionProxy(e, t, o) { const n = `${o}`, i = { original: e, config: t }; this.shareOrNotifyFunctions.set(n, i), i.promise = new Promise((h, u) => { i.resolve = h, i.reject = u; }); const a = this; return function() { var h, u; if (a.ws === null) { if (e) return e.apply(null, arguments); } else { let m = null, y = "BROADCAST"; return (((h = t.share) == null ? void 0 : h.type) === "afterExec" || ((u = t.notify) == null ? void 0 : u.type) === "afterExec") && (m = e.apply(null, arguments), y = "OTHERCAST"), a.sendMessage(A( y, { funcId: o, args: Array.from(arguments) } )), m ?? i.promise; } }; } createMethodProxy(e, t, o, n) { const i = `${o}:${n}`, a = { original: e, config: t }; this.shareOrNotifyMethods.set(i, a), a.promise = new Promise((u, m) => { a.resolve = u, a.reject = m; }); const h = this; return function() { var u, m; if (h.ws === null) { if (e) return e.apply(null, [...arguments, h]); } else { let y = null, p = "BROADCAST"; return (((u = t.share) == null ? void 0 : u.type) === "afterExec" || ((m = t.notify) == null ? void 0 : m.type) === "afterExec") && (y = e.apply(null, [...arguments, h]), p = "OTHERCAST"), h.sendMessage(L( p, { objId: o, objRevision: h.shareObjects.get(o).revision, methodId: n, args: Array.from(arguments) } )), y ?? a.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); } } export { G as BeforeEnterRoom, z as EnterRoomAllowed, q as EnterRoomDenied, J as GetState, Q as HostOnly, V as LeaveRoomDone, Z as Madoi, H as Notify, K as PeerEntered, X as PeerLeaved, Y as PeerProfileUpdated, W as RoomProfileUpdated, B as SetState, k as Share, N as ShareClass, _ as getStateConfigDefault, b as newDefineFunction, T as newDefineObject, I as newEnterRoom, A as newInvokeFunction, L as newInvokeMethod, F as newLeaveRoom, x as newPing, U as newUpdateObjectState, S as newUpdatePeerProfile, R as newUpdateRoomProfile, g as notifyConfigDefault, P as shareConfigDefault };