initial commit

This commit is contained in:
Joseph Montanaro 2021-10-17 21:05:23 -07:00
commit d0aaba7a7d
17 changed files with 1539 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
# create a new project in my-app
npm init svelte@next my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
```bash
npm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.

10
jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"$lib": ["src/lib"],
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

1084
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "blog",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview"
},
"devDependencies": {
"@sveltejs/kit": "next",
"mdsvex": "^0.9.8",
"svelte": "^3.42.6"
},
"type": "module"
}

16
src/app.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
</body>
</html>

1
src/global.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="@sveltejs/kit" />

20
src/routes/[slug].svelte Normal file
View File

@ -0,0 +1,20 @@
<script context="module">
export async function load({ page }) {
let post = await import(`./_posts/${page.params.slug}.svx`);
return {
props: {
metadata: post.metadata,
BlogPost: post.default
}
}
}
</script>
<script>
export let metadata, BlogPost;
</script>
<div id="post">
<h1>{metadata.title}</h1>
<svelte:component this={BlogPost} />
</div>

View File

@ -0,0 +1,42 @@
<style>
:global(main) {
max-width: 42rem;
margin: 0 auto;
}
#header {
background-color: #4f5f68;
}
#nav-main {
max-width: 30rem;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
column-gap: 40px;
}
#nav-main a {
font-size: 1.5rem;
color: white;
text-decoration: none;
text-align: center;
padding: 6px 0;
}
#nav-main a:hover {
background-color: #00000025;
}
</style>
<div id="header">
<nav id="nav-main">
<a href="/">Home</a>
<a href="/posts">Posts</a>
<a href="/about">About</a>
</nav>
</div>
<main>
<slot></slot>
</main>

View File

@ -0,0 +1,25 @@
---
title: Imagining A Passwordless Future
description: Can replace passwords with something more user-friendly?
date: 2021-04-30
---
Passwords are the *worst*.
How many times have you groaned becuase *yet another* password-related thing
was messed up? Forgotten passwords, passwords that you're *sure* you wrote
down but can't find for some reason, passwords that you definitely *did* make
a record of but the site is inexplicably refusing to accept, passwords that
get silently truncated because your bank is still using 3DES for some reason,
the list goes on. It's constant point of pain for almost everyone, and even
after 20+ years of trying to make it work we *still* haven't figured out a
foolproof method. Password managers help, but they aren't perfect. How many
times have you created a password for a new account somewhere, saved it, and
then discovered that your save didn't go through - maybe it didn't meet the
requirements (because your 24-character string of gibberish didn't includ a s
p e c i a l c h a r a c t e r), or maybe your cable box got hit by
lightning just as you clicked Save, or *whatever*. The fact is that passwords
are a pain, and it seems to be a pretty intractable problem.
You know what aren't a pain, or at least not nearly to the same extent? Keys.
That's right, physical stick-em-in-a-lock-and-turn metal keys. They've been
around since forever, and I doubt they'll be going anywhere any time soon.

View File

@ -0,0 +1,130 @@
---
title: Let's Design A Simpler SocketIO
date: 2021-10-16
description: >-
SocketIO is packed with features.
But do we really need all of them all the time?
---
Listen, don't get me wrong. SocketIO is great. It provides tons of features,
fantastic platform support, is widely deployed by a hugely diverse set of
companies, and has been around long enough that it probably has most of the
easily-encountered bugs ironed out.
So why wouldn't you want to use it? Well, a couple of reasons occur to me.
One, it's not exactly small. The unpacked library weighs in at just over 1MB,
which isn't a lot if it's a core component of your application (e.g. if
you're building a real time chat app) but is a bit much if you're only
looking for a small subset of its features.
Two, it's reasonably complex. Again, not insurmountably so, but complex enough
that it probably isn't worth hacking together a basic SocketIO client in your
REPL of choice if you just want to test something real quick. And on the
server side, it's complex enough that you'll probably want to avoid rolling
your own if possible. This becomes especially troublesome if you already have
a working application and you just want to sprinkle in a little real-time
interactivity, rather than building your whole application around that
assumption. In my (admittedly limited) experience, the existing SocketIO
integrations don't always play very nicely with the existing server
frameworks, although if you stick to the most widely-used stuff you're
probably fine.
And honestly, it's just a lot of complexity to introduce if you just want a
simple event stream. You could argue that you don't even need websockets for
this - Server Sent Events are a thing, as are simple HTTP requests with a
streaming response - but in this day and age, the set of solutions to the
problem of "persistent communication between server and client" has pretty
firmly coalesed around websockets. They're there, they're supported, there
are lots of libraries - you might as well just go with the flow.
## Ok, so what are you saying?
Basically, that we need something that's _like_ SocketIO but lighter-weight,
and solves a more limited set of problems. Specifically, the problem I'm
looking to solve is _event streams_ - given a web service and a client, how
does the client detect that _things are happening_ in the background so that
it can update itself accordingly?
The use cases for this are pretty extensive. Most obviously, you can use it to
implement a notifications system on pretty much any webapp that
involves "users want to know when things happen," which is pretty broad.
Maybe you're running an ecommerce shop and you want to notify your customers
that the item they've had their eye on is back in stock. Or you've just
opened up a new promotion and they should check it out. Maybe you're running
a website that displays a stock ticker, and you need up-to-the-second data on
stock prices. Or you've got a dashboard with some kind of real-time
monitoring chart, whatever it's measuring, and you want to keep it up to date
with a minimum of overhead. Pub/sub is a powerful concept, which is why
people keep re-implementing it. Like I'm doing here. Get over it.
## But why can't I just use SocketIO for this, again?
I mean, you _can._ But SocketIO does so much _more_ than simple pub/sub.
Connection multiplexing, automatic reconnects, receipt acknowledgements, the
list goes on. All of these features are great if, again, you are implementing
an instant messenger. They even have a feature called "rooms," which mentions
in its documentation that it "makes it easy to implement private messages,"
among other things, so it's pretty clear who their target is.
And it's a great target! Lots of people need instant messaging. Every website
in the world seems to pop up a bubble saying "Hi, I'm [friendly-sounding
name]! Do you want to talk about giving us money?" 30 seconds after you hit
their page. Everyone with a customer-service team has discovered or will soon
discover that most issues are easier to resolve over text than over the
phone, especially if your CS is outsourced to some foriegn country so your
reps all have an accent. SocketIO exists for a reason, and it's a very good
reason, and if that's what you need then go for it, knock yourself out.
But if all you need is a simple event stream with pub/sub semantics, then keep
reading, because that's what I want to talk about.
## Fine. Make your pitch, I'll listen.
The protocol I'm imagining should solve three basic problems:
* Authentication
* Connection management (keepalive, automatic reconnects)
* ...and the actual pub/sub itself, of course.
Let's go through each of these in turn.
### Authentication
The protocol purists might start to set up a bit of a racket here. Ignore
those guys, they suck. Listen, every web-based protocol in the world should
at least be _aware_ of the question of authentication. Maybe the awareness
should stop at "that's being handled so I don't need to think about it," but
at least that much is pretty necessary. I don't know exactly how much Web
traffic is authenticated vs. unauthenticated (ask Cloudflare, they might) but
according to some quick Googling an Akamai bigwig said in 2019 that 83% of
the traffic they see is API traffic. I imagine that API traffic is
overwhelmingly authenticated, and when you factor in the fact that a large
part of the rest is social media, which is also going to be
mostly-authenticated, I imagine you'll end up with somewhere between "a whole
lot" and "the vast majority."
So you need authentication, and websockets don't give it to you. Well, they
leave it open, kinda - RFC 6455 says that the websocket opening handshake
(which starts with an HTTP request) can include
> Optionally, other header
fields, such as those used to send cookies or request authentication to a
server.
But in practice, this still kinda sucks. It sucks because you can't
have _one_ authentication method that's dead-simple and works for everybody.
Either you're coming from the browser, in which case you're stuck with
session cookies or URL params and that's it, or you're coming from some kind
of scripting environment where you'd love to be able to just stick a bearer
token in the `Authorization` header like everybody else does, but that's not
how the browser does it so tough luck.
The only solution that works easily with all clients is to put the
auth in a URL param. So let's just do that. Unfortunately, that creates a new
issue: we can't just use a plain bearer token any more, because now it's in
the URL and URL's go all sorts of places - server logs, CDNs, browser address
bars, etc. Probably the best thing to do here is to simply sign the URL
[a la AWS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/RESTAuthentication.html#RESTAuthenticationQueryStringAuth).
Fortunately, since we're only dealing with a very specific type of request,
we don't need to bother with the authenticated headers business that AWS
does.
The browser has very limited capabilities when it comes to modifying the request, so we should probably stick to a signature that can be included directly in the URL as a couple of querystring params.

18
src/routes/all.json.js Normal file
View File

@ -0,0 +1,18 @@
const posts = import.meta.globEager('./_posts/*.svx');
export let postData = [];
for (const path in posts) {
const urlPath = path.slice(8, -4)
posts[path].metadata.path = urlPath;
postData.push(posts[path].metadata);
}
postData.sort((a, b) => {
// sorting in reverse, so we flip the intuitive order
if (a.date > b.date) return -1;
if (a.date < b.date) return 1;
return 0;
})
export async function get() {
return {body: postData};
}

11
src/routes/index.js Normal file
View File

@ -0,0 +1,11 @@
import { postData } from './all.json.js';
const path = postData[0].path;
export async function get({ host }) {
const resp = await fetch(`http://${host}${path}`);
return {
status: resp.status,
headers: {'Content-Type': 'text/html'},
body: await resp.text()
}
}

