+369
src/components/FronterList.svelte
+369
src/components/FronterList.svelte
···
1
+
<script lang="ts">
2
+
import { fetchMember, type MemberUri } from "@/lib/utils";
3
+
4
+
interface Props {
5
+
fronters: string[];
6
+
onUpdate: (fronters: string[]) => void;
7
+
label?: string;
8
+
placeholder?: string;
9
+
note?: string;
10
+
fetchNames?: boolean; // If true, treat as PK member IDs and fetch names
11
+
}
12
+
13
+
let {
14
+
fronters = $bindable([]),
15
+
onUpdate,
16
+
label = "FRONTERS",
17
+
placeholder = "enter_identifier",
18
+
note = "list of identifiers",
19
+
fetchNames = false,
20
+
}: Props = $props();
21
+
22
+
let inputValue = $state("");
23
+
let inputElement: HTMLInputElement;
24
+
let memberNames = $state<Map<string, string | null>>(new Map());
25
+
let memberErrors = $state<Map<string, string>>(new Map());
26
+
27
+
const fetchMemberName = async (memberId: string) => {
28
+
try {
29
+
const memberUri: MemberUri = { type: "pk", memberId };
30
+
const name = await fetchMember(memberUri);
31
+
if (name) {
32
+
memberNames.set(memberId, name);
33
+
memberErrors.delete(memberId);
34
+
} else {
35
+
memberNames.set(memberId, null);
36
+
memberErrors.set(memberId, "Member not found");
37
+
}
38
+
} catch (error) {
39
+
memberNames.set(memberId, null);
40
+
memberErrors.set(memberId, `Error: ${error}`);
41
+
}
42
+
// Trigger reactivity
43
+
memberNames = new Map(memberNames);
44
+
memberErrors = new Map(memberErrors);
45
+
};
46
+
47
+
const addFronter = (name: string) => {
48
+
const trimmedName = name.trim();
49
+
if (!trimmedName || fronters.includes(trimmedName)) return;
50
+
51
+
const updatedFronters = [...fronters, trimmedName];
52
+
fronters = updatedFronters;
53
+
onUpdate(updatedFronters);
54
+
inputValue = "";
55
+
56
+
// Fetch the member name if this is a PK fronter
57
+
if (fetchNames) {
58
+
fetchMemberName(trimmedName);
59
+
}
60
+
};
61
+
62
+
const removeFronter = (index: number) => {
63
+
const identifier = fronters[index];
64
+
const updatedFronters = fronters.filter((_, i) => i !== index);
65
+
fronters = updatedFronters;
66
+
onUpdate(updatedFronters);
67
+
68
+
// Clean up the member name cache if this is a PK fronter
69
+
if (fetchNames) {
70
+
memberNames.delete(identifier);
71
+
memberErrors.delete(identifier);
72
+
memberNames = new Map(memberNames);
73
+
memberErrors = new Map(memberErrors);
74
+
}
75
+
76
+
inputElement?.focus();
77
+
};
78
+
79
+
const handleKeyPress = (event: KeyboardEvent) => {
80
+
if (event.key === "Enter" || event.key === "," || event.key === " ") {
81
+
event.preventDefault();
82
+
addFronter(inputValue);
83
+
} else if (
84
+
event.key === "Backspace" &&
85
+
inputValue === "" &&
86
+
fronters.length > 0
87
+
) {
88
+
// Remove last tag when backspacing on empty input
89
+
removeFronter(fronters.length - 1);
90
+
}
91
+
};
92
+
93
+
const handleInput = (event: Event) => {
94
+
const target = event.target as HTMLInputElement;
95
+
const value = target.value;
96
+
97
+
// Check for comma or space at the end
98
+
if (value.endsWith(",") || value.endsWith(" ")) {
99
+
addFronter(value.slice(0, -1));
100
+
} else {
101
+
inputValue = value;
102
+
}
103
+
};
104
+
105
+
const focusInput = () => {
106
+
inputElement?.focus();
107
+
};
108
+
109
+
// Load existing member names on mount (only for PK fronters)
110
+
$effect(() => {
111
+
if (fetchNames) {
112
+
fronters.forEach((identifier) => {
113
+
if (
114
+
!memberNames.has(identifier) &&
115
+
!memberErrors.has(identifier)
116
+
) {
117
+
fetchMemberName(identifier);
118
+
}
119
+
});
120
+
}
121
+
});
122
+
123
+
// Helper function to get display text for a fronter
124
+
const getDisplayText = (identifier: string) => {
125
+
if (!fetchNames) return identifier;
126
+
return memberNames.get(identifier) || identifier;
127
+
};
128
+
129
+
// Helper function to check if we should show error/loading state
130
+
const getStatusInfo = (identifier: string) => {
131
+
if (!fetchNames) return null;
132
+
133
+
if (memberErrors.has(identifier)) {
134
+
return { type: "error", text: memberErrors.get(identifier) };
135
+
}
136
+
if (memberNames.get(identifier) === undefined) {
137
+
return { type: "loading", text: "loading..." };
138
+
}
139
+
return null;
140
+
};
141
+
</script>
142
+
143
+
<div class="config-card">
144
+
<div class="config-row">
145
+
<span class="config-label">{label}</span>
146
+
<div
147
+
class="tag-input-container"
148
+
onclick={focusInput}
149
+
onkeydown={(e) => e.key === "Enter" && focusInput()}
150
+
role="textbox"
151
+
tabindex="0"
152
+
>
153
+
<div class="tag-input-wrapper">
154
+
{#each fronters as identifier, index}
155
+
<div class="fronter-tag">
156
+
<div class="tag-content">
157
+
<span class="tag-text">
158
+
{getDisplayText(identifier)}
159
+
</span>
160
+
{#if getStatusInfo(identifier)}
161
+
{@const status = getStatusInfo(identifier)}
162
+
{#if status}
163
+
<span class="tag-{status.type}"
164
+
>{status.text}</span
165
+
>
166
+
{/if}
167
+
{/if}
168
+
</div>
169
+
<button
170
+
onclick={() => removeFronter(index)}
171
+
class="tag-remove"
172
+
title="Remove fronter"
173
+
>
174
+
×
175
+
</button>
176
+
</div>
177
+
{/each}
178
+
<input
179
+
bind:this={inputElement}
180
+
type="text"
181
+
placeholder={fronters.length === 0 ? placeholder : ""}
182
+
value={inputValue}
183
+
oninput={handleInput}
184
+
onkeydown={handleKeyPress}
185
+
class="tag-input"
186
+
/>
187
+
</div>
188
+
</div>
189
+
</div>
190
+
191
+
<div class="config-note">
192
+
<span class="note-text">{note}</span>
193
+
</div>
194
+
</div>
195
+
196
+
<style>
197
+
.config-card {
198
+
background: #0d0d0d;
199
+
border: 1px solid #2a2a2a;
200
+
border-left: 3px solid #444444;
201
+
padding: 10px;
202
+
display: flex;
203
+
flex-direction: column;
204
+
gap: 6px;
205
+
transition: border-left-color 0.2s ease;
206
+
}
207
+
208
+
.config-card:hover {
209
+
border-left-color: #555555;
210
+
}
211
+
212
+
.config-row {
213
+
display: flex;
214
+
align-items: center;
215
+
gap: 12px;
216
+
margin-bottom: 0;
217
+
}
218
+
219
+
.config-label {
220
+
font-size: 12px;
221
+
color: #cccccc;
222
+
letter-spacing: 1px;
223
+
font-weight: 700;
224
+
white-space: nowrap;
225
+
min-width: 90px;
226
+
}
227
+
228
+
.tag-input-container {
229
+
flex: 1;
230
+
background: #181818;
231
+
border: 1px solid #333333;
232
+
transition: border-color 0.2s ease;
233
+
cursor: text;
234
+
min-height: 42px;
235
+
display: flex;
236
+
align-items: center;
237
+
}
238
+
239
+
.tag-input-container:focus-within {
240
+
border-color: #666666;
241
+
}
242
+
243
+
.tag-input-container:focus-within:has(.fronter-tag) {
244
+
border-bottom-color: #00ff41;
245
+
}
246
+
247
+
.tag-input-wrapper {
248
+
display: flex;
249
+
flex-wrap: wrap;
250
+
align-items: center;
251
+
gap: 6px;
252
+
padding: 8px 12px;
253
+
width: 100%;
254
+
min-height: 26px;
255
+
}
256
+
257
+
.fronter-tag {
258
+
display: flex;
259
+
align-items: center;
260
+
background: #2a2a2a;
261
+
border: 1px solid #444444;
262
+
border-radius: 3px;
263
+
padding: 4px 6px;
264
+
gap: 6px;
265
+
font-size: 11px;
266
+
color: #ffffff;
267
+
font-weight: 600;
268
+
line-height: 1;
269
+
transition: all 0.15s ease;
270
+
animation: tagAppear 0.2s ease-out;
271
+
}
272
+
273
+
.fronter-tag:hover {
274
+
background: #333333;
275
+
border-color: #555555;
276
+
}
277
+
278
+
.tag-content {
279
+
display: flex;
280
+
flex-direction: column;
281
+
gap: 2px;
282
+
}
283
+
284
+
.tag-text {
285
+
white-space: nowrap;
286
+
letter-spacing: 0.5px;
287
+
}
288
+
289
+
.tag-error {
290
+
font-size: 9px;
291
+
color: #ff6666;
292
+
font-weight: 500;
293
+
letter-spacing: 0.3px;
294
+
}
295
+
296
+
.tag-loading {
297
+
font-size: 9px;
298
+
color: #888888;
299
+
font-weight: 500;
300
+
letter-spacing: 0.3px;
301
+
font-style: italic;
302
+
}
303
+
304
+
.tag-remove {
305
+
background: none;
306
+
border: none;
307
+
color: #888888;
308
+
font-size: 14px;
309
+
font-weight: 700;
310
+
cursor: pointer;
311
+
padding: 0;
312
+
line-height: 1;
313
+
transition: color 0.15s ease;
314
+
display: flex;
315
+
align-items: center;
316
+
justify-content: center;
317
+
width: 14px;
318
+
height: 14px;
319
+
margin-left: 2px;
320
+
}
321
+
322
+
.tag-remove:hover {
323
+
color: #ff4444;
324
+
}
325
+
326
+
.tag-input {
327
+
background: transparent;
328
+
border: none;
329
+
outline: none;
330
+
color: #ffffff;
331
+
font-family: inherit;
332
+
font-size: 12px;
333
+
font-weight: 500;
334
+
flex: 1;
335
+
min-width: 120px;
336
+
height: 26px;
337
+
}
338
+
339
+
.tag-input::placeholder {
340
+
color: #777777;
341
+
font-size: 12px;
342
+
}
343
+
344
+
.config-note {
345
+
padding: 0;
346
+
background: transparent;
347
+
border: none;
348
+
margin: 0;
349
+
}
350
+
351
+
.note-text {
352
+
font-size: 11px;
353
+
color: #bbbbbb;
354
+
line-height: 1.3;
355
+
font-weight: 500;
356
+
letter-spacing: 0.5px;
357
+
}
358
+
359
+
@keyframes tagAppear {
360
+
0% {
361
+
opacity: 0;
362
+
transform: scale(0.8);
363
+
}
364
+
100% {
365
+
opacity: 1;
366
+
transform: scale(1);
367
+
}
368
+
}
369
+
</style>
+12
-11
src/entrypoints/background.ts
+12
-11
src/entrypoints/background.ts
···
55
55
authToken: string | null,
56
56
sender: globalThis.Browser.runtime.MessageSender,
57
57
) => {
58
-
const fronterName = await storage.getItem<string>("sync:fronter");
59
-
const spFronters = (await getSpFronters()).map((m) => memberUriString(m));
60
58
if (!authToken) return;
61
-
const fronter = {
62
-
names: fronterName?.split(",").map((name) => name.trim()) ?? [],
63
-
members: spFronters,
64
-
};
59
+
const frontersArray = await storage.getItem<string[]>("sync:fronters");
60
+
let members: Parameters<typeof putFronter>["1"] = frontersArray ?? [];
61
+
if (members.length === 0) {
62
+
const pkFronters = await storage.getItem<string[]>("sync:pk-fronter");
63
+
if (pkFronters) {
64
+
members = pkFronters.map((id) => ({ type: "pk", memberId: id }));
65
+
} else {
66
+
members = await getSpFronters();
67
+
}
68
+
}
65
69
// dont write if no names is specified or no sp/pk fronters are fetched
66
-
if (fronter.names.length === 0 && fronter.members.length === 0) return;
70
+
if (members.length === 0) return;
67
71
const results = [];
68
72
for (const result of items) {
69
-
const resp = await putFronter(
70
-
{ subject: result.uri, ...fronter },
71
-
authToken,
72
-
);
73
+
const resp = await putFronter(result.uri, members, authToken);
73
74
if (resp.ok) {
74
75
const parsedUri = cacheFronter(result.uri, resp.value);
75
76
results.push({
+4
-4
src/entrypoints/content.ts
+4
-4
src/entrypoints/content.ts
···
111
111
});
112
112
respEventSetup.then((name) => (respEventName = name));
113
113
114
-
const applyFronterName = (el: Element, fronterNames: string[]) => {
114
+
const applyFronterName = (el: Element, fronters: Fronter["members"]) => {
115
115
if (el.getAttribute("data-fronter")) return;
116
-
const s = fronterNames.join(", ");
116
+
const s = fronters.map((f) => f.name).join(", ");
117
117
el.textContent += ` [f: ${s}]`;
118
118
el.setAttribute("data-fronter", s);
119
119
};
···
123
123
for (const el of document.getElementsByTagName("a")) {
124
124
const path = `/${el.href.split("/").slice(3).join("/")}`;
125
125
const fronter = fronters.get(path);
126
-
if (!fronter) continue;
126
+
if (!fronter || fronter.members.length === 0) continue;
127
127
const isFocusedPost = fronter.depth === 0;
128
128
if (isFocusedPost && match && match.rkey !== fronter.rkey) continue;
129
129
if (isFocusedPost && el.ariaLabel !== fronter.displayName) continue;
···
134
134
?.firstElementChild?.firstElementChild ?? null);
135
135
if (!displayNameElement) continue;
136
136
// console.log(path, fronter, displayNameElement);
137
-
applyFronterName(displayNameElement, fronter.names);
137
+
applyFronterName(displayNameElement, fronter.members);
138
138
}
139
139
};
140
140
let postTabObserver: MutationObserver | null = null;
+52
-30
src/entrypoints/popup/App.svelte
+52
-30
src/entrypoints/popup/App.svelte
···
1
1
<script lang="ts">
2
2
import { expect } from "@/lib/result";
3
-
import { getFronter } from "@/lib/utils";
3
+
import { getFronter, getMemberPublicUri } from "@/lib/utils";
4
4
import { isResourceUri } from "@atcute/lexicons";
5
5
import type { ResourceUri } from "@atcute/lexicons/syntax";
6
+
import FronterList from "@/components/FronterList.svelte";
6
7
7
8
let recordAtUri = $state("");
8
9
let queryResult = $state("");
9
10
let isQuerying = $state(false);
10
-
let fronterName = $state("");
11
+
let fronters = $state<string[]>([]);
12
+
let pkFronters = $state<string[]>([]);
11
13
let spToken = $state("");
12
14
let isFromCurrentTab = $state(false);
13
15
14
-
const makeOutput = (fronter: any) => {
15
-
return `HANDLE: ${fronter.handle ?? "handle.invalid"}<br>FRONTER(S): ${fronter.names.join(", ")}`;
16
+
const makeOutput = (record: any) => {
17
+
const fronters = record.members
18
+
.map((f: any) => {
19
+
if (!f.uri) return f.name;
20
+
const publicUri = getMemberPublicUri(f.uri);
21
+
if (!publicUri) return f.name;
22
+
return `<a href="${publicUri}">${f.name}</a>`;
23
+
})
24
+
.join(", ");
25
+
return [
26
+
`HANDLE: ${record.handle ?? `handle.invalid (${record.did})`}`,
27
+
`FRONTER(S): ${fronters}`,
28
+
].join("<br>");
16
29
};
17
30
18
31
const queryRecord = async (recordUri: ResourceUri) => {
···
32
45
}
33
46
};
34
47
35
-
const updateFronter = (event: any) => {
36
-
fronterName = (event.target as HTMLInputElement).value;
37
-
storage.setItem("sync:fronter", fronterName);
48
+
const updateFronters = (newFronters: string[]) => {
49
+
fronters = newFronters;
50
+
storage.setItem("sync:fronters", newFronters);
51
+
};
52
+
53
+
const updatePkFronters = (newPkFronters: string[]) => {
54
+
pkFronters = newPkFronters;
55
+
storage.setItem("sync:pk-fronter", newPkFronters);
38
56
};
39
57
40
58
const updateSpToken = (event: any) => {
···
55
73
};
56
74
57
75
onMount(async () => {
58
-
const fronter = await storage.getItem<string>("sync:fronter");
59
-
if (fronter) {
60
-
fronterName = fronter;
76
+
const frontersArray = await storage.getItem<string[]>("sync:fronters");
77
+
if (frontersArray && Array.isArray(frontersArray)) {
78
+
fronters = frontersArray;
79
+
}
80
+
81
+
const pkFrontersArray =
82
+
await storage.getItem<string[]>("sync:pk-fronter");
83
+
if (pkFrontersArray && Array.isArray(pkFrontersArray)) {
84
+
pkFronters = pkFrontersArray;
61
85
}
62
86
63
87
const token = await storage.getItem<string>("sync:sp_token");
···
172
196
</div>
173
197
<div class="config-card">
174
198
<div class="config-row">
175
-
<span class="config-label">SP_TOKEN</span>
199
+
<span class="config-label">SP TOKEN</span>
176
200
<input
177
201
type="password"
178
202
placeholder="enter_simply_plural_token"
···
184
208
</div>
185
209
<div class="config-note">
186
210
<span class="note-text">
187
-
token requires only read permissions
188
-
</span>
189
-
</div>
190
-
</div>
191
-
<div class="config-card">
192
-
<div class="config-row">
193
-
<span class="config-label">FRONTER_NAME</span>
194
-
<input
195
-
type="text"
196
-
placeholder="enter_identifier"
197
-
oninput={updateFronter}
198
-
bind:value={fronterName}
199
-
class="config-input"
200
-
class:has-value={fronterName}
201
-
/>
202
-
</div>
203
-
<div class="config-note">
204
-
<span class="note-text">
205
-
overrides Simply Plural fronters when set
211
+
when set, pulls fronters from Simply Plural (token
212
+
only requires read permissions)
206
213
</span>
207
214
</div>
208
215
</div>
216
+
<FronterList
217
+
bind:fronters={pkFronters}
218
+
onUpdate={updatePkFronters}
219
+
label="PK FRONTERS"
220
+
placeholder="enter_member_ids"
221
+
note="PluralKit member IDs, overrides SP fronters"
222
+
fetchNames={true}
223
+
/>
224
+
<FronterList
225
+
bind:fronters
226
+
onUpdate={updateFronters}
227
+
label="FRONTERS"
228
+
placeholder="enter_fronter_names"
229
+
note="just names, overrides SP & PK fronters"
230
+
/>
209
231
</section>
210
232
</div>
211
233
+72
-38
src/lib/utils.ts
+72
-38
src/lib/utils.ts
···
27
27
import { getAtprotoHandle, getPdsEndpoint } from "@atcute/identity";
28
28
29
29
export type Fronter = {
30
-
memberUris: MemberUri[];
31
-
names: string[];
30
+
members: {
31
+
uri?: MemberUri;
32
+
name: string;
33
+
}[];
32
34
handle: Handle | null;
33
35
did: AtprotoDid;
34
36
};
35
37
36
-
const fronterSchema = v.record(
38
+
export const fronterSchema = v.record(
37
39
v.string(),
38
40
v.object({
39
41
$type: v.literal("systems.gaze.atfronter.fronter"),
40
42
subject: v.resourceUriString(),
41
-
names: v.array(v.string()),
42
-
members: v.array(v.genericUriString()), // identifier(s) for pk or sp or etc. (maybe member record in the future?)
43
+
members: v.array(
44
+
v.object({
45
+
name: v.string(),
46
+
uri: v.optional(v.genericUriString()), // identifier(s) for pk or sp or etc. (maybe member record in the future?)
47
+
}),
48
+
),
43
49
}),
44
50
);
51
+
export type FronterSchema = InferOutput<typeof fronterSchema>;
45
52
46
-
type MemberUri =
53
+
export type MemberUri =
47
54
| { type: "at"; recordUri: ResourceUri }
48
-
| { type: "pk"; systemId: string; memberId: string }
55
+
| { type: "pk"; memberId: string }
49
56
| { type: "sp"; systemId: string; memberId: string };
50
57
51
58
export const parseMemberId = (memberId: GenericUri): MemberUri => {
···
53
60
switch (uri.protocol) {
54
61
case "pk:": {
55
62
const split = uri.pathname.split("/").slice(1);
56
-
return { type: "pk", systemId: split[0], memberId: split[1] };
63
+
return { type: "pk", memberId: split[0] };
57
64
}
58
65
case "sp:": {
59
66
const split = uri.pathname.split("/").slice(1);
···
70
77
export const memberUriString = (memberUri: MemberUri): GenericUri => {
71
78
switch (memberUri.type) {
72
79
case "pk": {
73
-
return `pk://api.pluralkit.com/${memberUri.systemId}/${memberUri.memberId}`;
80
+
return `pk://api.pluralkit.me/${memberUri.memberId}`;
74
81
}
75
82
case "sp": {
76
83
return `sp://api.apparyllis.com/${memberUri.systemId}/${memberUri.memberId}`;
···
80
87
}
81
88
}
82
89
};
90
+
export const getMemberPublicUri = (memberUri: MemberUri) => {
91
+
switch (memberUri.type) {
92
+
case "pk": {
93
+
return `https://dash.pluralkit.me/profile/m/${memberUri.memberId}`;
94
+
}
95
+
case "sp": {
96
+
return null;
97
+
}
98
+
case "at": {
99
+
return `https://pdsls.dev/${memberUri.recordUri}`;
100
+
}
101
+
}
102
+
};
83
103
84
104
let memberCache = new Map<string, any>();
85
105
export const fetchMember = async (
86
106
memberUri: MemberUri,
87
107
): Promise<string | undefined> => {
108
+
const s = memberUriString(memberUri);
109
+
const cached = memberCache.get(s);
88
110
switch (memberUri.type) {
89
111
case "sp": {
90
-
const s = memberUriString(memberUri);
91
-
const cached = memberCache.get(s);
92
112
if (cached) return cached.content.name;
93
113
const token = await storage.getItem<string>("sync:sp_token");
94
114
if (!token) return;
···
105
125
memberCache.set(s, member);
106
126
return member.content.name;
107
127
}
128
+
case "pk": {
129
+
if (cached) return cached.name;
130
+
const resp = await fetch(
131
+
`https://api.pluralkit.me/v2/members/${memberUri.memberId}`,
132
+
);
133
+
if (!resp.ok) return;
134
+
const member = await resp.json();
135
+
memberCache.set(s, member);
136
+
return member.name;
137
+
}
108
138
}
109
139
};
110
140
111
-
export const getFronterNames = async (
112
-
name: string[],
113
-
memberUris: MemberUri[],
114
-
) => {
115
-
let fronterNames = name;
116
-
if (memberUris.length > 0) {
117
-
fronterNames = (
118
-
await Promise.allSettled(memberUris.map((m) => fetchMember(m)))
119
-
)
120
-
.filter((p) => p.status === "fulfilled")
121
-
.flatMap((p) => p.value ?? []);
122
-
}
123
-
return fronterNames;
141
+
export const getFronterNames = async (members: (string | MemberUri)[]) => {
142
+
const promises = await Promise.allSettled(
143
+
members.map(async (m): Promise<Fronter["members"][0] | null> => {
144
+
if (typeof m === "string")
145
+
return Promise.resolve({ uri: undefined, name: m });
146
+
const name = await fetchMember(m);
147
+
return name ? { uri: m, name } : null;
148
+
}),
149
+
);
150
+
return promises
151
+
.filter((p) => p.status === "fulfilled")
152
+
.flatMap((p) => p.value ?? []);
124
153
};
125
154
126
155
const handleResolver = new CompositeHandleResolver({
···
187
216
const maybeTyped = safeParse(fronterSchema, maybeRecord.data.value);
188
217
if (!maybeTyped.ok) return err(maybeTyped.message);
189
218
190
-
let memberUris, fronterNames;
219
+
let members: Fronter["members"];
191
220
try {
192
-
memberUris = maybeTyped.value.members.map((m) => parseMemberId(m));
193
-
// fronterNames = await getFronterNames(maybeTyped.value.names, memberUris);
194
-
fronterNames = maybeTyped.value.names;
221
+
members = maybeTyped.value.members.map((m) => ({
222
+
name: m.name,
223
+
uri: m.uri ? parseMemberId(m.uri) : undefined,
224
+
}));
195
225
} catch (error) {
196
226
return err(`error fetching fronter names: ${error}`);
197
227
}
198
228
199
229
return ok({
200
-
memberUris,
201
-
names: fronterNames,
230
+
members,
202
231
handle,
203
232
did,
204
233
});
205
234
};
206
235
207
236
export const putFronter = async (
208
-
record: Omit<InferOutput<typeof fronterSchema>, "$type">,
237
+
subject: FronterSchema["subject"],
238
+
members: (string | MemberUri)[],
209
239
authToken: string,
210
240
): Promise<Result<Fronter, string>> => {
211
-
const parsedRecordUri = parseResourceUri(record.subject);
241
+
const parsedRecordUri = parseResourceUri(subject);
212
242
if (!parsedRecordUri.ok) return err(parsedRecordUri.error);
213
243
const { repo, collection, rkey } = parsedRecordUri.value;
214
244
···
218
248
// make client
219
249
const atpClient = await getAtpClient(did);
220
250
221
-
let memberUris, fronterNames;
251
+
let filteredMembers: Fronter["members"];
222
252
try {
223
-
memberUris = record.members.map((m) => parseMemberId(m));
224
-
fronterNames = await getFronterNames(record.names, memberUris);
253
+
filteredMembers = await getFronterNames(members);
225
254
} catch (error) {
226
255
return err(`error fetching fronter names: ${error}`);
227
256
}
···
232
261
repo: did,
233
262
collection: fronterSchema.object.shape.$type.expected,
234
263
rkey: `${collection}_${rkey}`,
235
-
record: { ...record, names: fronterNames },
264
+
record: {
265
+
subject,
266
+
members: filteredMembers.map((member) => ({
267
+
name: member.name,
268
+
uri: member.uri ? memberUriString(member.uri) : undefined,
269
+
})),
270
+
},
236
271
validate: false,
237
272
},
238
273
headers: { authorization: `Bearer ${authToken}` },
···
243
278
return ok({
244
279
did,
245
280
handle,
246
-
names: fronterNames,
247
-
memberUris,
281
+
members: filteredMembers,
248
282
});
249
283
};
250
284