1
|
/**
|
2
|
* This file is part of Haketilo.
|
3
|
*
|
4
|
* Function: Driving the site payload creation form.
|
5
|
*
|
6
|
* Copyright (C) 2022 Wojtek Kosior
|
7
|
*
|
8
|
* This program is free software: you can redistribute it and/or modify
|
9
|
* it under the terms of the GNU General Public License as published by
|
10
|
* the Free Software Foundation, either version 3 of the License, or
|
11
|
* (at your option) any later version.
|
12
|
*
|
13
|
* This program is distributed in the hope that it will be useful,
|
14
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
* GNU General Public License for more details.
|
17
|
*
|
18
|
* As additional permission under GNU GPL version 3 section 7, you
|
19
|
* may distribute forms of that code without the copy of the GNU
|
20
|
* GPL normally required by section 4, provided you include this
|
21
|
* license notice and, in case of non-source distribution, a URL
|
22
|
* through which recipients can access the Corresponding Source.
|
23
|
* If you modify file(s) with this exception, you may extend this
|
24
|
* exception to your version of the file(s), but you are not
|
25
|
* obligated to do so. If you do not wish to do so, delete this
|
26
|
* exception statement from your version.
|
27
|
*
|
28
|
* As a special exception to the GPL, any HTML file which merely
|
29
|
* makes function calls to this code, and for that purpose
|
30
|
* includes it by reference shall be deemed a separate work for
|
31
|
* copyright law purposes. If you modify this code, you may extend
|
32
|
* this exception to your version of the code, but you are not
|
33
|
* obligated to do so. If you do not wish to do so, delete this
|
34
|
* exception statement from your version.
|
35
|
*
|
36
|
* You should have received a copy of the GNU General Public License
|
37
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
38
|
*
|
39
|
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's
|
40
|
* license. Although I request that you do not make use of this code in a
|
41
|
* proprietary program, I am not going to enforce this in court.
|
42
|
*/
|
43
|
|
44
|
#IMPORT html/dialog.js
|
45
|
#IMPORT common/indexeddb.js AS haketilodb
|
46
|
|
47
|
#FROM html/DOM_helpers.js IMPORT clone_template
|
48
|
#FROM common/sha256.js IMPORT sha256 AS compute_sha256
|
49
|
#FROM common/patterns.js IMPORT validate_normalize_url_pattern, \
|
50
|
patterns_doc_url
|
51
|
|
52
|
/* https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid */
|
53
|
/* This is a helper function used by uuidv4(). */
|
54
|
function uuid_replace_num(num)
|
55
|
{
|
56
|
const randbyte = crypto.getRandomValues(new Uint8Array(1))[0];
|
57
|
return (num ^ randbyte & 15 >> num / 4).toString(16);
|
58
|
}
|
59
|
|
60
|
/* Generate a new UUID. */
|
61
|
function uuidv4()
|
62
|
{
|
63
|
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid_replace_num);
|
64
|
}
|
65
|
|
66
|
function collect_form_data(form_ctx)
|
67
|
{
|
68
|
const identifier_nonprepended = form_ctx.identifier.value;
|
69
|
if (!identifier_nonprepended)
|
70
|
throw "The 'identifier' field is required!";
|
71
|
if (!/[-a-zA-Z]+/.test(identifier_nonprepended))
|
72
|
throw "Identifier may only contain digits 0-9, lowercase letters a-z and hyphens '-'!"
|
73
|
const identifier = `local-${identifier_nonprepended}`;
|
74
|
|
75
|
const long_name = form_ctx.long_name.value || identifier_nonprepended;
|
76
|
|
77
|
const description = form_ctx.description.value;
|
78
|
|
79
|
const url_patterns = form_ctx.patterns.value.split("\n").filter(i => i);
|
80
|
if (url_patterns.length === 0)
|
81
|
throw "The 'URL patterns' field is required!";
|
82
|
|
83
|
const payloads = {};
|
84
|
|
85
|
for (let pattern of url_patterns) {
|
86
|
pattern = validate_normalize_url_pattern(pattern);
|
87
|
|
88
|
if (pattern in payloads)
|
89
|
throw `Pattern '${pattern}' specified multiple times!`;
|
90
|
|
91
|
payloads[pattern] = {identifier};
|
92
|
}
|
93
|
|
94
|
const script = form_ctx.script.value;
|
95
|
if (!script)
|
96
|
throw "The 'script' field is required!";
|
97
|
const sha256 = compute_sha256(script);
|
98
|
|
99
|
const resource = {
|
100
|
$schema: "https://hydrilla.koszko.org/schemas/api_resource_description-1.0.1.schema.json",
|
101
|
source_name: identifier,
|
102
|
source_copyright: [],
|
103
|
type: "resource",
|
104
|
identifier,
|
105
|
long_name,
|
106
|
uuid: uuidv4(),
|
107
|
version: [1],
|
108
|
revision: 1,
|
109
|
description,
|
110
|
dependencies: [],
|
111
|
scripts: [{file: "payload.js", sha256}]
|
112
|
};
|
113
|
|
114
|
const mapping = {
|
115
|
$schema: "https://hydrilla.koszko.org/schemas/api_mapping_description-1.0.1.schema.json",
|
116
|
source_name: identifier,
|
117
|
source_copyright: [],
|
118
|
type: "mapping",
|
119
|
identifier,
|
120
|
long_name,
|
121
|
uuid: uuidv4(),
|
122
|
version: [1],
|
123
|
description,
|
124
|
payloads
|
125
|
};
|
126
|
|
127
|
return {identifier, resource, mapping, files_by_sha256: {[sha256]: script}};
|
128
|
}
|
129
|
|
130
|
function clear_form(form_ctx)
|
131
|
{
|
132
|
form_ctx.identifier.value = "";
|
133
|
form_ctx.long_name.value = "";
|
134
|
form_ctx.description.value = "";
|
135
|
form_ctx.patterns.value = "https://example.com/***";
|
136
|
form_ctx.script.value = `console.log("Hello, World!");`;
|
137
|
}
|
138
|
|
139
|
async function save_payload(saving)
|
140
|
{
|
141
|
const db = await haketilodb.get();
|
142
|
const tx_starter = haketilodb.start_items_transaction;
|
143
|
const files = {sha256: saving.files_by_sha256};
|
144
|
const tx_ctx = await tx_starter(["resource", "mapping"], files);
|
145
|
|
146
|
for (const type of ["resource", "mapping"]) {
|
147
|
if (!saving[`override_${type}`] &&
|
148
|
(await haketilodb.idb_get(tx_ctx.transaction, type,
|
149
|
saving.identifier))) {
|
150
|
saving.ask_override = type;
|
151
|
return;
|
152
|
}
|
153
|
}
|
154
|
|
155
|
await haketilodb.save_item(saving.resource, tx_ctx);
|
156
|
await haketilodb.save_item(saving.mapping, tx_ctx);
|
157
|
|
158
|
return haketilodb.finalize_transaction(tx_ctx);
|
159
|
}
|
160
|
|
161
|
function override_question(saving)
|
162
|
{
|
163
|
return saving.ask_override === "resource" ?
|
164
|
`Resource '${saving.identifier}' already exists. Override?` :
|
165
|
`Mapping '${saving.identifier}' already exists. Override?`;
|
166
|
}
|
167
|
|
168
|
async function create_clicked(form_ctx)
|
169
|
{
|
170
|
if (form_ctx.dialog_ctx.shown)
|
171
|
return;
|
172
|
|
173
|
try {
|
174
|
var saving = collect_form_data(form_ctx);
|
175
|
} catch(e) {
|
176
|
dialog.error(form_ctx.dialog_ctx, e);
|
177
|
return;
|
178
|
}
|
179
|
|
180
|
dialog.loader(form_ctx.dialog_ctx, "Saving payload...");
|
181
|
|
182
|
try {
|
183
|
do {
|
184
|
if (saving.ask_override) {
|
185
|
const override_prom = dialog.ask(form_ctx.dialog_ctx,
|
186
|
override_question(saving));
|
187
|
dialog.loader(form_ctx.dialog_ctx, "Saving payload...");
|
188
|
dialog.close(form_ctx.dialog_ctx);
|
189
|
if (!(await override_prom))
|
190
|
throw "Saving would override existing data.";
|
191
|
|
192
|
saving[`override_${saving.ask_override}`] = true;
|
193
|
delete saving.ask_override;
|
194
|
}
|
195
|
|
196
|
await save_payload(saving);
|
197
|
} while (saving.ask_override);
|
198
|
|
199
|
dialog.info(form_ctx.dialog_ctx, "Successfully saved payload!");
|
200
|
clear_form(form_ctx);
|
201
|
} catch(e) {
|
202
|
console.error(e);
|
203
|
dialog.error(form_ctx.dialog_ctx, "Failed to save payload :(");
|
204
|
}
|
205
|
|
206
|
dialog.close(form_ctx.dialog_ctx);
|
207
|
}
|
208
|
|
209
|
function on_show_hide(form_ctx, what_to_show)
|
210
|
{
|
211
|
for (const item_id of ["form", "dialog"]) {
|
212
|
const action = item_id === what_to_show ? "remove" : "add";
|
213
|
form_ctx[`${item_id}_container`].classList[action]("hide");
|
214
|
}
|
215
|
}
|
216
|
|
217
|
function payload_create_form()
|
218
|
{
|
219
|
const form_ctx = clone_template("payload_create");
|
220
|
|
221
|
form_ctx.dialog_ctx = dialog.make(() => on_show_hide(form_ctx, "dialog"),
|
222
|
() => on_show_hide(form_ctx, "form"));
|
223
|
form_ctx.dialog_container.prepend(form_ctx.dialog_ctx.main_div);
|
224
|
|
225
|
form_ctx.patterns_link.href = patterns_doc_url;
|
226
|
form_ctx.create_but.addEventListener("click",
|
227
|
() => create_clicked(form_ctx));
|
228
|
|
229
|
return form_ctx;
|
230
|
}
|
231
|
#EXPORT payload_create_form
|