OverviewFilesCommitsIssuesDocsReleasesPeers

ANProto commits / a736d97

interactive aproto generator
a736d97654dbbeffe3abe03dcf0c2fd09f5abb91
by Ev Bogue <ev@evbogue.com> · 2025-08-20T19:16:14.000Z

Changed files (4) +365 -2

a.js +1 -2
……@@ -19,9 +19,8 @@ a.hash = async (d) => {
1919 );
2020};
2121
22a.sign = async (d, k) => {
22a.sign = async (h, k) => {
2323 const ts = Date.now();
24 const h = await a.hash(d);
2524 const s = encode(
2625 nacl.sign(new TextEncoder().encode(ts + h), decode(k.substring(44))),
2726 );
example.js +146 -0
……@@ -0,0 +1,146 @@
1import { Hono } from "jsr:@hono/hono";
2import { serveStatic } from 'jsr:@hono/hono/deno'
3
4import { foot, head } from "./template.js";
5
6const app = new Hono();
7
8app.get('/', async (c) => {
9 const content = `
10 <div id="scroller">
11 <div class='message'>
12 <h1>AProto</h1>
13 <p><a href="./try">Try AProto</a></p>
14 <p><a href="https://wiredove.net/">Wiredove</a></p>
15 </div>
16 </div>
17 `
18
19 const html = await head('Index') + content + await foot()
20 return await c.html(html)
21})
22
23app.get('/try', async (c) => {
24
25 const body = `<body>
26 <div id='scroller'>
27 <div class='message'>
28 <h1>Try AProto</h1>
29
30 <p><em>An Interactive Demonstration</em></p>
31
32 <p><strong>Step 1.</strong> Generate an ed25519 keypair</p>
33
34 <code>const kp = await a.gen()</code>
35
36 <input style='width: 100%;' id='key' placeholder='Make a keypair'></input>
37
38 <button id='but'>Generate keypair</button>
39
40 </div>
41
42 <div class='message'>
43
44 <p><strong>Step 2.</strong> Hash your blob with sha256</p>
45
46 <code>const hash = await a.hash(content)</code>
47
48 <input style='width: 100%;' id='content' placeholder='Write a message'></input>
49
50 <button id='hash'>Generate hash</button>
51
52 <span id='sha256'></span>
53
54 </div>
55
56 <div class='message'>
57
58 <p><strong>Step 3.</strong> Sign an AProto message</p>
59
60 <code>const sig = await a.sign(hash, keypair)</code>
61
62 <input style='width: 100%' id='sig'></input>
63
64 <button id='sign'>Sign message</button>
65
66 </div>
67 <div class="message">
68
69 <p><strong>Step 4.</strong> Open the AProto message</p>
70
71 <code>const opened = await a.open(msg)</code>
72
73 <input style='width: 100%;' id='openen'></input>
74
75 <button id='open'>Open</button>
76
77 </div>
78 <div class="message">
79
80 <p><strong>Step 5.</strong> Retrieve the blob</p>
81
82 <span id='msg'></span>
83
84 <button id='get'>Get</button>
85
86 </div>
87</div>
88</body>
89
90<script type='module'>
91import { a } from './a.js'
92
93const key = document.getElementById('key')
94const button = document.getElementById('but')
95
96button.onclick = async () => {
97 key.value = await a.gen()
98}
99
100const content = document.getElementById('content')
101const hashbutton = document.getElementById('hash')
102const sha = document.getElementById('sha256')
103
104let blobs = []
105
106hashbutton.onclick = async () => {
107 sha.textContent = await a.hash(content.value)
108 blobs[sha.textContent] = content.value
109}
110
111const siginput = document.getElementById('sig')
112const signbutton = document.getElementById('sign')
113
114signbutton.onclick = async () => {
115 siginput.value = await a.sign(sha.textContent, key.value)
116}
117
118const openbutton = document.getElementById('open')
119
120const openen = document.getElementById('openen')
121
122openbutton.onclick = async () => {
123 openen.value = await a.open(siginput.value)
124}
125
126const msgspan = document.getElementById('msg')
127const getbutton = document.getElementById('get')
128
129getbutton.onclick = async () => {
130 msgspan.textContent = blobs[openen.value.substring(13)]
131 if (openen.value.substring(13) == sha.textContent) {
132 msgspan.textContent = msgspan.textContent + ' ✅'
133 } else {
134 msgspan.textContent = msgspan.textContent + ' ❌'
135 }
136}
137
138</script>`
139
140 const html = await head('Try it') + body + await foot()
141 return await c.html(html)
142})
143
144app.use('*', serveStatic({ root: './' }))
145
146export default app
style.css +189 -0
……@@ -0,0 +1,189 @@
1body {
2 background-color: #f2f2f2;
3 color: #444;
4 font-family: "Source Sans 3", sans-serif;
5 max-width: 100%;
6 margin-top: 45px;
7 margin-bottom: 10em;
8}
9
10#scroller {max-width: 680px; margin-left: auto; margin-right: auto;}
11
12blockquote { border-left: 5px solid #f5f5f5; margin-left: none; padding-left: 10px; color: #777; }
13
14p, h1, h2, h3, h4, h5, h6 { margin-top: 0px; margin-bottom: 2px; }
15
16pre {
17 //color: #dd1144;
18 background: #f5f5f5;
19 width: 100%;
20 display: block;
21}
22
23code {
24 background: #f5f5f5;
25 padding: 5px;
26 border-radius: 5px;
27 display: inline-block;
28 vertical-align: bottom;
29}
30
31code, pre {
32 font-family: "Roboto Mono", monospace;
33 font-size: .9em;
34 overflow: auto;
35 word-break: break-all;
36 word-wrap: break-word;
37 white-space: pre;
38 white-space: -moz-pre-wrap;
39 white-space: pre-wrap;
40 white-space: pre\9;
41}
42
43button {
44 font-size: .85em;
45 background: #fff;
46 background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
47 border: 1px solid #e4e4e4;
48 padding: 5px 10px 5px 10px;
49 border-radius: 5px;
50}
51
52hr { border: 1px solid #e4e4e4;}
53
54button:hover {
55 background: #f2f2f2;
56 cursor: pointer;
57}
58
59textarea, input {
60 font-size: 1em;
61 font-family: "Source Sans 3", sans-serif;
62 border: 1px solid #f8f8f8;
63 border-radius: 5px;
64 background: #f8f8f8;
65 color: #555;
66 padding: 5px;
67 //box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
68}
69
70.composer { margin-left: 37px; margin-top: 0;}
71
72textarea:hover, textarea:focus, input:hover, input:focus, {
73 background: transparent;
74}
75
76textarea:focus, input:focus {
77 outline: none !important;
78}
79
80textarea {
81 margin-top: 5px;
82 margin-bottom: 5px;
83 width: 99%;
84 height: 150px;
85}
86
87a {
88 color: #045fd0;
89 text-decoration: none;
90}
91
92a:hover {
93 color: #8d82fe;
94}
95
96img {width: 95%; margin: 1em;}
97
98.material-symbols-outlined { color: #666; vertical-align: middle; font-size: 18px; cursor: pointer;}
99
100iframe {
101 width: 100%;
102 border: 1px solid #e4e4e4;
103 border-radius: 5px;
104 margin-top: 5px;
105 height: 275px;
106}
107
108#navbar {
109 padding-top: .5em;
110 padding-left: 1em;
111 padding-bottom: .5em;
112 position: fixed;
113 width: 100%;
114 z-index: 1;
115 top: 0;
116 left: 0;
117 background-color: rgba(242,242,242,0.5);
118 backdrop-filter: blur(10px);
119 border-bottom: 1px solid #333;
120}
121
122.message {
123 padding: .75em;
124 margin-top: 5px;
125 background: #f8f8f8;
126 border: 1px solid #f5f5f5;
127 min-height: 35px;
128 border-radius: 5px;
129 overflow: hidden;
130}
131
132.message:hover {
133 border: 1px solid #eee;
134}
135
136@media (prefers-color-scheme: dark) {
137 body {
138 background-color: #181818;
139 color: #ccc;
140 }
141 #navbar { background-color: rgba(24,24,24,0.2); }
142 .message { background-color: #222; border: 1px solid #1e1e1e;}
143 .message:hover { border: 1px solid #333;}
144
145 textarea, input, iframe { background: #222; color: #f5f5f5; border: 1px solid #222;}
146
147 button { color: #ccc; background: #333; border: 1px solid #444;}
148 button:hover { background: #222;}
149 hr { border: 1px solid #333;}
150 pre, code { background: #333; color: #f5f5f5;}
151 a {color: #50afe4;}
152}
153
154.content {margin-top: 5px;}
155
156.message, .message > * {
157 animation: fadein .5s;
158}
159
160@keyframes fadein {
161 from { opacity: 0; }
162 to { opacity: 1; }
163}
164
165.pubkey {
166 color: #9da0a4;
167 font-family: monospace;
168}
169
170.avatar, .avatar_small {
171 border-radius: 100%;
172 margin: 0px;
173 margin-right: 10px;
174 object-fit: cover;
175 vertical-align: top;
176}
177
178.avatar {
179 height: 33px;
180 width: 33px;
181}
182
183.avatar_small { height: 25px; width: 25px;}
184
185.breadcrumbs { font-size: 1em; }
186
187.avatarlink { font-weight: 600;}
188.unstyled { color: #ccc;}
189.hljs { padding: 10px; border-radius: 5px; background: #555; color: #f2f2f2;}
template.js +29 -0
……@@ -0,0 +1,29 @@
1export const head = async (title) => {
2 return await `
3<!doctype html>
4<html>
5 <head>
6 <title>A Protocol | ${title}</title>
7 <link rel='stylesheet' href='./style.css' type='text/css' />
8 <meta name='viewport' content='width=device-width initial-scale=1' />
9 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
10 <link rel="preconnect" href="https://fonts.googleapis.com">
11 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12 <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
13 <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap" rel="stylesheet">
14
15 </head>
16 <body>
17 <div id='navbar'>
18 <img src='https://wiredove.net/doveorange_sm.png' class='avatar_small' style='vertical-align: middle;'></strong>
19 <strong><span style="color: #fe7a00;">A</span>Proto</strong>
20 </div>
21`;
22};
23
24export const foot = async () => {
25 return await `
26 </body>
27</html>
28 `;
29};