아키텍처
ActivityPlug는 이식 가능한 제품 계약을 원격 API 매핑과 전송 수단으로부터 분리합니다. 어댑터가 제공하는 동일한 서비스 동작을 TypeScript 클라이언트, 서버의 HTTP·GraphQL API, 브라우저 경계에서 호출할 수 있습니다.
패키지 계층
저장소는 다음 계층으로 구성됩니다.
@activityplug/core는 정규화된 엔티티, 어댑터·클라이언트 계약, 인증, capability, 불투명 ID, 페이지네이션, 오류, 원격 권한, 예산, 스트림 타입을 정의합니다.- 어댑터 패키지는
ActivityPlugAdapter를 구현합니다. Mastodon·Pleroma/Akkoma·Hollo는@activityplug/mastodon-base를 공유하고, Misskey와 HackersPub은 각각의 API를 매핑합니다. @activityplug/server는 어댑터를 선택해 HTTP, GraphQL, WebSocket, 선택적 브라우저 라우트를 통해 서비스를 제공합니다.@activityplug/session-postgres와@activityplug/session-redis는 공유되거나 영속적인 보안 상태가 필요한 배포 환경을 위한 서버 저장소 계약을 구현합니다.- 예제 패키지는 라이브러리, 프록시 클라이언트, 브라우저 클라이언트의 통합 경로를 실행합니다. 픽스처 패키지는 비공개 개발 도구이며 애플리케이션의 런타임 의존성이 아닙니다.
코어 패키지는 어댑터나 서버에 의존하지 않습니다. 어댑터는 코어 계약에 의존합니다. 서버는 구체적인 어댑터에 의존하고 코어를 peer dependency로 선언하므로, 워크스페이스에는 공개 계약 인스턴스가 하나만 유지됩니다.
라이브러리 요청 흐름
라이브러리 호출자는 어댑터, 인스턴스 origin, 선택적 세션 저장소, 원격 권한, capability 집합, 예산 팩터리로 클라이언트를 생성합니다. 각 서비스 호출은 다음 순서로 처리됩니다.
- 클라이언트가 입력, 필수 capability, 세션 대상, 불투명 ID, 페이지 커서, 이식 가능 제한을 검증합니다.
- 어댑터 작업을 찾고 정규화된 origin, 어댑터 ID, 작업, capability, 범위가 제한된 fetch, 세션 저장소, 감지된 소프트웨어, 선택적 예산을 포함하는
AdapterOperationContext를 구성합니다. - 어댑터가 정규화된 입력을 원격 요청으로 변환하고, 범위가 제한된 fetch를 통해 실행합니다.
- 어댑터가 응답을 검증하고 정규화된 엔티티, 연결, 타입이 지정된 오류로 매핑합니다.
- 클라이언트가 이식 가능한 결과를 반환합니다. 원격 페이로드는
raw에 남을 수 있지만, 그 형태가 모든 어댑터에서 같다고 가정할 수 없습니다.
클라이언트는 전역 fetch를 대체 수단으로 사용하지 않으므로, RemoteAuthority가 없으면 원격 작업은 네트워크 I/O 전에 ORIGIN_NOT_ALLOWED로 실패합니다.
서버 요청 흐름
createActivityPlugServer()는 Node.js 런타임을 조립합니다. 검증된 외부 요청용 fetch 경계를 하나 만들고, 설정된 어댑터를 ActivityPlugApiService에 연결한 뒤 공개 애플리케이션을 마운트합니다.
공개 애플리케이션은 다음을 제공합니다.
- JSON 또는 multipart 입력을 받는 HTTP 라우트
- 동일한 서비스 메서드 위에 구축된 GraphQL 엔드포인트
- HTTP 표면을 위한 OpenAPI 메타데이터
- 스트리밍 작업을 위한 WebSocket 업그레이드
- 상태 확인과 준비 상태 동작
HTTP와 GraphQL은 동일한 서비스 계약을 전송 방식에 맞게 매핑한 것이며, 각각 별도로 원격 API를 호출하지 않습니다. 요청 중단 신호, 입력 제한, GraphQL 복잡도 제한, 인증, capability 검사, ActivityPlugError 직렬화는 각 경계에서 적용됩니다.
서버는 선택된 어댑터·origin마다 작업 범위의 클라이언트를 하나 생성합니다. 이 클라이언트의 원격 권한은 서버의 origin 정책, 고정 DNS 디스패치, 사설 네트워크 설정, credential 허용 범위, 요청 제한을 사용합니다. 따라서 어댑터가 배포 환경의 외부 요청 정책을 우회할 수 없습니다.
브라우저 경계
브라우저 옵션이 제공되면 서버는 공개 라우트보다 먼저 /v1/browser/*를 마운트합니다. 이 경계는 브라우저 전용 백엔드 표면입니다.
- 브라우저 세션은 서명된 보안 쿠키에 보관됩니다.
- 상태를 변경하는 요청에는 설정된 CSRF 헤더가 필요합니다.
- 브라우저 라우트는
Authorization헤더와sessionId쿼리 매개변수를 거부합니다. - 서버는 브라우저 세션을 ActivityPlug 인증 세션으로 해석하고 HTTP· GraphQL과 동일한 API 서비스를 호출합니다.
- 수명이 짧고 한 번만 사용할 수 있는 티켓으로 브라우저 WebSocket 업그레이드를 승인하므로, ActivityPlug 세션 ID가 URL에 노출되지 않습니다.
브라우저 경계는 별도의 브라우저 세션과 임시 상태를 갖지만, 원격 credential에는 어댑터 인증 세션을 재사용합니다.
인증과 저장소
어댑터 인증 전략은 토큰 집합을 코어 인증 서비스에 반환합니다. 서비스는 이를 StoredAuthSession 레코드로 저장하고, 민감 정보를 제거한 AuthSession을 호출자에게 반환합니다. 변경 작업에 리비전 검사를 사용하므로, 동시에 실행된 검증·갱신·취소·소비 작업이 서로의 변경을 알리지 않고 덮어쓸 수 없습니다.
서버는 다음과 같은 여러 보안 상태 계약을 조정합니다.
- 인증 세션
- credential 임대와 OAuth 클라이언트 비밀
- 브라우저 세션
- OAuth 콜백 상태와 인증 challenge
- OAuth 시작 속도 제한
- 스트림 티켓
인메모리 구현은 프로세스에 한정됩니다. PostgreSQL·Redis 패키지는 세션 저장소에 설명된 프로덕션 저장소 계약을 지원합니다. SecurityStateLifecycle은 저장소를 초기화하고, 백엔드가 주기적 정리를 요구하면 만료 항목을 제거하며, 소유한 리소스를 닫습니다.
Capability와 탐지
정적 어댑터 메타데이터는 초기 호환성 계약입니다. 인스턴스 탐지는 NodeInfo, OAuth, 인스턴스 엔드포인트, 프로브 판정을 추가할 수 있습니다. Capability를 병합할 때 최종 판정의 출처, 이유, 선택적 제약 조건이 보존됩니다.
클라이언트와 서버 모두 공개 작업 경계에서 capability를 적용합니다. 어댑터 메서드가 존재하는 것만으로는 충분하지 않습니다. 감지된 소프트웨어나 버전에 의존하는 작업은 capability 판정에서 허용할 때까지 사용할 수 없습니다.
식별자와 페이지네이션 경계
어댑터는 원격 ID와 커서를 사용합니다. 공개 전송 계층은 ActivityPlug의 불투명 값을 사용합니다. 엔티티 ID와 페이지 커서는 서로 다른 변환 경계를 통과합니다.
- 클라이언트는 엔티티 ID의 어댑터, origin, 엔티티 타입을 검증한 뒤 디코딩된 원시 ID를 어댑터에 전달합니다.
- 원격 페이지네이션 계약은 어댑터가 소유하므로, 어댑터는 어댑터 ID, origin, 정확한 공개 작업을 포함해 페이지 커서를 인코딩·디코딩합니다.
- 어댑터 매핑은 결과가 어댑터 계층을 떠나기 전에 정규화된 엔티티 참조를 생성합니다.
이 설계로 호출자가 Mastodon ID를 Misskey 어댑터에 실수로 보내거나 다른 엔드포인트의 커서를 재사용하는 일을 방지합니다. 원시 식별자는 진단과 명시적 상호 운용을 위해 계속 확인할 수 있습니다.
스트리밍 흐름
스트림은 AsyncIterable<StreamEvent>를 반환합니다. 어댑터는 원격 WebSocket 프로토콜을 타임라인, 알림, 삭제, 편집, 필터 변경, heartbeat 이벤트로 변환합니다.
스트리밍 어댑터에는 주입된 WebSocketFactory가 필요합니다. 서버는 고정된 Node.js 구현을 제공합니다. 팩터리는 신뢰된 공개 작업과, 프로토콜이 허용하는 경우 인증 값을 받습니다. 공유 코어 유틸리티는 팩터리 시작 시간과 대기 중인 이벤트 수를 제한하며, 취소 후 늦게 도착한 소켓을 닫습니다.
확장 경계
새 서버 계열은 코어나 전송 계층에 원격 서비스별 조건문을 추가하지 말고 어댑터로 추가하십시오. 새 전송 표면은 ActivityPlugApiService 위에 구축해 동일한 정규화·capability 동작을 상속받도록 하십시오. 저장소 백엔드는 공개된 저장소 계약을 구현해 추가하고, 공개 세션을 통해 백엔드 핸들을 노출하지 마십시오.