LoginSignup
0
0

動的OGPもどきをAzure Storage Account で行ったメモ

Posted at

概要

Azureの静的ページを使ってSPAを公開している。
ユーザの個別ページに動的OGPを設定したいが、LambdaのEdgeFunctionsのようなものがないので、OGPの設定をしたページをStorageAccountに保存し、SPAにリダイレクトするように設定した。

ソースコード

ソースコード

OGP用のファイル作成

meta タグによるリダイレクトを採用している。
twitter-cardの設定を、Xに合わせたものにしている。

app/src/domain/fallMagia/services/persistent/saveCharacterTwitterCard.ts
const SERVICE_NAME = '魔法少女フォールマギア';

interface Args {
  url: string;
  title: string;
  description: string;
  src: string;
}
export const createCharacterTwitterCard = ({
  url,
  title,
  description,
  src,
}: Args) =>
  `
<!doctype html>
<html lang="ja">
  <head prefix="og: http://ogp.me/ns#">
    <meta charset="UTF-8" />
    <title>${title}${SERVICE_NAME}</title>
    <meta name="description" content="${description}" />
    <meta name="author" content="hibohiboo">
    <meta name="keywords" content="TRPG,${SERVICE_NAME},キャラクターシート" />
    <meta property="og:type" content="article" />
    <meta property="og:locale" content="ja_JP" />
    <meta property="og:site_name" content="${SERVICE_NAME}">
    <meta property="og:title" content="${title}${SERVICE_NAME}" />
    <meta property="og:description" content="${description}" />
    <meta property="og:image" content="${src}" />
    <meta property="og:url" content="${url}" />
    <meta name="x:card" content="summary_large_image" />
    <meta name="x:site" content="@hibohiboo" />
    <meta name="x:creator" content="@hibohiboo" />
    <meta name="x:image:alt" content="${title}${SERVICE_NAME}">
    <meta http-equiv="Refresh" content="0; URL=${url}" />
  </head>
  <body></body>
</html>
  `;

アップロード

app/src/domain/fallMagia/store/slices/fallMagiaCharacterPageSlice.ts
  const directory = `upload/${userPage.id}`;
  // ストレージアカウントにTwitterCardを保存
  await uploadToStorageAccount(
    new File(
      [
        new Blob(
          [
            createCharacterTwitterCard({
              url: `https://${location.hostname}/app/spa/userpage/${userPage.id}`,
              title: userPage.title,
              src: userPage.src,
              description: userPage.description,
            }),
          ],
          { type: 'text/html' },
        ),
      ],
      'twitter-card.html',
      { type: 'text/html' },
    ),
    'twitter-card.html',
    directory,
    {
      blobHTTPHeaders: {
        blobContentType: 'text/html',
      },
    },
  );
app/src/domain/storageAccount/uploadToStorageAccount.ts
import {
  BlockBlobClient,
  BlockBlobParallelUploadOptions,
} from '@azure/storage-blob';
import { postSASToken } from '../api/crud';
import { convertFileToArrayBuffer } from './convert-file-to-arraybuffer';

const containerName = `$web`;
const permission = 'w'; //write
const timerange = 5; //minutes
const minFileSize = 1; //bytes
const maxFileSize = 5 * 1024 * 1024; // 5M bytes
/**
 * Uploads a file to a storage account web container
 * @param file - The file to upload
 * @param fileName - The name of the file
 * @param directory - The directory to upload the file to
 */
export const uploadToStorageAccount = async (
  file: File,
  fileName: string,
  directory = 'uploaded',
  option?: BlockBlobParallelUploadOptions,
) => {
  const filePath = `${directory}/${fileName}`;
  const sasToken = await getSasTokenUrl(filePath);
  if (!sasToken) {
    console.warn('Failed to get sas token');
    return false;
  }
  try {
    const fileArrayBuffer = await convertFileToArrayBuffer(file as File);

    if (
      fileArrayBuffer === null ||
      fileArrayBuffer.byteLength < minFileSize ||
      fileArrayBuffer.byteLength > maxFileSize
    ) {
      console.warn('Failed to convert file to array buffer');
      return;
    }

    const blockBlobClient = new BlockBlobClient(sasToken);

    await blockBlobClient.uploadData(fileArrayBuffer, option);
    return true;
  } catch (error) {
    if (error instanceof Error) {
      const { message, stack } = error;

      console.warn(
        `Failed to finish upload with error : ${message} ${stack || ''}`,
      );
    } else {
      console.warn(error as string);
    }
    return false;
  }
};

async function getSasTokenUrl(filePath: string) {
  try {
    const response = await postSASToken(
      filePath,
      permission,
      containerName,
      timerange,
    );
    const data = await response.json();
    const { url } = data;
    return url;
  } catch (error) {
    console.warn(error);
    if (error instanceof Error) {
      const { message, stack } = error;
      console.warn(`Error getting sas token: ${message} ${stack || ''}`);
    } else {
      console.warn(error as string);
    }
  }
}
app/src/domain/api/crud.ts

const baseUrl = `${import.meta.env.VITE_API_SERVER ?? ''}/api`;
const apiBaseUrl = '/data-api/rest';
export const postSASToken = async (
  filePath: string,
  permission: string,
  containerName: string,
  timerange: number,
) => {
  const requestUrl = `${baseUrl}/credentials?file=${encodeURIComponent(
    filePath,
  )}&permission=${permission}&container=${containerName}&timerange=${timerange}`;
  const response = await fetch(requestUrl, {
    method: 'POST',
    mode: 'cors',
    headers: {
      'Content-Type': 'application/json',
    },
  });
  return response;
};

SASトークン取得のサーバ側の処理は静的 Web アプリから Azure Blob Storage に画像をアップロードするメモと同様。

参考

VueのSPAでの動的OGPをS3+Cloudfront+Lambda@Edgeの構成でcdkを使って実現したメモ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0