251 lines
6.8 KiB
JavaScript
251 lines
6.8 KiB
JavaScript
"use strict";
|
|
|
|
export class Domain {
|
|
name;
|
|
id;
|
|
servers;
|
|
invalidServers;
|
|
|
|
toString() {
|
|
return `[${this.name}]`;
|
|
}
|
|
|
|
constructor(jsonData, domainServers) {
|
|
// FIXME check values
|
|
this.name = jsonData.name;
|
|
this.id = jsonData.id;
|
|
this.servers = [];
|
|
for (const domainServer of domainServers) {
|
|
this.servers.push(new DomainServer(domainServer));
|
|
}
|
|
this.invalidServers = [];
|
|
}
|
|
|
|
static async fromName(domainName) {
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), 2000);
|
|
|
|
const response = await fetch(`https://${domainName}/.well-known/usimp.json`, {
|
|
redirect: "manual",
|
|
signal: controller.signal,
|
|
});
|
|
|
|
clearTimeout(timer);
|
|
if (!response.ok) {
|
|
throw Error("Invalid response");
|
|
}
|
|
|
|
const data = await response.json();
|
|
return new Domain(data.domain, data.domain_servers);
|
|
}
|
|
|
|
chooseDomainServer() {
|
|
if (this.servers.length === 0) throw Error("No domain servers specified");
|
|
|
|
const servers = this.servers.filter(srv => !this.invalidServers.map(srv => srv.id).includes(srv.id));
|
|
if (servers.length === 0) throw Error("No domain servers reachable");
|
|
if (servers.length === 1) return servers[0];
|
|
|
|
const priority = servers.reduce((min, srv) => Math.min(min, srv.priority), Infinity);
|
|
const domainServers = servers.filter(srv => srv.priority === priority);
|
|
if (domainServers.length === 1) return servers[0];
|
|
|
|
const totalWeight = domainServers.reduce((total, srv) => total + srv.weight, 0);
|
|
const w = Math.floor(Math.random() * totalWeight);
|
|
|
|
let accumulator = 0;
|
|
for (const srv of domainServers) {
|
|
accumulator += srv.weight;
|
|
if (w < accumulator) return srv;
|
|
}
|
|
|
|
throw Error("Domain server selection did not work correctly");
|
|
}
|
|
}
|
|
|
|
export class DomainServer {
|
|
host;
|
|
id;
|
|
priority;
|
|
weight;
|
|
protocols;
|
|
|
|
toString() {
|
|
return `[${this.host}/${this.priority}/${this.weight}]`;
|
|
}
|
|
|
|
constructor(jsonData) {
|
|
// FIXME check values
|
|
this.host = jsonData.host;
|
|
this.id = jsonData.id;
|
|
this.priority = jsonData.priority;
|
|
this.weight = jsonData.weight;
|
|
this.protocols = jsonData.protocols;
|
|
}
|
|
}
|
|
|
|
export class Account {
|
|
|
|
}
|
|
|
|
export class Member {
|
|
|
|
}
|
|
|
|
export class Room {
|
|
|
|
}
|
|
|
|
export class Event {
|
|
|
|
}
|
|
|
|
export class Session {
|
|
domain;
|
|
server;
|
|
token;
|
|
|
|
httpBaseUrl;
|
|
websocket;
|
|
numRequests;
|
|
|
|
constructor(domain) {
|
|
this.domain = domain;
|
|
this.numRequests = 0;
|
|
}
|
|
|
|
async chooseDomainServer() {
|
|
while (!this.server) {
|
|
this.server = this.domain.chooseDomainServer();
|
|
|
|
const host = this.server.host;
|
|
const protocols = this.server.protocols;
|
|
|
|
/*
|
|
if ("ws" in protocols) {
|
|
this.websocket = new WebSocket(`ws://${host}:${protocols.ws}/_usimp/websocket`, ["usimp"]);
|
|
}
|
|
*/
|
|
|
|
// TODO http -> https
|
|
if ("http" in protocols) {
|
|
this.httpBaseUrl = `http://${host}:${protocols.http}/_usimp`;
|
|
try {
|
|
return await this.ping();
|
|
} catch {
|
|
this.domain.invalidServers.push(this.server);
|
|
this.server = null;
|
|
}
|
|
} else {
|
|
console.warn(`Domain server ${this.server} does not support 'https' transport protocol`);
|
|
this.domain.invalidServers.push(this.server);
|
|
this.server = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
async send(endpoint, data, timeout = 2000, forceHttp = false) {
|
|
this.numRequests++;
|
|
|
|
if (!forceHttp && this.websocket) {
|
|
this.websocket.send(JSON.stringify({
|
|
'request_num': this.numRequests,
|
|
'data': data
|
|
}));
|
|
} else {
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), timeout);
|
|
|
|
let headers = {
|
|
'Content-Type': 'application/json',
|
|
'To-Domain': this.domain.id,
|
|
};
|
|
if (this.token) {
|
|
headers['Authorization'] = `usimp ${this.token}`;
|
|
}
|
|
|
|
const startTime = new Date();
|
|
const response = await fetch(`${this.httpBaseUrl}/${endpoint}`, {
|
|
method: "POST",
|
|
headers: headers,
|
|
body: JSON.stringify(data),
|
|
signal: controller.signal,
|
|
});
|
|
const endTime = new Date();
|
|
clearTimeout(timer);
|
|
|
|
const responseData = await response.json();
|
|
responseData.duration = endTime - startTime;
|
|
return responseData;
|
|
}
|
|
}
|
|
|
|
async ping() {
|
|
let result = {"http": null, "ws": null};
|
|
const resHttp = await this.send("ping", {}, undefined, true);
|
|
result.http = resHttp.duration;
|
|
|
|
if (this.websocket) {
|
|
const resWs = await this.send("ping", {});
|
|
result.ws = resWs.duration;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
async authenticate(accountName, password) {
|
|
const response = await this.send("authenticate", {
|
|
type: "password",
|
|
name: accountName,
|
|
password: password,
|
|
});
|
|
if (response.status === "success") {
|
|
this.token = response.data.token;
|
|
}
|
|
return response;
|
|
}
|
|
|
|
async sendEvent(roomId, data) {
|
|
return this.send("send_event", {
|
|
room_id: roomId,
|
|
data: data,
|
|
});
|
|
}
|
|
|
|
subscribe(func) {
|
|
this.numRequests++;
|
|
if (this.websocket) {
|
|
// TODO
|
|
} else {
|
|
let headers = {
|
|
'Content-Type': 'application/json',
|
|
'To-Domain': this.domain.id,
|
|
};
|
|
if (this.token) {
|
|
headers['Authorization'] = `usimp ${this.token}`;
|
|
}
|
|
|
|
fetch(`${this.httpBaseUrl}/subscribe`, {
|
|
method: "POST",
|
|
headers: headers,
|
|
body: JSON.stringify({}),
|
|
}).then(response => {
|
|
return response.json();
|
|
}).then(response => {
|
|
if (response.status === "success") {
|
|
this.subscribe(func);
|
|
func(response);
|
|
} else {
|
|
setTimeout(() => {
|
|
this.subscribe(func);
|
|
}, 1000);
|
|
}
|
|
}).catch(() => {
|
|
setTimeout(() => {
|
|
this.subscribe(func);
|
|
}, 1000);
|
|
});
|
|
}
|
|
}
|
|
}
|