mirror of
https://github.com/altcha-org/altcha.git
synced 2026-01-25 04:16:41 +00:00
2.2.4
This commit is contained in:
3382
dist/altcha.i18n.js
vendored
3382
dist/altcha.i18n.js
vendored
File diff suppressed because one or more lines are too long
8
dist/altcha.i18n.umd.js
vendored
8
dist/altcha.i18n.umd.js
vendored
File diff suppressed because one or more lines are too long
3246
dist/altcha.js
vendored
3246
dist/altcha.js
vendored
File diff suppressed because one or more lines are too long
8
dist/altcha.umd.cjs
vendored
8
dist/altcha.umd.cjs
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
(function(){"use strict";const d=new TextEncoder;function p(e){return[...new Uint8Array(e)].map(t=>t.toString(16).padStart(2,"0")).join("")}async function b(e,t,r){if(typeof crypto>"u"||!("subtle"in crypto)||!("digest"in crypto.subtle))throw new Error("Web Crypto is not available. Secure context is required (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).");return p(await crypto.subtle.digest(r.toUpperCase(),d.encode(e+t)))}function w(e,t,r="SHA-256",n=1e6,s=0){const o=new AbortController,a=Date.now();return{promise:(async()=>{for(let c=s;c<=n;c+=1){if(o.signal.aborted)return null;if(await b(t,c,r)===e)return{number:c,took:Date.now()-a}}return null})(),controller:o}}function h(e){const t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r}function g(e,t=12){const r=new Uint8Array(t);for(let n=0;n<t;n++)r[n]=e%256,e=Math.floor(e/256);return r}async function m(e,t="",r=1e6,n=0){const s="AES-GCM",o=new AbortController,a=Date.now(),l=async()=>{for(let u=n;u<=r;u+=1){if(o.signal.aborted||!c||!y)return null;try{const f=await crypto.subtle.decrypt({name:s,iv:g(u)},c,y);if(f)return{clearText:new TextDecoder().decode(f),took:Date.now()-a}}catch{}}return null};let c=null,y=null;try{y=h(e);const u=await crypto.subtle.digest("SHA-256",d.encode(t));c=await crypto.subtle.importKey("raw",u,s,!1,["decrypt"])}catch{return{promise:Promise.reject(),controller:o}}return{promise:l(),controller:o}}let i;onmessage=async e=>{const{type:t,payload:r,start:n,max:s}=e.data;let o=null;if(t==="abort")i==null||i.abort(),i=void 0;else if(t==="work"){if("obfuscated"in r){const{key:a,obfuscated:l}=r||{};o=await m(l,a,s,n)}else{const{algorithm:a,challenge:l,salt:c}=r||{};o=w(l,c,a,s,n)}i=o.controller,o.promise.then(a=>{self.postMessage(a&&{...a,worker:!0})})}}})();
|
(function(){"use strict";const d=new TextEncoder;function p(e){return[...new Uint8Array(e)].map(t=>t.toString(16).padStart(2,"0")).join("")}async function b(e,t,r){if(typeof crypto>"u"||!("subtle"in crypto)||!("digest"in crypto.subtle))throw new Error("Web Crypto is not available. Secure context is required (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).");return p(await crypto.subtle.digest(r.toUpperCase(),d.encode(e+t)))}function w(e,t,r="SHA-256",n=1e6,l=0){const o=new AbortController,a=Date.now();return{promise:(async()=>{for(let c=l;c<=n;c+=1){if(o.signal.aborted)return null;if(await b(t,c,r)===e)return{number:c,took:Date.now()-a}}return null})(),controller:o}}function h(e){const t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r}function g(e,t=12){const r=new Uint8Array(t);for(let n=0;n<t;n++)r[n]=e%256,e=Math.floor(e/256);return r}async function m(e,t="",r=1e6,n=0){const l="AES-GCM",o=new AbortController,a=Date.now(),s=async()=>{for(let i=n;i<=r;i+=1){if(o.signal.aborted||!c||!u)return null;try{const f=await crypto.subtle.decrypt({name:l,iv:g(i)},c,u);if(f)return{clearText:new TextDecoder().decode(f),took:Date.now()-a}}catch{}}return null};let c=null,u=null;try{u=h(e);const i=await crypto.subtle.digest("SHA-256",d.encode(t));c=await crypto.subtle.importKey("raw",i,l,!1,["decrypt"])}catch{return{promise:Promise.reject(),controller:o}}return{promise:s(),controller:o}}let y;onmessage=async e=>{const{type:t,payload:r,start:n,max:l}=e.data;let o=null;if(t==="abort")y?.abort(),y=void 0;else if(t==="work"){if("obfuscated"in r){const{key:a,obfuscated:s}=r||{};o=await m(s,a,l,n)}else{const{algorithm:a,challenge:s,salt:c}=r||{};o=w(s,c,a,l,n)}y=o.controller,o.promise.then(a=>{self.postMessage(a&&{...a,worker:!0})})}}})();
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
var y = Object.defineProperty;
|
|
||||||
var p = (s) => {
|
|
||||||
throw TypeError(s);
|
|
||||||
};
|
|
||||||
var C = (s, t, e) => t in s ? y(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e;
|
|
||||||
var l = (s, t, e) => C(s, typeof t != "symbol" ? t + "" : t, e), F = (s, t, e) => t.has(s) || p("Cannot " + e);
|
|
||||||
var i = (s, t, e) => (F(s, t, "read from private field"), e ? e.call(s) : t.get(s)), r = (s, t, e) => t.has(s) ? p("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(s) : t.set(s, e), T = (s, t, e, n) => (F(s, t, "write to private field"), n ? n.call(s, e) : t.set(s, e), e), E = (s, t, e) => (F(s, t, "access private method"), e);
|
|
||||||
new TextEncoder();
|
new TextEncoder();
|
||||||
function L() {
|
function r() {
|
||||||
try {
|
try {
|
||||||
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class v {
|
class n {
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance of the Plugin.
|
* Constructs a new instance of the Plugin.
|
||||||
*
|
*
|
||||||
@@ -21,6 +14,10 @@ class v {
|
|||||||
constructor(t) {
|
constructor(t) {
|
||||||
this.context = t;
|
this.context = t;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* A distinct name of the plugin. Every plugin must have it's own name.
|
||||||
|
*/
|
||||||
|
static pluginName;
|
||||||
/**
|
/**
|
||||||
* Registers a plugin class in the global `altchaPlugins` array.
|
* Registers a plugin class in the global `altchaPlugins` array.
|
||||||
* Ensures the plugin is added only once.
|
* Ensures the plugin is added only once.
|
||||||
@@ -53,64 +50,54 @@ class v {
|
|||||||
onStateChange(t) {
|
onStateChange(t) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
class a extends n {
|
||||||
* A distinct name of the plugin. Every plugin must have it's own name.
|
static pluginName = "analytics";
|
||||||
*/
|
// HTML form element associated with the plugin
|
||||||
l(v, "pluginName");
|
#t;
|
||||||
var o, a, m, f, w;
|
// Session instance for tracking analytics data
|
||||||
class A extends v {
|
#e;
|
||||||
|
// Bound method for form submission handling
|
||||||
|
#i = this.#s.bind(this);
|
||||||
/**
|
/**
|
||||||
* Creates an instance of PluginAnalytics.
|
* Creates an instance of PluginAnalytics.
|
||||||
*
|
*
|
||||||
* @param {PluginContext} context - The context object containing plugin configurations.
|
* @param {PluginContext} context - The context object containing plugin configurations.
|
||||||
*/
|
*/
|
||||||
constructor(e) {
|
constructor(t) {
|
||||||
super(e);
|
if (super(t), this.#t = this.context.el.closest("form"), this.#t) {
|
||||||
r(this, f);
|
let e = this.#t.getAttribute("data-beacon-url");
|
||||||
// HTML form element associated with the plugin
|
const i = this.#t.getAttribute("action");
|
||||||
r(this, o);
|
!e && i && (e = i + "/beacon"), this.#t.addEventListener("submit", this.#i), this.#e = new h(this.#t, e);
|
||||||
// Session instance for tracking analytics data
|
|
||||||
r(this, a);
|
|
||||||
// Bound method for form submission handling
|
|
||||||
r(this, m, E(this, f, w).bind(this));
|
|
||||||
if (T(this, o, this.context.el.closest("form")), i(this, o)) {
|
|
||||||
let n = i(this, o).getAttribute("data-beacon-url");
|
|
||||||
const h = i(this, o).getAttribute("action");
|
|
||||||
!n && h && (n = h + "/beacon"), i(this, o).addEventListener("submit", i(this, m)), T(this, a, new N(i(this, o), n));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Destroys the plugin instance, removing event listeners and cleaning up the session.
|
* Destroys the plugin instance, removing event listeners and cleaning up the session.
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
var e, n;
|
this.#t?.removeEventListener("submit", this.#i), this.#e?.destroy();
|
||||||
(e = i(this, o)) == null || e.removeEventListener("submit", i(this, m)), (n = i(this, a)) == null || n.destroy();
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Tracks errors by forwarding them to the session instance.
|
* Tracks errors by forwarding them to the session instance.
|
||||||
*
|
*
|
||||||
* @param {string | null} err - The error message, or `null` if no error exists.
|
* @param {string | null} err - The error message, or `null` if no error exists.
|
||||||
*/
|
*/
|
||||||
onErrorChange(e) {
|
onErrorChange(t) {
|
||||||
var n;
|
this.#e?.trackError(t);
|
||||||
(n = i(this, a)) == null || n.trackError(e);
|
}
|
||||||
|
/**
|
||||||
|
* Handles form submission events, appending session data to the form if applicable.
|
||||||
|
*/
|
||||||
|
#s() {
|
||||||
|
if (this.#e && !this.#e.submitTime) {
|
||||||
|
this.#e.end();
|
||||||
|
const t = this.#e.dataAsBase64();
|
||||||
|
this.context.dispatch("session", t);
|
||||||
|
const e = document.createElement("input");
|
||||||
|
e.type = "hidden", e.name = "__session", e.value = t, this.#t?.appendChild(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
o = new WeakMap(), a = new WeakMap(), m = new WeakMap(), f = new WeakSet(), /**
|
class h {
|
||||||
* Handles form submission events, appending session data to the form if applicable.
|
|
||||||
*/
|
|
||||||
w = function() {
|
|
||||||
var e;
|
|
||||||
if (i(this, a) && !i(this, a).submitTime) {
|
|
||||||
i(this, a).end();
|
|
||||||
const n = i(this, a).dataAsBase64();
|
|
||||||
this.context.dispatch("session", n);
|
|
||||||
const h = document.createElement("input");
|
|
||||||
h.type = "hidden", h.name = "__session", h.value = n, (e = i(this, o)) == null || e.appendChild(h);
|
|
||||||
}
|
|
||||||
}, l(A, "pluginName", "analytics");
|
|
||||||
var c, u, d, g, b;
|
|
||||||
class N {
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Session instance.
|
* Creates a new Session instance.
|
||||||
*
|
*
|
||||||
@@ -118,43 +105,43 @@ class N {
|
|||||||
* @param {string | null} [beaconUrl=null] - The URL to send analytics data to.
|
* @param {string | null} [beaconUrl=null] - The URL to send analytics data to.
|
||||||
*/
|
*/
|
||||||
constructor(t, e = null) {
|
constructor(t, e = null) {
|
||||||
// Error message associated with the session
|
this.elForm = t, this.beaconUrl = e, window.addEventListener("unload", this.#n), this.elForm.addEventListener("change", this.#i), this.elForm.addEventListener("focusin", this.#s);
|
||||||
l(this, "error", null);
|
|
||||||
// Timestamp when the form was loaded
|
|
||||||
l(this, "loadTime", Date.now());
|
|
||||||
// Timestamp when the form was submitted
|
|
||||||
l(this, "submitTime", null);
|
|
||||||
// Timestamp when the user started interacting with the form
|
|
||||||
l(this, "startTime", null);
|
|
||||||
// Minimum time in milliseconds required to consider the form "viewed"
|
|
||||||
l(this, "viewTimeThresholdMs", 1500);
|
|
||||||
// Tracks the number of changes made to each form field
|
|
||||||
r(this, c, {});
|
|
||||||
// Name of the last input field focused by the user
|
|
||||||
r(this, u, null);
|
|
||||||
// Bound method for handling form change events
|
|
||||||
r(this, d, this.onFormChange.bind(this));
|
|
||||||
// Bound method for handling form focus events
|
|
||||||
r(this, g, this.onFormFocus.bind(this));
|
|
||||||
// Bound method for handling the unload event
|
|
||||||
r(this, b, this.onUnload.bind(this));
|
|
||||||
this.elForm = t, this.beaconUrl = e, window.addEventListener("unload", i(this, b)), this.elForm.addEventListener("change", i(this, d)), this.elForm.addEventListener("focusin", i(this, g));
|
|
||||||
}
|
}
|
||||||
|
// Error message associated with the session
|
||||||
|
error = null;
|
||||||
|
// Timestamp when the form was loaded
|
||||||
|
loadTime = Date.now();
|
||||||
|
// Timestamp when the form was submitted
|
||||||
|
submitTime = null;
|
||||||
|
// Timestamp when the user started interacting with the form
|
||||||
|
startTime = null;
|
||||||
|
// Minimum time in milliseconds required to consider the form "viewed"
|
||||||
|
viewTimeThresholdMs = 1500;
|
||||||
|
// Tracks the number of changes made to each form field
|
||||||
|
#t = {};
|
||||||
|
// Name of the last input field focused by the user
|
||||||
|
#e = null;
|
||||||
|
// Bound method for handling form change events
|
||||||
|
#i = this.onFormChange.bind(this);
|
||||||
|
// Bound method for handling form focus events
|
||||||
|
#s = this.onFormFocus.bind(this);
|
||||||
|
// Bound method for handling the unload event
|
||||||
|
#n = this.onUnload.bind(this);
|
||||||
/**
|
/**
|
||||||
* Collects and returns analytics data about the form interaction.
|
* Collects and returns analytics data about the form interaction.
|
||||||
*
|
*
|
||||||
* @returns {Record<string, unknown>} - An object containing analytics data.
|
* @returns {Record<string, unknown>} - An object containing analytics data.
|
||||||
*/
|
*/
|
||||||
data() {
|
data() {
|
||||||
const t = Object.entries(i(this, c));
|
const t = Object.entries(this.#t);
|
||||||
return {
|
return {
|
||||||
correction: t.length && t.filter(([e, n]) => n > 1).length / t.length || 0,
|
correction: t.length && t.filter(([e, i]) => i > 1).length / t.length || 0,
|
||||||
dropoff: !this.submitTime && !this.error && i(this, u) ? i(this, u) : null,
|
dropoff: !this.submitTime && !this.error && this.#e ? this.#e : null,
|
||||||
error: this.error,
|
error: this.error,
|
||||||
mobile: this.isMobile(),
|
mobile: this.isMobile(),
|
||||||
start: this.startTime,
|
start: this.startTime,
|
||||||
submit: this.submitTime,
|
submit: this.submitTime,
|
||||||
tz: L()
|
tz: r()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -174,7 +161,7 @@ class N {
|
|||||||
* Destroys the session, removing event listeners.
|
* Destroys the session, removing event listeners.
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
window.removeEventListener("unload", i(this, b)), this.elForm.removeEventListener("change", i(this, d)), this.elForm.removeEventListener("focusin", i(this, g));
|
window.removeEventListener("unload", this.#n), this.elForm.removeEventListener("change", this.#i), this.elForm.removeEventListener("focusin", this.#s);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Marks the session as ended by recording the submission time.
|
* Marks the session as ended by recording the submission time.
|
||||||
@@ -190,8 +177,8 @@ class N {
|
|||||||
* @returns {string} - The field name, truncated to `maxLength` if necessary.
|
* @returns {string} - The field name, truncated to `maxLength` if necessary.
|
||||||
*/
|
*/
|
||||||
getFieldName(t, e = 40) {
|
getFieldName(t, e = 40) {
|
||||||
const n = t.getAttribute("data-group-label"), h = t.getAttribute("name") || t.getAttribute("aria-label");
|
const i = t.getAttribute("data-group-label"), o = t.getAttribute("name") || t.getAttribute("aria-label");
|
||||||
return ((n ? n + ": " : "") + h).slice(0, e);
|
return ((i ? i + ": " : "") + o).slice(0, e);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Determines if the current device is a mobile device.
|
* Determines if the current device is a mobile device.
|
||||||
@@ -237,8 +224,8 @@ class N {
|
|||||||
onFormFocus(t) {
|
onFormFocus(t) {
|
||||||
const e = t.target;
|
const e = t.target;
|
||||||
if (this.startTime || this.start(), e && this.isInput(e)) {
|
if (this.startTime || this.start(), e && this.isInput(e)) {
|
||||||
const n = this.getFieldName(e);
|
const i = this.getFieldName(e);
|
||||||
n && T(this, u, n);
|
i && (this.#e = i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -280,11 +267,10 @@ class N {
|
|||||||
* @param {string} name - The name of the form field.
|
* @param {string} name - The name of the form field.
|
||||||
*/
|
*/
|
||||||
trackFieldChange(t) {
|
trackFieldChange(t) {
|
||||||
i(this, c)[t] = (i(this, c)[t] || 0) + 1;
|
this.#t[t] = (this.#t[t] || 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c = new WeakMap(), u = new WeakMap(), d = new WeakMap(), g = new WeakMap(), b = new WeakMap();
|
n.register(a);
|
||||||
v.register(A);
|
|
||||||
export {
|
export {
|
||||||
A as PluginAnalytics
|
a as PluginAnalytics
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
(function(e,i){typeof exports=="object"&&typeof module<"u"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(e=typeof globalThis<"u"?globalThis:e||self,i(e["[name]"]={}))})(this,function(e){"use strict";var N=Object.defineProperty;var A=e=>{throw TypeError(e)};var S=(e,i,o)=>i in e?N(e,i,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[i]=o;var c=(e,i,o)=>S(e,typeof i!="symbol"?i+"":i,o),y=(e,i,o)=>i.has(e)||A("Cannot "+o);var s=(e,i,o)=>(y(e,i,"read from private field"),o?o.call(e):i.get(e)),l=(e,i,o)=>i.has(e)?A("Cannot add the same private member more than once"):i instanceof WeakSet?i.add(e):i.set(e,o),v=(e,i,o,d)=>(y(e,i,"write to private field"),d?d.call(e,o):i.set(e,o),o),w=(e,i,o)=>(y(e,i,"access private method"),o);var a,h,b,F,C,m,g,f,T,p;new TextEncoder;function i(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{}}class o{constructor(t){this.context=t}static register(t){typeof globalThis.altchaPlugins!="object"&&(globalThis.altchaPlugins=[]),globalThis.altchaPlugins.includes(t)||globalThis.altchaPlugins.push(t)}destroy(){}onErrorChange(t){}onStateChange(t){}}c(o,"pluginName");class d extends o{constructor(n){super(n);l(this,F);l(this,a);l(this,h);l(this,b,w(this,F,C).bind(this));if(v(this,a,this.context.el.closest("form")),s(this,a)){let r=s(this,a).getAttribute("data-beacon-url");const u=s(this,a).getAttribute("action");!r&&u&&(r=u+"/beacon"),s(this,a).addEventListener("submit",s(this,b)),v(this,h,new L(s(this,a),r))}}destroy(){var n,r;(n=s(this,a))==null||n.removeEventListener("submit",s(this,b)),(r=s(this,h))==null||r.destroy()}onErrorChange(n){var r;(r=s(this,h))==null||r.trackError(n)}}a=new WeakMap,h=new WeakMap,b=new WeakMap,F=new WeakSet,C=function(){var n;if(s(this,h)&&!s(this,h).submitTime){s(this,h).end();const r=s(this,h).dataAsBase64();this.context.dispatch("session",r);const u=document.createElement("input");u.type="hidden",u.name="__session",u.value=r,(n=s(this,a))==null||n.appendChild(u)}},c(d,"pluginName","analytics");class L{constructor(t,n=null){c(this,"error",null);c(this,"loadTime",Date.now());c(this,"submitTime",null);c(this,"startTime",null);c(this,"viewTimeThresholdMs",1500);l(this,m,{});l(this,g,null);l(this,f,this.onFormChange.bind(this));l(this,T,this.onFormFocus.bind(this));l(this,p,this.onUnload.bind(this));this.elForm=t,this.beaconUrl=n,window.addEventListener("unload",s(this,p)),this.elForm.addEventListener("change",s(this,f)),this.elForm.addEventListener("focusin",s(this,T))}data(){const t=Object.entries(s(this,m));return{correction:t.length&&t.filter(([n,r])=>r>1).length/t.length||0,dropoff:!this.submitTime&&!this.error&&s(this,g)?s(this,g):null,error:this.error,mobile:this.isMobile(),start:this.startTime,submit:this.submitTime,tz:i()}}dataAsBase64(){try{return btoa(JSON.stringify(this.data()))}catch(t){console.error("failed to encode ALTCHA session data to base64",t)}return""}destroy(){window.removeEventListener("unload",s(this,p)),this.elForm.removeEventListener("change",s(this,f)),this.elForm.removeEventListener("focusin",s(this,T))}end(){this.submitTime||(this.submitTime=Date.now())}getFieldName(t,n=40){const r=t.getAttribute("data-group-label"),u=t.getAttribute("name")||t.getAttribute("aria-label");return((r?r+": ":"")+u).slice(0,n)}isMobile(){const t="userAgentData"in navigator&&navigator.userAgentData?navigator.userAgentData:{};return"mobile"in t?t.mobile===!0:/Mobi/i.test(window.navigator.userAgent)}isInput(t){return["INPUT","SELECT","TEXTAREA"].includes(t.tagName)}onFormFieldChange(t){const n=this.getFieldName(t);n&&this.trackFieldChange(n)}onFormChange(t){const n=t.target;n&&this.isInput(n)&&this.onFormFieldChange(n)}onFormFocus(t){const n=t.target;if(this.startTime||this.start(),n&&this.isInput(n)){const r=this.getFieldName(n);r&&v(this,g,r)}}onUnload(){this.loadTime<=Date.now()-this.viewTimeThresholdMs&&!this.submitTime&&this.sendBeacon()}async sendBeacon(){if(this.beaconUrl&&"sendBeacon"in navigator)try{navigator.sendBeacon(new URL(this.beaconUrl,location.origin),JSON.stringify(this.data()))}catch{}}start(){this.startTime=Date.now()}trackError(t){this.error=t===null?null:String(t)}trackFieldChange(t){s(this,m)[t]=(s(this,m)[t]||0)+1}}m=new WeakMap,g=new WeakMap,f=new WeakMap,T=new WeakMap,p=new WeakMap,o.register(d),e.PluginAnalytics=d,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
(function(s,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(s=typeof globalThis<"u"?globalThis:s||self,n(s["[name]"]={}))})(this,function(s){"use strict";new TextEncoder;function n(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{}}class o{constructor(t){this.context=t}static pluginName;static register(t){typeof globalThis.altchaPlugins!="object"&&(globalThis.altchaPlugins=[]),globalThis.altchaPlugins.includes(t)||globalThis.altchaPlugins.push(t)}destroy(){}onErrorChange(t){}onStateChange(t){}}class r extends o{static pluginName="analytics";#t;#e;#i=this.#s.bind(this);constructor(t){if(super(t),this.#t=this.context.el.closest("form"),this.#t){let e=this.#t.getAttribute("data-beacon-url");const i=this.#t.getAttribute("action");!e&&i&&(e=i+"/beacon"),this.#t.addEventListener("submit",this.#i),this.#e=new h(this.#t,e)}}destroy(){this.#t?.removeEventListener("submit",this.#i),this.#e?.destroy()}onErrorChange(t){this.#e?.trackError(t)}#s(){if(this.#e&&!this.#e.submitTime){this.#e.end();const t=this.#e.dataAsBase64();this.context.dispatch("session",t);const e=document.createElement("input");e.type="hidden",e.name="__session",e.value=t,this.#t?.appendChild(e)}}}class h{constructor(t,e=null){this.elForm=t,this.beaconUrl=e,window.addEventListener("unload",this.#n),this.elForm.addEventListener("change",this.#i),this.elForm.addEventListener("focusin",this.#s)}error=null;loadTime=Date.now();submitTime=null;startTime=null;viewTimeThresholdMs=1500;#t={};#e=null;#i=this.onFormChange.bind(this);#s=this.onFormFocus.bind(this);#n=this.onUnload.bind(this);data(){const t=Object.entries(this.#t);return{correction:t.length&&t.filter(([e,i])=>i>1).length/t.length||0,dropoff:!this.submitTime&&!this.error&&this.#e?this.#e:null,error:this.error,mobile:this.isMobile(),start:this.startTime,submit:this.submitTime,tz:n()}}dataAsBase64(){try{return btoa(JSON.stringify(this.data()))}catch(t){console.error("failed to encode ALTCHA session data to base64",t)}return""}destroy(){window.removeEventListener("unload",this.#n),this.elForm.removeEventListener("change",this.#i),this.elForm.removeEventListener("focusin",this.#s)}end(){this.submitTime||(this.submitTime=Date.now())}getFieldName(t,e=40){const i=t.getAttribute("data-group-label"),l=t.getAttribute("name")||t.getAttribute("aria-label");return((i?i+": ":"")+l).slice(0,e)}isMobile(){const t="userAgentData"in navigator&&navigator.userAgentData?navigator.userAgentData:{};return"mobile"in t?t.mobile===!0:/Mobi/i.test(window.navigator.userAgent)}isInput(t){return["INPUT","SELECT","TEXTAREA"].includes(t.tagName)}onFormFieldChange(t){const e=this.getFieldName(t);e&&this.trackFieldChange(e)}onFormChange(t){const e=t.target;e&&this.isInput(e)&&this.onFormFieldChange(e)}onFormFocus(t){const e=t.target;if(this.startTime||this.start(),e&&this.isInput(e)){const i=this.getFieldName(e);i&&(this.#e=i)}}onUnload(){this.loadTime<=Date.now()-this.viewTimeThresholdMs&&!this.submitTime&&this.sendBeacon()}async sendBeacon(){if(this.beaconUrl&&"sendBeacon"in navigator)try{navigator.sendBeacon(new URL(this.beaconUrl,location.origin),JSON.stringify(this.data()))}catch{}}start(){this.startTime=Date.now()}trackError(t){this.error=t===null?null:String(t)}trackFieldChange(t){this.#t[t]=(this.#t[t]||0)+1}}o.register(r),s.PluginAnalytics=r,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
|
||||||
|
|||||||
@@ -1,28 +1,24 @@
|
|||||||
var N = Object.defineProperty;
|
class h {
|
||||||
var B = (t) => {
|
|
||||||
throw TypeError(t);
|
|
||||||
};
|
|
||||||
var O = (t, e, i) => e in t ? N(t, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : t[e] = i;
|
|
||||||
var f = (t, e, i) => O(t, typeof e != "symbol" ? e + "" : e, i), b = (t, e, i) => e.has(t) || B("Cannot " + i);
|
|
||||||
var p = (t, e, i) => (b(t, e, "read from private field"), i ? i.call(t) : e.get(t)), g = (t, e, i) => e.has(t) ? B("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(t) : e.set(t, i);
|
|
||||||
var E = (t, e, i) => (b(t, e, "access private method"), i);
|
|
||||||
class y {
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance of the Plugin.
|
* Constructs a new instance of the Plugin.
|
||||||
*
|
*
|
||||||
* @param {PluginContext} context - The context provided to the plugin, containing necessary configurations and dependencies.
|
* @param {PluginContext} context - The context provided to the plugin, containing necessary configurations and dependencies.
|
||||||
*/
|
*/
|
||||||
constructor(e) {
|
constructor(t) {
|
||||||
this.context = e;
|
this.context = t;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* A distinct name of the plugin. Every plugin must have it's own name.
|
||||||
|
*/
|
||||||
|
static pluginName;
|
||||||
/**
|
/**
|
||||||
* Registers a plugin class in the global `altchaPlugins` array.
|
* Registers a plugin class in the global `altchaPlugins` array.
|
||||||
* Ensures the plugin is added only once.
|
* Ensures the plugin is added only once.
|
||||||
*
|
*
|
||||||
* @param {new(context: PluginContext) => Plugin} cls - The plugin class to register.
|
* @param {new(context: PluginContext) => Plugin} cls - The plugin class to register.
|
||||||
*/
|
*/
|
||||||
static register(e) {
|
static register(t) {
|
||||||
typeof globalThis.altchaPlugins != "object" && (globalThis.altchaPlugins = []), globalThis.altchaPlugins.includes(e) || globalThis.altchaPlugins.push(e);
|
typeof globalThis.altchaPlugins != "object" && (globalThis.altchaPlugins = []), globalThis.altchaPlugins.includes(t) || globalThis.altchaPlugins.push(t);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Clean up resources when the plugin is destroyed.
|
* Clean up resources when the plugin is destroyed.
|
||||||
@@ -36,7 +32,7 @@ class y {
|
|||||||
*
|
*
|
||||||
* @param {string | null} err - The error message or `null` if there's no error.
|
* @param {string | null} err - The error message or `null` if there's no error.
|
||||||
*/
|
*/
|
||||||
onErrorChange(e) {
|
onErrorChange(t) {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Callback triggered when the plugin state changes.
|
* Callback triggered when the plugin state changes.
|
||||||
@@ -44,95 +40,90 @@ class y {
|
|||||||
*
|
*
|
||||||
* @param {State} state - The new state of the plugin.
|
* @param {State} state - The new state of the plugin.
|
||||||
*/
|
*/
|
||||||
onStateChange(e) {
|
onStateChange(t) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
var s = /* @__PURE__ */ ((e) => (e.CODE = "code", e.ERROR = "error", e.VERIFIED = "verified", e.VERIFYING = "verifying", e.UNVERIFIED = "unverified", e.EXPIRED = "expired", e))(s || {});
|
||||||
* A distinct name of the plugin. Every plugin must have it's own name.
|
class R extends h {
|
||||||
*/
|
static pluginName = "obfuscation";
|
||||||
f(y, "pluginName");
|
// The button element associated with revealing the obfuscated data
|
||||||
var l = /* @__PURE__ */ ((t) => (t.CODE = "code", t.ERROR = "error", t.VERIFIED = "verified", t.VERIFYING = "verifying", t.UNVERIFIED = "unverified", t.EXPIRED = "expired", t))(l || {}), r, a, I, x;
|
elButton;
|
||||||
class v extends y {
|
// Bound method for handling button click events
|
||||||
|
#t = this.#e.bind(this);
|
||||||
/**
|
/**
|
||||||
* Creates an instance of PluginObfuscation.
|
* Creates an instance of PluginObfuscation.
|
||||||
*
|
*
|
||||||
* @param {PluginContext} context - The context object containing plugin configurations.
|
* @param {PluginContext} context - The context object containing plugin configurations.
|
||||||
*/
|
*/
|
||||||
constructor(i) {
|
constructor(t) {
|
||||||
var n, o;
|
super(t);
|
||||||
super(i);
|
const i = t.el;
|
||||||
g(this, a);
|
this.elButton = i.parentElement?.querySelector("[data-clarify-button]") || i.parentElement?.querySelector("button, a"), this.elButton && this.elButton.addEventListener("click", this.#t);
|
||||||
// The button element associated with revealing the obfuscated data
|
|
||||||
f(this, "elButton");
|
|
||||||
// Bound method for handling button click events
|
|
||||||
g(this, r, E(this, a, I).bind(this));
|
|
||||||
const s = i.el;
|
|
||||||
this.elButton = ((n = s.parentElement) == null ? void 0 : n.querySelector("[data-clarify-button]")) || ((o = s.parentElement) == null ? void 0 : o.querySelector("button, a")), this.elButton && this.elButton.addEventListener("click", p(this, r));
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Destroys the plugin instance, removing event listeners.
|
* Destroys the plugin instance, removing event listeners.
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
this.elButton && this.elButton.removeEventListener("click", p(this, r));
|
this.elButton && this.elButton.removeEventListener("click", this.#t);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Handles the clarification process by decrypting the obfuscated data and rendering the clear text.
|
* Handles the clarification process by decrypting the obfuscated data and rendering the clear text.
|
||||||
*/
|
*/
|
||||||
async clarify() {
|
async clarify() {
|
||||||
const {
|
const {
|
||||||
el: i,
|
el: t,
|
||||||
getConfiguration: s,
|
getConfiguration: i,
|
||||||
getFloatingAnchor: n,
|
getFloatingAnchor: n,
|
||||||
setFloatingAnchor: o,
|
setFloatingAnchor: r,
|
||||||
reset: d,
|
reset: f,
|
||||||
solve: C,
|
solve: d,
|
||||||
setState: m
|
setState: c
|
||||||
} = this.context, { delay: P, floating: F, maxnumber: T, obfuscated: R } = s();
|
} = this.context, { delay: g, floating: m, maxnumber: p, obfuscated: u } = i();
|
||||||
if (this.elButton && !n() && o(this.elButton), !R) {
|
if (this.elButton && !n() && r(this.elButton), !u) {
|
||||||
m(l.ERROR);
|
c(s.ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
d(l.VERIFYING), await new Promise((h) => setTimeout(h, P || 0));
|
f(s.VERIFYING), await new Promise((l) => setTimeout(l, g || 0));
|
||||||
const [k, D] = R.split("?");
|
const [E, y] = u.split("?");
|
||||||
let c = new URLSearchParams(D || "").get("key") || void 0;
|
let o = new URLSearchParams(y || "").get("key") || void 0;
|
||||||
if (c) {
|
if (o) {
|
||||||
const h = c.match(/^\(prompt:?(.*)\)$/);
|
const l = o.match(/^\(prompt:?(.*)\)$/);
|
||||||
h && (c = prompt(h[1] || "Enter Key:") || void 0);
|
l && (o = prompt(l[1] || "Enter Key:") || void 0);
|
||||||
}
|
}
|
||||||
const { solution: u } = await C({
|
const { solution: a } = await d({
|
||||||
obfuscated: k,
|
obfuscated: E,
|
||||||
key: c,
|
key: o,
|
||||||
maxnumber: T
|
maxnumber: p
|
||||||
});
|
});
|
||||||
u && "clearText" in u ? (E(this, a, x).call(this, u.clearText), m(l.VERIFIED), this.context.dispatch("cleartext", u.clearText), F && i && (i.style.display = "none")) : m(l.ERROR, "Unable to decrypt data.");
|
a && "clearText" in a ? (this.#i(a.clearText), c(s.VERIFIED), this.context.dispatch("cleartext", a.clearText), m && t && (t.style.display = "none")) : c(s.ERROR, "Unable to decrypt data.");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Handles the button click event, triggering the clarification process.
|
||||||
|
*
|
||||||
|
* @param {Event} ev - The click event.
|
||||||
|
*/
|
||||||
|
#e(t) {
|
||||||
|
t.preventDefault();
|
||||||
|
const { auto: i } = this.context.getConfiguration();
|
||||||
|
i === "off" || this.clarify();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Renders the clear text data by creating an appropriate element (e.g., a link or text node).
|
||||||
|
*
|
||||||
|
* @param {string} clearText - The decrypted clear text data to render.
|
||||||
|
*/
|
||||||
|
#i(t) {
|
||||||
|
const i = t.match(/^(mailto|tel|sms|https?):/);
|
||||||
|
let n;
|
||||||
|
if (i) {
|
||||||
|
const [r] = t.slice(t.indexOf(":") + 1).replace(/^\/\//, "").split("?");
|
||||||
|
n = document.createElement("a"), n.href = t, n.innerHTML = r;
|
||||||
|
} else
|
||||||
|
n = document.createTextNode(t);
|
||||||
|
this.elButton && n && (this.elButton.after(n), this.elButton.parentElement?.removeChild(this.elButton));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r = new WeakMap(), a = new WeakSet(), /**
|
h.register(R);
|
||||||
* Handles the button click event, triggering the clarification process.
|
|
||||||
*
|
|
||||||
* @param {Event} ev - The click event.
|
|
||||||
*/
|
|
||||||
I = function(i) {
|
|
||||||
i.preventDefault();
|
|
||||||
const { auto: s } = this.context.getConfiguration();
|
|
||||||
s === "off" || this.clarify();
|
|
||||||
}, /**
|
|
||||||
* Renders the clear text data by creating an appropriate element (e.g., a link or text node).
|
|
||||||
*
|
|
||||||
* @param {string} clearText - The decrypted clear text data to render.
|
|
||||||
*/
|
|
||||||
x = function(i) {
|
|
||||||
var o;
|
|
||||||
const s = i.match(/^(mailto|tel|sms|https?):/);
|
|
||||||
let n;
|
|
||||||
if (s) {
|
|
||||||
const [d] = i.slice(i.indexOf(":") + 1).replace(/^\/\//, "").split("?");
|
|
||||||
n = document.createElement("a"), n.href = i, n.innerHTML = d;
|
|
||||||
} else
|
|
||||||
n = document.createTextNode(i);
|
|
||||||
this.elButton && n && (this.elButton.after(n), (o = this.elButton.parentElement) == null || o.removeChild(this.elButton));
|
|
||||||
}, f(v, "pluginName", "obfuscation");
|
|
||||||
y.register(v);
|
|
||||||
export {
|
export {
|
||||||
v as PluginObfuscation
|
R as PluginObfuscation
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
(function(e,t){typeof exports=="object"&&typeof module<"u"?t(exports):typeof define=="function"&&define.amd?define(["exports"],t):(e=typeof globalThis<"u"?globalThis:e||self,t(e["[name]"]={}))})(this,function(e){"use strict";var N=Object.defineProperty;var v=e=>{throw TypeError(e)};var V=(e,t,i)=>t in e?N(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i;var p=(e,t,i)=>V(e,typeof t!="symbol"?t+"":t,i),x=(e,t,i)=>t.has(e)||v("Cannot "+i);var y=(e,t,i)=>(x(e,t,"read from private field"),i?i.call(e):t.get(e)),b=(e,t,i)=>t.has(e)?v("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i);var R=(e,t,i)=>(x(e,t,"access private method"),i);var u,c,I,P;class t{constructor(l){this.context=l}static register(l){typeof globalThis.altchaPlugins!="object"&&(globalThis.altchaPlugins=[]),globalThis.altchaPlugins.includes(l)||globalThis.altchaPlugins.push(l)}destroy(){}onErrorChange(l){}onStateChange(l){}}p(t,"pluginName");var i=(s=>(s.CODE="code",s.ERROR="error",s.VERIFIED="verified",s.VERIFYING="verifying",s.UNVERIFIED="unverified",s.EXPIRED="expired",s))(i||{});class m extends t{constructor(n){var o,r;super(n);b(this,c);p(this,"elButton");b(this,u,R(this,c,I).bind(this));const a=n.el;this.elButton=((o=a.parentElement)==null?void 0:o.querySelector("[data-clarify-button]"))||((r=a.parentElement)==null?void 0:r.querySelector("button, a")),this.elButton&&this.elButton.addEventListener("click",y(this,u))}destroy(){this.elButton&&this.elButton.removeEventListener("click",y(this,u))}async clarify(){const{el:n,getConfiguration:a,getFloatingAnchor:o,setFloatingAnchor:r,reset:g,solve:T,setState:E}=this.context,{delay:C,floating:F,maxnumber:O,obfuscated:B}=a();if(this.elButton&&!o()&&r(this.elButton),!B){E(i.ERROR);return}g(i.VERIFYING),await new Promise(d=>setTimeout(d,C||0));const[k,D]=B.split("?");let h=new URLSearchParams(D||"").get("key")||void 0;if(h){const d=h.match(/^\(prompt:?(.*)\)$/);d&&(h=prompt(d[1]||"Enter Key:")||void 0)}const{solution:f}=await T({obfuscated:k,key:h,maxnumber:O});f&&"clearText"in f?(R(this,c,P).call(this,f.clearText),E(i.VERIFIED),this.context.dispatch("cleartext",f.clearText),F&&n&&(n.style.display="none")):E(i.ERROR,"Unable to decrypt data.")}}u=new WeakMap,c=new WeakSet,I=function(n){n.preventDefault();const{auto:a}=this.context.getConfiguration();a==="off"||this.clarify()},P=function(n){var r;const a=n.match(/^(mailto|tel|sms|https?):/);let o;if(a){const[g]=n.slice(n.indexOf(":")+1).replace(/^\/\//,"").split("?");o=document.createElement("a"),o.href=n,o.innerHTML=g}else o=document.createTextNode(n);this.elButton&&o&&(this.elButton.after(o),(r=this.elButton.parentElement)==null||r.removeChild(this.elButton))},p(m,"pluginName","obfuscation"),t.register(m),e.PluginObfuscation=m,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|
(function(o,s){typeof exports=="object"&&typeof module<"u"?s(exports):typeof define=="function"&&define.amd?define(["exports"],s):(o=typeof globalThis<"u"?globalThis:o||self,s(o["[name]"]={}))})(this,function(o){"use strict";class s{constructor(t){this.context=t}static pluginName;static register(t){typeof globalThis.altchaPlugins!="object"&&(globalThis.altchaPlugins=[]),globalThis.altchaPlugins.includes(t)||globalThis.altchaPlugins.push(t)}destroy(){}onErrorChange(t){}onStateChange(t){}}var a=(e=>(e.CODE="code",e.ERROR="error",e.VERIFIED="verified",e.VERIFYING="verifying",e.UNVERIFIED="unverified",e.EXPIRED="expired",e))(a||{});class h extends s{static pluginName="obfuscation";elButton;#t=this.#e.bind(this);constructor(t){super(t);const i=t.el;this.elButton=i.parentElement?.querySelector("[data-clarify-button]")||i.parentElement?.querySelector("button, a"),this.elButton&&this.elButton.addEventListener("click",this.#t)}destroy(){this.elButton&&this.elButton.removeEventListener("click",this.#t)}async clarify(){const{el:t,getConfiguration:i,getFloatingAnchor:n,setFloatingAnchor:u,reset:p,solve:m,setState:f}=this.context,{delay:g,floating:E,maxnumber:y,obfuscated:d}=i();if(this.elButton&&!n()&&u(this.elButton),!d){f(a.ERROR);return}p(a.VERIFYING),await new Promise(c=>setTimeout(c,g||0));const[b,R]=d.split("?");let l=new URLSearchParams(R||"").get("key")||void 0;if(l){const c=l.match(/^\(prompt:?(.*)\)$/);c&&(l=prompt(c[1]||"Enter Key:")||void 0)}const{solution:r}=await m({obfuscated:b,key:l,maxnumber:y});r&&"clearText"in r?(this.#i(r.clearText),f(a.VERIFIED),this.context.dispatch("cleartext",r.clearText),E&&t&&(t.style.display="none")):f(a.ERROR,"Unable to decrypt data.")}#e(t){t.preventDefault();const{auto:i}=this.context.getConfiguration();i==="off"||this.clarify()}#i(t){const i=t.match(/^(mailto|tel|sms|https?):/);let n;if(i){const[u]=t.slice(t.indexOf(":")+1).replace(/^\/\//,"").split("?");n=document.createElement("a"),n.href=t,n.innerHTML=u}else n=document.createTextNode(t);this.elButton&&n&&(this.elButton.after(n),this.elButton.parentElement?.removeChild(this.elButton))}}s.register(h),o.PluginObfuscation=h,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
|
||||||
|
|||||||
@@ -1,167 +1,163 @@
|
|||||||
var J = Object.defineProperty;
|
const u = {
|
||||||
var v = (t) => {
|
generateKey: v,
|
||||||
throw TypeError(t);
|
exportKey: S,
|
||||||
|
importKey: x,
|
||||||
|
decrypt: C,
|
||||||
|
encrypt: L
|
||||||
};
|
};
|
||||||
var X = (t, n, e) => n in t ? J(t, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : t[n] = e;
|
async function v(n = 256) {
|
||||||
var p = (t, n, e) => X(t, typeof n != "symbol" ? n + "" : n, e), S = (t, n, e) => n.has(t) || v("Cannot " + e);
|
|
||||||
var f = (t, n, e) => (S(t, n, "read from private field"), e ? e.call(t) : n.get(t)), w = (t, n, e) => n.has(t) ? v("Cannot add the same private member more than once") : n instanceof WeakSet ? n.add(t) : n.set(t, e);
|
|
||||||
var u = (t, n, e) => (S(t, n, "access private method"), e);
|
|
||||||
const E = {
|
|
||||||
generateKey: Q,
|
|
||||||
exportKey: W,
|
|
||||||
importKey: Z,
|
|
||||||
decrypt: te,
|
|
||||||
encrypt: ee
|
|
||||||
};
|
|
||||||
async function Q(t = 256) {
|
|
||||||
return crypto.subtle.generateKey({
|
return crypto.subtle.generateKey({
|
||||||
name: "AES-GCM",
|
name: "AES-GCM",
|
||||||
length: t
|
length: n
|
||||||
}, !0, ["encrypt", "decrypt"]);
|
}, !0, ["encrypt", "decrypt"]);
|
||||||
}
|
}
|
||||||
async function W(t) {
|
async function S(n) {
|
||||||
return new Uint8Array(await crypto.subtle.exportKey("raw", t));
|
return new Uint8Array(await crypto.subtle.exportKey("raw", n));
|
||||||
}
|
}
|
||||||
async function Z(t) {
|
async function x(n) {
|
||||||
return crypto.subtle.importKey("raw", t, {
|
return crypto.subtle.importKey("raw", n, {
|
||||||
name: "AES-GCM"
|
name: "AES-GCM"
|
||||||
}, !0, ["encrypt", "decrypt"]);
|
}, !0, ["encrypt", "decrypt"]);
|
||||||
}
|
}
|
||||||
async function ee(t, n, e = 16) {
|
async function L(n, e, t = 16) {
|
||||||
const i = crypto.getRandomValues(new Uint8Array(e));
|
const r = crypto.getRandomValues(new Uint8Array(t));
|
||||||
return {
|
return {
|
||||||
encrypted: new Uint8Array(await crypto.subtle.encrypt({
|
encrypted: new Uint8Array(await crypto.subtle.encrypt({
|
||||||
name: "AES-GCM",
|
name: "AES-GCM",
|
||||||
iv: i
|
iv: r
|
||||||
}, t, n)),
|
}, n, e)),
|
||||||
iv: i
|
iv: r
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async function te(t, n, e) {
|
async function C(n, e, t) {
|
||||||
return new Uint8Array(await crypto.subtle.decrypt({
|
return new Uint8Array(await crypto.subtle.decrypt({
|
||||||
name: "AES-GCM",
|
name: "AES-GCM",
|
||||||
iv: e
|
iv: t
|
||||||
}, t, n));
|
}, n, e));
|
||||||
}
|
}
|
||||||
function ne(t, n = !1) {
|
function I(n, e = !1) {
|
||||||
return n && (t = t.replace(/_/g, "/").replace(/-/g, "+") + "=".repeat(3 - (3 + t.length) % 4)), Uint8Array.from(atob(t), (e) => e.charCodeAt(0));
|
return e && (n = n.replace(/_/g, "/").replace(/-/g, "+") + "=".repeat(3 - (3 + n.length) % 4)), Uint8Array.from(atob(n), (t) => t.charCodeAt(0));
|
||||||
}
|
}
|
||||||
function x(t, n = !1) {
|
function h(n, e = !1) {
|
||||||
const e = btoa(String.fromCharCode(...t));
|
const t = btoa(String.fromCharCode(...n));
|
||||||
return n ? e.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") : e;
|
return e ? t.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") : t;
|
||||||
}
|
}
|
||||||
function L(t, n = 80) {
|
function f(n, e = 80) {
|
||||||
let e = "";
|
let t = "";
|
||||||
for (; t.length > 0; )
|
for (; n.length > 0; )
|
||||||
e += t.slice(0, n) + `
|
t += n.slice(0, e) + `
|
||||||
`, t = t.slice(n);
|
`, n = n.slice(e);
|
||||||
return e;
|
return t;
|
||||||
}
|
}
|
||||||
function C(t) {
|
function g(n) {
|
||||||
return ne(t.split(/\r?\n/).filter((n) => !n.startsWith("-----")).join(""));
|
return I(n.split(/\r?\n/).filter((e) => !e.startsWith("-----")).join(""));
|
||||||
}
|
}
|
||||||
const h = "RSA-OAEP", b = "SHA-256", re = 2048, ie = new Uint8Array([1, 0, 1]), se = {
|
const l = "RSA-OAEP", p = "SHA-256", N = 2048, z = new Uint8Array([1, 0, 1]), T = {
|
||||||
generateKeyPair: oe,
|
generateKeyPair: _,
|
||||||
encrypt: ae,
|
encrypt: R,
|
||||||
decrypt: ce,
|
decrypt: j,
|
||||||
exportPrivateKey: I,
|
exportPrivateKey: m,
|
||||||
exportPrivateKeyPem: pe,
|
exportPrivateKeyPem: H,
|
||||||
exportPublicKey: F,
|
exportPublicKey: d,
|
||||||
exportPublicKeyPem: le,
|
exportPublicKeyPem: k,
|
||||||
exportPublicKeyFromPrivateKey: de,
|
exportPublicKeyFromPrivateKey: O,
|
||||||
importPrivateKey: T,
|
importPrivateKey: E,
|
||||||
importPrivateKeyPem: ue,
|
importPrivateKeyPem: q,
|
||||||
importPublicKey: N,
|
importPublicKey: w,
|
||||||
importPublicKeyPem: z
|
importPublicKeyPem: b
|
||||||
};
|
};
|
||||||
async function oe() {
|
async function _() {
|
||||||
return crypto.subtle.generateKey({
|
return crypto.subtle.generateKey({
|
||||||
name: h,
|
name: l,
|
||||||
modulusLength: re,
|
modulusLength: N,
|
||||||
publicExponent: ie,
|
publicExponent: z,
|
||||||
hash: b
|
hash: p
|
||||||
}, !0, ["encrypt", "decrypt"]);
|
}, !0, ["encrypt", "decrypt"]);
|
||||||
}
|
}
|
||||||
async function ae(t, n) {
|
async function R(n, e) {
|
||||||
return new Uint8Array(await crypto.subtle.encrypt({
|
return new Uint8Array(await crypto.subtle.encrypt({
|
||||||
name: h
|
name: l
|
||||||
}, t, n));
|
}, n, e));
|
||||||
}
|
}
|
||||||
async function ce(t, n) {
|
async function j(n, e) {
|
||||||
return new Uint8Array(await crypto.subtle.decrypt({
|
return new Uint8Array(await crypto.subtle.decrypt({
|
||||||
name: h
|
name: l
|
||||||
}, t, n));
|
}, n, e));
|
||||||
}
|
}
|
||||||
async function F(t) {
|
async function d(n) {
|
||||||
return new Uint8Array(await crypto.subtle.exportKey("spki", t));
|
return new Uint8Array(await crypto.subtle.exportKey("spki", n));
|
||||||
}
|
}
|
||||||
async function I(t) {
|
async function m(n) {
|
||||||
return new Uint8Array(await crypto.subtle.exportKey("pkcs8", t));
|
return new Uint8Array(await crypto.subtle.exportKey("pkcs8", n));
|
||||||
}
|
}
|
||||||
async function le(t) {
|
async function k(n) {
|
||||||
return `-----BEGIN PUBLIC KEY-----
|
return `-----BEGIN PUBLIC KEY-----
|
||||||
` + L(x(await F(t)), 64) + "-----END PUBLIC KEY-----";
|
` + f(h(await d(n)), 64) + "-----END PUBLIC KEY-----";
|
||||||
}
|
}
|
||||||
async function pe(t) {
|
async function H(n) {
|
||||||
return `-----BEGIN PRIVATE KEY-----
|
return `-----BEGIN PRIVATE KEY-----
|
||||||
` + L(x(await I(t)), 64) + "-----END PRIVATE KEY-----";
|
` + f(h(await m(n)), 64) + "-----END PRIVATE KEY-----";
|
||||||
}
|
}
|
||||||
async function N(t) {
|
async function w(n) {
|
||||||
return crypto.subtle.importKey("spki", t, {
|
return crypto.subtle.importKey("spki", n, {
|
||||||
name: h,
|
name: l,
|
||||||
hash: b
|
hash: p
|
||||||
}, !0, ["encrypt"]);
|
}, !0, ["encrypt"]);
|
||||||
}
|
}
|
||||||
async function z(t) {
|
async function b(n) {
|
||||||
return N(C(t));
|
return w(g(n));
|
||||||
}
|
}
|
||||||
async function T(t) {
|
async function E(n) {
|
||||||
return crypto.subtle.importKey("pkcs8", t, {
|
return crypto.subtle.importKey("pkcs8", n, {
|
||||||
name: h,
|
name: l,
|
||||||
hash: b
|
hash: p
|
||||||
}, !0, ["decrypt"]);
|
}, !0, ["decrypt"]);
|
||||||
}
|
}
|
||||||
async function ue(t) {
|
async function q(n) {
|
||||||
return T(C(t));
|
return E(g(n));
|
||||||
}
|
}
|
||||||
async function de(t) {
|
async function O(n) {
|
||||||
const n = await crypto.subtle.exportKey("jwk", t);
|
const e = await crypto.subtle.exportKey("jwk", n);
|
||||||
delete n.d, delete n.dp, delete n.dq, delete n.q, delete n.qi, n.key_ops = ["encrypt"];
|
delete e.d, delete e.dp, delete e.dq, delete e.q, delete e.qi, e.key_ops = ["encrypt"];
|
||||||
const e = await crypto.subtle.importKey("jwk", n, {
|
const t = await crypto.subtle.importKey("jwk", e, {
|
||||||
name: h,
|
name: l,
|
||||||
hash: b
|
hash: p
|
||||||
}, !0, ["encrypt"]);
|
}, !0, ["encrypt"]);
|
||||||
return F(e);
|
return d(t);
|
||||||
}
|
}
|
||||||
const ye = new Uint8Array([1, 0, 1]), he = 256, fe = 16;
|
const $ = new Uint8Array([1, 0, 1]), B = 256, G = 16;
|
||||||
async function me(t, n, e = {}) {
|
async function M(n, e, t = {}) {
|
||||||
const { aesIVLength: i = fe, aesKeyLength: r = he } = e, o = await E.generateKey(r), { encrypted: a, iv: s } = await E.encrypt(o, n, i), l = await se.encrypt(t, await E.exportKey(o));
|
const { aesIVLength: r = G, aesKeyLength: i = B } = t, s = await u.generateKey(i), { encrypted: o, iv: a } = await u.encrypt(s, e, r), c = await T.encrypt(n, await u.exportKey(s));
|
||||||
return new Uint8Array([
|
return new Uint8Array([
|
||||||
...ye,
|
...$,
|
||||||
...new Uint8Array([l.length]),
|
...new Uint8Array([c.length]),
|
||||||
...new Uint8Array([s.length]),
|
...new Uint8Array([a.length]),
|
||||||
...l,
|
...c,
|
||||||
...s,
|
...a,
|
||||||
...a
|
...o
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
class P {
|
class U {
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance of the Plugin.
|
* Constructs a new instance of the Plugin.
|
||||||
*
|
*
|
||||||
* @param {PluginContext} context - The context provided to the plugin, containing necessary configurations and dependencies.
|
* @param {PluginContext} context - The context provided to the plugin, containing necessary configurations and dependencies.
|
||||||
*/
|
*/
|
||||||
constructor(n) {
|
constructor(e) {
|
||||||
this.context = n;
|
this.context = e;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* A distinct name of the plugin. Every plugin must have it's own name.
|
||||||
|
*/
|
||||||
|
static pluginName;
|
||||||
/**
|
/**
|
||||||
* Registers a plugin class in the global `altchaPlugins` array.
|
* Registers a plugin class in the global `altchaPlugins` array.
|
||||||
* Ensures the plugin is added only once.
|
* Ensures the plugin is added only once.
|
||||||
*
|
*
|
||||||
* @param {new(context: PluginContext) => Plugin} cls - The plugin class to register.
|
* @param {new(context: PluginContext) => Plugin} cls - The plugin class to register.
|
||||||
*/
|
*/
|
||||||
static register(n) {
|
static register(e) {
|
||||||
typeof globalThis.altchaPlugins != "object" && (globalThis.altchaPlugins = []), globalThis.altchaPlugins.includes(n) || globalThis.altchaPlugins.push(n);
|
typeof globalThis.altchaPlugins != "object" && (globalThis.altchaPlugins = []), globalThis.altchaPlugins.includes(e) || globalThis.altchaPlugins.push(e);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Clean up resources when the plugin is destroyed.
|
* Clean up resources when the plugin is destroyed.
|
||||||
@@ -175,7 +171,7 @@ class P {
|
|||||||
*
|
*
|
||||||
* @param {string | null} err - The error message or `null` if there's no error.
|
* @param {string | null} err - The error message or `null` if there's no error.
|
||||||
*/
|
*/
|
||||||
onErrorChange(n) {
|
onErrorChange(e) {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Callback triggered when the plugin state changes.
|
* Callback triggered when the plugin state changes.
|
||||||
@@ -183,29 +179,23 @@ class P {
|
|||||||
*
|
*
|
||||||
* @param {State} state - The new state of the plugin.
|
* @param {State} state - The new state of the plugin.
|
||||||
*/
|
*/
|
||||||
onStateChange(n) {
|
onStateChange(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
class Y extends U {
|
||||||
* A distinct name of the plugin. Every plugin must have it's own name.
|
static pluginName = "upload";
|
||||||
*/
|
pendingFiles = [];
|
||||||
p(P, "pluginName");
|
uploadHandles = [];
|
||||||
var m, g, c, R, j, k, U, H, q, O, A, $, B, G;
|
elForm;
|
||||||
class _ extends P {
|
#e = this.#c.bind(this);
|
||||||
|
#t = this.#l.bind(this);
|
||||||
/**
|
/**
|
||||||
* Constructor initializes the plugin, setting up event listeners on the form.
|
* Constructor initializes the plugin, setting up event listeners on the form.
|
||||||
*
|
*
|
||||||
* @param {PluginContext} context - Plugin context providing access to the element, configuration, and utility methods.
|
* @param {PluginContext} context - Plugin context providing access to the element, configuration, and utility methods.
|
||||||
*/
|
*/
|
||||||
constructor(e) {
|
constructor(e) {
|
||||||
super(e);
|
super(e), this.elForm = this.context.el.closest("form"), this.elForm && (this.elForm.addEventListener("change", this.#e), this.elForm.addEventListener("submit", this.#t, {
|
||||||
w(this, c);
|
|
||||||
p(this, "pendingFiles", []);
|
|
||||||
p(this, "uploadHandles", []);
|
|
||||||
p(this, "elForm");
|
|
||||||
w(this, m, u(this, c, q).bind(this));
|
|
||||||
w(this, g, u(this, c, O).bind(this));
|
|
||||||
this.elForm = this.context.el.closest("form"), this.elForm && (this.elForm.addEventListener("change", f(this, m)), this.elForm.addEventListener("submit", f(this, g), {
|
|
||||||
capture: !0
|
capture: !0
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -215,245 +205,283 @@ class _ extends P {
|
|||||||
* @param {string} fieldName - The field name associated with the file input.
|
* @param {string} fieldName - The field name associated with the file input.
|
||||||
* @param {File} file - The file to be uploaded.
|
* @param {File} file - The file to be uploaded.
|
||||||
*/
|
*/
|
||||||
addFile(e, i) {
|
addFile(e, t) {
|
||||||
this.pendingFiles.find(([r, o]) => r === e && o === i) || this.pendingFiles.push([e, i]);
|
this.pendingFiles.find(([r, i]) => r === e && i === t) || this.pendingFiles.push([e, t]);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Cleans up event listeners and other resources when the plugin is destroyed.
|
* Cleans up event listeners and other resources when the plugin is destroyed.
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
this.elForm && (this.elForm.removeEventListener("change", f(this, m)), this.elForm.removeEventListener("submit", f(this, g)));
|
this.elForm && (this.elForm.removeEventListener("change", this.#e), this.elForm.removeEventListener("submit", this.#t));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Uploads all pending files in the list.
|
* Uploads all pending files in the list.
|
||||||
*/
|
*/
|
||||||
async uploadPendingFiles() {
|
async uploadPendingFiles() {
|
||||||
var i;
|
|
||||||
const e = async () => {
|
const e = async () => {
|
||||||
const r = this.pendingFiles[0];
|
const t = this.pendingFiles[0];
|
||||||
if (r && await u(this, c, A).call(this, u(this, c, j).call(this, r)), this.pendingFiles.length)
|
if (t && await this.#r(this.#s(t)), this.pendingFiles.length)
|
||||||
return e();
|
return e();
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
await e();
|
await e();
|
||||||
} catch (r) {
|
} catch (t) {
|
||||||
return this.context.log("upload failed", r), this.context.dispatch("uploaderror", {
|
return this.context.log("upload failed", t), this.context.dispatch("uploaderror", {
|
||||||
error: r
|
error: t
|
||||||
}), !1;
|
}), !1;
|
||||||
}
|
}
|
||||||
this.pendingFiles.length === 0 && (u(this, c, R).call(this), (i = this.elForm) == null || i.requestSubmit());
|
this.pendingFiles.length === 0 && (this.#i(), this.elForm?.requestSubmit());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Adds hidden input fields to the form containing the file IDs of uploaded files.
|
||||||
|
*/
|
||||||
|
#i() {
|
||||||
|
const e = this.uploadHandles.reduce(
|
||||||
|
(t, r) => (t[r.fieldName] || (t[r.fieldName] = []), r.fileId && t[r.fieldName].push(r.fileId), t),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
for (const t in e) {
|
||||||
|
const r = document.createElement("input");
|
||||||
|
r.name = t, r.type = "hidden", r.value = e[t].join(","), this.elForm?.querySelector(`[name="${t}"]`)?.setAttribute("disabled", "disabled"), this.elForm?.appendChild(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Creates an upload handle for the specified pending file.
|
||||||
|
*
|
||||||
|
* @param {[string, File]} pendingFile - The field name and file to be uploaded.
|
||||||
|
* @returns {UploadHandle} The created upload handle.
|
||||||
|
* @throws Will throw an error if the upload handle cannot be created.
|
||||||
|
*/
|
||||||
|
#s(e) {
|
||||||
|
const t = this.pendingFiles.findIndex(
|
||||||
|
([i, s]) => i === e[0] && s === e[1]
|
||||||
|
);
|
||||||
|
if (t < 0)
|
||||||
|
throw new Error("Cannot create upload handle.");
|
||||||
|
const r = new D(e[0], e[1]);
|
||||||
|
return this.uploadHandles.push(r), this.pendingFiles.splice(t, 1), this.#o(r), this.#n(), r;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Dispatches a custom event when a file upload starts.
|
||||||
|
*
|
||||||
|
* @param {UploadHandle} handle - The upload handle associated with the file upload.
|
||||||
|
*/
|
||||||
|
#o(e) {
|
||||||
|
this.context.dispatch("upload", { handle: e });
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Dispatches a custom event to track the progress of ongoing file uploads.
|
||||||
|
*/
|
||||||
|
#n() {
|
||||||
|
const e = this.pendingFiles.reduce((r, [i, s]) => r + s.size, 0) + this.uploadHandles.reduce((r, { uploadSize: i }) => r + i, 0), t = this.uploadHandles.reduce(
|
||||||
|
(r, { loaded: i }) => r + i,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
this.context.dispatch("uploadprogress", {
|
||||||
|
bytesLoaded: t,
|
||||||
|
bytesTotal: e,
|
||||||
|
pendingFiles: this.pendingFiles,
|
||||||
|
uploadHandles: this.uploadHandles
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieves the upload URL from the form's attributes.
|
||||||
|
*
|
||||||
|
* @returns {string | null} The upload URL, or null if not found.
|
||||||
|
*/
|
||||||
|
#a() {
|
||||||
|
if (this.elForm) {
|
||||||
|
const e = this.elForm.getAttribute("action"), t = this.elForm.getAttribute("data-upload-url");
|
||||||
|
if (t)
|
||||||
|
return t;
|
||||||
|
const r = new URL(e || location.origin);
|
||||||
|
return r.pathname = r.pathname + "/file", r.toString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Handles the form's change event, adding files to the pending files list.
|
||||||
|
*
|
||||||
|
* @param {Event} ev - The change event.
|
||||||
|
*/
|
||||||
|
#c(e) {
|
||||||
|
const t = e.target;
|
||||||
|
if (t && t.type === "file") {
|
||||||
|
const r = t.files;
|
||||||
|
if (r?.length)
|
||||||
|
for (const i of r)
|
||||||
|
this.addFile(t.name, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Handles the form's submit event, preventing submission until all pending files are uploaded.
|
||||||
|
*
|
||||||
|
* @param {SubmitEvent} ev - The submit event.
|
||||||
|
*/
|
||||||
|
#l(e) {
|
||||||
|
e.target?.hasAttribute(
|
||||||
|
"data-code-challenge-form"
|
||||||
|
) || this.pendingFiles.length && (e.preventDefault(), e.stopPropagation(), this.uploadPendingFiles());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Uploads a single file, handling encryption and ALTCHA challenges if necessary.
|
||||||
|
*
|
||||||
|
* @param {UploadHandle} handle - The upload handle associated with the file.
|
||||||
|
* @param {string} [altchaPayload] - The ALTCHA payload, if available.
|
||||||
|
* @returns {Promise<unknown>} A promise that resolves when the upload is complete.
|
||||||
|
* @throws Will throw an error if the upload fails or if an ALTCHA challenge cannot be solved.
|
||||||
|
*/
|
||||||
|
async #r(e, t) {
|
||||||
|
const r = this.#a();
|
||||||
|
if (!r)
|
||||||
|
throw new Error("Upload url not specified.");
|
||||||
|
const i = {
|
||||||
|
"content-type": "application/json"
|
||||||
|
};
|
||||||
|
t && (i.authorization = "Altcha payload=" + t);
|
||||||
|
const s = await fetch(r, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: e.file.name || "file",
|
||||||
|
size: e.file.size,
|
||||||
|
type: e.file.type || "application/octet-stream"
|
||||||
|
}),
|
||||||
|
credentials: "include",
|
||||||
|
headers: i,
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
if (s.status === 401)
|
||||||
|
return this.#p(s, e);
|
||||||
|
if (s.status !== 200)
|
||||||
|
throw new Error(`Unexpected server response ${s.status}.`);
|
||||||
|
const o = await s.json();
|
||||||
|
let a = e.file;
|
||||||
|
if (o.encrypted && o.encryptionPublicKey) {
|
||||||
|
const c = await b(o.encryptionPublicKey), P = await new Response(
|
||||||
|
new ReadableStream({
|
||||||
|
async start(y) {
|
||||||
|
const A = e.file.stream().getReader();
|
||||||
|
for (; ; ) {
|
||||||
|
const { done: F, value: K } = await A.read();
|
||||||
|
if (F)
|
||||||
|
break;
|
||||||
|
y.enqueue(K);
|
||||||
|
}
|
||||||
|
y.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).arrayBuffer();
|
||||||
|
a = await M(c, new Uint8Array(P));
|
||||||
|
}
|
||||||
|
return e.uploadSize = a instanceof Uint8Array ? a.byteLength : e.file.size, await this.#d(o.uploadUrl, e, a, {
|
||||||
|
"content-type": e.file.type || "application/octet-stream"
|
||||||
|
}), o.finalizeUrl && await this.#u(o.finalizeUrl, e.uploadSize), e.fileId = o.fileId, e.resolve({
|
||||||
|
encrypted: o.encrypted,
|
||||||
|
fileId: o.fileId
|
||||||
|
}), e.promise;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Handles ALTCHA challenges during file upload, solving the challenge and retrying the upload.
|
||||||
|
*
|
||||||
|
* @param {Response} resp - The response from the server containing the ALTCHA challenge.
|
||||||
|
* @param {UploadHandle} handle - The upload handle associated with the file.
|
||||||
|
* @returns {Promise<unknown>} A promise that resolves when the challenge is solved and the upload is complete.
|
||||||
|
* @throws Will throw an error if the challenge cannot be solved.
|
||||||
|
*/
|
||||||
|
async #p(e, t) {
|
||||||
|
try {
|
||||||
|
const i = e.headers.get("www-authenticate")?.match(/challenge=(.*),/)?.[1];
|
||||||
|
if (!i)
|
||||||
|
throw new Error(
|
||||||
|
"Unable to retrieve altcha challenge from www-authenticate header."
|
||||||
|
);
|
||||||
|
const s = JSON.parse(i);
|
||||||
|
if (s && "challenge" in s) {
|
||||||
|
const { solution: o } = await this.context.solve(s);
|
||||||
|
if (o && "number" in o)
|
||||||
|
return this.#r(
|
||||||
|
t,
|
||||||
|
btoa(
|
||||||
|
JSON.stringify({
|
||||||
|
...s,
|
||||||
|
number: o.number
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
throw new Error("Invalid challenge solution.");
|
||||||
|
}
|
||||||
|
} catch (r) {
|
||||||
|
throw this.context.log(r), new Error("Unable to solve altcha challenge for upload.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Finalizes the file upload by sending a request to the server.
|
||||||
|
*
|
||||||
|
* @param {string} finalizeUrl - The URL to finalize the upload.
|
||||||
|
* @param {number} uploadSize - The size of the uploaded file.
|
||||||
|
*/
|
||||||
|
async #u(e, t) {
|
||||||
|
const r = await fetch(e, {
|
||||||
|
body: JSON.stringify({
|
||||||
|
uploadedBytes: t
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
},
|
||||||
|
method: "POST"
|
||||||
|
});
|
||||||
|
if (r.status > 204)
|
||||||
|
throw new Error(`Unexpected server response ${r.status}.`);
|
||||||
|
return !0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Uploads the file's contents to the server using the PUT method.
|
||||||
|
*
|
||||||
|
* @param {string} url - The URL to upload the file to.
|
||||||
|
* @param {UploadHandle} handle - The upload handle associated with the file.
|
||||||
|
* @param {Uint8Array | File} body - The file's contents.
|
||||||
|
* @param {Record<string, string>} headers - Additional headers for the upload request.
|
||||||
|
*/
|
||||||
|
async #d(e, t, r, i = {}) {
|
||||||
|
return e = new URL(
|
||||||
|
e,
|
||||||
|
this.elForm?.getAttribute("action") || location.origin
|
||||||
|
).toString(), new Promise((s, o) => {
|
||||||
|
const a = new XMLHttpRequest();
|
||||||
|
t.controller.signal.addEventListener("abort", () => {
|
||||||
|
a.abort();
|
||||||
|
}), a.upload.addEventListener("progress", (c) => {
|
||||||
|
t.setProgress(c.loaded), this.#n();
|
||||||
|
}), a.addEventListener("error", (c) => {
|
||||||
|
o(new Error("Upload failed."));
|
||||||
|
}), a.addEventListener("load", () => {
|
||||||
|
a.status >= 400 ? o(new Error(`Server responded with ${a.status}`)) : s(void 0);
|
||||||
|
}), a.open("PUT", e);
|
||||||
|
for (const c in i)
|
||||||
|
a.setRequestHeader(c, i[c]);
|
||||||
|
a.send(r);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m = new WeakMap(), g = new WeakMap(), c = new WeakSet(), /**
|
class D {
|
||||||
* Adds hidden input fields to the form containing the file IDs of uploaded files.
|
|
||||||
*/
|
|
||||||
R = function() {
|
|
||||||
var i, r, o;
|
|
||||||
const e = this.uploadHandles.reduce(
|
|
||||||
(a, s) => (a[s.fieldName] || (a[s.fieldName] = []), s.fileId && a[s.fieldName].push(s.fileId), a),
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
for (const a in e) {
|
|
||||||
const s = document.createElement("input");
|
|
||||||
s.name = a, s.type = "hidden", s.value = e[a].join(","), (r = (i = this.elForm) == null ? void 0 : i.querySelector(`[name="${a}"]`)) == null || r.setAttribute("disabled", "disabled"), (o = this.elForm) == null || o.appendChild(s);
|
|
||||||
}
|
|
||||||
}, /**
|
|
||||||
* Creates an upload handle for the specified pending file.
|
|
||||||
*
|
|
||||||
* @param {[string, File]} pendingFile - The field name and file to be uploaded.
|
|
||||||
* @returns {UploadHandle} The created upload handle.
|
|
||||||
* @throws Will throw an error if the upload handle cannot be created.
|
|
||||||
*/
|
|
||||||
j = function(e) {
|
|
||||||
const i = this.pendingFiles.findIndex(
|
|
||||||
([o, a]) => o === e[0] && a === e[1]
|
|
||||||
);
|
|
||||||
if (i < 0)
|
|
||||||
throw new Error("Cannot create upload handle.");
|
|
||||||
const r = new ge(e[0], e[1]);
|
|
||||||
return this.uploadHandles.push(r), this.pendingFiles.splice(i, 1), u(this, c, k).call(this, r), u(this, c, U).call(this), r;
|
|
||||||
}, /**
|
|
||||||
* Dispatches a custom event when a file upload starts.
|
|
||||||
*
|
|
||||||
* @param {UploadHandle} handle - The upload handle associated with the file upload.
|
|
||||||
*/
|
|
||||||
k = function(e) {
|
|
||||||
this.context.dispatch("upload", { handle: e });
|
|
||||||
}, /**
|
|
||||||
* Dispatches a custom event to track the progress of ongoing file uploads.
|
|
||||||
*/
|
|
||||||
U = function() {
|
|
||||||
const e = this.pendingFiles.reduce((r, [o, a]) => r + a.size, 0) + this.uploadHandles.reduce((r, { uploadSize: o }) => r + o, 0), i = this.uploadHandles.reduce(
|
|
||||||
(r, { loaded: o }) => r + o,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
this.context.dispatch("uploadprogress", {
|
|
||||||
bytesLoaded: i,
|
|
||||||
bytesTotal: e,
|
|
||||||
pendingFiles: this.pendingFiles,
|
|
||||||
uploadHandles: this.uploadHandles
|
|
||||||
});
|
|
||||||
}, /**
|
|
||||||
* Retrieves the upload URL from the form's attributes.
|
|
||||||
*
|
|
||||||
* @returns {string | null} The upload URL, or null if not found.
|
|
||||||
*/
|
|
||||||
H = function() {
|
|
||||||
if (this.elForm) {
|
|
||||||
const e = this.elForm.getAttribute("action"), i = this.elForm.getAttribute("data-upload-url");
|
|
||||||
if (i)
|
|
||||||
return i;
|
|
||||||
const r = new URL(e || location.origin);
|
|
||||||
return r.pathname = r.pathname + "/file", r.toString();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, /**
|
|
||||||
* Handles the form's change event, adding files to the pending files list.
|
|
||||||
*
|
|
||||||
* @param {Event} ev - The change event.
|
|
||||||
*/
|
|
||||||
q = function(e) {
|
|
||||||
const i = e.target;
|
|
||||||
if (i && i.type === "file") {
|
|
||||||
const r = i.files;
|
|
||||||
if (r != null && r.length)
|
|
||||||
for (const o of r)
|
|
||||||
this.addFile(i.name, o);
|
|
||||||
}
|
|
||||||
}, /**
|
|
||||||
* Handles the form's submit event, preventing submission until all pending files are uploaded.
|
|
||||||
*
|
|
||||||
* @param {SubmitEvent} ev - The submit event.
|
|
||||||
*/
|
|
||||||
O = function(e) {
|
|
||||||
const i = e.target;
|
|
||||||
i != null && i.hasAttribute(
|
|
||||||
"data-code-challenge-form"
|
|
||||||
) || this.pendingFiles.length && (e.preventDefault(), e.stopPropagation(), this.uploadPendingFiles());
|
|
||||||
}, A = async function(e, i) {
|
|
||||||
const r = u(this, c, H).call(this);
|
|
||||||
if (!r)
|
|
||||||
throw new Error("Upload url not specified.");
|
|
||||||
const o = {
|
|
||||||
"content-type": "application/json"
|
|
||||||
};
|
|
||||||
i && (o.authorization = "Altcha payload=" + i);
|
|
||||||
const a = await fetch(r, {
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: e.file.name || "file",
|
|
||||||
size: e.file.size,
|
|
||||||
type: e.file.type || "application/octet-stream"
|
|
||||||
}),
|
|
||||||
credentials: "include",
|
|
||||||
headers: o,
|
|
||||||
method: "POST"
|
|
||||||
});
|
|
||||||
if (a.status === 401)
|
|
||||||
return u(this, c, $).call(this, a, e);
|
|
||||||
if (a.status !== 200)
|
|
||||||
throw new Error(`Unexpected server response ${a.status}.`);
|
|
||||||
const s = await a.json();
|
|
||||||
let l = e.file;
|
|
||||||
if (s.encrypted && s.encryptionPublicKey) {
|
|
||||||
const d = await z(s.encryptionPublicKey), M = await new Response(
|
|
||||||
new ReadableStream({
|
|
||||||
async start(K) {
|
|
||||||
const Y = e.file.stream().getReader();
|
|
||||||
for (; ; ) {
|
|
||||||
const { done: D, value: V } = await Y.read();
|
|
||||||
if (D)
|
|
||||||
break;
|
|
||||||
K.enqueue(V);
|
|
||||||
}
|
|
||||||
K.close();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
).arrayBuffer();
|
|
||||||
l = await me(d, new Uint8Array(M));
|
|
||||||
}
|
|
||||||
return e.uploadSize = l instanceof Uint8Array ? l.byteLength : e.file.size, await u(this, c, G).call(this, s.uploadUrl, e, l, {
|
|
||||||
"content-type": e.file.type || "application/octet-stream"
|
|
||||||
}), s.finalizeUrl && await u(this, c, B).call(this, s.finalizeUrl, e.uploadSize), e.fileId = s.fileId, e.resolve({
|
|
||||||
encrypted: s.encrypted,
|
|
||||||
fileId: s.fileId
|
|
||||||
}), e.promise;
|
|
||||||
}, $ = async function(e, i) {
|
|
||||||
var r;
|
|
||||||
try {
|
|
||||||
const o = e.headers.get("www-authenticate"), a = (r = o == null ? void 0 : o.match(/challenge=(.*),/)) == null ? void 0 : r[1];
|
|
||||||
if (!a)
|
|
||||||
throw new Error(
|
|
||||||
"Unable to retrieve altcha challenge from www-authenticate header."
|
|
||||||
);
|
|
||||||
const s = JSON.parse(a);
|
|
||||||
if (s && "challenge" in s) {
|
|
||||||
const { solution: l } = await this.context.solve(s);
|
|
||||||
if (l && "number" in l)
|
|
||||||
return u(this, c, A).call(this, i, btoa(
|
|
||||||
JSON.stringify({
|
|
||||||
...s,
|
|
||||||
number: l.number
|
|
||||||
})
|
|
||||||
));
|
|
||||||
throw new Error("Invalid challenge solution.");
|
|
||||||
}
|
|
||||||
} catch (o) {
|
|
||||||
throw this.context.log(o), new Error("Unable to solve altcha challenge for upload.");
|
|
||||||
}
|
|
||||||
}, B = async function(e, i) {
|
|
||||||
const r = await fetch(e, {
|
|
||||||
body: JSON.stringify({
|
|
||||||
uploadedBytes: i
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json"
|
|
||||||
},
|
|
||||||
method: "POST"
|
|
||||||
});
|
|
||||||
if (r.status > 204)
|
|
||||||
throw new Error(`Unexpected server response ${r.status}.`);
|
|
||||||
return !0;
|
|
||||||
}, G = async function(e, i, r, o = {}) {
|
|
||||||
var a;
|
|
||||||
return e = new URL(
|
|
||||||
e,
|
|
||||||
((a = this.elForm) == null ? void 0 : a.getAttribute("action")) || location.origin
|
|
||||||
).toString(), new Promise((s, l) => {
|
|
||||||
const d = new XMLHttpRequest();
|
|
||||||
i.controller.signal.addEventListener("abort", () => {
|
|
||||||
d.abort();
|
|
||||||
}), d.upload.addEventListener("progress", (y) => {
|
|
||||||
i.setProgress(y.loaded), u(this, c, U).call(this);
|
|
||||||
}), d.addEventListener("error", (y) => {
|
|
||||||
l(new Error("Upload failed."));
|
|
||||||
}), d.addEventListener("load", () => {
|
|
||||||
d.status >= 400 ? l(new Error(`Server responded with ${d.status}`)) : s(void 0);
|
|
||||||
}), d.open("PUT", e);
|
|
||||||
for (const y in o)
|
|
||||||
d.setRequestHeader(y, o[y]);
|
|
||||||
d.send(r);
|
|
||||||
});
|
|
||||||
}, p(_, "pluginName", "upload");
|
|
||||||
class ge {
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of UploadHandle.
|
* Creates an instance of UploadHandle.
|
||||||
*
|
*
|
||||||
* @param {string} fieldName - The name of the field associated with the file upload.
|
* @param {string} fieldName - The name of the field associated with the file upload.
|
||||||
* @param {File} file - The file to be uploaded.
|
* @param {File} file - The file to be uploaded.
|
||||||
*/
|
*/
|
||||||
constructor(n, e) {
|
constructor(e, t) {
|
||||||
p(this, "controller", new AbortController());
|
this.fieldName = e, this.file = t, this.uploadSize = this.file.size, this.promise = new Promise((r, i) => {
|
||||||
p(this, "promise");
|
this.resolve = r, this.reject = i;
|
||||||
p(this, "fileId");
|
|
||||||
p(this, "loaded", 0);
|
|
||||||
p(this, "progress", 0);
|
|
||||||
p(this, "uploadSize", 0);
|
|
||||||
p(this, "resolve");
|
|
||||||
p(this, "reject");
|
|
||||||
this.fieldName = n, this.file = e, this.uploadSize = this.file.size, this.promise = new Promise((i, r) => {
|
|
||||||
this.resolve = i, this.reject = r;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
controller = new AbortController();
|
||||||
|
promise;
|
||||||
|
fileId;
|
||||||
|
loaded = 0;
|
||||||
|
progress = 0;
|
||||||
|
uploadSize = 0;
|
||||||
|
resolve;
|
||||||
|
reject;
|
||||||
/**
|
/**
|
||||||
* Aborts the file upload by invoking the AbortController's abort method.
|
* Aborts the file upload by invoking the AbortController's abort method.
|
||||||
*/
|
*/
|
||||||
@@ -465,11 +493,11 @@ class ge {
|
|||||||
*
|
*
|
||||||
* @param {number} loaded - The number of bytes that have been uploaded.
|
* @param {number} loaded - The number of bytes that have been uploaded.
|
||||||
*/
|
*/
|
||||||
setProgress(n) {
|
setProgress(e) {
|
||||||
this.loaded = n, this.progress = this.file.size && n ? Math.min(1, n / this.file.size) : 0;
|
this.loaded = e, this.progress = this.file.size && e ? Math.min(1, e / this.file.size) : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
P.register(_);
|
U.register(Y);
|
||||||
export {
|
export {
|
||||||
_ as PluginUpload
|
Y as PluginUpload
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "altcha",
|
"name": "altcha",
|
||||||
"version": "2.2.3",
|
"version": "2.2.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "altcha",
|
"name": "altcha",
|
||||||
"version": "2.2.3",
|
"version": "2.2.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@altcha/crypto": "^0.0.1"
|
"@altcha/crypto": "^0.0.1"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "altcha",
|
"name": "altcha",
|
||||||
"description": "Privacy-first CAPTCHA widget, compliant with global regulations (GDPR/HIPAA/CCPA/LGDP/DPDPA/PIPL) and WCAG accessible. No tracking, self-verifying.",
|
"description": "Privacy-first CAPTCHA widget, compliant with global regulations (GDPR/HIPAA/CCPA/LGDP/DPDPA/PIPL) and WCAG accessible. No tracking, self-verifying.",
|
||||||
"version": "2.2.3",
|
"version": "2.2.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Daniel Regeci",
|
"name": "Daniel Regeci",
|
||||||
|
|||||||
Reference in New Issue
Block a user