adapters.ts raw
1 /**
2 * Adapter functions for gradual migration from legacy code to Relay domain objects.
3 */
4
5 import { Event } from 'nostr-tools'
6 import { RelayUrl, tryToRelayUrl } from '../shared'
7 import { RelayList, RelayScope } from './RelayList'
8 import { RelaySet } from './RelaySet'
9 import { FavoriteRelays } from './FavoriteRelays'
10
11 // ============================================================================
12 // RelayList Adapters
13 // ============================================================================
14
15 /**
16 * Convert a Nostr event to a RelayList domain object
17 */
18 export const toRelayList = (event: Event, filterOutOnion = false): RelayList => {
19 return RelayList.fromEvent(event, filterOutOnion)
20 }
21
22 /**
23 * Try to create a RelayList from an event, returns null if invalid
24 */
25 export const tryToRelayList = (
26 event: Event | null | undefined,
27 filterOutOnion = false
28 ): RelayList | null => {
29 if (!event) return null
30 try {
31 return RelayList.fromEvent(event, filterOutOnion)
32 } catch {
33 return null
34 }
35 }
36
37 /**
38 * Convert a RelayList to the legacy TRelayList format
39 */
40 export const fromRelayListToLegacy = (
41 relayList: RelayList
42 ): {
43 read: string[]
44 write: string[]
45 originalRelays: Array<{ url: string; scope: RelayScope }>
46 } => {
47 return relayList.toLegacyFormat()
48 }
49
50 /**
51 * Create a RelayList from legacy format
52 */
53 export const toRelayListFromLegacy = (
54 ownerHex: string,
55 legacy: {
56 read: string[]
57 write: string[]
58 originalRelays?: Array<{ url: string; scope: RelayScope }>
59 }
60 ): RelayList | null => {
61 const { Pubkey } = require('../shared')
62 const owner = Pubkey.tryFromString(ownerHex)
63 if (!owner) return null
64
65 const relayList = RelayList.empty(owner)
66
67 if (legacy.originalRelays) {
68 for (const { url, scope } of legacy.originalRelays) {
69 relayList.setRelayUrl(url, scope)
70 }
71 } else {
72 // Reconstruct from read/write arrays
73 const readSet = new Set(legacy.read)
74 const writeSet = new Set(legacy.write)
75 const allUrls = new Set([...legacy.read, ...legacy.write])
76
77 for (const url of allUrls) {
78 const isRead = readSet.has(url)
79 const isWrite = writeSet.has(url)
80 let scope: RelayScope = 'both'
81 if (isRead && !isWrite) scope = 'read'
82 else if (!isRead && isWrite) scope = 'write'
83
84 relayList.setRelayUrl(url, scope)
85 }
86 }
87
88 return relayList
89 }
90
91 // ============================================================================
92 // RelaySet Adapters
93 // ============================================================================
94
95 /**
96 * Convert a Nostr event to a RelaySet domain object
97 */
98 export const toRelaySet = (event: Event): RelaySet => {
99 return RelaySet.fromEvent(event)
100 }
101
102 /**
103 * Try to create a RelaySet from an event, returns null if invalid
104 */
105 export const tryToRelaySet = (event: Event | null | undefined): RelaySet | null => {
106 if (!event) return null
107 try {
108 return RelaySet.fromEvent(event)
109 } catch {
110 return null
111 }
112 }
113
114 /**
115 * Convert a RelaySet to the legacy TRelaySet format
116 */
117 export const fromRelaySetToLegacy = (
118 relaySet: RelaySet,
119 pubkey: string
120 ): {
121 id: string
122 aTag: string[]
123 name: string
124 relayUrls: string[]
125 } => {
126 return {
127 id: relaySet.id,
128 aTag: relaySet.toATag(pubkey),
129 name: relaySet.name,
130 relayUrls: relaySet.getRelayUrls()
131 }
132 }
133
134 /**
135 * Create a RelaySet from legacy format
136 */
137 export const toRelaySetFromLegacy = (legacy: {
138 id: string
139 name: string
140 relayUrls: string[]
141 }): RelaySet => {
142 return RelaySet.createWithRelays(legacy.name, legacy.relayUrls, legacy.id)
143 }
144
145 // ============================================================================
146 // FavoriteRelays Adapters
147 // ============================================================================
148
149 /**
150 * Convert events to a FavoriteRelays domain object
151 */
152 export const toFavoriteRelays = (
153 event: Event,
154 relaySetEvents: Event[] = []
155 ): FavoriteRelays => {
156 const relaySets = relaySetEvents.map((e) => tryToRelaySet(e)).filter(Boolean) as RelaySet[]
157 return FavoriteRelays.fromEvent(event, relaySets)
158 }
159
160 /**
161 * Try to create FavoriteRelays from an event
162 */
163 export const tryToFavoriteRelays = (
164 event: Event | null | undefined,
165 relaySetEvents: Event[] = []
166 ): FavoriteRelays | null => {
167 if (!event) return null
168 try {
169 return toFavoriteRelays(event, relaySetEvents)
170 } catch {
171 return null
172 }
173 }
174
175 // ============================================================================
176 // Utility Adapters
177 // ============================================================================
178
179 /**
180 * Convert an array of URL strings to RelayUrl objects
181 * Skips invalid URLs
182 */
183 export const urlsToRelayUrls = (urls: string[]): RelayUrl[] => {
184 return urls.map((u) => tryToRelayUrl(u)).filter((r): r is RelayUrl => r !== null)
185 }
186
187 /**
188 * Convert an array of RelayUrl objects to URL strings
189 */
190 export const relayUrlsToStrings = (relays: RelayUrl[]): string[] => {
191 return relays.map((r) => r.value)
192 }
193
194 /**
195 * Normalize a relay URL string
196 * Returns null if invalid
197 */
198 export const normalizeRelayUrl = (url: string): string | null => {
199 const relay = tryToRelayUrl(url)
200 return relay ? relay.value : null
201 }
202
203 /**
204 * Check if a string is a valid WebSocket URL
205 */
206 export const isValidRelayUrl = (url: string): boolean => {
207 return RelayUrl.isValid(url)
208 }
209