logo-Chiyu

Chiyu

Blog

Here I share my stories.

怎麼用 ES6 初始化 Firebase-Admin?以 Next.js + Vercel 為例。

最近跟 Firebase Admin SDK 惡戰苦鬥的故事。

🕒 published on ー 2022. 1. 15.

thumbnail.png

Background by OKUMONO

Free Talk

要先認清的幾件事:

  1. Firebase-Admin SDK 是用在 Server 端,Firebase SDK 是用在 Client 端,這就是它們的差別,詳細可看 Bringing Firebase To Your Server 這篇官方部落格。
  2. Next.js 裡 屬於 Server Side 的地方就要用 Admin,例如想在 getStaticProps() 裡跟 Firestore 溝通,就要用 Admin;反之,若在屬於 Client Side 的地方用到 Admin,就會獲得精美報錯。
  3. 承上,所以官方文件範例記得要看 Node.js 那個 Tab 的才對。
  4. 再承上,所以初始化,官方文件的初始化教學是看 Add Firebase to a server 這篇。
  5. 官方文件最好還是看英文的,中文版是它自家 Google 雲翻譯機翻的,雖然說不上翻錯但我看了血壓是有點上升 w 它把 Firebase 本人翻成 火力堡、Node.js 變 節點.js、token 變 令牌

邊犯蠢、邊踩坑才知道這些事情的就是我,認為很值得筆記起來 LOL, 對了,接下來並不會逐字解說官方文件,主是記下我踩到的坑 & 我的做法而已。

為什麼我要用 ES6 語法來初始化 Firebase-Admin?

2021/10/14 的 Firebase Admin Node.js SDK Release Notes 寫說 Admin 自 v10.0.0 開始也改用 ES6 Module 了, 以往的舊版寫法在未來某個時間點將會被淘汰掉,剛好最近我在家玩 Next.js 時想導入 Admin 來用,距離我上次跟 Firebase 打交道已時隔兩年,覺得那就索性忘掉過去(其實早就忘了#),向新版寫法看齊吧。

以上,沒什麼大不了的原因 w 就一個遲早要升版的感覺。

坑之 1:有關 Google Application Credentials

Google Application Credentials 其實就是一個 JSON 檔,內容寫著 API Key 之類初始化需要用到的機密資料。 但按照官方文件的初始化教學, 也就是我在 Free Talk 第四點貼的 Add Firebase to a server 這篇, 它叫大家把這個 JSON 檔存在自己電腦某處,然後寫上該檔案存放的路徑來初始化。

由於我之後打算把 Next.js 專案部署到 Vercel 上,必須告訴 Vercel 這些機密資料才行,那…該怎麼辦? 官方文件沒教到這個,未來應該不會也沒義務教,但總不是叫 Vercel 通靈、觀落硬碟找到我的 JSON 檔吧(X),只好還是去估狗了舊版寫法,從中推敲。

// 這是舊版寫法唷,在我撰文的當下(2022-01-15)還是可以用的啦。
import * as firebaseAdmin from 'firebase-admin';

if (!firebaseAdmin.apps.length) {
  const adminCredentials = {
    credential: firebaseAdmin.credential.cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.PRIVATE_KEY),
    }),
    databaseURL: env.process.DATABASE_URL,
  };
  firebaseAdmin.initializeApp(adminCredentials)
};

export default firebaseAdmin

我觀察到:

  1. 應該能把 Google Application Credential 那個 JSON 檔裡寫的秘密,放進環境變數裡,且不需要每個項目都用上。
  2. 用 Firestore 不需給 Database Url;用 Realtime Database 才要給。 看官方文件有一句 ★ Note 寫說 Database Url 並非初始化必要的項目,加上 Firestore 的主控台頁面到處找不到寫網址的地方,但 Realtime Database 的主控台頁面就找得到這個網址,就能瞭解了。
  3. 舊版寫法大家是用 apps.length 來檢查有無重複初始化。

坑之 2:啊咧,那用新版寫法要怎麼檢查是否重複初始化?

正所謂坑坑相扣(?),官方文件也沒有寫到這點,但我推測 apps.length 的這個 apps,從前就是個在 Module 裡的東西,現在應該也還是,既然如此就應該有辦法叫出來用吧!

果不其然,在 firebase-admin/app module 這篇介紹 App Moudule 的官方文件裡, 可以發現 App Module 裡面有 getApps()getApp() 這兩個 Function,前者回傳的就是 apps、後者回傳 APP 本人, 知道有這兩個 Function 後,總算可以來填坑了。

兩個坑一起填了!

經過上述的一番研究,初始化 Firebase-Admin 的 ES6 新版寫法就此誕生:

import { initializeApp, getApps, getApp, cert } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';

