Bỏ qua, tới nội dung chính

Hướng dẫn

Tiện ích trình duyệt

Thêm tiện ích Chrome vào thư viện của không gian làm việc, duyệt quyền từng phiên bản, gán cho hồ sơ và xem tiện ích nào thật sự đã nạp.

Xem dạng Markdown

Hồ sơ chỉ nạp tiện ích từ thư viện tiện ích của không gian làm việc. Không ai cài tiện ích thẳng vào một hồ sơ: tiện ích được thêm một lần, người có quyền duyệt xem chính xác mỗi phiên bản được làm gì, và lượt gán quyết định hồ sơ nào nạp nó. Cùng một tệp đã duyệt, cùng một mã tiện ích, đến mọi máy.

Thêm tiện ích

Hai nguồn, cả hai đều do máy chủ Browlix tự đọc và kiểm — không bao giờ tin vào điều người dùng khai về tệp:

  • Chrome Web Store: đường dẫn chromewebstore.google.com hoặc mã 32 chữ cái. Máy chủ tải bản dành cho phiên bản trình duyệt Browlix và đòi chữ ký của chính Chrome Web Store.
  • Tệp: .crx đã ký, hoặc .zip của thư mục tiện ích (có manifest.json), tối đa 32 MB. CRX chỉ được nhận khi mọi chữ ký trong tệp khớp nội dung và mã tiện ích khớp khoá. ZIP không có chữ ký; Browlix tạo khoá cho nó để mã tiện ích giống nhau trên mọi máy.

Chỉ nhận tiện ích Manifest V3. Máy chủ đọc tên, phiên bản, quyền, quyền theo trang, quyền tuỳ chọn và tệp giấy phép (nếu có) từ chính gói.

const { data } = await bx.call("extensions.add", {
  body: { source: "store", store: "https://chromewebstore.google.com/detail/json-formatter/bcjindcccaagfpapjjmafapmmgkkhgoa" },
});
console.log(data.package.state); // "unreviewed": chưa hồ sơ nào nạp

Với tệp: xin một chỗ tải lên dùng một lần, PUT nguyên tệp vào đó, rồi thêm:

ts
import { readFile } from "node:fs/promises";

const slot = await bx.call("extensions.upload", {});
await fetch(slot.data.upload_url, {
  method: "PUT",
  headers: { "Content-Type": "application/octet-stream" },
  body: await readFile("./my-extension.zip"),
});
const added = await bx.call("extensions.add", { body: { source: "upload", upload_path: slot.data.upload_path } });
console.log(added.data.package.chrome_id);

Thêm lại đúng tệp cũ sẽ trả về phiên bản đã có (created: false). Thêm phiên bản mới của một tiện ích bằng cách gửi kèm package_id; tệp của tiện ích khác bị từ chối.

Duyệt: chấp nhận đúng những gì phiên bản thêm vào

Mọi phiên bản mới bắt đầu ở trạng thái chưa duyệt và không hồ sơ nào nạp. Duyệt là một quyền riêng (extension.approve), tách khỏi quyền thêm và gán (extension.manage).

  • Với phiên bản đầu tiên, lượt duyệt phải nêu mọi quyền tiện ích xin.
  • Với bản cập nhật, phải nêu đúng những quyền thêm vào so với phiên bản hồ sơ đang dùng (added_permissions). Chưa duyệt thì hồ sơ vẫn dùng bản cũ.
  • Tệp khác với tệp đã có nhưng cùng số phiên bản được đánh dấu (same_version_changed) — hãy kiểm nguồn.
ts
const { data: library } = await bx.call("extensions.list", {});

for (const ext of library) {
  const pending = ext.pending_version;
  if (!pending) continue;
  // cho người duyệt xem pending.permissions / pending.added_permissions, rồi:
  await bx.call("extensions.review", {
    path: { id: pending.id },
    body: { decision: "approve", acknowledged_permissions: pending.added_permissions ?? pending.permissions },
  });
}