37
src/routes/posts.svelte Normal file
View File

@ -0,0 +1,37 @@
<script context="module">
export async function load({ page, fetch }) {
const resp = await fetch(`http://${page.host}/all.json`);
return {
props: {
postData: await resp.json()
}
}
}
</script>
<script>
export let postData;
</script>
<style>
#posts {
text-align: center;
margin-top: 1.25rem;
}
.post-link {
text-decoration: none;
}
.post-link:hover {
text-decoration: underline;
}
</style>
<div id="posts">
<h1>All Posts</h1>
{#each postData as post}
<p>
<a class="post-link" href="{post.path}"><h3>{post.title}</h3></a>
</p>
{/each}
</div>

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

74
static/style.css Normal file
View File

@ -0,0 +1,74 @@
/* ### TYPOGRAPHY ### */
html {
font-family: 'Tajawal', sans-serif;
font-size: 20px;
line-height: 1.3;
letter-spacing: -0.005em;
}
body {
margin: 0;
}
h1, h2, h3, h4, h5, h6 {
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Ubuntu, Arial, sans-serif;;
font-weight: 600;
color: #464646;
}
h1, h2 {
margin-bottom: 0.75rem;
}
h3, h4 {
margin-bottom: 0.5rem;
}
h5, h6 {
margin-bottom: 0;
}
h1 {
font-variant: petite-caps;
}
h3 {
font-size: 1.2rem;
}
h4 {
font-size: 1.1rem;
}
h5, h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
/*ul, ol {
margin: 0.5rem 0;
}*/
code {
background: #eee;
border-radius: 0.2rem;
font-family: Consolas, monospace;
font-size: 0.85rem;
padding: 0 0.15rem;
}
pre {
padding: 0.5rem;
line-height: 1.1;
border-radius: 0.15rem;
}
pre > code {
padding: 0;
font-size: 0.8rem;
background-color: transparent;
}

13
svelte.config.js Normal file
View File

@ -0,0 +1,13 @@
import { mdsvex } from "mdsvex";
const config = {
extensions: ['.svelte', '.svx'],
preprocess: mdsvex(),
kit: {
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
}
};
export default config;