madoi-client
Version:
A Client library of Madoi Server for Distributed Sharing
718 lines (717 loc) • 21.6 kB
JavaScript
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
};