Azure SAS TOken storage in netlify edge

please help!

Working via azure portal


not working via my code
https://y2drag.blob.core.windows.net/y2drag/irj4xgf3.png?sp=r&st=2025-05-19T21:50:50Z&se=2025-05-20T05:50:50Z&spr=https&sv=2024-11-04&sr=b&sig=hwAlXGH6g%2FHeq2s6oJEB9EHnBozKo6kb3fmNh7j63Uk%3D

what do i do wrong??

import { HTMLRewriter } from “https://raw.githubusercontent.com/worker-tools/html-rewriter/master/index.ts”;

const accountName = Deno.env.get(“REACT_APP_AZURE_ACCOUNT_NAME”)?.toLowerCase();
const containerName = Deno.env
.get(“REACT_APP_AZURE_CONTAINER_NAME”)
?.toLowerCase();
const accountKey = Deno.env.get(“REACT_APP_AZURE_ACCOUNT_KEY”);

// Utility functions
function base64ToUint8Array(base64) {
const binaryStr = atob(base64);
const bytes = new Uint8Array(binaryStr.length);
for (let i = 0; i < binaryStr.length; i++) {
bytes[i] = binaryStr.charCodeAt(i);
}
return bytes;
}

function stringToUint8Array(str) {
return new TextEncoder().encode(str);
}

function base64Encode(bytes) {
let binary = “”;
bytes.forEach((b) => (binary += String.fromCharCode(b)));
return btoa(binary);
}

async function getSignature(keyBase64, stringToSign) {
const key = base64ToUint8Array(keyBase64);
const msg = stringToUint8Array(stringToSign);
const cryptoKey = await crypto.subtle.importKey(
“raw”,
key,
{ name: “HMAC”, hash: “SHA-256” },
false,
[“sign”]
);
const sigBuffer = await crypto.subtle.sign(“HMAC”, cryptoKey, msg);
return base64Encode(new Uint8Array(sigBuffer));
}
function formatAzureTime(date) {
// Format: YYYY-MM-DDTHH:MM:SSZ (no milliseconds)
return date.toISOString().replace(/.\d{3}Z$/, “Z”);
}

async function generateBlobSasToken(
accountName,
accountKey,
containerName,
blobName
) {
const now = new Date();

// Adjust start and expiry times
const startTime = new Date(now.getTime() - 53 * 60 * 1000); // 53 minutes ago
const expiryTime = new Date(startTime.getTime() + 8 * 60 * 60 * 1000); // 8 hours later

const st = formatAzureTime(startTime);
const se = formatAzureTime(expiryTime);

const canonicalizedResource = /blob/${accountName}/${containerName}/${blobName};
const stringToSign = [
“r”, // signed permissions
st, // start time
se, // expiry time
canonicalizedResource,
“”, // signed identifier
“”, // signed IP
“https”, // signed protocol
“2024-11-04”, // signed version
“b”, // signed resource — this MUST be here
“”,
“”,
“”,
“”,
“”,
“”,
“”,
].join(“\n”);

const signature = await getSignature(accountKey, stringToSign);

const token = [
sp=r,
st=${st},
se=${se},
spr=https,
sv=2024-11-04,
sr=b,
sig=${encodeURIComponent(signature)},
].join(“&”);

return {
token,
stringToSign,
signature,
};
}

const fallbackImageUrl = “https://i.ibb.co/J35J7Jy/y2dragbanner.png”;

class MetaRewriter {
constructor(imageUrl) {
this.imageUrl = imageUrl;
this.found = false;
}
element(element) {
if (element.getAttribute(“property”) === “og:image”) {
element.setAttribute(“content”, this.imageUrl);
this.found = true;
}
}
}

class HeadRewriter {
constructor(metaRewriter, debugInfo) {
this.metaRewriter = metaRewriter;
this.debugInfo = debugInfo;
}
element(element) {
if (!this.metaRewriter.found) {
element.append(
<meta property="og:image" content="${this.metaRewriter.imageUrl}">,
{ html: true }
);
}
element.append(\n<!-- DEBUG START -->\n, { html: true });
element.append(<!-- ACCOUNT: ${this.debugInfo.accountName} -->\n, {
html: true,
});
element.append(<!-- CONTAINER: ${this.debugInfo.containerName} -->\n, {
html: true,
});
element.append(<!-- BLOB: ${this.debugInfo.blobName} -->\n, {
html: true,
});
element.append(<!-- FULL URL: ${this.debugInfo.candidateUrl} -->\n, {
html: true,
});
element.append(<!-- SAS TOKEN: ${this.debugInfo.sasToken} -->\n, {
html: true,
});
element.append(
<!-- STRING TO SIGN: ${this.debugInfo.stringToSign.replace( /\n/g, "\\n" )} -->\n,
{ html: true }
);
element.append(<!-- DEBUG END -->\n, { html: true });
}
}

