├── .gitignore ├── .eslintrc.json ├── src ├── app │ └── [locale] │ │ ├── icon.ico │ │ ├── guides │ │ ├── oauth │ │ │ ├── oauth-domain.jpg │ │ │ └── page.tsx │ │ ├── applications │ │ │ ├── app-banner.png │ │ │ ├── app-login.png │ │ │ ├── diagram-repo.png │ │ │ ├── app-screenshot.png │ │ │ ├── diagram-oauth.png │ │ │ ├── diagram-info-flow.png │ │ │ ├── app-status-history.png │ │ │ ├── app-status-options.png │ │ │ ├── diagram-event-stream.png │ │ │ ├── diagram-optimistic-update.png │ │ │ └── page.tsx │ │ ├── faq │ │ │ ├── page.tsx │ │ │ ├── ko.mdx │ │ │ ├── ja.mdx │ │ │ ├── en.mdx │ │ │ └── pt.mdx │ │ ├── lexicon │ │ │ └── page.tsx │ │ ├── identity │ │ │ └── page.tsx │ │ ├── overview │ │ │ ├── page.tsx │ │ │ ├── ja.mdx │ │ │ └── ko.mdx │ │ ├── account-migration │ │ │ ├── page.tsx │ │ │ └── ko.mdx │ │ ├── account-lifecycle │ │ │ ├── page.tsx │ │ │ └── ko.mdx │ │ ├── going-to-production │ │ │ └── page.tsx │ │ ├── data-repos │ │ │ ├── page.tsx │ │ │ ├── ja.mdx │ │ │ ├── ko.mdx │ │ │ ├── en.mdx │ │ │ └── pt.mdx │ │ ├── lexicon-style-guide │ │ │ └── page.tsx │ │ ├── data-validation │ │ │ └── page.tsx │ │ ├── glossary │ │ │ └── page.tsx │ │ └── self-hosting │ │ │ └── page.tsx │ │ ├── explorer │ │ └── page.mdx │ │ ├── articles │ │ ├── why-atproto │ │ │ └── page.mdx │ │ ├── atproto-for-distsys-engineers │ │ │ ├── image1.png │ │ │ ├── image2.png │ │ │ ├── image3.png │ │ │ ├── image4.png │ │ │ ├── image5.png │ │ │ ├── image6.png │ │ │ ├── image7.png │ │ │ ├── image8.png │ │ │ ├── image9.png │ │ │ ├── image10.png │ │ │ ├── image11.png │ │ │ ├── image12.png │ │ │ ├── image13.png │ │ │ ├── image14.png │ │ │ ├── image15.png │ │ │ ├── image16.png │ │ │ ├── image17.png │ │ │ ├── image18.png │ │ │ ├── image19.png │ │ │ ├── image20.png │ │ │ ├── image21.png │ │ │ ├── image22.png │ │ │ ├── image23.png │ │ │ ├── image24.png │ │ │ └── page.tsx │ │ └── atproto-ethos │ │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── specs │ │ ├── blob │ │ │ ├── page.tsx │ │ │ └── ko.mdx │ │ ├── account │ │ │ └── page.tsx │ │ ├── lexicon │ │ │ └── page.tsx │ │ ├── permission │ │ │ └── page.tsx │ │ ├── sync │ │ │ └── page.tsx │ │ ├── did │ │ │ └── page.tsx │ │ ├── handle │ │ │ └── page.tsx │ │ ├── oauth │ │ │ └── page.tsx │ │ ├── xrpc │ │ │ └── page.tsx │ │ ├── data-model │ │ │ └── page.tsx │ │ ├── record-key │ │ │ ├── page.tsx │ │ │ └── ko.mdx │ │ ├── nsid │ │ │ ├── page.tsx │ │ │ ├── ko.mdx │ │ │ └── en.mdx │ │ ├── repository │ │ │ └── page.tsx │ │ ├── at-uri-scheme │ │ │ └── page.tsx │ │ ├── atp │ │ │ ├── page.tsx │ │ │ └── ko.mdx │ │ ├── cryptography │ │ │ ├── page.tsx │ │ │ └── ko.mdx │ │ ├── tid │ │ │ ├── page.tsx │ │ │ ├── ko.mdx │ │ │ └── en.mdx │ │ ├── label │ │ │ └── page.tsx │ │ └── event-stream │ │ │ └── page.tsx │ │ ├── sdks │ │ └── page.mdx │ │ ├── not-found.tsx │ │ ├── providers.tsx │ │ ├── layout.tsx │ │ └── pt.mdx ├── images │ ├── media │ │ ├── whitepaper.png │ │ ├── tbd-interview.jpg │ │ ├── web-without-walls.png │ │ ├── dotmedia-interview.png │ │ └── how-does-bsky-work-blogpost.png │ └── logos │ │ ├── ruby.svg │ │ ├── python.svg │ │ ├── node.svg │ │ ├── php.svg │ │ ├── ts.svg │ │ └── go.svg ├── mdx │ ├── recma.mjs │ ├── remark.mjs │ └── rehype.mjs ├── lib │ └── remToPx.ts ├── components │ ├── Container.tsx │ ├── icons │ │ ├── BoltIcon.tsx │ │ ├── ArrowIcon.tsx │ │ ├── ShirtIcon.tsx │ │ ├── LinkIcon.tsx │ │ ├── MagnifyingGlassIcon.tsx │ │ ├── CheckIcon.tsx │ │ ├── BookIcon.tsx │ │ ├── DocumentIcon.tsx │ │ ├── RssIcon.tsx │ │ ├── PaperAirplaneIcon.tsx │ │ ├── FaceSmileIcon.tsx │ │ ├── ListIcon.tsx │ │ ├── PaperClipIcon.tsx │ │ ├── PackageIcon.tsx │ │ ├── CopyIcon.tsx │ │ ├── ChevronRightLeftIcon.tsx │ │ ├── EnvelopeIcon.tsx │ │ ├── BellIcon.tsx │ │ ├── CartIcon.tsx │ │ ├── ChatBubbleIcon.tsx │ │ ├── ShapesIcon.tsx │ │ ├── SignalIcon.tsx │ │ ├── SquaresPlusIcon.tsx │ │ ├── ClipboardIcon.tsx │ │ ├── MapPinIcon.tsx │ │ ├── CalendarIcon.tsx │ │ ├── FolderIcon.tsx │ │ ├── UsersIcon.tsx │ │ ├── UserIcon.tsx │ │ ├── TagIcon.tsx │ │ └── CogIcon.tsx │ ├── IconContainer.tsx │ ├── Prose.tsx │ ├── DescriptionList.tsx │ ├── HeroPattern.tsx │ ├── ImageCard.tsx │ ├── CodeCard.tsx │ ├── GridPattern.tsx │ ├── ArticleSummary.tsx │ ├── ThemeToggle.tsx │ ├── Layout.tsx │ ├── LanguageChanger.tsx │ ├── Tag.tsx │ ├── Button.tsx │ ├── Guides.tsx │ ├── Heading.tsx │ ├── Feedback.tsx │ ├── Header.tsx │ ├── FooterCTA.tsx │ ├── mdx.tsx │ ├── SectionProvider.tsx │ └── MobileNavigation.tsx ├── middleware.js └── styles │ └── tailwind.css ├── postcss.config.js ├── public └── default-social-card.png ├── i18nConfig.js ├── prettier.config.js ├── next-env.d.ts ├── mdx-components.tsx ├── types.d.ts ├── LICENSE.txt ├── tsconfig.json ├── Makefile ├── _redirects ├── tailwind.config.ts ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /src/app/[locale]/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/icon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/default-social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/public/default-social-card.png -------------------------------------------------------------------------------- /src/images/media/whitepaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/images/media/whitepaper.png -------------------------------------------------------------------------------- /src/mdx/recma.mjs: -------------------------------------------------------------------------------- 1 | import { mdxAnnotations } from 'mdx-annotations' 2 | 3 | export const recmaPlugins = [mdxAnnotations.recma] 4 | -------------------------------------------------------------------------------- /src/images/media/tbd-interview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/images/media/tbd-interview.jpg -------------------------------------------------------------------------------- /src/images/media/web-without-walls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/images/media/web-without-walls.png -------------------------------------------------------------------------------- /src/images/media/dotmedia-interview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/images/media/dotmedia-interview.png -------------------------------------------------------------------------------- /i18nConfig.js: -------------------------------------------------------------------------------- 1 | const i18nConfig = { 2 | locales: ['en', 'pt', 'ja', 'ko'], 3 | defaultLocale: 'en', 4 | } 5 | 6 | module.exports = i18nConfig 7 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/oauth/oauth-domain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/oauth/oauth-domain.jpg -------------------------------------------------------------------------------- /src/app/[locale]/explorer/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Explorer', 3 | description: 4 | 'TODO', 5 | } 6 | 7 | # Explorer 8 | 9 | TODO -------------------------------------------------------------------------------- /src/images/media/how-does-bsky-work-blogpost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/images/media/how-does-bsky-work-blogpost.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/app-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/app-banner.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/app-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/app-login.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/diagram-repo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/diagram-repo.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/app-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/app-screenshot.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/diagram-oauth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/diagram-oauth.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/diagram-info-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/diagram-info-flow.png -------------------------------------------------------------------------------- /src/mdx/remark.mjs: -------------------------------------------------------------------------------- 1 | import { mdxAnnotations } from 'mdx-annotations' 2 | import remarkGfm from 'remark-gfm' 3 | 4 | export const remarkPlugins = [mdxAnnotations.remark, remarkGfm] 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Options} */ 2 | module.exports = { 3 | singleQuote: true, 4 | semi: false, 5 | plugins: ['prettier-plugin-tailwindcss'], 6 | } 7 | -------------------------------------------------------------------------------- /src/app/[locale]/articles/why-atproto/page.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Why AT Protocol?', 3 | description: 4 | 'TODO', 5 | } 6 | 7 | # Why AT Protocol? 8 | 9 | TODO -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/app-status-history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/app-status-history.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/app-status-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/app-status-options.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/diagram-event-stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/diagram-event-stream.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image1.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image2.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image3.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image4.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image5.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image6.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image7.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image8.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image9.png -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/diagram-optimistic-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/guides/applications/diagram-optimistic-update.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image10.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image11.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image12.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image13.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image14.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image15.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image16.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image17.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image18.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image19.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image20.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image21.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image22.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image23.png -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/image24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluesky-social/atproto-website/HEAD/src/app/[locale]/articles/atproto-for-distsys-engineers/image24.png -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import * as mdxComponents from '@/components/mdx' 2 | 3 | declare global { 4 | type MDXProvidedComponents = typeof mdxComponents 5 | } 6 | 7 | export function useMDXComponents() { 8 | return mdxComponents 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/remToPx.ts: -------------------------------------------------------------------------------- 1 | export function remToPx(remValue: number) { 2 | let rootFontSize = 3 | typeof window === 'undefined' 4 | ? 16 5 | : parseFloat(window.getComputedStyle(document.documentElement).fontSize) 6 | 7 | return remValue * rootFontSize 8 | } 9 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | import { type SearchOptions } from 'flexsearch' 2 | 3 | declare module '@/mdx/search.mjs' { 4 | export type Result = { 5 | url: string 6 | title: string 7 | pageTitle?: string 8 | } 9 | 10 | export function search(query: string, options?: SearchOptions): Array 11 | } 12 | -------------------------------------------------------------------------------- /src/app/[locale]/page.tsx: -------------------------------------------------------------------------------- 1 | export default async function HomePage({ params }: any) { 2 | try { 3 | const Content = (await import(`./${params.locale}.mdx`)).default 4 | return 5 | } catch (error) { 6 | const Content = (await import(`./en.mdx`)).default 7 | return 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Container.tsx: -------------------------------------------------------------------------------- 1 | export function Container({ children }: { children: React.ReactNode }) { 2 | return {children} 3 | } 4 | // flex-auto prose dark:prose-invert [html_:where(&>*)]:mx-auto [html_:where(&>*)]:max-w-2xl [html_:where(&>*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(&>*)]:lg:max-w-3xl 5 | -------------------------------------------------------------------------------- /src/middleware.js: -------------------------------------------------------------------------------- 1 | import { i18nRouter } from 'next-i18n-router' 2 | import i18nConfig from '../i18nConfig' 3 | 4 | export function middleware(request) { 5 | return i18nRouter(request, i18nConfig) 6 | } 7 | 8 | // only applies this middleware to files in the app directory 9 | export const config = { 10 | matcher: '/((?!api|static|.*\\..*|_next).*)', 11 | } 12 | -------------------------------------------------------------------------------- /src/components/icons/BoltIcon.tsx: -------------------------------------------------------------------------------- 1 | export function BoltIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/icons/ArrowIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ArrowIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/blob/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Blobs', 3 | description: 'Media files referenced by records', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/account/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Accounts', 3 | description: 'Account Hosting and Lifecycle', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/lexicon/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Lexicon', 3 | description: 'A schema definition language.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/icons/ShirtIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ShirtIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/faq/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'FAQ', 3 | description: 'Frequently Asked Questions about AT Protocol.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/lexicon/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Lexicon', 3 | description: 'A schema-driven interoperability framework', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/identity/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Identity', 3 | description: 'How the AT Protocol handles user identity.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/overview/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Protocol Overview', 3 | description: 'An introduction to the AT Protocol.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/permission/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Permissions', 3 | description: 'Auth Permissions for Account Resources', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/sync/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Sync', 3 | description: 'Firehose and other data synchronization mechanisms.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/did/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'DID', 3 | description: 'Persistent decentralized identifiers (as used in atproto)', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/handle/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Handle', 3 | description: 'A specification for human-friendly account identifiers.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/oauth/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'OAuth', 3 | description: 'OAuth for Client/Server Authentication and Authorization', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/xrpc/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'HTTP API (XRPC)', 3 | description: 'Cross-system queries and procedures over HTTP', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/account-migration/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Account Migration', 3 | description: 'How to implement account migration.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/oauth/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'OAuth Introduction', 3 | description: 'OAuth for AT Protocol application developers.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/data-model/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Data Model', 3 | description: 'Consistent data encoding for records and messages.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/record-key/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Record Key', 3 | description: 'Identifier for individual records in a collection', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/account-lifecycle/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Account Lifecyle Events', 3 | description: 'Account Lifecycle Best Practices.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/going-to-production/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Going to production', 3 | description: 4 | 'Improve your PDS deployment.', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/nsid/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Namespaced Identifiers (NSIDs)', 3 | description: 'A specification for global semantic IDs.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/repository/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Repository', 3 | description: 'Self-authenticating storage for public account content', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/data-repos/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Personal Data Repositories', 3 | description: 'A guide to the AT Protocol repo structure.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/at-uri-scheme/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'AT URI scheme (at://)', 3 | description: 'A URI scheme for addressing ATP repository data.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/icons/LinkIcon.tsx: -------------------------------------------------------------------------------- 1 | export function LinkIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/lexicon-style-guide/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Lexicon Style Guide', 3 | description: 'A style guide for creating new ATProto schemas', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/sdks/page.mdx: -------------------------------------------------------------------------------- 1 | import { Libraries } from '@/components/Libraries' 2 | import logoGo from '@/images/logos/go.svg' 3 | 4 | export const metadata = { 5 | title: 'SDKs', 6 | description: 7 | 'Official and unofficial libraries for building on AT Protocol', 8 | } 9 | 10 | export const sections = [ 11 | { title: 'Official libraries', id: 'official-libraries' }, 12 | { title: 'Community libraries', id: 'community-libraries' }, 13 | ] 14 | 15 | # SDKs 16 | 17 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/data-validation/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Data Validation', 3 | description: 'Expectations around recommended data limits and validation.', 4 | } 5 | 6 | export default async function HomePage({ params }: any) { 7 | try { 8 | const Content = (await import(`./${params.locale}.mdx`)).default 9 | return 10 | } catch (error) { 11 | const Content = (await import(`./en.mdx`)).default 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/atp/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'AT Protocol', 3 | description: 4 | 'Specification for the Authenticated Transfer Protocol (AT Protocol)', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/cryptography/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Cryptography', 3 | description: 4 | 'Cryptographic systems, curves, and key types used in AT Protocol', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/tid/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Timestamp Identifiers (TIDs)', 3 | description: 4 | 'A compact timestamp-based identifier for revisions and records.', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/glossary/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Glossary of terms', 3 | description: 4 | 'A collection of terminology used in the AT Protocol and their definitions.', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/icons/MagnifyingGlassIcon.tsx: -------------------------------------------------------------------------------- 1 | export function MagnifyingGlassIcon( 2 | props: React.ComponentPropsWithoutRef<'svg'>, 3 | ) { 4 | return ( 5 | 6 | 7 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/label/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Labels', 3 | description: 4 | 'Self-authenticating string annotations on accounts or content for moderation and other purposes.', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/event-stream/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Event Stream', 3 | description: 4 | 'Network wire protocol for subscribing to a stream of Lexicon objects', 5 | wip: true, 6 | } 7 | 8 | export default async function HomePage({ params }: any) { 9 | try { 10 | const Content = (await import(`./${params.locale}.mdx`)).default 11 | return 12 | } catch (error) { 13 | const Content = (await import(`./en.mdx`)).default 14 | return 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-ethos/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Atproto Ethos', 3 | description: 4 | "A deep dive into the philosophical and aesthetic principles underlying the design of AT Protocol.", 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/icons/CheckIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CheckIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/BookIcon.tsx: -------------------------------------------------------------------------------- 1 | export function BookIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/DocumentIcon.tsx: -------------------------------------------------------------------------------- 1 | export function DocumentIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/RssIcon.tsx: -------------------------------------------------------------------------------- 1 | export function RssIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 10 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/PaperAirplaneIcon.tsx: -------------------------------------------------------------------------------- 1 | export function PaperAirplaneIcon( 2 | props: React.ComponentPropsWithoutRef<'svg'>, 3 | ) { 4 | return ( 5 | 6 | 12 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/self-hosting/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Self-hosting', 3 | description: 4 | 'Self-hosting a Bluesky PDS means running your own Personal Data Server that is capable of federating with the wider ATProto network.', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/icons/FaceSmileIcon.tsx: -------------------------------------------------------------------------------- 1 | export function FaceSmileIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/ListIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ListIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/images/logos/ruby.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/icons/PaperClipIcon.tsx: -------------------------------------------------------------------------------- 1 | export function PaperClipIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/applications/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Quick start guide to building applications on AT Protocol', 3 | description: 4 | 'In this guide, we\'re going to build a simple multi-user app that publishes your current "status" as an emoji.', 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/icons/PackageIcon.tsx: -------------------------------------------------------------------------------- 1 | export function PackageIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 8 | 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/icons/CopyIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CopyIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/ChevronRightLeftIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ChevronRightLeftIcon( 2 | props: React.ComponentPropsWithoutRef<'svg'>, 3 | ) { 4 | return ( 5 | 6 | 11 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/components/icons/EnvelopeIcon.tsx: -------------------------------------------------------------------------------- 1 | export function EnvelopeIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/BellIcon.tsx: -------------------------------------------------------------------------------- 1 | export function BellIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/CartIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CartIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 8 | 13 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/app/[locale]/articles/atproto-for-distsys-engineers/page.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'ATProto for distributed systems engineers', 3 | description: 4 | "AT Protocol is the tech developed at Bluesky for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering.", 5 | } 6 | 7 | export default async function HomePage({ params }: any) { 8 | try { 9 | const Content = (await import(`./${params.locale}.mdx`)).default 10 | return 11 | } catch (error) { 12 | const Content = (await import(`./en.mdx`)).default 13 | return 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/icons/ChatBubbleIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ChatBubbleIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/ShapesIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ShapesIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution (CC-BY) 2 | 3 | Copyright (c) 2022-2025 Bluesky Social PBC, and Contributors 4 | 5 | Documentation text and blog posts in this repository are licensed under a permissive CC-BY license. 6 | 7 | For anybody interested in derivative works of documents and specifications, remember that: 8 | 9 | - you must give attribution (credit) to the original work 10 | - you must indicate any changes made 11 | - trademark rights are *not* granted (for example, to "Bluesky", "AT Protocol", or "atproto", or any logos or icons) 12 | 13 | Inline code examples, example data, and regular expressions are under Creative Commons Zero (CC-0, aka Public Domain) and copy/pasted without attribution. 14 | -------------------------------------------------------------------------------- /src/components/icons/SignalIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SignalIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 10 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/SquaresPlusIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SquaresPlusIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 9 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/IconContainer.tsx: -------------------------------------------------------------------------------- 1 | export function IconContainer({ 2 | icon: Icon, 3 | }: { 4 | icon: React.ComponentType<{ className?: string }> 5 | }) { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/icons/ClipboardIcon.tsx: -------------------------------------------------------------------------------- 1 | export function ClipboardIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/MapPinIcon.tsx: -------------------------------------------------------------------------------- 1 | export function MapPinIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/components/icons/CalendarIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CalendarIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 15 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Prose.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | 3 | export function Prose({ 4 | as, 5 | className, 6 | ...props 7 | }: Omit, 'as' | 'className'> & { 8 | as?: T 9 | className?: string 10 | }) { 11 | let Component = as ?? 'div' 12 | 13 | return ( 14 | *)` is used to select all direct children without an increase in specificity like you'd get from just `& > *` 19 | '[html_:where(&>*)]:mx-auto [html_:where(&>*)]:max-w-2xl [html_:where(&>*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(&>*)]:lg:max-w-3xl', 20 | )} 21 | {...props} 22 | /> 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/components/icons/FolderIcon.tsx: -------------------------------------------------------------------------------- 1 | export function FolderIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 14 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/DescriptionList.tsx: -------------------------------------------------------------------------------- 1 | export function DescriptionList({ children }: { children: React.ReactNode }) { 2 | return ( 3 | 4 | 5 | {children} 6 | 7 | 8 | ) 9 | } 10 | 11 | export function Description({ 12 | title, 13 | children, 14 | }: { 15 | title: string 16 | children: React.ReactNode 17 | }) { 18 | return ( 19 | 20 | 21 | {title} 22 | 23 | 24 | {children} 25 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "bundler", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "mdx": { 27 | "checkMdx": true 28 | }, 29 | "include": [ 30 | "next-env.d.ts", 31 | "**/*.mdx", 32 | "**/*.ts", 33 | "**/*.tsx", 34 | ".next/types/**/*.ts" 35 | ], 36 | "exclude": ["node_modules"] 37 | } 38 | -------------------------------------------------------------------------------- /src/app/[locale]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/Button' 2 | import { HeroPattern } from '@/components/HeroPattern' 3 | 4 | export default function NotFound() { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 404 11 | 12 | 13 | Page not found 14 | 15 | 16 | Sorry, we couldn’t find the page you’re looking for. 17 | 18 | 19 | Back to docs 20 | 21 | 22 | > 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/images/logos/python.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/icons/UsersIcon.tsx: -------------------------------------------------------------------------------- 1 | export function UsersIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 15 | 21 | 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/components/HeroPattern.tsx: -------------------------------------------------------------------------------- 1 | import { GridPattern } from '@/components/GridPattern' 2 | 3 | export function HeroPattern() { 4 | return ( 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/components/icons/UserIcon.tsx: -------------------------------------------------------------------------------- 1 | export function UserIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 16 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/app/[locale]/providers.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect } from 'react' 4 | import { ThemeProvider, useTheme } from 'next-themes' 5 | 6 | function ThemeWatcher() { 7 | let { resolvedTheme, setTheme } = useTheme() 8 | 9 | useEffect(() => { 10 | let media = window.matchMedia('(prefers-color-scheme: dark)') 11 | 12 | function onMediaChange() { 13 | let systemTheme = media.matches ? 'dark' : 'light' 14 | if (resolvedTheme === systemTheme) { 15 | setTheme('system') 16 | } 17 | } 18 | 19 | onMediaChange() 20 | media.addEventListener('change', onMediaChange) 21 | 22 | return () => { 23 | media.removeEventListener('change', onMediaChange) 24 | } 25 | }, [resolvedTheme, setTheme]) 26 | 27 | return null 28 | } 29 | 30 | export function Providers({ children }: { children: React.ReactNode }) { 31 | return ( 32 | 33 | 34 | {children} 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | SHELL = /bin/bash 3 | .SHELLFLAGS = -o pipefail -c 4 | 5 | .PHONY: help 6 | help: ## Print info about all commands 7 | @echo "Helper Commands:" 8 | @echo 9 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[01;32m%-20s\033[0m %s\n", $$1, $$2}' 10 | @echo 11 | @echo "NOTE: dependencies between commands are not automatic. Eg, you must run 'deps' and 'build' first, and after any changes" 12 | 13 | .PHONY: build 14 | build: ## Compile all modules 15 | yarn build 16 | 17 | .PHONY: test 18 | test: ## Run tests 19 | yarn type-check 20 | 21 | .PHONY: dev 22 | dev: ## Run a "development environment" shell 23 | yarn dev 24 | 25 | .PHONY: lint 26 | lint: ## Run style checks and verify syntax 27 | yarn lint 28 | 29 | .PHONY: deps 30 | deps: ## Installs dependent libs using 'yarn install' 31 | yarn install --frozen-lockfile 32 | 33 | .PHONY: nvm-setup 34 | nvm-setup: ## Use NVM to install and activate node+yarn 35 | nvm install 22 36 | nvm use 22 37 | npm install --global yarn 38 | -------------------------------------------------------------------------------- /src/components/ImageCard.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import Image from 'next/image' 3 | import { Tag } from './Tag' 4 | 5 | export function ImageCard({ 6 | image, 7 | href, 8 | title, 9 | tag, 10 | description, 11 | }: React.PropsWithChildren<{ 12 | image: string 13 | href: string 14 | title: string 15 | tag: string 16 | description: string 17 | }>) { 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | {title} 29 | 30 | 31 | {tag} 32 | 33 | {description} 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /src/components/icons/TagIcon.tsx: -------------------------------------------------------------------------------- 1 | export function TagIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | # Redirects for /community URLs 2 | /community https://docs.bsky.app/showcase 301 3 | /community/quick-start https://docs.bsky.app/docs/get-started 301 4 | /community/projects https://docs.bsky.app/showcase 301 5 | /community/groups https://docs.bsky.app/showcase 301 6 | 7 | # Redirects for /blog URLs 8 | /blog https://docs.bsky.app/blog 301 9 | /blog/feature-bridgyfed https://docs.bsky.app/blog/feature-bridgyfed 301 10 | /blog/repo-export https://docs.bsky.app/blog/repo-export 301 11 | /blog/2023-protocol-roadmap https://docs.bsky.app/blog/protocol-roadmap 301 12 | /blog/building-on-atproto https://docs.bsky.app/blog/building-on-atproto 301 13 | /blog/bgs-and-did-doc https://docs.bsky.app/blog/bgs-and-did-doc 301 14 | /blog/rate-limits-pds-v3 https://docs.bsky.app/blog/rate-limits-pds-v3 301 15 | /blog/repo-sync-update https://docs.bsky.app/blog/repo-sync-update 301 16 | /blog/create-post https://docs.bsky.app/docs/advanced-guides/posts 301 17 | /blog/feature-skyfeed https://docs.bsky.app/blog/feature-skyfeed 301 18 | /blog/call-for-developers https://docs.bsky.app/blog/call-for-developers 301 19 | /blog/federation-developer-sandbox https://docs.bsky.app/blog/federation-sandbox 301 20 | /blog/block-implementation https://docs.bsky.app/blog/block-implementation 301 21 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @layer base { 2 | .dark { 3 | --shiki-color-text: theme('colors.white'); 4 | --shiki-token-constant: theme('colors.emerald.300'); 5 | --shiki-token-string: theme('colors.emerald.300'); 6 | --shiki-token-comment: theme('colors.zinc.500'); 7 | --shiki-token-keyword: theme('colors.sky.300'); 8 | --shiki-token-parameter: theme('colors.pink.300'); 9 | --shiki-token-function: theme('colors.violet.300'); 10 | --shiki-token-string-expression: theme('colors.emerald.300'); 11 | --shiki-token-punctuation: theme('colors.zinc.500'); 12 | } 13 | 14 | .light { 15 | --shiki-color-text: theme('colors.zinc.700'); 16 | --shiki-token-constant: theme('colors.green.600'); 17 | --shiki-token-string: theme('colors.green.600'); 18 | --shiki-token-comment: theme('colors.zinc.500'); 19 | --shiki-token-keyword: theme('colors.blue.500'); 20 | --shiki-token-parameter: theme('colors.pink.500'); 21 | --shiki-token-function: theme('colors.violet.500'); 22 | --shiki-token-string-expression: theme('colors.green.600'); 23 | --shiki-token-punctuation: theme('colors.zinc.500'); 24 | } 25 | 26 | [inert] ::-webkit-scrollbar { 27 | display: none; 28 | } 29 | } 30 | 31 | @tailwind base; 32 | @tailwind components; 33 | @tailwind utilities; 34 | -------------------------------------------------------------------------------- /src/components/CodeCard.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { IconContainer } from './IconContainer' 3 | import { ArrowIcon } from './icons/ArrowIcon' 4 | 5 | export function CodeCard({ 6 | href, 7 | title, 8 | description, 9 | icon, 10 | children, 11 | }: React.PropsWithChildren<{ 12 | href: string 13 | title: string 14 | description: string 15 | icon?: React.ComponentType<{ className?: string }> 16 | }>) { 17 | return ( 18 | 19 | 20 | {icon && } 21 | 22 | 23 | {description} 24 | 25 | 26 | 27 | Learn more 28 | 29 | 30 | 31 | {children} 32 | 33 | 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/images/logos/node.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/GridPattern.tsx: -------------------------------------------------------------------------------- 1 | import { useId } from 'react' 2 | 3 | export function GridPattern({ 4 | width, 5 | height, 6 | x, 7 | y, 8 | squares, 9 | ...props 10 | }: React.ComponentPropsWithoutRef<'svg'> & { 11 | width: number 12 | height: number 13 | x: string | number 14 | y: string | number 15 | squares: Array<[x: number, y: number]> 16 | }) { 17 | let patternId = useId() 18 | 19 | return ( 20 | 21 | 22 | 30 | 31 | 32 | 33 | 39 | {squares && ( 40 | 41 | {squares.map(([x, y]) => ( 42 | 50 | ))} 51 | 52 | )} 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/images/logos/php.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/ArticleSummary.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import { Button } from './Button' 3 | import { Tag } from './Tag' 4 | 5 | export function ArticleSummary({ 6 | tag, 7 | title, 8 | children, 9 | href, 10 | }: React.PropsWithChildren<{ 11 | tag: string 12 | title: string 13 | href: string 14 | }>) { 15 | return ( 16 | 17 | 18 | {tag} 19 | 20 | 21 | 22 | {title} 23 | 24 | 25 | {children} 26 | 27 | 28 | <>Keep reading> 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | export function ArticleSummarySmall({ 36 | title, 37 | children, 38 | href, 39 | }: React.PropsWithChildren<{ 40 | title: string 41 | href: string 42 | }>) { 43 | return ( 44 | 45 | 46 | 47 | 48 | 49 | 50 | {title} 51 | 52 | {children} 53 | 54 | 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import headlessuiPlugin from '@headlessui/tailwindcss' 2 | import typographyPlugin from '@tailwindcss/typography' 3 | import { type Config } from 'tailwindcss' 4 | 5 | import typographyStyles from './typography' 6 | 7 | export default { 8 | content: ['./src/**/*.{js,mjs,jsx,ts,tsx,mdx}'], 9 | darkMode: 'selector', 10 | theme: { 11 | fontSize: { 12 | '2xs': ['0.75rem', { lineHeight: '1.25rem' }], 13 | xs: ['0.8125rem', { lineHeight: '1.5rem' }], 14 | sm: ['0.875rem', { lineHeight: '1.5rem' }], 15 | base: ['1rem', { lineHeight: '1.75rem' }], 16 | lg: ['1.125rem', { lineHeight: '1.75rem' }], 17 | xl: ['1.25rem', { lineHeight: '1.75rem' }], 18 | '2xl': ['1.5rem', { lineHeight: '2rem' }], 19 | '3xl': ['1.875rem', { lineHeight: '2.25rem' }], 20 | '4xl': ['2.25rem', { lineHeight: '2.5rem' }], 21 | '5xl': ['3rem', { lineHeight: '1' }], 22 | '6xl': ['3.75rem', { lineHeight: '1' }], 23 | '7xl': ['4.5rem', { lineHeight: '1' }], 24 | '8xl': ['6rem', { lineHeight: '1' }], 25 | '9xl': ['8rem', { lineHeight: '1' }], 26 | }, 27 | typography: typographyStyles, 28 | extend: { 29 | boxShadow: { 30 | glow: '0 0 4px rgb(0 0 0 / 0.1)', 31 | }, 32 | maxWidth: { 33 | lg: '33rem', 34 | '2xl': '40rem', 35 | '3xl': '44rem', 36 | '5xl': '66rem', 37 | }, 38 | opacity: { 39 | 1: '0.01', 40 | 2.5: '0.025', 41 | 7.5: '0.075', 42 | 15: '0.15', 43 | }, 44 | }, 45 | }, 46 | plugins: [typographyPlugin, headlessuiPlugin], 47 | } satisfies Config 48 | -------------------------------------------------------------------------------- /src/components/ThemeToggle.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useTheme } from 'next-themes' 3 | 4 | function SunIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 5 | return ( 6 | 7 | 8 | 12 | 13 | ) 14 | } 15 | 16 | function MoonIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 17 | return ( 18 | 19 | 20 | 21 | ) 22 | } 23 | 24 | export function ThemeToggle() { 25 | let { resolvedTheme, setTheme } = useTheme() 26 | let otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark' 27 | let [mounted, setMounted] = useState(false) 28 | 29 | useEffect(() => { 30 | setMounted(true) 31 | }, []) 32 | 33 | return ( 34 | setTheme(otherTheme)} 39 | > 40 | 41 | 42 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/images/logos/ts.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import Link from 'next/link' 4 | import { usePathname } from 'next/navigation' 5 | import { motion } from 'framer-motion' 6 | 7 | import { Footer } from '@/components/Footer' 8 | import { Header } from '@/components/Header' 9 | import { Logo } from '@/components/Logo' 10 | import { Navigation } from '@/components/Navigation' 11 | import { type Section, SectionProvider } from '@/components/SectionProvider' 12 | 13 | export function Layout({ 14 | children, 15 | allSections, 16 | }: { 17 | children: React.ReactNode 18 | allSections: Record> 19 | }) { 20 | let pathname = usePathname() 21 | 22 | return ( 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {children} 41 | 42 | 43 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/components/LanguageChanger.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useRouter } from 'next/navigation' 4 | import { usePathname } from 'next/navigation' 5 | import { useCurrentLocale } from 'next-i18n-router/client' 6 | import i18nConfig from '../../i18nConfig' 7 | import { ChangeEvent } from 'react' 8 | 9 | export default function LanguageChanger() { 10 | const currentLocale = useCurrentLocale(i18nConfig) 11 | const router = useRouter() 12 | const currentPathname = usePathname() 13 | 14 | const handleChange = (e: ChangeEvent) => { 15 | const newLocale = e.target.value 16 | 17 | // set cookie for next-i18n-router 18 | const days = 30 19 | const date = new Date() 20 | date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000) 21 | document.cookie = `NEXT_LOCALE=${newLocale};expires=${date.toUTCString()};path=/` 22 | 23 | // redirect to the new locale path 24 | if (currentLocale === i18nConfig.defaultLocale) { 25 | router.push('/' + newLocale + currentPathname) 26 | } else { 27 | router.push(currentPathname.replace(`/${currentLocale}`, `/${newLocale}`)) 28 | } 29 | 30 | router.refresh() 31 | } 32 | 33 | return ( 34 | 40 | English 41 | Português 42 | 日本語 43 | 한국어 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atproto-website 2 | 3 | This repository contains the documentation for the AT Protocol, available to read at [atproto.com](https://atproto.com/). 4 | 5 | To read documentation for the Bluesky API, go to [docs.bsky.app](https://docs.bsky.app/) or this [repo](https://github.com/bluesky-social/bsky-docs). 6 | --- 7 | 8 | ### Making edits to atproto.com 9 | 10 | - clone this repo 11 | - run `npm install` 12 | - run the development server with `npm run dev` or `yarn dev` 13 | - open [http://localhost:3000](http://localhost:3000) with your browser. 14 | 15 | --- 16 | 17 | [src/app/[locale]/en.mdx](https://github.com/bluesky-social/atproto-website/blob/main/src/app/[locale]/en.mdx) generates [http://localhost:3000](http://localhost:3000) -- start there if you'd like to make changes. 18 | 19 | The page auto-updates as you edit the file. 20 | 21 | ### Are you a developer interested in building on atproto? 22 | 23 | Bluesky is an open social network built on the AT Protocol, a flexible technology that will never lock developers out of the ecosystems that they help build. With atproto, third-party can be as seamless as first-party through custom feeds, federated services, clients, and more. 24 | 25 | ## License 26 | 27 | Documentation text and the atproto specifications are under Creative Commons Attribution (CC-BY). 28 | 29 | Inline code examples, example data, and regular expressions are under Creative Commons Zero (CC-0, aka Public Domain) and copy/pasted without attribution. 30 | 31 | Please see [LICENSE.txt]() with reminders about derivative works, and [LICENSE-CC-BY.txt]() for a copy of license legal text. 32 | 33 | Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge). 34 | -------------------------------------------------------------------------------- /src/images/logos/go.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/[locale]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type Metadata } from 'next' 2 | import glob from 'fast-glob' 3 | 4 | import { Providers } from '@/app/[locale]/providers' 5 | import { Layout } from '@/components/Layout' 6 | import { type Section } from '@/components/SectionProvider' 7 | 8 | import '@/styles/tailwind.css' 9 | 10 | export const metadata: Metadata = { 11 | title: { 12 | template: '%s - AT Protocol', 13 | default: 'AT Protocol', 14 | }, 15 | openGraph: { 16 | url: 'https://atproto.com/', 17 | siteName: 'AT Protocol', 18 | type: 'website', 19 | images: [ 20 | { 21 | url: 'https://atproto.com/default-social-card.png', 22 | secureUrl: 'https://atproto.com/default-social-card.png', 23 | width: 1200, 24 | height: 630, 25 | }, 26 | ], 27 | }, 28 | twitter: { 29 | card: 'summary_large_image', 30 | images: { 31 | url: 'https://atproto.com/default-social-card.png', 32 | }, 33 | }, 34 | } 35 | 36 | export default async function RootLayout({ 37 | children, 38 | }: { 39 | children: React.ReactNode 40 | }) { 41 | let pages = await glob('**/*.mdx', { cwd: 'src/app/[locale]' }) 42 | let allSectionsEntries = (await Promise.all( 43 | pages.map(async (filename) => [ 44 | '/' + filename.replace(/(^|\/)page\.mdx$/, ''), 45 | (await import(`./${filename}`)).sections, 46 | ]), 47 | )) as Array<[string, Array]> 48 | let allSections = Object.fromEntries(allSectionsEntries) 49 | 50 | return ( 51 | 52 | 53 | 54 | 55 | {children} 56 | 57 | 58 | 59 | 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Tag.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | 3 | const variantStyles = { 4 | small: '', 5 | medium: 'rounded-lg px-1.5 ring-1 ring-inset', 6 | } 7 | 8 | const colorStyles = { 9 | emerald: { 10 | small: 'text-emerald-500 dark:text-emerald-400', 11 | medium: 12 | 'ring-emerald-300 dark:ring-emerald-400/30 bg-emerald-400/10 text-emerald-500 dark:text-emerald-400', 13 | }, 14 | sky: { 15 | small: 'text-sky-500', 16 | medium: 17 | 'ring-sky-300 bg-sky-400/10 text-sky-500 dark:ring-sky-400/30 dark:bg-sky-400/10 dark:text-sky-400', 18 | }, 19 | amber: { 20 | small: 'text-amber-500', 21 | medium: 22 | 'ring-amber-300 bg-amber-400/10 text-amber-500 dark:ring-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400', 23 | }, 24 | rose: { 25 | small: 'text-red-500 dark:text-rose-500', 26 | medium: 27 | 'ring-rose-200 bg-rose-50 text-red-500 dark:ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-400', 28 | }, 29 | zinc: { 30 | small: 'text-zinc-400 dark:text-zinc-500', 31 | medium: 32 | 'ring-zinc-200 bg-zinc-50 text-zinc-500 dark:ring-zinc-500/20 dark:bg-zinc-400/10 dark:text-zinc-400', 33 | }, 34 | } 35 | 36 | const valueColorMap = { 37 | GET: 'emerald', 38 | POST: 'sky', 39 | PUT: 'amber', 40 | DELETE: 'rose', 41 | } as Record 42 | 43 | export function Tag({ 44 | children, 45 | variant = 'medium', 46 | color = valueColorMap[children] ?? 'emerald', 47 | }: { 48 | children: keyof typeof valueColorMap & (string | {}) 49 | variant?: keyof typeof variantStyles 50 | color?: keyof typeof colorStyles 51 | }) { 52 | return ( 53 | 60 | {children} 61 | 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /src/components/icons/CogIcon.tsx: -------------------------------------------------------------------------------- 1 | export function CogIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 2 | return ( 3 | 4 | 10 | 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atproto-website", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "pages:build": "npx @cloudflare/next-on-pages", 11 | "preview": "npm run pages:build && wrangler pages dev", 12 | "deploy": "npm run pages:build && wrangler pages deploy" 13 | }, 14 | "browserslist": "defaults, not ie <= 11", 15 | "dependencies": { 16 | "@algolia/autocomplete-core": "^1.7.3", 17 | "@headlessui/react": "^2.1.0", 18 | "@headlessui/tailwindcss": "^0.2.0", 19 | "@mdx-js/loader": "^3.0.0", 20 | "@mdx-js/react": "^3.0.0", 21 | "@next/mdx": "^14.0.4", 22 | "@sindresorhus/slugify": "^2.1.1", 23 | "@tailwindcss/typography": "^0.5.10", 24 | "@types/mdx": "^2.0.8", 25 | "@types/node": "^20.10.8", 26 | "@types/react": "^18.2.47", 27 | "@types/react-dom": "^18.2.18", 28 | "@types/react-highlight-words": "^0.16.4", 29 | "acorn": "^8.8.1", 30 | "autoprefixer": "^10.4.7", 31 | "clsx": "^2.1.0", 32 | "fast-glob": "^3.3.0", 33 | "flexsearch": "^0.7.31", 34 | "framer-motion": "^10.18.0", 35 | "mdast-util-to-string": "^4.0.0", 36 | "mdx-annotations": "^0.1.1", 37 | "next": "^14.0.4", 38 | "next-i18n-router": "^5.5.1", 39 | "next-themes": "^0.2.1", 40 | "react": "^18.2.0", 41 | "react-dom": "^18.2.0", 42 | "react-highlight-words": "^0.20.0", 43 | "remark": "^15.0.1", 44 | "remark-gfm": "^4.0.0", 45 | "remark-mdx": "^3.0.0", 46 | "shiki": "^0.14.7", 47 | "simple-functional-loader": "^1.2.1", 48 | "tailwindcss": "^3.4.1", 49 | "typescript": "^5.3.3", 50 | "unist-util-filter": "^5.0.1", 51 | "unist-util-visit": "^5.0.0", 52 | "zustand": "^4.3.2" 53 | }, 54 | "devDependencies": { 55 | "eslint": "^8.56.0", 56 | "eslint-config-next": "^14.0.4", 57 | "prettier": "^3.3.2", 58 | "prettier-plugin-tailwindcss": "^0.6.5", 59 | "sharp": "0.33.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/tid/ko.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Timestamp Identifiers (TIDs)', 3 | description: '수정 및 레코드를 위한 간결한 타임스탬프 기반 식별자입니다.', 4 | } 5 | 6 | # Timestamp Identifiers (TIDs) 7 | 8 | TID("Timestamp Identifiers")는 정수 타임스탬프를 기반으로 한 간결한 문자열 식별자입니다. 9 | 이 식별자는 정렬이 가능하며 웹 URL에 적합하고, 네트워크 시스템 내에서 "논리적 시계" 역할을 수행할 수 있습니다. 10 | 현재 TID는 atproto에서 레코드 키와 저장소 커밋의 "리비전" 번호로 사용되고 있습니다. 11 | 12 | **참고:** TID는 ["스노우플레이크 식별자"](https://en.wikipedia.org/wiki/Snowflake_ID)와 유사한 점이 있습니다. 13 | 그러나 atproto의 탈중앙화 맥락에서는 TID의 전역적 유일성이 보장되지 않으며, 악의적인 저장소 관리자가 알려진 TID를 재사용하는 레코드를 쉽게 생성할 수 있습니다. 14 | 15 | ## TID 구조 16 | 17 | TID의 높은 수준의 의미는 다음과 같습니다: 18 | 19 | - 64비트 정수 20 | - 빅 엔디언 바이트 순서 21 | - `base32-sortable` 인코딩: 즉, `234567abcdefghijklmnopqrstuvwxyz` 문자셋을 사용하여 인코딩합니다. 22 | - 특별한 패딩 문자(예: `=`)를 사용하지 않으며, 모든 자릿수가 항상 인코딩되므로 길이는 항상 13개의 ASCII 문자입니다. 23 | 정수 0에 해당하는 TID는 `2222222222222`입니다. 24 | 25 | 64비트 정수의 배치는 다음과 같습니다: 26 | 27 | - 최상위 비트는 항상 0입니다. 28 | - 다음 53비트는 UNIX 에포크(1970년 1월 1일) 이후의 마이크로초를 나타냅니다. 29 | 53비트를 선택한 이유는 자바스크립트에서 사용하는 64비트 부동소수점 수의 최대 안전 정수 정밀도 때문입니다. 30 | - 마지막 10비트는 임의의 "클록 식별자"입니다. 31 | 32 | TID 생성기는 가능한 충돌을 피하기 위해 임의의 클록 식별자 번호를 생성해야 합니다 (예: 여러 PDS 서비스 클러스터의 작업자 인스턴스 간). 33 | 타임스탬프는 로컬 클록을 사용하여 생성할 수 있습니다. 34 | 동일한 마이크로초에 여러 TID가 생성되거나, "클록 스미어" 또는 클록 동기화 이슈가 발생하는 경우에도 TID 출력 스트림이 단조 증가하며 절대 반복되지 않도록 주의해야 합니다. 35 | 만약 로컬 클록이 밀리초 정밀도만 제공한다면, 타임스탬프를 패딩해야 합니다. (예: 값을 1000으로 곱하여 마이크로초 단위로 변환할 수 있습니다.) 36 | 37 | ## TID 구문 38 | 39 | Lexicon 문자열 타입: `tid` 40 | 41 | TID 문자열 구문 파싱 규칙: 42 | 43 | - 길이는 항상 13개의 ASCII 문자여야 합니다. 44 | - `234567abcdefghijklmnopqrstuvwxyz` 문자셋, 즉 base32-sortable 문자셋을 사용합니다. 45 | - 첫 번째 문자는 반드시 `234567abcdefghij` 중 하나여야 합니다. 46 | 47 | 초기 버전의 TID 구문에서는 하이픈을 허용했으나, 현재는 허용되지 않으며 파싱 시 거부해야 합니다. 48 | 49 | TID에 대한 참고 정규식은 다음과 같습니다: 50 | 51 | ``` 52 | /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/ 53 | ``` 54 | 55 | ### 예시 56 | 57 | #### 구문적으로 유효한 TID: 58 | 59 | ``` 60 | 3jzfcijpj2z2a 61 | 7777777777777 62 | 3zzzzzzzzzzzz 63 | 2222222222222 64 | ``` 65 | 66 | #### 유효하지 않은 TID: 67 | 68 | ``` 69 | # base32 문자가 아님 70 | 3jzfcijpj2z21 71 | 0000000000000 72 | 73 | # 대소문자 구분 (대문자 사용됨) 74 | 3JZFCIJPJ2Z2A 75 | 76 | # 길이가 너무 길거나 짧음 77 | 3jzfcijpj2z2aa 78 | 3jzfcijpj2z2 79 | 222 80 | 81 | # 더 이상 지원되지 않는 레거시 하이픈 구문 (TTTT-TTT-TTTT-CC) 82 | 3jzf-cij-pj2z-2a 83 | 84 | # 최상위 비트가 1인 경우 85 | zzzzzzzzzzzzz 86 | kjzfcijpj2z2a 87 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/data-repos/ja.mdx: -------------------------------------------------------------------------------- 1 | import {DescriptionList, Description} from '@/components/DescriptionList' 2 | import {Heading} from '@/components/Heading' 3 | 4 | export const metadata = { 5 | title: '個人データ リポジトリ', 6 | description: 7 | 'AT プロトコル リポジトリ構造のガイド。', 8 | } 9 | 10 | # データ リポジトリ 11 | 12 | データ リポジトリは、単一のユーザーによって公開されたデータのコレクションです。リポジトリは自己認証データ構造であり、各更新は署名されており、誰でも検証できます。 13 | 14 | [リポジトリ仕様](/specs/repository) で詳細に説明されています。 15 | 16 | データ レイアウト 17 | 18 | リポジトリの内容は [Merkle 検索ツリー (MST)](https://hal.inria.fr/hal-02303490/document) にレイアウトされ、状態が単一のルート ハッシュに縮小されます。これは次のレイアウトとして視覚化できます: 19 | 20 | ``` 21 | ┌────────────────┐ 22 | │ Commit │ (Signed Root) 23 | └───────┬────────┘ 24 | ↓ 25 | ┌────────────────┐ 26 | │ Tree Nodes │ 27 | └───────┬────────┘ 28 | ↓ 29 | ┌────────────────┐ 30 | │ Record │ 31 | └────────────────┘ 32 | ``` 33 | 34 | すべてのノードは [IPLD](https://ipld.io/) オブジェクト ([dag-cbor](https://ipld.io/docs/codecs/known/dag-cbor/)) であり、[CID](https://github.com/multiformats/cid) ハッシュによって参照されます。上の図の矢印は CID 参照を表します。 35 | 36 | このレイアウトは、[AT URI](/specs/at-uri-scheme) に反映されています: 37 | 38 | ``` 39 | ルート | at://alice.com 40 | コレクション | at://alice.com/app.bsky.feed.post 41 | レコード | at://alice.com/app.bsky.feed.post/1234 42 | ``` 43 | 44 | データ リポジトリへの「コミット」は、ルート ノードの CID に対するキーペア署名にすぎません。リポジトリが変更されるたびに、新しいコミット ノードが生成されます。 45 | 46 | 識別子の種類 47 | 48 | 個人データ リポジトリ内では、複数の種類の識別子が使用されます。 49 | 50 | 51 | 分散 ID (DID) は、データ リポジトリを識別します。これらはユーザー ID として広く使用されていますが、すべてのユーザーには 1 つのデータ リポジトリがあるため、DID はデータ リポジトリへの参照と見なすことができます。DID の形式は使用される「DID 方式」によって異なりますが、すべての DID は最終的にキー ペアとサービス プロバイダーのリストに解決されます。このキー ペアは、データ リポジトリへのコミットに署名できます。 52 | コンテンツ ID (CID) は、フィンガープリント ハッシュを使用してコンテンツを識別します。これらはリポジトリ全体で使用され、リポジトリ内のオブジェクト (ノード) を参照します。リポジトリ内のノードが変更されると、その CID も変更されます。ノードを参照する親は参照を更新する必要があり、その結果、親の CID も変更されます。これはコミット ノードまで連鎖し、その後コミット ノードが署名されます。 53 | 名前空間識別子 (NSID) は、リポジトリ内のレコード グループの Lexicon タイプを識別します。 54 | レコード キー ("rkeys") は、特定のリポジトリ内のコレクション内の個々のレコードを識別します。形式はコレクション Lexicon によって指定され、一部のコレクションには "self" などのキーを持つレコードが 1 つだけ含まれ、他のコレクションには多数のレコードがあり、キーにはタイムスタンプ識別子 (TID) と呼ばれる base32 エンコードされたタイムスタンプが使用されます。 55 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/data-repos/ko.mdx: -------------------------------------------------------------------------------- 1 | import {DescriptionList, Description} from '@/components/DescriptionList' 2 | import {Heading} from '@/components/Heading' 3 | 4 | export const metadata = { 5 | title: '개인 데이터 레포지토리', 6 | description: 'AT 프로토콜 레포지토리 구조에 대한 가이드입니다.', 7 | } 8 | 9 | # 데이터 레포지토리 10 | 11 | 데이터 레포지토리는 단일 사용자가 게시한 데이터 모음입니다. 레포지토리는 자체 인증(self-authenticating) 데이터 구조로, 각 업데이트가 서명되어 누구나 검증할 수 있습니다. 12 | 13 | 레포지토리에 대해서는 [레포지토리 사양](/specs/repository)에서 자세히 설명되어 있습니다. 14 | 15 | 데이터 레이아웃 16 | 17 | 레포지토리의 내용은 [Merkle Search Tree (MST)](https://hal.inria.fr/hal-02303490/document)를 사용하여 단일 루트 해시로 상태를 축소합니다. 이는 다음과 같은 레이아웃으로 시각화할 수 있습니다: 18 | 19 | ``` 20 | ┌────────────────┐ 21 | │ Commit │ (Signed Root) 22 | └───────┬────────┘ 23 | ↓ 24 | ┌────────────────┐ 25 | │ Tree Nodes │ 26 | └───────┬────────┘ 27 | ↓ 28 | ┌────────────────┐ 29 | │ Record │ 30 | └────────────────┘ 31 | ``` 32 | 33 | 모든 노드는 [IPLD](https://ipld.io/) 객체 ([dag-cbor](https://ipld.io/docs/codecs/known/dag-cbor/))로, [CID](https://github.com/multiformats/cid) 해시를 통해 참조됩니다. 위 다이어그램의 화살표는 CID 참조를 나타냅니다. 34 | 35 | 이 레이아웃은 [AT URI](/specs/at-uri-scheme)에 반영되어 있습니다: 36 | 37 | ``` 38 | 루트 | at://alice.com 39 | 컬렉션 | at://alice.com/app.bsky.feed.post 40 | 레코드 | at://alice.com/app.bsky.feed.post/1234 41 | ``` 42 | 43 | 데이터 레포지토리에 대한 “커밋”은 단순히 루트 노드의 CID에 대해 키페어로 서명한 것입니다. 레포지토리의 각 변경은 새로운 커밋 노드를 생성합니다. 44 | 45 | 식별자 유형 46 | 47 | 개인 데이터 레포지토리 내에서는 여러 종류의 식별자가 사용됩니다. 48 | 49 | 50 | 51 | Decentralized IDs (DIDs)는 데이터 레포지토리를 식별합니다. 이들은 사용자 ID로 널리 사용되지만, 모든 사용자가 하나의 데이터 레포지토리를 갖기 때문에 DID는 데이터 레포지토리를 참조하는 것으로 볼 수 있습니다. DID의 형식은 사용되는 “DID 방식”에 따라 달라지지만, 모든 DID는 궁극적으로 키페어와 서비스 제공자 목록으로 해석됩니다. 이 키페어는 데이터 레포지토리에 대한 커밋에 서명할 수 있습니다. 52 | 53 | 54 | 콘텐츠 ID (CIDs)는 지문 해시를 사용하여 콘텐츠를 식별합니다. 레포지토리 전체에서 객체(노드)를 참조하는 데 사용됩니다. 레포지토리 내의 노드가 변경되면 해당 노드의 CID도 변경됩니다. 노드를 참조하는 상위 노드들은 참조를 업데이트해야 하며, 이로 인해 상위 노드의 CID도 변경됩니다. 이 체인은 커밋 노드까지 이어지며, 그 후 서명됩니다. 55 | 56 | 57 | Namespaced Identifiers (NSIDs)는 레포지토리 내의 레코드 그룹의 Lexicon 유형을 식별합니다. 58 | 59 | 60 | Record Keys ("rkeys")는 특정 레포지토리 내의 컬렉션에서 개별 레코드를 식별합니다. 형식은 컬렉션 Lexicon에 의해 지정되며, 일부 컬렉션은 "self"와 같은 단일 키를 가진 레코드만 포함하고, 다른 컬렉션은 base32로 인코딩된 타임스탬프(Timestamp Identifier, TID)를 사용하는 여러 레코드를 포함합니다. 61 | 62 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import clsx from 'clsx' 3 | 4 | function ArrowIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 5 | return ( 6 | 7 | 13 | 14 | ) 15 | } 16 | 17 | const variantStyles = { 18 | primary: 19 | 'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-1 dark:ring-inset dark:ring-blue-400/20 dark:hover:bg-blue-400/10 dark:hover:text-blue-300 dark:hover:ring-blue-300', 20 | secondary: 21 | 'rounded-full bg-zinc-100 py-1 px-3 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800/40 dark:text-zinc-400 dark:ring-1 dark:ring-inset dark:ring-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-300', 22 | filled: 23 | 'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-blue-500 dark:text-white dark:hover:bg-blue-400', 24 | outline: 25 | 'rounded-full py-1 px-3 text-zinc-700 ring-1 ring-inset ring-zinc-900/10 hover:bg-zinc-900/2.5 hover:text-zinc-900 dark:text-zinc-400 dark:ring-white/10 dark:hover:bg-white/5 dark:hover:text-white', 26 | text: 'text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500', 27 | } 28 | 29 | type ButtonProps = { 30 | variant?: keyof typeof variantStyles 31 | arrow?: 'left' | 'right' 32 | } & ( 33 | | React.ComponentPropsWithoutRef 34 | | (React.ComponentPropsWithoutRef<'button'> & { href?: undefined }) 35 | ) 36 | 37 | export function Button({ 38 | variant = 'primary', 39 | className, 40 | children, 41 | arrow, 42 | ...props 43 | }: ButtonProps) { 44 | className = clsx( 45 | 'inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition', 46 | variantStyles[variant], 47 | className, 48 | ) 49 | 50 | let arrowIcon = ( 51 | 59 | ) 60 | 61 | let inner = ( 62 | <> 63 | {arrow === 'left' && arrowIcon} 64 | {children} 65 | {arrow === 'right' && arrowIcon} 66 | > 67 | ) 68 | 69 | if (typeof props.href === 'undefined') { 70 | return ( 71 | 72 | {inner} 73 | 74 | ) 75 | } 76 | 77 | return ( 78 | 79 | {inner} 80 | 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/faq/ko.mdx: -------------------------------------------------------------------------------- 1 | import {Heading} from '@/components/Heading' 2 | 3 | export const metadata = { 4 | title: 'FAQ', 5 | description: 6 | 'AT Protocol에 대한 자주 묻는 질문들.', 7 | } 8 | 9 | # FAQ 10 | 11 | AT Protocol (AT Proto)에 관한 자주 묻는 질문들입니다. Bluesky에 관한 FAQ는 [여기](https://bsky.social/about/faq)에서 확인하실 수 있습니다. 12 | 13 | AT Protocol은 블록체인인가요? 14 | 15 | 아닙니다. AT Protocol은 [연합 프로토콜](https://ko.wikipedia.org/wiki/연합_(정보_기술))입니다. 블록체인이 아니며 블록체인을 사용하지도 않습니다. 16 | 17 | 왜 ActivityPub을 사용하지 않나요? 18 | 19 | [ActivityPub](https://ko.wikipedia.org/wiki/ActivityPub)은 [Mastodon](https://joinmastodon.org/)에서 널리 사용되는 연합형 소셜 네트워크 기술입니다. 20 | 21 | 우리는 계정 이식성 문제로 별도의 프로토콜을 개발하기로 했습니다. 우리는 이식성이 중요하다고 생각합니다. 이는 사용자들이 갑작스런 차단, 서버 중단, 정책 분쟁으로부터 보호받을 수 있도록 도와줍니다. 우리의 이식성 해결책은 [서명된 데이터 레포지토리](/guides/data-repos)와 [DID](/guides/identity)를 요구하며, 이 두 가지는 ActivityPub에 쉽게 추가할 수 없습니다. ActivityPub의 이식 도구는 상대적으로 제한적이며, 원래 서버가 리디렉션을 제공해야 하고 사용자의 이전 데이터를 이식할 수 없습니다. 22 | 23 | 또한 주요한 이유는 확장성입니다. ActivityPub은 작은 규모에서 중간 규모의 여러 노드 간에 메시지를 전달하는 데 크게 의존하며, 이로 인해 개별 노드는 트래픽이 과부하될 수 있고 전체 활동에 대한 글로벌 뷰를 제공하는 데 어려움을 겪습니다. 반면, AT Protocol은 사용자들의 호스트에서 활동을 합치는 애플리케이션을 사용하여 전체 트래픽을 줄이고 개별 호스트에 대한 부하를 크게 줄입니다. 24 | 25 | 그 외에도 작은 차이점들이 있습니다: 스키마 처리 방식에 대한 다른 관점, AP의 이중 @ 이메일 사용자 이름 대신 도메인 사용자 이름을 선호하는 점, 대규모 검색 및 알고리즘 피드를 목표로 하는 점 등이 있습니다. 26 | 27 | 왜 JSON-LD나 RDF 대신 Lexicon을 만들었나요? 28 | 29 | Atproto는 여러 조직 간에 데이터와 RPC 명령을 교환합니다. 데이터와 RPC가 유용하려면, 소프트웨어가 별도의 팀에서 생성된 스키마를 제대로 처리할 수 있어야 합니다. 이것이 [Lexicon](/guides/lexicon)의 목적입니다. 30 | 31 | 우리는 엔지니어들이 새 스키마를 쉽게 사용하고 만들 수 있도록 하고, 개발자들이 시스템의 DX(개발자 경험)를 즐기도록 하고 싶습니다. Lexicon은 우리가 매우 익숙한 강타입 API를 생성할 수 있도록 도와주며, 이를 통해 다양한 런타임 정확성 검사를 제공합니다. 32 | 33 | [RDF](https://ko.wikipedia.org/wiki/리소스_설명_프레임워크)는 시스템들이 매우 적은 인프라를 공유하는 일반적인 경우를 위해 설계되었습니다. 개념적으로 우아하지만 사용하기 어렵고 종종 개발자가 이해하지 못하는 구문을 많이 추가합니다. JSON-LD는 RDF 어휘를 소비하는 작업을 단순화하지만, 그 개념을 숨겨놓고 RDF를 더 읽기 쉽도록 만드는 것이 아닙니다. 34 | 35 | 우리는 RDF 사용을 면밀히 검토했지만, 개발자 경험(DX)이나 제공되는 도구들에 대해 크게 만족하지 못했습니다. 36 | 37 | "XRPC"란 무엇이며, 왜 ___을 사용하지 않나요? 38 | 39 | [XRPC](/specs/xrpc)는 일부 추가적인 규칙이 적용된 HTTP입니다. 우리는 "XRPC"라는 용어를 폐지하고 ATProto의 HTTP 사용법이라고 부르기로 했습니다. 40 | 41 | XRPC는 [Lexicon](/guides/lexicon)을 사용하여 HTTP 호출을 설명하고 이를 `/xrpc/{methodId}`로 매핑합니다. 예를 들어, 이 API 호출: 42 | 43 | ```typescript 44 | await api.com.atproto.repo.listRecords({ 45 | user: 'alice.com', 46 | collection: 'app.bsky.feed.post' 47 | }) 48 | ``` 49 | 50 | ...은 다음과 같이 매핑됩니다: 51 | 52 | ```text 53 | GET /xrpc/com.atproto.repo.listRecords 54 | ?user=alice.com 55 | &collection=app.bsky.feed.post 56 | ``` 57 | 58 | Lexicon은 공유된 메소드 ID(`com.atproto.repo.listRecords`)와 예상되는 쿼리 파라미터, 입력 본문, 출력 본문을 설정합니다. Lexicon을 사용함으로써 호출의 입력과 출력에 대한 런타임 검사를 수행하고, 위와 같은 API 호출 예제와 같은 형식의 타입이 지정된 코드를 생성할 수 있습니다. -------------------------------------------------------------------------------- /src/app/[locale]/guides/faq/ja.mdx: -------------------------------------------------------------------------------- 1 | import {Heading} from '@/components/Heading' 2 | 3 | export const metadata = { 4 | title: 'FAQ', 5 | description: 6 | 'AT プロトコルに関するよくある質問。', 7 | } 8 | 9 | # FAQ 10 | 11 | 認証転送プロトコル (AT Proto) に関するよくある質問。Bluesky に関するよくある質問については、[こちら](https://bsky.social/about/faq) をご覧ください。 12 | 13 | AT プロトコルはブロックチェーンですか? 14 | 15 | いいえ。AT プロトコルは [フェデレーション プロトコル](https://en.wikipedia.org/wiki/Federation_(information_technology)) です。ブロックチェーンではありませんし、ブロックチェーンを使用していません。 16 | 17 | ActivityPub を使用しないのはなぜですか? 18 | 19 | [ActivityPub](https://en.wikipedia.org/wiki/ActivityPub) は、[Mastodon](https://joinmastodon.org/) によって普及したフェデレーション ソーシャル ネットワーキング テクノロジです。 20 | 21 | アカウントのポータビリティは、別のプロトコルを構築することを選択した主な理由です。ポータビリティは、突然の禁止、サーバーのシャットダウン、ポリシーの不一致からユーザーを保護するため、非常に重要であると考えています。ポータビリティのソリューションには、[署名済みデータ リポジトリ](/guides/data-repos) と [DID](/guides/identity) の両方が必要ですが、どちらも ActivityPub に後から簡単に組み込むことはできません。ActivityPub の移行ツールは比較的制限されており、元のサーバーがリダイレクトを提供する必要があり、ユーザーの以前のデータを移行することはできません。 22 | 23 | もう 1 つの大きな理由は、スケーラビリティです。ActivityPub は、小規模から中規模のノードの広範なネットワーク間でメッセージを配信することに大きく依存しているため、個々のノードにトラフィックが殺到し、アクティビティのグローバル ビューを提供するのが困難になることがあります。AT プロトコルは、集約アプリケーションを使用してユーザーのホストからのアクティビティをマージし、全体的なトラフィックを削減して、個々のホストの負荷を大幅に削減します。 24 | 25 | その他の小さな違いとしては、スキーマの扱い方に関する異なる視点、AP のダブル @ メール ユーザー名よりもドメイン ユーザー名を優先すること、大規模な検索とアルゴリズム フィードを実現するという目標などがあります。 26 | 27 | JSON-LD や RDF を使用する代わりに Lexicon を作成する理由 28 | 29 | Atproto は組織間でデータと RPC コマンドを交換します。データと RPC が有用であるためには、ソフトウェアが別のチームによって作成されたスキーマを正しく処理する必要があります。これが [Lexicon](/guides/lexicon) の目的です。 30 | 31 | 私たちは、エンジニアが新しいスキーマを快適に使用および作成できるようにし、開発者がシステムの DX を楽しめるようにしたいと考えています。Lexicon は、開発者にとって非常に馴染み深く、さまざまな実行時正確性チェックを提供する、厳密に型指定された API の作成に役立ちます。 32 | 33 | [RDF](https://en.wikipedia.org/wiki/Resource_Description_Framework) は、システムがほとんどインフラストラクチャを共有しない、極めて一般的なケースを対象としています。概念的にはエレガントですが、使いにくく、開発者が理解できない構文が多数追加されることがよくあります。JSON-LD は RDF ボキャブラリの使用タスクを簡素化しますが、RDF を読みやすくするのではなく、基礎となる概念を隠すことによって簡素化します。 34 | 35 | 私たちは RDF の使用を非常に詳しく検討しましたが、開発者エクスペリエンス (DX) やそれが提供するツールが気に入りませんでした。 36 | 37 | 「XRPC」とは何ですか。なぜ ___ を使用しないのですか? 38 | 39 | [XRPC](/specs/xrpc) は、いくつかの規則が追加された HTTP です。私たちは「XRPC」という用語を廃止し、単に HTTP の ATProto 使用法と呼ぶことを目指しています。 40 | 41 | XRPC は [Lexicon](/guides/lexicon) を使用して HTTP 呼び出しを記述し、それらを `/xrpc/{methodId}` にマッピングします。たとえば、次の API 呼び出し: 42 | 43 | ```typescript 44 | await api.com.atproto.repo.listRecords({ 45 | user: 'alice.com', 46 | collection: 'app.bsky.feed.post' 47 | }) 48 | ``` 49 | 50 | ...次のものにマップされます: 51 | 52 | ```text 53 | GET /xrpc/com.atproto.repo.listRecords 54 | ?user=alice.com 55 | &collection=app.bsky.feed.post 56 | ``` 57 | 58 | Lexicon は、共有メソッド ID (`com.atproto.repo.listRecords`) と、予想されるクエリ パラメータ、入力本文、および出力本文を確立します。Lexicon を使用すると、呼び出しの入力と出力のランタイム チェックが実行され、上記の API 呼び出しの例のような型付きコードを生成できます。 -------------------------------------------------------------------------------- /src/app/[locale]/specs/tid/en.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Timestamp Identifiers (TIDs)', 3 | description: 4 | 'A compact timestamp-based identifier for revisions and records.', 5 | } 6 | 7 | # Timestamp Identifiers (TIDs) 8 | 9 | A TID ("timestamp identifier") is a compact string identifier based on an integer timestamp. They are sortable, appropriate for use in web URLs, and useful as a "logical clock" in networked systems. TIDs are currently used in atproto as record keys and for repository commit "revision" numbers. 10 | 11 | Note: There are similarities to ["snowflake identifiers"](https://en.wikipedia.org/wiki/Snowflake_ID). In the decentralized context of atproto, the global uniqueness of TIDs can not be guaranteed, and an antagonistic repo controller could trivially create records re-using known TIDs. 12 | 13 | ## TID Structure 14 | 15 | The high-level semantics of a TID are: 16 | 17 | - 64-bit integer 18 | - big-endian byte ordering 19 | - encoded as `base32-sortable`. That is, encoded with characters `234567abcdefghijklmnopqrstuvwxyz` 20 | - no special padding characters (like `=`) are used, but all digits are always encoded, so length is always 13 ASCII characters. The TID corresponding to integer zero is `2222222222222`. 21 | 22 | The layout of the 64-bit integer is: 23 | 24 | - The top bit is always 0 25 | - The next 53 bits represent microseconds since the UNIX epoch. 53 bits is chosen as the maximum safe integer precision in a 64-bit floating point number, as used by Javascript. 26 | - The final 10 bits are a random "clock identifier." 27 | 28 | TID generators should generate a random clock identifier number, chosen to avoid collisions as much as possible (for example, between multiple worker instances of a PDS service cluster). A local clock can be used to generate the timestamp itself. Care should be taken to ensure the TID output stream is monotonically increasing and never repeats, even if multiple TIDs are generated in the same microsecond, or during "clock smear" or clock synchronization incidents. If the local clock has only millisecond precision, the timestamp should be padded. (You can do this by multiplying by 1000.) 29 | 30 | 31 | ## TID Syntax 32 | 33 | Lexicon string type: `tid` 34 | 35 | TID string syntax parsing rules: 36 | 37 | - length is always 13 ASCII characters 38 | - uses base32-sortable character set, meaning `234567abcdefghijklmnopqrstuvwxyz` 39 | - the first character must be one of `234567abcdefghij` 40 | 41 | Early versions of the TID syntax allowed hyphens, but they are no longer allowed and should be rejected when parsing. 42 | 43 | A reference regex for TID is: 44 | 45 | ``` 46 | /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/ 47 | ``` 48 | 49 | ### Examples 50 | 51 | Syntactically valid TIDs: 52 | 53 | ``` 54 | 3jzfcijpj2z2a 55 | 7777777777777 56 | 3zzzzzzzzzzzz 57 | 2222222222222 58 | ``` 59 | 60 | Invalid TIDs: 61 | 62 | ``` 63 | # not base32 64 | 3jzfcijpj2z21 65 | 0000000000000 66 | 67 | # case-sensitive 68 | 3JZFCIJPJ2Z2A 69 | 70 | # too long/short 71 | 3jzfcijpj2z2aa 72 | 3jzfcijpj2z2 73 | 222 74 | 75 | # legacy dash syntax *not* supported (TTTT-TTT-TTTT-CC) 76 | 3jzf-cij-pj2z-2a 77 | 78 | # high bit can't be set 79 | zzzzzzzzzzzzz 80 | kjzfcijpj2z2a 81 | ``` 82 | -------------------------------------------------------------------------------- /src/components/Guides.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@/components/Button' 2 | import { Heading } from '@/components/Heading' 3 | 4 | interface Guide { 5 | href: string 6 | name: string 7 | description: string 8 | } 9 | 10 | const lang_guides: Record = { 11 | en: [ 12 | { 13 | href: '/guides/applications', 14 | name: 'Quickstart', 15 | description: 'Create an application and start building.', 16 | }, 17 | { 18 | href: '/guides/self-hosting', 19 | name: 'Self-host', 20 | description: 'Learn how to set up your own personal data server.', 21 | }, 22 | { 23 | href: '/guides/glossary', 24 | name: 'Glossary', 25 | description: 'Definitions for all the terminology used in AT Protocol.', 26 | }, 27 | { 28 | href: '/guides/faq', 29 | name: 'FAQ', 30 | description: 'Frequently Asked Questions about the Atmosphere.', 31 | }, 32 | ], 33 | pt: [ 34 | { 35 | href: '/guides/applications', 36 | name: 'Início rápido', 37 | description: 'Crie um aplicativo e comece a construir.', 38 | }, 39 | { 40 | href: '/guides/self-hosting', 41 | name: 'Auto-hospedagem', 42 | description: 43 | 'Aprenda a configurar seu próprio servidor de dados pessoais.', 44 | }, 45 | { 46 | href: '/guides/glossary', 47 | name: 'Glossário', 48 | description: 'Definições para toda a terminologia usada no Protocolo AT.', 49 | }, 50 | { 51 | href: '/guides/faq', 52 | name: 'FAQ', 53 | description: 'Perguntas frequentes sobre a atmosfera.', 54 | }, 55 | ], 56 | ja: [ 57 | { 58 | href: '/guides/applications', 59 | name: 'クイックスタート', 60 | description: 'アプリケーションを作成して構築を開始します。', 61 | }, 62 | { 63 | href: '/guides/self-hosting', 64 | name: 'セルフホスト', 65 | description: '独自のパーソナル データ サーバーを設定する方法を学びます。', 66 | }, 67 | { 68 | href: '/guides/glossary', 69 | name: '用語集', 70 | description: 'AT プロトコルで使用されるすべての用語の定義。', 71 | }, 72 | { 73 | href: '/guides/faq', 74 | name: 'FAQ', 75 | description: 'Atmosphere に関するよくある質問。', 76 | }, 77 | ], 78 | } 79 | 80 | export function Guides({ lang }: { lang: string }) { 81 | const guides = lang in lang_guides ? lang_guides[lang] : lang_guides.en 82 | 83 | return ( 84 | 85 | 86 | Guides 87 | 88 | 89 | {guides.map((guide) => ( 90 | 91 | 92 | {guide.name} 93 | 94 | 95 | {guide.description} 96 | 97 | 98 | 99 | Read more 100 | 101 | 102 | 103 | ))} 104 | 105 | 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/data-repos/en.mdx: -------------------------------------------------------------------------------- 1 | import {DescriptionList, Description} from '@/components/DescriptionList' 2 | 3 | export const metadata = { 4 | title: 'Personal Data Repositories', 5 | description: 6 | 'A guide to the AT Protocol repo structure.', 7 | } 8 | 9 | # Data Repositories 10 | 11 | A data repository is a collection of data published by a single user. Repositories are self-authenticating data structures, meaning each update is signed and can be verified by anyone. 12 | 13 | They are described in more depth in the [Repository specification](/specs/repository). 14 | 15 | ## Data Layout 16 | 17 | The content of a repository is laid out in a [Merkle Search Tree (MST)](https://hal.inria.fr/hal-02303490/document) which reduces the state to a single root hash. It can be visualized as the following layout: 18 | 19 | ``` 20 | ┌────────────────┐ 21 | │ Commit │ (Signed Root) 22 | └───────┬────────┘ 23 | ↓ 24 | ┌────────────────┐ 25 | │ Tree Nodes │ 26 | └───────┬────────┘ 27 | ↓ 28 | ┌────────────────┐ 29 | │ Record │ 30 | └────────────────┘ 31 | ``` 32 | 33 | Every node is an [IPLD](https://ipld.io/) object ([dag-cbor](https://ipld.io/docs/codecs/known/dag-cbor/)) which is referenced by a [CID](https://github.com/multiformats/cid) hash. The arrows in the diagram above represent a CID reference. 34 | 35 | This layout is reflected in the [AT URIs](/specs/at-uri-scheme): 36 | 37 | ``` 38 | Root | at://alice.com 39 | Collection | at://alice.com/app.bsky.feed.post 40 | Record | at://alice.com/app.bsky.feed.post/1234 41 | ``` 42 | 43 | A “commit” to a data repository is simply a keypair signature over a Root node’s CID. Each mutation to the repository produces a new Commit node. 44 | 45 | ## Identifier Types 46 | 47 | Multiple types of identifiers are used within a Personal Data Repository. 48 | 49 | 50 | Decentralized IDs (DIDs) identify data repositories. They are broadly used as user IDs, but since every user has one data repository then a DID can be considered a reference to a data repository. The format of a DID varies by the “DID method” used but all DIDs ultimately resolve to a keypair and a list of service providers. This keypair can sign commits to the data repository. 51 | Content IDs (CIDs) identify content using a fingerprint hash. They are used throughout the repository to reference the objects (nodes) within it. When a node in the repository changes, its CID also changes. Parents which reference the node must then update their reference, which in turn changes the parent’s CID as well. This chains all the way to the Commit node, which is then signed. 52 | Namespaced Identifiers (NSIDs) identify the Lexicon type for groups of records within a repository. 53 | Record Keys ("rkeys") identify individual records within a collection in a given repository. The format is specified by the collection Lexicon, with some collections having only a single record with a key like "self", and other collections having many records, with keys using a base32-encoded timestamp called a Timestamp Identifier (TID). 54 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/nsid/ko.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Namespaced Identifiers (NSIDs)', 3 | description: '전역 의미 체계 식별자에 대한 명세입니다.', 4 | } 5 | 6 | # Namespaced Identifiers (NSIDs) 7 | 8 | Namespaced Identifiers (NSIDs)는 레코드, XRPC 엔드포인트 등 다양한 Lexicon 스키마를 참조하는 데 사용됩니다. {{ className: 'lead' }} 9 | 10 | NSID의 기본 구조와 의미는 역순 도메인 네임 형식의 완전한 호스트 이름 다음에 간단한 이름이 이어지는 형태입니다. 호스트 이름 부분은 **도메인 권한**을 나타내며, 마지막 세그먼트는 **이름**을 나타냅니다. {{ className: 'lead' }} 11 | 12 | ### NSID 구문 13 | 14 | Lexicon 문자열 타입: `nsid` 15 | 16 | NSID의 도메인 권한 부분은 역순으로 배열된 유효한 핸들이어야 합니다. 그 뒤에는 ASCII 카멜케이스 문자열이어야 하는 이름 세그먼트가 이어집니다. 17 | 18 | 예를 들어, `com.example.fooBar`는 구문상으로 유효한 NSID로, `com.example`는 도메인 권한이며, `fooBar`는 이름 세그먼트입니다. 19 | 20 | 구문 규칙의 전체 목록은 다음과 같습니다: 21 | 22 | - **전체 NSID:** 23 | - 오직 ASCII 문자만 포함해야 함 24 | - 도메인 권한과 이름은 ASCII 마침표 문자(`.`)로 구분되어야 함 25 | - 최소 3개의 세그먼트를 포함해야 함 26 | - 총 길이가 최대 317자여야 함 27 | - **도메인 권한:** 28 | - 마침표(`.`)로 구분된 세그먼트로 구성됨 29 | - (마침표 포함) 최대 253자이며, 최소 두 개의 세그먼트를 포함해야 함 30 | - 각 세그먼트는 최소 1자, 최대 63자여야 함 (마침표 제외) 31 | - 허용되는 문자는 ASCII 알파벳(`a-z`), 숫자(`0-9`), 하이픈(`-`)임 32 | - 세그먼트는 하이픈으로 시작하거나 끝날 수 없음 33 | - 첫 번째 세그먼트(최상위 도메인)는 숫자로 시작할 수 없음 34 | - 도메인 권한은 대소문자를 구분하지 않으며, 소문자로 정규화되어야 함 (즉, ASCII `A-Z`를 `a-z`로 변환) 35 | - **이름:** 36 | - 최소 1자, 최대 63자여야 함 37 | - 허용되는 문자는 ASCII 알파벳(`A-Z`, `a-z`)만 가능함 38 | - 숫자와 하이픈은 허용되지 않음 39 | - 대소문자를 구분하며 정규화해서는 안 됨 40 | 41 | NSID에 대한 참조 정규 표현식은: 42 | 43 | ``` 44 | /^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z]{0,61}[a-zA-Z])?)$/ 45 | ``` 46 | 47 | ### NSID 구문 변형 48 | 49 | 일부 상황에서는 스키마 내 특정 하위 필드를 참조하기 위해 NSID에 **프래그먼트**를 추가할 수 있습니다. 프래그먼트는 NSID와 ASCII 해시 문자(`#`)로 구분됩니다. 프래그먼트 식별자 문자열(즉, `#` 뒤의 부분)은 NSID의 마지막 세그먼트와 동일한 구문 제한을 가집니다: ASCII 알파벳, 하나 이상의 문자, 길이 제한 등. 50 | 51 | NSID의 그룹이나 패턴을 참조할 때는, 끝에 ASCII 별표 문자(`*`)를 "글로브(glob)" 문자로 사용할 수 있습니다. 예를 들어, `com.atproto.*`는 `atproto.com` 도메인 권한 아래의 모든 NSID(중첩된 하위 도메인 포함)를 참조합니다. 독립적인 `*`는 모든 도메인 권한의 모든 NSID와 매칭됩니다. 현재는 단 하나의 별표 문자만 허용되며, 반드시 마지막 문자여야 하고, 세그먼트 경계에 있어야 합니다 (세그먼트 이름의 일부만 매칭하는 것은 불가능). 즉, 별표 문자는 마침표 뒤에 오거나, 모든 NSID와 매칭되는 독립된 별표여야 합니다. 52 | 53 | ### 예시 54 | 55 | 구문상 유효한 NSID: 56 | 57 | ``` 58 | com.example.fooBar 59 | net.users.bob.ping 60 | a-0.b-1.c 61 | a.b.c 62 | cn.8.lex.stuff 63 | ``` 64 | 65 | 유효하지 않은 NSID: 66 | 67 | ``` 68 | com.exa💩ple.thing 69 | com.example 70 | ``` 71 | 72 | ### 사용 및 구현 지침 73 | 74 | **강력히 권장되는** 최선의 관행은 도메인 권한에 ASCII 알파벳 문자만 사용하는 것입니다 (즉, 숫자나 하이픈을 사용하지 않음). 이는 대부분의 프로그래밍 언어에서 클라이언트 라이브러리를 생성하는 데 크게 용이합니다. 75 | 76 | 전체 NSID는 표시, 저장, 검증에 있어서 대소문자를 구분합니다. 그러나 대소문자만 다른 여러 NSID를 허용해서는 안 됩니다. 네임스페이스 권한은 중복과 혼란을 방지할 책임이 있습니다. 구현체는 NSID를 강제로 소문자로 변환해서는 안 됩니다. 77 | 78 | 관련 NSID를 체계적으로 구성하기 위해 도메인 권한의 일부로 "서브도메인"을 사용하는 것이 일반적입니다. 예를 들어, NSID `com.atproto.sync.getHead`는 `sync` 세그먼트를 사용합니다. 이는 `atproto.com` 도메인뿐만 아니라 `sync.atproto.com` 전체 도메인에 대한 제어 권한이 필요함을 의미합니다. 79 | 80 | Lexicon 언어 문서에는 레코드 유형 및 XRPC 메서드에 대해 NSID를 선택하고 구성하는 스타일 가이드라인이 제공될 것입니다. 요약하면, 레코드는 보통 복수형이 아닌 단수 명사이며, XRPC 메서드는 보통 "동사명사(verbNoun)" 형태를 취합니다. 81 | 82 | ### 향후 변경 가능성 83 | 84 | NSID 구문이 마지막 세그먼트에 유니코드 문자를 허용하도록 완화될 가능성이 있습니다. 85 | 86 | "글로브(glob)" 구문 변형은 단일 레벨과 중첩 매칭의 구분을 보다 명확하게 하기 위해 확장될 수 있습니다. 87 | 88 | "프래그먼트(fragment)" 구문 변형은 향후 중첩 참조를 허용하도록 완화될 수 있습니다. 89 | 90 | 현재 "도메인 권한"의 제어를 검증하는 자동화된 메커니즘은 존재하지 않습니다. 또한, 주어진 NSID에 대한 Lexicon 스키마를 가져오거나 기본 도메인의 모든 NSID를 열거하는 자동화된 메커니즘도 존재하지 않습니다. 91 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/record-key/ko.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: '레코드 키', 3 | description: 4 | 'AT 프로토콜 레포지토리 내 컬렉션의 개별 레코드를 식별하는 식별자', 5 | } 6 | 7 | # 레코드 키 8 | 9 | **레코드 키**(때로는 `rkey`로 축약됨)는 atproto 레포지토리 내 동일 컬렉션의 개별 레코드를 명명하고 참조하는 데 사용됩니다. 이 값은 AT URI의 한 부분이 되며, 레포지토리의 MST(머클 서명 트리) 경로에도 포함됩니다. {{ className: 'lead' }} 10 | 11 | 여러 가지 레코드 키 명명 방식이 지원됩니다. 모든 레코드 Lexicon 스키마는 레코드 컬렉션의 필요와 의미에 따라 사용해야 하는 레코드 키 타입을 지정합니다. {{ className: 'lead' }} 12 | 13 | ### 레코드 키 타입: `tid` 14 | 15 | 가장 일반적인 레코드 명명 방식으로, [TID ("timestamp identifier") 문법](/specs/tid)을 사용합니다. 예를 들어 `3jzfcijpj2z2a`와 같이 표현됩니다. TID는 보통 레코드 생성 시점의 로컬 시계를 기반으로 생성되며, 동일 컬렉션 내에서 중복 또는 재사용되지 않도록 추가 메커니즘이 적용됩니다. 16 | 17 | TID 레코드 키를 사용하는 경우 레코드의 원래 생성 시점을 유추할 수 있지만, 이러한 키는 최종 사용자가 지정할 수 있어 임의의 값이 될 수 있으므로 절대 신뢰해서는 안 됩니다. 18 | 19 | 동일한 TID가 동일 레포지토리의 다른 컬렉션의 레코드에도 사용될 수 있습니다. 이는 두 레코드 간의 관계(예: "사이드카" 확장 레코드)를 나타내는 경우가 많습니다. 20 | 21 | TID 방식의 초기 동기는 레코드의 느슨한 시간 순서를 제공하여 레포지토리 데이터 구조(MST)의 저장 효율성을 높이기 위함이었습니다. 22 | 23 | ### 레코드 키 타입: `nsid` 24 | 25 | 레코드 키가 유효한 [NSID](/specs/nsid)여야 하는 경우에 사용합니다. 26 | 27 | ### 레코드 키 타입: `literal:` 28 | 29 | 컬렉션 내에 단 하나의 레코드만 존재해야 하며, 고정되고 잘 알려진 레코드 키가 필요한 경우 사용합니다. 30 | 31 | 가장 일반적인 값은 `self`이며, Lexicon 스키마에서는 `literal:self`로 지정됩니다. 32 | 33 | ### 레코드 키 타입: `any` 34 | 35 | 전체 레코드 키 스키마 요구사항(아래 참조)을 충족하는 임의의 문자열이 허용됩니다. 가장 유연한 타입의 레코드 키입니다. 36 | 37 | 이 방식은 도메인 이름, 정수 또는 (변환된) AT URI 등 이름에 의미를 부여하기 위해 사용될 수 있습니다. 이를 통해 중복 제거 및 알려진 URI 조회가 가능합니다. 38 | 39 | ### 레코드 키 문법 40 | 41 | Lexicon 문자열 타입: `record-key` 42 | 43 | 타입에 관계없이, 레코드 키는 다음 기본 문법 제약을 충족해야 합니다: 44 | 45 | - ASCII 문자 중 제한된 일부만 사용 — 허용 문자는 영문 대소문자 및 숫자(`A-Za-z0-9`), 마침표, 대시, 밑줄, 콜론, 물결표(`.-_:~`) 46 | - 최소 1자, 최대 512자 47 | - 특정 값 `.` 와 `..` 는 허용되지 않음 48 | - 레포지토리 MST 경로 문자열의 일부로 사용할 수 있어야 함 (위의 제약 조건을 충족) 49 | - URI의 경로 구성 요소에 포함될 수 있어야 함 (RFC-3986, 섹션 3.3 참조). 위의 제약 조건은 URI 경로의 "unreserved" 문자와 일치함 50 | 51 | 레코드 키는 대소문자를 구분합니다. 52 | 53 | ### 예제 54 | 55 | **유효한 레코드 키:** 56 | 57 | ``` 58 | 3jui7kd54zh2y 59 | self 60 | example.com 61 | ~1.2-3_ 62 | dHJ1ZQ 63 | pre:fix 64 | _ 65 | ``` 66 | 67 | **유효하지 않은 레코드 키:** 68 | 69 | ``` 70 | alpha/beta 71 | . 72 | .. 73 | #extra 74 | @handle 75 | any space 76 | any+space 77 | number[3] 78 | number(3) 79 | "quote" 80 | dHJ1ZQ== 81 | ``` 82 | 83 | ### 사용 및 구현 가이드라인 84 | 85 | 구현체는 TID의 전역 고유성에 의존해서는 안 되며, TID에 내포된 타임스탬프를 실제 레코드 생성 시간으로 신뢰해서는 안 됩니다. 레코드 키는 "사용자 제어 데이터"이므로, 악의적인 계정에 의해 임의로 선택될 수 있습니다. 86 | 87 | 대부분의 레포지토리 및 레코드 처리를 위한 소프트웨어는 레코드 키 타입과 값을 단순 문자열로 취급해야 하며, 특별한 의미를 부여하지 않아야 합니다. 예를 들어, TID 키를 `base32`로 디코딩하여 고유 `uint64` 값으로 사용하는 것을 데이터베이스 키로 활용하는 것은 키 포맷 변경에 취약하므로 권장되지 않습니다. 88 | 89 | 레포지토리 내에서 동일한 레코드 키 값이 여러 컬렉션에서 사용될 수 있음을 유의하세요. `(did, rkey)` 조합은 고유하지 않고, `(did, collection, rkey)` 조합이 유일합니다. 90 | 91 | 최선의 관례로, 대부분의 상황에서 키 경로 길이는 80자 미만으로 유지하는 것이 좋습니다. 92 | 93 | 콜론(`:`) 문자는 2023년 기준 참조 구현에서 사실상 허용되었으며, 2024년 2월에 스펙이 업데이트되어 공식적으로 허용되었습니다. 94 | 95 | 대부분의 DID는 레코드 키로 사용할 수 있으나, DID W3C 전체 스펙은 추가 문자를 허용하므로, 현재는 작동하더라도 향후 DID 메서드나 기능(예: 경로 포함 `did:web`)이 변경되면 문제가 발생할 수 있습니다. 96 | 97 | 레코드 키는 대소문자를 구분하지만, 혼동을 피하고 대소문자 구분이 없는 환경에서도 재사용 가능하도록 모두 소문자로 사용하는 것이 권장됩니다. 98 | 99 | ### 향후 변경 가능 사항 100 | 101 | - 레코드 키 문법에 대한 제약은 향후 비 ASCII 유니코드 문자 허용으로 완화될 수 있습니다. 단, 레코드 키는 항상 유효한 유니코드여야 하며, 임의의 바이트 문자열은 허용되지 않습니다. 102 | - 추가적인 레코드 키 타입이 정의될 수 있습니다. 103 | - 최대 길이가 조정될 수 있습니다. 104 | - `%` 문자는 URL 인코딩과 관련하여 예약되어 있으나, 현재 인코딩은 지원되지 않습니다. 105 | - 전체 문법에 대한 추가 제약(예: 최소 한 개 이상의 영문자 또는 숫자 포함)이 추가될 수 있습니다. 106 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/atp/ko.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'AT Protocol', 3 | description: 'Authenticated Transfer Protocol (AT Protocol)의 명세', 4 | } 5 | 6 | # AT Protocol 7 | 8 | Authenticated Transfer Protocol (AT Protocol 또는 atproto)은 개방형 소셜 미디어 애플리케이션 구축을 위한 범용 연합 프로토콜입니다. 다음은 이 프로토콜의 반복적으로 등장하는 주제와 특징들입니다. {{ className: 'lead' }} 9 | 10 | - 사용자 데이터와 신원의 자체 인증: 원활한 계정 이전 및 콘텐츠 재배포를 가능하게 합니다. {{ className: 'lead' }} 11 | - "빅 월드" 사용 사례에 맞춘 설계: 수십억 계정에 확장할 수 있습니다. {{ className: 'lead' }} 12 | - 애플리케이션 계층 스키마와 집계 인프라에 대한 위임된 권한 부여. {{ className: 'lead' }} 13 | - dweb 프로토콜 계열의 기존 데이터 모델과 웹 플랫폼의 네트워크 원시 요소 재사용. {{ className: 'lead' }} 14 | 15 | ## 프로토콜 구조 16 | 17 | **신원:** 계정 제어는 안정적인 [DID](/specs/did) 식별자에 기반하며, 이를 통해 현재 서비스 제공 위치와 계정에 연관된 [암호화 키](/specs/cryptography)를 신속하게 확인할 수 있습니다. [핸들](/specs/handle)은 계정을 보다 인간 친화적이고 변경 가능한 식별자로 제공합니다. 18 | 19 | **데이터:** 공개 콘텐츠는 내용 기반 주소 지정 및 암호학적으로 검증 가능한 [레포지토리](/specs/repository)에 저장됩니다. 데이터 레코드와 네트워크 메시지는 모두 통합된 [데이터 모델](/specs/data-model)을 따르며, [CBOR](https://en.wikipedia.org/wiki/CBOR)와 JSON 형식을 지원합니다. [라벨](/specs/label)은 개별 서명이 포함된 경량 메타데이터로, 레포지토리 외부에서 배포됩니다. 20 | 21 | **네트워크:** HTTP 클라이언트-서버 및 서버-서버 [API](/specs/xrpc)는 Lexicon을 사용하여 기술되며, WebSocket [이벤트 스트림](/specs/event-stream)도 동일한 방식으로 정의됩니다. 개별 레코드는 네트워크 상에서 [AT URI](/specs/at-uri-scheme)를 통해 참조할 수 있습니다. [개인 데이터 서버(PDS)](/specs/account)는 계정의 신뢰할 수 있는 대리인 역할을 하며, 클라이언트의 네트워크 요청을 라우팅하고 레포지토리를 호스팅합니다. 또한, 릴레이는 여러 레포지토리를 크롤링하여 통합된 이벤트 [Firehose](/specs/sync)를 제공합니다. 22 | 23 | **애플리케이션:** atproto 위에서 구축된 애플리케이션의 API와 레코드 스키마는 [Lexicon](/specs/lexicon)으로 지정되며, [Namespaced Identifiers](/specs/nsid) (NSID)를 통해 참조됩니다. 애플리케이션별 집계(예: 검색)는 애플리케이션 뷰(App View) 서비스를 통해 제공됩니다. 클라이언트에는 모바일 앱, 데스크톱 소프트웨어, 웹 인터페이스 등이 포함됩니다. 24 | 25 | AT Protocol 자체는 팔로우나 아바타와 같은 일반적인 소셜 미디어 관례를 규정하지 않으며, 이는 애플리케이션 수준의 Lexicon에 위임됩니다. `com.atproto.*` Lexicon은 계정 가입, 로그인 등 일반 API를 제공하며, 이는 AT Protocol의 일부로 간주될 수 있으나 필요에 따라 확장 또는 대체될 수 있습니다. Bluesky는 `app.bsky.*` 네임스페이스 하에 Lexicon을 사용하여 구현된 AT Protocol 기반의 마이크로블로깅 소셜 애플리케이션입니다. 26 | 27 | 또한, atproto는 IPFS 생태계의 여러 포맷과 명세(IPLD, CID 등)를 차용하지만, atproto 데이터가 반드시 IPFS 네트워크에 저장될 필요는 없으며, 참조 구현 역시 IPFS 네트워크를 사용하지 않습니다. 28 | 29 | ## 프로토콜 확장 및 애플리케이션 30 | 31 | AT Protocol은 제3자 애플리케이션 개발에 유연성을 부여하면서도 안정성과 상호 운용성을 유지하도록 설계되었습니다. 32 | 33 | 핵심 프로토콜 확장 메커니즘은 독립적인 네임스페이스 하에서 새로운 Lexicon을 개발하는 것입니다. Lexicon은 새로운 레포지토리 레코드 스키마(NSID를 통해 저장됨), 새로운 HTTP API 엔드포인트, 그리고 새로운 이벤트 스트림 엔드포인트와 메시지 유형을 선언할 수 있습니다. 또한, 새로운 애플리케이션은 새로운 네트워크 집계 서비스(예: AppView) 및 클라이언트 애플리케이션(모바일 앱이나 웹 인터페이스 등)을 필요로 할 수 있습니다. 34 | 35 | 제3자들은 네임스페이스 간에 Lexicon과 레코드 데이터를 재사용할 수 있으며, 예를 들어 새로운 애플리케이션은 `bsky.app` 권한이 관리하는 스키마를 준수하는 한 `app.bsky.*` Lexicon에 명시된 소셜 그래프 레코드를 활용할 수 있습니다. 36 | 37 | 개별 Lexicon 네임스페이스의 거버넌스 구조는 자원봉사 커뮤니티, 기업, 컨소시엄, 학계 연구진, 후원 비영리 단체 등 다양한 주체에 의해 관리될 수 있습니다. 38 | 39 | ## 누락된 부분 40 | 41 | 이 명세서는 Bluesky의 참조 구현에서 구현된 대부분의 세부 사항을 다룹니다. 그러나 해당 참조 구현과 이 명세서 모두에서 아직 최종 확정되지 않은 몇 가지 중요한 원시 요소가 남아 있습니다. 42 | 43 | **모더레이션 원시 요소:** 모더레이션 보고 처리 및 인프라 수준의 콘텐츠 삭제를 위한 `com.atproto.admin.*` 경로는 Lexicon에 명시되어 있으나, 이에 대한 추가적인 설명이 필요합니다. 44 | 45 | **Lexicon 해상도:** 주어진 타입 이름(NSID)에 대해 Lexicon 스키마 정의 파일을 자동으로 조회하고 가져오는 방법이 요구됩니다. 46 | 47 | ## 향후 작업 48 | 49 | 개별 명세 문서에서 다루는 소규모 변경 사항 외에도, 전체 프로토콜에 걸친 몇 가지 대규모 변경 사항이 계획되어 있습니다. 50 | 51 | **비공개 콘텐츠:** 개인 그룹 및 일대일 통신을 위한 메커니즘은 프로토콜 개발의 두 번째 단계로 진행될 예정입니다. 여기에는 "비공개 계정", 다이렉트 메시지, 암호화된 데이터 등의 원시 요소가 포함됩니다. 기존 프로토콜 원시 요소에 단순히 암호화나 비공개 콘텐츠 기능을 추가하는 방식은 권장되지 않습니다. 52 | 53 | **프로토콜 거버넌스 및 정식 표준 프로세스:** 현재의 개발 초점은 개방형 연합을 포함한 모든 핵심 프로토콜 기능을 참조 구현을 통해 입증하는 것입니다. 그 후, 하위 프로토콜을 안정화하고 IETF 또는 W3C와 같은 표준화 기구를 통해 명세를 독립적으로 검토 및 수정할 계획입니다. 54 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/cryptography/ko.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: '암호학', 3 | description: 4 | 'AT Protocol에서 사용되는 암호 시스템, 곡선 및 키 유형', 5 | } 6 | 7 | # 암호학 8 | 9 | 현재 프로토콜 전반에서 두 가지 타원 곡선이 지원되며, 구현체는 둘 모두를 완벽하게 지원해야 합니다: {{ className: 'lead' }} 10 | 11 | - `p256` 타원 곡선: 별칭 "NIST P-256", 별칭 `secp256r1` (여기서 `r`에 주의), 별칭 `prime256v1` 12 | - 이 곡선은 *WebCrypto API에 포함되어 있습니다*. 일반적으로 개인 기기 하드웨어(신뢰할 수 있는 플랫폼 모듈(TPM) 및 모바일 시큐어 인클레이브)와 클라우드 하드웨어 보안 모듈(HSM)에 의해 지원됩니다. 13 | - `k256` 타원 곡선: 별칭 `secp256k1` (여기서 `k`에 주의) 14 | - 이 곡선은 *WebCrypto API에 포함되어 있지 않습니다*. Bitcoin 및 기타 암호화폐에서 사용되며, 그 결과 개인 비밀 키 관리 기술에 폭넓게 지원됩니다. 클라우드 HSM에서도 지원됩니다. 15 | 16 | 전체 곡선 이름을 모두 작성할 때 미묘한 시각적 차이가 있기 때문에, 우리는 이를 종종 `p256` 또는 `k256`이라고 부릅니다. 17 | 18 | Bluesky의 atproto 참조 구현은 모든 문맥에서 두 곡선을 모두 지원하며, 기본적으로 `k256` 키 쌍을 생성합니다. 19 | 20 | 두 시스템의 키 포인트는 손실 없이 압축된(compressed) 표현을 가지며, 이는 공개 키를 공유할 때 유용합니다. 이는 보통 `k256`에서는 기본적으로 지원되지만, `p256`의 경우 추가 메서드나 복잡한 설정이 필요할 수 있습니다. 이에 대해서는 [02, 03 or 04? So What Are Compressed and Uncompressed Public Keys?](https://medium.com/asecuritysite-when-bob-met-alice/02-03-or-04-so-what-are-compressed-and-uncompressed-public-keys-6abcb57efeb6)에서 자세히 읽어보실 수 있습니다. 21 | 22 | atproto에서 데이터를 서명할 때의 일반적인 패턴은 데이터를 DAG-CBOR로 인코딩하고, 해당 CBOR 바이트를 SHA-256으로 해시하여 원시 바이트(헥스 인코딩 문자열이 아님)를 생성한 다음, 이 해시 바이트에 서명하는 것입니다. 23 | 24 | 25 | ## ECDSA 서명 변조 가능성 26 | 27 | 일부 ECDSA 서명은 변환되어 새로운 구분되지만 여전히 유효한 서명을 만들어낼 수 있습니다. 이는 서명에 사용된 개인 키나 데이터에 접근할 필요 없이 발생합니다. 이 속성을 이용한 공격의 범위는 제한적이지만, 예상치 못한 특성입니다. 28 | 29 | 특히 `k256`의 경우, 이는 [Bitcoin BIP-0062](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki)에서 논의된 바와 같이 "low-S"와 "high-S" 서명 간의 구분에 해당합니다. 30 | 31 | atproto에서는 `p256`과 `k256` 곡선 모두에 대해 "low-S" 서명 변형의 사용이 요구됩니다. 32 | 33 | atproto에서는 서명을 원시 바이트 값으로 단순 비교하는 대신, 항상 암호화 라이브러리에서 제공하는 검증 루틴을 사용하여 서명을 검증해야 합니다. 34 | 35 | 36 | ## 공개 키 인코딩 37 | 38 | 공개 키를 문자열로 인코딩할 때, 선호되는 표현 방식은 multibase(특히 `base58btc` 사용)와 특정 키 유형을 나타내기 위한 multicode 접두사를 사용하는 것입니다. 인코딩 자체에 키 유형에 대한 메타데이터를 포함함으로써, 이를 명확하게 파싱할 수 있습니다. 이 형식으로 공개 키를 인코딩하는 과정은 다음과 같습니다: 39 | 40 | - 공개 키 곡선의 "포인트"를 바이트로 인코딩합니다. 반드시 더 작은 "compact" 또는 "compressed" 표현 방식을 사용하세요. 이는 보통 `k256`에서는 간단하지만, `p256` 키의 경우 특별한 인자나 설정이 필요할 수 있습니다. 41 | - 키 바이트 앞에 적절한 곡선 multicodec 값을 varint 인코딩된 바이트 형태로 붙입니다: 42 | - `p256` (압축된, 33바이트 키 길이): `p256-pub`, 코드 0x1200, varint 인코딩 바이트: [0x80, 0x24] 43 | - `k256` (압축된, 33바이트 키 길이): `secp256k1-pub`, 코드 0xE7, varint 인코딩 바이트: [0xE7, 0x01] 44 | - 결합된 바이트를 `base58btc`로 인코딩하고, `z` 문자를 접두사로 추가하여 multibase 인코딩 문자열을 생성합니다. 45 | 46 | 디코딩 과정은 식별된 곡선 유형을 문맥으로 사용하여 반대로 진행됩니다. 47 | 48 | 키를 `did:key` 식별자로 인코딩하려면, 위의 multibase 인코딩을 사용하고 ASCII 접두사 `did:key:`를 추가합니다. 이 식별자는 DID PLC 메서드의 내부 구현 세부사항으로 사용됩니다. 49 | 50 | [atproto DID 사양 문서](/specs/did)에서 설명된 변형된 레거시 multibase 인코딩은 multicodec 유형 값을 포함하지 않고 키의 압축되지 않은 바이트 인코딩을 사용합니다. 이 형식은 더 이상 권장되지 않습니다. 51 | 52 | ### 인코딩 예시 53 | 54 | 아래는 multicodec을 포함한 multibase 인코딩 및 `did:key` 형식으로 인코딩된 P-256 공개 키 예시입니다: 55 | 56 | ``` 57 | zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo 58 | did:key:zDnaembgSGUhZULN2Caob4HLJPaxBh92N7rtH21TErzqf8HQo 59 | ``` 60 | 61 | 아래는 multicodec을 포함한 multibase 인코딩 및 `did:key` 형식으로 인코딩된 K-256 공개 키 예시입니다: 62 | 63 | ``` 64 | zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc 65 | did:key:zQ3shqwJEJyMBsBXCWyCBpUBMqxcon9oHB7mCvx4sSpMdLJwc 66 | ``` 67 | 68 | 69 | ## 사용 및 구현 가이드라인 70 | 71 | atproto 생태계 전반에서 개인 키에 대해 특정 추천 바이트 또는 문자열 인코딩 방식은 없습니다. 때로는 단순 헥스 인코딩이 사용되며, 때로는 multicodec 유형 정보가 포함되거나 생략된 multibase가 사용됩니다. 72 | 73 | 74 | ## 향후 변경 가능성 75 | 76 | 지원되는 암호 시스템 집합은 점진적으로 발전할 것으로 예상됩니다. 가능한 한 적은 시스템을 유지하는 것은 상호 운용성과 구현 측면에서 상당한 이점을 제공합니다. 77 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/data-repos/pt.mdx: -------------------------------------------------------------------------------- 1 | import {DescriptionList, Description} from '@/components/DescriptionList' 2 | 3 | export const metadata = { 4 | title: 'Repositórios de Dados Pessoais', 5 | description: 6 | 'Um guia para a estrutura de repositório do Protocolo AT.', 7 | } 8 | 9 | # Repositórios de dados 10 | 11 | Um repositório de dados é uma coleção de dados publicados por um único usuário. Repositórios são estruturas de dados autoautenticadas, o que significa que cada atualização é assinada e pode ser verificada por qualquer pessoa. 12 | 13 | Eles são descritos em mais detalhes na [especificação do repositório](/specs/repository). 14 | 15 | ## Layout de dados 16 | 17 | O conteúdo de um repositório é disposto em uma [Árvore de pesquisa Merkle (MST)](https://hal.inria.fr/hal-02303490/document) que reduz o estado a um único hash raiz. Ele pode ser visualizado como o seguinte layout: 18 | 19 | ``` 20 | ┌────────────────┐ 21 | │ Commit │ (Signed Root) 22 | └───────┬────────┘ 23 | ↓ 24 | ┌────────────────┐ 25 | │ Tree Nodes │ 26 | └───────┬────────┘ 27 | ↓ 28 | ┌────────────────┐ 29 | │ Record │ 30 | └────────────────┘ 31 | ``` 32 | 33 | Cada nó é um objeto [IPLD](https://ipld.io/) ([dag-cbor](https://ipld.io/docs/codecs/known/dag-cbor/)) que é referenciado por um hash [CID](https://github.com/multiformats/cid). As setas no diagrama acima representam uma referência CID. 34 | 35 | Este layout é refletido nos [AT URIs](/specs/at-uri-scheme): 36 | 37 | ``` 38 | Raiz | at://alice.com 39 | Coleção | at://alice.com/app.bsky.feed.post 40 | Registro | at://alice.com/app.bsky.feed.post/1234 41 | ``` 42 | 43 | Um “commit” para um repositório de dados é simplesmente uma assinatura de par de chaves sobre o CID de um nó Root. Cada mutação para o repositório produz um novo nó Commit. 44 | 45 | ## Tipos de Identificadores 46 | 47 | Vários tipos de identificadores são usados em um Repositório de Dados Pessoais. 48 | 49 | 50 | IDs descentralizados (DIDs) identificam repositórios de dados. Eles são amplamente usados como IDs de usuário, mas como cada usuário tem um repositório de dados, um DID pode ser considerado uma referência a um repositório de dados. O formato de um DID varia de acordo com o "método DID" usado, mas todos os DIDs resolvem, em última análise, para um par de chaves e uma lista de provedores de serviços. Este par de chaves pode assinar confirmações para o repositório de dados. 51 | IDs de conteúdo (CIDs) identificam o conteúdo usando um hash de impressão digital. Eles são usados em todo o repositório para referenciar os objetos (nós) dentro dele. Quando um nó no repositório muda, seu CID também muda. Os pais que referenciam o nó devem então atualizar sua referência, o que por sua vez altera o CID do pai também. Isso encadeia todo o caminho até o nó Commit, que é então assinado. 52 | Identificadores com namespace (NSIDs) identificam o tipo Lexicon para grupos de registros dentro de um repositório. 53 | Chaves de registro ("rkeys") identificam registros individuais dentro de uma coleção em um determinado repositório. O formato é especificado pelo Lexicon da coleção, com algumas coleções tendo apenas um único registro com uma chave como "self", e outras coleções tendo muitos registros, com chaves usando um timestamp codificado em base32 chamado de Identificador de Timestamp (TID). 54 | -------------------------------------------------------------------------------- /src/components/Heading.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect, useRef } from 'react' 4 | import Link from 'next/link' 5 | import { useInView } from 'framer-motion' 6 | 7 | import { useSectionStore } from '@/components/SectionProvider' 8 | import { Tag } from '@/components/Tag' 9 | import { remToPx } from '@/lib/remToPx' 10 | 11 | function AnchorIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 12 | return ( 13 | 20 | 21 | 22 | ) 23 | } 24 | 25 | function Eyebrow({ tag, label }: { tag?: string; label?: string }) { 26 | if (!tag && !label) { 27 | return null 28 | } 29 | 30 | return ( 31 | 32 | {tag && {tag}} 33 | {tag && label && ( 34 | 35 | )} 36 | {label && ( 37 | {label} 38 | )} 39 | 40 | ) 41 | } 42 | 43 | function Anchor({ 44 | id, 45 | inView, 46 | children, 47 | }: { 48 | id: string 49 | inView: boolean 50 | children: React.ReactNode 51 | }) { 52 | return ( 53 | 57 | {inView && ( 58 | 59 | 60 | 61 | 62 | 63 | )} 64 | {children} 65 | 66 | ) 67 | } 68 | 69 | export function Heading({ 70 | children, 71 | tag, 72 | label, 73 | level, 74 | anchor = true, 75 | ...props 76 | }: React.ComponentPropsWithoutRef<`h${Level}`> & { 77 | id: string 78 | tag?: string 79 | label?: string 80 | level?: Level 81 | anchor?: boolean 82 | }) { 83 | level = level ?? (2 as Level) 84 | let Component = `h${level}` as 'h2' | 'h3' 85 | let ref = useRef(null) 86 | let registerHeading = useSectionStore((s) => s.registerHeading) 87 | 88 | let inView = useInView(ref, { 89 | margin: `${remToPx(-3.5)}px 0px 0px 0px`, 90 | amount: 'all', 91 | }) 92 | 93 | useEffect(() => { 94 | if (level === 2) { 95 | registerHeading({ id: props.id, ref, offsetRem: tag || label ? 8 : 6 }) 96 | } 97 | }) 98 | 99 | return ( 100 | <> 101 | 102 | 107 | {anchor ? ( 108 | 109 | {children} 110 | 111 | ) : ( 112 | children 113 | )} 114 | 115 | > 116 | ) 117 | } 118 | -------------------------------------------------------------------------------- /src/components/Feedback.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { forwardRef, useState } from 'react' 4 | import { Transition } from '@headlessui/react' 5 | import clsx from 'clsx' 6 | 7 | function CheckIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 8 | return ( 9 | 10 | 11 | 18 | 19 | ) 20 | } 21 | 22 | function FeedbackButton( 23 | props: Omit, 'type' | 'className'>, 24 | ) { 25 | return ( 26 | 31 | ) 32 | } 33 | 34 | const FeedbackForm = forwardRef< 35 | React.ElementRef<'form'>, 36 | React.ComponentPropsWithoutRef<'form'> 37 | >(function FeedbackForm({ onSubmit, className, ...props }, ref) { 38 | return ( 39 | 48 | 49 | Was this page helpful? 50 | 51 | 52 | Yes 53 | 54 | No 55 | 56 | 57 | ) 58 | }) 59 | 60 | const FeedbackThanks = forwardRef< 61 | React.ElementRef<'div'>, 62 | React.ComponentPropsWithoutRef<'div'> 63 | >(function FeedbackThanks({ className, ...props }, ref) { 64 | return ( 65 | 73 | 74 | 75 | Thanks for your feedback! 76 | 77 | 78 | ) 79 | }) 80 | 81 | export function Feedback() { 82 | let [submitted, setSubmitted] = useState(false) 83 | 84 | function onSubmit(event: React.FormEvent) { 85 | event.preventDefault() 86 | 87 | // event.nativeEvent.submitter.dataset.response 88 | // => "yes" or "no" 89 | 90 | setSubmitted(true) 91 | } 92 | 93 | return ( 94 | 95 | 96 | 100 | 101 | 102 | 103 | 104 | 105 | ) 106 | } 107 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react' 2 | import Link from 'next/link' 3 | import clsx from 'clsx' 4 | import { motion, useScroll, useTransform } from 'framer-motion' 5 | 6 | import { Button } from '@/components/Button' 7 | import { Logo } from '@/components/Logo' 8 | import { 9 | MobileNavigation, 10 | useIsInsideMobileNavigation, 11 | } from '@/components/MobileNavigation' 12 | import { useMobileNavigationStore } from '@/components/MobileNavigation' 13 | import { MobileSearch, Search } from '@/components/Search' 14 | import { ThemeToggle } from '@/components/ThemeToggle' 15 | import LanguageChanger from './LanguageChanger' 16 | 17 | function TopLevelNavItem({ 18 | href, 19 | children, 20 | }: { 21 | href: string 22 | children: React.ReactNode 23 | }) { 24 | return ( 25 | 26 | 30 | {children} 31 | 32 | 33 | ) 34 | } 35 | 36 | export const Header = forwardRef< 37 | React.ElementRef<'div'>, 38 | React.ComponentPropsWithoutRef 39 | >(function Header({ className, ...props }, ref) { 40 | let { isOpen: mobileNavIsOpen } = useMobileNavigationStore() 41 | let isInsideMobileNavigation = useIsInsideMobileNavigation() 42 | 43 | let { scrollY } = useScroll() 44 | let bgOpacityLight = useTransform(scrollY, [0, 72], [0.5, 0.9]) 45 | let bgOpacityDark = useTransform(scrollY, [0, 72], [0.2, 0.8]) 46 | 47 | return ( 48 | 67 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | SDKs 85 | 86 | Blog 87 | 88 | 89 | GitHub 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ) 102 | }) 103 | -------------------------------------------------------------------------------- /src/mdx/rehype.mjs: -------------------------------------------------------------------------------- 1 | import { slugifyWithCounter } from '@sindresorhus/slugify' 2 | import * as acorn from 'acorn' 3 | import { toString } from 'mdast-util-to-string' 4 | import { mdxAnnotations } from 'mdx-annotations' 5 | import shiki from 'shiki' 6 | import { visit } from 'unist-util-visit' 7 | 8 | function rehypeParseCodeBlocks() { 9 | return (tree) => { 10 | visit(tree, 'element', (node, _nodeIndex, parentNode) => { 11 | if (node.tagName === 'code' && node.properties.className) { 12 | parentNode.properties.language = node.properties.className[0]?.replace( 13 | /^language-/, 14 | '', 15 | ) 16 | } 17 | }) 18 | } 19 | } 20 | 21 | let highlighter 22 | 23 | function rehypeShiki() { 24 | return async (tree) => { 25 | highlighter = 26 | highlighter ?? (await shiki.getHighlighter({ theme: 'css-variables' })) 27 | 28 | visit(tree, 'element', (node) => { 29 | if (node.tagName === 'pre' && node.children[0]?.tagName === 'code') { 30 | let codeNode = node.children[0] 31 | let textNode = codeNode.children[0] 32 | 33 | node.properties.code = textNode.value 34 | 35 | if (node.properties.language) { 36 | let tokens = highlighter.codeToThemedTokens( 37 | textNode.value, 38 | node.properties.language, 39 | ) 40 | 41 | textNode.value = shiki.renderToHtml(tokens, { 42 | elements: { 43 | pre: ({ children }) => children, 44 | code: ({ children }) => children, 45 | line: ({ children }) => `${children}`, 46 | }, 47 | }) 48 | } 49 | } 50 | }) 51 | } 52 | } 53 | 54 | function rehypeSlugify() { 55 | return (tree) => { 56 | let slugify = slugifyWithCounter() 57 | visit(tree, 'element', (node) => { 58 | if ( 59 | (node.tagName === 'h2' || node.tagName === 'h3') && 60 | !node.properties.id 61 | ) { 62 | node.properties.id = slugify(toString(node)) 63 | } 64 | }) 65 | } 66 | } 67 | 68 | function rehypeAddMDXExports(getExports) { 69 | return (tree) => { 70 | let exports = Object.entries(getExports(tree)) 71 | 72 | for (let [name, value] of exports) { 73 | for (let node of tree.children) { 74 | if ( 75 | node.type === 'mdxjsEsm' && 76 | new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value) 77 | ) { 78 | return 79 | } 80 | } 81 | 82 | let exportStr = `export const ${name} = ${value}` 83 | 84 | tree.children.push({ 85 | type: 'mdxjsEsm', 86 | value: exportStr, 87 | data: { 88 | estree: acorn.parse(exportStr, { 89 | sourceType: 'module', 90 | ecmaVersion: 'latest', 91 | }), 92 | }, 93 | }) 94 | } 95 | } 96 | } 97 | 98 | function getSections(node) { 99 | let sections = [] 100 | 101 | for (let child of node.children ?? []) { 102 | if (child.type === 'element' && child.tagName === 'h2') { 103 | sections.push(`{ 104 | title: ${JSON.stringify(toString(child))}, 105 | id: ${JSON.stringify(child.properties.id)}, 106 | ...${child.properties.annotation} 107 | }`) 108 | } else if (child.children) { 109 | sections.push(...getSections(child)) 110 | } 111 | } 112 | 113 | return sections 114 | } 115 | 116 | export const rehypePlugins = [ 117 | mdxAnnotations.rehype, 118 | rehypeParseCodeBlocks, 119 | rehypeShiki, 120 | rehypeSlugify, 121 | [ 122 | rehypeAddMDXExports, 123 | (tree) => ({ 124 | sections: `[${getSections(tree).join()}]`, 125 | }), 126 | ], 127 | ] 128 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/account-lifecycle/ko.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: '계정 수명 주기 이벤트', 3 | description: '계정 수명 주기 모범 사례', 4 | } 5 | 6 | # 계정 수명 주기 모범 사례 7 | 8 | 이 문서는 계정 수명 주기에 대한 개요를 제공하는 [계정 호스팅](/specs/account) 사양을 보완합니다. 여기에서는 몇 가지 일반적인 계정 수명 주기 전환에 대해 예상되는 동작과, 어떤 순서로 firehose 이벤트가 발생하는지를 요약합니다. 소프트웨어는 부분적이거나 잘못된 이벤트 전송에 대해 견고하게 동작해야 합니다. 9 | 10 | **신규 계정 생성:** PDS에서 계정이 등록되고 새로운 아이덴티티(DID)가 생성될 때. 11 | 12 | - PDS는 계정의 아이덴티티(DID와 핸들)를 생성하거나 기존의 것을 확인합니다. DID가 네트워크의 다른 서비스에 의해 확인될 수 있는 상태가 되고, 현재 PDS 인스턴스를 가리키면, PDS는 `#identity` 이벤트를 발생시킵니다. (특히 PDS가 계정에 핸들을 제공하는 경우, 제3자가 핸들을 확인할 수 있을 때까지 이벤트 발생을 지연하는 것이 좋지만 필수는 아닙니다.) 이 이벤트 발생 시 계정 상태가 `active`일 수도 있고 아닐 수도 있습니다. 13 | - 계정 생성이 완료되어, PDS가 API 요청에 대해 계정 상태를 `active`로 응답하면, `#account` 이벤트를 발생시킬 수 있습니다. 14 | - 계정의 레포지토리가 `rev`와 `commit`으로 초기화되면, `#commit` 메시지를 발생시킬 수 있습니다. 초기 레포지토리는 "비어있을"(레코드가 없을) 수도 있고, 레코드가 포함되어 있을 수도 있습니다. 15 | - 이벤트의 구체적인 순서는 공식적으로 지정되어 있지 않지만, 권장 순서는: `#identity`, `#account`, `#commit`입니다. 16 | - 다운스트림 서비스는 이 이벤트들을 처리하고 전달합니다. 17 | 18 | **계정 마이그레이션:** 아래에서 자세히 설명되지만, 관련 이벤트 및 동작은 다음과 같습니다: 19 | 20 | - 새 PDS는 초기 계정 생성 시 아무런 이벤트도 발생시키지 않습니다. 새 PDS에서의 계정 상태는 `deactivated`(비활성화)로 설정되며, API 요청에도 그렇게 응답합니다. 21 | - 아이덴티티가 업데이트되어 새 PDS에서 확인되면, `#identity` 이벤트를 발생시켜야 합니다. 22 | - 새 PDS에서 계정이 `active` 상태로 전환되면, `#account` 이벤트와 `#commit` 이벤트를 발생시켜야 합니다. 구체적인 순서는 필수적이지 않지만, `#account` 이벤트를 먼저 발생시키는 것이 권장됩니다. 이상적으로는 `#commit` 이벤트는 빈 이벤트(새로운 레코드 없음)여야 하지만, 새 서명 키로 서명되고 `rev`가 갱신 또는 증가되어야 합니다. 23 | - 이전 PDS에서 계정이 비활성화되면, 해당 PDS는 계정이 비활성화되었음을 나타내는 `#account` 이벤트를 발생시켜야 합니다. 24 | - 릴레이는 현재 선언된 아이덴티티의 PDS 인스턴스에서 발생하지 않은 `#account` 및 `#commit` 이벤트는 무시해야 하며, 출력 firehose로 전달되어서는 안 됩니다. 또한, 로컬 계정 상태가 `active`가 아닐 때 발생하는 `#commit` 이벤트 역시 무시되어야 합니다. 전반적으로, 계정 마이그레이션은 새 PDS에서 발생한 세 가지 이벤트, 즉 `#identity`, `#account`, `#commit` 이벤트가 릴레이에 의해 전달되어야 하며, 이전 PDS에서 발생한 `#account` 이벤트는 일반적으로 무시됩니다. 25 | - 다운스트림 서비스(예: AppView)는 아이덴티티 캐시를 업데이트하고, `#commit` 이벤트 수신 시 계정의 `rev`를 증가시키지만, 다른 추가 조치는 필요하지 않습니다. 26 | 27 | **계정 삭제:** 28 | 29 | - PDS는 `active`가 false이며 상태가 `deleted`인 `#account` 이벤트를 발생시킵니다. 30 | - 릴레이는 해당 레포지토리의 로컬 계정 상태를 업데이트하고 `#account` 이벤트를 전달합니다. 릴레이가 전체 미러일 경우, 즉시 `getRepo`, `getRecord` 등과 같은 API 요청에 대해 해당 계정을 제공하지 않으며, 응답 오류에 그 이유를 명시합니다. 릴레이는 현지 정책에 따라 로컬에 저장된 레포지토리 콘텐츠를 완전히 삭제할 수도 있습니다. 백필(backfill) 윈도우에 존재하는 커밋 이벤트를 즉시 삭제할 필요는 없으나, 해당 윈도우가 시간 제한이 있다면 삭제할 수 있습니다. 31 | - PDS는 계정이 "active"하지 않은 상태에서 `#commit` 이벤트를 발생시키지 않아야 합니다. 만약 실수나 순서 문제, 전달 오류 등으로 인해 추가적인 `#commit` 메시지가 발생한다면, 다운스트림 서비스는 해당 이벤트를 무시하고 전달하지 않아야 합니다. 32 | - 다운스트림 서비스(예: AppView)는 즉시 해당 계정의 콘텐츠 제공 및 배포를 중단해야 합니다. 로컬 데이터의 삭제는 현지 정책에 따라 연기될 수 있으며, 집계(예: 레코드 수) 업데이트 역시 백그라운드 큐에서 처리하거나 지연될 수 있습니다. 오류 메시지는 콘텐츠가 "사라졌다"(한때 존재했으나 더 이상 이용 불가) 또는 "찾을 수 없다"(이전에 존재했음을 밝히지 않음)를 나타낼 수 있습니다. 33 | 34 | 계정 정지는 계정 삭제와 유사하게 동작합니다. 35 | 36 | **계정 비활성화:** 37 | 38 | - PDS는 `active`가 false이고 상태가 `deactivated`인 `#account` 이벤트를 발생시킵니다. 39 | - 계정 삭제와 유사하게, 릴레이는 해당 이벤트를 처리하고 콘텐츠 재배포를 중단하며, 이벤트를 전달합니다. 릴레이는 로컬 콘텐츠를 완전히 삭제하지 않아야 하지만, 비활성화 상태가 오랫동안 지속될 경우 현지 정책에 따라 로컬 복사본을 삭제할 수도 있습니다. 40 | - 계정 삭제와 유사하게, PDS는 `#commit` 이벤트를 발생시키지 않아야 하며, 만약 발생하더라도 릴레이는 이를 무시하고 전달하지 않아야 합니다. 41 | - 다운스트림 서비스(예: AppViews)는 계정과 관련된 콘텐츠를 제공하지 않아야 하며, 로컬 데이터 삭제는 반드시 필요하지 않습니다. 대신 계정/콘텐츠 상태를 "이용 불가"로 표시하되, 최선의 방법은 계정 비활성화 때문임을 명확히 나타내는 것입니다. 42 | 43 | 계정 정지도 비활성화와 유사하게 동작합니다. 44 | 45 | **계정 재활성화:** 46 | 47 | - PDS는 `active` 상태의 `#account` 이벤트를 발생시킵니다. 48 | - 릴레이는 해당 이벤트가 현재 아이덴티티의 PDS 인스턴스에서 발생했는지 확인하고, 계정 재활성화가 유효한지 검증합니다. 이후 로컬 계정 상태를 업데이트하고 이벤트를 전달합니다. 49 | - 다운스트림 서비스(예: AppViews)는 해당 계정의 로컬 계정 상태를 업데이트해야 합니다. 50 | - 만약 서비스가 해당 계정의 최신 레포지토리 콘텐츠(예: 이전에 삭제되어 더 이상 로컬에 존재하지 않음)를 보유하고 있지 않다면, 백그라운드 작업으로 레포지토리 CAR 내보내기를 가져와 처리할 수 있습니다. "업스트림" 호스트(예: 릴레이)가 레포지토리 사본을 보유하고 있거나, 서비스가 직접 계정의 PDS 호스트에 연결할 수도 있습니다. 이는 필수 사항은 아니며, 대신 `#commit` 이벤트를 기다릴 수도 있습니다. 51 | - 계정이 이전에 삭제되었거나 오랜 시간 비활성화된 경우, 다운스트림 서비스와의 동기화를 보장하기 위해 PDS가 재활성화 후 빈 `#commit` 이벤트를 발생시키는 것이 권장됩니다. 52 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/overview/ja.mdx: -------------------------------------------------------------------------------- 1 | import {Heading} from '@/components/Heading' 2 | 3 | export const metadata = { 4 | title: 'プロトコルの概要', 5 | description: 6 | 'AT プロトコルの概要。', 7 | } 8 | 9 | # プロトコルの概要 10 | 11 | **認証転送プロトコル** (別名 **atproto**) は、大規模な分散型ソーシャル アプリケーション用のフェデレーション プロトコルです。このドキュメントでは、AT プロトコルの背後にある考え方を紹介します。 12 | 13 | アイデンティティ 14 | 15 | ユーザーは、AT プロトコル上のドメイン名で識別されます。これらのドメインは、ユーザーのアカウントとそのデータを保護する暗号化 URL にマップされます。 16 | 17 | データ リポジトリ 18 | 19 | ユーザー データは、[署名済みデータ リポジトリ](/guides/data-repos) で交換されます。これらのリポジトリは、投稿、コメント、いいね、フォロー、メディア BLOB などを含むレコードのコレクションです。 20 | 21 | フェデレーション 22 | 23 | AT プロトコルは、フェデレーション ネットワーク モデルでリポジトリを同期します。フェデレーションは、ネットワークが使いやすく、確実に利用できるようにするために選択されました。リポジトリ データは、標準の Web テクノロジー ([HTTP](/specs/xrpc) および [WebSocket](/specs/event-stream)) を介してサーバー間で同期されます。 24 | 25 | ネットワークの 3 つのコア サービスは、パーソナル データ サーバー (PDS)、リレー、およびアプリ ビューです。フィード ジェネレーターとラベラーにも取り組んでいます。 26 | 27 | さまざまな方法でスタックできる下位レベルのプリミティブは、リポジトリ、レキシコン、および DID です。フェデレーション アーキテクチャに関する技術的な決定の概要を [ブログ](https://bsky.social/about/blog/5-5-2023-federation-architecture) で公開しました。 28 | 29 | 相互運用 30 | 31 | [Lexicon](/specs/lexicon) と呼ばれるグローバル スキーマ ネットワークは、サーバー間での呼び出しの名前と動作を統一するために使用されます。サーバーは、ユーザー リポジトリを同期するためのコア `com.atproto.*` レキシコンや、基本的なソーシャル動作を提供する `app.bsky.*` レキシコンなどの機能セットをサポートするために「レキシコン」を実装します。 32 | 33 | Web がドキュメントを交換する一方で、AT プロトコルは図式情報とセマンティック情報を交換し、異なる組織のソフトウェアが互いのデータを理解できるようにします。これにより、atproto クライアントはサーバーから独立してユーザー インターフェイスを自由に作成でき、コンテンツを閲覧する際にレンダリング コード (HTML/JS/CSS) を交換する必要がなくなります。 34 | 35 | スケールの実現 36 | 37 | 個人データ サーバーは、クラウド内のホームです。データをホストし、配布し、ID を管理し、他のサービスへのリクエストを調整してビューを提供します。 38 | 39 | リレーは、大規模なメトリック (いいね、再投稿、フォロワー) の取得、コンテンツの検出 (アルゴリズム)、ユーザー検索など、すべてのイベントを処理します。 40 | 41 | この区別は、スケールと高度なユーザー選択を実現することを目的としています。 42 | 43 | アルゴリズムの選択 44 | 45 | Web 検索エンジンと同様に、ユーザーはアグリゲータを自由に選択できます。フィード、アプリ ビュー、検索インデックスは独立したサードパーティによって提供され、リクエストはユーザー構成に基づいて PDS によってルーティングされます。 46 | 47 | アカウントの移植性 48 | 49 | 個人データ サーバーは、完全にオフラインになるか、特定のユーザーに対するサービスが停止するかのいずれかによって、いつでも失敗する可能性があると想定しています。AT プロトコルの目的は、ユーザーがサーバーの関与なしに自分のアカウントを新しい PDS に移行できるようにすることです。 50 | 51 | ユーザー データは [署名済みデータ リポジトリ](/guides/data-repos) に保存され、[DID](/guides/identity) によって検証されます。署名済みデータ リポジトリは Git リポジトリに似ていますが、データベース レコード用です。DID は基本的にユーザー証明書のレジストリであり、いくつかの点で TLS 証明書システムに似ています。これらは、安全で信頼性が高く、ユーザーの PDS から独立していることが期待されています。 52 | 53 | 各 DID ドキュメントは、署名キーと回復キーの 2 つの公開キーを公開します。 54 | 55 | * **署名キー**: DID ドキュメントとユーザーのデータ リポジトリへの変更をアサートします。 56 | 57 | * **回復キー**: DID ドキュメントへの変更をアサートします。72 時間以内に署名キーを上書きできます。 58 | 59 | 署名キーは PDS に委託され、ユーザーのデータを管理できるようにしますが、回復キーはユーザーが保存します (紙のキーなど)。これにより、ユーザーは元のホストの助けを借りずにアカウントを新しい PDS に更新できます。 60 | 61 | ユーザーのデータのバックアップは、バックアップとしてクライアントに永続的に同期されます (使用可能なディスク領域によって異なります)。PDS が予告なく消えた場合、ユーザーは DID ドキュメントを更新してバックアップをアップロードすることで、新しいプロバイダーに移行できる必要があります。 62 | 63 | スピーチ、リーチ、モデレーション 64 | 65 | Atproto のモデルでは、_スピーチ_ と _リーチ_ は 2 つの別々のレイヤーとして構築され、互いに連携するように構築されます。「スピーチ」レイヤーは、権限を分散し、誰もが発言権を持つように設計されている必要があります。「リーチ」レイヤーは、その上にあり、柔軟性と拡張性を考慮して構築されています。 66 | 67 | atproto の基本レイヤー (個人データ リポジトリとフェデレーション ネットワーク) は、誰もが自由に参加できるスピーチのための共通スペースを作成します。これは、誰でも Web サイトを作成できる Web に似ています。次に、インデックス サービスがネットワークからコンテンツを集約してリーチを有効にします。これは、検索エンジンに似ています。 68 | 69 | 仕様 70 | 71 | 主要な仕様の一部は次のとおりです。AT プロトコルの初期バージョンには次のものが含まれています: 72 | 73 | - [認証転送プロトコル](/specs/atp) 74 | - [DID](/specs/did) と [ハンドル](/specs/handle) 75 | - [リポジトリ](/specs/repository) と [データ モデル](/specs/data-model) 76 | - [用語集](/specs/lexicon) 77 | - [HTTP API (XRPC)](/specs/xrpc) と [イベント ストリーム](/specs/event-stream) 78 | 79 | ここから、[ガイドと仕様](/) を読み進めることができます。 -------------------------------------------------------------------------------- /src/app/[locale]/guides/faq/en.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'FAQ', 3 | description: 4 | 'Frequently Asked Questions about AT Protocol.', 5 | } 6 | 7 | # FAQ 8 | 9 | Frequently Asked Questions about the Authenticated Transfer Protocol (AT Proto). For FAQ about Bluesky, visit [here](https://bsky.social/about/faq). 10 | 11 | ## Is the AT Protocol a blockchain? 12 | 13 | No. The AT Protocol is a [federated protocol](https://en.wikipedia.org/wiki/Federation_(information_technology)). It's not a blockchain nor does it use a blockchain. 14 | 15 | ## Why not use ActivityPub? 16 | 17 | [ActivityPub](https://en.wikipedia.org/wiki/ActivityPub) is a federated social networking technology popularized by [Mastodon](https://joinmastodon.org/). 18 | 19 | Account portability is a major reason why we chose to build a separate protocol. We consider portability to be crucial because it protects users from sudden bans, server shutdowns, and policy disagreements. Our solution for portability requires both [signed data repositories](/guides/data-repos) and [DIDs](/guides/identity), neither of which are easy to retrofit into ActivityPub. The migration tools for ActivityPub are comparatively limited; they require the original server to provide a redirect and cannot migrate the user's previous data. 20 | 21 | Another major reason is scalability. ActivityPub depends heavily on delivering messages between a wide network of small-to-medium sized nodes, which can cause individual nodes to be flooded with traffic and generally struggles to provide global views of activity. The AT Protocol uses aggregating applications to merge activity from the users' hosts, reducing the overall traffic and dramatically reducing the load on individual hosts. 22 | 23 | Other smaller differences include: a different viewpoint about how schemas should be handled, a preference for domain usernames over AP's double-@ email usernames, and the goal of having large scale search and algorithmic feeds. 24 | 25 | ## Why create Lexicon instead of using JSON-LD or RDF? 26 | 27 | Atproto exchanges data and RPC commands across organizations. For the data and RPC to be useful, the software needs to correctly handle schemas created by separate teams. This is the purpose of [Lexicon](/guides/lexicon). 28 | 29 | We want engineers to feel comfortable using and creating new schemas, and we want developers to enjoy the DX of the system. Lexicon helps us produce strongly typed APIs which are extremely familiar to developers and which provides a variety of runtime correctness checks. 30 | 31 | [RDF](https://en.wikipedia.org/wiki/Resource_Description_Framework) is intended for extremely general cases in which the systems share very little infrastructure. It's conceptually elegant but difficult to use, often adding a lot of syntax which devs don't understand. JSON-LD simplifies the task of consuming RDF vocabularies, but it does so by hiding the underlying concepts, not by making RDF more legible. 32 | 33 | We looked very closely at using RDF but just didn't love the developer experience (DX) or the tooling it offered. 34 | 35 | ## What is “XRPC,” and why not use ___? 36 | 37 | [XRPC](/specs/xrpc) is HTTP with some added conventions. We're aiming to retire the term "XRPC" and just refer to it as the ATProto usage of HTTP. 38 | 39 | XRPC uses [Lexicon](/guides/lexicon) to describe HTTP calls and maps them to `/xrpc/{methodId}`. For example, this API call: 40 | 41 | ```typescript 42 | await api.com.atproto.repo.listRecords({ 43 | user: 'alice.com', 44 | collection: 'app.bsky.feed.post' 45 | }) 46 | ``` 47 | 48 | ...maps to: 49 | 50 | ```text 51 | GET /xrpc/com.atproto.repo.listRecords 52 | ?user=alice.com 53 | &collection=app.bsky.feed.post 54 | ``` 55 | 56 | Lexicon establishes a shared method id (`com.atproto.repo.listRecords`) and the expected query params, input body, and output body. By using Lexicon we get runtime checks on the inputs and outputs of the call, and can generate typed code like the API call example above. 57 | -------------------------------------------------------------------------------- /src/components/FooterCTA.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import Link from 'next/link' 4 | import { 5 | type MotionValue, 6 | motion, 7 | useMotionTemplate, 8 | useMotionValue, 9 | } from 'framer-motion' 10 | 11 | import { GridPattern } from '@/components/GridPattern' 12 | 13 | const PATTERN: Omit< 14 | React.ComponentPropsWithoutRef, 15 | 'width' | 'height' | 'x' 16 | > = { 17 | y: 22, 18 | squares: [[0, 1]], 19 | } 20 | 21 | function ArrowIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 22 | return ( 23 | 24 | 30 | 31 | ) 32 | } 33 | 34 | function FooterCTAPattern({ 35 | mouseX, 36 | mouseY, 37 | }: { 38 | mouseX: MotionValue 39 | mouseY: MotionValue 40 | }) { 41 | let maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)` 42 | let style = { maskImage, WebkitMaskImage: maskImage } 43 | 44 | return ( 45 | 46 | 47 | 54 | 55 | 59 | 63 | 70 | 71 | 72 | ) 73 | } 74 | 75 | export function FooterCTA({ 76 | href, 77 | title, 78 | description, 79 | }: { 80 | href: string 81 | title: string 82 | description: string 83 | }) { 84 | let mouseX = useMotionValue(0) 85 | let mouseY = useMotionValue(0) 86 | 87 | function onMouseMove({ 88 | currentTarget, 89 | clientX, 90 | clientY, 91 | }: React.MouseEvent) { 92 | let { left, top } = currentTarget.getBoundingClientRect() 93 | mouseX.set(clientX - left) 94 | mouseY.set(clientY - top) 95 | } 96 | 97 | return ( 98 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | {title} 110 | 111 | 112 | 113 | {description} 114 | 115 | 116 | 117 | 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/faq/pt.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'FAQ', 3 | description: 4 | 'Perguntas frequentes sobre o Protocolo AT.', 5 | } 6 | 7 | # FAQ 8 | 9 | Perguntas Frequentes sobre o Authenticated Transfer Protocol (AT Proto). Para FAQ sobre Bluesky, visite [aqui](https://bsky.social/about/faq). 10 | 11 | ## O Protocolo AT é uma blockchain? 12 | 13 | Não. O Protocolo AT é um [protocolo federado](https://en.wikipedia.org/wiki/Federation_(information_technology)). Não é um blockchain nem usa um blockchain. 14 | 15 | ## Por que não usar o ActivityPub? 16 | 17 | [ActivityPub](https://en.wikipedia.org/wiki/ActivityPub) é uma tecnologia de rede social federada popularizada pelo [Mastodon](https://joinmastodon.org/). 18 | 19 | A portabilidade da conta é um dos principais motivos pelos quais escolhemos criar um protocolo separado. Consideramos a portabilidade crucial porque ela protege os usuários de banimentos repentinos, desligamentos de servidores e desacordos de políticas. Nossa solução para portabilidade requer [repositórios de dados assinados](/guides/data-repos) e [DIDs](/guides/identity), nenhum dos quais é fácil de adaptar ao ActivityPub. As ferramentas de migração para o ActivityPub são comparativamente limitadas; elas exigem que o servidor original forneça um redirecionamento e não podem migrar os dados anteriores do usuário. 20 | 21 | Outro motivo importante é a escalabilidade. O ActivityPub depende muito da entrega de mensagens entre uma ampla rede de nós de pequeno a médio porte, o que pode fazer com que nós individuais sejam inundados com tráfego e geralmente tem dificuldades para fornecer visualizações globais da atividade. O Protocolo AT usa aplicativos de agregação para mesclar atividades dos hosts dos usuários, reduzindo o tráfego geral e reduzindo drasticamente a carga em hosts individuais. 22 | 23 | Outras diferenças menores incluem: um ponto de vista diferente sobre como os esquemas devem ser manipulados, uma preferência por nomes de usuários de domínio em vez de nomes de usuários de e-mail com @ duplo do AP e o objetivo de ter feeds de pesquisa e algoritmos em larga escala. 24 | 25 | ## Por que criar o Lexicon em vez de usar JSON-LD ou RDF? 26 | 27 | O Atproto troca dados e comandos RPC entre organizações. Para que os dados e o RPC sejam úteis, o software precisa manipular corretamente os esquemas criados por equipes separadas. Esse é o propósito do [Lexicon](/guides/lexicon). 28 | 29 | Queremos que os engenheiros se sintam confortáveis usando e criando novos esquemas, e queremos que os desenvolvedores aproveitem o DX do sistema. O Lexicon nos ajuda a produzir APIs fortemente tipadas que são extremamente familiares aos desenvolvedores e que fornecem uma variedade de verificações de correção em tempo de execução. 30 | 31 | O [RDF](https://en.wikipedia.org/wiki/Resource_Description_Framework) é destinado a casos extremamente gerais nos quais os sistemas compartilham muito pouca infraestrutura. É conceitualmente elegante, mas difícil de usar, frequentemente adicionando muita sintaxe que os desenvolvedores não entendem. O JSON-LD simplifica a tarefa de consumir vocabulários RDF, mas o faz ocultando os conceitos subjacentes, não tornando o RDF mais legível. 32 | 33 | Analisamos atentamente o uso do RDF, mas não gostamos da experiência do desenvolvedor (DX) nem das ferramentas que ele oferecia. 34 | 35 | ## O que é “XRPC” e por que não usar ___? 36 | 37 | [XRPC](/specs/xrpc) é HTTP com algumas convenções adicionadas. Nosso objetivo é aposentar o termo "XRPC" e apenas nos referir a ele como o uso ATProto de HTTP. 38 | 39 | XRPC usa [Lexicon](/guides/lexicon) para descrever chamadas HTTP e mapeá-las para `/xrpc/{methodId}`. Por exemplo, esta chamada de API: 40 | 41 | ```typescript 42 | await api.com.atproto.repo.listRecords({ 43 | user: 'alice.com', 44 | collection: 'app.bsky.feed.post' 45 | }) 46 | ``` 47 | 48 | ...maps para: 49 | 50 | ```text 51 | GET /xrpc/com.atproto.repo.listRecords 52 | ?user=alice.com 53 | &collection=app.bsky.feed.post 54 | ``` 55 | 56 | O Lexicon estabelece um id de método compartilhado (`com.atproto.repo.listRecords`) e os parâmetros de consulta esperados, corpo de entrada e corpo de saída. Ao usar o Lexicon, obtemos verificações de tempo de execução nas entradas e saídas da chamada e podemos gerar código digitado como o exemplo de chamada de API acima. -------------------------------------------------------------------------------- /src/app/[locale]/guides/overview/ko.mdx: -------------------------------------------------------------------------------- 1 | import {Heading} from '@/components/Heading' 2 | 3 | export const metadata = { 4 | title: '프로토콜 개요', 5 | description: 'AT Protocol에 대한 소개입니다.', 6 | } 7 | 8 | # 프로토콜 개요 9 | 10 | **Authenticated Transfer Protocol**(atproto라고도 함)은 대규모 소셜 웹 애플리케이션을 위한 탈중앙화 프로토콜입니다. 이 문서는 AT Protocol의 기본 개념을 소개합니다. 11 | 12 | 아이덴티티 13 | 14 | AT Protocol의 사용자들은 자신의 계정에 대해 영구적이고 탈중앙화된 식별자(DID)를 갖습니다. 또한, 사람이 읽기 쉬운 핸들 역할을 하는 구성 가능한 도메인 이름도 소유합니다. 아이덴티티에는 사용자의 현재 호스팅 제공자에 대한 참조와 암호화 키가 포함됩니다. 15 | 16 | 데이터 레포지토리 17 | 18 | 사용자 데이터는 [서명된 데이터 레포지토리](/guides/data-repos)에서 교환됩니다. 이 레포지토리들은 게시물, 댓글, 좋아요, 팔로우 등 다양한 레코드들의 모음입니다. 19 | 20 | 네트워크 아키텍처 21 | 22 | AT Protocol은 연합(federated) 네트워크 아키텍처를 채택하고 있습니다. 즉, 계정 데이터는 종단 장치 간의 피어 투 피어 모델 대신 호스트 서버에 저장됩니다. 연합 방식은 네트워크를 사용하기 편리하고 안정적으로 유지하기 위해 선택되었습니다. 레포지토리 데이터는 표준 웹 기술([HTTP](/specs/xrpc)와 [WebSockets](/specs/event-stream))을 통해 서버 간 동기화됩니다. 23 | 24 | 네트워크의 세 가지 핵심 서비스는 Personal Data Server(PDS), 릴레이, 그리고 앱 뷰(App Views)입니다. 그 외에도 피드 생성기나 라벨러와 같은 지원 서비스들이 존재합니다. 25 | 26 | 더 낮은 수준의 기본 요소들로는 레포지토리, Lexicon, 그리고 DID가 있으며, 이들이 다양한 방식으로 조합될 수 있습니다. 연합 아키텍처에 관한 기술적 결정의 개요는 [블로그](https://bsky.social/about/blog/5-5-2023-federation-architecture)에서 확인할 수 있습니다. 27 | 28 | 상호운용성 29 | 30 | [Lexicon](/specs/lexicon)이라는 글로벌 스키마 네트워크를 통해, 서버 간 호출의 이름과 동작이 통합됩니다. 서버들은 기능 집합을 지원하기 위해 "lexicons"을 구현하며, 여기에는 사용자 레포지토리 동기화를 위한 핵심 `com.atproto.*` lexicons과 기본 소셜 기능을 제공하는 `app.bsky.*` lexicons이 포함됩니다. 31 | 32 | 웹이 문서를 교환하는 반면, AT Protocol은 도식적 및 의미론적 정보를 교환하여 서로 다른 조직의 소프트웨어가 상대방의 데이터를 이해할 수 있도록 합니다. 이를 통해 atproto 클라이언트는 서버에 의존하지 않고도 독자적인 사용자 인터페이스를 제작할 수 있으며, 콘텐츠 탐색 시 렌더링 코드(HTML/JS/CSS)를 교환할 필요가 없어집니다. 33 | 34 | 확장성 달성 35 | 36 | Personal Data Server(PDS)는 클라우드 상에서 사용자의 집과도 같습니다. PDS는 데이터를 호스팅하고 배포하며, 아이덴티티를 관리하고, 다른 서비스에 요청을 조율하여 사용자에게 맞는 뷰를 제공합니다. 37 | 38 | 릴레이는 여러 서버의 데이터 업데이트를 하나의 firehose로 집계합니다. 39 | 40 | 앱 뷰(App Views)는 전체 네트워크에 대한 집계된 애플리케이션 데이터를 제공합니다. 이들은 대규모 지표(좋아요, 리포스트, 팔로워), 콘텐츠 발견(알고리즘), 사용자 검색 기능을 지원합니다. 41 | 42 | 이러한 역할 분리는 사용자가 여러 상호운용 가능한 제공자 중에서 자유롭게 선택할 수 있도록 하는 동시에, 대규모 네트워크 확장성을 보장하기 위한 것입니다. 43 | 44 | 알고리즘 선택 45 | 46 | 웹 검색 엔진과 마찬가지로, 사용자는 자신의 집계기를 자유롭게 선택할 수 있습니다. 피드, 라벨러, 검색 인덱스는 독립적인 제3자에 의해 제공될 수 있으며, 요청은 클라이언트 앱의 설정에 따라 PDS를 통해 라우팅됩니다. 클라이언트 앱은 특정 서비스, 예를 들어 앱 뷰(App Views)나 필수 라벨러에 연결될 수도 있습니다. 47 | 48 | 계정 이동성 49 | 50 | Personal Data Server(PDS)는 전체적으로 오프라인 상태가 되거나 특정 사용자에 대해 서비스를 중단할 수 있다고 가정합니다. AT Protocol의 목표는 사용자가 서버의 도움 없이도 자신의 계정을 새로운 PDS로 이전할 수 있도록 하는 것입니다. 51 | 52 | 사용자 데이터는 [서명된 데이터 레포지토리](/guides/data-repos)에 저장되며, [DID](/guides/identity)에 의해 인증됩니다. 서명된 데이터 레포지토리는 Git 저장소와 유사하지만 데이터베이스 레코드를 위한 것이며, DID는 TLS 인증서 시스템과 유사하게 암호화 키의 디렉터리 역할을 합니다. 아이덴티티는 안전하고 신뢰할 수 있으며, 사용자의 PDS와 독립적이어야 합니다. 53 | 54 | 대부분의 DID 문서는 두 종류의 공개 키를 포함합니다: **서명 키**와 **회전 키**. 55 | 56 | * **서명 키(Signing key)**: 사용자의 데이터 레포지토리를 검증합니다. 모든 DID에는 이 키가 포함되어 있습니다. 57 | * **회전 키(Rotation keys)**: DID 문서 자체의 변경 사항을 인증합니다. PLC DID 메서드는 회전 키를 포함하지만, DID Web 메서드는 포함하지 않습니다. 58 | 59 | 서명 키는 사용자의 데이터를 관리할 수 있도록 PDS에 위임되지만, 회전 키는 예를 들어 종이 키와 같이 사용자가 직접 관리할 수 있습니다. 이를 통해 사용자는 원래 호스트의 도움 없이 자신의 계정을 새로운 PDS로 이전할 수 있습니다. 60 | 61 | 사용자 데이터의 백업은 사용자의 기기와 지속적으로 동기화되거나(사용 가능한 디스크 공간에 따라), 또는 제3자 서비스에 의해 미러링될 수 있습니다. 만약 PDS가 예고 없이 사라진다면, 사용자는 자신의 DID 문서를 업데이트하고 데이터 백업을 업로드하여 새로운 제공자로 이전할 수 있어야 합니다. 62 | 63 | 발언, 도달, 그리고 모더레이션 64 | 65 | AT Protocol의 모델은 _발언(speech)_ 과 _도달(reach)_ 이 서로 보완하는 두 개의 별도 레이어로 구성되어야 한다는 것입니다. “발언” 레이어는 모두가 목소리를 낼 수 있도록 허용적이어야 하며, 권한 분산을 보장하기 위해 설계되었습니다. 반면 “도달” 레이어는 유연성과 확장성을 고려하여 구축됩니다. 66 | 67 | atproto의 기본 레이어(개인 데이터 레포지토리와 연합 네트워킹)는 모든 이가 자유롭게 참여할 수 있는 발언의 공통 공간을 제공합니다. 이는 누구나 웹에서 웹사이트를 만들 수 있는 것과 유사합니다. 이후 인덱싱 서비스는 네트워크의 콘텐츠를 집계하여 검색 엔진과 같이 도달 범위를 확장합니다. 68 | 69 | 사양 70 | 71 | AT Protocol 초기 버전을 구성하는 주요 사양은 다음과 같습니다: 72 | 73 | - [Authenticated Transfer Protocol](/specs/atp) 74 | - [DID](/specs/did) 및 [핸들](/specs/handle) 75 | - [레포지토리](/specs/repository) 및 [데이터 모델](/specs/data-model) 76 | - [Lexicon](/specs/lexicon) 77 | - [HTTP API (XRPC)](/specs/xrpc) 및 [이벤트 스트림](/specs/event-stream) 78 | -------------------------------------------------------------------------------- /src/app/[locale]/pt.mdx: -------------------------------------------------------------------------------- 1 | import { Guides } from '@/components/Guides' 2 | import { Specs } from '@/components/Specs' 3 | import { HeroPattern } from '@/components/HeroPattern' 4 | import { Firehose } from '@/components/Firehose' 5 | import { CodeCard } from '@/components/CodeCard' 6 | import { ImageCard } from '@/components/ImageCard' 7 | import { ArticleSummary, ArticleSummarySmall } from '@/components/ArticleSummary' 8 | import { UserIcon } from '@/components/icons/UserIcon' 9 | import WebWithoutWallsPng from '@/images/media/web-without-walls.png' 10 | import DotMediaInterviewPng from '@/images/media/dotmedia-interview.png' 11 | import WhitepaperPng from '@/images/media/whitepaper.png' 12 | import HowDoesBlueskyWorkBlogpostPng from '@/images/media/how-does-bsky-work-blogpost.png' 13 | 14 | export const metadata = { 15 | title: 'AT Protocol', 16 | description: 17 | 'O Protocolo AT é uma rede aberta, descentralizada e de alto desempenho para a criação de aplicativos sociais.', 18 | } 19 | 20 | export const sections = [] 21 | 22 | 23 | 24 | # Bem-vindo à atmosfera 25 | 26 | O Protocolo AT é uma rede aberta e descentralizada para a construção de aplicações sociais. {{ className: 'lead' }} 27 | 28 | 29 | 30 | <>Início rápido> 31 | 32 | 33 | <>Explorar SDKs> 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ## Saber mais 42 | 43 | 44 | 45 | 46 | O que é um PDS? Um AppView? Um DID? Se você está se sentindo um pouco impressionado com novos conceitos, dê uma olhada neste glossário. 47 | 48 | 49 | 50 | Neste guia, criamos um aplicativo multiusuário simples que publica seu "status" atual como um emoji. 51 | 52 | 53 | 54 | Neste artigo, exploramos o AT Proto da perspectiva da engenharia de backend distribuída. 55 | 56 | 57 | 58 | Este guia apresentará o Lexicon e ajudará você a começar a criar seus próprios esquemas. 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | {/* */} 72 | 73 | {/* */} 74 | 75 | 76 | 77 | ## Procurando a documentação da API Bluesky? 78 | 79 | Acesse [docs.bsky.app](https://docs.bsky.app) para obter documentação específica do Bluesky. 80 | -------------------------------------------------------------------------------- /src/components/mdx.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link' 2 | import NextImage, { ImageProps } from 'next/image' 3 | import clsx from 'clsx' 4 | 5 | import { Heading } from '@/components/Heading' 6 | import { Prose } from '@/components/Prose' 7 | 8 | export function Image({ className, ...props }: ImageProps) { 9 | // eslint-disable-next-line jsx-a11y/alt-text 10 | const cls = 11 | className && className?.includes('max-w') 12 | ? className 13 | : clsx(className, 'max-w-full') 14 | return 15 | } 16 | 17 | export const a = Link 18 | export { Button } from '@/components/Button' 19 | export { CodeGroup, Code as code, Pre as pre } from '@/components/Code' 20 | 21 | export function wrapper({ children }: { children: React.ReactNode }) { 22 | return ( 23 | 24 | {children} 25 | {/* */} 28 | 29 | ) 30 | } 31 | 32 | export const h2 = function H2( 33 | props: Omit, 'level'>, 34 | ) { 35 | return 36 | } 37 | 38 | export const h3 = function H3( 39 | props: Omit, 'level'>, 40 | ) { 41 | return 42 | } 43 | 44 | function InfoIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 45 | return ( 46 | 47 | 48 | 55 | 56 | 57 | ) 58 | } 59 | 60 | export function Note({ children }: { children: React.ReactNode }) { 61 | return ( 62 | 63 | 64 | 65 | {children} 66 | 67 | 68 | ) 69 | } 70 | 71 | export function Row({ children }: { children: React.ReactNode }) { 72 | return ( 73 | 74 | {children} 75 | 76 | ) 77 | } 78 | 79 | export function Col({ 80 | children, 81 | sticky = false, 82 | }: { 83 | children: React.ReactNode 84 | sticky?: boolean 85 | }) { 86 | return ( 87 | :first-child]:mt-0 [&>:last-child]:mb-0', 90 | sticky && 'xl:sticky xl:top-24', 91 | )} 92 | > 93 | {children} 94 | 95 | ) 96 | } 97 | 98 | export function Properties({ children }: { children: React.ReactNode }) { 99 | return ( 100 | 101 | 105 | {children} 106 | 107 | 108 | ) 109 | } 110 | 111 | export function Property({ 112 | name, 113 | children, 114 | type, 115 | }: { 116 | name: string 117 | children: React.ReactNode 118 | type?: string 119 | }) { 120 | return ( 121 | 122 | 123 | Name 124 | 125 | {name} 126 | 127 | {type && ( 128 | <> 129 | Type 130 | 131 | {type} 132 | 133 | > 134 | )} 135 | Description 136 | 137 | {children} 138 | 139 | 140 | 141 | ) 142 | } 143 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/blob/ko.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: '블롭', 3 | description: '레코드가 참조하는 미디어 파일', 4 | } 5 | 6 | # 블롭 7 | 8 | "블롭"은 계정의 레포지토리와 함께 저장되는 미디어 파일입니다. 이들은 이미지, 비디오, 오디오 등을 포함하지만, 다른 파일 형식도 포함할 수 있습니다. 블롭은 각 레코드에서 `blob` 렉시콘 데이터 유형으로 참조되며, 여기에는 블롭의 콘텐츠 해시(CID)가 포함됩니다. 9 | 10 | 블롭 파일은 레코드와 별도로 업로드 및 배포됩니다. 블롭은 계정의 PDS 인스턴스에 의해 권위적으로 저장되지만, 보통 애플리케이션(AppViews)과 연관된 CDN을 통해 뷰가 제공되어 PDS의 트래픽을 줄입니다. CDN은 원본 블롭의 변환된(크기 조정, 트랜스코딩 등) 버전을 제공할 수 있습니다. 11 | 12 | 블롭은 보편적으로 콘텐츠 주소 지정(CID)되지만, 항상 개별 계정(DID)의 컨텍스트 내에서 참조 및 관리됩니다. 13 | 14 | 빈 블롭(0 바이트)은 일반적으로 유효하지만, 일부 렉시콘이나 애플리케이션에서는 허용되지 않을 수 있습니다. 15 | 16 | ## 블롭 메타데이터 17 | 18 | 현재 블롭에 대해 "인증된" CID 유형은 레포지토리 레코드와 유사하지만 `raw` 멀티코덱을 사용합니다: 19 | 20 | - CIDv1 21 | - 멀티베이스: DAG-CBOR 내의 바이너리 직렬화 (또는 JSON 매핑의 경우 `base32`) 22 | - 멀티코덱: `raw` (0x55) 23 | - 멀티해시: 256비트 `sha-256` (0x12) 24 | 25 | 예시 블롭 CID (base32 문자열 인코딩): `bafkreibjfgx2gprinfvicegelk5kosd6y2frmqpqzwqkg7usac74l3t2v4` 26 | 27 | 블롭 메타데이터는 또한 블롭의 크기(바이트 단위)와 MIME 타입을 포함합니다. 크기와 CID는 결정적이며 유효하고 일관되어야 합니다. MIME 타입은 다소 주관적입니다: 동일한 바이트들이 여러 MIME 타입에 대해 유효할 수 있습니다. 28 | 29 | ## 블롭 수명 주기 30 | 31 | 레코드가 해당 블롭을 참조하기 전에 블롭은 반드시 PDS에 업로드되어야 합니다. 서버는 업로드 시 의도된 렉시콘을 알 수 없으므로, 초기 업로드 시에는 일반적인 블롭 제한 및 제약만 적용할 수 있고, 이후 레코드 생성 시 렉시콘에서 정의한 제한을 적용할 수 있습니다. 32 | 33 | 클라이언트는 PDS의 `com.atproto.repo.uploadBlob` 엔드포인트를 사용하며, 이 엔드포인트는 렉시콘 블롭 객체의 형태로 검증된 메타데이터를 반환합니다. 클라이언트는 HTTP `Content-Type` 헤더와 `Content-Length` 헤더를 업로드 요청에 "설정해야" 합니다. 청크 전송 인코딩도 업로드에 허용될 수 있습니다. 서버는 업로드된 블롭의 MIME 타입을 감지하여 선언된 `Content-Type` 헤더와 비교 검증할 수 있으며, 수정된 MIME 타입을 응답으로 반환하거나 업로드를 거부할 수 있습니다. 실제 업로드된 블롭의 크기가 `Content-Length` 헤더와 다를 경우, 서버는 업로드를 거부해야 합니다. 34 | 35 | 업로드가 성공하면, 블롭은 임시 저장소에 배치됩니다. 이 상태의 블롭은 다운로드나 배포가 불가능합니다. 서버는 임시 상태의 참조되지 않은 블롭을 적절한 시간 경과 후에 "가비지 컬렉션"(삭제) 해야 합니다 (아래 구현 지침 참조). 임시 저장소에 있는 블롭은 `listBlobs` 출력에 포함되어서는 안 됩니다. 36 | 37 | 이제 업로드된 블롭은 반환된 블롭 메타데이터를 레코드에 포함하여 참조할 수 있습니다. 레코드 생성 처리 시, 서버는 참조된 모든 블롭들을 추출하고, 해당 블롭들이 이미 참조되고 있거나 임시 저장소에 있는지 확인합니다. 레코드 생성이 성공하면, 서버는 블롭을 공개적으로 접근 가능하게 만듭니다. 38 | 39 | 동일한 블롭은 같은 레포지토리 내 여러 레코드에서 참조될 수 있습니다. 이미 저장되어 참조되고 있는 블롭을 다시 업로드해도 기존의 블롭이나 레코드에는 변화가 없습니다. 40 | 41 | 블롭을 참조하는 레코드가 삭제되면, 서버는 같은 레포지토리 내 다른 현재 레코드가 해당 블롭을 참조하는지 확인합니다. 참조하는 레코드가 없다면, 해당 블롭은 레코드와 함께 삭제됩니다. 42 | 43 | 계정이 삭제되면, 모든 호스팅된 블롭은 합리적인 시간 내에 삭제됩니다. 계정이 비활성화되거나 중단, 정지된 경우, 블롭은 공개적으로 접근할 수 없어야 합니다. 44 | 45 | 서버는 개별 블롭을 계정의 중단이나 다른 계정 수명 주기 이벤트와 별개로 접근 불가능하게 만들 수도 있습니다. 46 | 47 | 존재하지 않는 블롭을 참조하는 새로운 레코드 생성은 생성(또는 업데이트) 시 거부되어야 합니다. 그러나, 서버가 로컬에서 사용 가능한 블롭이 아닌 레포지토리 레코드를 호스팅할 가능성은 있습니다. 예를 들어, 대량 레포지토리 가져오기나 계정 마이그레이션, 데이터 손실 또는 정책상의 이유로 콘텐츠가 삭제/제거되는 경우가 이에 해당합니다. 48 | 49 | 원본 블롭은 `com.atproto.sync.getBlob` 엔드포인트를 통해 PDS에서 가져올 수 있습니다. 서버는 적절한 `Content-Type` 및 `Content-Length` HTTP 헤더를 반환해야 합니다. 미디어 파일을 PDS에서 직접 브라우저로 제공하는 것은 권장되거나 요구되는 패턴이 아니므로, 서버는 이를 지원할 필요가 없습니다. (아래 "보안 고려 사항" 참조) 50 | 51 | ## 사용 및 구현 지침 52 | 53 | 서버는 렉시콘에서 정의한 제약과는 별개로 블롭에 대해 자체적인 일반 제한 및 정책을 가질 수 있습니다. 예를 들어, 계정 전체의 데이터 저장소 쿼터, 최대 블롭 크기, 콘텐츠 정책 등을 구현할 수 있으며, 이러한 제한은 초기 업로드 시 적용될 수 있습니다. 운영자는 제한 및 기타 제약 사항이 기존 및 향후 애플리케이션의 기능에 영향을 미칠 수 있음을 인지해야 합니다. 상호 운용성을 극대화하기 위해, 운영자는 개별 블롭 크기 제한보다는 계정 전체의 리소스 소비(예: "전체 블롭 크기" 쿼터)에 대한 제한을 선호하는 것이 좋습니다. 54 | 55 | 일부 애플리케이션은 블롭 업로드와 레코드 참조 사이에 긴 지연 시간이 있을 수 있습니다. 상호 운용성을 극대화하기 위해, 서버 구현 및 운영자는 "가비지 컬렉션"을 최소 한 시간 이상의 확실한 시간 후, 몇 시간의 유예 기간을 두고 수행하는 것이 좋습니다. 56 | 57 | ## 보안 고려 사항 58 | 59 | 사용자가 업로드한 임의의 파일을 웹 서버에서 제공하는 것은 다양한 콘텐츠 보안 문제를 야기할 수 있습니다. 예를 들어, 스크립트나 SVG 콘텐츠에서의 교차 사이트 스크립팅(XSS)이 동일 출처 정책에 위배될 수 있습니다. `getBlob` 엔드포인트에는 Content Security Policy(CSP, [MDN 문서](https://developer.mozilla.org/ko/docs/Web/HTTP/CSP))를 활성화하는 것이 필수적입니다. PDS에서 직접 블롭을 브라우저나 웹 애플리케이션에 동적으로 제공하는 것은 실질적으로 지원되지 않습니다. 애플리케이션은 블롭, 파일, 자산을 독립적인 CDN, 프록시 또는 기타 웹 서비스로 중계하여 제공해야 하며, 이러한 서비스는 보안 예방 조치를 구현해야 합니다. 60 | 61 | 예시로, 이 엔드포인트에 적합한 콘텐츠 보안 헤더는 다음과 같습니다: 62 | 63 | ``` 64 | Content-Security-Policy: default-src 'none'; sandbox 65 | X-Content-Type-Options: nosniff 66 | ``` 67 | 68 | 일부 미디어 타입은 민감한 메타데이터를 포함할 수 있습니다. 예를 들어, JPEG 이미지 파일의 EXIF 메타데이터에는 GPS 좌표가 포함될 수 있습니다. 서버는 이러한 메타데이터의 우발적 누출을 방지하기 위해, 예를 들어 메타데이터를 포함하는 블롭의 업로드를 차단하는 등의 조치를 취할 수 있습니다. (자세한 내용은 "향후 변경 사항" 섹션의 주석을 참조하십시오.) 69 | 70 | 미디어 파일 파싱은 메모리 안전 버그 및 보안 취약점의 주요 원인이 될 수 있습니다. 콘텐츠 타입 감지(스니핑) 또한 취약점의 원인이 될 수 있으므로, 서버는 강력한 샌드박싱 메커니즘 없이 미디어 파일(이미지, 비디오, 오디오 또는 기타 복잡한 형식)을 직접 파싱하는 것을 강력히 권장하지 않습니다. 특히, PDS 인스턴스 자체가 미디어 크기 조정이나 트랜스코딩을 직접 구현해서는 안 됩니다. 71 | 72 | 더 복잡한 미디어 타입은 악의적이거나 불법적인 콘텐츠의 위험을 증가시킵니다. 서버는 그러한 콘텐츠가 감지되고 신고될 경우 적절한 제거 메커니즘을 구현해야 합니다. 73 | 74 | 서버는 악의적인 리소스 소비를 방지하기 위한 조치를 취해야 할 수도 있습니다. 예를 들어, 의도적인 디스크 공간 고갈, 네트워크 혼잡, 대역폭 사용 등이 이에 해당합니다. 속도 제한, 크기 제한 및 쿼터 적용이 권장됩니다. 75 | 76 | ## 향후 변경 사항 77 | 78 | 허용된 CID 유형은 시간이 지남에 따라 진화할 것으로 예상됩니다. 대용량 파일에 대해 `blake3`에 대한 관심이 있습니다. 79 | 80 | EXIF 메타데이터 제거와 같이 메타데이터 누출을 보다 구체적으로 완화하는 방안이 API 변경을 통해 권장되거나 활성화되어야 합니다. 이는 기본적인 안전성을 제공하는 것과 사용자가 업로드한 "원본" 데이터를 항상 변경하지 않고 제공하는 것 사이의 긴장을 야기합니다. 또한, 업로드된 미디어 파일을 파싱하고 조작하는 것은 다른 종류의 보안 문제를 발생시킬 수 있습니다. -------------------------------------------------------------------------------- /src/components/SectionProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { 4 | createContext, 5 | useContext, 6 | useEffect, 7 | useLayoutEffect, 8 | useState, 9 | } from 'react' 10 | import { type StoreApi, createStore, useStore } from 'zustand' 11 | 12 | import { remToPx } from '@/lib/remToPx' 13 | 14 | export interface Section { 15 | id: string 16 | title: string 17 | offsetRem?: number 18 | tag?: string 19 | headingRef?: React.RefObject 20 | } 21 | 22 | interface SectionState { 23 | sections: Array 24 | visibleSections: Array 25 | setVisibleSections: (visibleSections: Array) => void 26 | registerHeading: ({ 27 | id, 28 | ref, 29 | offsetRem, 30 | }: { 31 | id: string 32 | ref: React.RefObject 33 | offsetRem: number 34 | }) => void 35 | } 36 | 37 | function createSectionStore(sections: Array) { 38 | return createStore()((set) => ({ 39 | sections, 40 | visibleSections: [], 41 | setVisibleSections: (visibleSections) => 42 | set((state) => 43 | state.visibleSections.join() === visibleSections.join() 44 | ? {} 45 | : { visibleSections }, 46 | ), 47 | registerHeading: ({ id, ref, offsetRem }) => 48 | set((state) => { 49 | return { 50 | sections: state.sections.map((section) => { 51 | if (section.id === id) { 52 | return { 53 | ...section, 54 | headingRef: ref, 55 | offsetRem, 56 | } 57 | } 58 | return section 59 | }), 60 | } 61 | }), 62 | })) 63 | } 64 | 65 | function useVisibleSections(sectionStore: StoreApi) { 66 | let setVisibleSections = useStore(sectionStore, (s) => s.setVisibleSections) 67 | let sections = useStore(sectionStore, (s) => s.sections) 68 | 69 | useEffect(() => { 70 | function checkVisibleSections() { 71 | let { innerHeight, scrollY } = window 72 | let newVisibleSections = [] 73 | 74 | for ( 75 | let sectionIndex = 0; 76 | sectionIndex < sections.length; 77 | sectionIndex++ 78 | ) { 79 | let { id, headingRef, offsetRem = 0 } = sections[sectionIndex] 80 | 81 | if (!headingRef?.current) { 82 | continue 83 | } 84 | 85 | let offset = remToPx(offsetRem) 86 | let top = headingRef.current.getBoundingClientRect().top + scrollY 87 | 88 | if (sectionIndex === 0 && top - offset > scrollY) { 89 | newVisibleSections.push('_top') 90 | } 91 | 92 | let nextSection = sections[sectionIndex + 1] 93 | let bottom = 94 | (nextSection?.headingRef?.current?.getBoundingClientRect().top ?? 95 | Infinity) + 96 | scrollY - 97 | remToPx(nextSection?.offsetRem ?? 0) 98 | 99 | if ( 100 | (top > scrollY && top < scrollY + innerHeight) || 101 | (bottom > scrollY && bottom < scrollY + innerHeight) || 102 | (top <= scrollY && bottom >= scrollY + innerHeight) 103 | ) { 104 | newVisibleSections.push(id) 105 | } 106 | } 107 | 108 | setVisibleSections(newVisibleSections) 109 | } 110 | 111 | let raf = window.requestAnimationFrame(() => checkVisibleSections()) 112 | window.addEventListener('scroll', checkVisibleSections, { passive: true }) 113 | window.addEventListener('resize', checkVisibleSections) 114 | 115 | return () => { 116 | window.cancelAnimationFrame(raf) 117 | window.removeEventListener('scroll', checkVisibleSections) 118 | window.removeEventListener('resize', checkVisibleSections) 119 | } 120 | }, [setVisibleSections, sections]) 121 | } 122 | 123 | const SectionStoreContext = createContext | null>(null) 124 | 125 | const useIsomorphicLayoutEffect = 126 | typeof window === 'undefined' ? useEffect : useLayoutEffect 127 | 128 | export function SectionProvider({ 129 | sections, 130 | children, 131 | }: { 132 | sections: Array 133 | children: React.ReactNode 134 | }) { 135 | let [sectionStore] = useState(() => createSectionStore(sections)) 136 | 137 | useVisibleSections(sectionStore) 138 | 139 | useIsomorphicLayoutEffect(() => { 140 | sectionStore.setState({ sections }) 141 | }, [sectionStore, sections]) 142 | 143 | return ( 144 | 145 | {children} 146 | 147 | ) 148 | } 149 | 150 | export function useSectionStore(selector: (state: SectionState) => T) { 151 | let store = useContext(SectionStoreContext) 152 | return useStore(store!, selector) 153 | } 154 | -------------------------------------------------------------------------------- /src/app/[locale]/guides/account-migration/ko.mdx: -------------------------------------------------------------------------------- 1 | import {Heading} from '@/components/Heading' 2 | 3 | export const metadata = { 4 | title: '계정 마이그레이션', 5 | description: '계정 마이그레이션 세부사항', 6 | } 7 | 8 | # 계정 마이그레이션 세부사항 9 | 10 | 이 문서는 계정 수명 주기에 대한 개요를 제공하는 [계정 호스팅](/specs/account) 사양을 보완합니다. 여기에서는 "쉬운" 마이그레이션(두 PDS 인스턴스가 모두 참여하는 경우)과 계정 복구 시나리오 모두에 대해 개별 마이그레이션 단계를 상세히 설명합니다. 이 특정 메커니즘들은 프로토콜의 공식 일부가 아니며, 시간이 지남에 따라 변경될 수 있습니다. 11 | 12 | 새로운 계정 생성 13 | 14 | 기존 아이덴티티를 사용하여 PDS 계정을 생성하려면, 해당 아이덴티티에 대한 제어 권한을 증명해야 합니다. 15 | 16 | - 다른 PDS에서 활성화된 계정의 경우, 아이덴티티의 DID 문서에 명시된 현재 atproto 서명 키를 사용하여 서비스 인증 토큰(JWT)을 생성합니다. 이 토큰은 이전 PDS 인스턴스에서 `com.atproto.server.getServiceAuth` 엔드포인트(또는 이에 상응하는 인터페이스/API)를 통해 요청할 수 있습니다. 17 | - 만약 독자적으로 제어되는 아이덴티티(예: `did:web` 또는 이전 PDS가 오프라인이거나 협조하지 않는 `did:plc`)인 경우, 아이덴티티를 업데이트하여 사용자가 제어하는 atproto 서명 키를 포함시키고, 오프라인에서 서비스 인증 토큰을 생성해야 할 수 있습니다. 18 | 19 | 서비스 인증 토큰은 새 PDS에서 `com.atproto.server.createAccount` (또는 이에 상응하는 인터페이스/API)를 호출할 때 기존 DID와 함께 제공됩니다. 20 | 21 | 새 계정은 `비활성화(deactivated)` 상태로 생성됩니다. 사용자는 직접 로그인하여 인증할 수 있지만, 네트워크에 참여하지는 않습니다. 다른 네트워크 서비스의 관점에서는 이전 PDS 계정이 여전히 현재 상태로 간주되며, 새 PDS 계정은 아직 활성화되거나 유효하지 않습니다. 따라서 OAuth나 서비스 인증을 통한 프록시 요청 등의 기능은 아직 새 PDS에서 동작하지 않습니다. 22 | 23 | 데이터 마이그레이션 24 | 25 | 일반적으로 마이그레이션되는 데이터 유형은 다음과 같습니다: 26 | 27 | - 공개 레포지토리 28 | - 공개 블롭(미디어 파일) 29 | - 개인 설정 30 | 31 | 마이그레이션의 어느 단계에서든, 인증된 상태로 `com.atproto.server.checkAccountStatus` 엔드포인트를 이전 PDS와 새 PDS 양쪽에서 호출하여 현재 색인된 데이터의 통계를 확인할 수 있습니다. 32 | 33 | - 공개 레포지토리의 사본은 이전 PDS에서 공개 `com.atproto.sync.getRepo` 엔드포인트를 사용하여 CAR 파일 형식으로 가져올 수 있습니다. 만약 이전 PDS에 접근할 수 없다면, 공개 릴레이에서 미러를 찾거나 로컬 백업을 사용할 수 있습니다. 백업이 없다면, 새 계정은 동일한 아이덴티티로 기능하지만 이전 콘텐츠는 누락됩니다. 34 | - CAR 파일은 새 PDS에서 인증된 `com.atproto.repo.importRepo` 엔드포인트를 통해 가져올 수 있습니다. 35 | 36 | 블롭(미디어 파일)은 개별적으로 다운로드하여 재업로드해야 합니다. 레포지토리가 가져와져서 완전히 색인되기 전에는 새 PDS에 블롭을 업로드하면 안 됩니다. 그래야 블롭이 참조하는 레코드와 올바르게 연결되고, 가비지 컬렉션 대상이 되지 않습니다. 관련 블롭의 전체 목록(CID 기준)은 이전 PDS의 `com.atproto.sync.listBlobs` 엔드포인트 또는 새 PDS의 `com.atproto.repo.listMissingBlobs`를 통해 확인할 수 있습니다. 만약 일부 블롭을 찾을 수 없다 하더라도 마이그레이션은 계속 진행할 수 있으며, 나중에 블롭이 복구되면(정확한 CID가 일치할 경우) 업로드할 수 있습니다. 37 | 38 | 개인 계정 설정은 이전 PDS에서 인증된 `app.bsky.actor.getPreferences` 엔드포인트를 통해 내보낸 후, `app.bsky.actor.putPreferences`를 사용하여 가져올 수 있습니다. 이는 Bluesky 앱 전용 엔드포인트이며, 다른 앱(Lexicons)에서는 자체 설정 API를 정의할 수 있습니다. 단, 이 방식은 PDS에 저장된 개인 상태만 포함하며, 일부 설정이나 상태는 외부 서비스(예: 중앙집중식 채팅/DM 구현)에 존재할 수 있습니다. 39 | 40 | 아이덴티티 업데이트 41 | 42 | 콘텐츠 마이그레이션이 완료되면, 아이덴티티(DID 및 핸들)를 업데이트하여 새 PDS가 해당 계정의 현재 호스트임을 나타낼 수 있습니다. 43 | 44 | 새 PDS에서 `com.atproto.identity.getRecommendedDidCredentials` 엔드포인트를 호출하면 "권장" DID 문서 파라미터를 가져올 수 있습니다. 이에는 DID 서비스 호스트명, 계정 생성 시 요청한 로컬 핸들, PDS에서 관리하는 atproto 서명 키(공개키), 그리고 (해당되는 경우) PLC 회전 키(공개키)가 포함됩니다. 45 | 46 | 만약 사용자가 안전하게 개인 암호화 키 쌍을 관리할 수 있다면(예: 비밀번호 관리자나 디지털 지갑에 저장), PLC 작업에 자체 제어 PLC 회전 키(공개키)를 포함시키는 것이 권장됩니다. 47 | 48 | - **자체 제어 아이덴티티:** (예: `did:web` 또는 로컬 회전 키를 사용하는 `did:plc`)의 경우, 사용자가 직접 아이덴티티 업데이트를 수행할 수 있습니다. 49 | - **이전 PDS에서 관리하는 `did:plc` 계정:** PLC "작업"은 이전 PDS가 서명한 후 새 PDS를 통해 제출됩니다. 새 PDS가 PLC 작업을 직접 제출하도록 하는 이유는, 이를 통해 새 PDS가 작업을 검증하고 계정이 손상되는 것을 방지하기 위한 안전 검사를 수행할 수 있기 때문입니다. 50 | 51 | 아이덴티티 작업은 민감하므로, 추가적인 보안 토큰이 추가 인증 수단으로 요구됩니다. 이 토큰은 이전 PDS에서 `com.atproto.identity.requestPlcOperationSignature`를 호출하여 요청할 수 있으며, 기본적으로 확인된 계정 이메일로 전달됩니다. 52 | 53 | 해당 토큰은 `com.atproto.identity.signPlcOperation` 호출 시 함께 포함되어, 요청된 DID 필드(새 서명 키, 회전 키, PDS 위치 등)와 함께 전달됩니다. 이전 PDS는 요청을 검증하고, PDS에서 관리하는 PLC 회전 키를 사용하여 서명한 PLC 작업을 반환합니다. 이 작업은 아직 PLC 디렉터리에 제출되지는 않습니다. 54 | 55 | 사용자는 이후 새 PDS에서 `com.atproto.identity.submitPlcOperation` 엔드포인트를 호출하여 작업을 제출하는 것이 권장됩니다. 이때 새 PDS는 변경 사항이 PDS가 아이덴티티와 atproto 계정을 관리할 수 있도록 "안전"한지 검증한 후, PLC 디렉터리에 제출합니다. 56 | 57 | 아이덴티티 업데이트가 성공적으로 완료되면, 전체 네트워크 관점에서 새 PDS가 해당 계정의 "현재" 호스트가 됩니다. 이는 아이덴티티를 해석하는 새로운 서비스에서는 즉시 반영되며, 기존의 파이어호스(firehose) 소비 서비스는 `#identity` 이벤트를 통해 이를 인지하게 됩니다. 캐시된 아이덴티티 메타데이터를 가진 다른 서비스는 캐시 만료 시 또는 서명 오류(예: 잘못된 서비스 인증 서명) 발생 시 새로고침을 수행해야 합니다. 58 | 59 | 그러나 이 시점에서도 새 계정은 아직 "활성화(active)" 상태가 아닙니다. 60 | 61 | 계정 상태 최종화 62 | 63 | 현재 사용자는 두 PDS 인스턴스 모두에서 인증이 가능합니다. 새 PDS는 해당 계정의 현재 호스트임을 인지하고 있으나, 계정 상태는 여전히 "비활성화"로 표시됩니다. 반면, 이전 PDS는 자신이 더 이상 최신 계정이 아님을 인지하지 못할 수 있습니다. 64 | 65 | `com.atproto.server.checkAccountStatus` 엔드포인트를 두 PDS에서 모두 호출하여 모든 콘텐츠가 올바르게 마이그레이션되었는지 확인하는 것이 좋습니다. 66 | 67 | 사용자는 새 PDS에서 `com.atproto.server.activateAccount`를 호출하여 계정을 활성화하고, 이전 PDS에서는 `com.atproto.server.deactivateAccount`를 호출하여 계정을 비활성화할 수 있습니다. 68 | 69 | 이 시점에서 마이그레이션은 완료됩니다. 이후에는 레포지토리에 게시물을 작성하거나 개인 설정을 업데이트하는 등 정상적인 서비스 이용이 가능해지며, 인터서비스 인증 및 프록시 요청 또한 예상대로 작동합니다. 일부 클라이언트의 경우, 로그아웃 후 재로그인이 필요할 수 있습니다. 또한, 일부 서비스가 공격적인 아이덴티티 캐싱을 사용하여 서명 실패 시 즉시 새로고침하지 않는 경우, 서비스 인증 요청이 최대 24시간까지 실패할 수 있습니다. 70 | 71 | 이후에도 이전 PDS를 통해 로그인 및 인증은 가능하므로, 사용자는 원할 경우 이전 계정을 완전히 종료할 수 있습니다. 이는 `com.atproto.server.deactivateAccount` 요청의 `deleteAfter` 파라미터를 사용하여 자동화할 수 있습니다. 단, 이전 PDS는 계정이 완전히 삭제되지 않은 경우에 한해, 고정된 72시간 내에 PLC 아이덴티티 복구를 지원할 수 있습니다. 72 | -------------------------------------------------------------------------------- /src/app/[locale]/specs/nsid/en.mdx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Namespaced Identifiers (NSIDs)', 3 | description: 4 | 'A specification for global semantic IDs.', 5 | } 6 | 7 | # Namespaced Identifiers (NSIDs) 8 | 9 | Namespaced Identifiers (NSIDs) are used to reference Lexicon schemas for records, XRPC endpoints, and more. {{ className: 'lead' }} 10 | 11 | The basic structure and semantics of an NSID are a fully-qualified hostname in Reverse Domain-Name Order, followed by a simple name. The hostname part is the **domain authority,** and the final segment is the **name**. {{ className: 'lead' }} 12 | 13 | 14 | ### NSID Syntax 15 | 16 | Lexicon string type: `nsid` 17 | 18 | The domain authority part of an NSID must be a valid handle with the order of segments reversed. That is followed by a name segment which must be an ASCII camel-case string. 19 | 20 | For example, `com.example.fooBar` is a syntactically valid NSID, where `com.example` is the domain authority, and `fooBar` is the name segment. 21 | 22 | The comprehensive list of syntax rules is: 23 | 24 | - Overall NSID: 25 | - must contain only ASCII characters 26 | - separate the domain authority and the name by an ASCII period character (`.`) 27 | - must have at least 3 segments 28 | - can have a maximum total length of 317 characters 29 | - Domain authority: 30 | - made of segments separated by periods (`.`) 31 | - at most 253 characters (including periods), and must contain at least two segments 32 | - each segment must have at least 1 and at most 63 characters (not including any periods) 33 | - the allowed characters are ASCII letters (`a-z`), digits (`0-9`), and hyphens (`-`) 34 | - segments can not start or end with a hyphen 35 | - the first segment (the top-level domain) can not start with a numeric digit 36 | - the domain authority is not case-sensitive, and should be normalized to lowercase (that is, normalize ASCII `A-Z` to `a-z`) 37 | - Name: 38 | - must have at least 1 and at most 63 characters 39 | - the allowed characters are ASCII letters and digits only (`A-Z`, `a-z`, `0-9`) 40 | - hyphens are not allowed 41 | - the first character can not be a digit 42 | - case-sensitive and should not be normalized 43 | 44 | A reference regex for NSID is: 45 | 46 | ``` 47 | /^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$/ 48 | ``` 49 | 50 | 51 | ### NSID Syntax Variations 52 | 53 | When referring to a group or pattern of NSIDs, a trailing ASCII star character (`*`) can be used as a "glob" character. For example, `com.atproto.*` would refer to any NSIDs under the `atproto.com` domain authority, including nested sub-domains (sub-authorities). A free-standing `*` would match all NSIDs from all authorities. Currently, there may be only a single star character; it must be the last character; and it must be at a segment boundary (no partial matching of segment names). This means the start character must be proceeded by a period, or be a bare star matching all NSIDs. 54 | 55 | 56 | ### Examples 57 | 58 | Syntactically valid NSIDs: 59 | 60 | ``` 61 | com.example.fooBar 62 | net.users.bob.ping 63 | a-0.b-1.c 64 | a.b.c 65 | com.example.fooBarV2 66 | cn.8.lex.stuff 67 | ``` 68 | 69 | Invalid NSIDs: 70 | 71 | ``` 72 | com.exa💩ple.thing 73 | com.example 74 | com.example.3 75 | ``` 76 | 77 | 78 | ### Usage and Implementation Guidelines 79 | 80 | A **strongly-encouraged** best practice is to use authority domains with only ASCII alphabetic characters (that is, no digits or hyphens). This makes it significantly easier to generate client libraries in most programming languages. 81 | 82 | The overall NSID is case-sensitive for display, storage, and validation. However, having multiple NSIDs that differ only by casing is not allowed. Namespace authorities are responsible for preventing duplication and confusion. Implementations should not force-lowercase NSIDs. 83 | 84 | It is common to use "subdomains" as part of the "domain authority" to organize related NSIDs. For example, the NSID `com.atproto.sync.getHead` uses the `sync` segment. Note that this requires control of the full domain `sync.atproto.com`, in addition to the domain `atproto.com`. 85 | 86 | Lexicon language documentation will provide style guidelines on choosing and organizing NSIDs for both record types and XRPC methods. In short, records are usually single nouns, not pluralized. XRPC methods are usually in "verbNoun" form. 87 | 88 | Interop test vectors are available from the [atproto-interop-tests](https://github.com/bluesky-social/atproto-interop-tests) repository. 89 | 90 | ### Possible Future Changes 91 | 92 | It is conceivable that NSID syntax would be relaxed to allow Unicode characters in the final segment. 93 | 94 | The "glob" syntax variation may be modified to extended to make the distinction between single-level and nested matching more explicit. 95 | 96 | No automated mechanism for verifying control of a "domain authority" currently exists. Also, not automated mechanism exists for fetching a lexicon schema for a given NSID, or for enumerating all NSIDs for a base domain. 97 | -------------------------------------------------------------------------------- /src/components/MobileNavigation.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { createContext, Suspense, useContext, useEffect, useRef } from 'react' 4 | import { usePathname, useSearchParams } from 'next/navigation' 5 | import { 6 | Dialog, 7 | DialogPanel, 8 | DialogBackdrop, 9 | TransitionChild, 10 | } from '@headlessui/react' 11 | import { motion } from 'framer-motion' 12 | import { create } from 'zustand' 13 | 14 | import { Header } from '@/components/Header' 15 | import { Navigation } from '@/components/Navigation' 16 | import LanguageChanger from './LanguageChanger' 17 | 18 | function MenuIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 19 | return ( 20 | 27 | 28 | 29 | ) 30 | } 31 | 32 | function XIcon(props: React.ComponentPropsWithoutRef<'svg'>) { 33 | return ( 34 | 41 | 42 | 43 | ) 44 | } 45 | 46 | const IsInsideMobileNavigationContext = createContext(false) 47 | 48 | function MobileNavigationDialog({ 49 | isOpen, 50 | close, 51 | }: { 52 | isOpen: boolean 53 | close: () => void 54 | }) { 55 | let pathname = usePathname() 56 | let searchParams = useSearchParams() 57 | let initialPathname = useRef(pathname).current 58 | let initialSearchParams = useRef(searchParams).current 59 | 60 | useEffect(() => { 61 | if (pathname !== initialPathname || searchParams !== initialSearchParams) { 62 | close() 63 | } 64 | }, [pathname, searchParams, close, initialPathname, initialSearchParams]) 65 | 66 | function onClickDialog(event: React.MouseEvent) { 67 | if (!(event.target instanceof HTMLElement)) { 68 | return 69 | } 70 | 71 | let link = event.target.closest('a') 72 | if ( 73 | link && 74 | link.pathname + link.search + link.hash === 75 | window.location.pathname + window.location.search + window.location.hash 76 | ) { 77 | close() 78 | } 79 | } 80 | 81 | return ( 82 | 88 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | ) 112 | } 113 | 114 | export function useIsInsideMobileNavigation() { 115 | return useContext(IsInsideMobileNavigationContext) 116 | } 117 | 118 | export const useMobileNavigationStore = create<{ 119 | isOpen: boolean 120 | open: () => void 121 | close: () => void 122 | toggle: () => void 123 | }>()((set) => ({ 124 | isOpen: false, 125 | open: () => set({ isOpen: true }), 126 | close: () => set({ isOpen: false }), 127 | toggle: () => set((state) => ({ isOpen: !state.isOpen })), 128 | })) 129 | 130 | export function MobileNavigation() { 131 | let isInsideMobileNavigation = useIsInsideMobileNavigation() 132 | let { isOpen, toggle, close } = useMobileNavigationStore() 133 | let ToggleIcon = isOpen ? XIcon : MenuIcon 134 | 135 | return ( 136 | 137 | 143 | 144 | 145 | {!isInsideMobileNavigation && ( 146 | 147 | 148 | 149 | )} 150 | 151 | ) 152 | } 153 | --------------------------------------------------------------------------------
10 | 404 11 |
16 | Sorry, we couldn’t find the page you’re looking for. 17 |
{description}
23 | {description} 24 |
95 | {guide.description} 96 |
98 | 99 | Read more 100 | 101 |
49 | Was this page helpful? 50 |
{name}