@@ -18,6 +18,52 @@ const logger = createLogger('BlobClient')
1818
1919let _blobServiceClient : BlobServiceClientInstance | null = null
2020
21+ interface ParsedCredentials {
22+ accountName : string
23+ accountKey : string
24+ }
25+
26+ /**
27+ * Extract account name and key from an Azure connection string.
28+ * Connection strings have the format: DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=...
29+ */
30+ function parseConnectionString ( connectionString : string ) : ParsedCredentials {
31+ const accountNameMatch = connectionString . match ( / A c c o u n t N a m e = ( [ ^ ; ] + ) / )
32+ if ( ! accountNameMatch ) {
33+ throw new Error ( 'Cannot extract account name from connection string' )
34+ }
35+
36+ const accountKeyMatch = connectionString . match ( / A c c o u n t K e y = ( [ ^ ; ] + ) / )
37+ if ( ! accountKeyMatch ) {
38+ throw new Error ( 'Cannot extract account key from connection string' )
39+ }
40+
41+ return {
42+ accountName : accountNameMatch [ 1 ] ,
43+ accountKey : accountKeyMatch [ 1 ] ,
44+ }
45+ }
46+
47+ /**
48+ * Get account credentials from BLOB_CONFIG, extracting from connection string if necessary.
49+ */
50+ function getAccountCredentials ( ) : ParsedCredentials {
51+ if ( BLOB_CONFIG . accountName && BLOB_CONFIG . accountKey ) {
52+ return {
53+ accountName : BLOB_CONFIG . accountName ,
54+ accountKey : BLOB_CONFIG . accountKey ,
55+ }
56+ }
57+
58+ if ( BLOB_CONFIG . connectionString ) {
59+ return parseConnectionString ( BLOB_CONFIG . connectionString )
60+ }
61+
62+ throw new Error (
63+ 'Azure Blob Storage credentials are missing – set AZURE_CONNECTION_STRING or both AZURE_ACCOUNT_NAME and AZURE_ACCOUNT_KEY'
64+ )
65+ }
66+
2167export async function getBlobServiceClient ( ) : Promise < BlobServiceClientInstance > {
2268 if ( _blobServiceClient ) return _blobServiceClient
2369
@@ -127,6 +173,8 @@ export async function getPresignedUrl(key: string, expiresIn = 3600) {
127173 const containerClient = blobServiceClient . getContainerClient ( BLOB_CONFIG . containerName )
128174 const blockBlobClient = containerClient . getBlockBlobClient ( key )
129175
176+ const { accountName, accountKey } = getAccountCredentials ( )
177+
130178 const sasOptions = {
131179 containerName : BLOB_CONFIG . containerName ,
132180 blobName : key ,
@@ -137,13 +185,7 @@ export async function getPresignedUrl(key: string, expiresIn = 3600) {
137185
138186 const sasToken = generateBlobSASQueryParameters (
139187 sasOptions ,
140- new StorageSharedKeyCredential (
141- BLOB_CONFIG . accountName ,
142- BLOB_CONFIG . accountKey ??
143- ( ( ) => {
144- throw new Error ( 'AZURE_ACCOUNT_KEY is required when using account name authentication' )
145- } ) ( )
146- )
188+ new StorageSharedKeyCredential ( accountName , accountKey )
147189 ) . toString ( )
148190
149191 return `${ blockBlobClient . url } ?${ sasToken } `
@@ -168,9 +210,14 @@ export async function getPresignedUrlWithConfig(
168210 StorageSharedKeyCredential,
169211 } = await import ( '@azure/storage-blob' )
170212 let tempBlobServiceClient : BlobServiceClientInstance
213+ let accountName : string
214+ let accountKey : string
171215
172216 if ( customConfig . connectionString ) {
173217 tempBlobServiceClient = BlobServiceClient . fromConnectionString ( customConfig . connectionString )
218+ const credentials = parseConnectionString ( customConfig . connectionString )
219+ accountName = credentials . accountName
220+ accountKey = credentials . accountKey
174221 } else if ( customConfig . accountName && customConfig . accountKey ) {
175222 const sharedKeyCredential = new StorageSharedKeyCredential (
176223 customConfig . accountName ,
@@ -180,6 +227,8 @@ export async function getPresignedUrlWithConfig(
180227 `https://${ customConfig . accountName } .blob.core.windows.net` ,
181228 sharedKeyCredential
182229 )
230+ accountName = customConfig . accountName
231+ accountKey = customConfig . accountKey
183232 } else {
184233 throw new Error (
185234 'Custom blob config must include either connectionString or accountName + accountKey'
@@ -199,13 +248,7 @@ export async function getPresignedUrlWithConfig(
199248
200249 const sasToken = generateBlobSASQueryParameters (
201250 sasOptions ,
202- new StorageSharedKeyCredential (
203- customConfig . accountName ,
204- customConfig . accountKey ??
205- ( ( ) => {
206- throw new Error ( 'Account key is required when using account name authentication' )
207- } ) ( )
208- )
251+ new StorageSharedKeyCredential ( accountName , accountKey )
209252 ) . toString ( )
210253
211254 return `${ blockBlobClient . url } ?${ sasToken } `
@@ -403,13 +446,9 @@ export async function getMultipartPartUrls(
403446 if ( customConfig ) {
404447 if ( customConfig . connectionString ) {
405448 blobServiceClient = BlobServiceClient . fromConnectionString ( customConfig . connectionString )
406- const match = customConfig . connectionString . match ( / A c c o u n t N a m e = ( [ ^ ; ] + ) / )
407- if ( ! match ) throw new Error ( 'Cannot extract account name from connection string' )
408- accountName = match [ 1 ]
409-
410- const keyMatch = customConfig . connectionString . match ( / A c c o u n t K e y = ( [ ^ ; ] + ) / )
411- if ( ! keyMatch ) throw new Error ( 'Cannot extract account key from connection string' )
412- accountKey = keyMatch [ 1 ]
449+ const credentials = parseConnectionString ( customConfig . connectionString )
450+ accountName = credentials . accountName
451+ accountKey = credentials . accountKey
413452 } else if ( customConfig . accountName && customConfig . accountKey ) {
414453 const credential = new StorageSharedKeyCredential (
415454 customConfig . accountName ,
@@ -428,12 +467,9 @@ export async function getMultipartPartUrls(
428467 } else {
429468 blobServiceClient = await getBlobServiceClient ( )
430469 containerName = BLOB_CONFIG . containerName
431- accountName = BLOB_CONFIG . accountName
432- accountKey =
433- BLOB_CONFIG . accountKey ||
434- ( ( ) => {
435- throw new Error ( 'AZURE_ACCOUNT_KEY is required' )
436- } ) ( )
470+ const credentials = getAccountCredentials ( )
471+ accountName = credentials . accountName
472+ accountKey = credentials . accountKey
437473 }
438474
439475 const containerClient = blobServiceClient . getContainerClient ( containerName )
@@ -501,12 +537,10 @@ export async function completeMultipartUpload(
501537 const containerClient = blobServiceClient . getContainerClient ( containerName )
502538 const blockBlobClient = containerClient . getBlockBlobClient ( key )
503539
504- // Sort parts by part number and extract block IDs
505540 const sortedBlockIds = parts
506541 . sort ( ( a , b ) => a . partNumber - b . partNumber )
507542 . map ( ( part ) => part . blockId )
508543
509- // Commit the block list to create the final blob
510544 await blockBlobClient . commitBlockList ( sortedBlockIds , {
511545 metadata : {
512546 multipartUpload : 'completed' ,
@@ -557,10 +591,8 @@ export async function abortMultipartUpload(key: string, customConfig?: BlobConfi
557591 const blockBlobClient = containerClient . getBlockBlobClient ( key )
558592
559593 try {
560- // Delete the blob if it exists (this also cleans up any uncommitted blocks)
561594 await blockBlobClient . deleteIfExists ( )
562595 } catch ( error ) {
563- // Ignore errors since we're just cleaning up
564596 logger . warn ( 'Error cleaning up multipart upload:' , error )
565597 }
566598}
0 commit comments