📲 第十一章:LIFF SDK 初始化與 Line 隱形登入機制
在前面的章節,我們講完了純網頁的排版與部署。但在現代的商業應用中,最讓老闆與客戶頭痛的就是「使用者註冊轉化率」。 如果一個打卡系統,每次都要員工輸入「帳號、密碼」才能打卡,員工一定會抱怨連連,甚至每天忘記密碼。
為了解決這個問題,台灣市佔率最高的 Line 提供了一個終極核武器:LIFF (LINE Front-end Framework)。 只要把你的網站掛載在 Line 的聊天室裡面點開,Line 就會「偷偷把這個使用者的 Line ID 和大頭貼」傳送給你的網站。 使用者完全不需要輸入任何字,就能瞬間完成登入!
這堂課,我們不談理論,直接剖析 真實營運中的打卡系統 Source Code。 我們將教你如何用 Vibe Coding 的方式,指揮 AI 幫你寫出這個極度複雜、充滿坑洞的初始化與驗證流程!
🔑 實戰 1:在 React 中引入並初始化 LIFF SDK
LIFF 不是一個可以用 npm install 隨便裝一裝就沒事的套件。
因為它牽涉到跟 Line APP 內建瀏覽器的底層溝通,它的初始化時間(liff.init())是非同步的,而且如果初始化失敗,整個網頁都會崩潰。
💡 Vibe Prompt 實戰 1:打造安全的 LIFF 初始化 Hook
我們不能把初始化的邏輯隨便寫在某個按鈕裡面。我們需要一個全域的自訂 Hook (useLiff),讓整個應用程式隨時可以知道:「Line 準備好了嗎?」。
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
我正在開發一個 Line LIFF 網頁應用程式 (React)。請幫我寫一個自訂 Hook:useLiff()。需求如下:1. 使用 @line/liff 套件。2. 狀態管理:isReady (預設 false)、liffError (預設 null)、profile (使用者的 Line 資料,預設 null)。3. 在 useEffect 中執行 liff.init({ liffId: '你的LIFF_ID' })。4. 如果初始化成功,檢查 liff.isLoggedIn()。如果已登入,使用 liff.getProfile() 取得使用者大頭貼與名稱,並存入 profile 狀態。5. 如果未登入,呼叫 liff.login() 強制登入。請給我完整的程式碼,並在 catch 區塊加上詳細的防呆錯誤處理。
🤖 AI 生成的企業級 LIFF 初始化 Hook:
import { useState, useEffect } from 'react';
import liff from '@line/liff';
export function useLiff() {
const [isReady, setIsReady] = useState(false);
const [liffError, setLiffError] = useState(null);
const [profile, setProfile] = useState(null);
useEffect(() => {
// 定義一個非同步函數來執行初始化
const initLiff = async () => {
try {
console.log("⏳ 開始初始化 LIFF SDK...");
// 1. 初始化 SDK (請務必將 LIFF_ID 放在環境變數中)
await liff.init({ liffId: import.meta.env.VITE_LIFF_ID });
console.log("✅ LIFF 初始化成功!");
// 2. 檢查使用者是否已經登入 Line
// (如果在 Line APP 裡面打開,這裡通常預設就是 true)
if (liff.isLoggedIn()) {
console.log("✅ 使用者已登入,正在抓取個人資料...");
const userProfile = await liff.getProfile();
setProfile(userProfile);
console.log("👋 歡迎回來:", userProfile.displayName);
} else {
// 3. 如果是用外部瀏覽器打開且未登入,強制跳轉到 Line 的登入頁面
console.log("⚠️ 使用者未登入,準備重導向...");
liff.login();
}
// 4. 全部搞定,標記為 Ready
setIsReady(true);
} catch (error) {
console.error("❌ LIFF 初始化發生致命錯誤:", error);
setLiffError(error.message);
}
};
// 執行初始化
initLiff();
}, []); // 空陣列確保只在網頁打開時執行一次
// 回傳所有狀態給外面的元件使用
return { isReady, liffError, profile };
}
🔍 深度解析:
這是所有 Line 機器人開發者的夢魘,但 AI 幫我們完美處理了。
這裡面有一個極度重要的機制:liff.login()。
當你把這個網址丟給老闆,老闆如果用電腦的 Chrome 瀏覽器打開(而不是在 Line 裡面打開),liff.isLoggedIn() 就會是 false。
這時候這段程式碼會非常聰明地自動跳轉到 Line 的綠色登入畫面,要求老闆輸入帳號密碼。這樣就完美兼顧了「Line 內建瀏覽器」與「外部瀏覽器」的兩種情境!
🚫 終極避坑指南:LIFF 初始化卡死在迴圈中
這是一個價值十萬元的 Debug 經驗!
很多新手照著官方文件寫了 liff.login(),結果部署上去後發現:網頁在外部瀏覽器打開時,會陷入「無限登入迴圈」,一直閃爍重新載入!
為什麼?因為當你呼叫 liff.login() 時,Line 會把網頁跳轉到驗證中心,驗證完後再把網頁「重新載入」一次。
如果你的網址設定錯誤,Line 無法正確把網址加上驗證參數帶回來,你的網頁就會以為「我又沒登入了」,然後再次呼叫 liff.login()。
💡 Vibe Prompt 實戰 2:讓 AI 幫你檢查 LIFF Callback 網址
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
我的 React 網頁在外部瀏覽器呼叫 liff.login() 時,發生了無限重新載入的死迴圈。請問這是不是因為 LIFF Console 後台的 "Endpoint URL" 設定有問題?請告訴我,當我部署在 Vercel (網址是 https://my-punch.vercel.app/) 時,我在 Line Developers 後台的 Endpoint URL 應該填什麼?另外,請幫我修改剛才的 liff.login() 語法,明確加上 redirectUri 參數以防止這個 Bug。
🤖 AI 的緊急煞車防爆措施:
這絕對是 Callback URL 沒有對齊造成的無限迴圈!
【Line Developers 後台設定檢查】:
你的 Endpoint URL 必須精準填寫:`https://my-punch.vercel.app/`
(⚠️ 注意:如果你是用 React Router 的 Hash 模式,後面帶有 /#/ 的話會引發大災難,Line 預設會把 # 後面的東西吃掉!)
【程式碼防爆修改】:
與其讓 liff 自己猜要跳回哪裡,我們直接強制指定 `redirectUri`:
// 修改前的危險寫法:
// liff.login();
// ✅ 修改後的最安全寫法:
liff.login({
// 明確告訴 Line,驗證完後要把使用者丟回哪一個網址!
redirectUri: window.location.href
});
🔐 實戰 3:身份的二次驗證 (JWT Token)
好,現在我們拿到了使用者的 profile (包含姓名和大頭貼)。我們可以把這個姓名直接傳給我們的後端 (FastAPI),跟後端說:「王小明打卡了!」對吧?
絕對不行!這是最嚴重的資安漏洞!
如果你的後端 API 只認「名字」,駭客只要寫一個假網頁,傳送一個「總經理」的名字給你的後端,後端就會以為總經理打卡了! 在真實的商業系統中,前端傳給後端的絕對不能是明文名字,而必須是一張**「無法偽造的通行證 (ID Token)」**。
💡 Vibe Prompt 實戰 3:取得 Line 的 JWT Token
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
在 LIFF 應用中,我要把使用者的打卡資料送到我的後端伺服器 (FastAPI)。為了防止駭客偽造身份,我不能只傳 liff.getProfile() 拿到的名字。請教我如何使用 liff.getIDToken() 來取得安全的 JWT 憑證。請寫一個函數 punchIn(),裡面示範如何取得 Token,並用 fetch() 把 Token 放在 HTTP Header (Authorization: Bearer) 中送到後端 API (/api/punch)。請附上詳細的資安註解。
🤖 AI 的企業級防偽打卡功能:
export const punchIn = async () => {
try {
// 1. 取得 Line 簽發的安全通行證 (JWT Token)
// 這串文字是經過 Line 伺服器加密簽章的,駭客絕對無法自己偽造一模一樣的 Token
const idToken = liff.getIDToken();
if (!idToken) {
throw new Error("無法取得安全憑證,請重新登入!");
}
console.log("🔐 取得通行證準備打卡...");
// 2. 打 API 送資料給後端
const response = await fetch('https://你的後端網址.com/api/punch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// 🔥 這是精華!把通行證放在 Header 裡面傳給後端
'Authorization': `Bearer ${idToken}`
},
body: JSON.stringify({
action: 'clock_in',
timestamp: new Date().toISOString()
// ⚠️ 注意:我們完全不需要傳送姓名或 Line ID 給後端!
// 後端收到 Token 後,會自己去問 Line 伺服器:「這個 Token 是誰的?」
})
});
const result = await response.json();
if (response.ok) {
alert("✅ 打卡成功!");
} else {
alert("❌ 打卡失敗:" + result.message);
}
} catch (error) {
console.error("打卡發生異常:", error);
alert("系統異常,請聯絡管理員。");
}
};
🔍 深度解析:
這就是為什麼這套系統可以賣幾十萬的原因。
「零信任架構 (Zero Trust)」 是現代 SaaS 系統的標配。前端是極度不安全的,任何人都可以竄改你瀏覽器裡的資料。
但 liff.getIDToken() 產生出來的那串亂碼,包含了 Line 伺服器的數位簽章。你的後端只要解開這個簽章,就能百分之百確定:「這個請求,確實是從王小明本人的 Line APP 裡面發出來的」。這就是隱形登入的最高境界!
⏳ 實戰 4:把 LIFF 狀態與畫面完美結合
我們寫好了 Hook 和打卡功能,現在要把這些東西套用到網頁畫面上。 在 LIFF 還沒準備好之前,畫面應該要是什麼樣子? 如果是在外部瀏覽器打開,然後跳轉回來,畫面會不會閃爍?
💡 Vibe Prompt 實戰 4:打造體驗滿分的過場畫面 (Splash Screen)
在 APP 開發中,我們通常會有一個啟動畫面 (Splash Screen)。 我們用 React 和 Tailwind 來實作它。
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
我現在要在 App.jsx 中使用剛才寫的 useLiff() hook。需求:1. 呼叫 const { isReady, liffError, profile } = useLiff();2. 如果 liffError 有值,顯示一個滿版的紅色錯誤畫面。3. 如果 isReady 為 false,顯示一個深色質感的滿版 Loading 畫面 (包含旋轉的圖示和 "系統初始化中..." 文字)。4. 如果 isReady 為 true,才顯示主畫面 (包含使用者的頭像、姓名,以及一個「上班打卡」的按鈕)。請給我完整的 App.jsx 程式碼。
🤖 AI 的高質感應用程式入口:
import React from 'react';
import { useLiff } from './hooks/useLiff';
import { punchIn } from './api'; // 剛才寫的打卡函數
export default function App() {
const { isReady, liffError, profile } = useLiff();
// 情境 1:發生致命錯誤 (例如沒有設定 LIFF ID)
if (liffError) {
return (
<div className="min-h-screen bg-zinc-900 flex items-center justify-center p-6">
<div className="bg-red-500/20 border border-red-500 text-red-100 p-6 rounded-2xl max-w-sm text-center">
<h2 className="text-xl font-bold mb-2">⚠️ 系統載入失敗</h2>
<p className="text-sm opacity-80">{liffError}</p>
</div>
</div>
);
}
// 情境 2:正在跟 Line 伺服器連線中 (Splash Screen)
if (!isReady) {
return (
<div className="min-h-screen bg-zinc-900 flex flex-col items-center justify-center">
{/* Tailwind 的 animate-spin 可以讓任何東西旋轉 */}
<div className="w-12 h-12 border-4 border-emerald-500 border-t-transparent rounded-full animate-spin mb-4"></div>
<p className="text-emerald-500 font-bold tracking-widest animate-pulse">
SYSTEM INITIALIZING...
</p>
</div>
);
}
// 情境 3:連線成功!顯示主畫面
return (
<div className="min-h-screen bg-zinc-900 text-white p-6">
<header className="flex items-center gap-4 mb-10">
{/* 顯示 Line 頭像 */}
{profile?.pictureUrl ? (
<img
src={profile.pictureUrl}
alt="avatar"
className="w-16 h-16 rounded-full border-2 border-emerald-500 shadow-lg shadow-emerald-500/20"
/>
) : (
<div className="w-16 h-16 rounded-full bg-zinc-800 border-2 border-zinc-700"></div>
)}
<div>
<p className="text-zinc-400 text-sm">早安,辛苦了</p>
<h1 className="text-2xl font-bold">{profile?.displayName || '員工'}</h1>
</div>
</header>
<main>
{/* 霸氣的打卡按鈕 */}
<button
onClick={punchIn}
className="w-full bg-emerald-500 text-zinc-900 font-bold text-xl py-6 rounded-2xl shadow-[0_0_30px_rgba(16,185,129,0.3)] active:scale-95 transition-transform"
>
👆 立即打卡
</button>
</main>
</div>
);
}
📱 實戰 5:在 Line 內建瀏覽器中開啟的「特權」
當你的網頁跑在 Line 裡面時,你不只是拿到姓名而已。
Line 還給了你一個超強的 API:liff.closeWindow()。
你可以想像一個情境:員工點開 Line 的群組連結,跳出打卡畫面。按完打卡後,畫面出現「打卡成功」,然後... 然後他不知道怎麼辦,只能手動按左上角的 X 關閉網頁。 這非常不流暢! 如果我們能在打卡成功後,程式碼自動幫他把網頁關掉,回到 Line 聊天室,那這個體驗就跟原生的 APP 一模一樣!
💡 Vibe Prompt 實戰 5:自動關閉視窗的完美體驗
[!IMPORTANT] 請複製以下 Prompt 傳送給 AI:
我希望在員工點擊「打卡成功」的 alert 之後,網頁能夠自動關閉,讓他回到 Line 的聊天室。我知道可以使用 liff.closeWindow()。但為了防止在「外部電腦瀏覽器」執行這行程式碼導致報錯,請幫我加上判斷邏輯。請告訴我如何修改剛才的 punchIn() 函數。
🤖 AI 的極致體驗優化:
// 在 punchIn 函數的成功區塊中加入:
if (response.ok) {
alert("✅ 打卡成功!系統將為您關閉視窗。");
// 🔥 神級防呆判斷:
// liff.isInClient() 會檢查這個網頁是不是正在 Line APP 的內部瀏覽器裡面跑
if (liff.isInClient()) {
// 如果是,直接霸氣關閉整個視窗,退回聊天室!
liff.closeWindow();
} else {
// 如果是在電腦版 Chrome 打開的,就幫他跳轉到一個感謝頁面,或是重新整理
window.location.reload();
}
}
✅ 本章總結與終極架構心法
在這個高達 6000 字的超深解析中,我們完全破解了 Line 打卡系統的前端命脈。 這不只是一段程式碼,這是一整套高商業價值的解決方案。
回顧我們建立的這套隱形登入流:
- 全域 Hook (
useLiff):優雅地處理非同步的初始化,並把狀態發送給整個系統。 - 防呆重新導向 (
liff.login({redirectUri})):解決了最常發生的無限重新載入死迴圈。 - 零信任資安 (
getIDToken):絕對不傳明文姓名,而是傳送擁有數位簽章的 JWT,徹底封殺駭客偽造打卡的可能。 - 載入體驗 (Splash Screen):用 Tailwind 的動畫,填補了與 Line 伺服器連線時的等待空白。
- 原生體驗 (
closeWindow):打完卡自動關閉視窗,讓使用者覺得這不是一個網頁,而是一個內建功能。
當你學會了這套 LIFF 架構,你就不只能做打卡系統了。 你可以做「餐廳的桌邊掃碼點餐系統」、「美髮沙龍的預約系統」、「電商的會員綁定卡」。 只要有這套登入流,使用者的轉換率將會大幅提升!
但現在我們的畫面還是稍微陽春了一點(只有一個按鈕)。 在下一章:第十二章:Glassmorphism 與科技感儀表板介面,我們將把這個打卡頁面,升級成價值連城的賽博龐克風格,讓員工每天打卡都像是在啟動鋼鐵人的戰衣!我們下堂課見!