+280
index.html
+280
index.html
···
1
+
<!doctype html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="utf-8">
5
+
<meta name="viewport" content="width=device-width"/>
6
+
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
7
+
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css"/>
8
+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
9
+
10
+
<script type="module">
11
+
import { Client, ClientResponseError, ok, simpleFetchHandler } from 'https://esm.sh/@atcute/client@4.1.1';
12
+
window.SimpleQuery = service => {
13
+
const client = new Client({ handler: simpleFetchHandler({ service }) });
14
+
return (...args) => ok(client.get(...args));
15
+
};
16
+
window.SimpleProc = service => {
17
+
const client = new Client({ handler: simpleFetchHandler({ service }) });
18
+
return (...args) => ok(client.post(...args));
19
+
};
20
+
window.isXrpcErr = e => e instanceof ClientResponseError;
21
+
window.slingshot = window.SimpleQuery('https://slingshot.microcosm.blue');
22
+
window.relays = [
23
+
{
24
+
name: 'Bluesky production',
25
+
hostname: 'bsky.network',
26
+
},
27
+
{
28
+
name: 'Bluesky sync1.1 East',
29
+
hostname: 'relay1.us-east.bsky.network',
30
+
},
31
+
{
32
+
name: 'Bluesky sync1.1 West',
33
+
hostname: 'relay1.us-west.bsky.network',
34
+
},
35
+
{
36
+
name: 'Blacksky',
37
+
hostname: 'atproto.africa',
38
+
},
39
+
{
40
+
name: 'Microcosm Montreal',
41
+
hostname: 'relay.fire.hose.cam',
42
+
},
43
+
{
44
+
name: 'Microcosm France',
45
+
hostname: 'relay3.fr.hose.cam',
46
+
},
47
+
];
48
+
</script>
49
+
50
+
<script>
51
+
document.addEventListener('alpine:init', () => {
52
+
Alpine.data('debug', () => ({
53
+
// form input
54
+
identifier: '',
55
+
56
+
// state
57
+
identifierLoading: false,
58
+
identifierError: null,
59
+
60
+
// stuff to check
61
+
pds: null,
62
+
did: null,
63
+
handle: null,
64
+
65
+
async diagnose() {
66
+
this.identifierLoading = true;
67
+
this.identifierError = null;
68
+
this.pds = null;
69
+
this.did = null;
70
+
if (this.identifier.startsWith('https://')) {
71
+
this.pds = this.identifier;
72
+
} else {
73
+
if (this.identifier.startsWith('at://')) {
74
+
this.identifier = this.identifier.slice('at://'.length);
75
+
}
76
+
if (this.identifier.startsWith('did:')) {
77
+
this.did = this.identifier;
78
+
} else {
79
+
let data;
80
+
try {
81
+
data = await window.slingshot('com.bad-example.identity.resolveMiniDoc', {
82
+
params: { identifier: this.identifier },
83
+
});
84
+
this.did = data.did;
85
+
this.pds = data.pds;
86
+
} catch (e) {
87
+
if (window.isXrpcErr(e)) {
88
+
this.identifierError = e.error;
89
+
if (e.message) this.description += ` ${e.description}`;
90
+
} else {
91
+
this.identifierError = 'Failed to resolve identifier, see console for error.';
92
+
console.error(e);
93
+
}
94
+
}
95
+
}
96
+
}
97
+
this.identifierLoading = false;
98
+
},
99
+
}));
100
+
101
+
Alpine.data('relayCheckHost', (pds, relay) => ({
102
+
loading: false,
103
+
error: null,
104
+
status: null,
105
+
reqCrawlStatus: null,
106
+
reqCrawlError: null,
107
+
108
+
async init() {
109
+
await this.check();
110
+
},
111
+
112
+
async check() {
113
+
this.loading = true;
114
+
this.error = null;
115
+
this.status = null;
116
+
let query = window.SimpleQuery(`https://${relay.hostname}`);
117
+
const hostname = pds.split('://')[1];
118
+
let data;
119
+
try {
120
+
data = await query('com.atproto.sync.getHostStatus', {
121
+
params: { hostname },
122
+
});
123
+
this.status = data.status;
124
+
} catch(e) {
125
+
if (window.isXrpcErr(e)) {
126
+
this.error = e.error;
127
+
} else {
128
+
this.error = 'Failed to check (see console)';
129
+
console.error(e);
130
+
}
131
+
}
132
+
this.loading = false;
133
+
this.reqCrawlStatus = null;
134
+
this.reqCrawlError = null;
135
+
},
136
+
137
+
async requestCrawl() {
138
+
this.reqCrawlStatus = "loading";
139
+
const proc = window.SimpleProc(`https://${relay.hostname}`);
140
+
const hostname = pds.split('://')[1];
141
+
let data;
142
+
try {
143
+
data = await proc('com.atproto.sync.requestCrawl', {
144
+
input: { hostname },
145
+
});
146
+
} catch (e) {
147
+
if (window.isXrpcErr(e)) {
148
+
this.reqCrawlError = e.error;
149
+
} else {
150
+
this.reqCrawlError = 'failed (see console)';
151
+
console.error(e);
152
+
}
153
+
}
154
+
this.reqCrawlStatus = "done";
155
+
},
156
+
}))
157
+
})
158
+
</script>
159
+
</head>
160
+
<body x-data="debug">
161
+
<div class="hero bg-base-200">
162
+
<div class="hero-content flex-col">
163
+
<h1>PDS Debugger</h1>
164
+
165
+
<p>Work in progress!</p>
166
+
<details class="text-xs">
167
+
<summary>Would be nice</summary>
168
+
<ul>
169
+
<li>anything that actually works</li>
170
+
<li>firehose listener for missing pds events</li>
171
+
<li>jetstream listener for missing pds events</li>
172
+
<li>check relays for account status</li>
173
+
<li>check relays for pds state</li>
174
+
<li>plc: check old pds hosts for active account state</li>
175
+
</ul>
176
+
</details>
177
+
<details class="text-xs">
178
+
<summary>Limitations</summary>
179
+
<ul>
180
+
<li>it's all client-side</li>
181
+
</ul>
182
+
</details>
183
+
184
+
<div class="card bg-base-100 w-full max-w-sm shrink-0 shadow-2xl">
185
+
<div class="card-body">
186
+
<form @submit.prevent="await diagnose()">
187
+
<label>
188
+
Enter an atproto handle, DID, or HTTPS PDS URL
189
+
<input
190
+
class="input"
191
+
x-model="identifier"
192
+
:disabled="identifierLoading"
193
+
autofocus
194
+
/>
195
+
</label>
196
+
</form>
197
+
</div>
198
+
</div>
199
+
200
+
<template x-if="identifierError">
201
+
<p>uh oh: <span x-text="identifierError"></span></p>
202
+
</template>
203
+
204
+
<template x-if="pds != null">
205
+
<div class="card bg-base-100 w-full max-w-lg shrink-0 shadow-2xl">
206
+
<div class="card-body">
207
+
<h2 class="card-title">
208
+
<span class="badge badge-secondary">PDS</span>
209
+
<span x-text="pds"></span>
210
+
</h2>
211
+
212
+
<h3 class="text-lg">Relay host status</h3>
213
+
<div class="overflow-x-auto">
214
+
<table class="table table-xs">
215
+
<tbody>
216
+
<template x-for="relay in window.relays">
217
+
<tr x-data="relayCheckHost(pds, relay)">
218
+
<td x-text="relay.name" class="text-sm"></td>
219
+
<td>
220
+
<template x-if="loading">
221
+
<em>loading…</em>
222
+
</template>
223
+
<template x-if="error">
224
+
<span
225
+
x-text="error"
226
+
class="text-xs text-warning"
227
+
></span>
228
+
</template>
229
+
<template x-if="status">
230
+
<span
231
+
x-text="status"
232
+
class="badge badge-sm"
233
+
:class="status === 'active' && 'badge-soft badge-success'"
234
+
></span>
235
+
</template>
236
+
</td>
237
+
<td>
238
+
<div x-show="status !== 'active'">
239
+
<button
240
+
x-show="reqCrawlStatus !== 'done'"
241
+
class="btn btn-xs btn-ghost whitespace-nowrap"
242
+
:disabled="reqCrawlStatus === 'loading'"
243
+
@click="requestCrawl"
244
+
>
245
+
request crawl
246
+
</button>
247
+
<span
248
+
x-show="reqCrawlError !== null"
249
+
x-text="reqCrawlError"
250
+
class="text-xs text-warning"
251
+
></span>
252
+
<button
253
+
x-show="reqCrawlError === null && reqCrawlStatus === 'done'"
254
+
class="btn btn-xs btn-soft btn-primary whitespace-nowrap"
255
+
@click="check"
256
+
>
257
+
refresh
258
+
</button>
259
+
</div>
260
+
</td>
261
+
</tr>
262
+
</template>
263
+
</tbody>
264
+
</table>
265
+
</div>
266
+
</div>
267
+
</div>
268
+
</template>
269
+
270
+
<template x-if="did != null">
271
+
<div class="card bg-base-100 w-full max-w-sm shrink-0 shadow-2xl">
272
+
<div class="card-body">
273
+
<p x-text="`DID: ${did}`"></p>
274
+
</div>
275
+
</div>
276
+
</template>
277
+
</div>
278
+
</div>
279
+
</body>
280
+
</html>