import { Subscriber } from "@supersoniks/concorde/mixins"; import { dp, HTML } from "@supersoniks/concorde/utils"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ConcordeWindow } from "@supersoniks/concorde/core/_types/types"; import type { DataProvider, Publisher, } from "@supersoniks/concorde/core/utils/PublisherProxy"; import { generateKey, encryptToBase64, } from "@supersoniks/concorde/core/utils/aesCrypto"; import { altchaStyles } from "./altchaStyles"; const PASS_PHRASE = "supersoniks_altcha"; const IV = "Si2\\]X8M4!n9DCLd"; let scriptAdded = false; declare const window: ConcordeWindow; const tagName = "sonic-captcha"; /** * Un bouton simple avec deux slots, un nommé préfix et un nomé suffix de manière à pouvoir mettre (par exemple) une icone avant ou après le contenu. * * L'objet et ses slot sont en display flex avec direction / alignement et justifications configurables * * Le bouton est comparable au badge car il possèdent tous les deux les propriétés *type* (primary...), *variant*(outline, ghost), size(xs...)... * * Le bouton possède cependant et notamment une propriété href contrairement à un badge */ @customElement(tagName) export class Captcha extends Subscriber(LitElement) { static styles = [ altchaStyles, css` :host { --altcha-border-width: var(--sc-border-width, 1px); --altcha-border-radius: var(--sc-rounded); --altcha-color-base: var(--sc-base); --altcha-color-border: var( --sc-input-border-color, var(--sc-base-content, #000) ); --altcha-color-text: var(--sc-base-content, #000); --altcha-color-active: var(--sc-base-content, #000); --altcha-color-border-focus: currentColor; --altcha-color-error-text: var( --sc-danger, var(--sc-base-content, #000) ); --altcha-color-footer-bg: var(--sc-base-100, #000); --altcha-max-width: 260px; } `, ]; @property() key = ""; @property() action: string | null = null; @property({ type: Number }) zIndex = 9999; formPublisher?: DataProvider<{ needsCaptchaValidation: boolean; captchaKey: string; captchaToken: string; }>; onCaptchaTokenChanged = (v: string) => { if (v == "request_token") { if (this.formPublisher) this.formPublisher.captchaToken.set(""); this.requestToken(); } }; connectedCallback() { if (!document.location.protocol.includes("https")) { return; } if (!scriptAdded) { const script = document.createElement("script"); script.type = "module"; this.setAttribute("async", ""); this.setAttribute("defer", ""); script.src = "https://cdn.jsdelivr.net/gh/altcha-org/altcha/dist/altcha.min.js"; scriptAdded = true; document.head.appendChild(script); } this.generateEncryptedKey(); super.connectedCallback(); this.formPublisher = dp<{ needsCaptchaValidation: boolean; captchaKey: string; captchaToken: string; }>( this.getAncestorAttributeValue("headersDataProvider") ?? this.getAncestorAttributeValue("formDataProvider"), ); if ( this.formPublisher && !(this.formPublisher.captchaToken as Publisher).get() ) { this.formPublisher.needsCaptchaValidation.set(true); (this.formPublisher.captchaToken as Publisher).onAssign( this.onCaptchaTokenChanged, ); } } disconnectedCallback(): void { if (this.formPublisher) { (this.formPublisher.captchaToken as Publisher).offAssign( this.onCaptchaTokenChanged, ); this.formPublisher.captchaToken.set(""); this.formPublisher.needsCaptchaValidation.set(false); } super.disconnectedCallback(); } requestToken() { if (!this.formPublisher) return; const form = this.shadowRoot.querySelector("form"); if (!form) return; const formData = new FormData(form); this.formPublisher.captchaKey.set(this.key); this.formPublisher.captchaToken.set( formData.get("altcha")?.toString() || "", ); } async generateEncryptedKey() { if (this.key) return; const key = await generateKey(); const iv = new TextEncoder().encode(IV); const encryptedBase64 = await encryptToBase64(PASS_PHRASE, key, iv); this.key = encryptedBase64; } maxNumber = 50000; chalengeUrl = "https://altcha.supersoniks.org/get-challenge"; protected render() { if (!this.key) { return nothing; } const isFR = HTML.getLanguage().match("^fr\\b"); const labels = isFR ? { aria: "Visitez altcha.org", error: "La vérification a échoué, réessayez plus tard.", expired: "La vérification a expiré, réessayez.", footer: "Protégé par Altcha", label: "Je ne suis pas un robot.", verified: "Vérifié", verifying: "Vérification en cours...", wait: "Vérification en cours… Veuillez patienter.", } : { aria: "Visit altcha.org", error: "Verification failed, try again later.", expired: "Verification expired, try again.", footer: "Protected by Altcha", label: "I'm not a robot.", verified: "Verified", verifying: "Verifying...", wait: "Verifying... Please wait.", }; return html`
`; } }