@@ -57,23 +57,76 @@ export function getKeyFingerprint(key?: v2.PublicKey): string | undefined {
5757 return bytesToHex ( key . key ) . substring ( 0 , 16 ) ;
5858}
5959
60+ /** Every `Content.contentBody` variant that carries a payload — i.e. each
61+ * `oneofKind` except the empty `undefined` case. */
62+ export type ContentKind = Exclude <
63+ v2 . Content [ 'contentBody' ] [ 'oneofKind' ] ,
64+ undefined
65+ > ;
66+
67+ /** The payload a given content kind carries. `ContentBodyOf<'follow'>` is
68+ * `v2.Follow`, `ContentBodyOf<'post'>` is `v2.Post`, and so on. Inferred
69+ * via a mapped type rather than `[K]` indexing, which TS rejects for a
70+ * generic key over a discriminated union. */
71+ export type ContentBodyOf < K extends ContentKind > =
72+ Extract < v2 . Content [ 'contentBody' ] , { oneofKind : K } > extends {
73+ [ P in K ] : infer T ;
74+ }
75+ ? T
76+ : never ;
77+
78+ /** A bundle decoded against a specific content kind: the parsed event, the
79+ * content narrowed to that kind's payload type, and the bundle's raw
80+ * signed event (validated to be present). */
81+ export type DecodedBundle < K extends ContentKind > = {
82+ event : v2 . Event ;
83+ content : ContentBodyOf < K > ;
84+ signedEvent : v2 . SignedEvent ;
85+ } ;
86+
87+ /**
88+ * Decode an `EventBundle` as a specific content kind, replacing the
89+ * per-kind `decodeReaction` / `decodeFollow` / `decodePost` helpers.
90+ *
91+ * Returns `null` when the bundle is malformed or carries a different kind.
92+ * The `kind` argument both filters and types the result: the returned
93+ * `content` is narrowed to that kind's payload, so `decodeBundle(b, 'follow')`
94+ * yields a `v2.Follow` and `decodeBundle(b, 'post')` a `v2.Post`.
95+ */
96+ export function decodeBundle < K extends ContentKind > (
97+ bundle : v2 . EventBundle ,
98+ kind : K ,
99+ ) : DecodedBundle < K > | null {
100+ if ( ! bundle . signedEvent || ! bundle . serializedContent ?. contentBytes ) {
101+ return null ;
102+ }
103+ let parsed : v2 . Content ;
104+ let event : v2 . Event ;
105+ try {
106+ parsed = v2 . Content . fromBinary ( bundle . serializedContent . contentBytes ) ;
107+ event = v2 . Event . fromBinary ( bundle . signedEvent . eventBytes ) ;
108+ } catch {
109+ return null ;
110+ }
111+ if ( parsed . contentBody . oneofKind !== kind ) return null ;
112+ // Safe: the oneofKind check above guarantees `kind` is the live payload
113+ // key. TS can't index a discriminated union by a generic key, so cast.
114+ const content = (
115+ parsed . contentBody as unknown as Record < K , ContentBodyOf < K > >
116+ ) [ kind ] ;
117+ return { event, content, signedEvent : bundle . signedEvent } ;
118+ }
119+
60120/** Decode a v2 EventBundle into PostData, or null if not a post. */
61121export function decodePostBundle ( bundle : v2 . EventBundle ) : PostData | null {
122+ const decoded = decodeBundle ( bundle , 'post' ) ;
123+ if ( ! decoded ) return null ;
62124 try {
63- if ( ! bundle . signedEvent ) return null ;
64- const event = v2 . Event . fromBinary ( bundle . signedEvent . eventBytes ) ;
125+ const { event, content : post , signedEvent } = decoded ;
65126 const key = event . key ;
66127 if ( ! key ?. signedBy ?. key ) return null ;
67128
68- if ( ! bundle . serializedContent ?. contentBytes ) return null ;
69- const content = v2 . Content . fromBinary (
70- bundle . serializedContent . contentBytes ,
71- ) ;
72- if ( content . contentBody . oneofKind !== 'post' ) return null ;
73-
74129 const id = bytesToHex ( v2 . EventKey . toBinary ( key ) ) ;
75-
76- const post = content . contentBody . post ;
77130 const reply = post . reply
78131 ? {
79132 rootId : post . reply . root
@@ -99,8 +152,8 @@ export function decodePostBundle(bundle: v2.EventBundle): PostData | null {
99152 reply,
100153 quoteId,
101154 signedEvent : v2 . SignedEvent . create ( {
102- eventBytes : bundle . signedEvent . eventBytes ,
103- signature : bundle . signedEvent . signature ,
155+ eventBytes : signedEvent . eventBytes ,
156+ signature : signedEvent . signature ,
104157 } ) ,
105158 } ;
106159 } catch ( e ) {
@@ -116,17 +169,12 @@ function decodeRepostBundle(bundle: v2.EventBundle): {
116169 targetId : string ;
117170 repostId : string ;
118171} | null {
172+ const decoded = decodeBundle ( bundle , 'repost' ) ;
173+ if ( ! decoded ) return null ;
119174 try {
120- if ( ! bundle . signedEvent ) return null ;
121- const event = v2 . Event . fromBinary ( bundle . signedEvent . eventBytes ) ;
122- const key = event . key ;
175+ const key = decoded . event . key ;
123176 if ( ! key ?. signedBy ?. key ) return null ;
124- if ( ! bundle . serializedContent ?. contentBytes ) return null ;
125- const content = v2 . Content . fromBinary (
126- bundle . serializedContent . contentBytes ,
127- ) ;
128- if ( content . contentBody . oneofKind !== 'repost' ) return null ;
129- const target = content . contentBody . repost . post ;
177+ const target = decoded . content . post ;
130178 if ( ! target ) return null ;
131179 return {
132180 repostedBy : key . identity ,
0 commit comments