-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathindex.html
724 lines (593 loc) · 28.5 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
<!doctype html>
<html>
<head>
<!-- tuplate_start(head.html) -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>patchbay - Poor Man's Web</title>
<link rel="stylesheet"
href="//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/atelier-sulphurpool-dark.min.css">
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-89445274-5"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-89445274-5');
</script>
<!-- tuplate_end() -->
<link rel="stylesheet" type="text/css" href="./styles.css">
<link rel="stylesheet" type="text/css" href="./apps/simple_chat/chat.css">
</head>
<body>
<div class='content'>
<!-- tuplate_start(header.html) -->
<header>
<nav class='navbar'>
<a href='./index.html' class='navbar__col'>
<img class='navbar__image' src='./logo.svg' alt='logo'></img>
</a>
<span class='navbar__col'>
<a class='navbar__link' href='./index.html'>Home</a>
</span>
<span class='navbar__col'>
<a class='navbar__link' href='./docs/index.html'>Docs</a>
</span>
<span class='navbar__col'>
<a class='navbar__link' href='./blog/index.html'>Blog</a>
</span>
<span class='navbar__col'>
<a class='navbar__link' href='./pro.html'>Pro</a>
</span>
</nav>
</header>
<!-- tuplate_end() -->
<main>
<h1>TL;DR</h1>
<p>
patchbay.pub is a free web service you can use to implement things like
<a href='#static-hosting'>static site hosting</a>,
<!--
<a href='#serverless'>dynamic site hosting</a>,
-->
<a href='#file-sharing'>file sharing</a>,
<a href='#notifications'>cross-platform notifications</a>,
<a href='#webhooks'>webhooks handling</a>,
smart home event routing, IoT reporting,
<a href='#job-queues'>job queues</a>,
<a href='#chat-systems'>chat systems</a>, bots,
etc, all completely serverless and requiring no account creation or
authentication. Most implementations need nothing but curl and simple
bash snippets.
</p>
<h1>Why did you make this?</h1>
<p>
I originally wanted an easy way to get a notification on my laptop when
a long-running job on my server completed. After a bit of
experimenting I decided a small amount of additional features would
result in a more generally useful tool. This evolved into the
following question:
</p>
<p class='emphasized'>
"What is the
<a target='_blank' href='https://en.wikipedia.org/wiki/Pareto_principle'>20%</a>
of
<a target='_blank' href='https://ifttt.com/'>IFTTT</a>
functionality I could implement to have 80% of IFTTT features that I
would personally use?"
</p>
<p>
patchbay is what I ended up with.
</p>
<p class='emphasized'>
The entire philosophy of patchbay is that all the logic is done on your
local machine[s], typically with small shell snippets. The server
exists only for you to connect
("<a target='_blank' href='https://en.wikipedia.org/wiki/Patch_panel'>patch</a>")
different components together.
</p>
<h1>How is it implemented?</h1>
<p>
patchbay provides an infinite number of virtual HTTP "channels" anyone
can use. Each channel is represented by a URL. Here's an
example channel:
</p>
<pre><code class='plaintext'>
https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>
</code></pre>
<p>
Generally, a channel is chosen by randomly generating a string long
enough that no one else will guess it (all channels are publicly
accessible; see
<a href='#security'>Security</a>),
and then sending HTTP requests to it. The channel above was generated
by your browser when you loaded this page, and should be fine to use
for running the examples. Channels don't have to be explicitly created
before use. Channels can operate in one or both of two modes. By
default, a channel models a multi-producer, multi-consumer (MPMC)
queue, where GET requests add a consumer, and POSTs add a producer.
Consumers will block if there aren't any producers, and producers will
block if there aren't any consumers. As soon as a producer gets
matched with a consumer, anything in the producer's HTTP body is
streamed over the channel to the consumer.
</p>
<p>
Enough theory; let's try out a trivial example. If you run this GET to
create a consumer, it will block:
</p>
<pre><code class='bash'>
curl https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>
</code></pre>
<p>
Until you also run this POST in another terminal to create a producer:
</p>
<pre><code class='bash'>
curl https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span> -d "Hi there"
</code></pre>
<p>
You can also try reversing the order, and observe that the producer
blocks until you run the consumer. If you start 2 producers at the
same time, you'll have to run the consumer twice in order to unblock
both of them, one after the other.
</p>
<p>
If you use the <code>/pubsub/</code>
<a href='./docs.html#protocols'>protocol</a>,
like this:
</p>
<pre><code class='bash'>
curl https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>
</code></pre>
<pre><code class='bash'>
curl https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span> -d "Hi there"
</code></pre>
<p>
the request uses the channel in a different mode. GETs act similarly to
before, but POSTs become non-blocking, and will broadcast messages to
all blocked pubsub consumers, not just the first one. As the name
suggest, this models the
<a target='_blank' href='https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern'>PubSub pattern</a>.
</p>
<p>
So, with that brief introduction, here are a few examples of things you
can implement with MPMC queues and pubsub messages over HTTP:
</p>
<h2 id='notifications'>Poor man's desktop notifications</h2>
<p>
Here's how my original goal can be implemented using patchbay.pub.
First, on my remote server:
</p>
<pre><code class='bash'>
./longjob.sh; curl -X POST https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>
</code></pre>
<p>
And on my Linux laptop with
<a target='_blank' href='https://wiki.archlinux.org/index.php/Desktop_notifications'>desktop notifications</a>
support:
</p>
<pre><code class='bash'>
curl https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>; notify-send "Job done"
</code></pre>
<p>
That's it. I'll get a popup on my screen when the job is done. (Note
that MacOS has
<a target='_blank' href='https://apple.stackexchange.com/a/115373'>notification functionality</a>
built-in.) If I want to get real fancy I can re-run the consumer in a
loop. I keep the following script running in the background ready to
receive notifications from whatever producers I want, displaying the
HTTP body from the producer:
</p>
<pre><code class='bash'>
#!/bin/bash
while true
do
# Run curl in a subshell and pass the results to notify-send
notify-send $(curl https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>)
# Avoid too many requests if server is down or rejecting
if [ "$?" -ne "0" ]; then
sleep 1
fi
done
</code></pre>
<p>
It's also possible to use a GET request to create a producer. This is
done by using the <code>method</code> and <code>body</code> query
parameters, like this:
</p>
<pre><code class='bash'>
curl "https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>?method=post&body=Job%20Done"
</code></pre>
<p>
This is useful if you want to do something like send a signal from a
mobile browser, where you only have access to the address bar. I like
to create mobile browser shortcuts for triggering different things.
</p>
<h2 id='notifications'>Poor man's SMS notifications</h2>
<p>
Let's extend the previous example a bit. What if I want to go to lunch,
but still get notified when the job on my server is done? The server
is already broadcasting a pubsub message, so I don't need to make any
changes there. I just need to add a consumer that can notify me on the
go. How about using
<a target='_blank' href='https://www.twilio.com/'>Twilio</a>
to send myself a text message? First I followed
<a target='_blank' href='https://www.twilio.com/docs/twilio-cli/quickstart'>the instructions</a>
to get the Twilio CLI installed and logged in on my laptop, then it's
just a matter of calling it with the body received by the pubsub
consumer:
</p>
<pre><code class='bash'>
twilio api:core:messages:create --from "+15017122661" --to "+15558675310" --body $(curl https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>)
</code></pre>
<p>
Now I'll get a desktop notification, <em>and</em> a text message when
the job is done. If you don't want to pay for texts, you can do
something similar with any messaging app that offers an API.
</p>
<h2 id='webhooks'>Poor man's webhooks</h2>
Receiving a text notification is useful, but what if I want to
<em>send</em> a text to my Twilio number and have it trigger some other
event? This is easily done by logging in to the Twilio website and
pointing the SMS webhook to
<code>https://patchbay.pub/pubsub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span></code>.
Any texts to my number will now trigger a pubsub event on the same
channel as before. I can use whatever command line tools I want to
process the webhook.
<h2 id='chat-systems'>Poor man's IRC</h2>
<p>
How about an ad-hoc chat app? This chat includes everyone currently
visiting this page, using the https://patchbay.pub/pubchat channel. It
doesn't require any fancy WebRTC or WebSockets; just HTTP. It's
implemented using the
<a target='_blank' href='https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events'>server-sent events (SSE) protocol</a>.
Instead of the events being generated on the server, they originate
from peer producers and are broadcast to all consumers. If there's no
one else around, try opening it in another tab and talking to yourself.
</p>
<a href="./apps/simple_chat/" target="_blank">Open chat in tab</a>
<div class='chat-container'></div>
<p>
The
<a target='_blank' href='https://github.com/patchbay-pub/patchbay.pub/tree/master/apps/simple_chat'>code</a>
is quite simple, and most of it is just UI stuff. Here's the meat of
it:
</p>
<pre><code class='javascript'>
// Send a message:
const message = JSON.stringify({
author: "Default Nickname",
text: "Hi there",
});
fetch('https://patchbay.pub/pubsub/pubchat', {
method: 'POST',
body: `data: ${message}\n\n`,
});
// Receive messages:
const evtSource = new EventSource("https://patchbay.pub/pubsub/pubchat?mime=text%2Fevent-stream&persist=true");
evtSource.onmessage = function(event) {
console.log(event.data);
}
</code></pre>
<p>
There are a couple new pieces of the patchbay API shown here. First of
all, notice that I'm overriding the Content-Type returned from the
server by setting mime=text/event-stream in the GET request. This is
necessary to make the browser use the SSE protocol. The server will
return whatever you specify. I'm also setting persist=true. This keeps
the consumer connection open, rather than requiring it to loop after
each message. This is useful for ensuring no messages are missed.
</p>
<p>
You can also participate with curl using something like the following:
</p>
<pre><code class='bash'>
printf 'data: {"author": "Curly Jefferson", "text": "Hi there"}\n\n' | curl https://patchbay.pub/pubsub/pubchat --data-binary @-
</code></pre>
<p>
Note that printf is necessary in order to properly pass the newlines
to curl, and I'm using --data-binary to prevent curl from stripping
whitespace. Also note that this chat system is very easy for
bad actors to interfere with by sending crafted messages. A real
solution would probably need something more sophisticated than SSE,
including some sort of client-side filtering.
</p>
<strong>Update 2022-02-21: </strong> <a
href='https://github.com/omar2205'>omar2205</a> implemented a
slick little private chat codepen you can find <a
href='https://codepen.io/omar2205/pen/bGoZevP'>here</a>.
<h2 id='job-queues'>Poor man's job queue</h2>
<p>
What if you had a directory with 1000 MP3s you want to transcode
without using more than 4 cores on your machine? You could google how
to use GNU parallel for the 27th time (assuming it runs on your
operating system), or you could run something like this in one
terminal:
</p>
<pre><code class='bash'>
#!/bin/bash
# IFS determines what to split on. By default it will split on spaces. Change
# it to newlines
# See https://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
ifsbak=$IFS
IFS=$(echo -en "\n\b")
for filename in *.mp3
do
curl https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span> -d $filename
done
# Need to restore IFS to its previous value
IFS=$ifsbak
</code></pre>
<p>
And this in 4 others:
</p>
<pre><code class='bash'>
#!/bin/bash
while true
do
filename=$(curl -s https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>)
if [ "$filename" != "Too Many Requests" ]
then
echo $filename
ffmpeg -i "$filename" "$filename.ogg"
else
sleep 1
fi
done
</code></pre>
<p>
The work will be evenly distributed across the consumer workers,
without using more than 4 cores at a time.
</p>
<h2 id='static-hosting'>Poor man's web hosting</h2>
Time to get funky. This entire web site is being hosted over a
patchbay.pub channel. How? This is essentially how I'm hosting the
index.html for the page you're reading (note that it uses the root
channel, /):
<pre><code class='bash'>
while true; do curl -X POST https://patchbay.pub/ --data-binary ./index.html; done
</code></pre>
<p>
And chat.js:
</p>
<pre><code class='bash'>
while true; do curl -X POST https://patchbay.pub/apps/simple_chat/chat.js --data-binary ./apps/simple_chat/chat.js; done
</code></pre>
<p>
That's right; I'm advocating bash as your next web server! (that's not
something I ever thought I'd write). This works because static site
hosting can be modeled as a MPMC queue, where each file you want to
host is a producer in a loop, and web browsers create a consumer with
each request to a resource. You can also assign multiple producers to
each hosted file for poor man's load balancing.
</p>
<p>
There's nothing special about channel ids. Any valid URL path will do.
In this case the channel ids carry extra semantic information
corresponding to a HTTP resource location, but as far as the server is
concerned, apps/simple_chat/chat.js is just a bunch of characters
forming an id.
</p>
<p>
Astute observers will see a problem here: what's preventing any of you
from sending POST requests to those channels and competing with my
producers to publish your own content in place of this page? For these
few endpoints, I added a bit of authentication to ensure I'm the only
one who can publish to them. In general I don't expect people to need
publicly facing sites on patchbay.pub. I think sites hosted on
privately shared channels (a la poor man's
<a target='_blank' href='https://ngrok.com/'>ngrok</a>) are much more likely.
But feel free to reach out if you want a "vanity channel" for some
reason.
</p>
<p>
Just because you can't host something on the root channel, doesn't mean
you can't host it somewhere else on patchbay.pub. There's a
<code>host.sh</code> script in
<a target='_blank' href='https://github.com/patchbay-pub/patchbay.pub'>the repo</a>).
You can host your own copy of this site on your own channel by
cloning the repo and running this command:
</p>
<pre><code class='bash'>
./host.sh https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>
</code></pre>
<p>
Then point your browser here (make sure to include the trailing slash;
it's required for relative browser imports to work):
</p>
<pre><code class='bash'>
https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span>/
</code></pre>
<p>
You can now make whatever changes you want to the code, and they will
immediately take effect when you refresh the browser. I actually wrote
this page, which is a simple static HTML page, using this workflow.
Note that you need to refresh twice, in order to flush the curl
producers that are still blocked waiting for consumers of the old code.
</p>
<p>
I'm working on a more robust
<a target='_blank' href='https://github.com/patchbay-pub/patchbay-cli'>CLI tool</a>,
which will turn common use cases like hosting a static site with many
files into a one liner (rather than having to manually write something
like host.sh yourself):
</p>
<pre><code class='bash'>
patchbay-cli host https://patchbay.pub/<span class='channel'>aa7cc811-d21c-42ef-92cc-a5566fa38344</span> ./dir/to/host
</code></pre>
<!--
<h2 id='serverless'>Poor man's serverless</h2>
<p>
MPMC is an elegant and useful tool, but it has some fundamental
weaknesses. The most important is that consumers can only consume, and
producers can only produce. It doesn't allow you to model a full
client/server, request/response architecture, because consumers can't
upload anything, and producers can't change their response based on
the client's parameters. In order to handle these more complicated
scenarios, patchbay implements a third protocol, called
requester/responder or reqres for short.
</p>
<p>
Take a gander at the following
<a target='_blank' href='https://durian.blender.org/'>Sintel</a>
video, which is being served over patchbay:
<div class='video-container'>
<video controls>
<source src="https://patchbay.pub/videos/sintel-2048-surround.mp4" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video>
</div>
Now observe that seeking to specific points in the video works just
fine. If you're not familiar with HTTP streaming, this is
accomplished using
<a target='_blank' href='https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests'>HTTP range requests</a>,
which are basically just a way for an HTTP client (your browser) to say
to the server, "Hey, I only need these specific bytes from this really
big file". The browser then requests the file chunks around wherever
you currently are in the video. This is done using HTTP headers. How
patchbay does this is beyond the scope of this intro. It requires a bit
more work when implementing the responder (though it's a lot simpler
than you might imagine), but it's seamless for the requester, ie your
browser. It just requests the file segments as it normally would.
When all is said and done, anything you can do with a normal server,
you can do with patchbay.
</p>
-->
<!--
<p>
Ok, time to get <em>really</em> funky. Remember that patchbay is 100%
HTTP. That means all the functionality is available in a web browser. But
surely you couldn't host a streamable video like above from your browser?
Use the dialog below to open a video file:
<h1>TODO: add fibridge UI</h1>
If you scan that QR code on your phone, you'll be able to stream the
video just as before. But this time your browser is the responder. As
long as you keep this tab of your browser open, you can share that link
with friends and they can stream the video.
</p>
-->
<h2 id='file-sharing'>Poor man's file sharing</h2>
No sketchy web service is complete without the ability to share files.
Pubsub messages are restricted in size, because each message has to be
copied into memory in order to be broadcast. MPMC streams are under no
such limitation. If you POST a 10GB file and send your friend a link, it
will be efficiently streamed from your machine, through a patchbay.pub
server, to your friend's machine. Note that bandwidth is currently quite
rate-limited on the free server, so I only recommend this for relatively
small files. Otherwise you'll be waiting a while.
<h1 id='security'>Security</h1>
<p>
patchbay is designed for simple ad-hoc tasks, with very low friction
being a primary goal. Having to juggle auth tokens and logins runs
counter to these aims. As such, it probably shouldn't be used for any
highly sensitive data. That said, I think it's secure enough for many
uses. In general, the longer and more random your channel id is, the
less likely anyone else can guess it or stumble upon it. You'll
probably want to user longer ids than the one generated for these
examples. Maybe a UUID or something. Also note that due to 1) rate
limiting and 2) the fact that requests block by default, brute forcing
probably isn't a viable attack strategy for channel ids of even
moderate length.
</p>
<h1 name='privacy'>Privacy</h1>
<p>
We don't look at anyone's data as it goes through our servers, but
you can also use end-to-end encryption if you don't trust us.
</p>
<h1>Caveats</h1>
<ul>
<li>
The goal is to keep patchbay free for everyone to use for projects of
a reasonable size. However, it's obviously necessary to implement
request rate and bandwidth limits. If there's enough interest, I'll
probably spin out a product of some sort for high-load use cases.
Feel free to reach out at
<a href="mailto:[email protected]">[email protected]</a>
if you have any thoughts.
</li>
<li>
Due to the way TCP works, it can be tricky to detect when an HTTP
client has disconnected. So if you connect a producer and then
cancel it, the server might keep it around for a while before
cleaning it up, and a consumer might grab the stale/partial data.
The simplest way to handle these situations is to flush it manually
by attaching a consumer as soon as you cancel the producer.
</li>
<li>
In general, your code needs to assume the connection can fail at any
time without having transferred any data. Additionally, even if some
or all of a producer body is read from the request, that doesn't
guarantee it's been delivered to a consumer. It might just be in an
OS buffer. That said, the server should never close a producer
connection until a consumer has attached and all data has been read
from the producer.
</li>
</ul>
<h1>Related Projects</h1>
<p>
One nice thing about patchbay is that the core functionality is so
simple it lends itself nicely to fresh implementations and derivative
ideas. Check some of these out:
</p>
<ul>
<li>
<a href='https://github.com/prologic/conduit'>https://github.com/prologic/conduit</a> - Open source implementation of queues/pubsub, inspired by patchbay
</li>
<li>
<a href='https://github.com/schollz/duct'>https://github.com/schollz/duct</a> - Open source implementation of queues/pubsub, inspired by patchbay
</li>
<li>
<a href='https://github.com/VictorioBerra/patch-me'>https://github.com/VictorioBerra/patch-me</a> - A GUI for services like patchbay.pub
</li>
<li>
<a href='https://github.com/ripienaar/piper'>https://github.com/ripienaar/piper</a> - Patchbay style distributed pipe using NATS.io
</li>
</ul>
<p>
If you have a patchbay-related project, let us know so we can add it
to the list!
</p>
<h1>Newsletter</h1>
<p>
If you'd like to stay up to date on future developments, consider
subscribing to our newsletter:
</p>
<!-- Begin Mailchimp Signup Form -->
<link href="//cdn-images.mailchimp.com/embedcode/slim-10_7.css" rel="stylesheet" type="text/css">
<style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; border: 1px solid #000; }
/* Add your own Mailchimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style>
<div id="mc_embed_signup">
<form action="https://moosedrive.us20.list-manage.com/subscribe/post?u=0c3ecbf40cf99a4c67c9e9659&id=0fea9c2dcb" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
<div id="mc_embed_signup_scroll">
<label for="mce-EMAIL">Subscribe</label>
<input type="email" value="" name="EMAIL" class="email" id="mce-EMAIL" placeholder="email address" required>
<!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_0c3ecbf40cf99a4c67c9e9659_0fea9c2dcb" tabindex="-1" value=""></div>
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
</div>
</form>
</div>
<!--End mc_embed_signup-->
</main>
<script src="//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
<script type='module'>
import ChatComponent from './apps/simple_chat/chat.js';
import { setRandomChannels } from './utils.js';
const historyLength = 200;
//const channel = 'http://localhost:9001/pubchat';
const channel = 'https://patchbay.pub/pubsub/pubchat';
const chatContainerEl = document.querySelector('.chat-container');
const chatEl = ChatComponent(channel, historyLength);
chatContainerEl.appendChild(chatEl);
setRandomChannels();
hljs.initHighlightingOnLoad();
</script>
</div>
<!-- tuplate_start(footer.html) -->
<script data-goatcounter="https://patchbay_pub.goatcounter.com/count"
async src="//gc.zgo.at/count.js"></script>
<!-- tuplate_end() -->
</body>
</html>