Published
Edited
Jul 19, 2022
4 forks
14 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
exampleEndpoint = endpoint(
/* endpoint name, must be unique per notebook */ "example",
/* endpoint handler, written like an express handler https://expressjs.com/en/api.html#app */
async (req, res) => {
console.log(`Received request from '${req.query.name}'`, req);
console.log("Writing to response", res);
mutable handlerLog = mutable handlerLog.concat(req);
debugger; // 👈 triggers a breakpoint if DevTools is open
res.send({
msg: `Hi! ${req.query.name}`,
time: Date.now()
});
},
/* endpoint options */ {
// For _this_ example, we turn on **public livecoding** in the options with `{livecode: 'public'}`
// I production use you would only allow livecoding by a developer (the default).
// Never combine secrets and *public* livecoding!
// Any secrets bound to a *public* livecode endpoint will be exposed.
// In contrast, default livecoding secrets are only exposed to the authenticated team members,
// who presumably had access by other means anyway.
livecode: "public",
host // WEBCode is a federated compute technology, you can host it yourself on a custom domain
}
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof response = Inputs.button("Make random request (press me!)", {
reduce: async () => {
try {
console.log(`Sending request with name parameter '${name}'`);
const response = await fetch(
exampleEndpoint.href + `?name=${encodeURIComponent(name)}`
);
console.log("Got response", response);
return response.json();
} catch (err) {
return err.message;
}
}
})
Insert cell
Insert cell
response || html``
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
webserver = endpoint(
"webserver",
async (req, res, ctx) => {
try {
mutable weblog = mutable weblog.concat(Date.now());
// Forward to a flowQueue for (async) processing
const response = await viewof webRequest.send({
req,
res,
ctx
});
} catch (err) {
res.status(err.code || 500).send(err.message);
}
},
{
livecode: "public",
host
}
)
Insert cell
Insert cell
viewof webRequest = flowQueue({
timeout_ms: 1000
})
Insert cell
webRequest
Insert cell
Insert cell
Insert cell
webRequest.req.url
Insert cell
Insert cell
webRequest.req
Insert cell
Insert cell
router = {
switch (webRequest.req.url) {
default:
return viewof webRequest.resolve(viewof defaultRequest.send(webRequest));

// Form handling example
case "/form.html":
return viewof webRequest.resolve(viewof formRequest.send(webRequest));

// Image serving (SVG and PNG)
case "/weblog.svg":
return viewof webRequest.resolve(viewof imgRequest.send(webRequest));
case "/weblog.png":
return viewof webRequest.resolve(viewof imgRequest.send(webRequest));

// HTML streaming
case "/stream.html":
return viewof webRequest.resolve(viewof streamRequest.send(webRequest));
}
}
Insert cell
Insert cell
viewof defaultRequest = flowQueue()
Insert cell
defaultRequest
Insert cell
Insert cell
defaultRequestHandler = {
const message = `Unknown path ${defaultRequest.req.url}`;
defaultRequest.res.status(404).send(message);
return message;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof formRequest = flowQueue()
Insert cell
formRequest
Insert cell
formResponseContent = {
if (formRequest.req.method === "POST") {
return await viewof formPostRequest.send(formRequest);
} else if (formRequest.req.method === "GET") {
return `<form method="post">
<textarea id="note" name="note"
rows="5" cols="40"></textarea>
<div><button type="submit">Submit</button></div>
</form>`;
}
}
Insert cell
Insert cell
Insert cell
${/* Programatically refresh this cell by incrementing refreshForm */ (refreshForm, '')}
<iframe width="${width}" height="170px" src=${webserver.href + "/form.html"}></iframe>
Insert cell
Insert cell
Insert cell
formResponder = {
if (formRequest.req.id !== /* previous value of cell */ this) {
// New request, respond to it
formRequest.res.status(200).send(formResponseContent);
} else {
mutable refreshForm++;
}
return formRequest.req.id;
}
Insert cell
Insert cell
viewof formPostRequest = flowQueue()
Insert cell
formPostRequest
Insert cell
Insert cell
formDataRaw = formPostRequest.req.body
Insert cell
Insert cell
decodedFormData = new URLSearchParams(formDataRaw)
Insert cell
Insert cell
formData = Object.fromEntries(decodedFormData.entries())
Insert cell
formPostRequestResolver = {
formPostRequest;
viewof formPostRequest.resolve(
`<h2>Thanks, your note was:</h2><p>${formData.note}</p>`
);
}
Insert cell
Insert cell
viewof imgRequest = flowQueue()
Insert cell
imgRequest
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
imageData = {
if (imgRequest.req.url.endsWith(".svg")) {
return serialize(mutable weblogDataviz);
} else if (imgRequest.req.url.endsWith(".png")) {
return rasterize(mutable weblogDataviz);
} else {
const error = new Error(
`Unrecognised image extension ${imgRequest.req.url}`
);
error.code = 400;
viewof imgRequest.reject(error);
throw error;
}
}
Insert cell
Insert cell
imageData.type
Insert cell
Insert cell
imgRequestResponder = {
imgRequest.res.header("content-type", imageData.type);
imgRequest.res.send(await imageData.arrayBuffer());
}
Insert cell
Insert cell
imgRequestResolve = {
imgRequestResponder;
if (imgRequest.req.id !== this) {
viewof imgRequest.resolve("OK");
return imgRequest.req.id;
} else {
mutable refreshDashboardImage++;
return this;
}
}
Insert cell
Insert cell
${(refreshDashboardImage, '')}
<img width="${Math.min(width, 640)}" src=${webserver.href + "/weblog.svg"}></img>
Insert cell
Insert cell
${(refreshDashboardImage, '')}
<img width="${Math.min(width, 640)}" src=${webserver.href + "/weblog.png"}></img>
Insert cell
Insert cell
Insert cell
viewof streamRequest = flowQueue()
Insert cell
streamRequest
Insert cell
Insert cell
streamRequestResponse = {
yield "ok"; // Yeild a value so request processing can proceed dowstream

// Run the streaming outside of the runtime with event listeners
// Its not easily possible to handle concurrent long lived requests using dataflow
const res = streamRequest.res;
const req = streamRequest.req;
const changeHandler = () => {
console.log(`change ${req.id}`);
res.write(
`<script>document.querySelector("pre")?.remove()</script>` +
`<pre>latest: ${viewof streamValue.value}</pre>`
);
};
console.log(`opening ${req.id}`);
res.write(`<body>Streaming for request ${req.id}`);
changeHandler();

viewof streamValue.addEventListener("input", changeHandler);
invalidation.then(() => {
console.log(`close ${req.id}`);
viewof streamValue.removeEventListener("input", changeHandler);
res.end();
});
}
Insert cell
streamRequestResolver = {
streamRequestResponse;
if (this !== streamRequest.req.id) {
viewof streamRequest.resolve("ok");
} else {
mutable streamingPreviewRefresh++;
}
return streamRequest.req.id;
}
Insert cell
Insert cell
Insert cell
Insert cell
${runStreamingPreview && streamingPreviewRefresh}
<iframe width="${width}" height="100px" src=${link}></iframe>

Insert cell
Insert cell
Insert cell
Insert cell
mutable streamingPreviewRefresh = 0
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more