You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jfa-go/ts/form.ts

400 lines
15 KiB

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 { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } 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;
userPageEnabled: boolean;
userPageAddress: 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["errorInvalidPIN"],
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 checkbox = document.getElementById("contact-via-telegram") as HTMLInputElement;
checkbox.parentElement.classList.remove("unfocused");
checkbox.checked = true;
validator.validate();
}
};
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["errorInvalidPIN"],
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 checkbox = document.getElementById("contact-via-discord") as HTMLInputElement;
checkbox.parentElement.classList.remove("unfocused")
checkbox.checked = true;
validator.validate();
}
};
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;
const matrixConf: MatrixConfiguration = {
modal: window.matrixModal as Modal,
sendMessageURL: "/invite/" + window.code + "/matrix/user",
verifiedURL: "/invite/" + window.code + "/matrix/verified/",
invalidCodeError: window.messages["errorInvalidPIN"],
accountLinkedError: window.messages["errorAccountLinked"],
unknownError: window.messages["errorUnknown"],
successError: window.messages["verified"],
successFunc: () => {
matrixVerified = true;
matrixPIN = matrix.pin;
matrixButton.classList.add("unfocused");
document.getElementById("contact-via").classList.remove("unfocused");
document.getElementById("contact-via-email").parentElement.classList.remove("unfocused");
const checkbox = document.getElementById("contact-via-matrix") as HTMLInputElement;
checkbox.parentElement.classList.remove("unfocused");
checkbox.checked = true;
validator.validate();
}
};
const matrix = new Matrix(matrixConf);
matrixButton.onclick = () => { matrix.show(); };
}
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 submitInput = 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 = "";
let baseValidator = (oncomplete: (valid: boolean) => void): void => {
if (window.captcha && !window.reCAPTCHA && (captchaInput.value != prevCaptcha)) {
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;
} else {
captchaCheckbox.innerHTML = `<i class="ri-close-line"></i>`;
captchaCheckbox.classList.add("~critical");
captchaCheckbox.classList.remove("~positive");
captchaVerified = false;
}
_baseValidator(oncomplete, captchaVerified);
}
});
} else {
_baseValidator(oncomplete, captchaVerified);
}
}
function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: boolean): void {
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 && !captchaValid) {
oncomplete(false);
return;
}
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 validatorConf: ValidatorConf = {
passwordField: passwordField,
rePasswordField: rePasswordField,
submitInput: submitInput,
submitButton: submitSpan,
validatorFunc: baseValidator
};
let validator = new Validator(validatorConf);
var requirements = validator.requirements;
if (window.emailRequired) {
emailField.addEventListener("keyup", validator.validate)
}
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 = validator.validate;
}
const create = (event: SubmitEvent) => {
event.preventDefault();
if (window.captcha && !window.reCAPTCHA && !captchaVerified) {
}
addLoader(submitSpan);
let send: sendDTO = {
code: window.code,
username: usernameField.value,
email: emailField.value,
password: passwordField.value
};
if (telegramVerified) {
send.telegram_pin = window.telegramPIN;
const checkbox = document.getElementById("contact-via-telegram") as HTMLInputElement;
if (checkbox.checked) {
send.telegram_contact = true;
}
}
if (discordVerified) {
send.discord_pin = window.discordPIN;
const checkbox = document.getElementById("contact-via-discord") as HTMLInputElement;
if (checkbox.checked) {
send.discord_contact = true;
}
}
if (matrixVerified) {
send.matrix_pin = matrixPIN;
const checkbox = document.getElementById("contact-via-matrix") as HTMLInputElement;
if (checkbox.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) return;
removeLoader(submitSpan);
let vals = req.response as ValidatorRespDTO;
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 {
if (window.userPageEnabled) {
const userPageNoticeArea = document.getElementById("modal-success-user-page-area");
const link = `<a href="${window.userPageAddress}" target="_blank">${userPageNoticeArea.getAttribute("my-account-term")}</a>`;
userPageNoticeArea.innerHTML = userPageNoticeArea.textContent.replace("{myAccount}", link);
}
window.successModal.show();
}
} else if (req.status != 401 && req.status != 400){
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) return;
removeLoader(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);
}
}
});
};
validator.validate();
form.onsubmit = create;
const invitedByAside = document.getElementById("invite-from-user");
if (typeof(invitedByAside) != "undefined" && invitedByAside != null) {
invitedByAside.textContent = invitedByAside.textContent.replace("{user}", invitedByAside.getAttribute("data-from"));
}