+184
-24
src/entrypoints/background.ts
+184
-24
src/entrypoints/background.ts
···
1
-
import { getFronter, putFronter } from "@/lib/utils";
1
+
import { expect } from "@/lib/result";
2
+
import {
3
+
Fronter,
4
+
fronterGetSocialAppHref,
5
+
getFronter,
6
+
putFronter,
7
+
} from "@/lib/utils";
8
+
import { parseResourceUri, ResourceUri } from "@atcute/lexicons";
2
9
3
10
export default defineBackground({
4
11
persistent: true,
5
12
main: () => {
6
13
console.log("setting up background script");
7
-
browser.runtime.onMessage.addListener(async (message, sender) => {
8
-
// console.log("received message", message);
9
-
if (message.type !== "RESPONSE_CAPTURED") return;
10
-
const fronter = await storage.getItem<string>("sync:fronter");
11
-
if (!fronter) return;
12
-
const authToken = message.data.authToken;
13
-
if (!authToken) return;
14
-
const data: any = JSON.parse(message.data.body);
15
-
// console.log("will put fronter", fronter, "for records", data.results);
16
-
for (const result of data.results) {
17
-
// TODO: validate response
18
-
await putFronter(result.uri, fronter, authToken);
19
-
}
20
-
});
21
14
22
-
browser.runtime.onMessage.addListener(async (message, sender) => {
23
-
// console.log("received message", message);
24
-
if (message.type !== "TAB_FRONTER") return;
25
-
const { recordUri } = message;
26
-
const fronter = await getFronter(recordUri);
27
-
if (!fronter.ok) return;
15
+
let fronters = new Map<ResourceUri, Fronter | null>();
16
+
const cacheFronter = (uri: ResourceUri, fronter: Fronter) => {
17
+
const parsedUri = expect(parseResourceUri(uri));
18
+
fronters.set(uri, fronter);
19
+
fronters.set(
20
+
`at://${fronter.did}/${parsedUri.collection!}/${parsedUri.rkey!}`,
21
+
fronter,
22
+
);
23
+
fronters.set(
24
+
`at://${fronter.handle}/${parsedUri.collection!}/${parsedUri.rkey!}`,
25
+
fronter,
26
+
);
27
+
return parsedUri;
28
+
};
29
+
30
+
const setTabFronter = async (recordUri: ResourceUri, fronter: Fronter) => {
28
31
const tabs = await browser.tabs.query({
29
32
active: true,
30
33
currentWindow: true,
31
34
});
32
35
const tab = tabs[0];
33
36
const tabKey: StorageItemKey = `local:tab-${tab.id!}-fronter`;
34
-
await storage.setItem(tabKey, {
35
-
fronterName: fronter.value.name,
37
+
const tabFronter = {
36
38
recordUri,
37
-
});
39
+
...fronter,
40
+
};
41
+
await storage.setItem(tabKey, tabFronter);
38
42
const deleteOld = async (tabId: number) => {
39
43
if (`local:tab-${tabId}-fronter` !== tabKey) return;
40
44
await storage.removeItem(tabKey);
···
42
46
browser.tabs.onRemoved.addListener(deleteOld);
43
47
browser.tabs.onReplaced.addListener(deleteOld);
44
48
browser.tabs.onUpdated.addListener(deleteOld);
49
+
};
50
+
51
+
const handleWrite = async (
52
+
{ data: { body, authToken } }: any,
53
+
sender: globalThis.Browser.runtime.MessageSender,
54
+
) => {
55
+
const fronter = await storage.getItem<string>("sync:fronter");
56
+
if (!fronter) return;
57
+
if (!authToken) return;
58
+
const data: any = JSON.parse(body);
59
+
// console.log("will put fronter", fronter, "for records", data.results);
60
+
const results = [];
61
+
for (const result of data.results) {
62
+
const resp = await putFronter(result.uri, fronter, authToken);
63
+
if (resp.ok) {
64
+
const parsedUri = cacheFronter(result.uri, resp.value);
65
+
results.push({
66
+
rkey: parsedUri.rkey!,
67
+
...resp.value,
68
+
});
69
+
}
70
+
}
71
+
browser.tabs.sendMessage(sender.tab?.id!, {
72
+
type: "TIMELINE_FRONTER",
73
+
results: new Map(
74
+
results.map((fronter) => [
75
+
fronterGetSocialAppHref(fronter, fronter.rkey),
76
+
fronter,
77
+
]),
78
+
),
79
+
});
80
+
};
81
+
const handleTimeline = async (
82
+
feed: any[],
83
+
sender: globalThis.Browser.runtime.MessageSender,
84
+
) => {
85
+
const handlePost = async (post: any) => {
86
+
const cachedFronter = fronters.get(post.uri);
87
+
if (cachedFronter === null) return;
88
+
const promise = cachedFronter
89
+
? Promise.resolve(cachedFronter)
90
+
: getFronter(post.uri).then(async (fronter) => {
91
+
if (!fronter.ok) {
92
+
fronters.set(post.uri, null);
93
+
return;
94
+
}
95
+
return fronter.value;
96
+
});
97
+
return promise.then((fronter) => {
98
+
if (!fronter) return;
99
+
const parsedUri = cacheFronter(post.uri, fronter);
100
+
return {
101
+
rkey: parsedUri.rkey!,
102
+
...fronter,
103
+
};
104
+
});
105
+
};
106
+
const allPromises = feed.flatMap((item) => {
107
+
const promises = [handlePost(item.post)];
108
+
if (item.reply?.parent) {
109
+
promises.push(handlePost(item.reply.parent));
110
+
}
111
+
if (item.reply?.root) {
112
+
promises.push(handlePost(item.reply.root));
113
+
}
114
+
return promises;
115
+
});
116
+
const results = new Map(
117
+
(await Promise.allSettled(allPromises))
118
+
.filter((result) => result.status === "fulfilled")
119
+
.flatMap((result) => result.value ?? [])
120
+
.map((fronter) => [
121
+
fronterGetSocialAppHref(fronter, fronter.rkey),
122
+
fronter,
123
+
]),
124
+
);
125
+
browser.tabs.sendMessage(sender.tab?.id!, {
126
+
type: "TIMELINE_FRONTER",
127
+
results,
128
+
});
129
+
// console.log("sent timeline fronters", results);
130
+
};
131
+
const handleThread = async (
132
+
{ data: { body } }: any,
133
+
sender: globalThis.Browser.runtime.MessageSender,
134
+
) => {
135
+
const data: any = JSON.parse(body);
136
+
const promises = (data.thread as any[]).flatMap((item) => {
137
+
const cachedFronter = fronters.get(item.uri);
138
+
if (cachedFronter === null) return [];
139
+
const promise = cachedFronter
140
+
? Promise.resolve(cachedFronter)
141
+
: getFronter(item.uri).then(async (fronter) => {
142
+
if (!fronter.ok) {
143
+
fronters.set(item.uri, null);
144
+
return;
145
+
}
146
+
return fronter.value;
147
+
});
148
+
return promise.then(async (fronter) => {
149
+
if (!fronter) return;
150
+
const parsedUri = cacheFronter(item.uri, fronter);
151
+
if (item.depth === 0) await setTabFronter(item.uri, fronter);
152
+
return {
153
+
rkey: parsedUri.rkey!,
154
+
displayName: item.value.post.author.displayName,
155
+
depth: item.depth,
156
+
...fronter,
157
+
};
158
+
});
159
+
});
160
+
const results = new Map(
161
+
(await Promise.allSettled(promises))
162
+
.filter((result) => result.status === "fulfilled")
163
+
.flatMap((result) => result.value ?? [])
164
+
.map((fronter) => [
165
+
fronterGetSocialAppHref(fronter, fronter.rkey, fronter.depth),
166
+
fronter,
167
+
]),
168
+
);
169
+
browser.tabs.sendMessage(sender.tab?.id!, {
170
+
type: "THREAD_FRONTER",
171
+
results,
172
+
});
173
+
// console.log("sent thread fronters", results);
174
+
};
175
+
176
+
browser.runtime.onMessage.addListener(async (message, sender) => {
177
+
if (message.type !== "RESPONSE_CAPTURED") return;
178
+
// console.log("handling response event", message);
179
+
switch (message.data.type as string) {
180
+
case "write":
181
+
await handleWrite(message, sender);
182
+
break;
183
+
case "posts":
184
+
const posts = JSON.parse(message.data.body) as any[];
185
+
await handleTimeline(
186
+
posts.map((post) => ({ post })),
187
+
sender,
188
+
);
189
+
break;
190
+
case "timeline":
191
+
await handleTimeline(JSON.parse(message.data.body).feed, sender);
192
+
break;
193
+
case "thread":
194
+
await handleThread(message, sender);
195
+
break;
196
+
}
197
+
browser.tabs.sendMessage(sender.tab?.id!, {
198
+
type: "CACHED_FRONTERS",
199
+
fronters,
200
+
});
201
+
});
202
+
browser.runtime.onMessage.addListener(async (message, sender) => {
203
+
if (message.type !== "TAB_FRONTER") return;
204
+
await setTabFronter(message.recordUri, message.fronter);
45
205
});
46
206
},
47
207
});
+92
-29
src/entrypoints/content.ts
+92
-29
src/entrypoints/content.ts
···
1
+
import { expect } from "@/lib/result";
2
+
import { Fronter, fronterGetSocialAppHref } from "@/lib/utils";
3
+
import { parseResourceUri, ResourceUri } from "@atcute/lexicons";
4
+
1
5
const getAuthHeader = (headers: any): string | null => {
2
6
if (headers instanceof Headers) {
3
7
return headers.get("authorization");
···
21
25
const response = await originalFetch.apply(this, args);
22
26
23
27
if (respEventName === null) return response;
28
+
if (response.status !== 200) return response;
24
29
25
-
let authHeader: string | null = null;
26
-
if (typeof args[0] === "string") {
27
-
if (args[1]?.headers) {
28
-
authHeader = getAuthHeader(args[1].headers);
29
-
}
30
-
} else if (args[0] instanceof Request) {
31
-
authHeader = getAuthHeader(args[0].headers);
32
-
}
30
+
const body = await response.clone().text();
33
31
34
-
if (
35
-
!response.url.includes("/xrpc/com.atproto.repo.applyWrites") ||
36
-
response.status !== 200
37
-
)
38
-
return response;
32
+
const sendEvent = (detail: any) => {
33
+
// console.log("sending response event", detail);
34
+
window.dispatchEvent.call(
35
+
window,
36
+
new window.CustomEvent(`${respEventName}-isolated`, {
37
+
detail,
38
+
}),
39
+
);
40
+
};
39
41
40
-
const body = await response.clone().text();
42
+
let detail: any;
43
+
if (response.url.includes("/xrpc/com.atproto.repo.applyWrites")) {
44
+
let authHeader: string | null = null;
45
+
if (typeof args[0] === "string") {
46
+
if (args[1]?.headers) {
47
+
authHeader = getAuthHeader(args[1].headers);
48
+
}
49
+
} else if (args[0] instanceof Request) {
50
+
authHeader = getAuthHeader(args[0].headers);
51
+
}
41
52
42
-
const detail = {
43
-
url: response.url,
44
-
body,
45
-
authToken: authHeader?.split(" ")[1] || null,
46
-
};
47
-
window.dispatchEvent.call(
48
-
window,
49
-
new window.CustomEvent(`${respEventName}-isolated`, {
50
-
detail,
51
-
}),
52
-
);
53
+
detail = {
54
+
type: "write",
55
+
body,
56
+
authToken: authHeader?.split(" ")[1] || null,
57
+
};
58
+
} else if (
59
+
response.url.includes("/xrpc/app.bsky.feed.getAuthorFeed") ||
60
+
response.url.includes("/xrpc/app.bsky.feed.getTimeline") ||
61
+
response.url.includes("/xrpc/app.bsky.feed.getFeed")
62
+
) {
63
+
detail = {
64
+
type: "timeline",
65
+
body,
66
+
};
67
+
} else if (
68
+
response.url.includes("/xrpc/app.bsky.unspecced.getPostThreadV2")
69
+
) {
70
+
detail = {
71
+
type: "thread",
72
+
body,
73
+
};
74
+
} else if (response.url.includes("/xrpc/app.bsky.feed.getPosts")) {
75
+
detail = {
76
+
type: "posts",
77
+
body,
78
+
};
79
+
}
80
+
sendEvent(detail);
53
81
54
82
return response;
55
83
};
56
84
globalThis.fetch = overriddenFetch;
57
85
(globalThis as any).oldFetch = originalFetch;
58
86
59
-
console.log("waiting for response channel setup...");
60
87
const respEventSetup = new Promise<string>((resolve) => {
61
88
document.addEventListener(
62
89
"at-fronter-channel-setup",
···
67
94
{ once: true, capture: true },
68
95
);
69
96
});
70
-
respEventSetup.then((name) => {
71
-
console.log("set up response channel ", name);
72
-
respEventName = name;
97
+
respEventSetup.then((name) => (respEventName = name));
98
+
99
+
const applyFronterName = (el: Element, fronterName: string) => {
100
+
if (el.getAttribute("data-fronter")) return;
101
+
el.textContent += ` [f: ${fronterName}]`;
102
+
el.setAttribute("data-fronter", fronterName);
103
+
};
104
+
const applyFrontersToPage = (fronters: Map<string, any>) => {
105
+
for (const el of document.getElementsByTagName("a")) {
106
+
const path = `/${el.href.split("/").slice(3).join("/")}`;
107
+
const fronter = fronters.get(path);
108
+
if (!fronter) continue;
109
+
const isFocusedPost = fronter.depth === 0;
110
+
if (isFocusedPost && el.ariaLabel !== fronter.displayName) continue;
111
+
const displayNameElement = isFocusedPost
112
+
? (el.firstElementChild?.firstElementChild?.firstElementChild
113
+
?.firstElementChild?.firstElementChild ?? null)
114
+
: (el.parentElement?.firstElementChild?.firstElementChild
115
+
?.firstElementChild?.firstElementChild ?? null);
116
+
if (!displayNameElement) continue;
117
+
applyFronterName(displayNameElement, fronter.fronterName);
118
+
}
119
+
};
120
+
window.addEventListener("message", (event) => {
121
+
if (event.data.type !== "CACHED_FRONTERS") return;
122
+
const fronters = event.data.fronters as Map<string, Fronter | null>;
123
+
const updated = new Map(
124
+
fronters.entries().flatMap(([uri, fronter]) => {
125
+
if (!fronter) return [];
126
+
const rkey = expect(parseResourceUri(uri)).rkey!;
127
+
return [[fronterGetSocialAppHref(fronter, rkey), fronter]];
128
+
}),
129
+
);
130
+
applyFrontersToPage(updated);
131
+
});
132
+
window.addEventListener("message", (event) => {
133
+
if (!["TIMELINE_FRONTER", "THREAD_FRONTER"].includes(event.data.type))
134
+
return;
135
+
applyFrontersToPage(event.data.results as Map<string, any>);
73
136
});
74
137
},
75
138
});
+50
-16
src/entrypoints/isolated.content.ts
+50
-16
src/entrypoints/isolated.content.ts
···
1
-
import { expect } from "@/lib/result";
2
-
import { getFronter } from "@/lib/utils";
1
+
import { Fronter } from "@/lib/utils";
3
2
import { ResourceUri } from "@atcute/lexicons";
4
3
5
4
export default defineContentScript({
···
7
6
runAt: "document_start",
8
7
world: "ISOLATED",
9
8
main: (ctx) => {
9
+
let fronters = new Map<ResourceUri, Fronter | null>();
10
+
10
11
const checkFronter = (url: string) => {
11
12
// match https://*/profile/<actor_identifier>/post/<rkey> regex with named params to extract actor_identifier and rkey
12
13
const match = url.match(
13
14
/https:\/\/[^/]+\/profile\/([^/]+)\/post\/([^/]+)/,
14
15
);
15
-
if (match) {
16
-
const [website, actorIdentifier, rkey] = match;
17
-
const recordUri =
18
-
`at://${actorIdentifier}/app.bsky.feed.post/${rkey}` as ResourceUri;
19
-
browser.runtime.sendMessage({
20
-
type: "TAB_FRONTER",
21
-
recordUri,
22
-
});
23
-
}
16
+
if (!match) return false;
17
+
const [website, actorIdentifier, rkey] = match;
18
+
const recordUri =
19
+
`at://${actorIdentifier}/app.bsky.feed.post/${rkey}` as ResourceUri;
20
+
const fronter = fronters.get(recordUri);
21
+
if (!fronter) return false;
22
+
browser.runtime.sendMessage({
23
+
type: "TAB_FRONTER",
24
+
recordUri,
25
+
fronter,
26
+
});
27
+
return true;
24
28
};
29
+
25
30
const respEventName = Math.random().toString(36).slice(2);
26
31
window.addEventListener(`${respEventName}-isolated`, async (event) => {
27
-
// console.log("sending event to bg:", event);
32
+
const data = (event as any).detail;
33
+
// console.log("passing response event to bg", data);
28
34
await browser.runtime
29
35
.sendMessage({
30
36
type: "RESPONSE_CAPTURED",
31
-
data: (event as any).detail,
37
+
data,
32
38
})
33
39
.catch(() => {
34
40
console.log("background script not ready");
35
41
});
36
42
});
37
-
ctx.addEventListener(window, "wxt:locationchange", async (event) => {
43
+
const messageTypes = [
44
+
"TAB_FRONTER",
45
+
"THREAD_FRONTER",
46
+
"TIMELINE_FRONTER",
47
+
"CACHED_FRONTERS",
48
+
];
49
+
browser.runtime.onMessage.addListener((message) => {
50
+
if (!messageTypes.includes(message.type)) return;
51
+
if (message.type === "CACHED_FRONTERS") {
52
+
fronters = message.fronters;
53
+
}
54
+
window.postMessage(message);
55
+
});
56
+
let postTabObserver: MutationObserver | null = null;
57
+
ctx.addEventListener(window, "wxt:locationchange", (event) => {
58
+
window.postMessage({ type: "CACHED_FRONTERS", fronters });
59
+
// check if we are on profile so we can update fronters if the post tab is clicked on
60
+
const postTabElement = document.querySelector(
61
+
'[data-testid="profilePager-Posts"]',
62
+
);
63
+
if (postTabElement) {
64
+
postTabObserver = new MutationObserver(() => {
65
+
window.postMessage({ type: "CACHED_FRONTERS", fronters });
66
+
});
67
+
postTabObserver.observe(postTabElement, { attributes: true });
68
+
} else if (postTabObserver) {
69
+
postTabObserver.disconnect();
70
+
postTabObserver = null;
71
+
}
72
+
// check for tab fronter for the current "post"
38
73
checkFronter(event.newUrl.toString());
39
74
});
40
75
41
76
// setup response "channel"
42
-
console.log("sending setup message for response channel", respEventName);
43
77
document.dispatchEvent(
44
78
new CustomEvent("at-fronter-channel-setup", {
45
79
detail: respEventName,
46
80
}),
47
81
);
48
82
49
-
checkFronter(document.URL);
83
+
// checkFronter(document.URL);
50
84
},
51
85
});
+16
-3
src/entrypoints/popup/App.svelte
+16
-3
src/entrypoints/popup/App.svelte
···
2
2
import { expect } from "@/lib/result";
3
3
import { getFronter } from "@/lib/utils";
4
4
import { isResourceUri } from "@atcute/lexicons";
5
-
import type { ResourceUri } from "@atcute/lexicons/syntax";
5
+
import type {
6
+
AtprotoDid,
7
+
Handle,
8
+
ResourceUri,
9
+
} from "@atcute/lexicons/syntax";
6
10
7
11
let recordAtUri = $state("");
8
12
let queryResult = $state("");
9
13
let isQuerying = $state(false);
10
14
let fronterName = $state("");
15
+
16
+
const makeOutput = (fronterName: string, handle: Handle | null) => {
17
+
return `HANDLE: ${handle ?? "handle.invalid"}\nFRONTER: ${fronterName}`;
18
+
};
11
19
12
20
const queryRecord = async (recordUri: ResourceUri) => {
13
21
if (!recordAtUri.trim()) return;
···
18
26
try {
19
27
if (!isResourceUri(recordUri)) throw "INVALID_RESOURCE_URI";
20
28
const result = expect(await getFronter(recordUri));
21
-
queryResult = `FRONTER: ${result.name}` || "NO_FRONTER_FOUND";
29
+
queryResult =
30
+
makeOutput(result.fronterName, result.handle) ||
31
+
"NO_FRONTER_FOUND";
22
32
} catch (error) {
23
33
queryResult = `ERROR: ${error}`;
24
34
} finally {
···
47
57
if (fronter) {
48
58
fronterName = fronter;
49
59
}
60
+
50
61
const tabs = await browser.tabs.query({
51
62
active: true,
52
63
currentWindow: true,
···
54
65
const tabFronter = await storage.getItem<{
55
66
fronterName: string;
56
67
recordUri: ResourceUri;
68
+
handle: Handle | null;
69
+
did: AtprotoDid;
57
70
}>(`local:tab-${tabs[0].id!}-fronter`);
58
71
if (tabFronter) {
59
-
queryResult = `FRONTER: ${tabFronter.fronterName}`;
72
+
queryResult = makeOutput(tabFronter.fronterName, tabFronter.handle);
60
73
recordAtUri = tabFronter.recordUri;
61
74
}
62
75
});
+51
-20
src/lib/utils.ts
+51
-20
src/lib/utils.ts
···
5
5
} from "@atcute/lexicons";
6
6
import { Client as AtpClient, simpleFetchHandler } from "@atcute/client";
7
7
import {
8
+
ActorIdentifier,
8
9
Did,
10
+
Handle,
9
11
isHandle,
12
+
RecordKey,
10
13
type AtprotoDid,
11
14
type ResourceUri,
12
15
} from "@atcute/lexicons/syntax";
···
20
23
WebDidDocumentResolver,
21
24
WellKnownHandleResolver,
22
25
} from "@atcute/identity-resolver";
23
-
import { DidDocument, getPdsEndpoint } from "@atcute/identity";
26
+
import { getAtprotoHandle, getPdsEndpoint } from "@atcute/identity";
27
+
28
+
export type Fronter = {
29
+
fronterName: string;
30
+
handle: Handle | null;
31
+
did: AtprotoDid;
32
+
};
33
+
34
+
export const fronterGetSocialAppHref = (
35
+
fronter: Fronter,
36
+
rkey: RecordKey,
37
+
depth?: number,
38
+
) => {
39
+
return depth === 0
40
+
? `/profile/${fronter.handle ?? fronter.did}`
41
+
: `/profile/${fronter.handle ?? fronter.did}/post/${rkey}`;
42
+
};
24
43
25
44
const fronterSchema = v.record(
26
45
v.string(),
···
46
65
},
47
66
});
48
67
49
-
// TODO: cache
68
+
const resolveRepo = async (repo: ActorIdentifier) => {
69
+
let handle: Handle | null;
70
+
let did = repo;
71
+
if (isHandle(repo)) {
72
+
handle = repo;
73
+
did = await handleResolver.resolve(repo);
74
+
} else {
75
+
const didDoc = await docResolver.resolve(repo as AtprotoDid);
76
+
handle = getAtprotoHandle(didDoc) ?? null;
77
+
}
78
+
return { did: did as AtprotoDid, handle };
79
+
};
80
+
50
81
const getAtpClient = async (repo: AtprotoDid) => {
51
82
const didDoc = await docResolver.resolve(repo);
52
83
const pdsUrl = getPdsEndpoint(didDoc);
···
57
88
58
89
export const getFronter = async <Uri extends ResourceUri>(
59
90
recordUri: Uri,
60
-
): Promise<Result<InferOutput<typeof fronterSchema>, string>> => {
91
+
): Promise<Result<Fronter, string>> => {
61
92
const parsedRecordUri = parseResourceUri(recordUri);
62
93
if (!parsedRecordUri.ok) return err(parsedRecordUri.error);
63
94
64
95
// resolve repo
65
-
let repo = parsedRecordUri.value.repo;
66
-
if (isHandle(repo)) {
67
-
// TODO: cache
68
-
repo = await handleResolver.resolve(repo);
69
-
}
96
+
const { did, handle } = await resolveRepo(parsedRecordUri.value.repo);
70
97
71
98
// make client
72
-
const atpClient = await getAtpClient(repo as AtprotoDid);
99
+
const atpClient = await getAtpClient(did);
73
100
74
101
// fetch
75
102
let maybeRecord = await atpClient.get("com.atproto.repo.getRecord", {
76
103
params: {
77
-
repo,
104
+
repo: did,
78
105
collection: fronterSchema.object.shape.$type.expected,
79
106
rkey: `${parsedRecordUri.value.collection}_${parsedRecordUri.value.rkey}`,
80
107
},
···
86
113
const maybeTyped = safeParse(fronterSchema, maybeRecord.data.value);
87
114
if (!maybeTyped.ok) return err(maybeTyped.message);
88
115
89
-
return maybeTyped;
116
+
return ok({
117
+
fronterName: maybeTyped.value.name,
118
+
handle,
119
+
did,
120
+
});
90
121
};
91
122
92
123
export const putFronter = async <Uri extends ResourceUri>(
93
124
recordUri: Uri,
94
125
name: string,
95
126
authToken: string,
96
-
): Promise<Result<void, string>> => {
127
+
): Promise<Result<Fronter, string>> => {
97
128
const parsedRecordUri = parseResourceUri(recordUri);
98
129
if (!parsedRecordUri.ok) return err(parsedRecordUri.error);
99
130
100
131
// resolve repo
101
-
let repo = parsedRecordUri.value.repo;
102
-
if (isHandle(repo)) {
103
-
// TODO: cache
104
-
repo = await handleResolver.resolve(repo);
105
-
}
132
+
const { did, handle } = await resolveRepo(parsedRecordUri.value.repo);
106
133
107
134
// make client
108
-
const atpClient = await getAtpClient(repo as AtprotoDid);
135
+
const atpClient = await getAtpClient(did);
109
136
110
137
// put
111
138
let maybeRecord = await atpClient.post("com.atproto.repo.putRecord", {
112
139
input: {
113
-
repo,
140
+
repo: did,
114
141
collection: fronterSchema.object.shape.$type.expected,
115
142
rkey: `${parsedRecordUri.value.collection}_${parsedRecordUri.value.rkey}`,
116
143
record: { name },
···
121
148
if (!maybeRecord.ok)
122
149
return err(maybeRecord.data.message ?? maybeRecord.data.error);
123
150
124
-
return ok(undefined);
151
+
return ok({
152
+
did,
153
+
handle,
154
+
fronterName: name,
155
+
});
125
156
};