const serviceAccount = {
  type: process.env.FIREBASE_TYPE,
  project_id: process.env.FIREBASE_PROJECT_ID,
  private_key_id: process.env.FIREBASE_PRIVATE_KEY_ID,
  private_key: process.env.FIREBASE_PRIVATE_KEY,
  client_email: process.env.FIREBASE_CLIENT_EMAIL,
  client_id: process.env.FIREBASE_CLIENT_ID,
  auth_uri: process.env.FIREBASE_AUTH_URI,
  token_uri: process.env.FIREBASE_TOKEN_URI,
  auth_provider_x509_cert_url: process.env.FIREBASE_AUTH_PROVIDER_X509_CERT_URL,
  client_x509_cert_url: process.env.FIREBASE_CLIENT_X509_CERT_URL,
};

const firebaseAdmin =
  getApps().length === 0
    ? initializeApp({
        credential: cert(serviceAccount),
      })
    : getApp();

const db = getFirestore(firebaseAdmin);

export default db;

簡短解說,分幾個步驟:

  1. 我先在 Next.js 專案裡的 .local.env 檔裡先設好環境變數,要用的東西 import 起來備用
  2. 製作 serviceAccount 物件,要給幫忙初始化的 initializeApp()cert() 吃的,Google Application Credential 那個 JSON 檔的內容,我還是每個項目都餵了, 因為我經歷了一陣報錯風暴,Try 到最後成功的版本就是寫這樣,然後就不想再改了 XD 理論上只用 Firestore 的話應該跟舊版一樣只餵 projectIdclientEmailprivateKey 就好啦。
  3. 初始化跟寫一些判斷:如果 apps 裡面已經有東西,那就用清單裡的那個 APP,否則初始化一個。
  4. 召喚我的 Firestore 出來放進一個叫 db 的變數裡,然後 export 它,讓我能在有需要的地方使用。

大致是這樣,我可是…花了兩天才搞定呀(我就爛.jpg)。

坑之 3:Invalid PEM formatted message

一坑未平一坑又起啊,這個錯誤是我在填上面兩個坑時冒出來的,這個錯誤訊息主要是告訴人說 Private Key 的格式不對,所以我可以肯定上面那段初始化 Code 是 OK 的,有問題的是環境變數或是 Private Key 本人!

但…我 Private Key 是從 Google Application Cre dential 那個 JSON 檔複製貼上的,環境變數寫法我也確定沒錯呀,怎麼會這樣哩? 話又說回來,那 Private Key 無敵爆長一串的,還有一堆換行符號 \n 在裡面(這是要人真的換行),開頭結尾還有註解 -----BEGIN PRIVATR KEY----------END PRIVATE KEY-----(這也是 Private Key 的一部分不能刪),484 在擾民。

估狗發現還真不少人踩到這個坑,幸好問得人多,解答的人也就多,歸類查到的 3 種解法:

  1. 給環境變數加雙/單引號,例如:FIREBASE_PRIVATE_KEY="………",因為 dotenv 或 Next.js 本身也內建有會處理環境變數的東西,看到引號就會知道「喔好我自動幫你換行」,不會傻傻把 \n 當成字串。 參考:規則不公開狀態下控制 firestore
  2. 環境變數不加任何引號照常寫,使用時尾巴加寫個 replace() 把換行符號轉成真的換行,例如:const private_key = process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n')。 參考:Node.js -Firebase Service Account Private Key won’t parse
  3. 把 Private Key 轉成 base64,甚至把 Google Application Credential 那整個 JSON 檔都轉成 base64,吃我的 base64 啦! 參考:Accessing Google Firestore on Vercel

我先試了解法 1、2,一下通通不行,一下 Local 行、部署到 Vercel 時不行!只好試了解法 3,結果 Vercel 不接受環境變數裡塞了 base64 這麼長的字串 w 到底該怎辦!不然環境變數刪了重寫,再打開 Google Application Credential 那個 JSON 檔複製貼上一次看看好了?

然後,就都好了…。就都沒有問題了!Why!我不能接受,非得找出原因。

坑之 4:謎底揭曉

經過一番調查,找到了這篇文章:Firebase (Server-Side),畫面拉到最底下有一句:

Note the extra new line at the end.

…原來,Private Key 最後 -----END PRIVATE KEY----- 後面還有一個 \n,其實是 -----END PRIVATE KEY-----\n, 不曉得何時誤把最後那個 \n 給刪了!加上很多文章範例最後也都是虛號結尾,就沒多想,難怪什麼解法都沒用。 想必寫那篇文章的人也犯過跟我一樣的蠢吧(別牽拖#) 最後回去用解法 1 ,不管是在 Loacal 還是 Vercel 總算都給我 Success,1 天又平安度過。

然而我總計花了 3 天才完全搞定!

Ending

那麼以上故事結束,希望寫下來或多或少能幫到人吧。 補充個,在 Vercel 潮潮的網站上用 GUI 設定環境變數的時候, 直接複製貼上不用多做任何動作就行,可參考它官方文件 Environment Variables,那麼就這樣。

Copyright © 2022 Chiyu