.tangled/images/network.webp
.tangled/images/network.webp
This is a binary file and will not be displayed.
+2
-2
README.md
+2
-2
README.md
···
9
9
10
10
- Looking for the old pds moover for simple code to fork
11
11
check [here](https://tangled.org/@baileytownsend.dev/pds-moover/tree/803d8a70b7100c9e14df3402277441050e0f6194), if
12
-
you'd like to see the newer front end check [here](./web/ui-code/src)
12
+
you'd like to see the newer front end check [here](./web-ui)
13
13
- Want to run your own instance of PDS MOOver? [check this docker compose](./compose.selfhost.yml). It should have all
14
14
the
15
15
services in one easy `docker compose up`, just don't forget to create a `.env` from [.env.template](.env.template)
···
43
43
## Do you have a pretty picture to show how the network looks?
44
44
45
45
yes. Thanks to [Orual](https://bsky.app/profile/nonbinary.computer)
46
-

46
+

+1
-1
justfile
+1
-1
justfile
test.compose.yml
test.compose.yml
This is a binary file and will not be displayed.
+1
web-ui/package.json
+1
web-ui/package.json
+10
web-ui/pnpm-lock.yaml
+10
web-ui/pnpm-lock.yaml
···
8
8
9
9
.:
10
10
dependencies:
11
+
'@atcute/atproto':
12
+
specifier: ^3.1.9
13
+
version: 3.1.9
11
14
'@atcute/client':
12
15
specifier: ^4.0.5
13
16
version: 4.0.5
···
72
75
version: 4.52.5
73
76
74
77
packages:
78
+
79
+
'@atcute/atproto@3.1.9':
80
+
resolution: {integrity: sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w==}
75
81
76
82
'@atcute/cbor@2.2.7':
77
83
resolution: {integrity: sha512-/mwAF0gnokOphceZqFq3uzMGdd8sbw5y6bxF8CRutRkCCUcpjjpJc5fkLwhxyGgOveF3mZuHE6p7t/+IAqb7Aw==}
···
1341
1347
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
1342
1348
1343
1349
snapshots:
1350
+
1351
+
'@atcute/atproto@3.1.9':
1352
+
dependencies:
1353
+
'@atcute/lexicons': 1.2.2
1344
1354
1345
1355
'@atcute/cbor@2.2.7':
1346
1356
dependencies:
+8
-8
web-ui/src/app.html
+8
-8
web-ui/src/app.html
···
1
1
<!doctype html>
2
2
<html lang="en">
3
-
<head>
4
-
<meta charset="utf-8" />
5
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
6
-
%sveltekit.head%
7
-
</head>
8
-
<body data-sveltekit-preload-data="hover">
9
-
<div style="display: contents">%sveltekit.body%</div>
10
-
</body>
3
+
<head>
4
+
<meta charset="utf-8"/>
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
6
+
%sveltekit.head%
7
+
</head>
8
+
<body data-sveltekit-preload-data="hover">
9
+
<div style="display: contents">%sveltekit.body%</div>
10
+
</body>
11
11
</html>
+33
web-ui/src/lib/assets/style.css
+33
web-ui/src/lib/assets/style.css
···
89
89
box-sizing: border-box;
90
90
}
91
91
92
+
/* Input group for handle with domain dropdown */
93
+
.input-group {
94
+
display: flex;
95
+
width: 100%;
96
+
}
97
+
98
+
.input-group input {
99
+
flex: 1;
100
+
border-top-right-radius: 0;
101
+
border-bottom-right-radius: 0;
102
+
border-right: none;
103
+
}
104
+
105
+
.input-group .domain-select {
106
+
padding: 8px;
107
+
border: 1px solid rgba(128, 128, 128, 0.5);
108
+
border-top-left-radius: 0;
109
+
border-bottom-left-radius: 0;
110
+
border-top-right-radius: 4px;
111
+
border-bottom-right-radius: 4px;
112
+
background-color: #1a1a1a;
113
+
color: rgba(255, 255, 255, 0.87);
114
+
cursor: pointer;
115
+
min-width: 120px;
116
+
}
117
+
118
+
@media (prefers-color-scheme: light) {
119
+
.input-group .domain-select {
120
+
background-color: #f9f9f9;
121
+
color: #213547;
122
+
}
123
+
}
124
+
92
125
.cow-image {
93
126
height: 150px;
94
127
margin: 20px 0 8px 0;
+122
-20
web-ui/src/routes/moover/+page.svelte
web-ui/src/routes/moover/[[pds]]/+page.svelte
+122
-20
web-ui/src/routes/moover/+page.svelte
web-ui/src/routes/moover/[[pds]]/+page.svelte
···
5
5
import {Migrator} from '@pds-moover/moover';
6
6
import SignThePapers from './SignThePapers.svelte';
7
7
8
+
let {data} = $props();
9
+
10
+
let selectedPds = $derived(data.pdsOptions);
11
+
let cleanSelectedPds = $derived(selectedPds?.did.replace('did:web:', ''));
12
+
//Kept as a "global" state to handle logic of passing the full handle that is used to SignThePapers
13
+
let newHandle = $state('');
14
+
15
+
let selectedDomain = $state(data.intinalDomain);
16
+
17
+
let handlePlaceHolder = $derived(
18
+
selectedPds ? `username${selectedDomain === 'custom' ? '' : `${selectedPds?.availableUserDomains[0]}`} or mydomain.com` : 'username.newpds.com or mycooldomain.com')
19
+
20
+
21
+
$effect(() => {
22
+
if (!selectedPds) return;
23
+
24
+
if (selectedDomain == 'custom') return;
25
+
26
+
27
+
if (formData.newHandle.includes('.')) {
28
+
// When a period is typed, force custom domain selection
29
+
selectedDomain = 'custom';
30
+
} else {
31
+
// If user clears the dot and we have provider domains, fall back to first option
32
+
if ((selectedPds?.availableUserDomains?.length ?? 0) > 0 && selectedDomain === 'custom') {
33
+
selectedDomain = selectedPds!.availableUserDomains[0]!
34
+
}
35
+
}
36
+
});
37
+
8
38
let formData = $state({
9
39
handle: '',
10
40
password: '',
···
14
44
inviteCode: null,
15
45
twoFactorCode: null,
16
46
confirmation: false,
47
+
// Acceptance of provider policies (when required by selected PDS)
48
+
acceptPolicies: false,
17
49
// Advanced options
18
50
createNewAccount: true,
19
51
migrateRepo: true,
···
35
67
let errorMessage: null | string = $state(null);
36
68
let statusMessage: null | string = $state(null);
37
69
70
+
// Links that may require acceptance prior to migration from the selected PDS
71
+
const privacyUrl = $derived(selectedPds?.links?.privacyPolicy);
72
+
const tosUrl = $derived(selectedPds?.links?.termsOfService);
73
+
const requiresAccept = $derived(!!(privacyUrl || tosUrl));
74
+
38
75
const updateStatusHandler = (status: string) => {
39
76
statusMessage = status;
40
77
}
···
49
86
errorMessage = 'Please confirm that you understand the risks of doing an account migration';
50
87
disableSubmit = false;
51
88
return;
89
+
}
90
+
91
+
// If the selected PDS provides policy or privacy links, require explicit acceptance
92
+
if (requiresAccept && !formData.acceptPolicies) {
93
+
errorMessage = 'Please review and accept the providers policies';
94
+
disableSubmit = false;
95
+
return;
96
+
}
97
+
newHandle = formData.newHandle;
98
+
if (selectedPds) {
99
+
//Not happy about this unwrap, but it should always have a value on a legit PDS that I know of
100
+
101
+
formData.newPds = `https://${cleanSelectedPds!}`;
102
+
// Combine username and selected domain for the new handle
103
+
if (selectedDomain !== 'custom') {
104
+
newHandle = formData.newHandle + selectedDomain;
105
+
}
52
106
}
53
107
54
108
try {
···
69
123
migrator.migratePrefs = formData.migratePrefs;
70
124
migrator.migratePlcRecord = formData.migratePlcRecord;
71
125
72
-
console.log(migrator);
126
+
console.log(formData.newPds, newHandle);
73
127
74
128
updateStatusHandler('Starting migration...');
75
129
showStatusMessage = true;
···
78
132
formData.password,
79
133
formData.newPds,
80
134
formData.newEmail,
81
-
formData.newHandle,
135
+
newHandle,
82
136
formData.inviteCode,
83
137
updateStatusHandler,
84
138
formData.twoFactorCode,
···
144
198
145
199
<!-- Second section: New account details -->
146
200
<div class="section">
147
-
<h2>Setup for the new PDS</h2>
148
-
<div class="form-group">
149
-
<label for="new-pds">New PDS (URL):</label>
150
-
<input type="url" id="new-pds" name="newPds" placeholder="https://coolnewpds.com"
151
-
required bind:value={formData.newPds}>
152
-
</div>
201
+
<h2>{selectedPds ? `Setup for ${cleanSelectedPds}` : 'Setup for the new PDS'}</h2>
202
+
{#if !selectedPds}
203
+
<div class="form-group">
204
+
<label for="new-pds">New PDS (URL):</label>
205
+
<input type="url" id="new-pds" name="newPds" placeholder="https://coolnewpds.com"
206
+
required bind:value={formData.newPds}>
207
+
</div>
208
+
{/if}
153
209
154
210
<div class="form-group">
155
211
<label for="new-email">New Email:</label>
156
-
<input type="email" id="new-email" name="newEmail" placeholder="CanBeSameEmailAsTheOldPds@email.com"
212
+
<input type="email" id="new-email" name="newEmail"
213
+
placeholder="CanBeSameEmailAsTheOldPds@email.com"
157
214
required bind:value={formData.newEmail}>
158
215
</div>
216
+
159
217
160
218
<div class="form-group">
161
219
<label for="new-handle">New Handle:</label>
162
-
<input type="text" id="new-handle" name="newHandle"
163
-
placeholder="username.newpds.com or mycooldomain.com" required
164
-
bind:value={formData.newHandle}>
220
+
<div class={selectedPds ? 'input-group' : ''}>
221
+
<input type="text" id="new-handle" name="newHandle"
222
+
placeholder="{handlePlaceHolder}"
223
+
required
224
+
bind:value={formData.newHandle}>
225
+
{#if selectedPds}
226
+
227
+
<select bind:value={selectedDomain} class="domain-select">
228
+
{#each selectedPds?.availableUserDomains as domain (domain)}
229
+
<option value={domain}>{domain}</option>
230
+
{/each}
231
+
<option value="custom">I have my own domain setup</option>
232
+
233
+
</select>
234
+
{/if}
235
+
</div>
165
236
</div>
166
237
167
-
<div class="form-group">
168
-
<label for="invite-code">Invite Code:</label>
169
-
<input type="text" id="invite-code" name="inviteCode"
170
-
placeholder="Invite code from your new PDS (Leave blank if you don't have one)"
171
-
bind:value={formData.inviteCode}>
172
-
</div>
238
+
{#if !selectedPds || selectedPds.inviteCodeRequired !== false}
239
+
<div class="form-group">
240
+
<label for="invite-code">Invite Code:</label>
241
+
<input type="text" id="invite-code" name="inviteCode"
242
+
placeholder="Invite code from your new PDS (Leave blank if you don't have one)"
243
+
bind:value={formData.inviteCode}>
244
+
</div>
245
+
{/if}
173
246
</div>
247
+
174
248
175
249
<div class="form-group">
176
250
<button type="button" onclick={() => showAdvance = !showAdvance} id="advance" name="advance">Advance
···
227
301
</div>
228
302
{/if}
229
303
304
+
{#if requiresAccept}
305
+
<div class="section" style="text-align: left">
306
+
<h3>Provider policies</h3>
307
+
<p>
308
+
To migrate to {cleanSelectedPds}, you must review and accept:
309
+
</p>
310
+
<ul>
311
+
{#if privacyUrl}
312
+
<li><a href={privacyUrl} target="_blank" rel="noopener noreferrer">Privacy
313
+
Policy</a></li>
314
+
{/if}
315
+
{#if tosUrl}
316
+
<li><a href={tosUrl} target="_blank" rel="noopener noreferrer">Terms of Service</a></li>
317
+
{/if}
318
+
</ul>
319
+
<div class="form-group">
320
+
<label for="accept-policies" class="moove-checkbox-label">
321
+
<input bind:checked={formData.acceptPolicies} type="checkbox" id="accept-policies"
322
+
name="acceptPolicies" required>
323
+
<span>
324
+
I have read and accept
325
+
326
+
</span>
327
+
</label>
328
+
</div>
329
+
</div>
330
+
{/if}
230
331
<p style="text-align: left">There are some risks that come with doing an account migration.
231
332
(Can view them
232
333
<a href="https://github.com/bluesky-social/pds/blob/main/ACCOUNT_MIGRATION.md#%EF%B8%8F-warning-%EF%B8%8F-%EF%B8%8F">here</a>)
···
257
358
{/if}
258
359
259
360
<div>
260
-
<button disabled={disableSubmit} type="submit">MOOve</button>
361
+
<button disabled={disableSubmit}
362
+
type="submit">{selectedPds ? `MOOve to ${cleanSelectedPds}` : 'MOOve'}</button>
261
363
</div>
262
364
</form>
263
365
264
366
{:else}
265
-
<SignThePapers migrator={migrator} newHandle={formData.newHandle}/>
367
+
<SignThePapers migrator={migrator} newHandle={newHandle}/>
266
368
{/if}
267
369
</div>
web-ui/src/routes/moover/SignThePapers.svelte
web-ui/src/routes/moover/[[pds]]/SignThePapers.svelte
web-ui/src/routes/moover/SignThePapers.svelte
web-ui/src/routes/moover/[[pds]]/SignThePapers.svelte
+34
web-ui/src/routes/moover/[[pds]]/+page.server.ts
+34
web-ui/src/routes/moover/[[pds]]/+page.server.ts
···
1
+
import type {PageServerLoad} from './$types';
2
+
import {Client, simpleFetchHandler} from '@atcute/client';
3
+
import type {} from '@atcute/atproto';
4
+
import {env} from '$env/dynamic/private';
5
+
6
+
export const load: PageServerLoad = async ({params}) => {
7
+
8
+
if (!params.pds) {
9
+
return {pdsOptions: null, intinalDomain: null};
10
+
}
11
+
12
+
const allowedPds = env.PDS_AUTOFILL.split(',');
13
+
if (!allowedPds.includes(params.pds.toLowerCase())) {
14
+
console.error('PDS not allowed', params.pds);
15
+
return {pdsOptions: null, intinalDomain: null};
16
+
}
17
+
18
+
try {
19
+
const handler = simpleFetchHandler({service: `https://${params.pds}`});
20
+
const rpc = new Client({handler});
21
+
const {ok, data} = await rpc.get('com.atproto.server.describeServer', {})
22
+
if (!ok) {
23
+
console.error('Failed to describe the PDS server', data);
24
+
return {pds: null};
25
+
}
26
+
return {
27
+
pdsOptions: data,
28
+
intinalDomain: data?.availableUserDomains[0] ?? ''
29
+
};
30
+
} catch (e) {
31
+
console.error('Failed to describe the PDS server', e);
32
+
return {pdsOptions: null, intinalDomain: null};
33
+
}
34
+
};