@@ -104,6 +104,14 @@ export function isMatchingPattern(value: string, pattern: RegExp | string): bool
104104 return false ;
105105}
106106
107+ type GlobalWithBase64Helpers = {
108+ // browser
109+ atob ?: ( base64String : string ) => string ;
110+ btoa ?: ( utf8String : string ) => string ;
111+ // Node
112+ Buffer ?: { from : ( input : string , encoding : string ) => { toString : ( encoding : string ) => string } } ;
113+ } ;
114+
107115/**
108116 * Convert a Unicode string to a base64 string.
109117 *
@@ -112,42 +120,49 @@ export function isMatchingPattern(value: string, pattern: RegExp | string): bool
112120 * @returns A base64-encoded version of the string
113121 */
114122export function unicodeToBase64 ( plaintext : string ) : string {
115- const global = getGlobalObject ( ) ;
116-
117- // Cast to a string just in case we're given something else
118- const stringifiedInput = String ( plaintext ) ;
119- const errMsg = `Unable to convert to base64: ${
120- stringifiedInput . length > 256 ? `${ stringifiedInput . slice ( 0 , 256 ) } ...` : stringifiedInput
121- } `;
123+ const globalObject = getGlobalObject < GlobalWithBase64Helpers > ( ) ;
122124
123125 // To account for the fact that different platforms use different character encodings natively, our `tracestate`
124126 // spec calls for all jsonified data to be encoded in UTF-8 bytes before being passed to the base64 encoder.
125127 try {
128+ if ( typeof plaintext !== 'string' ) {
129+ throw new Error ( `Input must be a string. Received input of type '${ typeof plaintext } '.` ) ;
130+ }
131+
126132 // browser
127- if ( 'btoa' in global ) {
133+ if ( 'btoa' in globalObject ) {
128134 // encode using UTF-8
129135 const bytes = new TextEncoder ( ) . encode ( plaintext ) ;
130136
131137 // decode using UTF-16 (JS's native encoding) since `btoa` requires string input
132138 const bytesAsString = String . fromCharCode ( ...bytes ) ;
133139
134- return btoa ( bytesAsString ) ;
140+ // TODO: if TS ever learns about "in", we can get rid of the non-null assertion
141+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
142+ return globalObject . btoa ! ( bytesAsString ) ;
135143 }
136144
137145 // Node
138- if ( 'Buffer' in global ) {
146+ if ( 'Buffer' in globalObject ) {
139147 // encode using UTF-8
140- const bytes = Buffer . from ( plaintext , 'utf-8' ) ;
148+ // TODO: if TS ever learns about "in", we can get rid of the non-null assertion
149+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
150+ const bytes = globalObject . Buffer ! . from ( plaintext , 'utf-8' ) ;
141151
142152 // unlike the browser, Node can go straight from bytes to base64
143153 return bytes . toString ( 'base64' ) ;
144154 }
155+
156+ // we shouldn't ever get here, because one of `btoa` and `Buffer` should exist, but just in case...
157+ throw new SentryError ( 'Neither `window.btoa` nor `global.Buffer` is defined.' ) ;
145158 } catch ( err ) {
159+ // Cast to a string just in case we're given something else
160+ const stringifiedInput = JSON . stringify ( plaintext ) ;
161+ const errMsg = `Unable to convert to base64: ${
162+ stringifiedInput ?. length > 256 ? `${ stringifiedInput . slice ( 0 , 256 ) } ...` : stringifiedInput
163+ } .`;
146164 throw new SentryError ( `${ errMsg } \nGot error: ${ err } ` ) ;
147165 }
148-
149- // we shouldn't ever get here, because one of `btoa` and `Buffer` should exist, but just in case...
150- throw new SentryError ( errMsg ) ;
151166}
152167
153168/**
@@ -158,22 +173,22 @@ export function unicodeToBase64(plaintext: string): string {
158173 * @returns A Unicode string
159174 */
160175export function base64ToUnicode ( base64String : string ) : string {
161- const globalObject = getGlobalObject ( ) ;
162-
163- // we cast to a string just in case we're given something else
164- const stringifiedInput = String ( base64String ) ;
165- const errMsg = `Unable to convert from base64: ${
166- stringifiedInput . length > 256 ? `${ stringifiedInput . slice ( 0 , 256 ) } ...` : stringifiedInput
167- } `;
176+ const globalObject = getGlobalObject < GlobalWithBase64Helpers > ( ) ;
168177
169178 // To account for the fact that different platforms use different character encodings natively, our `tracestate` spec
170179 // calls for all jsonified data to be encoded in UTF-8 bytes before being passed to the base64 encoder. So to reverse
171180 // the process, decode from base64 to bytes, then feed those bytes to a UTF-8 decoder.
172181 try {
182+ if ( typeof base64String !== 'string' ) {
183+ throw new Error ( `Input must be a string. Received input of type '${ typeof base64String } '.` ) ;
184+ }
185+
173186 // browser
174187 if ( 'atob' in globalObject ) {
175188 // `atob` returns a string rather than bytes, so we first need to encode using the native encoding (UTF-16)
176- const bytesAsString = atob ( base64String ) ;
189+ // TODO: if TS ever learns about "in", we can get rid of the non-null assertion
190+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
191+ const bytesAsString = globalObject . atob ! ( base64String ) ;
177192 const bytes = [ ...bytesAsString ] . map ( char => char . charCodeAt ( 0 ) ) ;
178193
179194 // decode using UTF-8 (cast the `bytes` arry to a Uint8Array just because that's the format `decode()` expects)
@@ -183,15 +198,22 @@ export function base64ToUnicode(base64String: string): string {
183198 // Node
184199 if ( 'Buffer' in globalObject ) {
185200 // unlike the browser, Node can go straight from base64 to bytes
186- const bytes = Buffer . from ( base64String , 'base64' ) ;
201+ // TODO: if TS ever learns about "in", we can get rid of the non-null assertion
202+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
203+ const bytes = globalObject . Buffer ! . from ( base64String , 'base64' ) ;
187204
188205 // decode using UTF-8
189206 return bytes . toString ( 'utf-8' ) ;
190207 }
208+
209+ // we shouldn't ever get here, because one of `atob` and `Buffer` should exist, but just in case...
210+ throw new SentryError ( 'Neither `window.atob` nor `global.Buffer` is defined.' ) ;
191211 } catch ( err ) {
212+ // we cast to a string just in case we're given something else
213+ const stringifiedInput = JSON . stringify ( base64String ) ;
214+ const errMsg = `Unable to convert from base64: ${
215+ stringifiedInput ?. length > 256 ? `${ stringifiedInput . slice ( 0 , 256 ) } ...` : stringifiedInput
216+ } .`;
192217 throw new SentryError ( `${ errMsg } \nGot error: ${ err } ` ) ;
193218 }
194-
195- // we shouldn't ever get here, because one of `atob` and `Buffer` should exist, but just in case...
196- throw new SentryError ( errMsg ) ;
197219}
0 commit comments