Skip to content

Commit

Permalink
feat: Add a new panel in the debug UI to see active subscriptions
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Jun 8, 2020
1 parent 40006b7 commit b7430a9
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 172 deletions.
9 changes: 9 additions & 0 deletions public/app.css
Expand Up @@ -5,3 +5,12 @@
.hero img {
width: 40%;
}

#updates {
height: 250px;
overflow: scroll;
}

#subscriptions .card-header-title {
overflow-wrap: anywhere;
}
205 changes: 139 additions & 66 deletions public/app.js
Expand Up @@ -3,46 +3,49 @@
(function () {
const origin = window.location.origin;
const defaultTopic = origin + "/demo/books/1.jsonld";
const placeholderTopic = "https://example.com/my-private-topic";
const defaultJwt =
"eyJhbGciOiJIUzI1NiJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiKiJdLCJzdWJzY3JpYmUiOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9teS1wcml2YXRlLXRvcGljIiwie3NjaGVtZX06Ly97K2hvc3R9L2RlbW8vYm9va3Mve2lkfS5qc29ubGQiLCIvLndlbGwta25vd24vbWVyY3VyZS9zdWJzY3JpcHRpb25zey90b3BpY317L3N1YnNjcmliZXJ9Il0sInBheWxvYWQiOnsidXNlciI6Imh0dHBzOi8vZXhhbXBsZS5jb20vdXNlcnMvZHVuZ2xhcyIsInJlbW90ZUFkZHIiOiIxMjcuMC4wLjEifX19.z5YrkHwtkz3O_nOnhC_FP7_bmeISe3eykAkGbAl5K7c";

const updates = document.querySelector("#updates");
const settingsForm = document.forms.settings;
const discoverForm = document.forms.discover;
const subscribeForm = document.forms.subscribe;
const publishForm = document.forms.publish;
const $updates = document.getElementById("updates");
const $subscriptions = document.getElementById("subscriptions");
const $settingsForm = document.forms.settings;
const $discoverForm = document.forms.discover;
const $subscribeForm = document.forms.subscribe;
const $publishForm = document.forms.publish;
const $subscriptionsForm = document.forms.subscriptions;

function error(e) {
const error = (e) => {
const message = e.toString();
console.error(e);
alert(message);
}
};

function getHubUrl(response) {
const link = response.headers.get("Link");
if (link) {
const match = link.match(/<(.*)>.*rel="mercure".*/);
if (match && match[1]) return match[1];
const getHubUrl = (resp) => {
const link = resp.headers.get("Link");
if (!link) {
error('No rel="mercure" Link header provided.');
}

error('No rel="mercure" Link header provided.');
}
const match = link.match(/<(.*)>.*rel="mercure".*/);
if (match && match[1]) return match[1];
};

// Set default values
document.addEventListener("DOMContentLoaded", function () {
settingsForm.hubUrl.value = origin + "/.well-known/mercure";
settingsForm.jwt.value = defaultJwt;
document.addEventListener("DOMContentLoaded", () => {
$settingsForm.hubUrl.value = origin + "/.well-known/mercure";
$settingsForm.jwt.value = defaultJwt;

discoverForm.topic.value = defaultTopic;
discoverForm.body.value = JSON.stringify(
$discoverForm.topic.value = defaultTopic;
$discoverForm.body.value = JSON.stringify(
{
"@id": defaultTopic,
availability: "https://schema.org/InStock",
},
null,
2
);
publishForm.data.value = JSON.stringify(
$publishForm.data.value = JSON.stringify(
{
"@id": defaultTopic,
availability: "https://schema.org/OutOfStock",
Expand All @@ -59,93 +62,94 @@ foo`;
});

// Discover
discoverForm.onsubmit = function (e) {
$discoverForm.onsubmit = async function (e) {
e.preventDefault();
const {
elements: { topic, body },
} = this;
const jwt = settingsForm.jwt.value;
const jwt = $settingsForm.jwt.value;

const url = new URL(topic.value);
if (body.value) url.searchParams.append("body", body.value);
if (jwt) url.searchParams.append("jwt", jwt);

fetch(url)
.then((response) => {
if (!response.ok) throw new Error(response.statusText);
try {
const resp = await fetch(url);
if (!resp.ok) throw new Error(resp.statusText);

// Set hub default
const hubUrl = getHubUrl(response);
if (hubUrl) settingsForm.hubUrl.value = new URL(hubUrl, topic.value);
// Set hub default
const hubUrl = getHubUrl(resp);
if (hubUrl) $settingsForm.hubUrl.value = new URL(hubUrl, topic.value);

const subscribeTopics = subscribeForm.topics;
if (subscribeTopics.value === "https://example.com/my-private-topic")
subscribeTopics.value = topic.value;
const subscribeTopics = $subscribeForm.topics;
if (subscribeTopics.value === placeholderTopic)
subscribeTopics.value = topic.value;

// Set publish default values
const publishTopics = publishForm.topics;
if (publishTopics.value === "https://example.com/my-private-topic")
publishTopics.value = topic.value;
// Set publish default values
const publishTopics = $publishForm.topics;
if (publishTopics.value === placeholderTopic)
publishTopics.value = topic.value;

return response.text();
})
.then((data) => (body.value = data))
.catch((e) => error(e));
const text = await resp.text();
body.value = text;
} catch (e) {
error(e);
}
};

// Subscribe
const template = document.querySelector("#update");
let eventSource;
subscribeForm.onsubmit = function (e) {
const $updateTemplate = document.getElementById("update");
let updateEventSource;
$subscribeForm.onsubmit = function (e) {
e.preventDefault();

eventSource && eventSource.close();
updates.innerText = "No updates pushed yet.";
updateEventSource && updateEventSource.close();
$updates.textContent = "No updates pushed yet.";

const {
elements: { topics, lastEventId },
} = this;

const topicList = topics.value.split("\n");
const u = new URL(settingsForm.hubUrl.value);
const u = new URL($settingsForm.hubUrl.value);
topicList.forEach((topic) => u.searchParams.append("topic", topic));
if (lastEventId.value)
u.searchParams.append("Last-Event-ID", lastEventId.value);

let ol = null;
if (settingsForm.authorization.value === "header")
eventSource = new EventSourcePolyfill(u, {
if ($settingsForm.authorization.value === "header")
updateEventSource = new EventSourcePolyfill(u, {
headers: {
Authorization: `Bearer ${settingsForm.jwt.value}`,
Authorization: `Bearer ${$settingsForm.jwt.value}`,
},
});
else eventSource = new EventSource(u);
else updateEventSource = new EventSource(u);

eventSource.onmessage = function (e) {
updateEventSource.onmessage = function (e) {
if (!ol) {
ol = document.createElement("ol");
ol.reversed = true;

updates.textContent = "";
updates.appendChild(ol);
$updates.textContent = "";
$updates.appendChild(ol);
}

const li = document.importNode(template.content, true);
const li = document.importNode($updateTemplate.content, true);
li.querySelector("h2").textContent = e.lastEventId;
li.querySelector("pre").textContent = e.data;
ol.firstChild ? ol.insertBefore(li, ol.firstChild) : ol.appendChild(li);
};
eventSource.onerror = console.log;
updateEventSource.onerror = console.log;
this.elements.unsubscribe.disabled = false;
};
subscribeForm.elements.unsubscribe.onclick = function () {
eventSource.close();
$subscribeForm.elements.unsubscribe.onclick = function () {
updateEventSource.close();
this.disabled = true;
updates.innerText = "Unsubscribed.";
$updates.textContent = "Unsubscribed.";
};

// Publish
publishForm.onsubmit = function (e) {
$publishForm.onsubmit = async function (e) {
e.preventDefault();
const {
elements: { topics, data, priv, id, type, retry },
Expand All @@ -162,13 +166,82 @@ foo`;
priv.checked && body.append("private", "on");

const opt = { method: "POST", body };
if (settingsForm.authorization.value === "header")
opt.headers = { Authorization: `Bearer ${settingsForm.jwt.value}` };

fetch(settingsForm.hubUrl.value, opt)
.then((response) => {
if (!response.ok) throw new Error(response.statusText);
})
.catch((e) => error(e));
if ($settingsForm.authorization.value === "header")
opt.headers = { Authorization: `Bearer ${$settingsForm.jwt.value}` };

try {
const resp = await fetch($settingsForm.hubUrl.value, opt);
if (!resp.ok) throw new Error(resp.statusText);
} catch (e) {
error(e);
}
};

// Subscriptions
const $subscriptionTemplate = document.getElementById("subscription");
let subscriptionEventSource;

const addSubscription = (s) => {
const subscription = document.importNode(
$subscriptionTemplate.content,
true
);
subscription.querySelector("div").setAttribute("id", s.id);
subscription.querySelector(".card-header-title").textContent = s.id;
subscription.querySelector(".topic").textContent = s.topic;
subscription.querySelector(".subscriber").textContent = s.subscriber;
subscription.querySelector("code").textContent = JSON.stringify(
s.payload,
null,
2
);
$subscriptions.appendChild(subscription);
};

$subscriptionsForm.onsubmit = async (e) => {
e.preventDefault();

const opt =
$settingsForm.authorization.value === "header"
? { Authorization: `Bearer ${$settingsForm.jwt.value}` }
: undefined;
const resp = await fetch(
`${$settingsForm.hubUrl.value}/subscriptions`,
opt
);
const json = await resp.json();

json.subscriptions.forEach(addSubscription);

const u = new URL($settingsForm.hubUrl.value);
u.searchParams.append(
"topic",
"/.well-known/mercure/subscriptions{/topic}{/subscriber}"
);
u.searchParams.append("Last-Event-ID", json.lastEventID);

if (opt) subscriptionEventSource = new EventSourcePolyfill(u, opt);
else subscriptionEventSource = new EventSource(u);

subscriptionEventSource.onmessage = function (e) {
const s = JSON.parse(e.data);

if (s.active) {
addSubscription(s);
return;
}

document.getElementById(s.id).remove();
};
subscriptionEventSource.onerror = console.log;

$subscriptionsForm.elements.unsubscribe.disabled = false;
};
$subscriptionsForm.elements.unsubscribe.onclick = function (e) {
e.preventDefault();

subscriptionEventSource.close();
this.disabled = true;
$subscriptions.textContent = "";
};
})();

0 comments on commit b7430a9

Please sign in to comment.