|
|
|
import { Modal } from "./modules/modal.js";
|
|
|
|
import { notificationBox, whichAnimationEvent } from "./modules/common.js";
|
|
|
|
import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from "./modules/common.js";
|
|
|
|
import { loadLangSelector } from "./modules/lang.js";
|
|
|
|
import { initValidator } from "./modules/validator.js";
|
|
|
|
import { Discord, Telegram, ServiceConfiguration } from "./modules/account-linking.js";
|
|
|
|
|
|
|
|
interface formWindow extends Window {
|
|
|
|
invalidPassword: string;
|
|
|
|
successModal: Modal;
|
|
|
|
telegramModal: Modal;
|
|
|
|
discordModal: Modal;
|
|
|
|
matrixModal: Modal;
|
|
|
|
confirmationModal: Modal;
|
|
|
|
redirectToJellyfin: boolean;
|
|
|
|
code: string;
|
|
|
|
messages: { [key: string]: string };
|
|
|
|
confirmation: boolean;
|
|
|
|
telegramRequired: boolean;
|
|
|
|
telegramPIN: string;
|
|
|
|
discordRequired: boolean;
|
|
|
|
discordPIN: string;
|
|
|
|
discordStartCommand: string;
|
|
|
|
discordInviteLink: boolean;
|
|
|
|
discordServerName: string;
|
|
|
|
matrixRequired: boolean;
|
|
|
|
matrixUserID: string;
|
|
|
|
userExpiryEnabled: boolean;
|
|
|
|
userExpiryMonths: number;
|
|
|
|
userExpiryDays: number;
|
|
|
|
userExpiryHours: number;
|
|
|
|
userExpiryMinutes: number;
|
|
|
|
userExpiryMessage: string;
|
|
|
|
emailRequired: boolean;
|
|
|
|
captcha: boolean;
|
|
|
|
reCAPTCHA: boolean;
|
|
|
|
reCAPTCHASiteKey: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
loadLangSelector("form");
|
|
|
|
|
|
|
|
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
|
|
|
|
|
|
|
|
window.animationEvent = whichAnimationEvent();
|
|
|
|
|
|
|
|
window.successModal = new Modal(document.getElementById("modal-success"), true);
|
|
|
|
|
|
|
|
|
|
|
|
var telegramVerified = false;
|
|
|
|
if (window.telegramEnabled) {
|
|
|
|
window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired);
|
|
|
|
const telegramButton = document.getElementById("link-telegram") as HTMLSpanElement;
|
|
|
|
|
|
|
|
const telegramConf: ServiceConfiguration = {
|
|
|
|
modal: window.telegramModal as Modal,
|
|
|
|
pin: window.telegramPIN,
|
|
|
|
pinURL: "",
|
|
|
|
verifiedURL: "/invite/" + window.code + "/telegram/verified/",
|
|
|
|
invalidCodeError: window.messages["errorInvalidCode"],
|
|
|
|
accountLinkedError: window.messages["errorAccountLinked"],
|
|
|
|
successError: window.messages["verified"],
|
|
|
|
successFunc: (modalClosed: boolean) => {
|
|
|
|
if (modalClosed) return;
|
|
|
|
telegramVerified = true;
|
|
|
|
telegramButton.classList.add("unfocused");
|
|
|
|
document.getElementById("contact-via").classList.remove("unfocused");
|
|
|
|
document.getElementById("contact-via-email").parentElement.classList.remove("unfocused");
|
|
|
|
const radio = document.getElementById("contact-via-telegram") as HTMLInputElement;
|
|
|
|
radio.parentElement.classList.remove("unfocused");
|
|
|
|
radio.checked = true;
|
|
|
|
validatorFunc();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const telegram = new Telegram(telegramConf);
|
|
|
|
|
|
|
|
telegramButton.onclick = () => { telegram.onclick(); };
|
|
|
|
}
|
|
|
|
|
|
|
|
var discordVerified = false;
|
|
|
|
if (window.discordEnabled) {
|
|
|
|
window.discordModal = new Modal(document.getElementById("modal-discord"), window.discordRequired);
|
|
|
|
const discordButton = document.getElementById("link-discord") as HTMLSpanElement;
|
|
|
|
|
|
|
|
const discordConf: ServiceConfiguration = {
|
|
|
|
modal: window.discordModal as Modal,
|
|
|
|
pin: window.discordPIN,
|
|
|
|
inviteURL: window.discordInviteLink ? ("/invite/" + window.code + "/discord/invite") : "",
|
|
|
|
pinURL: "",
|
|
|
|
verifiedURL: "/invite/" + window.code + "/discord/verified/",
|
|
|
|
invalidCodeError: window.messages["errorInvalidCode"],
|
|
|
|
accountLinkedError: window.messages["errorAccountLinked"],
|
|
|
|
successError: window.messages["verified"],
|
|
|
|
successFunc: (modalClosed: boolean) => {
|
|
|
|
if (modalClosed) return;
|
|
|
|
discordVerified = true;
|
|
|
|
discordButton.classList.add("unfocused");
|
|
|
|
document.getElementById("contact-via").classList.remove("unfocused");
|
|
|
|
document.getElementById("contact-via-email").parentElement.classList.remove("unfocused");
|
|
|
|
const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
|
|
|
|
radio.parentElement.classList.remove("unfocused")
|
|
|
|
radio.checked = true;
|
|
|
|
validatorFunc();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const discord = new Discord(discordConf);
|
|
|
|
|
|
|
|
discordButton.onclick = () => { discord.onclick(); };
|
|
|
|
}
|
|
|
|
|
|
|
|
var matrixVerified = false;
|
|
|
|
var matrixPIN = "";
|
|
|
|
if (window.matrixEnabled) {
|
|
|
|
window.matrixModal = new Modal(document.getElementById("modal-matrix"), window.matrixRequired);
|
|
|
|
const matrixButton = document.getElementById("link-matrix") as HTMLSpanElement;
|
|
|
|
matrixButton.onclick = window.matrixModal.show;
|
|
|
|
const submitButton = document.getElementById("matrix-send") as HTMLSpanElement;
|
|
|
|
const input = document.getElementById("matrix-userid") as HTMLInputElement;
|
|
|
|
let userID = "";
|
|
|
|
submitButton.onclick = () => {
|
|
|
|
addLoader(submitButton);
|
|
|
|
if (userID == "") {
|
|
|
|
const send = {
|
|
|
|
user_id: input.value
|
|
|
|
};
|
|
|
|
_post("/invite/" + window.code + "/matrix/user", send, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
if (req.status == 400 && req.response["error"] == "errorAccountLinked") {
|
|
|
|
window.matrixModal.close();
|
|
|
|
window.notifications.customError("accountLinkedError", window.messages["errorAccountLinked"]);
|
|
|
|
}
|
|
|
|
removeLoader(submitButton);
|
|
|
|
userID = input.value;
|
|
|
|
if (req.status != 200) {
|
|
|
|
window.notifications.customError("errorUnknown", window.messages["errorUnknown"]);
|
|
|
|
window.matrixModal.close();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
submitButton.classList.add("~positive");
|
|
|
|
submitButton.classList.remove("~info");
|
|
|
|
setTimeout(() => {
|
|
|
|
submitButton.classList.add("~info");
|
|
|
|
submitButton.classList.remove("~positive");
|
|
|
|
}, 2000);
|
|
|
|
input.placeholder = "PIN";
|
|
|
|
input.value = "";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
_get("/invite/" + window.code + "/matrix/verified/" + userID + "/" + input.value, null, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
removeLoader(submitButton)
|
|
|
|
const valid = req.response["success"] as boolean;
|
|
|
|
if (valid) {
|
|
|
|
window.matrixModal.close();
|
|
|
|
window.notifications.customPositive("successVerified", "", window.messages["verified"]);
|
|
|
|
matrixVerified = true;
|
|
|
|
matrixPIN = input.value;
|
|
|
|
matrixButton.classList.add("unfocused");
|
|
|
|
document.getElementById("contact-via").classList.remove("unfocused");
|
|
|
|
document.getElementById("contact-via-email").parentElement.classList.remove("unfocused");
|
|
|
|
const radio = document.getElementById("contact-via-matrix") as HTMLInputElement;
|
|
|
|
radio.parentElement.classList.remove("unfocused");
|
|
|
|
radio.checked = true;
|
|
|
|
validatorFunc();
|
|
|
|
} else {
|
|
|
|
window.notifications.customError("errorInvalidPIN", window.messages["errorInvalidPIN"]);
|
|
|
|
submitButton.classList.add("~critical");
|
|
|
|
submitButton.classList.remove("~info");
|
|
|
|
setTimeout(() => {
|
|
|
|
submitButton.classList.add("~info");
|
|
|
|
submitButton.classList.remove("~critical");
|
|
|
|
}, 800);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (window.confirmation) {
|
|
|
|
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
|
|
|
|
}
|
|
|
|
declare var window: formWindow;
|
|
|
|
|
|
|
|
if (window.userExpiryEnabled) {
|
|
|
|
const messageEl = document.getElementById("user-expiry-message") as HTMLElement;
|
|
|
|
const calculateTime = () => {
|
|
|
|
let time = new Date()
|
|
|
|
time.setMonth(time.getMonth() + window.userExpiryMonths);
|
|
|
|
time.setDate(time.getDate() + window.userExpiryDays);
|
|
|
|
time.setHours(time.getHours() + window.userExpiryHours);
|
|
|
|
time.setMinutes(time.getMinutes() + window.userExpiryMinutes);
|
|
|
|
messageEl.textContent = window.userExpiryMessage.replace("{date}", toDateString(time));
|
|
|
|
setTimeout(calculateTime, 1000);
|
|
|
|
};
|
|
|
|
calculateTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
const form = document.getElementById("form-create") as HTMLFormElement;
|
|
|
|
const submitButton = form.querySelector("input[type=submit]") as HTMLInputElement;
|
|
|
|
const submitSpan = form.querySelector("span.submit") as HTMLSpanElement;
|
|
|
|
const submitText = submitSpan.textContent;
|
|
|
|
let usernameField = document.getElementById("create-username") as HTMLInputElement;
|
|
|
|
const emailField = document.getElementById("create-email") as HTMLInputElement;
|
|
|
|
if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameField = emailField; }
|
|
|
|
const passwordField = document.getElementById("create-password") as HTMLInputElement;
|
|
|
|
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
|
|
|
|
|
|
|
|
let captchaVerified = false;
|
|
|
|
let captchaID = "";
|
|
|
|
let captchaInput = document.getElementById("captcha-input") as HTMLInputElement;
|
|
|
|
const captchaCheckbox = document.getElementById("captcha-success") as HTMLSpanElement;
|
|
|
|
let prevCaptcha = "";
|
|
|
|
|
|
|
|
function baseValidator(oncomplete: (valid: boolean) => void): void {
|
|
|
|
let captchaChecked = false;
|
|
|
|
let captchaChange = false;
|
|
|
|
if (window.captcha && !window.reCAPTCHA) {
|
|
|
|
captchaChange = captchaInput.value != prevCaptcha;
|
|
|
|
if (captchaChange) {
|
|
|
|
prevCaptcha = captchaInput.value;
|
|
|
|
_post("/captcha/verify/" + window.code + "/" + captchaID + "/" + captchaInput.value, null, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
if (req.status == 204) {
|
|
|
|
captchaCheckbox.innerHTML = `<i class="ri-check-line"></i>`;
|
|
|
|
captchaCheckbox.classList.add("~positive");
|
|
|
|
captchaCheckbox.classList.remove("~critical");
|
|
|
|
captchaVerified = true;
|
|
|
|
captchaChecked = true;
|
|
|
|
} else {
|
|
|
|
captchaCheckbox.innerHTML = `<i class="ri-close-line"></i>`;
|
|
|
|
captchaCheckbox.classList.add("~critical");
|
|
|
|
captchaCheckbox.classList.remove("~positive");
|
|
|
|
captchaVerified = false;
|
|
|
|
captchaChecked = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (window.emailRequired) {
|
|
|
|
if (!emailField.value.includes("@")) {
|
|
|
|
oncomplete(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (window.discordEnabled && window.discordRequired && !discordVerified) {
|
|
|
|
oncomplete(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (window.telegramEnabled && window.telegramRequired && !telegramVerified) {
|
|
|
|
oncomplete(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (window.matrixEnabled && window.matrixRequired && !matrixVerified) {
|
|
|
|
oncomplete(false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (window.captcha && !window.reCAPTCHA) {
|
|
|
|
if (!captchaChange) {
|
|
|
|
oncomplete(captchaVerified);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
while (!captchaChecked) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
oncomplete(captchaVerified);
|
|
|
|
} else {
|
|
|
|
oncomplete(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface GreCAPTCHA {
|
|
|
|
render: (container: HTMLDivElement, parameters: {
|
|
|
|
sitekey?: string,
|
|
|
|
theme?: string,
|
|
|
|
size?: string,
|
|
|
|
tabindex?: number,
|
|
|
|
"callback"?: () => void,
|
|
|
|
"expired-callback"?: () => void,
|
|
|
|
"error-callback"?: () => void
|
|
|
|
}) => void;
|
|
|
|
getResponse: (opt_widget_id?: HTMLDivElement) => string;
|
|
|
|
}
|
|
|
|
|
|
|
|
declare var grecaptcha: GreCAPTCHA
|
|
|
|
|
|
|
|
let r = initValidator(passwordField, rePasswordField, submitButton, submitSpan, baseValidator);
|
|
|
|
var requirements = r[0];
|
|
|
|
var validatorFunc = r[1] as () => void;
|
|
|
|
|
|
|
|
if (window.emailRequired) {
|
|
|
|
emailField.addEventListener("keyup", validatorFunc)
|
|
|
|
}
|
|
|
|
|
|
|
|
interface respDTO {
|
|
|
|
response: boolean;
|
|
|
|
error: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface sendDTO {
|
|
|
|
code: string;
|
|
|
|
email: string;
|
|
|
|
username: string;
|
|
|
|
password: string;
|
|
|
|
telegram_pin?: string;
|
|
|
|
telegram_contact?: boolean;
|
|
|
|
discord_pin?: string;
|
|
|
|
discord_contact?: boolean;
|
|
|
|
matrix_pin?: string;
|
|
|
|
matrix_contact?: boolean;
|
|
|
|
captcha_id?: string;
|
|
|
|
captcha_text?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
const genCaptcha = () => {
|
|
|
|
_get("/captcha/gen/"+window.code, null, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
if (req.status == 200) {
|
|
|
|
captchaID = req.response["id"];
|
|
|
|
document.getElementById("captcha-img").innerHTML = `
|
|
|
|
<img class="w-100" src="${window.location.toString().substring(0, window.location.toString().lastIndexOf("/invite"))}/captcha/img/${window.code}/${captchaID}"></img>
|
|
|
|
`;
|
|
|
|
captchaInput.value = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
if (window.captcha && !window.reCAPTCHA) {
|
|
|
|
genCaptcha();
|
|
|
|
(document.getElementById("captcha-regen") as HTMLSpanElement).onclick = genCaptcha;
|
|
|
|
captchaInput.onkeyup = validatorFunc;
|
|
|
|
}
|
|
|
|
|
|
|
|
const create = (event: SubmitEvent) => {
|
|
|
|
event.preventDefault();
|
|
|
|
if (window.captcha && !window.reCAPTCHA && !captchaVerified) {
|
|
|
|
|
|
|
|
}
|
|
|
|
toggleLoader(submitSpan);
|
|
|
|
let send: sendDTO = {
|
|
|
|
code: window.code,
|
|
|
|
username: usernameField.value,
|
|
|
|
email: emailField.value,
|
|
|
|
password: passwordField.value
|
|
|
|
};
|
|
|
|
if (telegramVerified) {
|
|
|
|
send.telegram_pin = window.telegramPIN;
|
|
|
|
const radio = document.getElementById("contact-via-telegram") as HTMLInputElement;
|
|
|
|
if (radio.checked) {
|
|
|
|
send.telegram_contact = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (discordVerified) {
|
|
|
|
send.discord_pin = window.discordPIN;
|
|
|
|
const radio = document.getElementById("contact-via-discord") as HTMLInputElement;
|
|
|
|
if (radio.checked) {
|
|
|
|
send.discord_contact = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (matrixVerified) {
|
|
|
|
send.matrix_pin = matrixPIN;
|
|
|
|
const radio = document.getElementById("contact-via-matrix") as HTMLInputElement;
|
|
|
|
if (radio.checked) {
|
|
|
|
send.matrix_contact = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (window.captcha) {
|
|
|
|
if (window.reCAPTCHA) {
|
|
|
|
send.captcha_text = grecaptcha.getResponse();
|
|
|
|
} else {
|
|
|
|
send.captcha_id = captchaID;
|
|
|
|
send.captcha_text = captchaInput.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_post("/newUser", send, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
let vals = req.response as respDTO;
|
|
|
|
let valid = true;
|
|
|
|
for (let type in vals) {
|
|
|
|
if (requirements[type]) { requirements[type].valid = vals[type]; }
|
|
|
|
if (!vals[type]) { valid = false; }
|
|
|
|
}
|
|
|
|
if (req.status == 200 && valid) {
|
|
|
|
if (window.redirectToJellyfin == true) {
|
|
|
|
const url = ((document.getElementById("modal-success") as HTMLDivElement).querySelector("a.submit") as HTMLAnchorElement).href;
|
|
|
|
window.location.href = url;
|
|
|
|
} else {
|
|
|
|
window.successModal.show();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
submitSpan.classList.add("~critical");
|
|
|
|
submitSpan.classList.remove("~urge");
|
|
|
|
if (req.response["error"] as string) {
|
|
|
|
submitSpan.textContent = window.messages[req.response["error"]];
|
|
|
|
} else {
|
|
|
|
submitSpan.textContent = window.messages["errorPassword"];
|
|
|
|
}
|
|
|
|
setTimeout(() => {
|
|
|
|
submitSpan.classList.add("~urge");
|
|
|
|
submitSpan.classList.remove("~critical");
|
|
|
|
submitSpan.textContent = submitText;
|
|
|
|
}, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, true, (req: XMLHttpRequest) => {
|
|
|
|
if (req.readyState == 4) {
|
|
|
|
toggleLoader(submitSpan);
|
|
|
|
if (req.status == 401 || req.status == 400) {
|
|
|
|
if (req.response["error"] as string) {
|
|
|
|
if (req.response["error"] == "confirmEmail") {
|
|
|
|
window.confirmationModal.show();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (req.response["error"] in window.messages) {
|
|
|
|
submitSpan.textContent = window.messages[req.response["error"]];
|
|
|
|
} else {
|
|
|
|
submitSpan.textContent = req.response["error"];
|
|
|
|
}
|
|
|
|
setTimeout(() => { submitSpan.textContent = submitText; }, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
validatorFunc();
|
|
|
|
|
|
|
|
form.onsubmit = create;
|