@@ -20,6 +20,7 @@ import { createRelayCacheAdapter } from './relay_cache_adapter.ts';
2020
2121export type JsonPrimitive = string | number | boolean | null ;
2222export type JsonValue = JsonPrimitive | JsonValue [ ] | { [ key : string ] : JsonValue } ;
23+ export type IssueSourceOfTruth = 'last_write' | 'github' | 'bit' ;
2324
2425type JsonObject = { [ key : string ] : JsonValue } ;
2526type KeyStatus = 'active' | 'revoked' ;
@@ -180,6 +181,8 @@ export interface IdentitySnapshot {
180181
181182export interface MemoryRelayOptions {
182183 authToken ?: string ;
184+ peerAuthToken ?: string ;
185+ issueSourceOfTruth ?: IssueSourceOfTruth ;
183186 maxMessagesPerRoom ?: number ;
184187 publishPayloadMaxBytes ?: number ;
185188 publishLimitPerWindow ?: number ;
@@ -237,6 +240,7 @@ const DEFAULT_WS_PING_INTERVAL_MS = 30_000;
237240const DEFAULT_WS_IDLE_TIMEOUT_MS = 90_000 ;
238241const DEFAULT_CACHE_EXCHANGE_MAX_HOPS = 3 ;
239242const DEFAULT_CACHE_EXCHANGE_MAX_RECORDS = 10_000 ;
243+ const DEFAULT_ISSUE_SOURCE_OF_TRUTH : IssueSourceOfTruth = 'last_write' ;
240244const MAX_GITHUB_WEBHOOK_DELIVERY_IDS = 10_000 ;
241245const MAX_GITHUB_WEBHOOK_DLQ_ENTRIES = 10_000 ;
242246const GITHUB_WEBHOOK_RETRY_BASE_SEC = 30 ;
@@ -1100,6 +1104,7 @@ function parseBoolOrDefault(raw: boolean | undefined, fallback: boolean): boolea
11001104
11011105export function createMemoryRelayService ( options : MemoryRelayOptions = { } ) : MemoryRelayService {
11021106 const authToken = normalizeAuthToken ( options . authToken ) ;
1107+ const peerAuthToken = normalizeAuthToken ( options . peerAuthToken ) ;
11031108 const roomTokens = parseRoomTokens ( options . roomTokens ) ;
11041109 const maxMessagesPerRoom = Math . max (
11051110 1 ,
@@ -1165,6 +1170,10 @@ export function createMemoryRelayService(options: MemoryRelayOptions = {}): Memo
11651170 const cacheStore = options . cacheStore ?? null ;
11661171 const githubWebhookSecret = ( options . githubWebhookSecret ?? '' ) . trim ( ) ;
11671172 const fetchFn = options . fetchFn ?? globalThis . fetch ;
1173+ const issueSourceOfTruth = options . issueSourceOfTruth === 'github' ||
1174+ options . issueSourceOfTruth === 'bit'
1175+ ? options . issueSourceOfTruth
1176+ : DEFAULT_ISSUE_SOURCE_OF_TRUTH ;
11681177 const cacheAdapter = createRelayCacheAdapter ( {
11691178 cacheStore,
11701179 isValidRoomName,
@@ -1173,6 +1182,7 @@ export function createMemoryRelayService(options: MemoryRelayOptions = {}): Memo
11731182 cachePersistMaxRetries : options . cachePersistMaxRetries ,
11741183 cachePersistRetryBaseDelayMs : options . cachePersistRetryBaseDelayMs ,
11751184 cachePersistRetryMaxDelayMs : options . cachePersistRetryMaxDelayMs ,
1185+ issueSourceOfTruth,
11761186 } ) ;
11771187
11781188 const rooms = new Map < string , RoomState > ( ) ;
@@ -1386,10 +1396,21 @@ export function createMemoryRelayService(options: MemoryRelayOptions = {}): Memo
13861396 return null ;
13871397 }
13881398
1399+ function checkPeerAuth ( request : Request ) : Response | null {
1400+ if ( peerAuthToken . length === 0 ) return null ;
1401+ const presented = extractPresentedToken ( request ) ;
1402+ if ( presented . length === 0 || ! timingSafeEqual ( presented , peerAuthToken ) ) {
1403+ return unauthorizedResponse ( ) ;
1404+ }
1405+ return null ;
1406+ }
1407+
13891408 function handleCacheExchangeDiscovery ( request : Request ) : Response {
13901409 if ( request . method !== 'GET' ) {
13911410 return methodNotAllowedResponse ( ) ;
13921411 }
1412+ const peerAuthError = checkPeerAuth ( request ) ;
1413+ if ( peerAuthError ) return peerAuthError ;
13931414 return Response . json ( {
13941415 ok : true ,
13951416 protocol : 'cache.exchange.v1' ,
@@ -1409,6 +1430,8 @@ export function createMemoryRelayService(options: MemoryRelayOptions = {}): Memo
14091430 if ( request . method !== 'GET' ) {
14101431 return methodNotAllowedResponse ( ) ;
14111432 }
1433+ const peerAuthError = checkPeerAuth ( request ) ;
1434+ if ( peerAuthError ) return peerAuthError ;
14121435 const after = normalizeAfter ( url . searchParams . get ( 'after' ) , 0 ) ;
14131436 const limit = normalizeLimit ( url . searchParams . get ( 'limit' ) , 100 ) ;
14141437 const peerRaw = ( url . searchParams . get ( 'peer' ) ?? '' ) . trim ( ) ;
@@ -1447,6 +1470,8 @@ export function createMemoryRelayService(options: MemoryRelayOptions = {}): Memo
14471470 if ( request . method !== 'POST' ) {
14481471 return methodNotAllowedResponse ( ) ;
14491472 }
1473+ const peerAuthError = checkPeerAuth ( request ) ;
1474+ if ( peerAuthError ) return peerAuthError ;
14501475
14511476 let parsed : unknown ;
14521477 try {
@@ -1569,6 +1594,8 @@ export function createMemoryRelayService(options: MemoryRelayOptions = {}): Memo
15691594 if ( request . method !== 'GET' ) {
15701595 return methodNotAllowedResponse ( ) ;
15711596 }
1597+ const peerAuthError = checkPeerAuth ( request ) ;
1598+ if ( peerAuthError ) return peerAuthError ;
15721599 const room = normalizeRoom ( url . searchParams . get ( 'room' ) ) ;
15731600 if ( ! isValidRoomName ( room ) ) {
15741601 return invalidRoomResponse ( ) ;
@@ -1607,6 +1634,8 @@ export function createMemoryRelayService(options: MemoryRelayOptions = {}): Memo
16071634 if ( request . method !== 'GET' ) {
16081635 return methodNotAllowedResponse ( ) ;
16091636 }
1637+ const peerAuthError = checkPeerAuth ( request ) ;
1638+ if ( peerAuthError ) return peerAuthError ;
16101639 const room = normalizeRoom ( url . searchParams . get ( 'room' ) ) ;
16111640 if ( ! isValidRoomName ( room ) ) {
16121641 return invalidRoomResponse ( ) ;
0 commit comments