228 lines
7.5 KiB
TypeScript
228 lines
7.5 KiB
TypeScript
import * as ipc from "../ipc/BrowserIPC";
|
|
import * as loader from "tc-loader";
|
|
import {Stage} from "tc-loader";
|
|
|
|
import {
|
|
AbstractAvatarManager,
|
|
AbstractAvatarManagerFactory, AvatarState, AvatarStateData, ClientAvatar,
|
|
kIPCAvatarChannel,
|
|
setGlobalAvatarManagerFactory, uniqueId2AvatarId
|
|
} from "../file/Avatars";
|
|
import {IPCChannel} from "../ipc/BrowserIPC";
|
|
import {AppParameters} from "../settings";
|
|
import {ChannelMessage} from "../ipc/BrowserIPC";
|
|
import {guid} from "../crypto/uid";
|
|
import { tr } from "tc-shared/i18n/localize";
|
|
|
|
function isEquivalent(a, b) {
|
|
// Create arrays of property names
|
|
const aProps = Object.getOwnPropertyNames(a);
|
|
const bProps = Object.getOwnPropertyNames(b);
|
|
|
|
// If number of properties is different,
|
|
// objects are not equivalent
|
|
if (aProps.length != bProps.length) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < aProps.length; i++) {
|
|
const propName = aProps[i];
|
|
|
|
// If values of same property are not equal,
|
|
// objects are not equivalent
|
|
if (a[propName] !== b[propName]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If we made it this far, objects
|
|
// are considered equivalent
|
|
return true;
|
|
}
|
|
|
|
class RemoteAvatar extends ClientAvatar {
|
|
readonly avatarId: string;
|
|
readonly type: "avatar" | "client-avatar";
|
|
|
|
constructor(clientAvatarId: string, type: "avatar" | "client-avatar") {
|
|
super(clientAvatarId);
|
|
|
|
this.avatarId = guid();
|
|
this.type = type;
|
|
}
|
|
|
|
protected destroyStateData(state: AvatarState, data: AvatarStateData[AvatarState]) {}
|
|
|
|
public updateStateFromRemote(state: AvatarState, data: AvatarStateData[AvatarState]) {
|
|
if(this.getState() === state && isEquivalent(this.getStateData(), data))
|
|
return;
|
|
|
|
this.setState(state, data, true);
|
|
}
|
|
}
|
|
|
|
class RemoteAvatarManager extends AbstractAvatarManager {
|
|
readonly handlerId: string;
|
|
readonly ipcChannel: IPCChannel;
|
|
private knownAvatars: RemoteAvatar[] = [];
|
|
|
|
constructor(handlerId: string, ipcChannel: IPCChannel) {
|
|
super();
|
|
|
|
this.ipcChannel = ipcChannel;
|
|
this.handlerId = handlerId;
|
|
}
|
|
|
|
destroy() {
|
|
this.knownAvatars.forEach(e => e.destroy());
|
|
}
|
|
|
|
resolveAvatar(clientAvatarId: string, avatarHash?: string): ClientAvatar {
|
|
const sendRequest = (avatar: RemoteAvatar) => this.ipcChannel.sendMessage("load-avatar", {
|
|
avatarId: avatar.avatarId,
|
|
handlerId: this.handlerId,
|
|
|
|
keyType: "avatar",
|
|
clientAvatarId: avatar.clientAvatarId,
|
|
avatarVersion: avatarHash
|
|
});
|
|
|
|
const cachedAvatar = this.knownAvatars.find(e => e.type === "avatar" && e.avatarId === clientAvatarId);
|
|
if(cachedAvatar) {
|
|
if(cachedAvatar.getAvatarHash() !== avatarHash)
|
|
sendRequest(cachedAvatar); /* update */
|
|
return cachedAvatar;
|
|
}
|
|
|
|
let avatar = new RemoteAvatar(clientAvatarId, "avatar");
|
|
avatar.setLoading();
|
|
this.knownAvatars.push(avatar);
|
|
sendRequest(avatar);
|
|
return avatar;
|
|
}
|
|
|
|
resolveClientAvatar(client: { id?: number; database_id?: number; clientUniqueId: string }) {
|
|
const sendRequest = (avatar: RemoteAvatar) => this.ipcChannel.sendMessage("load-avatar", {
|
|
avatarId: avatar.avatarId,
|
|
handlerId: this.handlerId,
|
|
|
|
keyType: "client",
|
|
clientId: client.id,
|
|
clientUniqueId: client.clientUniqueId,
|
|
clientDatabaseId: client.database_id
|
|
});
|
|
|
|
const clientAvatarId = uniqueId2AvatarId(client.clientUniqueId);
|
|
const cachedAvatar = this.knownAvatars.find(e => e.type === "client-avatar" && e.clientAvatarId === clientAvatarId);
|
|
if(cachedAvatar) {
|
|
//sendRequest(cachedAvatar); /* just update in case */
|
|
return cachedAvatar;
|
|
}
|
|
|
|
let avatar = new RemoteAvatar(clientAvatarId, "client-avatar");
|
|
avatar.setLoading();
|
|
this.knownAvatars.push(avatar);
|
|
sendRequest(avatar);
|
|
|
|
return avatar;
|
|
}
|
|
|
|
handleAvatarLoadCallback(data: any) {
|
|
const avatar = this.knownAvatars.find(e => e.avatarId === data.avatarId);
|
|
if(!avatar) return;
|
|
|
|
if(!(data.success === true)) {
|
|
avatar.setErrored({ message: data.message });
|
|
return;
|
|
}
|
|
|
|
if(avatar.getAvatarHash() !== data.hash)
|
|
avatar.events.fire("avatar_changed", { newAvatarHash: data.hash });
|
|
|
|
avatar.updateStateFromRemote(data.state, data.stateData);
|
|
}
|
|
|
|
handleAvatarEvent(data: any) {
|
|
const avatar = this.knownAvatars.find(e => e.avatarId === data.avatarId);
|
|
if(!avatar) return;
|
|
|
|
avatar.events.fire(data.event.type, data.event, true);
|
|
}
|
|
}
|
|
|
|
class RemoteAvatarManagerFactory extends AbstractAvatarManagerFactory {
|
|
private readonly ipcChannel: IPCChannel;
|
|
private manager: {[key: string]: RemoteAvatarManager} = {};
|
|
|
|
private callbackHandlerQueried: () => void;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.ipcChannel = ipc.getIpcInstance().createChannel(AppParameters.getValue(AppParameters.KEY_IPC_REMOTE_ADDRESS, "invalid"), kIPCAvatarChannel);
|
|
this.ipcChannel.messageHandler = this.handleIpcMessage.bind(this);
|
|
}
|
|
|
|
async initialize() {
|
|
this.ipcChannel.sendMessage("query-handlers", {});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
const timeout = setTimeout(() => {
|
|
this.callbackHandlerQueried = undefined;
|
|
reject(tr("handler query timeout"));
|
|
}, 5000);
|
|
|
|
this.callbackHandlerQueried = () => {
|
|
clearTimeout(timeout);
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
|
|
getManager(handlerId: string): AbstractAvatarManager {
|
|
return this.manager[handlerId];
|
|
}
|
|
|
|
hasManager(handlerId: string): boolean {
|
|
return typeof this.manager[handlerId] !== "undefined";
|
|
}
|
|
|
|
private handleIpcMessage(_remoteId: string, broadcast: boolean, message: ChannelMessage) {
|
|
if(broadcast) {
|
|
if(message.type === "notify-handler-destroyed") {
|
|
const manager = this.manager[message.data.handler];
|
|
delete this.manager[message.data.handler];
|
|
manager?.destroy();
|
|
} else if(message.type === "notify-handler-created") {
|
|
this.manager[message.data.handler] = new RemoteAvatarManager(message.data.handler, this.ipcChannel);
|
|
}
|
|
} else {
|
|
if(message.type === "notify-handlers") {
|
|
Object.values(this.manager).forEach(e => e.destroy());
|
|
this.manager = {};
|
|
|
|
for(const handlerId of message.data.handlers)
|
|
this.manager[handlerId] = new RemoteAvatarManager(handlerId, this.ipcChannel);
|
|
|
|
if(this.callbackHandlerQueried)
|
|
this.callbackHandlerQueried();
|
|
} else if(message.type === "load-avatar-result") {
|
|
const manager = this.manager[message.data.handlerId];
|
|
manager?.handleAvatarLoadCallback(message.data);
|
|
} else if(message.type === "avatar-event") {
|
|
const manager = this.manager[message.data.handlerId];
|
|
manager?.handleAvatarEvent(message.data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
loader.register_task(Stage.JAVASCRIPT_INITIALIZING, {
|
|
priority: 10,
|
|
name: "IPC avatar init",
|
|
function: async () => {
|
|
let factory = new RemoteAvatarManagerFactory();
|
|
await factory.initialize();
|
|
setGlobalAvatarManagerFactory(factory);
|
|
}
|
|
}); |