using TypeScript
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
.idea/
|
.idea/
|
||||||
|
www/res/scripts/*
|
||||||
|
12
Makefile
Normal file
12
Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.DEFAULT_GOAL := build
|
||||||
|
|
||||||
|
OUT=www/res/scripts
|
||||||
|
|
||||||
|
${OUT}/locutus.js ${OUT}/usimp.js: src/locutus.ts src/usimp.ts
|
||||||
|
tsc
|
||||||
|
|
||||||
|
build: ${OUT}/locutus.js ${OUT}/usimp.js
|
||||||
|
perl -i -pE 's/(\?v=[0-9]+\.[0-9]+\.[0-9]+\+)([0-9]+)/($$1).($$2+1)/eg' www/index.html
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf "${OUT}"
|
@ -1,26 +1,26 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
import * as USIMP from "./usimp.js";
|
import * as USIMP from "./usimp";
|
||||||
|
|
||||||
export class App {
|
export class App {
|
||||||
account;
|
account: USIMP.Account | null;
|
||||||
defaultLocation;
|
defaultLocation: string;
|
||||||
main;
|
main: HTMLElement;
|
||||||
windows;
|
windows: HTMLElement;
|
||||||
session;
|
session: USIMP.Session | null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (localStorage.session === undefined) {
|
|
||||||
this.defaultLocation = '/welcome';
|
this.defaultLocation = '/welcome';
|
||||||
this.account = null;
|
this.account = null;
|
||||||
} else {
|
this.session = null;
|
||||||
const session = JSON.parse(localStorage.session);
|
|
||||||
this.defaultLocation = '/';
|
|
||||||
this.account = session.account;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.main = document.getElementsByTagName("main")[0];
|
const main = document.getElementsByTagName("main")[0];
|
||||||
this.windows = document.getElementById("windows");
|
if (!main) throw Error("Element <main> not found");
|
||||||
|
this.main = main;
|
||||||
|
|
||||||
|
const windows = document.getElementById("windows");
|
||||||
|
if (!windows) throw Error("Element #windows not found");
|
||||||
|
this.windows = windows;
|
||||||
|
|
||||||
window.addEventListener("hashchange", event => {
|
window.addEventListener("hashchange", event => {
|
||||||
this.handleUrl(event.newURL);
|
this.handleUrl(event.newURL);
|
||||||
@ -29,21 +29,21 @@ export class App {
|
|||||||
this.handleUrl(document.URL);
|
this.handleUrl(document.URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
setHash(hash) {
|
setHash(hash: string) {
|
||||||
const url = new URL(document.URL);
|
const url = new URL(document.URL);
|
||||||
url.hash = hash;
|
url.hash = hash;
|
||||||
location.href = url.toString();
|
location.href = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUrl(url) {
|
handleUrl(url: string) {
|
||||||
this.handleHash(new URL(url).hash);
|
this.handleHash(new URL(url).hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHash(hash) {
|
handleHash(hash: string) {
|
||||||
if (hash[0] === '#') hash = hash.substr(1);
|
if (hash[0] === '#') hash = hash.substr(1);
|
||||||
|
|
||||||
const defaultCase = () => {
|
const defaultCase = () => {
|
||||||
history.replaceState(null, null, `#${this.defaultLocation}`);
|
history.replaceState(null, document.title, `#${this.defaultLocation}`);
|
||||||
this.handleHash(this.defaultLocation);
|
this.handleHash(this.defaultLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ export class App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(accountName, domainName, password) {
|
async login(accountName: string, domainName: string, password: string) {
|
||||||
let domain;
|
let domain;
|
||||||
try {
|
try {
|
||||||
domain = await USIMP.Domain.fromName(domainName);
|
domain = await USIMP.Domain.fromName(domainName);
|
||||||
@ -135,13 +135,15 @@ export class App {
|
|||||||
this.windows.appendChild(win);
|
this.windows.appendChild(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
addMessage(message) {
|
addMessage(message: string) {
|
||||||
const msg = document.createElement("div");
|
const msg = document.createElement("div");
|
||||||
msg.classList.add("message");
|
msg.classList.add("message");
|
||||||
|
|
||||||
msg.innerText = message;
|
msg.innerText = message;
|
||||||
|
|
||||||
const chat = this.main.getElementsByClassName("chat-history")[0];
|
const chat = this.main.getElementsByClassName("chat-history")[0];
|
||||||
|
if (!chat) throw Error("Element .chat-history not found");
|
||||||
|
|
||||||
chat.appendChild(msg);
|
chat.appendChild(msg);
|
||||||
chat.scrollTop = chat.scrollHeight;
|
chat.scrollTop = chat.scrollHeight;
|
||||||
}
|
}
|
||||||
@ -159,26 +161,33 @@ export class App {
|
|||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</form>`;
|
</form>`;
|
||||||
|
|
||||||
win.getElementsByTagName("form")[0].addEventListener("submit", async event => {
|
const form = win.getElementsByTagName("form")[0];
|
||||||
|
if (!form) throw Error("Element <form> not found");
|
||||||
|
|
||||||
|
form.addEventListener("submit", async event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const form = event.target;
|
|
||||||
for (const e of form) {
|
for (const e of form.getElementsByTagName("button")) e.disabled = false;
|
||||||
|
for (const e of form.getElementsByTagName("input")) {
|
||||||
e.disabled = true;
|
e.disabled = true;
|
||||||
e.removeAttribute("invalid");
|
e.removeAttribute("invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
await this.login(form.account.value, form.domain.value, form.password.value);
|
await this.login(form['account'].value, form['domain'].value, form['password'].value);
|
||||||
} finally {
|
} finally {
|
||||||
for (const d of form.getElementsByTagName("div")) form.removeChild(d);
|
for (const d of form.getElementsByTagName("div")) form.removeChild(d);
|
||||||
for (const e of form) e.disabled = false;
|
for (const e of form.getElementsByTagName("input")) e.disabled = false;
|
||||||
|
for (const e of form.getElementsByTagName("button")) e.disabled = false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.toString() === "Error: unable to authenticate") {
|
if (error.toString() === "Error: unable to authenticate") {
|
||||||
document.getElementsByName("account")[0].setAttribute("invalid", "invalid");
|
const account = document.getElementsByName("account")[0];
|
||||||
|
if (account) account.setAttribute("invalid", "invalid");
|
||||||
} else {
|
} else {
|
||||||
document.getElementsByName("domain")[0].setAttribute("invalid", "invalid");
|
const domain = document.getElementsByName("domain")[0];
|
||||||
|
if (domain) domain.setAttribute("invalid", "invalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
@ -190,18 +199,26 @@ export class App {
|
|||||||
|
|
||||||
this.windows.appendChild(win);
|
this.windows.appendChild(win);
|
||||||
|
|
||||||
document.getElementsByName("domain")[0].addEventListener("input", (event) => {
|
const domain = document.getElementsByName("domain")[0];
|
||||||
event.target.removeAttribute("invalid");
|
if (!domain) throw Error("Element name=domain not found");
|
||||||
|
|
||||||
|
domain.addEventListener("input", (event) => {
|
||||||
|
domain.removeAttribute("invalid");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementsByName("account")[0].addEventListener("input", (event) => {
|
const account = document.getElementsByName("account")[0];
|
||||||
event.target.removeAttribute("invalid");
|
if (!account) throw Error("Element name=account not found");
|
||||||
|
|
||||||
|
account.addEventListener("input", (event) => {
|
||||||
|
account.removeAttribute("invalid");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementsByName("account")[0].focus();
|
account.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupMain() {
|
setupMain() {
|
||||||
|
if (!this.session) throw Error("Invalid state");
|
||||||
|
|
||||||
this.main.innerHTML = `
|
this.main.innerHTML = `
|
||||||
<div class="chat">
|
<div class="chat">
|
||||||
<div class="chat-history"></div>
|
<div class="chat-history"></div>
|
||||||
@ -210,9 +227,13 @@ export class App {
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
const input = document.getElementsByName("message")[0];
|
const input = document.getElementsByTagName("input")[0];
|
||||||
|
if (!input) throw Error("Element <input> not found");
|
||||||
|
|
||||||
input.addEventListener("keyup", async event => {
|
input.addEventListener("keyup", async event => {
|
||||||
if (event.key === "Enter" && input.value.length > 0) {
|
if (event.key === "Enter" && input.value.length > 0) {
|
||||||
|
if (!this.session) throw Error("No session found");
|
||||||
|
|
||||||
this.addMessage(input.value);
|
this.addMessage(input.value);
|
||||||
const val = input.value;
|
const val = input.value;
|
||||||
input.value = "";
|
input.value = "";
|
11
src/main.ts
Normal file
11
src/main.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import * as Locutus from "./locutus";
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// Remove <noscript> tags
|
||||||
|
for (const noscript of document.getElementsByTagName("noscript")) {
|
||||||
|
noscript.remove();
|
||||||
|
}
|
||||||
|
new Locutus.App();
|
||||||
|
});
|
@ -1,16 +1,48 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
interface DomainJson {
|
||||||
|
name: string,
|
||||||
|
id: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DomainServerJson {
|
||||||
|
host: string,
|
||||||
|
id: string,
|
||||||
|
priority: number,
|
||||||
|
weight: number,
|
||||||
|
protocols: {
|
||||||
|
https: number | undefined,
|
||||||
|
wss: number | undefined,
|
||||||
|
http: number | undefined,
|
||||||
|
ws: number | undefined,
|
||||||
|
udp: number | undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventJson {
|
||||||
|
data: {
|
||||||
|
event: {
|
||||||
|
data: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WellKnownJson {
|
||||||
|
domain: DomainJson,
|
||||||
|
domain_servers: DomainServerJson[]
|
||||||
|
}
|
||||||
|
|
||||||
export class Domain {
|
export class Domain {
|
||||||
name;
|
name: string;
|
||||||
id;
|
id: string;
|
||||||
servers;
|
servers: DomainServer[];
|
||||||
invalidServers;
|
invalidServers: DomainServer[];
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `[${this.name}]`;
|
return `[${this.name}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(jsonData, domainServers) {
|
constructor(jsonData: DomainJson, domainServers: DomainServerJson[]) {
|
||||||
// FIXME check values
|
// FIXME check values
|
||||||
this.name = jsonData.name;
|
this.name = jsonData.name;
|
||||||
this.id = jsonData.id;
|
this.id = jsonData.id;
|
||||||
@ -21,7 +53,7 @@ export class Domain {
|
|||||||
this.invalidServers = [];
|
this.invalidServers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromName(domainName) {
|
static async fromName(domainName: string): Promise<Domain> {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timer = setTimeout(() => controller.abort(), 2000);
|
const timer = setTimeout(() => controller.abort(), 2000);
|
||||||
|
|
||||||
@ -35,20 +67,20 @@ export class Domain {
|
|||||||
throw Error("Invalid response");
|
throw Error("Invalid response");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data: WellKnownJson = await response.json();
|
||||||
return new Domain(data.domain, data.domain_servers);
|
return new Domain(data.domain, data.domain_servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
chooseDomainServer() {
|
chooseDomainServer(): DomainServer {
|
||||||
if (this.servers.length === 0) throw Error("No domain servers specified");
|
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));
|
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 === 0) throw Error("No domain servers reachable");
|
||||||
if (servers.length === 1) return servers[0];
|
if (servers.length === 1 && servers[0]) return servers[0];
|
||||||
|
|
||||||
const priority = servers.reduce((min, srv) => Math.min(min, srv.priority), Infinity);
|
const priority = servers.reduce((min, srv) => Math.min(min, srv.priority), Infinity);
|
||||||
const domainServers = servers.filter(srv => srv.priority === priority);
|
const domainServers = servers.filter(srv => srv.priority === priority);
|
||||||
if (domainServers.length === 1) return servers[0];
|
if (domainServers.length === 1 && servers[0]) return servers[0];
|
||||||
|
|
||||||
const totalWeight = domainServers.reduce((total, srv) => total + srv.weight, 0);
|
const totalWeight = domainServers.reduce((total, srv) => total + srv.weight, 0);
|
||||||
const w = Math.floor(Math.random() * totalWeight);
|
const w = Math.floor(Math.random() * totalWeight);
|
||||||
@ -64,18 +96,23 @@ export class Domain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DomainServer {
|
export class DomainServer {
|
||||||
host;
|
host: string;
|
||||||
id;
|
id: string;
|
||||||
priority;
|
priority: number;
|
||||||
weight;
|
weight: number;
|
||||||
protocols;
|
protocols: {
|
||||||
|
https: number | undefined,
|
||||||
|
wss: number | undefined,
|
||||||
|
http: number | undefined,
|
||||||
|
ws: number | undefined,
|
||||||
|
udp: number | undefined
|
||||||
|
};
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `[${this.host}/${this.priority}/${this.weight}]`;
|
return `[${this.host}/${this.priority}/${this.weight}]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(jsonData) {
|
constructor(jsonData: DomainServerJson) {
|
||||||
// FIXME check values
|
|
||||||
this.host = jsonData.host;
|
this.host = jsonData.host;
|
||||||
this.id = jsonData.id;
|
this.id = jsonData.id;
|
||||||
this.priority = jsonData.priority;
|
this.priority = jsonData.priority;
|
||||||
@ -101,20 +138,24 @@ export class Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Session {
|
export class Session {
|
||||||
domain;
|
domain: Domain;
|
||||||
server;
|
server: DomainServer | null;
|
||||||
token;
|
token: string | null;
|
||||||
|
|
||||||
httpBaseUrl;
|
httpBaseUrl: string | null;
|
||||||
websocket;
|
websocket: WebSocket | null;
|
||||||
numRequests;
|
numRequests: number;
|
||||||
|
|
||||||
constructor(domain) {
|
constructor(domain: Domain) {
|
||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
this.numRequests = 0;
|
this.numRequests = 0;
|
||||||
|
this.server = null;
|
||||||
|
this.token = null;
|
||||||
|
this.httpBaseUrl = null;
|
||||||
|
this.websocket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async chooseDomainServer() {
|
async chooseDomainServer(): Promise<{ http: number | null, ws: number | null } | undefined> {
|
||||||
while (!this.server) {
|
while (!this.server) {
|
||||||
this.server = this.domain.chooseDomainServer();
|
this.server = this.domain.chooseDomainServer();
|
||||||
|
|
||||||
@ -128,7 +169,7 @@ export class Session {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO http -> https
|
// TODO http -> https
|
||||||
if ("http" in protocols) {
|
if (protocols) {
|
||||||
this.httpBaseUrl = `http://${host}:${protocols.http}/_usimp`;
|
this.httpBaseUrl = `http://${host}:${protocols.http}/_usimp`;
|
||||||
try {
|
try {
|
||||||
return await this.ping();
|
return await this.ping();
|
||||||
@ -142,9 +183,10 @@ export class Session {
|
|||||||
this.server = null;
|
this.server = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(endpoint, data, timeout = 2000, forceHttp = false) {
|
async send(endpoint: string, data: object, timeout: number = 2000, forceHttp: boolean = false) {
|
||||||
this.numRequests++;
|
this.numRequests++;
|
||||||
|
|
||||||
if (!forceHttp && this.websocket) {
|
if (!forceHttp && this.websocket) {
|
||||||
@ -156,7 +198,7 @@ export class Session {
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timer = setTimeout(() => controller.abort(), timeout);
|
const timer = setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
let headers = {
|
let headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'To-Domain': this.domain.id,
|
'To-Domain': this.domain.id,
|
||||||
};
|
};
|
||||||
@ -175,7 +217,7 @@ export class Session {
|
|||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
responseData.duration = endTime - startTime;
|
responseData.duration = endTime.getUTCMilliseconds() - startTime.getUTCMilliseconds();
|
||||||
return responseData;
|
return responseData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +235,7 @@ export class Session {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async authenticate(accountName, password) {
|
async authenticate(accountName: string, password: string) {
|
||||||
const response = await this.send("authenticate", {
|
const response = await this.send("authenticate", {
|
||||||
type: "password",
|
type: "password",
|
||||||
name: accountName,
|
name: accountName,
|
||||||
@ -205,19 +247,19 @@ export class Session {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendEvent(roomId, data) {
|
async sendEvent(roomId: string, data: object) {
|
||||||
return this.send("send_event", {
|
return this.send("send_event", {
|
||||||
room_id: roomId,
|
room_id: roomId,
|
||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(func) {
|
subscribe(func: (a: EventJson) => void) {
|
||||||
this.numRequests++;
|
this.numRequests++;
|
||||||
if (this.websocket) {
|
if (this.websocket) {
|
||||||
// TODO
|
// TODO
|
||||||
} else {
|
} else {
|
||||||
let headers = {
|
let headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'To-Domain': this.domain.id,
|
'To-Domain': this.domain.id,
|
||||||
};
|
};
|
71
tsconfig.json
Normal file
71
tsconfig.json
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||||
|
"module": "es2020", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "www/res/scripts", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
"strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
"strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
"noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
@ -19,8 +19,10 @@ Distributed, end-to-end encrypted instant messaging."/>
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<link rel="icon" sizes="64x64" href="/favicon.ico" type="image/x-icon"/>
|
<link rel="icon" sizes="64x64" href="/favicon.ico" type="image/x-icon"/>
|
||||||
<link rel="stylesheet" href="/res/styles/styles.css?v=0.0.0-0" type="text/css"/>
|
<link rel="stylesheet" href="/res/styles/styles.css?v=0.0.0+10" type="text/css"/>
|
||||||
<script src="/res/js/main.js?v=0.0.0-0" type="module"></script>
|
<script type="module" src="/res/scripts/usimp.js?v=0.0.0+10"></script>
|
||||||
|
<script type="module" src="/res/scripts/locutus.js?v=0.0.0+10"></script>
|
||||||
|
<script type="module" src="/res/scripts/main.js?v=0.0.0+10"></script>
|
||||||
<meta http-equiv="Content-Security-Policy" content="
|
<meta http-equiv="Content-Security-Policy" content="
|
||||||
default-src 'none';
|
default-src 'none';
|
||||||
style-src 'self';
|
style-src 'self';
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
import * as Locutus from "./modules/locutus.js";
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
|
||||||
// Remove <noscript> tag
|
|
||||||
document.getElementsByTagName("noscript")[0].remove();
|
|
||||||
new Locutus.App();
|
|
||||||
});
|
|
Reference in New Issue
Block a user