soulseek, extracted from the platform as a plugin
Seventh plugin out, and the first that was already dead. Soulseek had been switched off since 2026-08-13: hono.ts had the router import and its mount commented out, so createSidecarProxy never ran, registerProxiedPrefix was never called, the slskd:server announcement fired into a void, and officer-slskd was not in the ecosystem at all. The permission entry had been reduced to api: [], which is why assertPermissionTotality was satisfied and the server booted clean. The UI rendered perfectly and every request 404'd. So this is a resurrection as well as a move. The mount is now /api/soulseek, derived via mountPrefix from the appName — all 47 frontend call sites repointed from /slskd/. The credential key in service_connections stays 'slskd': it names the daemon, not the route. Those two strings differ on purpose and conflating them reads the wrong row. The connection stays in core service_connections, as transmission does, so 'slskd' remains in a platform union this plugin cannot extend. Recorded in PLUGIN.md. Unlike transmission this plugin does own a schema: favourites and the two halves of the cached browse tree, which slskd has no concept of. Those three tables had been dropped from the drizzle barrel, so bun db:push would have deleted them on any server holding data. 313 palette classes converted to the semantic tone set, including a shared PHASE_TONE the three transfer views previously each copied.
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import type { SlskdUserInfo, SlskdUserStatus } from './shared';
|
||||
|
||||
/** A peer's presence and profile, as far as either could be reached. */
|
||||
export type SoulseekUser = {
|
||||
username: string;
|
||||
status: SlskdUserStatus | null;
|
||||
info: SlskdUserInfo | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* One peer's presence and profile, cached per username. Selecting a peer you've already looked at paints
|
||||
* from cache instead of blanking the panel back to its empty state and re-asking the network for an
|
||||
* answer it just had — which is what local state did, once per click.
|
||||
*/
|
||||
export function useSoulseekUser(username: string) {
|
||||
const { get } = useClient();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['soulseek', 'user', username],
|
||||
queryFn: async (): Promise<SoulseekUser> => {
|
||||
const u = encodeURIComponent(username);
|
||||
const [status, info] = await Promise.allSettled([
|
||||
get<SlskdUserStatus>(`/soulseek/api/v0/users/${u}/status`),
|
||||
get<SlskdUserInfo>(`/soulseek/api/v0/users/${u}/info`),
|
||||
]);
|
||||
// Half an answer is still worth showing — a reachable peer with a private profile answers only one
|
||||
// of the two. Both failing is what actually means "couldn't reach them".
|
||||
if (status.status === 'rejected' && info.status === 'rejected') {
|
||||
throw new Error(`Couldn't reach ${username} — they may be offline.`);
|
||||
}
|
||||
return {
|
||||
username,
|
||||
status: status.status === 'fulfilled' ? status.value : null,
|
||||
info: info.status === 'fulfilled' ? info.value : null,
|
||||
};
|
||||
},
|
||||
enabled: !!username,
|
||||
// Presence drifts, but not fast enough to be worth a round trip every time the panel remounts.
|
||||
staleTime: 60_000,
|
||||
// An unreachable peer IS the answer, not a fault to retry: retrying only delays saying so.
|
||||
retry: false,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user