Project

General

Profile

Download (7.11 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / html / payload_create.js @ 22fe27f0

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
(17-17/27)