@@ -13,7 +13,7 @@ import type { WebhookUpdateData } from './resource_clients/webhook';
1313const NOT_FOUND_STATUS_CODE = 404 ;
1414const RECORD_NOT_FOUND_TYPE = 'record-not-found' ;
1515const RECORD_OR_TOKEN_NOT_FOUND_TYPE = 'record-or-token-not-found' ;
16- const MIN_GZIP_BYTES = 1024 ;
16+ const MIN_COMPRESS_BYTES = 1024 ;
1717
1818/**
1919 * Generic interface for objects that may contain a data property.
@@ -112,28 +112,65 @@ export function stringifyWebhooksToBase64(webhooks: WebhookUpdateData[]): string
112112let gzipPromisified : ( ( arg : string | Buffer < ArrayBufferLike > ) => Promise < Buffer > ) | undefined ;
113113
114114/**
115- * Gzip provided value, otherwise returns undefined .
115+ * Gzip-compress the provided value.
116116 */
117- export async function maybeGzipValue ( value : unknown ) : Promise < Buffer | undefined > {
118- if ( ! isNode ( ) ) return ;
119- if ( typeof value !== 'string' && ! Buffer . isBuffer ( value ) ) return ;
117+ async function gzipValue ( value : string | Buffer < ArrayBufferLike > ) : Promise < Buffer > {
118+ if ( ! gzipPromisified ) {
119+ const { promisify } = await import ( 'node:util' ) ;
120+ const { gzip } = await import ( 'node:zlib' ) ;
121+ gzipPromisified = promisify ( gzip ) ;
122+ }
120123
121- // Request compression is not that important so let's
122- // skip it instead of throwing for unsupported types.
123- const areDataLargeEnough = Buffer . byteLength ( value as string ) >= MIN_GZIP_BYTES ;
124- if ( areDataLargeEnough ) {
125- if ( ! gzipPromisified ) {
126- const { promisify } = await import ( 'node:util' ) ;
127- const { gzip } = await import ( 'node:zlib' ) ;
128- gzipPromisified = promisify ( gzip ) ;
129- }
124+ return gzipPromisified ( value ) ;
125+ }
126+
127+ // null = confirmed unavailable; undefined = not yet checked
128+ let brotliCompressPromisified : ( ( arg : string | Buffer < ArrayBufferLike > ) => Promise < Buffer > ) | null | undefined ;
130129
131- return gzipPromisified ( value ) ;
130+ /**
131+ * Brotli-compress the provided value, or return undefined if brotli is unavailable
132+ * (Node.js < v10.16.0), this is a strict defensive guard.
133+ */
134+ async function maybeBrotliValue ( value : string | Buffer < ArrayBufferLike > ) : Promise < Buffer | undefined > {
135+ if ( brotliCompressPromisified === undefined ) {
136+ const { promisify } = await import ( 'node:util' ) ;
137+ const { brotliCompress } = await import ( 'node:zlib' ) ;
138+ brotliCompressPromisified = typeof brotliCompress === 'function' ? promisify ( brotliCompress ) : null ;
139+ }
140+
141+ if ( brotliCompressPromisified !== null ) {
142+ return brotliCompressPromisified ( value ) ;
132143 }
133144
134145 return undefined ;
135146}
136147
148+ export interface CompressedValue {
149+ data : Buffer ;
150+ encoding : 'br' | 'gzip' ;
151+ }
152+
153+ /**
154+ * Compress the passed value using brotli if available or using gzip as a fallback. Returns undefined
155+ * if the data is too small / wrong type.
156+ */
157+ export async function maybeCompressValue ( value : unknown ) : Promise < CompressedValue | undefined > {
158+ if ( ! isNode ( ) ) return undefined ;
159+
160+ // Request compression is not that important so let's
161+ // skip it instead of throwing for unsupported types.
162+ if ( typeof value !== 'string' && ! Buffer . isBuffer ( value ) ) return undefined ;
163+
164+ const areDataLargeEnough = Buffer . byteLength ( value ) >= MIN_COMPRESS_BYTES ;
165+ if ( ! areDataLargeEnough ) return undefined ;
166+
167+ const brotli = await maybeBrotliValue ( value ) ;
168+ if ( brotli ) return { data : brotli , encoding : 'br' } ;
169+
170+ const gzipped = await gzipValue ( value ) ;
171+ return { data : gzipped , encoding : 'gzip' } ;
172+ }
173+
137174/**
138175 * Helper function slice the items from array to fit the max byte length.
139176 */
0 commit comments