export default async (request, context) => {
const url = new URL(request.url);
const pathname = url.pathname;
const queryString = pathname.replace(/^/+/, “”);

let imageUrl = fallbackImageUrl;
let candidateUrl = null;
let sasDetails = null;
let headStatus = “not requested”;
let errorMsg = “”;

if (
accountName &&
containerName &&
accountKey &&
queryString &&
!queryString.includes(“.”)
) {
const blobName = ${queryString}.png;
try {
sasDetails = await generateBlobSasToken(
accountName,
accountKey,
containerName,
blobName
);
candidateUrl = https://${accountName}.blob.core.windows.net/${containerName}/${encodeURIComponent( blobName )}?${sasDetails.token};

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 5000);

  const headResponse = await fetch(candidateUrl, {
    method: "HEAD",
    signal: controller.signal,
  });

  clearTimeout(timeout);
  headStatus = headResponse.status;

  if (headResponse.ok) {
    imageUrl = candidateUrl;
  }
} catch (e) {
  errorMsg = e.message || String(e);
}

}

const response = await context.next();
if (!response.headers.get(“content-type”)?.includes(“text/html”)) {
return response;
}

const debugInfo = {
accountName,
containerName,
blobName: queryString.includes(“.”) ? “invalid” : ${queryString}.png,
candidateUrl,
sasToken: sasDetails?.token || “none”,
stringToSign: sasDetails?.stringToSign || “none”,
headStatus,
errorMsg: errorMsg || “none”,
};

const metaRewriter = new MetaRewriter(imageUrl);
return new HTMLRewriter()
.on(“meta[property=‘og:image’]”, metaRewriter)
.on(“head”, new HeadRewriter(metaRewriter, debugInfo))
.transform(response);
};