Thiếu một quyền mà phiên bản thêm vào thì trả VALIDATION_FAILED kèm danh sách quyền đó. activate: false duyệt nhưng chưa chuyển hồ sơ sang bản mới; sau này extensions.set_active chọn phiên bản đã duyệt nào hồ sơ nạp (lùi hoặc tiến).

Gán cho hồ sơ

Tiện ích được nạp ở mọi hồ sơ của không gian, của một nhóm, hoặc một hồ sơ — theo các lượt gán bao phủ hồ sơ đó:

ts
const formatter = (await bx.call("extensions.list", {})).data.find((e) => e.name === "JSON Formatter");

if (formatter) {
  await bx.call("extensions.assign", {
    path: { id: formatter.id },
    body: { target_kind: "profile", target_id: profileId, required: true },
  });
}

const loads = await bx.call("profiles.extensions", { path: { id: profileId } });
console.log(loads.extra.blocked_by); // tên tiện ích bắt buộc đang chặn lượt mở (null khi không có)
  • Bắt buộc (mặc định): hồ sơ không mở khi không nạp được tiện ích — chưa có bản duyệt, hoặc không tải/kiểm được tệp. sessions.start trả VALIDATION_FAILED và nêu tên.
  • Tuỳ chọn: trình duyệt mở mà không có tiện ích đó và phiên báo lý do.
  • extensions.set_state với disabled dừng nạp ở mọi hồ sơ, kể cả nơi gán bắt buộc, cho tới khi bật lại.

Thay đổi có hiệu lực ở lần mở tiếp theo của mỗi hồ sơ; trình duyệt đang mở giữ nguyên những gì nó đã nạp.

Máy chạy làm gì

Máy cần worker 0.15.0 trở lên để nạp tiện ích. Worker cũ hơn không bao giờ nhận tiện ích: hồ sơ có tiện ích bắt buộc không mở trên máy đó (UNSUPPORTED, hãy cập nhật worker), tiện ích tuỳ chọn được báo là bỏ qua.

Khi hồ sơ mở, chỉ máy đang giữ lượt mở đó nhận đường dẫn ngắn hạn tới từng tệp đã duyệt. Máy kiểm kích thước và SHA-256 mà thư viện đã duyệt, giải nén với khoá của chính tiện ích (để mã tiện ích — và dữ liệu tiện ích lưu — giống nhau giữa các máy), giữ bản đã kiểm cho lần mở sau rồi giao cho trình duyệt.

Sau đó sessions.get cho thấy báo cáo của chính máy cho từng tiện ích:

Trạng tháiNghĩa
runningđã thấy trang hoặc tiến trình nền của tiện ích trong trình duyệt
loadedđã giao cho trình duyệt; chưa có phần nào chạy (nhiều tiện ích chỉ chạy trên một số trang)
failedkhông tải, kiểm hoặc nạp được tệp (lý do nêu rõ)
skippedkhông nạp: worker quá cũ, hoặc tiện ích tuỳ chọn không chuẩn bị được

Các lỗi có thể gặp

MãKhi nào
FORBIDDENthêm/gán khi thiếu extension.manage, hoặc duyệt khi thiếu extension.approve
VALIDATION_FAILEDkhông phải tiện ích, thiếu manifest.json, quá 32 MB, tệp của tiện ích khác làm phiên bản mới, duyệt thiếu quyền thêm vào, hoặc tiện ích bắt buộc chặn lượt mở
INTEGRITY_FAILEDchữ ký hoặc mã tiện ích của CRX không khớp nội dung, hoặc bản tải từ cửa hàng không có chữ ký Chrome Web Store
UNSUPPORTEDManifest V2, phiên bản cần Chromium mới hơn, hoặc worker cũ hơn 0.15.0 với tiện ích bắt buộc
NOT_FOUNDChrome Web Store không cung cấp tiện ích đó cho trình duyệt Browlix

Mọi quyết định đều có trong nhật ký kiểm toán: extension.add, extension.approve (kèm các quyền đã thêm), extension.reject, extension.set_active, extension.assign, extension.unassign, extension.disable, extension.enable, extension.delete.