cloudFunctions上でpuppeteerのuploadFileを扱う


cloudFunctions上のpuppeteerで画像アップロードする際、ハマってしまったことがあるので備忘録です。

やりたいこと

①cloudFunctions内からpupeteerを動かす
②対象のファイルをinput type="file"に入れる

この流れです。
簡単そうに見えますが、ここでハマってしまいました…

ハマった原因

puppeteerで画像をuploadする際はuploadFileという関数が用意されています。
uploadFileは以下のように書くことができます。

// アップロードする対象を選択
const inputUploadHandle = await page.$('input[type=file]');
// アップロードするファイルのパスを選択
let fileToUpload = './upload_file.jpg';
// uploadFileでファイルをinputに入れる
inputUploadHandle.uploadFile(fileToUpload);

こちらは公式で以下のように説明があります。

...filePaths <...string> Sets the value of the file input to these paths. If some of the filePaths are relative paths, then they are resolved relative to the current working directory.

「filePathsの一部が相対パスの場合、現在の作業ディレクトリからの相対パスとして解決されます。」とあります。

cloudFunctionsでは静的ファイルをcloudStrageから取得することが推奨されているようで、その場合相対パスで取れなくないか?となりハマりました。。

やりたい流れとしては

①cloudFunctions内からpupeteerを動かす
②cloudStrageから対象のファイルを取得する
③取得したファイルを`input type="file"`に入れる

といった流れになりました。

試したこと① 絶対パスだとどうなる? (失敗)

では、uploadFile(path)の引数に絶対パスを入れることはできるだろうか?と思い、試してみました。

結論から言うとできませんでした
そもそもinput type="file"にファイルを入れる際、セキュリティ上の観点から元のファイルへの実際のパスが見えないようになっているようです。参照
おそらく絶対パスを入れても動かない理由は同じような観点からできないようになっていると考えました。

ちなみに、以下のようなコードで試してみると

const inputUploadHandle = await page.$('input[type=file]');
// 実際試す際は何らかのパスで
const filePath = 'https://~~~~~'
try{
  await inputUploadHandle.uploadFile(filePath);
}catch(e){
  console.log(e)
}

このようなエラーが出ます。

Error: https:~~~~~ does not exist or is not readable

試したこと② 相対パスで取るにはどのようにする? (解決策)

cloudFunctions内に一時保存できるtmpディレクトリなるものがあるとわかりました。

Cloud Functions では、「tmpfs」ボリュームと呼ばれるローカルのディスク マウント ポイント(/tmp)にアクセスできます。
参照

tmpディレクトリはcloudFunctions内から相対パスでたどることができるので、
上記の流れの項目を以下のように変更すればできそうです!

①cloudFunctions内からpupeteerを動かす
②cloudStrageから対象のファイルを取得する
③対象のファイルを/tmpに保存する
④/tmpに保存したファイルを`input type="file"`に入れる

実際個コードは以下の通りです。

  // ①cloudFunctions内からpupeteerを動かす
  const { Storage } = require('@google-cloud/storage');
  // :

  const tempFilePath = "/tmp/"

  //ファイルダウンロード用の関数
  const downloadFile = async (fileName) => {
    const storage = new Storage()
    // ②cloudStrageから対象のファイルを取得する
    const file = storage.bucket('bucket_name').file(`ファイルのパス`)
    await new Promise(function (resolve, reject) {
      //③対象のファイルを/tmpに保存する
      file.download({
        destination: tempFilePath + fileName
      }).then(() => {
        resolve("OK");
      });
    }).then((result) => {
      //ダウンロード完了
      console.log(result);
    })
  }


 // fileNameは動的に取得
 await downloadFile(fileName)
 //④ /tmpに保存したファイルを`input type="file"`に入れる
 await firstMenuImage.uploadFile(tempFilePath + fileName);

難しくない工程ですが、tmpディレクトリの存在など知らないことが多く時間がかかってしまいました。
もっといい解決方法などありましたらコメントいただけると幸いです!!