|
|
@@ -45,6 +45,11 @@ type AzureConfig = {
|
|
|
containerName: string,
|
|
|
}
|
|
|
|
|
|
+// Cache holders to avoid repeated instantiation of credential and clients
|
|
|
+let cachedCredential: { key: string, credential: TokenCredential } | null = null;
|
|
|
+let cachedBlobServiceClient: { key: string, client: BlobServiceClient } | null = null;
|
|
|
+let cachedContainerClient: { key: string, client: ContainerClient } | null = null;
|
|
|
+
|
|
|
|
|
|
function getAzureConfig(): AzureConfig {
|
|
|
const accountName = configManager.getConfig('azure:storageAccountName');
|
|
|
@@ -61,6 +66,7 @@ function getAzureConfig(): AzureConfig {
|
|
|
}
|
|
|
|
|
|
function getCredential(): TokenCredential {
|
|
|
+ // Build cache key from credential-related configs
|
|
|
const tenantId = toNonBlankStringOrUndefined(configManager.getConfig('azure:tenantId'));
|
|
|
const clientId = toNonBlankStringOrUndefined(configManager.getConfig('azure:clientId'));
|
|
|
const clientSecret = toNonBlankStringOrUndefined(configManager.getConfig('azure:clientSecret'));
|
|
|
@@ -69,13 +75,52 @@ function getCredential(): TokenCredential {
|
|
|
throw new Error(`Azure Blob Storage missing required configuration: tenantId=${tenantId}, clientId=${clientId}, clientSecret=${clientSecret}`);
|
|
|
}
|
|
|
|
|
|
- return new ClientSecretCredential(tenantId, clientId, clientSecret);
|
|
|
+ const key = `${tenantId}|${clientId}|${clientSecret}`;
|
|
|
+
|
|
|
+ // Reuse cached credential when config has not changed
|
|
|
+ if (cachedCredential != null && cachedCredential.key === key) {
|
|
|
+ return cachedCredential.credential;
|
|
|
+ }
|
|
|
+
|
|
|
+ const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
|
|
|
+ cachedCredential = { key, credential };
|
|
|
+ return credential;
|
|
|
+}
|
|
|
+
|
|
|
+function getBlobServiceClient(): BlobServiceClient {
|
|
|
+ const { accountName } = getAzureConfig();
|
|
|
+ // Include credential cache key to ensure we re-create if cred changed
|
|
|
+ const credential = getCredential();
|
|
|
+ const credentialKey = (cachedCredential?.key) ?? 'unknown-cred';
|
|
|
+ const key = `${accountName}|${credentialKey}`;
|
|
|
+
|
|
|
+ if (cachedBlobServiceClient != null && cachedBlobServiceClient.key === key) {
|
|
|
+ return cachedBlobServiceClient.client;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Use keep-alive to minimize socket churn; reuse client across calls
|
|
|
+ const client = new BlobServiceClient(
|
|
|
+ `https://${accountName}.blob.core.windows.net`,
|
|
|
+ credential,
|
|
|
+ { keepAliveOptions: { enable: true } },
|
|
|
+ );
|
|
|
+ cachedBlobServiceClient = { key, client };
|
|
|
+ return client;
|
|
|
}
|
|
|
|
|
|
async function getContainerClient(): Promise<ContainerClient> {
|
|
|
const { accountName, containerName } = getAzureConfig();
|
|
|
- const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net`, getCredential());
|
|
|
- return blobServiceClient.getContainerClient(containerName);
|
|
|
+ const credentialKey = (cachedCredential?.key) ?? 'unknown-cred';
|
|
|
+ const key = `${accountName}|${containerName}|${credentialKey}`;
|
|
|
+
|
|
|
+ if (cachedContainerClient != null && cachedContainerClient.key === key) {
|
|
|
+ return cachedContainerClient.client;
|
|
|
+ }
|
|
|
+
|
|
|
+ const blobServiceClient = getBlobServiceClient();
|
|
|
+ const client = blobServiceClient.getContainerClient(containerName);
|
|
|
+ cachedContainerClient = { key, client };
|
|
|
+ return client;
|
|
|
}
|
|
|
|
|
|
function getFilePathOnStorage(attachment: IAttachmentDocument) {
|
|
|
@@ -221,7 +266,8 @@ class AzureFileUploader extends AbstractFileUploader {
|
|
|
|
|
|
const sasToken = await (async() => {
|
|
|
const { accountName, containerName } = getAzureConfig();
|
|
|
- const blobServiceClient = new BlobServiceClient(`https://${accountName}.blob.core.windows.net`, getCredential());
|
|
|
+ // Reuse the same BlobServiceClient (singleton)
|
|
|
+ const blobServiceClient = getBlobServiceClient();
|
|
|
|
|
|
const now = Date.now();
|
|
|
const startsOn = new Date(now - 30 * 1000);
|