icons/bsky-favicon.png
icons/bsky-favicon.png
This is a binary file and will not be displayed.
icons/microcosm-favicon.png
icons/microcosm-favicon.png
This is a binary file and will not be displayed.
+1026
-197
index.html
+1026
-197
index.html
···
3
3
<head>
4
4
<meta charset="utf-8">
5
5
<meta name="viewport" content="width=device-width"/>
6
+
<title>atproto PDS & account debugger</title>
7
+
<meta name="description" content="Quick diagnostics for PDS hosts, handles, relay connections, handles, DIDs, ..." />
8
+
9
+
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/intersect@3.x.x/dist/cdn.min.js"></script>
6
10
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
7
11
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css"/>
8
12
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
···
17
21
import {
18
22
DohJsonHandleResolver,
19
23
WellKnownHandleResolver,
20
-
} from 'https://esm.sh/@atcute/identity-resolver@1.2.0';
24
+
} from 'https://esm.sh/@atcute/identity-resolver@1.2.1';
21
25
22
26
window.SimpleQuery = service => {
23
27
const client = new Client({ handler: simpleFetchHandler({ service }) });
···
28
32
return (...args) => ok(client.post(...args));
29
33
};
30
34
window.isXrpcErr = e => e instanceof ClientResponseError;
35
+
36
+
window.isBeforeNow = iso => new Date(iso) < new Date();
31
37
32
38
window.dnsResolver = new DohJsonHandleResolver({
33
39
dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query',
···
37
43
window.slingshot = window.SimpleQuery('https://slingshot.microcosm.blue');
38
44
window.relays = [
39
45
{
40
-
name: 'Bluesky production',
46
+
name: 'Bluesky',
47
+
icon: './icons/bsky-favicon.png',
41
48
hostname: 'bsky.network',
49
+
note: 'current',
50
+
missingApis: {
51
+
['com.atproto.sync.getHostStatus']: 'missing API (old relay code)',
52
+
['com.atproto.sync.getRepoStatus']: 'missing API (old relay code)',
53
+
},
42
54
},
43
55
{
44
-
name: 'Bluesky sync1.1 East',
45
-
hostname: 'relay1.us-east.bsky.network',
56
+
name: 'Microcosm Montreal',
57
+
icon: './icons/microcosm-favicon.png',
58
+
hostname: 'relay.fire.hose.cam',
59
+
},
60
+
{
61
+
name: 'Microcosm France',
62
+
icon: './icons/microcosm-favicon.png',
63
+
hostname: 'relay3.fr.hose.cam',
46
64
},
47
65
{
48
-
name: 'Bluesky sync1.1 West',
49
-
hostname: 'relay1.us-west.bsky.network',
66
+
name: 'Upcloud',
67
+
icon: 'https://upcloud.com/media/android-chrome-512x512-2-150x150.png',
68
+
hostname: 'relay.upcloud.world',
50
69
},
51
70
{
52
71
name: 'Blacksky',
72
+
icon: 'https://blacksky.community/static/favicon-32x32.png',
53
73
hostname: 'atproto.africa',
74
+
missingApis: {
75
+
['com.atproto.sync.getHostStatus']: 'API not yet deployed',
76
+
['com.atproto.sync.getRepoStatus']: 'API not implemented',
77
+
},
54
78
},
55
79
{
56
-
name: 'Microcosm Montreal',
57
-
hostname: 'relay.fire.hose.cam',
80
+
name: 'Bluesky East',
81
+
icon: './icons/bsky-favicon.png',
82
+
note: 'future',
83
+
hostname: 'relay1.us-east.bsky.network',
58
84
},
59
85
{
60
-
name: 'Microcosm France',
61
-
hostname: 'relay3.fr.hose.cam',
86
+
name: 'Bluesky West',
87
+
icon: './icons/bsky-favicon.png',
88
+
note: 'future',
89
+
hostname: 'relay1.us-west.bsky.network',
62
90
},
63
91
];
92
+
93
+
window.regionalModAccounts = [ // https://github.com/mary-ext/atproto-scraping?tab=readme-ov-file#bluesky-labelers
94
+
'https://mod-br.bsky.app',
95
+
'https://mod-de.bsky.app',
96
+
'https://mod-in.bsky.app',
97
+
'https://mod-ru.bsky.app',
98
+
'https://mod-tr.bsky.app',
99
+
];
100
+
101
+
window.bskyAccountDeathLabels = {
102
+
['needs-review']: 'Automated action, cleared by manual review from Bluesky moderation team. Your content can ve accessed via direct links on Bluesky, but invisible in feeds and replies to posts.',
103
+
['!suspend']: 'Moderation action from Bluesky moderation team. Makes your content inaccessible on the Bluesky app.',
104
+
['!takedown']: 'Moderation action from Bluesky moderation team. Makes your content inaccessible on the Bluesky app.',
105
+
['!hide']: 'Almost always used with !takedown, makes your content inaccessible on the Bluesky app.',
106
+
// other labels shouldn't cause problems that make you think your pds is broken
107
+
// 'spam': just hides replies by default + makes your posts click-through
108
+
// 'intolerant', etc: similar to spam
109
+
};
110
+
111
+
if (window.blehYeahReady) blehYeahReady();
112
+
else window.yeahBlehIsReady = true;
64
113
</script>
65
114
115
+
<style>
116
+
body:not(.ready) .hide-until-ready,
117
+
body.ready .show-until-ready {
118
+
display: none;
119
+
}
120
+
</style>
121
+
66
122
<script>
67
123
document.addEventListener('alpine:init', () => {
124
+
if (window.yeahBlehIsReady) {
125
+
document.body.classList.add('ready');
126
+
} else {
127
+
window.blehYeahReady = () => document.body.classList.add('ready');
128
+
}
129
+
68
130
Alpine.data('debug', () => ({
69
131
// form input
70
132
identifier: '',
···
117
179
}
118
180
}
119
181
} else {
120
-
this.handle = this.identifier;
182
+
this.handle = this.identifier.toLowerCase();
121
183
let data;
122
184
try {
123
185
data = await window.slingshot('com.bad-example.identity.resolveMiniDoc', {
124
-
params: { identifier: this.identifier },
186
+
params: { identifier: this.identifier.toLowerCase() },
125
187
});
126
188
this.did = data.did;
127
189
this.pds = data.pds;
···
146
208
description: null,
147
209
accounts: [],
148
210
accountsComplete: false,
211
+
version: null,
149
212
150
213
async init() {
151
214
await this.update(pds);
···
156
219
this.error = null;
157
220
this.description = null;
158
221
this.accounts = [];
222
+
this.accountsComplete = false;
223
+
this.version = null;
224
+
225
+
if (!pds) {
226
+
this.loadingDesc = false;
227
+
return;
228
+
}
229
+
159
230
let query = window.SimpleQuery(pds);
160
231
try {
161
232
this.description = await query('com.atproto.server.describeServer');
···
167
238
console.error(e);
168
239
}
169
240
}
241
+
let health
242
+
try {
243
+
health = await query('_health');
244
+
this.version = health.version;
245
+
} catch (e) {
246
+
if (window.isXrpcErr(e)) {
247
+
this.error = e.error;
248
+
} else {
249
+
this.error = 'Failed to reach (see console)';
250
+
console.error(e);
251
+
}
252
+
}
170
253
let accountsRes;
171
254
try {
172
255
accountsRes = await query('com.atproto.sync.listRepos', {
173
-
params: { limit: 7 },
256
+
params: { limit: 100 },
174
257
});
175
258
this.accounts = accountsRes.repos;
176
-
this.accountsComplete == !accountsRes.cursor;
259
+
260
+
// weird thing with the ref pds: it *always* has a cursor on the first page
261
+
if (accountsRes.cursor) {
262
+
// so grab a second page just to see if there really is a second page
263
+
try {
264
+
const secondPage = await query('com.atproto.sync.listRepos', {
265
+
params: { limit: 1, cursor: accountsRes.cursor },
266
+
});
267
+
this.accountsComplete = !secondPage.cursor || secondPage.repos.length == 0;
268
+
} catch (e) {
269
+
// we're in a niche spot. ignore errors and look at the original (faulty) cursor
270
+
this.accountsComplete = !accountsRes.cursor; // ๐คทโโ๏ธ
271
+
}
272
+
} else {
273
+
this.accountsComplete = true;
274
+
}
275
+
177
276
} catch (e) {
178
277
if (window.isXrpcErr(e)) {
179
278
this.error = e.error;
···
190
289
loading: false,
191
290
error: null,
192
291
status: null,
292
+
expectedErrorInfo: null,
193
293
reqCrawlStatus: null,
194
294
reqCrawlError: null,
195
295
···
201
301
this.loading = true;
202
302
this.error = null;
203
303
this.status = null;
304
+
this.expectedError = false;
204
305
const query = window.SimpleQuery(`https://${relay.hostname}`);
205
306
const hostname = pds.split('://')[1];
206
307
let data;
···
210
311
});
211
312
this.status = data.status;
212
313
} catch(e) {
213
-
if (window.isXrpcErr(e)) {
314
+
if (relay.missingApis?.['com.atproto.sync.getHostStatus']) {
315
+
this.error = 'Can\'t check';
316
+
this.expectedErrorInfo = relay.missingApis?.['com.atproto.sync.getHostStatus'];
317
+
} else if (window.isXrpcErr(e)) {
214
318
this.error = e.error;
215
319
} else {
216
320
this.error = 'Failed to check (see console)';
···
272
376
this.loading = false;
273
377
},
274
378
}));
379
+
380
+
Alpine.data('didToHandle', did => ({
381
+
loading: false,
382
+
error: null,
383
+
handle: null,
384
+
async load() {
385
+
loading = true;
386
+
error = null;
387
+
handle = null;
388
+
let data;
389
+
try {
390
+
data = await window.slingshot('com.bad-example.identity.resolveMiniDoc', {
391
+
params: { identifier: did },
392
+
});
393
+
this.handle = data.handle;
394
+
} catch (e) {
395
+
if (window.isXrpcErr(e)) {
396
+
this.error = e.error;
397
+
} else {
398
+
this.error = 'failed (see console)';
399
+
console.error(e);
400
+
}
401
+
}
402
+
loading = false;
403
+
}
404
+
}));
405
+
406
+
Alpine.data('didRepoState', (did, pds) => ({
407
+
loading: false,
408
+
error: null,
409
+
state: null,
410
+
411
+
async init() {
412
+
await this.checkRepoState(did, pds);
413
+
},
414
+
async checkRepoState(did, pds) {
415
+
this.loading = true;
416
+
this.error = null;
417
+
this.state = null;
418
+
419
+
if (!did || !pds) {
420
+
this.loading = false;
421
+
return;
422
+
}
423
+
const query = window.SimpleQuery(pds);
424
+
try {
425
+
this.state = await query('com.atproto.sync.getRepoStatus', {
426
+
params: { did },
427
+
});
428
+
} catch (e) {
429
+
if (window.isXrpcErr(e)) {
430
+
this.error = e.error;
431
+
} else {
432
+
this.error = 'failed (see console)';
433
+
console.error(e);
434
+
}
435
+
}
436
+
this.loading = false;
437
+
},
438
+
}));
439
+
440
+
Alpine.data('repoSize', () => ({
441
+
lading: false,
442
+
error: null,
443
+
size: null,
444
+
445
+
async loadRepoForSize(did, pds) {
446
+
this.loading = true;
447
+
448
+
if (!did || !pds) {
449
+
this.loading = false;
450
+
return;
451
+
}
452
+
const query = window.SimpleQuery(pds);
453
+
try {
454
+
const res = await query('com.atproto.sync.getRepo', {
455
+
params: { did },
456
+
as: 'blob',
457
+
});
458
+
let bytes = res.size;
459
+
let mbs = bytes / Math.pow(2, 20);
460
+
this.size = mbs.toFixed(1);
461
+
} catch (e) {
462
+
if (window.isXrpcErr(e)) {
463
+
this.error = e.error;
464
+
} else {
465
+
this.error = 'failed (see console)';
466
+
console.error(e);
467
+
}
468
+
}
469
+
this.loading = false;
470
+
},
471
+
}));
472
+
473
+
Alpine.data('relayCheckRepo', (did, relay) => ({
474
+
loading: false,
475
+
error: null,
476
+
status: null,
477
+
expectedErrorInfo: null,
478
+
479
+
async init() {
480
+
await this.check(did, relay);
481
+
},
482
+
483
+
async check(did, relay) {
484
+
this.loading = true;
485
+
this.error = null;
486
+
this.status = null;
487
+
this.expectedErrorInfo = null;
488
+
489
+
const query = window.SimpleQuery(`https://${relay.hostname}`);
490
+
try {
491
+
this.status = await query('com.atproto.sync.getRepoStatus', {
492
+
params: { did },
493
+
});
494
+
} catch(e) {
495
+
if (relay.missingApis?.['com.atproto.sync.getRepoStatus']) {
496
+
this.error = 'Can\'t check';
497
+
this.expectedErrorInfo = relay.missingApis?.['com.atproto.sync.getRepoStatus'];
498
+
} else if (window.isXrpcErr(e)) {
499
+
this.error = e.error;
500
+
} else {
501
+
this.error = 'Failed to check (see console)';
502
+
console.error(e);
503
+
}
504
+
}
505
+
506
+
this.loading = false;
507
+
},
508
+
509
+
revStatus(repoRev) {
510
+
if (
511
+
!repoRev ||
512
+
!(this.status && this.status.rev)
513
+
) return null;
514
+
515
+
if (this.status.rev < repoRev) return 'behind';
516
+
if (this.status.rev === repoRev) return 'current';
517
+
if (this.status.rev > repoRev) return 'ahead';
518
+
}
519
+
}));
520
+
521
+
Alpine.data('modLabels', did => ({
522
+
loading: false,
523
+
error: null,
524
+
regionalErrors: [],
525
+
labels: [],
526
+
527
+
async init() {
528
+
this.loading = true;
529
+
this.error = null;
530
+
this.regionalErrors = [];
531
+
this.labels = [];
532
+
533
+
const query = window.SimpleQuery('https://mod.bsky.app');
534
+
535
+
try {
536
+
const res = await query('com.atproto.label.queryLabels', {
537
+
params: { uriPatterns: [did] },
538
+
});
539
+
this.labels = res.labels ?? [];
540
+
// TODO: handle cursors?
541
+
542
+
for (const region of window.regionalModAccounts) {
543
+
// intentionally no await, these come in async
544
+
// (...and could get messy if we start re-checking labels before they're done)
545
+
this.checkRegionLabels(region);
546
+
}
547
+
} catch (e) {
548
+
if (window.isXrpcErr(e)) {
549
+
this.error = e.error;
550
+
} else {
551
+
this.error = 'Failed to check (see console)';
552
+
console.error(e);
553
+
}
554
+
}
555
+
this.loading = false;
556
+
},
557
+
558
+
async checkRegionLabels(labeler) {
559
+
const query = window.SimpleQuery(labeler);
560
+
try {
561
+
const res = await query('com.atproto.label.queryLabels', {
562
+
params: { uriPatterns: [did] },
563
+
});
564
+
if (res?.labels?.length > 0) this.labels.push(...res.labels);
565
+
} catch (e) {
566
+
if (window.isXrpcErr(e)) {
567
+
this.regionalErrors.push(`${labeler}: ${e.error}`);
568
+
} else {
569
+
this.regionalErrors.push(`Failed to check ${labeler} (see console)`);
570
+
console.error(`labeler: ${labeler}`, e);
571
+
}
572
+
}
573
+
}
574
+
}));
575
+
576
+
Alpine.data('pdsHistory', (did, currentPds) => ({
577
+
loading: false,
578
+
error: null,
579
+
history: [],
580
+
581
+
async init() {
582
+
this.loading = true;
583
+
this.error = null;
584
+
this.history = [];
585
+
try {
586
+
const res = await fetch(`https://plc.directory/${did}/log/audit`);
587
+
if (res.ok) {
588
+
const log = await res.json();
589
+
let prev = null;
590
+
for (op of log) {
591
+
let opPds = null;
592
+
const services = op.operation.services;
593
+
if (services) {
594
+
const app = services.atproto_pds;
595
+
if (app) {
596
+
opPds = app.endpoint;
597
+
}
598
+
}
599
+
if (opPds === prev) continue;
600
+
prev = opPds;
601
+
this.history.push({
602
+
pds: opPds,
603
+
date: op.createdAt,
604
+
});
605
+
}
606
+
this.history.reverse();
607
+
if (this.history[0]) this.history[0].current = true;
608
+
} else {
609
+
this.error = `${res.status}: ${await res.text()}`;
610
+
}
611
+
} catch (e) {
612
+
this.error = 'failed to get history';
613
+
console.error(e);
614
+
}
615
+
this.loading = false;
616
+
},
617
+
}));
618
+
619
+
Alpine.data('handleHistory', (did, currentHandle) => ({
620
+
loading: false,
621
+
error: null,
622
+
history: [],
623
+
624
+
async init() {
625
+
this.loading = true;
626
+
this.error = null;
627
+
this.history = [];
628
+
try {
629
+
const res = await fetch(`https://plc.directory/${did}/log/audit`);
630
+
if (res.ok) {
631
+
const log = await res.json();
632
+
let prev = null;
633
+
for (op of log) {
634
+
let opHandle = null;
635
+
if (op.operation.alsoKnownAs) {
636
+
for (aka of op.operation.alsoKnownAs) {
637
+
if (aka.startsWith("at://")) {
638
+
opHandle = aka.slice("at://".length);
639
+
break;
640
+
}
641
+
}
642
+
}
643
+
if (opHandle === prev) continue;
644
+
prev = opHandle;
645
+
this.history.push({
646
+
handle: opHandle,
647
+
date: op.createdAt,
648
+
});
649
+
}
650
+
this.history.reverse();
651
+
} else {
652
+
this.error = `${res.status}: ${await res.text()}`;
653
+
}
654
+
} catch (e) {
655
+
this.error = 'failed to get history';
656
+
console.error(e);
657
+
}
658
+
this.loading = false;
659
+
},
660
+
}));
275
661
})
276
662
</script>
277
663
</head>
278
-
<body x-data="debug">
279
-
<div class="hero bg-base-200">
664
+
<body x-data="debug" class="bg-base-200">
665
+
<div class="hero bg-base-200 p-8">
280
666
<div class="hero-content flex-col">
281
-
<h1>PDS Debugger</h1>
282
-
283
-
<p>Work in progress!</p>
284
-
<details class="text-xs">
285
-
<summary>Would be nice</summary>
286
-
<ul>
287
-
<li>anything that actually works</li>
288
-
<li>firehose listener for missing pds events</li>
289
-
<li>jetstream listener for missing pds events</li>
290
-
<li>check relays for account status</li>
291
-
<li>check relays for pds state</li>
292
-
<li>plc: check old pds hosts for active account state</li>
293
-
</ul>
294
-
</details>
295
-
<details class="text-xs">
296
-
<summary>Limitations</summary>
297
-
<ul>
298
-
<li>it's all client-side</li>
299
-
</ul>
300
-
</details>
301
-
302
-
<div class="card bg-base-100 w-full max-w-sm shrink-0 shadow-2xl">
303
-
<div class="card-body">
304
-
<form @submit.prevent="await diagnose()">
305
-
<label>
306
-
Enter an atproto handle, DID, or HTTPS PDS URL
307
-
<input
308
-
class="input"
309
-
x-model="identifier"
310
-
:disabled="identifierLoading"
311
-
autofocus
312
-
/>
313
-
</label>
314
-
</form>
667
+
<h1 class="text-2xl mb-8">PDS Debugger</h1>
668
+
<p class="show-until-ready"><em>Loading…</em></p>
669
+
<form class="hide-until-ready" @submit.prevent="await diagnose()">
670
+
<label class="text-sm text-primary" for="identifier">
671
+
atproto handle, DID, or HTTPS PDS URL
672
+
</label>
673
+
<br/>
674
+
<div class="join">
675
+
<input
676
+
id="identifier"
677
+
class="input join-item"
678
+
x-model="identifier"
679
+
:disabled="identifierLoading"
680
+
autofocus
681
+
/>
682
+
<button
683
+
class="btn btn-primary join-item"
684
+
type="submit"
685
+
>go</button>
315
686
</div>
316
-
</div>
687
+
</form>
688
+
</div>
689
+
</div>
690
+
691
+
<div class="w-full max-w-lg mx-auto">
692
+
<template x-if="identifierError">
693
+
<p>uh oh: <span x-text="identifierError"></span></p>
694
+
</template>
317
695
318
-
<template x-if="identifierError">
319
-
<p>uh oh: <span x-text="identifierError"></span></p>
320
-
</template>
696
+
<template x-if="pds != null">
321
697
322
-
<template x-if="pds != null">
323
-
<div class="card bg-base-100 w-full max-w-lg shrink-0 shadow-2xl">
324
-
<div class="card-body">
325
-
<h2 class="card-title">
326
-
<span class="badge badge-secondary">PDS</span>
327
-
<span x-text="pds"></span>
328
-
</h2>
698
+
<div class="card bg-base-100 w-full max-w-2xl shrink-0 shadow-2xl m-4">
699
+
<div class="card-body">
700
+
<h2 class="card-title">
701
+
<span class="badge badge-secondary">PDS</span>
702
+
<span x-text="pds"></span>
703
+
</h2>
329
704
330
-
<div
331
-
x-data="pdsCheck(pds)"
332
-
x-init="$watch('pds', v => update(v))"
333
-
>
334
-
<h3 class="text-lg">
335
-
Server
336
-
<span
337
-
x-show="description !== null"
338
-
class="badge badge-sm badge-soft badge-success"
339
-
>online</span>
340
-
</h3>
341
-
<p x-show="loadingDesc">Loading…</p>
342
-
<p x-show="error" class="text-warning" x-text="error"></p>
343
-
<template x-if="description !== null">
705
+
<div
706
+
x-data="pdsCheck(pds)"
707
+
x-init="$watch('pds', v => update(v))"
708
+
>
709
+
<h3 class="text-lg mt-3">
710
+
Server
711
+
<span
712
+
x-show="description !== null"
713
+
class="badge badge-sm badge-soft badge-success"
714
+
>online</span>
715
+
</h3>
716
+
<p x-show="loadingDesc">Loading…</p>
717
+
<p x-show="error" class="text-warning" x-text="error"></p>
718
+
<template x-if="description !== null">
719
+
<div>
344
720
<div class="overflow-x-auto">
345
721
<table class="table table-xs">
346
722
<tbody>
347
723
<tr>
348
-
<td class="text-sm">Open registration</td>
349
-
<td
350
-
class="text-sm"
351
-
x-text="!description.inviteCodeRequired"
352
-
></td>
724
+
<td class="text-sm">
725
+
Open registration:
726
+
<span
727
+
x-text="!description.inviteCodeRequired"
728
+
></span>
729
+
</td>
730
+
</tr>
731
+
<tr>
732
+
<td class="text-sm">
733
+
Version:
734
+
<code
735
+
class="text-xs"
736
+
x-text="version"
737
+
></code>
738
+
</td>
353
739
</tr>
354
740
</tbody>
355
741
</table>
356
-
<h4 class="font-bold">
357
-
Accounts
358
-
</h4>
742
+
</div>
743
+
<h4 class="font-bold">
744
+
Accounts
745
+
</h4>
746
+
<div class="overflow-x-auto overflow-y-auto max-h-26">
359
747
<table class="table table-xs">
360
748
<tbody>
361
749
<template x-for="account in accounts">
···
383
771
class="badge badge-sm badge-soft badge-warning"
384
772
></span>
385
773
</td>
774
+
<td
775
+
x-data="didToHandle(account.did)"
776
+
x-intersect:enter.once="load"
777
+
>
778
+
<span x-show="loading">Loading…</span>
779
+
<span x-show="error !== null" x-text="error"></span>
780
+
<a
781
+
href="#"
782
+
class="link"
783
+
@click.prevent="goto(handle)"
784
+
x-show="handle !== null"
785
+
x-text="`@${handle}`"
786
+
></a>
787
+
</td>
386
788
</tr>
387
789
</template>
388
-
<template x-if="!accountsComplete">
790
+
<template x-if="!loadingDesc && !accountsComplete">
389
791
<tr>
390
-
<td colspan="2" class="text-sm text-warning-content">
391
-
(account list clipped)
792
+
<td colspan="2" class="text-xs text-warning-content">
793
+
(more accounts not shown)
392
794
</td>
393
795
</tr>
394
796
</template>
395
797
</tbody>
396
798
</table>
397
799
</div>
398
-
</template>
399
-
</div>
800
+
</div>
801
+
</template>
802
+
</div>
803
+
804
+
<h3 class="text-lg mt-3">Relay host status</h3>
805
+
<div class="overflow-x-auto">
806
+
<table class="table table-xs">
807
+
<tbody>
808
+
<template x-for="relay in window.relays">
809
+
<tr
810
+
x-data="relayCheckHost(pds, relay)"
811
+
x-init="$watch('pds', pds => check(pds, relay))"
812
+
>
813
+
<td class="text-sm">
814
+
<div class="tooltip tooltip-right" :data-tip="relay.hostname">
815
+
<img
816
+
class="inline-block h-4 w-4"
817
+
:src="relay.icon"
818
+
alt=""
819
+
/>
820
+
<span x-text="relay.name"></span>
821
+
<span
822
+
x-show="!!relay.note"
823
+
x-text="relay.note"
824
+
class="badge badge-soft badge-neutral badge-xs"
825
+
></span>
826
+
</div>
827
+
</td>
828
+
<td>
829
+
<template x-if="loading">
830
+
<em>loading…</em>
831
+
</template>
832
+
<template x-if="error">
833
+
<div
834
+
class="text-xs"
835
+
:class="expectedErrorInfo
836
+
? 'text-info tooltip tooltip-left cursor-help'
837
+
: 'text-warning'"
838
+
:data-tip="expectedErrorInfo"
839
+
>
840
+
<span x-text="error"></span>
841
+
<span
842
+
x-show="!!expectedErrorInfo"
843
+
class="badge badge-soft badge-info badge-xs"
844
+
>i</span>
845
+
</div>
846
+
</template>
847
+
<template x-if="status">
848
+
<span
849
+
x-text="status"
850
+
class="badge badge-sm"
851
+
:class="status === 'active' && 'badge-soft badge-success'"
852
+
></span>
853
+
</template>
854
+
</td>
855
+
<td>
856
+
<div x-show="status !== 'active' && !expectedErrorInfo">
857
+
<button
858
+
x-show="reqCrawlStatus !== 'done'"
859
+
class="btn btn-xs btn-ghost whitespace-nowrap"
860
+
:disabled="reqCrawlStatus === 'loading'"
861
+
@click="requestCrawl(pds, relay)"
862
+
>
863
+
request crawl
864
+
</button>
865
+
<span
866
+
x-show="reqCrawlError !== null"
867
+
x-text="reqCrawlError"
868
+
class="text-xs text-warning"
869
+
></span>
870
+
<button
871
+
x-show="reqCrawlError === null && reqCrawlStatus === 'done'"
872
+
class="btn btn-xs btn-soft btn-primary whitespace-nowrap"
873
+
@click="check"
874
+
>
875
+
refresh
876
+
</button>
877
+
</div>
878
+
</td>
879
+
</tr>
880
+
</template>
881
+
</tbody>
882
+
</table>
883
+
</div>
884
+
</div>
885
+
</div>
886
+
</template>
887
+
888
+
<template x-if="did != null">
889
+
<div class="card bg-base-100 w-full max-w-2xl shrink-0 shadow-2xl m-4">
890
+
<div class="card-body">
891
+
<h2 class="card-title">
892
+
<span class="badge badge-secondary">DID</span>
893
+
<code x-text="did"></code>
894
+
</h2>
895
+
<template x-if="pds != null">
896
+
<div x-data="didRepoState(did, pds)">
897
+
<h3 class="text-lg mt-3">
898
+
Repo
899
+
<span
900
+
x-show="state && state.active"
901
+
class="badge badge-sm badge-soft badge-success"
902
+
>active</span>
903
+
</h3>
904
+
<div class="overflow-x-auto">
905
+
<table class="table table-xs">
906
+
<tbody>
907
+
<tr>
908
+
<td class="text-sm">
909
+
Rev:
910
+
<code x-text="state && state.rev"></code>
911
+
</td>
912
+
</tr>
913
+
<tr>
914
+
<td class="text-sm">
915
+
Size:
916
+
<span x-data="repoSize">
917
+
<template x-if="loading">
918
+
<em>loading…</em>
919
+
</template>
920
+
<template x-if="error">
921
+
<span class="text-xs text-warning" x-text="error"></span>
922
+
</template>
923
+
<template x-if="size">
924
+
<code>
925
+
<span x-text="size"></span> MiB
926
+
</code>
927
+
</template>
928
+
<template x-if="!size && !error && !loading">
929
+
<button
930
+
class="btn btn-xs btn-soft btn-primary"
931
+
@click.prevent="loadRepoForSize(did, pds)"
932
+
>load</button>
933
+
</template>
934
+
</span>
935
+
</td>
936
+
</tr>
937
+
<tr>
938
+
<td class="text-sm">
939
+
PDS:
940
+
<a
941
+
href="#"
942
+
class="link"
943
+
@click.prevent="goto(pds)"
944
+
x-text="pds"
945
+
></a>
946
+
</td>
947
+
</tr>
948
+
<!--<tr>
949
+
<td
950
+
class="text-sm"
951
+
x-data="repoMonitor(did, pds)"
952
+
>
953
+
<button
954
+
class="btn btn-xs btn-success"
955
+
>Start live monitoring</button>
956
+
</td>
957
+
</tr>-->
958
+
</tbody>
959
+
</table>
960
+
</div>
400
961
401
-
<h3 class="text-lg">Relay host status</h3>
402
-
<div class="overflow-x-auto">
403
-
<table class="table table-xs">
404
-
<tbody>
405
-
<template x-for="relay in window.relays">
406
-
<tr
407
-
x-data="relayCheckHost(pds, relay)"
408
-
x-init="$watch('pds', pds => check(pds, relay))"
409
-
>
410
-
<td x-text="relay.name" class="text-sm"></td>
411
-
<td>
962
+
<h3 class="text-lg mt-3">
963
+
Relay repo status
964
+
</h3>
965
+
<div class="overflow-x-auto">
966
+
<table class="table table-xs">
967
+
<tbody>
968
+
<template x-for="relay in window.relays">
969
+
<tr
970
+
x-data="relayCheckRepo(did, relay)"
971
+
x-init="$watch('pds', pds => check(did, relay))"
972
+
>
973
+
<td class="text-sm">
974
+
<div class="tooltip tooltip-right" :data-tip="relay.hostname">
975
+
<img
976
+
class="inline-block h-4 w-4"
977
+
:src="relay.icon"
978
+
alt=""
979
+
/>
980
+
<span x-text="relay.name"></span>
981
+
<span
982
+
x-show="!!relay.note"
983
+
x-text="relay.note"
984
+
class="badge badge-neutral badge-soft badge-xs"
985
+
></span>
986
+
</div>
987
+
</td>
412
988
<template x-if="loading">
413
-
<em>loading…</em>
989
+
<td>
990
+
<em>loading…</em>
991
+
</td>
414
992
</template>
415
993
<template x-if="error">
416
-
<span
417
-
x-text="error"
418
-
class="text-xs text-warning"
419
-
></span>
994
+
<td>
995
+
<div
996
+
class="text-xs"
997
+
:class="expectedErrorInfo
998
+
? 'text-info tooltip tooltip-left cursor-help'
999
+
: 'text-warning'"
1000
+
:data-tip="expectedErrorInfo"
1001
+
>
1002
+
<span x-text="error"></span>
1003
+
<span
1004
+
x-show="!!expectedErrorInfo"
1005
+
class="badge badge-soft badge-info badge-xs"
1006
+
>i</span>
1007
+
</div>
1008
+
</td>
420
1009
</template>
421
1010
<template x-if="status">
422
-
<span
423
-
x-text="status"
424
-
class="badge badge-sm"
425
-
:class="status === 'active' && 'badge-soft badge-success'"
426
-
></span>
1011
+
<td>
1012
+
<span
1013
+
x-show="status.active"
1014
+
class="badge badge-sm badge-soft badge-success"
1015
+
>
1016
+
active
1017
+
</span>
1018
+
<span
1019
+
x-show="!status.active"
1020
+
x-text="status.status"
1021
+
class="badge badge-sm badge-soft badge-warning"
1022
+
></span>
1023
+
</td>
1024
+
</template>
1025
+
<template x-if="revStatus(state && state.rev)">
1026
+
<td x-data="{ asdf: revStatus(state.rev) }">
1027
+
<span
1028
+
x-show="asdf === 'current'"
1029
+
class="badge badge-sm badge-soft badge-success"
1030
+
>current</span>
1031
+
<span
1032
+
x-show="asdf === 'behind'"
1033
+
class="badge badge-sm badge-soft badge-warning tooltip tooltip-left"
1034
+
:data-tip="status.rev"
1035
+
>behind</span>
1036
+
<span
1037
+
x-show="asdf === 'ahead'"
1038
+
class="badge badge-sm badge-soft badge-success tooltip tooltip-left"
1039
+
:data-tip="`Account may have updated between checks? ${status.rev}`"
1040
+
>ahead</span>
1041
+
</td>
1042
+
</template>
1043
+
<template x-if="!revStatus(state && state.rev)">
1044
+
<td></td>
1045
+
</template>
1046
+
</tr>
1047
+
</template>
1048
+
</tbody>
1049
+
</table>
1050
+
</div>
1051
+
1052
+
<div x-data="modLabels(did)">
1053
+
<h3 class="text-lg mt-3">
1054
+
Labels
1055
+
</h3>
1056
+
<div class="overflow-x-auto">
1057
+
<table class="table table-xs">
1058
+
<tbody>
1059
+
<template x-if="loading">
1060
+
<tr>
1061
+
<td>Loading…</td>
1062
+
</tr>
1063
+
</template>
1064
+
<template x-if="error">
1065
+
<tr>
1066
+
<td>Error: <span x-text="error"></span></td>
1067
+
</tr>
1068
+
</template>
1069
+
<template x-if="!loading && !error && labels.length === 0">
1070
+
<tr>
1071
+
<td class="text-xs">
1072
+
<em>No Bluesky moderation labels found</em>
1073
+
</td>
1074
+
</tr>
1075
+
</template>
1076
+
<template x-for="label in labels">
1077
+
<template x-if="!!label">
1078
+
<tr x-data="{ expired: isBeforeNow(label.exp) }">
1079
+
<td>
1080
+
<span x-show="label.neg">removed</span>
1081
+
<code
1082
+
x-text="label.cts.split('T')[0]"
1083
+
:title="label.cts"
1084
+
></code>
1085
+
</td>
1086
+
<td>
1087
+
<template x-if="!!label.exp">
1088
+
<span x-text="expired ? 'expired' : 'expires'"></span>
1089
+
<code
1090
+
x-text="label.exp.split('T')[0]"
1091
+
:title="label.exp"
1092
+
></code>
1093
+
</template>
1094
+
</td>
1095
+
<td>
1096
+
<code
1097
+
x-text="label.val"
1098
+
class="badge badge-sm badge-soft"
1099
+
:class="(label.neg || expired)
1100
+
? 'badge-neutral line-through'
1101
+
: !!window.bskyAccountDeathLabels[label.val]
1102
+
? 'badge-warning'
1103
+
: 'badge-info'"
1104
+
:title="label.neg
1105
+
? 'label negated'
1106
+
: expired
1107
+
? 'label expired'
1108
+
: window.bskyAccountDeathLabels[label.val] ?? ''"
1109
+
></code>
1110
+
</td>
1111
+
<td
1112
+
x-data="didToHandle(label.src)"
1113
+
x-intersect:enter.once="load"
1114
+
>
1115
+
<span x-show="loading">Loading…</span>
1116
+
<span x-show="error !== null" x-text="error"></span>
1117
+
<a
1118
+
href="#"
1119
+
class="link"
1120
+
@click.prevent="goto(handle)"
1121
+
x-show="handle !== null"
1122
+
x-text="`@${handle}`"
1123
+
></a>
1124
+
</td>
1125
+
</tr>
1126
+
</template>
1127
+
</template>
1128
+
</tbody>
1129
+
</table>
1130
+
</div>
1131
+
<template x-for="error in regionalErrors">
1132
+
<p
1133
+
x-text="error"
1134
+
class="text-xs text-warning"
1135
+
></p>
1136
+
</template>
1137
+
</div>
1138
+
1139
+
<template x-if="did.startsWith('did:plc:')">
1140
+
<div x-data="pdsHistory(did, pds)">
1141
+
<h3 class="text-lg mt-3">
1142
+
PLC PDS history
1143
+
</h3>
1144
+
<div class="overflow-x-auto">
1145
+
<table class="table table-xs">
1146
+
<tbody>
1147
+
<template x-if="loading">
1148
+
<tr>
1149
+
<td>Loading…</td>
1150
+
</tr>
1151
+
</template>
1152
+
<template x-if="error">
1153
+
<tr>
1154
+
<td>Error: <span x-text="error"></span></td>
1155
+
</tr>
1156
+
</template>
1157
+
<template x-if="!loading && !error && history.length === 0">
1158
+
<tr>
1159
+
<td class="text-sm">
1160
+
<em>no previous PDS</em>
1161
+
</td>
1162
+
</tr>
1163
+
</template>
1164
+
<template x-for="event in history">
1165
+
<tr x-data="didRepoState(did, event.pds)">
1166
+
<td>
1167
+
<code x-text="event.date.split('T')[0]"></code>
1168
+
</td>
1169
+
<td>
1170
+
<a
1171
+
href="#"
1172
+
class="link"
1173
+
@click.prevent="goto(event.pds)"
1174
+
x-text="event.pds"
1175
+
></a>
1176
+
</td>
1177
+
<template x-if="event.current">
1178
+
<td>
1179
+
<span
1180
+
x-show="state && !state.active"
1181
+
x-text="state && state.status"
1182
+
class="badge badge-sm badge-soft badge-warning"
1183
+
></span>
1184
+
<span
1185
+
x-show="state && state.active"
1186
+
class="badge badge-sm badge-soft badge-success"
1187
+
>current</span>
1188
+
</td>
1189
+
</template>
1190
+
<template x-if="!event.current">
1191
+
<td>
1192
+
<span
1193
+
x-show="state && !state.active"
1194
+
x-text="state && state.status"
1195
+
class="badge badge-sm badge-soft badge-success"
1196
+
></span>
1197
+
<span
1198
+
x-show="state && state.active"
1199
+
class="badge badge-sm badge-soft badge-warning"
1200
+
>active</span>
1201
+
</td>
1202
+
</template>
1203
+
</tr>
427
1204
</template>
428
-
</td>
429
-
<td>
430
-
<div x-show="status !== 'active'">
431
-
<button
432
-
x-show="reqCrawlStatus !== 'done'"
433
-
class="btn btn-xs btn-ghost whitespace-nowrap"
434
-
:disabled="reqCrawlStatus === 'loading'"
435
-
@click="requestCrawl(pds, relay)"
436
-
>
437
-
request crawl
438
-
</button>
439
-
<span
440
-
x-show="reqCrawlError !== null"
441
-
x-text="reqCrawlError"
442
-
class="text-xs text-warning"
443
-
></span>
444
-
<button
445
-
x-show="reqCrawlError === null && reqCrawlStatus === 'done'"
446
-
class="btn btn-xs btn-soft btn-primary whitespace-nowrap"
447
-
@click="check"
448
-
>
449
-
refresh
450
-
</button>
451
-
</div>
452
-
</td>
453
-
</tr>
454
-
</template>
455
-
</tbody>
456
-
</table>
1205
+
</tbody>
1206
+
</table>
1207
+
</div>
1208
+
</div>
1209
+
</template>
457
1210
</div>
458
-
</div>
1211
+
</template>
459
1212
</div>
460
-
</template>
1213
+
</div>
1214
+
</template>
1215
+
1216
+
<template x-if="handle !== null">
1217
+
<div class="card bg-base-100 w-full max-w-2xl shrink-0 shadow-2xl m-4">
1218
+
<div
1219
+
x-data="checkHandle(handle)"
1220
+
x-init="$watch('handle', h => updateHandle(h))"
1221
+
class="card-body"
1222
+
>
1223
+
<h2 class="card-title">
1224
+
<span class="badge badge-secondary">Handle</span>
1225
+
<span x-text="handle"></span>
1226
+
</h2>
461
1227
462
-
<template x-if="did != null">
463
-
<div class="card bg-base-100 w-full max-w-lg shrink-0 shadow-2xl">
464
-
<div class="card-body">
465
-
<h2 class="card-title">
466
-
<span class="badge badge-secondary">DID</span>
467
-
<code x-text="did"></code>
468
-
</h2>
469
-
<p>(wip)</p>
1228
+
<h3 class="text-lg mt-3">
1229
+
Resolution
1230
+
</h3>
1231
+
<p x-show="loading" class="text-i">Loading…</p>
1232
+
<div x-show="!loading" class="overflow-x-auto">
1233
+
<table class="table table-xs">
1234
+
<tbody>
1235
+
<tr>
1236
+
<td class="text-sm">DNS</td>
1237
+
<td class="text-sm">
1238
+
<code x-text="dnsDid"></code>
1239
+
</td>
1240
+
<td>
1241
+
<div
1242
+
class="badge badge-sm badge-soft badge-neutral"
1243
+
x-show="dnsErr !== null"
1244
+
x-text="dnsErr"
1245
+
></div>
1246
+
</td>
1247
+
</tr>
1248
+
<tr>
1249
+
<td class="text-sm">Http</td>
1250
+
<td class="text-sm">
1251
+
<code x-text="httpDid"></code>
1252
+
</td>
1253
+
<td>
1254
+
<div
1255
+
class="badge badge-sm badge-soft badge-neutral"
1256
+
x-show="httpErr !== null"
1257
+
x-text="httpErr"
1258
+
></div>
1259
+
</td>
1260
+
</tr>
1261
+
</tbody>
1262
+
</table>
470
1263
</div>
471
-
</div>
472
-
</template>
473
1264
474
-
<template x-if="handle != null">
475
-
<div class="card bg-base-100 w-full max-w-lg shrink-0 shadow-2xl">
476
-
<div
477
-
x-data="checkHandle(handle)"
478
-
x-init="$watch('handle', h => updateHandle(h))"
479
-
class="card-body"
480
-
>
481
-
<h2 class="card-title">
482
-
<span class="badge badge-secondary">Handle</span>
483
-
<span x-text="handle"></span>
484
-
</h2>
485
-
<p x-show="loading" class="text-i">Loading…</p>
486
-
<div x-show="!loading" class="overflow-x-auto">
487
-
<table class="table table-xs">
488
-
<tbody>
489
-
<tr>
490
-
<td class="text-sm">DNS</td>
491
-
<td class="text-sm">
492
-
<code x-text="dnsDid"></code>
493
-
</td>
494
-
<td>
495
-
<div
496
-
class="badge badge-sm badge-soft badge-neutral"
497
-
x-show="dnsErr !== null"
498
-
x-text="dnsErr"
499
-
></div>
500
-
</td>
501
-
</tr>
502
-
<tr>
503
-
<td class="text-sm">Http</td>
504
-
<td class="text-sm">
505
-
<code x-text="httpDid"></code>
506
-
</td>
507
-
<td>
508
-
<div
509
-
class="badge badge-sm badge-soft badge-neutral"
510
-
x-show="httpErr !== null"
511
-
x-text="httpErr"
512
-
></div>
513
-
</td>
514
-
</tr>
515
-
</tbody>
516
-
</table>
1265
+
<template x-if="did !== null && did.startsWith('did:plc:')">
1266
+
1267
+
<div x-data="handleHistory(did, handle)">
1268
+
<h3 class="text-lg mt-3">
1269
+
PLC handle history
1270
+
</h3>
1271
+
<div class="overflow-x-auto">
1272
+
<table class="table table-xs">
1273
+
<tbody>
1274
+
<template x-if="loading">
1275
+
<tr>
1276
+
<td>Loading…</td>
1277
+
</tr>
1278
+
</template>
1279
+
<template x-if="error">
1280
+
<tr>
1281
+
<td>Error: <span x-text="error"></span></td>
1282
+
</tr>
1283
+
</template>
1284
+
<template x-if="!loading && !error && history.length === 0">
1285
+
<tr>
1286
+
<td class="text-sm">
1287
+
<em>no previous handle</em>
1288
+
</td>
1289
+
</tr>
1290
+
</template>
1291
+
<template x-for="event in history">
1292
+
<tr>
1293
+
<td>
1294
+
<code x-text="event.date.split('T')[0]"></code>
1295
+
</td>
1296
+
<td>
1297
+
<a
1298
+
href="#"
1299
+
class="link"
1300
+
@click.prevent="goto(event.handle)"
1301
+
x-text="event.handle"
1302
+
></a>
1303
+
</td>
1304
+
</tr>
1305
+
</template>
1306
+
</tbody>
1307
+
</table>
1308
+
</div>
517
1309
</div>
518
-
</div>
1310
+
1311
+
</template>
519
1312
</div>
520
-
</template>
521
-
</div>
1313
+
</div>
1314
+
</template>
522
1315
</div>
1316
+
1317
+
1318
+
1319
+
<div class="footer text-xs sm:footer-horizontal text-neutral mt-32 p-8 max-w-2xl mx-auto">
1320
+
<nav>
1321
+
<h3 class="footer-title mt-3">Current limitations</h3>
1322
+
<p>PDS hosts without CORS will fail tests.</p>
1323
+
<p>Bluesky relay is missing API endpoints.</p>
1324
+
<p>Blacksky relay is also missing API endpoints.</p>
1325
+
<p>The requestCrawl button is not well tested.</p>
1326
+
1327
+
<h3 class="footer-title mt-3">Future features</h3>
1328
+
<p>Firehose listener</p>
1329
+
<p>URL routing</p>
1330
+
<p>Less strict identity resolution</p>
1331
+
</nav>
1332
+
1333
+
<nav>
1334
+
<h3 class="footer-title mt-3">Places</h3>
1335
+
<p><a href="https://tangled.org/microcosm.blue/pds-debug">Source code (tangled.org)</a></p>
1336
+
<p><a href="https://discord.gg/Vwamex5UFS">Discord (microcosm)</a></p>
1337
+
<p><a href="https://pdsmoover.com/">PDS Moover</a></p>
1338
+
<p><a href="https://microcosm.blue">microcosm</a></p>
1339
+
1340
+
<h3 class="footer-title mt-3">Made by</h3>
1341
+
<p>
1342
+
<a href="https://bsky.app/profile/did:plc:hdhoaan3xa3jiuq4fg4mefid">fig</a>
1343
+
<a href="https://github.com/sponsors/uniphil">(sponsor)</a>
1344
+
</p>
1345
+
<p>
1346
+
<a href="https://bsky.app/profile/did:plc:rnpkyqnmsw4ipey6eotbdnnf">bailey</a>
1347
+
<a href="https://github.com/sponsors/fatfingers23">(sponsor)</a>
1348
+
</p>
1349
+
</nav>
1350
+
</div>
1351
+
523
1352
</body>
524
1353
</html>
+5
readme.md
+5
readme.md
+13
useful-accounts.txt
+13
useful-accounts.txt
···
1
+
some accounts that show things useful for testing the debugger
2
+
3
+
4
+
Labels
5
+
6
+
- did:plc:bnwrgnvwkg2n5cbvk4xodb3h | !hide | no other labels
7
+
- did:plc:qhl3vg5tmwey536z2fil2lrh | !hide | from moderation-tr.bsky.app
8
+
- did:plc:fsmaoqqnm6knqh4cuphb4jow | !hide, ~!takedown | takedown negated
9
+
- did:plc:iv3yod6zf2j4zaakq6qyiz46 | !takedown |
10
+
- did:plc:nwrcwcrhpkgrqqvkg3lmaqky | ~needs-review, ~!takedown | both negated
11
+
- did:plc:2tinwgqvf4asiwh36ii6ko7l | needs-review | expired
12
+
- did:plc:5plqrpw3x6j5wzaosssqams7 | spam | no other labels
13
+
- did:plc:tqww7jdpqx5tb3w435fugmxi | intolerant |