@@ -104,6 +104,14 @@ export function isMatchingPattern(value: string, pattern: RegExp | string): bool
104
104
return false ;
105
105
}
106
106
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
+
107
115
/**
108
116
* Convert a Unicode string to a base64 string.
109
117
*
@@ -112,42 +120,49 @@ export function isMatchingPattern(value: string, pattern: RegExp | string): bool
112
120
* @returns A base64-encoded version of the string
113
121
*/
114
122
export 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 > ( ) ;
122
124
123
125
// To account for the fact that different platforms use different character encodings natively, our `tracestate`
124
126
// spec calls for all jsonified data to be encoded in UTF-8 bytes before being passed to the base64 encoder.
125
127
try {
128
+ if ( typeof plaintext !== 'string' ) {
129
+ throw new Error ( `Input must be a string. Received input of type '${ typeof plaintext } '.` ) ;
130
+ }
131
+
126
132
// browser
127
- if ( 'btoa' in global ) {
133
+ if ( 'btoa' in globalObject ) {
128
134
// encode using UTF-8
129
135
const bytes = new TextEncoder ( ) . encode ( plaintext ) ;
130
136
131
137
// decode using UTF-16 (JS's native encoding) since `btoa` requires string input
132
138
const bytesAsString = String . fromCharCode ( ...bytes ) ;
133
139
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 ) ;
135
143
}
136
144
137
145
// Node
138
- if ( 'Buffer' in global ) {
146
+ if ( 'Buffer' in globalObject ) {
139
147
// 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' ) ;
141
151
142
152
// unlike the browser, Node can go straight from bytes to base64
143
153
return bytes . toString ( 'base64' ) ;
144
154
}
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.' ) ;
145
158
} 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
+ } .`;
146
164
throw new SentryError ( `${ errMsg } \nGot error: ${ err } ` ) ;
147
165
}
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 ) ;
151
166
}
152
167
153
168
/**
@@ -158,22 +173,22 @@ export function unicodeToBase64(plaintext: string): string {
158
173
* @returns A Unicode string
159
174
*/
160
175
export 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 > ( ) ;
168
177
169
178
// To account for the fact that different platforms use different character encodings natively, our `tracestate` spec
170
179
// calls for all jsonified data to be encoded in UTF-8 bytes before being passed to the base64 encoder. So to reverse
171
180
// the process, decode from base64 to bytes, then feed those bytes to a UTF-8 decoder.
172
181
try {
182
+ if ( typeof base64String !== 'string' ) {
183
+ throw new Error ( `Input must be a string. Received input of type '${ typeof base64String } '.` ) ;
184
+ }
185
+
173
186
// browser
174
187
if ( 'atob' in globalObject ) {
175
188
// `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 ) ;
177
192
const bytes = [ ...bytesAsString ] . map ( char => char . charCodeAt ( 0 ) ) ;
178
193
179
194
// 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 {
183
198
// Node
184
199
if ( 'Buffer' in globalObject ) {
185
200
// 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' ) ;
187
204
188
205
// decode using UTF-8
189
206
return bytes . toString ( 'utf-8' ) ;
190
207
}
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.' ) ;
191
211
} 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
+ } .`;
192
217
throw new SentryError ( `${ errMsg } \nGot error: ${ err } ` ) ;
193
218
}
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 ) ;
197
219
}
0 commit comments