~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2
103 lines · 9.8 KB Raw History
// Auto-generated client-safe subset. Do not edit.
// Import this in app components instead of hatk.generated.ts
// to avoid pulling in server-only dependencies.
export type { XrpcSchema } from './hatk.generated.ts'
import type { XrpcSchema } from './hatk.generated.ts'
export type { BskyActorProfile, Post, Postgate, Threadgate, BskyGraphFollow, GermnetworkDeclaration, ApplyWrites, CreateReport, DescribeCollections, DescribeFeeds, DescribeLabels, GetFeed, GetPreferences, GetRecord, GetRecords, PutPreference, SearchRecords, UploadBlob, OpensocialDeclaration, Search, GrainActorProfile, Comment, Favorite, Gallery, GrainGalleryItem, Block, GrainGraphFollow, MuteActor, UnmuteActor, Decline, GrainGroupItem, Submission, Photo, Exif, Story, AddSpaceMember, CreateGroup, CreatePoolComment, CreatePoolGallery, CreatePrivateGallery, DeleteAccount, DeleteGallery, DeletePoolComment, DeletePoolGallery, GetActorFavorites, GetActorProfile, GetBlocks, GetCameras, GetCommentThread, GetFollowers, GetFollowing, GetGallery, GetGalleryFavorites, GetGroup, GetGroupHost, GetGroupSubmissions, GetKnownFollowers, GetLocations, GetMutes, GetNotifications, GetPoolGallery, GetPrivateGallery, GetSitemap, GetSpaceMembers, GetSpaceSupport, GetStories, GetStory, GetStoryArchive, GetStoryAuthors, GetSuggestedFollows, JoinGroup, LeaveGroup, ListGroups, ListPoolFeed, ListPoolGalleries, ListPrivateGalleries, ListSharedGalleries, MarkStoriesViewed, PutPoolFavorite, SearchActorsTypeahead, SearchGalleries, SearchProfiles, RecordRegistry, CreateRecord, DeleteRecord, PutRecord, Nux, MutedWord, SavedFeed, StatusView, BskyActorDefsProfileView, BskyActorDefsViewerState, FeedViewPref, LabelersPref, InterestsPref, KnownFollowers, MutedWordsPref, SavedFeedsPref, ThreadViewPref, DeclaredAgePref, HiddenPostsPref, LabelerPrefItem, AdultContentPref, BskyAppStatePref, ContentLabelPref, ProfileViewBasic, SavedFeedsPrefV2, VerificationView, ProfileAssociated, VerificationPrefs, VerificationState, PersonalDetailsPref, BskyActorDefsProfileViewDetailed, BskyAppProgressGuide, LiveEventPreferences, ProfileAssociatedChat, ProfileAssociatedGerm, PostInteractionSettingsPref, ProfileAssociatedActivitySubscription, BskyEmbedDefsAspectRatio, ExternalView, External, ViewExternal, ImagesView, Image, ViewImage, RecordView, ViewRecord, ViewBlocked, ViewDetached, ViewNotFound, RecordWithMediaView, VideoView, Caption, PostView, BskyFeedDefsReplyRef, ReasonPin, BlockedPost, Interaction, BskyFeedDefsViewerState, FeedViewPost, NotFoundPost, ReasonRepost, BlockedAuthor, GeneratorView, ThreadContext, ThreadViewPost, ThreadgateView, SkeletonFeedPost, SkeletonReasonPin, GeneratorViewerState, SkeletonReasonRepost, Entity, PostReplyRef, TextSlice, DisableRule, ListRule, MentionRule, FollowerRule, FollowingRule, ListView, ListItemView, Relationship, ListViewBasic, NotFoundActor, ListViewerState, StarterPackView, StarterPackViewBasic, LabelerView, LabelerPolicies, LabelerViewerState, LabelerViewDetailed, Preference, Preferences, RecordDeleted, ChatPreference, ActivitySubscription, FilterablePreference, SubjectActivitySubscription, Tag, Link, Mention, ByteSlice, Label, SelfLabels, SelfLabel, LabelValueDefinition, LabelValueDefinitionStrings, GermnetworkDeclarationMessageMe, Create, Update, Delete, CreateResult, UpdateResult, DeleteResult, RepoRef, LabelDefinition, LabelLocale, Result, MentionLabel, EmbedInfo, SearchAspectRatio, SubscopeInfo, GrainActorDefsProfileView, GrainActorDefsProfileViewDetailed, GrainActorDefsMessageMe, GrainActorDefsViewerState, CommentView, GrainCommentDefsViewerState, GrainDefsAspectRatio, GalleryView, CrossPostInfo, GrainGalleryDefsViewerState, GroupView, GrainGroupDefsViewerState, SubmissionState, GroupRef, SubmissionView, Rule, PhotoView, ExifView, GalleryState, StoryView, GrainStoryDefsViewerState, CreatePoolGalleryPhotoInput, CreatePrivateGalleryPhotoInput, BlockItem, CameraItem, GetFollowersFollowerItem, GetFollowersViewerState, FollowingItem, GetFollowingViewerState, FavoriteItem, GetKnownFollowersFollowerItem, LocationItem, MuteItem, NotificationItem, PoolComment, PoolAuthor, PrivateGallery, PrivateItem, SitemapEntry, MemberView, StoryAuthor, SuggestedItem, PoolFeedItem, PoolGallery, PrivateGalleryRef, SharedGalleryRef, ProfileSearchResult } from './hatk.generated.ts'

