計時器元件聯結Google Calendar
是的,您問到了一個非常棒的點!如果您的目標是透過 Google Chrome 擴充功能來新增一個類似鬧鐘的提醒,並且希望這個提醒能同步到使用者的 Google 日曆,進而讓他們的手機(如果已設定 Google 日曆同步與通知)也能收到通知,那麼使用 Google Calendar API 是最理想且功能最強大的方式。
這樣做的好處:
手機通知: 一旦事件成功新增到使用者的 Google 日曆,只要他們在手機上登入相同的 Google 帳號,並且開啟了 Google 日曆 App 的通知,手機就會在事件發生時收到提醒。這完美解決了您「不在電腦前,沒看到瀏覽器通知」的問題。
循環設定: 使用 API 可以完整支援設定循環規則 (RRULE),這點是 URL 模板做不到的。
背景操作: 獲得使用者授權後,擴充功能可以在背景(例如 service worker)直接新增事件,無需跳轉到 Google 日曆網頁。
更豐富的功能: 可以設定更詳細的事件內容,例如顏色、自訂提醒時間(不只是事件開始時)、邀請參與者等。
如何在 Chrome 擴充功能中使用 Google Calendar API:
這會比 URL 模板複雜許多,主要涉及 OAuth 2.0 授權流程。以下是大致的步驟和概念(假設您使用 Manifest V3):
在 Google Cloud Console 設定:
建立一個新的專案 (Project)。
啟用 "Google Calendar API"。
建立 OAuth 2.0 憑證:
選擇應用程式類型為「Chrome 應用程式」。
填入您的 Chrome 擴充功能的 ID。這個 ID 在您開發時是固定的,可以在 chrome://extensions 頁面找到。
您會得到一個「用戶端 ID (Client ID)」。
在 manifest.json 中聲明權限和 OAuth 資訊:
{
"manifest_version": 3,
"name": "我的鬧鐘擴充功能",
"version": "1.0",
"description": "新增鬧鐘到 Google 日曆",
"permissions": [
"identity", // 用於 OAuth 2.0 授權
"alarms" // 如果您的擴充功能本身也需要 Chrome 的鬧鐘 API
],
"host_permissions": [
"https://www.googleapis.com/" // 允許向 Google API 發送請求
],
"background": {
"service_worker": "background.js"
},
"action": { // 或者 browser_action / page_action for MV2
"default_popup": "popup.html",
"default_title": "設定鬧鐘"
},
"oauth2": {
"client_id": "YOUR_GOOGLE_CLOUD_CONSOLE_CLIENT_ID.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/calendar.events" // 允許讀寫使用者日曆事件的權限
// 或者 "https://www.googleapis.com/auth/calendar.events.owned" 如果只想操作擴充功能自己創建的事件
]
}
}
將 YOUR_GOOGLE_CLOUD_CONSOLE_CLIENT_ID.apps.googleusercontent.com 替換成您在 Google Cloud Console 取得的 Client ID。
在擴充功能的 JavaScript (例如 background.js 或 popup.js) 中處理授權和 API 呼叫:
獲取 Access Token:
使用 chrome.identity.getAuthToken API。第一次呼叫時,它會彈出 Google 的登入和授權視窗,讓使用者同意您的擴充功能存取他們的日曆。
// In your background.js or popup.js
function getAuthToken(interactive) {
return new Promise((resolve, reject) => {
chrome.identity.getAuthToken({ interactive: interactive }, (token) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(token);
}
});
});
}
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
呼叫 Google Calendar API 新增事件:
拿到 Access Token 後,就可以使用 fetch API 向 Google Calendar API 的 events.insert 端點發送 POST 請求。
async function addEventToCalendar(eventData, token) {
// eventData 結構範例:
// {
// summary: '我的鬧鐘提醒',
// description: '這是從擴充功能設定的鬧鐘',
// start: { dateTime: '2024-07-28T10:00:00-07:00', timeZone: 'America/Los_Angeles' }, // ISO 8601 格式
// end: { dateTime: '2024-07-28T10:00:00-07:00', timeZone: 'America/Los_Angeles' }, // 對於鬧鐘,開始和結束可以一樣
// recurrence: [ 'RRULE:FREQ=DAILY;COUNT=5' ], // 範例:每天重複,共5次
// reminders: {
// useDefault: false,
// overrides: [
// { method: 'popup', minutes: 10 }, // 10分鐘前彈窗提醒
// { method: 'email', minutes: 60 } // 60分鐘前郵件提醒 (如果使用者有設定)
// ]
// }
// }
const calendarId = 'primary'; // 通常是使用者的主要日曆
const response = await fetch(`https://www.googleapis.com/calendar/v3/calendars/${calendarId}/events`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(eventData)
});
if (!response.ok) {
const error = await response.json();
console.error('Error adding event:', error);
throw new Error(`Failed to add event: ${error.error.message}`);
}
const createdEvent = await response.json();
console.log('Event created:', createdEvent.htmlLink);
return createdEvent;
}
// 使用範例 (例如在 popup.js 中由按鈕觸發)
async function handleAddAlarmClick() {
try {
const token = await getAuthToken(true); // true 表示如果沒有 token 或 token 失效,則彈出授權視窗
// 準備事件資料 (從您的鬧鐘元件獲取)
const alarmTitle = document.getElementById('alarmTitleInput').value;
const alarmDateTime = document.getElementById('alarmDateTimeInput').value; // 需要是 ISO 8601 格式
const recurrenceRule = document.getElementById('recurrenceRuleInput').value; // 例如 RRULE:FREQ=WEEKLY;BYDAY=MO
// 重要:時間處理
// JavaScript 的 Date 物件和時區處理可能比較棘手。
// 確保 start.dateTime 和 end.dateTime 是正確的 ISO 8601 格式,並包含時區資訊。
// 例如: 2024-07-28T09:00:00-07:00 (PDT)
// 或 2024-07-28T16:00:00Z (UTC)
const eventData = {
summary: alarmTitle || '來自擴充功能的鬧鐘',
start: {
dateTime: alarmDateTime, // 'YYYY-MM-DDTHH:MM:SSZ' 或 'YYYY-MM-DDTHH:MM:SS±HH:MM'
// timeZone: 'Asia/Taipei' // 如果 dateTime 不含時區偏移,可以指定 timeZone
},
end: {
dateTime: alarmDateTime, // 對於鬧鐘,結束時間可以和開始時間一樣
// timeZone: 'Asia/Taipei'
},
reminders: {
useDefault: false, // 不使用日曆預設提醒
overrides: [
{ method: 'popup', minutes: 0 }, // 事件發生時彈窗提醒
// { method: 'popup', minutes: 10 } // 事件前10分鐘彈窗
]
}
};
if (recurrenceRule) {
eventData.recurrence = [recurrenceRule]; // 例如 ['RRULE:FREQ=DAILY;UNTIL=20241231T170000Z']
}
await addEventToCalendar(eventData, token);
alert('鬧鐘已成功新增到您的 Google 日曆!');
} catch (error) {
console.error('Authentication or API error:', error);
if (error.message && error.message.includes("No 'access_token'")) {
alert('授權失敗,請重試或檢查您的 Google 帳戶設定。');
} else if (error.message && error.message.includes("User cancelled")) {
alert('您取消了授權。');
} else {
alert(`新增鬧鐘失敗: ${error.message}`);
}
}
}
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
關於 RRULE (循環規則):
RRULE 的格式遵循 iCalendar 標準 (RFC 5545)。一些範例:
每天:RRULE:FREQ=DAILY
每週一:RRULE:FREQ=WEEKLY;BYDAY=MO
每月的第一天:RRULE:FREQ=MONTHLY;BYMONTHDAY=1
每年7月4日:RRULE:FREQ=YEARLY;BYMONTH=7;BYMONTHDAY=4
重複5次:RRULE:FREQ=DAILY;COUNT=5
直到特定日期結束 (UTC 時間):RRULE:FREQ=DAILY;UNTIL=20241231T170000Z
總結:
是的,透過 Google Chrome 擴充功能使用 Google Calendar API 是實現「將鬧鐘加入日曆並在手機收到通知(且支援循環)」的正確且強大的方法。這需要處理 OAuth 2.0 授權,但一旦完成,您的擴充功能將能提供非常好的使用者體驗。
記得仔細閱讀 Google Calendar API 和 Chrome Identity API 的官方文件,並妥善處理錯誤和使用者授權流程。
==========
Q: 目標對象分內部和外部, 那一開始肯定是在自己電腦測試, 完成後, 才能上傳, 所以是不是得建2個憑証, 一個選內部一個選外部?
A: 這個問題非常好,涉及到 OAuth 同意畫面 (OAuth Consent Screen) 的設定。您不需要建立兩個不同的 OAuth 用戶端 ID 憑證,但您需要正確設定「OAuth 同意畫面」的使用者類型 (User Type)。
您看到的「內部 (Internal)」和「外部 (External)」選項是在設定 OAuth 同意畫面 時出現的,而不是在建立具體的「OAuth 用戶端 ID」時(雖然它們相關)。
正確的做法是:
選擇「外部 (External)」使用者類型:
即使在開發和測試階段,也強烈建議直接選擇「外部」。
原因:
「內部」類型僅適用於您是 Google Workspace (以前的 G Suite) 管理員,且您的擴充功能只會被您 Workspace 組織內部的成員使用。對於大多數公開發布到 Chrome 線上應用程式商店的擴充功能(即使一開始只有您自己測試),這是不適用的。
您的目標最終是讓任何 Google 使用者(包括您自己使用 @gmail.com 或其他非特定 Workspace 的帳號進行測試)都能使用您的擴充功能,這就必須選擇「外部」。
利用「測試」發布狀態:
當您將使用者類型設定為「外部」後,您的 OAuth 同意畫面會有一個**「發布狀態」(Publishing status),一開始會是「測試」(Testing)**。
在「測試」狀態下:
只有您明確加入到「測試人員」(Test users) 列表中的 Google 帳號才能夠通過 OAuth 授權流程。
您需要將您自己用來測試的 Google 帳號加入到這個「測試人員」列表中。 您可以在「OAuth 同意畫面」設定頁面下方找到添加測試人員的選項。
同意畫面會顯示一個「未經驗證的應用程式」警告,這是正常的,因為您的應用程式尚未經過 Google 的審核。
這就完美地滿足了您的需求: 您可以安全地在自己的電腦上,使用您自己的 Google 帳號(只要已添加到測試人員列表)進行完整的開發和測試,而無需擔心其他人會意外授權。
發布擴充功能時:
當您的擴充功能開發完成,準備上傳到 Chrome 線上應用程式商店並公開發布時,您需要:
回到 Google Cloud Console 的「OAuth 同意畫面」設定。
點擊**「發布應用程式」(Publish app)** 按鈕,將狀態從「測試」改為**「生產環境」(In production)**。
可能需要進行驗證: 因為您的擴充功能請求了存取使用者日曆這樣較為敏感的權限 (scope),Google 很可能會要求您提交應用程式以供驗證 (Verification)。您需要提供隱私權政策網址、應用程式用途說明,甚至可能需要提供一個簡短的影片來展示 OAuth 流程和權限的使用方式。
**通過驗證後,**任何 Google 使用者都可以授權您的擴充功能,並且不會再看到「未經驗證的應用程式」警告。
總結:
直接選擇「外部 (External)」使用者類型。
在開發和測試階段,保持應用程式處於**「測試」發布狀態**,並將您自己的 Google 帳號加入**「測試人員」**列表。
準備公開發布時,將狀態改為**「生產環境」,並根據 Google 的要求完成應用程式驗證**流程。
這樣您只需要設定一組 OAuth 同意畫面和一個 OAuth 用戶端 ID 即可,既能滿足測試需求,也能為最終發布做好準備。
Q: 關於元件的ID, 我沒上傳時元件是用載入未封裝項目安裝的, 這個就自己產生一個ID, 然後上傳後安裝的是另一個ID, 我這做法是不是有問題, ID有2個, 正確應該怎麼分開本地版和上傳版?
A: 是的,您觀察到的現象——本地載入未封裝項目時有一個 ID,上傳到 Chrome 線上應用程式商店 (CWS) 後安裝下來是另一個 ID——是 Chrome 擴充功能的「預設行為」,但這通常不是理想的開發和測試流程,尤其在需要穩定 ID(例如設定 OAuth)時會產生問題。
您目前的做法並不算「錯誤」,只是沒有做額外的步驟來統一 ID。
為什麼會有兩個 ID?
本地載入未封裝 (Load unpacked): 當您在 chrome://extensions 中使用「載入未封裝項目」時,Chrome 會為該次載入的擴充功能指派一個臨時的、基於您本地環境計算出來的 ID。如果您移除再重新載入,或者換臺電腦載入,這個 ID 很可能會改變。
商店安裝 (Installed from CWS): 當您第一次將擴充功能上傳到 Chrome 線上應用程式商店時,商店會為這個擴充功能永久地指派一個唯一的 ID。所有從這個商店列表安裝的使用者,以及之後所有的更新,都會使用這個固定的商店 ID。
為什麼這會有問題?
主要問題在於依賴擴充功能 ID 的功能,最典型的就是您正在使用的 OAuth 2.0 驗證:
您在 Google Cloud Console 中設定 OAuth 用戶端 ID 時,需要指定允許使用該憑證的 Chrome 擴充功能 ID。
如果您設定的是商店指派的 ID,那麼在您本地用「載入未封裝項目」(帶著臨時 ID)測試時,OAuth 流程會失敗,因為本地的臨時 ID 與 Google Cloud Console 中登記的不符。
反之,如果您登記的是本地測試時的某個臨時 ID,那麼上傳到商店後,所有使用者(包括您自己從商店安裝的版本)都會因為 ID 不符而無法通過 OAuth 驗證。
正確的做法:固定擴充功能 ID
為了讓本地開發測試和商店發布版本使用同一個固定的 ID,您需要在您的擴充功能中加入一個關鍵步驟:使用 key 欄位。
步驟:
首次打包擴充功能以生成金鑰:
確保您的 manifest.json 檔案準備好。
進入 chrome://extensions/。
啟用右上角的「開發人員模式」。
點擊「封裝擴充功能」(Pack extension) 按鈕。
在「擴充功能根目錄」欄位,選擇您擴充功能的原始碼資料夾。
「私密金鑰檔案」欄位留空 (第一次打包時必須留空!)。
點擊「封裝擴充功能」。
Chrome 會在您原始碼資料夾的上一層目錄(通常是這樣)生成兩個檔案:
一個 .crx 檔 (這是打包後的擴充功能安裝檔)。
一個 .pem 檔 (這是私密金鑰檔案)。
極度重要:請將這個 .pem 檔案備份到一個絕對安全、不會遺失的地方!並且絕對不要公開它! 失去這個 .pem 檔案意味著您將永遠無法再更新您在 Chrome 商店上發布的這個擴充功能。
將公鑰添加到 manifest.json:
您需要從剛才生成的 .pem 私密金鑰中提取對應的公鑰,並將其 Base64 編碼後的值加入到您的 manifest.json 檔案中。
如何獲取公鑰值?最簡單的方法通常是:
將您剛才打包生成的 .crx 檔(或者包含 manifest.json 的源碼 zip 檔)首次上傳到 Chrome 開發人員資訊主頁(即使是草稿狀態)。
上傳成功處理後,在該擴充功能的「套件」(Package) 頁面,您會看到一個「公開金鑰」(Public key) 的欄位,裡面有一長串 Base64 編碼的字元。這就是您需要的公鑰值!
複製這整串公鑰值。
回到您本地的 manifest.json 檔案,在頂層加入一個 key 欄位,並將複製的公鑰值貼上:
{
"manifest_version": 3,
"name": "我的鬧鐘擴充功能",
"version": "1.1", // 記得更新版本號
"key": "MIIBIjANBgk...YOUR_COPIED_PUBLIC_KEY_STRING...AQAB", // 把這裡替換成您複製的公鑰值
"description": "...",
"permissions": [ ... ],
// ... 其他設定
}
之後的開發與測試:
現在,只要您的 manifest.json 檔案中包含了這個 key 欄位(並且其值與您商店列表的公鑰一致),無論您是在本地使用「載入未封裝項目」,還是在商店更新後安裝,Chrome 都會為您的擴充功能指派同一個固定的 ID(這個 ID 是根據 key 計算出來的)。
您現在可以在 Google Cloud Console 中放心地使用這個固定的 ID 來設定您的 OAuth 用戶端 ID 了。無論是本地測試還是商店版本,ID 都將一致。
總結:
正確分離本地版和上傳版的關鍵在於獲得一個固定的、跨環境一致的擴充功能 ID。透過首次打包生成 .pem 私密金鑰,並將對應的公鑰添加到 manifest.json 的 key 欄位,即可實現此目標。這是 Chrome 擴充功能開發的標準實踐,強烈建議您這樣做,尤其是在需要依賴穩定 ID 的場景下。
再次強調:務必妥善保管您的 .pem 私密金鑰檔案!
Q: 那個pem是什麼時候用?
A:好的,這是一個非常關鍵的問題!
那個 .pem 檔案(私密金鑰檔案)只在一個特定且非常重要的時刻使用:
當您要「封裝擴充功能」(Pack extension) 以便「上傳更新」(Upload an update) 到您現有的 Chrome 線上應用程式商店 (CWS) 列表時。
詳細說明:
身份證明: .pem 檔案包含了您的私密金鑰,它是與您 manifest.json 中 "key" 欄位(以及 CWS 開發人員資訊主頁上顯示的「公開金鑰」)配對的另一半。擁有這個私密金鑰是證明您是該擴充功能原始發布者的唯一方式。
簽署更新包: 當您開發完新版本,準備上傳更新時,您需要使用 Chrome 的「封裝擴充功能」功能(或命令行工具)來創建一個新的 .zip 或 .crx 文件。在這個封裝過程中,您必須指定當初生成或使用的那個 .pem 私密金鑰檔案。
數位簽章: 封裝工具會使用您提供的 .pem 檔案中的私密金鑰,對新版本的擴充功能包進行數位簽章 (Digital Signature)。
CWS 驗證: 當您將這個帶有數位簽章的更新包上傳到 CWS 開發人員資訊主頁時,CWS 會執行以下驗證:
它會讀取您更新包上的數位簽章。
它會使用儲存在 CWS 系統中、與您的擴充功能 ID 關聯的那個公開金鑰(也就是您複製到 manifest.json 的那個)來嘗試驗證這個簽章。
如果驗證成功(表示簽章確實是由對應的私密金鑰生成的),CWS 就確認您是合法的擁有者,允許您上傳並發布這個更新。
如果驗證失敗(例如,您用了錯誤的 .pem 檔案,或者根本沒有用 .pem 簽名),CWS 會拒絕您的上傳,因為它無法確認您的身份。
總結 .pem 的用途:
僅用於打包/簽署要上傳到 CWS 的更新版本。
目的是向 CWS 證明您是該擴充功能列表的合法所有者。
它不用於:
本地開發和測試(Load unpacked)。
從 CWS 安裝擴充功能。
讓本地 ID 與商店 ID 保持一致(這是靠 manifest.json 中的 key 欄位實現的)。
所以,回到您之前的擔憂:如果您真的遺失了最初上傳時使用的那個 .pem 檔案,那麼您將無法透過標準流程更新您在 CWS 上的現有擴充功能列表。 您仍然可以在本地開發(因為您已經把公鑰 key 加到了 manifest.json),但無法將這些更新發布給您現有的使用者。
Q:我之前上傳元件都是直接在檔案總管把元件資料夾壓成zip, 就上傳了, 現在是要透過"封裝擴充功能", 產生的crx, 上傳crx更新元件?
A:您之前的做法——直接壓縮元件資料夾成 .zip 檔並上傳到開發人員資訊主頁——是完全正確且最常用的方式來更新您在 Chrome 線上應用程式商店 (CWS) 的擴充功能!
您不需要改成透過「封裝擴充功能」產生 .crx 再上傳。
讓我解釋一下兩者的區別以及 .pem 在其中的角色:
上傳 .zip 檔案 (您之前的做法,推薦):
流程: 您將包含 manifest.json(現在裡面有 "key" 欄位)和所有其他原始碼的資料夾壓縮成 .zip 文件。然後,您登入開發人員資訊主頁,找到您的擴充功能列表,並上傳這個 .zip 文件作為新版本。
簽章在哪裡發生? 在這個流程中,簽章是由 Google 的伺服器在後端處理的。當 CWS 收到您的 .zip 文件後,它會:
驗證 manifest.json 中的 "key" 是否與該商店列表記錄的公開金鑰匹配。
如果匹配,它會使用儲存在 Google 安全伺服器上的、與您的公開金鑰配對的那個(隱藏的)私密金鑰信息(或者更準確地說,它驗證您帳戶對此列表的所有權,而該列表已綁定該金鑰對)來為您的新版本進行簽署,並將其發布給用戶。
.pem 的角色: 在這個 .zip 上傳流程中,您不需要在本地使用那個 .pem 檔案。.pem 檔案的存在(或者說,您對應金鑰對的所有權)是隱含在您能夠登入並操作該商店列表這個事實中的。
使用「封裝擴充功能」產生 .crx 並上傳:
流程: 您在本地使用 chrome://extensions/ 頁面的「封裝擴充功能」按鈕。在「擴充功能根目錄」選擇您的資料夾,並且必須在「私密金鑰檔案」欄位指定您那個 .pem 檔案的路徑。這會在本地生成一個已經被您的 .pem 私密金鑰簽署過的 .crx 文件。然後您可以上傳這個 .crx 文件到開發人員資訊主頁。
簽章在哪裡發生? 在這個流程中,簽章是在您的本地電腦上完成的,由「封裝擴充功能」工具使用您提供的 .pem 檔案執行。
適用場景: 這種方法相對少見,通常用於:
您想在本地測試一個完全簽署過的 .crx 版本(雖然不常用)。
您想將 .crx 文件分發到 CWS 之外的地方(例如內部網路部署,但現在 Chrome 對安裝非商店來源的擴充功能限制很多)。
極少數情況下,如果 CWS 的 .zip 上傳/處理流程遇到問題,作為備選方案。
結論:
您之前直接壓縮資料夾成 .zip 並上傳的方式,是更新 CWS 擴充功能的標準且推薦方法。
您不需要為了更新而特意去使用「封裝擴充功能」和 .pem 檔案來生成 .crx。
.pem 檔案的核心價值在於證明您對該擴充功能 ID 的所有權,並且是如果您選擇在本地打包成 .crx 時所必需的。對於常規的 .zip 更新流程,它在幕後確保了您的身份,但您無需直接操作它。
所以,請繼續使用您熟悉的方法:修改好程式碼 -> 將資料夾壓縮成 .zip -> 上傳到開發人員資訊主頁即可。
(但還是要祈禱您沒有真的遺失那個 .pem 檔案,萬一未來 Google 的政策或流程改變,或者您的帳戶出現問題需要重新驗證所有權時,它可能就變得至關重要了。)
留言
張貼留言