@@ -3,13 +3,15 @@ import { parse, mustEnd } from 'yieldparser';
33import { toCode } from 'scalemodel' ;
44import { IconElementHandler } from './view/icons' ;
55import { sha , yieldmachineSha } from './sha' ;
6+ import * as PageContent from "./pages" ;
67
78let devSHAs = { } ;
89if ( PRODUCTION_LIKE !== '1' ) {
910 devSHAs = require ( './sha.dev' ) . devSHAs ;
1011}
1112
1213const 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