Commit

Author:

Hash:

Timestamp:

+186 -51 +/-2 browse

Kevin Schoon [me@kevinschoon.com]

a4e44b4b5c17df8882fabe6954a2b49aeb7dd29e

Tue, 02 Dec 2025 22:57:45 +0000 (5 days ago)

hack up a basic semi-functioning ajax based validator
hack up a basic semi-functioning ajax based validator

More to do:

* Most of the specification is still broken on Ayllu
* Property Validation
* URL Validation
* Feed Validation
1diff --git a/static/verifier.js b/static/verifier.js
2index bd455b6..9bcd9f2 100644
3--- a/static/verifier.js
4+++ b/static/verifier.js
5 @@ -1,14 +1,131 @@
6+ const Kind = {
7+ HREF: "href",
8+ PROPERTIES: "properties",
9+ TITLES: "titles",
10+ };
11+
12+ function newRecorder() {
13+ const Recorder = {
14+ state: [],
15+ ok(kind) {
16+ this.state.push([kind])
17+ },
18+ error(kind, message, object) {
19+ this.state.push([kind, message, object])
20+ }
21+ };
22+ return Recorder
23+ }
24+
25+ // TODO
26+ function constraint(kind, values = []) {
27+ if (kind == Kind.HREF) {
28+ function check_href() {
29+ // TODO
30+ };
31+ return { kind: Kind.HREF, check: check_href }
32+ } else if (kind == Kind.PROPERTIES) {
33+ function check_properties() {
34+ // TODO
35+ };
36+ return { kind: Kind.PROPERTIES, check: check_properties }
37+ } else if (kind == Kind.TITLES) {
38+ function check_titles() { };
39+ return { kind: Kind.TITLES, check: check_titles }
40+ }
41+ }
42+
43+ function validateLink(object, recorder, constraints = []) {
44+ for (let i = 0; i < constraints.length; i++) {
45+ let constraint = constraints[i];
46+ if (constraint.kind == Kind.HREF) {
47+ if (!('href' in object)) {
48+ recorder.error(Kind.HREF, "href not in link", object)
49+ continue;
50+ }
51+ recorder.ok(Kind.HREF);
52+ if ('check' in constraint) {
53+ constraint.check(object["properties"], recorder);
54+ };
55+ } else if (constraint.kind == Kind.PROPERTIES) {
56+ if (!('properties' in object)) {
57+ recorder.error(Kind.PROPERTIES, "properties not present in link", object);
58+ continue;
59+ };
60+ recorder.ok(Kind.PROPERTIES);
61+ if ('check' in constraint) {
62+ constraint.check(object["properties"], recorder);
63+ };
64+ } else if (constraint.kind == Kind.TITLES) {
65+ if (!('titles' in object)) {
66+ recorder.error(Kind.TITLES, "titles not present in object", object);
67+ continue;
68+ };
69+ recorder.ok(Kind.TITLES);
70+ if ('check' in constraint) {
71+ constraint.check(object["titles"], recorder);
72+ };
73+ }
74+ }
75+ }
76+
77 const _project = {
78- "relationTypes": [
79- "http://forge-feed.org/rel/avatar",
80- "http://forge-feed.org/rel/chatroom",
81- "http://forge-feed.org/rel/description",
82- "http://forge-feed.org/rel/label",
83- "http://forge-feed.org/rel/homepage",
84- "http://forge-feed.org/rel/repository",
85- "http://forge-feed.org/rel/mailing-list",
86- "http://forge-feed.org/rel/ticketing-system",
87- ]
88+ "http://forge-feed.org/rel/avatar": [constraint(Kind.HREF)],
89+ "http://forge-feed.org/rel/chatroom": [
90+ constraint(Kind.HREF),
91+ constraint(Kind.PROPERTIES, {
92+ "http://forge-feed.org/ns/chatroom": ["irc", "xmpp"]
93+ })
94+ ],
95+ "http://forge-feed.org/rel/description": [constraint(Kind.TITLES)],
96+ "http://forge-feed.org/rel/label": [
97+ constraint(Kind.PROPERTIES, { "http://forge-feed.org/ns/label": null }),
98+ ],
99+ "http://forge-feed.org/rel/homepage": [constraint(Kind.HREF)],
100+ "http://forge-feed.org/rel/repository": [
101+ constraint(Kind.HREF),
102+ constraint(Kind.PROPERTIES, {
103+ "http://forge-feed.org/ns/repository-uri": null,
104+ "http://forge-feed.org/ns/vcs-type": [
105+ "bzr", "darcs", "fossil", "git", "hg", "pijul", "svn"],
106+ }),
107+ constraint(Kind.TITLES),
108+ ],
109+ "http://forge-feed.org/rel/mailing-list": [
110+ constraint(Kind.HREF),
111+ constraint(Kind.PROPERTIES, {
112+ "http://feed-forge.org/ns/mailing-list-subscribe": null,
113+ "http://feed-forge.org/ns/mailing-list-unsubscribe": null,
114+ })
115+ ],
116+ "http://forge-feed.org/rel/ticketing-system": [constraint(Kind.HREF)],
117+ };
118+
119+ const _repository = {
120+ "http://forge-feed.org/rel/avatar": [constraint(Kind.HREF)],
121+ "http://feed-forge.org/rel/clone": [
122+ constraint(Kind.HREF),
123+ constraint(Kind.PROPERTIES, {
124+ "http://feed-forge.org/ns/vcs-type": [
125+ "bzr", "darcs", "fossil", "git", "hg", "pijul", "svn",
126+ ]
127+ })],
128+ "http://forge-feed.org/rel/description": [constraint(Kind.TITLES)],
129+ "http://forge-feed.org/rel/license": [constraint(Kind.PROPERTIES, {
130+ "http://forge-feed.org/ns/spdx-identifier": null
131+ })],
132+ "http://forge-feed.org/rel/label": [
133+ constraint(Kind.PROPERTIES, { "http://forge-feed.org/ns/label": null }),
134+ ],
135+ "http://forge-feed.org/rel/homepage": [constraint(Kind.HREF)],
136+ "http://forge-feed.org/rel/mailing-list": [
137+ constraint(Kind.HREF),
138+ constraint(Kind.PROPERTIES, {
139+ "http://feed-forge.org/ns/mailing-list-subscribe": null,
140+ "http://feed-forge.org/ns/mailing-list-unsubscribe": null,
141+ })
142+ ],
143+ "http://forge-feed.org/rel/ticketing-system": [constraint(Kind.HREF)],
144 };
145
146
147 @@ -23,36 +140,59 @@ function doQuery(url, cb) {
148 xhr.send();
149 }
150
151- function update() {
152- let fqdn = document.getElementById("fqdn")[0].value;
153- let project = document.getElementById("project")[0].value;
154- let project_query = "https://" + fqdn + "/.well-known/webfinger?resource=" + project;
155- document.getElementById("project-uri-display").innerText = project_query;
156- const repository = document.getElementById("repository")[0].value;
157- const repository_query = "https://" + fqdn + "/.well-known/webfinger?resource=" + project;
158- document.getElementById("repository-uri-display").innerText = project_query;
159- }
160-
161- function projectQuery() {
162+ function validateProject() {
163+ document.getElementById("results").innerText = "";
164 const fqdn = document.getElementById("fqdn")[0].value;
165 const project = document.getElementById("project")[0].value;
166- doQuery("https://" + fqdn + "/.well-known/webfinger?resource=" + project, function(data) {
167+ let queryUrl = "https://" + fqdn + "/.well-known/webfinger?resource=" + project;
168+ document.getElementById("query-url").innerText = queryUrl;
169+ doQuery(queryUrl, function(data) {
170 let parsed = JSON.parse(data);
171- console.log(parsed);
172- // document.getElementById("project-response").innerText = data;
173+ let recorder = newRecorder();
174+ for (let i = 0; i < parsed.links.length; i++) {
175+ let link = parsed.links[i];
176+ if (link["rel"] in _project) {
177+ let constraints = _project[link["rel"]];
178+ validateLink(link, recorder, constraints);
179+ }
180+ };
181+ var textResult = "";
182+ for (let i = 0; i < recorder.state.length; i++) {
183+ console.log(recorder.state[i]);
184+ if (recorder.state[i][1]) {
185+ textResult += "Fail: " + recorder.state[i][1] + "\r\n";
186+ let asJson = JSON.stringify(recorder.state[i][2]);
187+ textResult += "\t\t" + asJson + "\r\n";
188+ }
189+ document.getElementById("results").innerText = textResult;
190+ }
191 });
192 }
193
194- function repositoryQuery() {
195+ function validateRepository() {
196+ document.getElementById("results").innerText = "";
197 const fqdn = document.getElementById("fqdn")[0].value;
198 const repository = document.getElementById("repository")[0].value;
199- doQuery("https://" + fqdn + "/.well-known/webfinger?resource=" + repository, function(data) {
200- document.getElementById("repository-response").innerText = data;
201+ let queryUrl = "https://" + fqdn + "/.well-known/webfinger?resource=" + repository;
202+ document.getElementById("query-url").innerText = queryUrl;
203+ doQuery(queryUrl, function(data) {
204+ let parsed = JSON.parse(data);
205+ let recorder = newRecorder();
206+ for (let i = 0; i < parsed.links.length; i++) {
207+ let link = parsed.links[i];
208+ if (link["rel"] in _repository) {
209+ let constraints = _project[link["rel"]];
210+ validateLink(link, recorder, constraints);
211+ }
212+ };
213+ var textResult = "";
214+ for (let i = 0; i < recorder.state.length; i++) {
215+ if (recorder.state[i][1]) {
216+ textResult += "Fail: " + recorder.state[i][1] + "\r\n";
217+ let asJson = JSON.stringify(recorder.state[i][2]);
218+ textResult += "\t\t" + asJson + "\r\n";
219+ }
220+ document.getElementById("results").innerText = textResult;
221+ }
222 });
223 }
224-
225- const init = function() {
226- update();
227- };
228-
229- init();
230 diff --git a/templates/validator.html b/templates/validator.html
231index 67f1d47..ab8d2fe 100644
232--- a/templates/validator.html
233+++ b/templates/validator.html
234 @@ -6,11 +6,11 @@
235 Fill out the form below to validate a ForgeFeed enabled website. Note that
236 your server must have the appropriate CORS headers:
237 <div>
238- <code>Access-Control-Allow-Origin: https://forge-feed.org</code>.
239+ <code>Access-Control-Allow-Origin: https://forge-feed.org</code>.
240 </div>
241 </p>
242 <div id="validator">
243- <form id="fqdn" onChange="update()">
244+ <form id="fqdn">
245 <label for="domain">
246 FQDN
247 </label>
248 @@ -24,26 +24,21 @@ your server must have the appropriate CORS headers:
249 <form id="project" onsubmit="return false">
250 <label for="project-uri">Project URI</label>
251 <input type="text" id="project-uri" name="project-uri" value="project://ayllu" required>
252- <button type="submit" onclick="projectQuery()">Validate</button>
253+ <button type="submit" onclick="validateProject()">Validate</button>
254 </form>
255 <form id="repository" onsubmit="return false">
256- <label for="repository-uri">Project URI</label>
257+ <label for="repository-uri">Repository URI</label>
258 <input type="text" id="repository-uri" name="repository-uri" value="repository://ayllu/ayllu" required>
259- <button type="submit" onclick="repositoryQuery()">Validate</button>
260+ <button type="submit" onclick="validateRepository()">Validate</button>
261 </form>
262- <form id="feed" onsubmit="return false">
263- <label for="feed">Feed URL</label>
264- <input type="text" id="feed-uri" name="feed-uri" value="/firehose.xml" required>
265- <button type="submit" onclick="verify()">Validate</button>
266- </form>
267- <div>
268- <div id="project-uri-display" class="computed"></div>
269- <div id="project-response" class="computed"></div>
270- </div>
271- <div>
272- <div id="repository-uri-display" class="computed"></div>
273- <div id="repository-response" class="computed"</div>
274- </div>
275+ <!-- <form id="feed" onsubmit="return false"> -->
276+ <!-- <label for="feed">Feed URL</label> -->
277+ <!-- <input type="text" id="feed-uri" name="feed-uri" value="/firehose.xml" required> -->
278+ <!-- <button type="submit" onclick="verify()">Validate</button> -->
279+ <!-- </form> -->
280+ <div id="query-url" class="computed"></div>
281+ <div id="results" class="computed"></div>
282+
283 </div>
284 <script src="/verifier.js"></script>
285 {% endblock content %}