Simplify handle resolution to a single AppView call
resolveHandle is a public read served by the AppView, so drop the PDS-first auth, the multi-endpoint fallback loop and the threaded service/account args in favor of one unauthenticated GET against config public_appview — matching how BlueskyAnalytics already reads from the AppView. The try/catch stays so a network error degrades the mention to plain text instead of failing the post. Remove the now-obsolete fallback test.
This commit is contained in:
parent
5227cde5c7
commit
60094c9316
2 changed files with 17 additions and 76 deletions
|
|
@ -61,7 +61,7 @@ public function publish(PostPlatform $postPlatform): array
|
|||
|
||||
// Parse facets (links, mentions, hashtags) from text
|
||||
$text = $content ?? '';
|
||||
$facets = $this->parseFacets($text, $service, $account);
|
||||
$facets = $this->parseFacets($text);
|
||||
|
||||
// Create post record
|
||||
$record = [
|
||||
|
|
@ -167,7 +167,7 @@ private function uploadBlob(SocialAccount $account, string $service, string $url
|
|||
}
|
||||
}
|
||||
|
||||
private function parseFacets(string $text, ?string $service = null, ?SocialAccount $account = null): array
|
||||
private function parseFacets(string $text): array
|
||||
{
|
||||
$facets = [];
|
||||
|
||||
|
|
@ -214,7 +214,7 @@ private function parseFacets(string $text, ?string $service = null, ?SocialAccou
|
|||
// A mention facet needs the target's DID, not the handle; skip it if unresolvable.
|
||||
// Cache by key (not ??) so an unresolvable handle is resolved once, not per occurrence.
|
||||
if (! array_key_exists($handle, $didCache)) {
|
||||
$didCache[$handle] = $this->resolveHandleToDid($handle, $service, $account);
|
||||
$didCache[$handle] = $this->resolveHandleToDid($handle);
|
||||
}
|
||||
$did = $didCache[$handle];
|
||||
if ($did === null) {
|
||||
|
|
@ -272,48 +272,26 @@ private function parseFacets(string $text, ?string $service = null, ?SocialAccou
|
|||
/**
|
||||
* Resolve a Bluesky handle to its DID via com.atproto.identity.resolveHandle.
|
||||
*
|
||||
* Tries the account's own PDS first (authenticated), then falls back to the
|
||||
* public AppView and the configured default service. Returns null on failure
|
||||
* so the caller can skip the mention facet instead of sending an invalid record.
|
||||
* resolveHandle is a public read served by the AppView (no auth). Returns
|
||||
* null on any failure so the caller can skip the mention facet and publish
|
||||
* the @handle as plain text instead of an invalid record.
|
||||
*/
|
||||
private function resolveHandleToDid(string $handle, ?string $service = null, ?SocialAccount $account = null): ?string
|
||||
private function resolveHandleToDid(string $handle): ?string
|
||||
{
|
||||
// Normalize so a trailing slash neither produces a double-slash URL nor
|
||||
// breaks the `=== $service` check below (which decides authentication).
|
||||
$service = $service !== null ? rtrim($service, '/') : null;
|
||||
$appView = (string) config('trypost.platforms.bluesky.public_appview');
|
||||
|
||||
$endpoints = array_values(array_unique(array_filter([
|
||||
$service,
|
||||
config('trypost.platforms.bluesky.public_appview'),
|
||||
config('trypost.platforms.bluesky.default_service'),
|
||||
])));
|
||||
try {
|
||||
$response = $this->socialHttp()->get(
|
||||
"{$appView}/xrpc/com.atproto.identity.resolveHandle",
|
||||
['handle' => $handle],
|
||||
);
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
try {
|
||||
$request = $this->socialHttp();
|
||||
if ($account && $endpoint === $service) {
|
||||
$request = $request->withToken($account->access_token);
|
||||
}
|
||||
$did = $response->successful() ? data_get($response->json(), 'did') : null;
|
||||
|
||||
$response = $request->get(
|
||||
"{$endpoint}/xrpc/com.atproto.identity.resolveHandle",
|
||||
['handle' => $handle],
|
||||
);
|
||||
|
||||
$did = $response->successful() ? data_get($response->json(), 'did') : null;
|
||||
if (is_string($did) && str_starts_with($did, 'did:')) {
|
||||
return $did;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
Log::debug('Bluesky handle resolution attempt failed', [
|
||||
'handle' => $handle,
|
||||
'endpoint' => $endpoint,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
return is_string($did) && str_starts_with($did, 'did:') ? $did : null;
|
||||
} catch (Throwable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getUtf8ByteOffset(string $text, int $charOffset): int
|
||||
|
|
|
|||
|
|
@ -169,43 +169,6 @@
|
|||
});
|
||||
});
|
||||
|
||||
test('bluesky publisher falls back to the public AppView when the PDS cannot resolve', function () {
|
||||
$this->post->update(['content' => 'Shout out to @friend.bsky.social']);
|
||||
|
||||
$service = config('trypost.platforms.bluesky.default_service');
|
||||
$appView = config('trypost.platforms.bluesky.public_appview');
|
||||
|
||||
Http::fake([
|
||||
// The account's PDS fails to resolve; resolution must fall back to the
|
||||
// public AppView rather than giving up after the first endpoint.
|
||||
$service.'/xrpc/com.atproto.identity.resolveHandle*' => Http::response(['error' => 'InvalidRequest'], 400),
|
||||
$appView.'/xrpc/com.atproto.identity.resolveHandle*' => Http::response(['did' => 'did:plc:friend456'], 200),
|
||||
$service.'/xrpc/com.atproto.repo.createRecord' => Http::response([
|
||||
'uri' => 'at://did:plc:testuser123/app.bsky.feed.post/3abc123xyz',
|
||||
'cid' => 'bafyreiabc123',
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
$record = $request->data()['record'] ?? null;
|
||||
|
||||
if (! $record) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($record['facets'] ?? [] as $facet) {
|
||||
$feature = $facet['features'][0];
|
||||
if ($feature['$type'] === 'app.bsky.richtext.facet#mention') {
|
||||
return $feature['did'] === 'did:plc:friend456';
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
test('bluesky publisher resolves some mentions and skips the unresolvable ones', function () {
|
||||
$this->post->update(['content' => 'cc @good.bsky.social and @bad.bsky.social']);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue