Skip to content

Commit ec8678e

Browse files
committed
Use collected.press’s new websocket API to render local content
1 parent 2227138 commit ec8678e

11 files changed

+1469
-6112
lines changed

Makefile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ latest:
1010

1111
dev: install
1212
@CF_ACCOUNT_ID=$(CF_ACCOUNT_ID) CF_ZONE_ID=$(CF_ZONE_ID) npm start
13-
14-
miniflare: install
15-
npx miniflare@latest --live-reload
1613

1714
production: sha.js
1815
@CF_ACCOUNT_ID=$(CF_ACCOUNT_ID) CF_ZONE_ID=$(CF_ZONE_ID) wrangler publish
@@ -49,7 +46,7 @@ define sha256_file
4946
endef
5047

5148
AWS_ENV := AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY)
52-
FILES := pages/machines.client.js pages/machines.md
49+
FILES := pages/machines.client.js pages/machines.md pages/parsing.md
5350
FILE := pages/machines.client.js
5451
DIGEST_HEX := $(call sha256_file,$(FILE))
5552
MIME_TYPE := application/javascript
@@ -79,3 +76,7 @@ tmp/sha/$(REGENERATED_DEV_SHA) tmp/sha/$(YIELDMACHINE_SHA):
7976

8077
sha.js: tmp/sha/$(REGENERATED_DEV_SHA) tmp/sha/$(YIELDMACHINE_SHA)
8178
@echo "export const sha = '$(REGENERATED_DEV_SHA)'; export const yieldmachineSha = '$(YIELDMACHINE_SHA)';" > sha.js
79+
80+
tmp/pages-txt/parsing.txt:
81+
@mkdir -p tmp/pages-txt
82+
@cp pages/parsing.md tmp/pages-txt/parsing.txt

collected-press-local.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const http = require("http");
2+
const host = 'localhost';
3+
const port = 8000;
4+
5+
function handler(req, res) {
6+
res.writeHead(200);
7+
res.end("My first server!");
8+
};

index.js

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { parse, mustEnd } from 'yieldparser';
33
import { toCode } from 'scalemodel';
44
import { IconElementHandler } from './view/icons';
55
import { sha, yieldmachineSha } from './sha';
6+
import * as PageContent from "./pages";
67

78
let devSHAs = {};
89
if (PRODUCTION_LIKE !== '1') {
910
devSHAs = require('./sha.dev').devSHAs;
1011
}
1112

