瀏覽器
dozin.capabilities 裡有 browser 時,Connector 或任務會得到 ctx.browser,可以讀取並操作 Cowork 分頁裡的網頁。只能使用使用者交出的分頁;有提供 dozin.origins 時,只限這些網站上的頁面。
ctx.browser.adoptTab(tabId)
接管一個分頁。使用者要求 Extension 接管某個頁面時,Cowork 會把 tabId 交給 Extension;Extension 不能自己挑選分頁。
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
tabId | string | 是 | Cowork 交過來的分頁 |
回傳 Promise<{ tabId, url, documentId }>。頁面每次載入新文件時,documentId 都會改變。同一時間最多 8 個分頁。
js
const tab = await ctx.browser.adoptTab(tabId);
ctx.log(`watching ${tab.url}`);ctx.browser.releaseTab(tabId)
交還分頁。它的注入腳本和音訊會停止。
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
tabId | string | 是 | 這個 Extension 接管的分頁 |
回傳 Promise<void>。
js
await ctx.browser.releaseTab(tabId);ctx.browser.evaluate(tabId, script)
在頁面裡執行一次腳本,並回傳結果。
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
tabId | string | 是 | 已接管的分頁 |
script.source | string | 是 | 一段函式原始碼,最多 64 KB;它會收到 args |
script.args | unknown | 否 | 以 JSON 傳入,不會貼進原始碼 |
回傳 Promise<unknown>:結果,為 JSON,最多 64 KB。
js
const title = await ctx.browser.evaluate(tabId, { source: '(args) => document.title' });ctx.browser.inject(tabId, script)
安裝一段留在頁面裡、能回傳訊息的腳本。
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
tabId | string | 是 | 已接管的分頁 |
script.id | string | 是 | 1–80 個字母、數字、_ 或 -;在這個分頁內唯一 |
script.source | string | 是 | 一個函式 (args, { emit, onDispose }) => cleanup?,最多 64 KB;emit(data) 會以 message 事件送達 |
script.args | unknown | 否 | 以 JSON 傳入 |
回傳 Promise<void>。每個分頁最多 16 段腳本。
js
await ctx.browser.inject(tabId, {
id: 'chat-watch',
source: `(args, { emit }) => {
const observer = new MutationObserver(() => emit({ count: document.querySelectorAll(args.selector).length }));
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}`,
args: { selector: '.message' }
});ctx.browser.uninject(tabId, injectionId)
移除一段注入腳本,並執行它的清理。
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
tabId | string | 是 | 已接管的分頁 |
injectionId | string | 是 | 傳給 inject 的 id |
回傳 Promise<void>。
js
await ctx.browser.uninject(tabId, 'chat-watch');ctx.browser.captureAudio(tabId)
還需要 audio。開始擷取分頁的聲音。
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
tabId | string | 是 | 已接管且已載入完成的分頁 |
回傳 Promise<AudioStream>:
| 欄位 | 類型 | 說明 |
|---|---|---|
tabId · url · documentId | 這個分頁,與 adoptTab 相同 | |
streamId | string | 這次擷取 |
frames | AsyncIterable<{ streamId, sequence, timestamp, sampleRate, channels, pcm: Float32Array }> | 聲音,一段一段送來 |
ended | Promise | 擷取結束時完成 |
stop() | () => Promise<void> | 停止擷取 |
每個分頁同一時間只能有一個擷取。
js
const stream = await ctx.browser.captureAudio(tabId);
for await (const chunk of stream.frames) handle(chunk.pcm, chunk.sampleRate);ctx.browser.stopAudio(tabId)
停止分頁的擷取。
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
tabId | string | 是 | 正在擷取的分頁 |
回傳 Promise<void>。
js
await ctx.browser.stopAudio(tabId);ctx.browser.onEvent(listener)
| 名稱 | 類型 | 必填 | 說明 |
|---|---|---|---|
listener | (event) => void | 是 | Extension 的分頁每發生一個事件就呼叫一次 |
回傳 () => void,用來停止監聽。每個事件都帶有 tabId、url 和 documentId,以及一個 type:
type | 額外欄位 | 時機 |
|---|---|---|
url-changed · navigation-start · ready | — | 頁面跳轉或載入完成 |
closed · crashed | — | 分頁已不存在 |
injected | injectionId | 一段腳本已安裝 |
message | injectionId、data | 注入腳本呼叫了 emit(data) |
error | code | 分頁裡有操作失敗 |
audio-ended | streamId、lastSequence | 一次擷取結束 |
js
const off = ctx.browser.onEvent((event) => {
if (event.type === 'message') ctx.emit({ text: String(event.data.count) });
if (event.type === 'closed') off();
});錯誤
這些函式會以下列其中一個代碼拒絕:
| 代碼 | 意思 |
|---|---|
capability_denied | 沒有宣告 browser(或 audio) |
invalid_tab · tab_not_granted · tab_not_owned | 分頁 id 有誤、Cowork 沒有交出這個分頁,或它已被交還 |
origin_not_allowed | 頁面的網站不在 dozin.origins 裡 |
tab_limit · injection_limit | 超過 8 個分頁或 16 段腳本 |
tab_unavailable · document_not_ready · stale_document | 分頁已關閉、仍在載入,或已載入另一個頁面 |
invalid_script · invalid_injection_id · injection_exists · invalid_json · result_too_large | 輸入或輸出有誤 |
audio_already_started · audio_transport_unavailable · invalid_audio_request | 無法開始擷取音訊 |
session_closed · browser_operation_failed | Extension 正在停止,或 Cowork 無法完成這次呼叫 |