@@ -91,9 +91,7 @@ export function toJA4(bytes) {
9191 let alpn = "00" ;
9292 for ( const ext of tlsr . handshake . value . extensions . value ) {
9393 if ( ext . type . value === "application_layer_protocol_negotiation" ) {
94- alpn = parseFirstALPNValue ( ext . value . data ) ;
95- alpn = alpn . charAt ( 0 ) + alpn . charAt ( alpn . length - 1 ) ;
96- if ( alpn . charCodeAt ( 0 ) > 127 ) alpn = "99" ;
94+ alpn = alpnFingerprint ( parseFirstALPNValue ( ext . value . data ) ) ;
9795 break ;
9896 }
9997 }
@@ -212,9 +210,7 @@ export function toJA4S(bytes) {
212210 let alpn = "00" ;
213211 for ( const ext of tlsr . handshake . value . extensions . value ) {
214212 if ( ext . type . value === "application_layer_protocol_negotiation" ) {
215- alpn = parseFirstALPNValue ( ext . value . data ) ;
216- alpn = alpn . charAt ( 0 ) + alpn . charAt ( alpn . length - 1 ) ;
217- if ( alpn . charCodeAt ( 0 ) > 127 ) alpn = "99" ;
213+ alpn = alpnFingerprint ( parseFirstALPNValue ( ext . value . data ) ) ;
218214 break ;
219215 }
220216 }
@@ -262,3 +258,33 @@ function tlsVersionMapper(version) {
262258 default : return "00" ; // Unknown
263259 }
264260}
261+
262+ /**
263+ * Checks if a byte is ASCII alphanumeric (0-9, A-Z, a-z).
264+ * @param {number } byte
265+ * @returns {boolean }
266+ */
267+ function isAlphanumeric ( byte ) {
268+ return ( byte >= 0x30 && byte <= 0x39 ) ||
269+ ( byte >= 0x41 && byte <= 0x5A ) ||
270+ ( byte >= 0x61 && byte <= 0x7A ) ;
271+ }
272+
273+ /**
274+ * Computes the 2-character ALPN fingerprint from raw ALPN bytes.
275+ * If both first and last bytes are ASCII alphanumeric, returns their characters.
276+ * Otherwise, returns first hex digit of first byte + last hex digit of last byte.
277+ * @param {Uint8Array|null } rawBytes
278+ * @returns {string }
279+ */
280+ function alpnFingerprint ( rawBytes ) {
281+ if ( ! rawBytes || rawBytes . length === 0 ) return "00" ;
282+ const firstByte = rawBytes [ 0 ] ;
283+ const lastByte = rawBytes [ rawBytes . length - 1 ] ;
284+ if ( isAlphanumeric ( firstByte ) && isAlphanumeric ( lastByte ) ) {
285+ return String . fromCharCode ( firstByte ) + String . fromCharCode ( lastByte ) ;
286+ }
287+ const firstHex = firstByte . toString ( 16 ) . padStart ( 2 , "0" ) ;
288+ const lastHex = lastByte . toString ( 16 ) . padStart ( 2 , "0" ) ;
289+ return firstHex [ 0 ] + lastHex [ 1 ] ;
290+ }
0 commit comments