altcha 2.2.2 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/altcha.d.ts +39 -19
- package/dist/altcha.i18n.d.ts +39 -19
- package/dist/altcha.i18n.js +1688 -1729
- package/dist/altcha.i18n.umd.js +4 -4
- package/dist/altcha.js +1625 -1666
- package/dist/altcha.umd.cjs +4 -4
- package/dist_external/altcha.css +1 -1
- package/dist_external/altcha.d.ts +39 -19
- package/dist_external/altcha.js +1685 -1724
- package/dist_external/altcha.umd.cjs +3 -3
- package/dist_external/worker.js +1 -1
- package/dist_plugins/analytics.js +68 -82
- package/dist_plugins/analytics.umd.cjs +1 -1
- package/dist_plugins/obfuscation.js +70 -79
- package/dist_plugins/obfuscation.umd.cjs +1 -1
- package/dist_plugins/upload.js +360 -332
- package/dist_plugins/upload.umd.cjs +4 -4
- package/package.json +7 -4
package/dist_plugins/upload.js
CHANGED
|
@@ -1,167 +1,163 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const u = {
|
|
2
|
+
generateKey: v,
|
|
3
|
+
exportKey: S,
|
|
4
|
+
importKey: x,
|
|
5
|
+
decrypt: C,
|
|
6
|
+
encrypt: L
|
|
4
7
|
};
|
|
5
|
-
|
|
6
|
-
var p = (t, n, e) => X(t, typeof n != "symbol" ? n + "" : n, e), S = (t, n, e) => n.has(t) || v("Cannot " + e);
|
|
7
|
-
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);
|
|
8
|
-
var u = (t, n, e) => (S(t, n, "access private method"), e);
|
|
9
|
-
const E = {
|
|
10
|
-
generateKey: Q,
|
|
11
|
-
exportKey: W,
|
|
12
|
-
importKey: Z,
|
|
13
|
-
decrypt: te,
|
|
14
|
-
encrypt: ee
|
|
15
|
-
};
|
|
16
|
-
async function Q(t = 256) {
|
|
8
|
+
async function v(n = 256) {
|
|
17
9
|
return crypto.subtle.generateKey({
|
|
18
10
|
name: "AES-GCM",
|
|
19
|
-
length:
|
|
11
|
+
length: n
|
|
20
12
|
}, !0, ["encrypt", "decrypt"]);
|
|
21
13
|
}
|
|
22
|
-
async function
|
|
23
|
-
return new Uint8Array(await crypto.subtle.exportKey("raw",
|
|
14
|
+
async function S(n) {
|
|
15
|
+
return new Uint8Array(await crypto.subtle.exportKey("raw", n));
|
|
24
16
|
}
|
|
25
|
-
async function
|
|
26
|
-
return crypto.subtle.importKey("raw",
|
|
17
|
+
async function x(n) {
|
|
18
|
+
return crypto.subtle.importKey("raw", n, {
|
|
27
19
|
name: "AES-GCM"
|
|
28
20
|
}, !0, ["encrypt", "decrypt"]);
|
|
29
21
|
}
|
|
30
|
-
async function
|
|
31
|
-
const
|
|
22
|
+
async function L(n, e, t = 16) {
|
|
23
|
+
const r = crypto.getRandomValues(new Uint8Array(t));
|
|
32
24
|
return {
|
|
33
25
|
encrypted: new Uint8Array(await crypto.subtle.encrypt({
|
|
34
26
|
name: "AES-GCM",
|
|
35
|
-
iv:
|
|
36
|
-
},
|
|
37
|
-
iv:
|
|
27
|
+
iv: r
|
|
28
|
+
}, n, e)),
|
|
29
|
+
iv: r
|
|
38
30
|
};
|
|
39
31
|
}
|
|
40
|
-
async function
|
|
32
|
+
async function C(n, e, t) {
|
|
41
33
|
return new Uint8Array(await crypto.subtle.decrypt({
|
|
42
34
|
name: "AES-GCM",
|
|
43
|
-
iv:
|
|
44
|
-
},
|
|
35
|
+
iv: t
|
|
36
|
+
}, n, e));
|
|
45
37
|
}
|
|
46
|
-
function
|
|
47
|
-
return
|
|
38
|
+
function I(n, e = !1) {
|
|
39
|
+
return e && (n = n.replace(/_/g, "/").replace(/-/g, "+") + "=".repeat(3 - (3 + n.length) % 4)), Uint8Array.from(atob(n), (t) => t.charCodeAt(0));
|
|
48
40
|
}
|
|
49
|
-
function
|
|
50
|
-
const
|
|
51
|
-
return
|
|
41
|
+
function h(n, e = !1) {
|
|
42
|
+
const t = btoa(String.fromCharCode(...n));
|
|
43
|
+
return e ? t.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") : t;
|
|
52
44
|
}
|
|
53
|
-
function
|
|
54
|
-
let
|
|
55
|
-
for (;
|
|
56
|
-
|
|
57
|
-
`,
|
|
58
|
-
return
|
|
45
|
+
function f(n, e = 80) {
|
|
46
|
+
let t = "";
|
|
47
|
+
for (; n.length > 0; )
|
|
48
|
+
t += n.slice(0, e) + `
|
|
49
|
+
`, n = n.slice(e);
|
|
50
|
+
return t;
|
|
59
51
|
}
|
|
60
|
-
function
|
|
61
|
-
return
|
|
52
|
+
function g(n) {
|
|
53
|
+
return I(n.split(/\r?\n/).filter((e) => !e.startsWith("-----")).join(""));
|
|
62
54
|
}
|
|
63
|
-
const
|
|
64
|
-
generateKeyPair:
|
|
65
|
-
encrypt:
|
|
66
|
-
decrypt:
|
|
67
|
-
exportPrivateKey:
|
|
68
|
-
exportPrivateKeyPem:
|
|
69
|
-
exportPublicKey:
|
|
70
|
-
exportPublicKeyPem:
|
|
71
|
-
exportPublicKeyFromPrivateKey:
|
|
72
|
-
importPrivateKey:
|
|
73
|
-
importPrivateKeyPem:
|
|
74
|
-
importPublicKey:
|
|
75
|
-
importPublicKeyPem:
|
|
55
|
+
const l = "RSA-OAEP", p = "SHA-256", N = 2048, z = new Uint8Array([1, 0, 1]), T = {
|
|
56
|
+
generateKeyPair: _,
|
|
57
|
+
encrypt: R,
|
|
58
|
+
decrypt: j,
|
|
59
|
+
exportPrivateKey: m,
|
|
60
|
+
exportPrivateKeyPem: H,
|
|
61
|
+
exportPublicKey: d,
|
|
62
|
+
exportPublicKeyPem: k,
|
|
63
|
+
exportPublicKeyFromPrivateKey: O,
|
|
64
|
+
importPrivateKey: E,
|
|
65
|
+
importPrivateKeyPem: q,
|
|
66
|
+
importPublicKey: w,
|
|
67
|
+
importPublicKeyPem: b
|
|
76
68
|
};
|
|
77
|
-
async function
|
|
69
|
+
async function _() {
|
|
78
70
|
return crypto.subtle.generateKey({
|
|
79
|
-
name:
|
|
80
|
-
modulusLength:
|
|
81
|
-
publicExponent:
|
|
82
|
-
hash:
|
|
71
|
+
name: l,
|
|
72
|
+
modulusLength: N,
|
|
73
|
+
publicExponent: z,
|
|
74
|
+
hash: p
|
|
83
75
|
}, !0, ["encrypt", "decrypt"]);
|
|
84
76
|
}
|
|
85
|
-
async function
|
|
77
|
+
async function R(n, e) {
|
|
86
78
|
return new Uint8Array(await crypto.subtle.encrypt({
|
|
87
|
-
name:
|
|
88
|
-
},
|
|
79
|
+
name: l
|
|
80
|
+
}, n, e));
|
|
89
81
|
}
|
|
90
|
-
async function
|
|
82
|
+
async function j(n, e) {
|
|
91
83
|
return new Uint8Array(await crypto.subtle.decrypt({
|
|
92
|
-
name:
|
|
93
|
-
},
|
|
84
|
+
name: l
|
|
85
|
+
}, n, e));
|
|
94
86
|
}
|
|
95
|
-
async function
|
|
96
|
-
return new Uint8Array(await crypto.subtle.exportKey("spki",
|
|
87
|
+
async function d(n) {
|
|
88
|
+
return new Uint8Array(await crypto.subtle.exportKey("spki", n));
|
|
97
89
|
}
|
|
98
|
-
async function
|
|
99
|
-
return new Uint8Array(await crypto.subtle.exportKey("pkcs8",
|
|
90
|
+
async function m(n) {
|
|
91
|
+
return new Uint8Array(await crypto.subtle.exportKey("pkcs8", n));
|
|
100
92
|
}
|
|
101
|
-
async function
|
|
93
|
+
async function k(n) {
|
|
102
94
|
return `-----BEGIN PUBLIC KEY-----
|
|
103
|
-
` +
|
|
95
|
+
` + f(h(await d(n)), 64) + "-----END PUBLIC KEY-----";
|
|
104
96
|
}
|
|
105
|
-
async function
|
|
97
|
+
async function H(n) {
|
|
106
98
|
return `-----BEGIN PRIVATE KEY-----
|
|
107
|
-
` +
|
|
99
|
+
` + f(h(await m(n)), 64) + "-----END PRIVATE KEY-----";
|
|
108
100
|
}
|
|
109
|
-
async function
|
|
110
|
-
return crypto.subtle.importKey("spki",
|
|
111
|
-
name:
|
|
112
|
-
hash:
|
|
101
|
+
async function w(n) {
|
|
102
|
+
return crypto.subtle.importKey("spki", n, {
|
|
103
|
+
name: l,
|
|
104
|
+
hash: p
|
|
113
105
|
}, !0, ["encrypt"]);
|
|
114
106
|
}
|
|
115
|
-
async function
|
|
116
|
-
return
|
|
107
|
+
async function b(n) {
|
|
108
|
+
return w(g(n));
|
|
117
109
|
}
|
|
118
|
-
async function
|
|
119
|
-
return crypto.subtle.importKey("pkcs8",
|
|
120
|
-
name:
|
|
121
|
-
hash:
|
|
110
|
+
async function E(n) {
|
|
111
|
+
return crypto.subtle.importKey("pkcs8", n, {
|
|
112
|
+
name: l,
|
|
113
|
+
hash: p
|
|
122
114
|
}, !0, ["decrypt"]);
|
|
123
115
|
}
|
|
124
|
-
async function
|
|
125
|
-
return
|
|
116
|
+
async function q(n) {
|
|
117
|
+
return E(g(n));
|
|
126
118
|
}
|
|
127
|
-
async function
|
|
128
|
-
const
|
|
129
|
-
delete
|
|
130
|
-
const
|
|
131
|
-
name:
|
|
132
|
-
hash:
|
|
119
|
+
async function O(n) {
|
|
120
|
+
const e = await crypto.subtle.exportKey("jwk", n);
|
|
121
|
+
delete e.d, delete e.dp, delete e.dq, delete e.q, delete e.qi, e.key_ops = ["encrypt"];
|
|
122
|
+
const t = await crypto.subtle.importKey("jwk", e, {
|
|
123
|
+
name: l,
|
|
124
|
+
hash: p
|
|
133
125
|
}, !0, ["encrypt"]);
|
|
134
|
-
return
|
|
126
|
+
return d(t);
|
|
135
127
|
}
|
|
136
|
-
const
|
|
137
|
-
async function
|
|
138
|
-
const { aesIVLength:
|
|
128
|
+
const $ = new Uint8Array([1, 0, 1]), B = 256, G = 16;
|
|
129
|
+
async function M(n, e, t = {}) {
|
|
130
|
+
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));
|
|
139
131
|
return new Uint8Array([
|
|
140
|
-
|
|
141
|
-
...new Uint8Array([
|
|
142
|
-
...new Uint8Array([
|
|
143
|
-
...
|
|
144
|
-
...
|
|
145
|
-
...
|
|
132
|
+
...$,
|
|
133
|
+
...new Uint8Array([c.length]),
|
|
134
|
+
...new Uint8Array([a.length]),
|
|
135
|
+
...c,
|
|
136
|
+
...a,
|
|
137
|
+
...o
|
|
146
138
|
]);
|
|
147
139
|
}
|
|
148
|
-
class
|
|
140
|
+
class U {
|
|
149
141
|
/**
|
|
150
142
|
* Constructs a new instance of the Plugin.
|
|
151
143
|
*
|
|
152
144
|
* @param {PluginContext} context - The context provided to the plugin, containing necessary configurations and dependencies.
|
|
153
145
|
*/
|
|
154
|
-
constructor(
|
|
155
|
-
this.context =
|
|
146
|
+
constructor(e) {
|
|
147
|
+
this.context = e;
|
|
156
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* A distinct name of the plugin. Every plugin must have it's own name.
|
|
151
|
+
*/
|
|
152
|
+
static pluginName;
|
|
157
153
|
/**
|
|
158
154
|
* Registers a plugin class in the global `altchaPlugins` array.
|
|
159
155
|
* Ensures the plugin is added only once.
|
|
160
156
|
*
|
|
161
157
|
* @param {new(context: PluginContext) => Plugin} cls - The plugin class to register.
|
|
162
158
|
*/
|
|
163
|
-
static register(
|
|
164
|
-
typeof globalThis.altchaPlugins != "object" && (globalThis.altchaPlugins = []), globalThis.altchaPlugins.includes(
|
|
159
|
+
static register(e) {
|
|
160
|
+
typeof globalThis.altchaPlugins != "object" && (globalThis.altchaPlugins = []), globalThis.altchaPlugins.includes(e) || globalThis.altchaPlugins.push(e);
|
|
165
161
|
}
|
|
166
162
|
/**
|
|
167
163
|
* Clean up resources when the plugin is destroyed.
|
|
@@ -175,7 +171,7 @@ class P {
|
|
|
175
171
|
*
|
|
176
172
|
* @param {string | null} err - The error message or `null` if there's no error.
|
|
177
173
|
*/
|
|
178
|
-
onErrorChange(
|
|
174
|
+
onErrorChange(e) {
|
|
179
175
|
}
|
|
180
176
|
/**
|
|
181
177
|
* Callback triggered when the plugin state changes.
|
|
@@ -183,29 +179,23 @@ class P {
|
|
|
183
179
|
*
|
|
184
180
|
* @param {State} state - The new state of the plugin.
|
|
185
181
|
*/
|
|
186
|
-
onStateChange(
|
|
182
|
+
onStateChange(e) {
|
|
187
183
|
}
|
|
188
184
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
185
|
+
class Y extends U {
|
|
186
|
+
static pluginName = "upload";
|
|
187
|
+
pendingFiles = [];
|
|
188
|
+
uploadHandles = [];
|
|
189
|
+
elForm;
|
|
190
|
+
#e = this.#c.bind(this);
|
|
191
|
+
#t = this.#l.bind(this);
|
|
195
192
|
/**
|
|
196
193
|
* Constructor initializes the plugin, setting up event listeners on the form.
|
|
197
194
|
*
|
|
198
195
|
* @param {PluginContext} context - Plugin context providing access to the element, configuration, and utility methods.
|
|
199
196
|
*/
|
|
200
197
|
constructor(e) {
|
|
201
|
-
super(e)
|
|
202
|
-
w(this, c);
|
|
203
|
-
p(this, "pendingFiles", []);
|
|
204
|
-
p(this, "uploadHandles", []);
|
|
205
|
-
p(this, "elForm");
|
|
206
|
-
w(this, m, u(this, c, q).bind(this));
|
|
207
|
-
w(this, g, u(this, c, O).bind(this));
|
|
208
|
-
this.elForm = this.context.el.closest("form"), this.elForm && (this.elForm.addEventListener("change", f(this, m)), this.elForm.addEventListener("submit", f(this, g), {
|
|
198
|
+
super(e), this.elForm = this.context.el.closest("form"), this.elForm && (this.elForm.addEventListener("change", this.#e), this.elForm.addEventListener("submit", this.#t, {
|
|
209
199
|
capture: !0
|
|
210
200
|
}));
|
|
211
201
|
}
|
|
@@ -215,245 +205,283 @@ class _ extends P {
|
|
|
215
205
|
* @param {string} fieldName - The field name associated with the file input.
|
|
216
206
|
* @param {File} file - The file to be uploaded.
|
|
217
207
|
*/
|
|
218
|
-
addFile(e,
|
|
219
|
-
this.pendingFiles.find(([r,
|
|
208
|
+
addFile(e, t) {
|
|
209
|
+
this.pendingFiles.find(([r, i]) => r === e && i === t) || this.pendingFiles.push([e, t]);
|
|
220
210
|
}
|
|
221
211
|
/**
|
|
222
212
|
* Cleans up event listeners and other resources when the plugin is destroyed.
|
|
223
213
|
*/
|
|
224
214
|
destroy() {
|
|
225
|
-
this.elForm && (this.elForm.removeEventListener("change",
|
|
215
|
+
this.elForm && (this.elForm.removeEventListener("change", this.#e), this.elForm.removeEventListener("submit", this.#t));
|
|
226
216
|
}
|
|
227
217
|
/**
|
|
228
218
|
* Uploads all pending files in the list.
|
|
229
219
|
*/
|
|
230
220
|
async uploadPendingFiles() {
|
|
231
|
-
var i;
|
|
232
221
|
const e = async () => {
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
222
|
+
const t = this.pendingFiles[0];
|
|
223
|
+
if (t && await this.#r(this.#s(t)), this.pendingFiles.length)
|
|
235
224
|
return e();
|
|
236
225
|
};
|
|
237
226
|
try {
|
|
238
227
|
await e();
|
|
239
|
-
} catch (
|
|
240
|
-
return this.context.log("upload failed",
|
|
241
|
-
error:
|
|
228
|
+
} catch (t) {
|
|
229
|
+
return this.context.log("upload failed", t), this.context.dispatch("uploaderror", {
|
|
230
|
+
error: t
|
|
242
231
|
}), !1;
|
|
243
232
|
}
|
|
244
|
-
this.pendingFiles.length === 0 && (
|
|
233
|
+
this.pendingFiles.length === 0 && (this.#i(), this.elForm?.requestSubmit());
|
|
245
234
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
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);
|
|
235
|
+
/**
|
|
236
|
+
* Adds hidden input fields to the form containing the file IDs of uploaded files.
|
|
237
|
+
*/
|
|
238
|
+
#i() {
|
|
239
|
+
const e = this.uploadHandles.reduce(
|
|
240
|
+
(t, r) => (t[r.fieldName] || (t[r.fieldName] = []), r.fileId && t[r.fieldName].push(r.fileId), t),
|
|
241
|
+
{}
|
|
242
|
+
);
|
|
243
|
+
for (const t in e) {
|
|
244
|
+
const r = document.createElement("input");
|
|
245
|
+
r.name = t, r.type = "hidden", r.value = e[t].join(","), this.elForm?.querySelector(`[name="${t}"]`)?.setAttribute("disabled", "disabled"), this.elForm?.appendChild(r);
|
|
246
|
+
}
|
|
259
247
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}, /**
|
|
276
|
-
* Dispatches a custom event when a file upload starts.
|
|
277
|
-
*
|
|
278
|
-
* @param {UploadHandle} handle - The upload handle associated with the file upload.
|
|
279
|
-
*/
|
|
280
|
-
k = function(e) {
|
|
281
|
-
this.context.dispatch("upload", { handle: e });
|
|
282
|
-
}, /**
|
|
283
|
-
* Dispatches a custom event to track the progress of ongoing file uploads.
|
|
284
|
-
*/
|
|
285
|
-
U = function() {
|
|
286
|
-
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(
|
|
287
|
-
(r, { loaded: o }) => r + o,
|
|
288
|
-
0
|
|
289
|
-
);
|
|
290
|
-
this.context.dispatch("uploadprogress", {
|
|
291
|
-
bytesLoaded: i,
|
|
292
|
-
bytesTotal: e,
|
|
293
|
-
pendingFiles: this.pendingFiles,
|
|
294
|
-
uploadHandles: this.uploadHandles
|
|
295
|
-
});
|
|
296
|
-
}, /**
|
|
297
|
-
* Retrieves the upload URL from the form's attributes.
|
|
298
|
-
*
|
|
299
|
-
* @returns {string | null} The upload URL, or null if not found.
|
|
300
|
-
*/
|
|
301
|
-
H = function() {
|
|
302
|
-
if (this.elForm) {
|
|
303
|
-
const e = this.elForm.getAttribute("action"), i = this.elForm.getAttribute("data-upload-url");
|
|
304
|
-
if (i)
|
|
305
|
-
return i;
|
|
306
|
-
const r = new URL(e || location.origin);
|
|
307
|
-
return r.pathname = r.pathname + "/file", r.toString();
|
|
248
|
+
/**
|
|
249
|
+
* Creates an upload handle for the specified pending file.
|
|
250
|
+
*
|
|
251
|
+
* @param {[string, File]} pendingFile - The field name and file to be uploaded.
|
|
252
|
+
* @returns {UploadHandle} The created upload handle.
|
|
253
|
+
* @throws Will throw an error if the upload handle cannot be created.
|
|
254
|
+
*/
|
|
255
|
+
#s(e) {
|
|
256
|
+
const t = this.pendingFiles.findIndex(
|
|
257
|
+
([i, s]) => i === e[0] && s === e[1]
|
|
258
|
+
);
|
|
259
|
+
if (t < 0)
|
|
260
|
+
throw new Error("Cannot create upload handle.");
|
|
261
|
+
const r = new D(e[0], e[1]);
|
|
262
|
+
return this.uploadHandles.push(r), this.pendingFiles.splice(t, 1), this.#o(r), this.#n(), r;
|
|
308
263
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const i = e.target;
|
|
317
|
-
if (i && i.type === "file") {
|
|
318
|
-
const r = i.files;
|
|
319
|
-
if (r != null && r.length)
|
|
320
|
-
for (const o of r)
|
|
321
|
-
this.addFile(i.name, o);
|
|
264
|
+
/**
|
|
265
|
+
* Dispatches a custom event when a file upload starts.
|
|
266
|
+
*
|
|
267
|
+
* @param {UploadHandle} handle - The upload handle associated with the file upload.
|
|
268
|
+
*/
|
|
269
|
+
#o(e) {
|
|
270
|
+
this.context.dispatch("upload", { handle: e });
|
|
322
271
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
"
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return u(this, c, $).call(this, a, e);
|
|
353
|
-
if (a.status !== 200)
|
|
354
|
-
throw new Error(`Unexpected server response ${a.status}.`);
|
|
355
|
-
const s = await a.json();
|
|
356
|
-
let l = e.file;
|
|
357
|
-
if (s.encrypted && s.encryptionPublicKey) {
|
|
358
|
-
const d = await z(s.encryptionPublicKey), M = await new Response(
|
|
359
|
-
new ReadableStream({
|
|
360
|
-
async start(K) {
|
|
361
|
-
const Y = e.file.stream().getReader();
|
|
362
|
-
for (; ; ) {
|
|
363
|
-
const { done: D, value: V } = await Y.read();
|
|
364
|
-
if (D)
|
|
365
|
-
break;
|
|
366
|
-
K.enqueue(V);
|
|
367
|
-
}
|
|
368
|
-
K.close();
|
|
369
|
-
}
|
|
370
|
-
})
|
|
371
|
-
).arrayBuffer();
|
|
372
|
-
l = await me(d, new Uint8Array(M));
|
|
272
|
+
/**
|
|
273
|
+
* Dispatches a custom event to track the progress of ongoing file uploads.
|
|
274
|
+
*/
|
|
275
|
+
#n() {
|
|
276
|
+
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(
|
|
277
|
+
(r, { loaded: i }) => r + i,
|
|
278
|
+
0
|
|
279
|
+
);
|
|
280
|
+
this.context.dispatch("uploadprogress", {
|
|
281
|
+
bytesLoaded: t,
|
|
282
|
+
bytesTotal: e,
|
|
283
|
+
pendingFiles: this.pendingFiles,
|
|
284
|
+
uploadHandles: this.uploadHandles
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Retrieves the upload URL from the form's attributes.
|
|
289
|
+
*
|
|
290
|
+
* @returns {string | null} The upload URL, or null if not found.
|
|
291
|
+
*/
|
|
292
|
+
#a() {
|
|
293
|
+
if (this.elForm) {
|
|
294
|
+
const e = this.elForm.getAttribute("action"), t = this.elForm.getAttribute("data-upload-url");
|
|
295
|
+
if (t)
|
|
296
|
+
return t;
|
|
297
|
+
const r = new URL(e || location.origin);
|
|
298
|
+
return r.pathname = r.pathname + "/file", r.toString();
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
373
301
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
"Unable to retrieve altcha challenge from www-authenticate header."
|
|
387
|
-
);
|
|
388
|
-
const s = JSON.parse(a);
|
|
389
|
-
if (s && "challenge" in s) {
|
|
390
|
-
const { solution: l } = await this.context.solve(s);
|
|
391
|
-
if (l && "number" in l)
|
|
392
|
-
return u(this, c, A).call(this, i, btoa(
|
|
393
|
-
JSON.stringify({
|
|
394
|
-
...s,
|
|
395
|
-
number: l.number
|
|
396
|
-
})
|
|
397
|
-
));
|
|
398
|
-
throw new Error("Invalid challenge solution.");
|
|
302
|
+
/**
|
|
303
|
+
* Handles the form's change event, adding files to the pending files list.
|
|
304
|
+
*
|
|
305
|
+
* @param {Event} ev - The change event.
|
|
306
|
+
*/
|
|
307
|
+
#c(e) {
|
|
308
|
+
const t = e.target;
|
|
309
|
+
if (t && t.type === "file") {
|
|
310
|
+
const r = t.files;
|
|
311
|
+
if (r?.length)
|
|
312
|
+
for (const i of r)
|
|
313
|
+
this.addFile(t.name, i);
|
|
399
314
|
}
|
|
400
|
-
} catch (o) {
|
|
401
|
-
throw this.context.log(o), new Error("Unable to solve altcha challenge for upload.");
|
|
402
315
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Handles the form's submit event, preventing submission until all pending files are uploaded.
|
|
318
|
+
*
|
|
319
|
+
* @param {SubmitEvent} ev - The submit event.
|
|
320
|
+
*/
|
|
321
|
+
#l(e) {
|
|
322
|
+
e.target?.hasAttribute(
|
|
323
|
+
"data-code-challenge-form"
|
|
324
|
+
) || this.pendingFiles.length && (e.preventDefault(), e.stopPropagation(), this.uploadPendingFiles());
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Uploads a single file, handling encryption and ALTCHA challenges if necessary.
|
|
328
|
+
*
|
|
329
|
+
* @param {UploadHandle} handle - The upload handle associated with the file.
|
|
330
|
+
* @param {string} [altchaPayload] - The ALTCHA payload, if available.
|
|
331
|
+
* @returns {Promise<unknown>} A promise that resolves when the upload is complete.
|
|
332
|
+
* @throws Will throw an error if the upload fails or if an ALTCHA challenge cannot be solved.
|
|
333
|
+
*/
|
|
334
|
+
async #r(e, t) {
|
|
335
|
+
const r = this.#a();
|
|
336
|
+
if (!r)
|
|
337
|
+
throw new Error("Upload url not specified.");
|
|
338
|
+
const i = {
|
|
409
339
|
"content-type": "application/json"
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
340
|
+
};
|
|
341
|
+
t && (i.authorization = "Altcha payload=" + t);
|
|
342
|
+
const s = await fetch(r, {
|
|
343
|
+
body: JSON.stringify({
|
|
344
|
+
name: e.file.name || "file",
|
|
345
|
+
size: e.file.size,
|
|
346
|
+
type: e.file.type || "application/octet-stream"
|
|
347
|
+
}),
|
|
348
|
+
credentials: "include",
|
|
349
|
+
headers: i,
|
|
350
|
+
method: "POST"
|
|
351
|
+
});
|
|
352
|
+
if (s.status === 401)
|
|
353
|
+
return this.#p(s, e);
|
|
354
|
+
if (s.status !== 200)
|
|
355
|
+
throw new Error(`Unexpected server response ${s.status}.`);
|
|
356
|
+
const o = await s.json();
|
|
357
|
+
let a = e.file;
|
|
358
|
+
if (o.encrypted && o.encryptionPublicKey) {
|
|
359
|
+
const c = await b(o.encryptionPublicKey), P = await new Response(
|
|
360
|
+
new ReadableStream({
|
|
361
|
+
async start(y) {
|
|
362
|
+
const A = e.file.stream().getReader();
|
|
363
|
+
for (; ; ) {
|
|
364
|
+
const { done: F, value: K } = await A.read();
|
|
365
|
+
if (F)
|
|
366
|
+
break;
|
|
367
|
+
y.enqueue(K);
|
|
368
|
+
}
|
|
369
|
+
y.close();
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
).arrayBuffer();
|
|
373
|
+
a = await M(c, new Uint8Array(P));
|
|
374
|
+
}
|
|
375
|
+
return e.uploadSize = a instanceof Uint8Array ? a.byteLength : e.file.size, await this.#d(o.uploadUrl, e, a, {
|
|
376
|
+
"content-type": e.file.type || "application/octet-stream"
|
|
377
|
+
}), o.finalizeUrl && await this.#u(o.finalizeUrl, e.uploadSize), e.fileId = o.fileId, e.resolve({
|
|
378
|
+
encrypted: o.encrypted,
|
|
379
|
+
fileId: o.fileId
|
|
380
|
+
}), e.promise;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Handles ALTCHA challenges during file upload, solving the challenge and retrying the upload.
|
|
384
|
+
*
|
|
385
|
+
* @param {Response} resp - The response from the server containing the ALTCHA challenge.
|
|
386
|
+
* @param {UploadHandle} handle - The upload handle associated with the file.
|
|
387
|
+
* @returns {Promise<unknown>} A promise that resolves when the challenge is solved and the upload is complete.
|
|
388
|
+
* @throws Will throw an error if the challenge cannot be solved.
|
|
389
|
+
*/
|
|
390
|
+
async #p(e, t) {
|
|
391
|
+
try {
|
|
392
|
+
const i = e.headers.get("www-authenticate")?.match(/challenge=(.*),/)?.[1];
|
|
393
|
+
if (!i)
|
|
394
|
+
throw new Error(
|
|
395
|
+
"Unable to retrieve altcha challenge from www-authenticate header."
|
|
396
|
+
);
|
|
397
|
+
const s = JSON.parse(i);
|
|
398
|
+
if (s && "challenge" in s) {
|
|
399
|
+
const { solution: o } = await this.context.solve(s);
|
|
400
|
+
if (o && "number" in o)
|
|
401
|
+
return this.#r(
|
|
402
|
+
t,
|
|
403
|
+
btoa(
|
|
404
|
+
JSON.stringify({
|
|
405
|
+
...s,
|
|
406
|
+
number: o.number
|
|
407
|
+
})
|
|
408
|
+
)
|
|
409
|
+
);
|
|
410
|
+
throw new Error("Invalid challenge solution.");
|
|
411
|
+
}
|
|
412
|
+
} catch (r) {
|
|
413
|
+
throw this.context.log(r), new Error("Unable to solve altcha challenge for upload.");
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Finalizes the file upload by sending a request to the server.
|
|
418
|
+
*
|
|
419
|
+
* @param {string} finalizeUrl - The URL to finalize the upload.
|
|
420
|
+
* @param {number} uploadSize - The size of the uploaded file.
|
|
421
|
+
*/
|
|
422
|
+
async #u(e, t) {
|
|
423
|
+
const r = await fetch(e, {
|
|
424
|
+
body: JSON.stringify({
|
|
425
|
+
uploadedBytes: t
|
|
426
|
+
}),
|
|
427
|
+
headers: {
|
|
428
|
+
"content-type": "application/json"
|
|
429
|
+
},
|
|
430
|
+
method: "POST"
|
|
431
|
+
});
|
|
432
|
+
if (r.status > 204)
|
|
433
|
+
throw new Error(`Unexpected server response ${r.status}.`);
|
|
434
|
+
return !0;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Uploads the file's contents to the server using the PUT method.
|
|
438
|
+
*
|
|
439
|
+
* @param {string} url - The URL to upload the file to.
|
|
440
|
+
* @param {UploadHandle} handle - The upload handle associated with the file.
|
|
441
|
+
* @param {Uint8Array | File} body - The file's contents.
|
|
442
|
+
* @param {Record<string, string>} headers - Additional headers for the upload request.
|
|
443
|
+
*/
|
|
444
|
+
async #d(e, t, r, i = {}) {
|
|
445
|
+
return e = new URL(
|
|
446
|
+
e,
|
|
447
|
+
this.elForm?.getAttribute("action") || location.origin
|
|
448
|
+
).toString(), new Promise((s, o) => {
|
|
449
|
+
const a = new XMLHttpRequest();
|
|
450
|
+
t.controller.signal.addEventListener("abort", () => {
|
|
451
|
+
a.abort();
|
|
452
|
+
}), a.upload.addEventListener("progress", (c) => {
|
|
453
|
+
t.setProgress(c.loaded), this.#n();
|
|
454
|
+
}), a.addEventListener("error", (c) => {
|
|
455
|
+
o(new Error("Upload failed."));
|
|
456
|
+
}), a.addEventListener("load", () => {
|
|
457
|
+
a.status >= 400 ? o(new Error(`Server responded with ${a.status}`)) : s(void 0);
|
|
458
|
+
}), a.open("PUT", e);
|
|
459
|
+
for (const c in i)
|
|
460
|
+
a.setRequestHeader(c, i[c]);
|
|
461
|
+
a.send(r);
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
class D {
|
|
438
466
|
/**
|
|
439
467
|
* Creates an instance of UploadHandle.
|
|
440
468
|
*
|
|
441
469
|
* @param {string} fieldName - The name of the field associated with the file upload.
|
|
442
470
|
* @param {File} file - The file to be uploaded.
|
|
443
471
|
*/
|
|
444
|
-
constructor(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
p(this, "fileId");
|
|
448
|
-
p(this, "loaded", 0);
|
|
449
|
-
p(this, "progress", 0);
|
|
450
|
-
p(this, "uploadSize", 0);
|
|
451
|
-
p(this, "resolve");
|
|
452
|
-
p(this, "reject");
|
|
453
|
-
this.fieldName = n, this.file = e, this.uploadSize = this.file.size, this.promise = new Promise((i, r) => {
|
|
454
|
-
this.resolve = i, this.reject = r;
|
|
472
|
+
constructor(e, t) {
|
|
473
|
+
this.fieldName = e, this.file = t, this.uploadSize = this.file.size, this.promise = new Promise((r, i) => {
|
|
474
|
+
this.resolve = r, this.reject = i;
|
|
455
475
|
});
|
|
456
476
|
}
|
|
477
|
+
controller = new AbortController();
|
|
478
|
+
promise;
|
|
479
|
+
fileId;
|
|
480
|
+
loaded = 0;
|
|
481
|
+
progress = 0;
|
|
482
|
+
uploadSize = 0;
|
|
483
|
+
resolve;
|
|
484
|
+
reject;
|
|
457
485
|
/**
|
|
458
486
|
* Aborts the file upload by invoking the AbortController's abort method.
|
|
459
487
|
*/
|
|
@@ -465,11 +493,11 @@ class ge {
|
|
|
465
493
|
*
|
|
466
494
|
* @param {number} loaded - The number of bytes that have been uploaded.
|
|
467
495
|
*/
|
|
468
|
-
setProgress(
|
|
469
|
-
this.loaded =
|
|
496
|
+
setProgress(e) {
|
|
497
|
+
this.loaded = e, this.progress = this.file.size && e ? Math.min(1, e / this.file.size) : 0;
|
|
470
498
|
}
|
|
471
499
|
}
|
|
472
|
-
|
|
500
|
+
U.register(Y);
|
|
473
501
|
export {
|
|
474
|
-
|
|
502
|
+
Y as PluginUpload
|
|
475
503
|
};
|