const _procedures = new Set(['dev.hatk.applyWrites', 'dev.hatk.createRecord', 'dev.hatk.createReport', 'dev.hatk.deleteRecord', 'dev.hatk.putPreference', 'dev.hatk.putRecord', 'social.grain.graph.muteActor', 'social.grain.graph.unmuteActor', 'social.grain.unspecced.addSpaceMember', 'social.grain.unspecced.createGroup', 'social.grain.unspecced.createPoolComment', 'social.grain.unspecced.createPoolGallery', 'social.grain.unspecced.createPrivateGallery', 'social.grain.unspecced.deleteAccount', 'social.grain.unspecced.deleteGallery', 'social.grain.unspecced.deletePoolComment', 'social.grain.unspecced.deletePoolGallery', 'social.grain.unspecced.joinGroup', 'social.grain.unspecced.leaveGroup', 'social.grain.unspecced.markStoriesViewed', 'social.grain.unspecced.putPoolFavorite'])
const _blobInputs = new Set(['dev.hatk.uploadBlob'])

type CallArg<K extends keyof XrpcSchema> =
  XrpcSchema[K] extends { input: infer I } ? I :
  XrpcSchema[K] extends { params: infer P } ? P :
  Record<string, unknown>
type OutputOf<K extends keyof XrpcSchema> = XrpcSchema[K]['output']

export async function callXrpc<K extends keyof XrpcSchema & string>(
  nsid: K,
  arg?: CallArg<K>,
  customFetch?: typeof globalThis.fetch,
): Promise<OutputOf<K>> {
  if (typeof window === 'undefined' && !customFetch) {
    const bridge = (globalThis as any).__hatk_callXrpc
    if (!bridge) throw new Error('callXrpc: server bridge not available — is hatk initialized?')
    if (_procedures.has(nsid) || _blobInputs.has(nsid)) return bridge(nsid, {}, arg) as Promise<OutputOf<K>>
    return bridge(nsid, arg) as Promise<OutputOf<K>>
  }
  const _fetch = customFetch ?? globalThis.fetch
  // Use relative URL so SvelteKit's fetch can deduplicate server/client requests
  let path = `/xrpc/${nsid}`
  if (_blobInputs.has(nsid)) {
    const blob = arg as Blob | ArrayBuffer
    const ct = blob instanceof Blob ? blob.type : 'application/octet-stream'
    const res = await _fetch(path, { method: 'POST', headers: { 'Content-Type': ct }, body: blob })
    if (typeof window !== 'undefined' && res.status === 401) { const _b = await res.json().catch(() => ({})); const _h = _b.handle ?? getViewer()?.handle; window.location.href = _h ? `/oauth/login?handle=${encodeURIComponent(_h)}` : '/oauth/login'; return new Promise(() => {}) as any }
    if (!res.ok) throw new Error(`XRPC ${nsid} failed: ${res.status}`)
    return res.json() as Promise<OutputOf<K>>
  }
  if (_procedures.has(nsid)) {
    const res = await _fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(arg) })
    if (typeof window !== 'undefined' && res.status === 401) { const _b = await res.json().catch(() => ({})); const _h = _b.handle ?? getViewer()?.handle; window.location.href = _h ? `/oauth/login?handle=${encodeURIComponent(_h)}` : '/oauth/login'; return new Promise(() => {}) as any }
    if (!res.ok) throw new Error(`XRPC ${nsid} failed: ${res.status}`)
    return res.json() as Promise<OutputOf<K>>
  }
  const params = new URLSearchParams()
  for (const [k, v] of Object.entries(arg || {})) {
    if (v == null) continue
    if (Array.isArray(v)) for (const item of v) params.append(k, String(item))
    else params.set(k, String(v))
  }
  const qs = params.toString()
  if (qs) path += `?${qs}`
  const res = await _fetch(path)
  if (typeof window !== 'undefined' && res.status === 401) { const _b = await res.json().catch(() => ({})); const _h = _b.handle ?? getViewer()?.handle; window.location.href = _h ? `/oauth/login?handle=${encodeURIComponent(_h)}` : '/oauth/login'; return new Promise(() => {}) as any }
  if (!res.ok) throw new Error(`XRPC ${nsid} failed: ${res.status}`)
  return res.json() as Promise<OutputOf<K>>
}

export function getViewer(): { did: string; handle: string } | null {
  return (globalThis as any).__hatk_viewer ?? null
}

// ─── Auth Helpers ────────────────────────────────────────────────────

export async function login(handle: string): Promise<void> {
  const res = await fetch(`/oauth/login?handle=${encodeURIComponent(handle)}`, { redirect: 'manual' })
  if (res.type === 'opaqueredirect') {
    window.location.href = `/oauth/login?handle=${encodeURIComponent(handle)}`
    return
  }
  if (res.ok) return
  const body = await res.json().catch(() => ({ error: 'Login failed' }))
  throw new Error(body.error || 'Login failed')
}

export async function logout(): Promise<void> {
  ;(globalThis as any).__hatk_viewer = null
  await fetch('/auth/logout', { method: 'POST' }).catch(() => {})
}

export function viewerDid(): string | null {
  if (typeof window === 'undefined') return null
  const viewer = (globalThis as any).__hatk_viewer
  return viewer?.did ?? null
}

// Expose viewer for getViewer() bridge
;(globalThis as any).__hatk_auth = { viewerDid }

// ─── Server Helpers ──────────────────────────────────────────────────

export async function parseViewer(cookies: { get(name: string): string | undefined }): Promise<{ did: string; handle?: string } | null> {
  const parseSessionCookie = (globalThis as any).__hatk_parseSessionCookie
  if (!parseSessionCookie) return null
  const cookieValue = cookies.get('__hatk_session')
  if (!cookieValue) return null
  try {
    const request = new Request('http://localhost', { headers: { cookie: `__hatk_session=${cookieValue}` } })
    const viewer = await parseSessionCookie(request)
    if (viewer) (globalThis as any).__hatk_viewer = viewer
    return viewer
  } catch { return null }
}