export const config = {
path: “/*”,
};

Sorry, you’ve not provided any meaningful description about your issue. Simply posting all your code does not explain what issue you’re having. Please explain in detail about the issue you’re facing or the question you have.

import { HTMLRewriter } from "https://raw.githubusercontent.com/worker-tools/html-rewriter/master/index.ts";

const accountName = Deno.env.get("REACT_APP_AZURE_ACCOUNT_NAME")?.toLowerCase();
const containerName = Deno.env
  .get("REACT_APP_AZURE_CONTAINER_NAME")
  ?.toLowerCase();
const accountKey = Deno.env.get("REACT_APP_AZURE_ACCOUNT_KEY");

const fallbackImageUrl =
  "yourwebsite_image_url.png";

// Utility functions
function base64ToUint8Array(base64) {
  const binaryStr = atob(base64);
  const bytes = new Uint8Array(binaryStr.length);
  for (let i = 0; i < binaryStr.length; i++) {
    bytes[i] = binaryStr.charCodeAt(i);
  }
  return bytes;
}

function stringToUint8Array(str) {
  return new TextEncoder().encode(str);
}

function base64Encode(bytes) {
  let binary = "";
  bytes.forEach((b) => (binary += String.fromCharCode(b)));
  return btoa(binary);
}

async function getSignature(keyBase64, stringToSign) {
  const key = base64ToUint8Array(keyBase64);
  const msg = stringToUint8Array(stringToSign);
  const cryptoKey = await crypto.subtle.importKey(
    "raw",
    key,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  const sigBuffer = await crypto.subtle.sign("HMAC", cryptoKey, msg);
  return base64Encode(new Uint8Array(sigBuffer));
}
function formatAzureTime(date) {
  // Format: YYYY-MM-DDTHH:MM:SSZ (no milliseconds)
  return date.toISOString().replace(/\.\d{3}Z$/, "Z");
}

async function generateBlobSasToken(
  accountName,
  accountKey,
  containerName,
  blobName
) {
  const now = new Date();

  // Adjust start and expiry times
  const startTime = new Date(now.getTime() - 53 * 60 * 1000); // 53 minutes ago
  const expiryTime = new Date(startTime.getTime() + 8 * 60 * 60 * 1000); // 8 hours later

  const st = formatAzureTime(startTime);
  const se = formatAzureTime(expiryTime);

  const canonicalizedResource = `/blob/${accountName}/${containerName}/${blobName}`;
  const stringToSign = [
    "r", // signed permissions
    st, // start time
    se, // expiry time
    canonicalizedResource,
    "", // signed identifier
    "", // signed IP
    "https", // signed protocol
    "2024-11-04", // signed version
    "b", // signed resource — this MUST be here
    "",
    "",
    "",
    "",
    "",
    "",
    "",
  ].join("\n");

  const signature = await getSignature(accountKey, stringToSign);

  const token = [
    `sp=r`,
    `st=${st}`,
    `se=${se}`,
    `spr=https`,
    `sv=2024-11-04`,
    `sr=b`,
    `sig=${encodeURIComponent(signature)}`,
  ].join("&");

  return {
    token,
    stringToSign,
    signature,
  };
}

class MetaRewriter {
  constructor(imageUrl) {
    this.imageUrl = imageUrl;
    this.found = false;
  }
  element(element) {
    if (element.getAttribute("property") === "og:image") {
      element.setAttribute("content", this.imageUrl);
      this.found = true;
    }
  }
}

class HeadRewriter {
  constructor(metaRewriter, debugInfo) {
    this.metaRewriter = metaRewriter;
    this.debugInfo = debugInfo;
  }
  element(element) {
    // if (!this.metaRewriter.found) {
    //   element.append(
    //     `<meta property="og:image" content="${this.metaRewriter.imageUrl}">`,
    //     { html: true }
    //   );
    // }
    // element.append(`\n<!-- DEBUG START -->\n`, { html: true });
    // element.append(`<!-- ACCOUNT: ${this.debugInfo.accountName} -->\n`, {
    //   html: true,
    // });
    // element.append(`<!-- CONTAINER: ${this.debugInfo.containerName} -->\n`, {
    //   html: true,
    // });
    // element.append(`<!-- BLOB: ${this.debugInfo.blobName} -->\n`, {
    //   html: true,
    // });
    // element.append(`<!-- FULL URL: ${this.debugInfo.candidateUrl} -->\n`, {
    //   html: true,
    // });
    // element.append(`<!-- SAS TOKEN: ${this.debugInfo.sasToken} -->\n`, {
    //   html: true,
    // });
    // element.append(
    //   `<!-- STRING TO SIGN: ${this.debugInfo.stringToSign.replace(
    //     /\n/g,
    //     "\\n"
    //   )} -->\n`,
    //   { html: true }
    // );
    // element.append(`<!-- DEBUG END -->\n`, { html: true });
  }
}

export default async (request, context) => {
  const url = new URL(request.url);
  const pathname = url.pathname;
  const queryString = pathname.replace(/^\/+/, "");

  let imageUrl = fallbackImageUrl;
  let candidateUrl = null;
  let sasDetails = null;
  let headStatus = "not requested";
  let errorMsg = "";

  if (
    accountName &&
    containerName &&
    accountKey &&
    queryString &&
    !queryString.includes(".")
  ) {
    const blobName = `${queryString}.png`;
    try {
      sasDetails = await generateBlobSasToken(
        accountName,
        accountKey,
        containerName,
        blobName
      );
      candidateUrl = `https://${accountName}.blob.core.windows.net/${containerName}/${encodeURIComponent(
        blobName
      )}?${sasDetails.token}`;

      const controller = new AbortController();
      const timeout = setTimeout(() => controller.abort(), 5000);

      const headResponse = await fetch(candidateUrl, {
        method: "HEAD",
        signal: controller.signal,
      });

      clearTimeout(timeout);
      headStatus = headResponse.status;

      if (headResponse.ok) {
        imageUrl = candidateUrl;
      }
    } catch (e) {
      errorMsg = e.message || String(e);
    }
  }

  const response = await context.next();
  if (!response.headers.get("content-type")?.includes("text/html")) {
    return response;
  }

  const debugInfo = {
    accountName,
    containerName,
    blobName: queryString.includes(".") ? "invalid" : `${queryString}.png`,
    candidateUrl,
    sasToken: sasDetails?.token || "none",
    stringToSign: sasDetails?.stringToSign || "none",
    headStatus,
    errorMsg: errorMsg || "none",
  };

  const metaRewriter = new MetaRewriter(imageUrl);
  return new HTMLRewriter()
    .on("meta[property='og:image']", metaRewriter)
    .on("head", new HeadRewriter(metaRewriter, debugInfo))
    .transform(response);
};

export const config = {
  path: "/*",
};

This is a working solution how to fetch dynamic meta-image on netlify before website loads enabling to use multiple meta/image for subdomains such as custom blog images, etc.

image is generated for me during the creation process and saved in a private bucket, so when the website link is shared this private image is loaded and updates the html meta-image tag