1213
const config = Object.freeze({
14+
// TODO: for development, we could start a local server `npx collected-press --port 8989 ./`
1315
pressGitHubURL: new URL(`https://collected.press/1/github/RoyalIcing/regenerated.dev@${sha}/`),
1416
pressS3URL: new URL(`https://staging.collected.press/1/s3/object/us-west-2/collected-workspaces/`),
1517
jsdelivrURL: new URL(`https://cdn.jsdelivr.net/gh/RoyalIcing/regenerated.dev@${sha}/`),
@@ -47,7 +49,7 @@ const ExternalScripts = {
4749
},
4850
}
4951

50-
function *PrismScript() {
52+
function* PrismScript() {
5153
return;
5254
yield html`<!-- Prism syntax highlighting -->
5355
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.21.0/components/prism-core.min.js" integrity="sha512-hqRrGU7ys5tkcqxx5FIZTBb7PkO2o3mU6U5+qB9b55kgMlBUT4J2wPwQfMCxeJW1fC8pBxuatxoH//z0FInhrA==" crossorigin="anonymous"></script>
@@ -124,7 +126,7 @@ function* PathParser() {
124126
yield mustEnd;
125127
return { type: 'press', url: new URL("readme.md", config.pressYieldmachineURL), title: 'Yield Machine' };
126128
}
127-
129+
128130
return yield [Home, ArticleModule, ArticlePage, YieldmachinePage];
129131
}
130132

@@ -298,6 +300,124 @@ async function renderPage(event, requestURL, contentURL, clientURL, title) {
298300
);
299301
}
300302

303+
// From: https://developers.cloudflare.com/workers/learning/using-websockets/#writing-a-websocket-client
304+
async function websocket(url) {
305+
// Make a fetch request including `Upgrade: websocket` header.
306+
// The Workers Runtime will automatically handle other requirements
307+
// of the WebSocket protocol, like the Sec-WebSocket-Key header.
308+
const response = await fetch(url, {
309+
headers: {
310+
Upgrade: 'websocket',
311+
},
312+
});
313+
314+
// If the WebSocket handshake completed successfully, then the
315+
// response has a `webSocket` property.
316+
let ws = response.webSocket;
317+
if (!ws) {
318+
throw new Error("server didn't accept WebSocket");
319+
}
320+
321+
// Call accept() to indicate that you'll be handling the socket here
322+
// in JavaScript, as opposed to returning it on to a client.
323+
await ws.accept();
324+
325+
// Now you can send and receive messages like before.
326+
return ws;
327+
}
328+
329+
class CollectedPressRenderer {
330+
constructor() {
331+
this.requestedIDs = new Set();
332+
this.replies = new Map();
333+
}
334+
335+
async start() {
336+
if (this.ws) {
337+
console.log("READY STATE", this.ws.readyState)
338+
if (this.ws.readyState <= 1) {
339+
return this.ws
340+
}
341+
}
342+
343+
const ws = await websocket('https://collected.press/1/ws')
344+
// const ws = await websocket('http://localhost:4321/1/ws')
345+
ws.addEventListener('message', event => {
346+
console.log("RECEIVED", event)
347+
try {
348+
const data = (typeof event.data === "string") ? event.data : (new TextDecoder()).decode(event.data)
349+
const reply = JSON.parse(data);
350+
const id = reply.id;
351+
if (typeof id === 'string' && this.requestedIDs.has(id)) {
352+
this.replies.set(id, reply);
353+
}
354+
}
355+
finally { }
356+
// console.log(event.data);
357+
});
358+
this.ws = ws;
359+
return ws;
360+
}
361+
362+
async send(id, method, params) {
363+
const ws = await this.start();
364+
365+
const message = {
366+
jsonrpc: "2.0",
367+
id,
368+
method,
369+
params
370+
};
371+
this.requestedIDs.add(id);
372+
ws.send(JSON.stringify(message));
373+
}
374+
375+
async renderMarkdown(source) {
376+
const id = crypto.randomUUID()
377+
await this.send(id, "renderMarkdown", {
378+
type: "text/markdown",
379+
source
380+
});
381+
382+
const aborter = new AbortController();
383+
setTimeout(() => {
384+
aborter.abort()
385+
}, 5000);
386+
387+
return new Promise((resolve, reject) => {
388+
let succeeded = false;
389+
// aborter.signal.addEventListener('close', () => {
390+
// console.log("CLOSE!")
391+
// aborter.abort()
392+
// }, { signal: aborter.signal });
393+
// aborter.signal.addEventListener('error', () => {
394+
// console.log("CLOSE ERROR!")
395+
// aborter.abort()
396+
// }, { signal: aborter.signal });
397+
aborter.signal.addEventListener('abort', () => {
398+
// We get an error unless we close, so we always restart the WebSocket!
399+
// It’s a bit unfortunate as the whole point is that the connection is left open.
400+
this.ws?.close()
401+
402+
if (!succeeded) {
403+
reject(Error("Timed out"));
404+
}
405+
})
406+
407+
this.ws.addEventListener('message', () => {
408+
if (this.replies.has(id)) {
409+
resolve(this.replies.get(id));
410+
succeeded = true;
411+
console.log('ID', id, aborter.signal.aborted)
412+
aborter.abort();
413+
}
414+
}, { signal: aborter.signal });
415+
});
416+
}
417+
}
418+
419+
let collectedPressRenderer = null;
420+
301421
/**
302422
* Respond with results
303423
* @param {Request} request
@@ -308,13 +428,29 @@ async function handleRequest(request, event) {
308428
const { pathname } = url;
309429
const { success, result } = parsePath(pathname);
310430

431+
if (!success) {
432+
return notFoundResponse(url);
433+
}
434+
435+
function pressURL(path) {
436+
if (PRODUCTION_LIKE === '1') {
437+
pressGitHubURL(path)
438+
} else {
439+
pressS3URL(`text/markdown/${devSHAs[path]}`)
440+
}
441+
}
442+
311443
console.log('Go!')
312444
const render = renderPage.bind(null, event, url)
313445
console.log(result, devSHAs)
314446

315-
if (!success) {
316-
return notFoundResponse(url);
317-
} else if (result.type === 'home') {
447+
if (collectedPressRenderer === null) {
448+
collectedPressRenderer = new CollectedPressRenderer()
449+
await collectedPressRenderer.start()
450+
}
451+
452+
453+
if (result.type === 'home') {
318454
if (url.searchParams.has('icons')) {
319455
const res = await render(pressGitHubURL("pages/home.md"), undefined, 'JavaScript Regenerated');
320456
const rewriter = new HTMLRewriter();
@@ -327,7 +463,24 @@ async function handleRequest(request, event) {
327463
/* return new Response('<!doctype html><html lang=en><meta charset=utf-8><meta name=viewport content="width=device-width"><p>Hello!</p>', { headers: { 'content-type': contentTypes.html } }); */
328464
} else if (result.type === 'article') {
329465
if (result.slug === 'parsing') {
330-
return render(pressGitHubURL("pages/parsing.md"), jsdelivrURL("pages/parsing.client.js"), 'JavaScript Regenerated: Parsing')
466+
if (PRODUCTION_LIKE === '1') {
467+
return render(pressGitHubURL("pages/parsing.md"), jsdelivrURL("pages/parsing.client.js"), 'JavaScript Regenerated: Parsing')
468+
} else {
469+
const pageContent = PageContent.Parsing;
470+
// const resultHTML = await collectedPressRenderer.renderMarkdown(pageContent).then(result => result?.result?.html)
471+
const [stream, promise] = streamStyledHTML(() => [
472+
// renderHTML([html`<title>`, title, html`</title>`]),
473+
`<body>`,
474+
renderBanner(),
475+
`<main>`,
476+
collectedPressRenderer.renderMarkdown(pageContent).then(result => result?.result?.html).catch(error => "Error loading from collected.press: " + error.message),
477+
`</main>`,
478+
// fetchContentHTML(pressGitHubURL("pages/_footer.md")),
479+
])
480+
event.waitUntil(promise);
481+
return new Response(stream, { headers: { ...secureHTMLHeaders, 'content-type': contentTypes.html } });
482+
// return new Response(resultHTML ?? "Error!", { headers: { ...secureHTMLHeaders, 'content-type': contentTypes.html } });
483+
}
331484
} else if (result.slug === 'routing') {
332485
return render(pressGitHubURL("pages/routing.md"), undefined, 'JavaScript Regenerated: Routing')
333486
} else if (result.slug === 'pattern-matching') {
@@ -372,5 +525,5 @@ async function handleRequest(request, event) {
372525
} else {
373526
return notFoundResponse(url);
374527
}
375-
/* return new Response(result.slug, { headers: { 'content-type': contentTypes.html } }); */
528+
/* return new Response(result.slug, { headers: { 'content-type': contentTypes.html } }); */
376529
}

0 commit comments

Comments
 (0)