Something went wrong. Try again.
grain.social is a photo sharing platform built on atproto. grain.social
appview atproto photography
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102// 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, OpensocialDeclaration, Member, OpensocialProfile, Rule, ApplyWrites, CreateReport, DescribeCollections, DescribeFeeds, DescribeLabels, GetFeed, GetPreferences, GetRecord, GetRecords, PutPreference, SearchRecords, UploadBlob, Search, GrainActorProfile, Comment, Favorite, Gallery, GrainGalleryItem, Block, GrainGraphFollow, MuteActor, UnmuteActor, Decline, GrainGroupItem, Submission, Photo, Exif, Story, AddSpaceMember, CreatePoolComment, CreatePoolGallery, CreatePrivateGallery, DeleteAccount, DeleteGallery, DeletePoolComment, GetActorFavorites, GetActorProfile, GetBlocks, GetCameras, GetCommentThread, GetFollowers, GetFollowing, GetGallery, GetGalleryFavorites, GetGroup, GetGroupSubmissions, GetKnownFollowers, GetLocations, GetMutes, GetNotifications, GetPoolGallery, GetPrivateBlob, GetPrivateGallery, 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, GrainGroupDefsRule, PhotoView, ExifView, GalleryState, StoryView, GrainStoryDefsViewerState, CreatePoolGalleryPhotoInput, CreatePrivateGalleryPhotoInput, BlockItem, CameraItem, GetFollowersFollowerItem, GetFollowersViewerState, FollowingItem, GetFollowingViewerState, FavoriteItem, GetKnownFollowersFollowerItem, LocationItem, MuteItem, NotificationItem, PoolComment, PoolAuthor, PrivateGallery, PrivateItem, 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.createPoolComment', 'social.grain.unspecced.createPoolGallery', 'social.grain.unspecced.createPrivateGallery', 'social.grain.unspecced.deleteAccount', 'social.grain.unspecced.deleteGallery', 'social.grain.unspecced.deletePoolComment', '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) 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 }}