+1
-2
a.js
+1
-2
a.js
+146
example.js
+146
example.js
···
1
+
import { Hono } from "jsr:@hono/hono";
2
+
import { serveStatic } from 'jsr:@hono/hono/deno'
3
+
4
+
import { foot, head } from "./template.js";
5
+
6
+
const app = new Hono();
7
+
8
+
app.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
+
23
+
app.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'>
91
+
import { a } from './a.js'
92
+
93
+
const key = document.getElementById('key')
94
+
const button = document.getElementById('but')
95
+
96
+
button.onclick = async () => {
97
+
key.value = await a.gen()
98
+
}
99
+
100
+
const content = document.getElementById('content')
101
+
const hashbutton = document.getElementById('hash')
102
+
const sha = document.getElementById('sha256')
103
+
104
+
let blobs = []
105
+
106
+
hashbutton.onclick = async () => {
107
+
sha.textContent = await a.hash(content.value)
108
+
blobs[sha.textContent] = content.value
109
+
}
110
+
111
+
const siginput = document.getElementById('sig')
112
+
const signbutton = document.getElementById('sign')
113
+
114
+
signbutton.onclick = async () => {
115
+
siginput.value = await a.sign(sha.textContent, key.value)
116
+
}
117
+
118
+
const openbutton = document.getElementById('open')
119
+
120
+
const openen = document.getElementById('openen')
121
+
122
+
openbutton.onclick = async () => {
123
+
openen.value = await a.open(siginput.value)
124
+
}
125
+
126
+
const msgspan = document.getElementById('msg')
127
+
const getbutton = document.getElementById('get')
128
+
129
+
getbutton.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
+
144
+
app.use('*', serveStatic({ root: './' }))
145
+
146
+
export default app
+189
style.css
+189
style.css
···
1
+
body {
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
+
12
+
blockquote { border-left: 5px solid #f5f5f5; margin-left: none; padding-left: 10px; color: #777; }
13
+
14
+
p, h1, h2, h3, h4, h5, h6 { margin-top: 0px; margin-bottom: 2px; }
15
+
16
+
pre {
17
+
//color: #dd1144;
18
+
background: #f5f5f5;
19
+
width: 100%;
20
+
display: block;
21
+
}
22
+
23
+
code {
24
+
background: #f5f5f5;
25
+
padding: 5px;
26
+
border-radius: 5px;
27
+
display: inline-block;
28
+
vertical-align: bottom;
29
+
}
30
+
31
+
code, 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
+
43
+
button {
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
+
52
+
hr { border: 1px solid #e4e4e4;}
53
+
54
+
button:hover {
55
+
background: #f2f2f2;
56
+
cursor: pointer;
57
+
}
58
+
59
+
textarea, 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
+
72
+
textarea:hover, textarea:focus, input:hover, input:focus, {
73
+
background: transparent;
74
+
}
75
+
76
+
textarea:focus, input:focus {
77
+
outline: none !important;
78
+
}
79
+
80
+
textarea {
81
+
margin-top: 5px;
82
+
margin-bottom: 5px;
83
+
width: 99%;
84
+
height: 150px;
85
+
}
86
+
87
+
a {
88
+
color: #045fd0;
89
+
text-decoration: none;
90
+
}
91
+
92
+
a:hover {
93
+
color: #8d82fe;
94
+
}
95
+
96
+
img {width: 95%; margin: 1em;}
97
+
98
+
.material-symbols-outlined { color: #666; vertical-align: middle; font-size: 18px; cursor: pointer;}
99
+
100
+
iframe {
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;}
+29
template.js
+29
template.js
···
1
+
export 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
+
24
+
export const foot = async () => {
25
+
return await `
26
+
</body>
27
+
</html>
28
+
`;
29
+
};