2018-11-14

Thử viết chrome extension

Mở đầu

Đối với các công ty cung cấp dịch vụ B2B, khách hàng là thượng đế, mà là thượng đế thì phải support 1 cách triệt để, với 1 thái độ niềm nở và nhiệt tình. Mà để có thể support các thượng đế 1 cách triệt để, tận tình nhất thì việc mà phải sờ vào các trang quản lý của khách hàng bằng tài khoản Root là chuyện đương cmn nhiên.
Tuy nhiên, chính vì sử dụng tài khoản Root này, thế nên đôi lúc cũng có chuyện rắc rối nó xảy ra.
Chuyện là có 1 thanh niên đang sửa 1 cái bug cho hệ thống, thì lúc ấy có contact từ thượng đế đến, báo có lỗi. Ngay lập tức, thanh niên dừng lại và bật trang quản lý của thượng đế lên bằng tài khoản Root. Sau khi tìm hiểu xong lỗi, báo cáo xong đến thượng đế xong thì không tắt trang quản lý đi mà bố tiếp tục fix bug. Và kết cục là: khi test, bố test nhầm cmn trên trang của thượng đế. Rất may là phát hiện kịp thời và dừng đúng lúc nên data của thượng đế không bị ảnh hưởng. Phù....
Sau khi việc ấy xảy ra, team đứng lại thành vòng tròn, nghiêm túc kiểm điểm, rút sợi dây kinh nghiệm, và tìm cách để tránh các miss tương tự xảy ra. Có mấy cách được đưa ra như sau:
1.Dev thêm chức năng đối với Root thì thay đổi luôn trang thành 1 màu khác hẳn, đưa ra warning message: 「Mày đang thực hiện với tài khoản Root đấy, cấm động đến mấy nút submit của form!!! Đứa nào lỡ động đến phát 1000¥.」
→ Cách này có vẻ ổn. Thế nhưng mà dev thì cũng phải đợi đến lúc release xong thì mới thực hiện được. Mà cái lịch release thì chưa rõ. Thế nên từ lúc này đến lần release tiếp theo cũng sẽ có khả năng xảy ra cái miss tương tự.
2.Viết 1 cái extension của chrome be bé, cài trên các máy của mấy thằng trong team tech. Nếu mà đang thao tác trên trang của thượng đế thì sẽ disable các nút submit, hiển thị warning message. 【Cái này do bần tăng đề xuất】
→ cái này có thể làm nhanh được, tuy không giải quyết được triệt để vấn đề nhưng có lẽ cũng giảm bớt được phần nào đấy, đợi cho đến lúc release xong.
Thế là bần tăng bắt tay vào tìm hiểu và thử viết xem.

Chức năng của extension

  • Check cái site hiện tại có phải là trang của thượng đế không.
    • Nếu là trang của thượng đế thì vào trang nào sẽ disable hết các nút submit và hiển thị message bên dưới nút submit ấy.
    • Nếu không thì cho thao tác bình thường

Bắt đầu

1. Tìm hiểu cách viết extension


Đọc qua thì ngộ ra mấy cái điều đơn giản như sau:
  • Cái chrome extension nó đơn giản là 1 tập các HTML, CSS, Javascript, images và các file khác đã được zip lại, và có thể áp dụng cho Google Chrome Browser.
  • Cái kiến trúc của nó gồm những file chính sau:
    • manifest.json: Nó như kiểu file config của extension. Cách config có thể xem tại đây
    • Background Script: Event handler của extensions.
    • UI Elements: Will Update..................
    • Content Script: Will Update..................
    • Options Page: Will Update..................
Đến đoạn này thì hơi ngộ ngộ ra được 1 điều gì đấy mà không biết là cái gì nữa. Thôi kệ. Bắt tay vào làm thử.

2.Giờ mới bắt đầu thật

  • Làm giống hệt như trong tutorial của nó với các bước như sau:
mkdir miss_prevent_extension
cd miss_prevent_extension
vi manifest.json
(Thật ra chỗ này bần tăng viết vi cho vui thôi, vì nghe các anh em nói dùng vi thì có vẻ pro lắm thế nên làm màu tí, chứ thật sự ra thì bần tăng dùng atom :kissing:)
  • Paste nội dung dưới đây vào manifest.json, đổi cái name với lại description rồi hãy save lại để người ta biết là mình viết chứ không copy. :sweat_smile:
{
  "name": "Miss action preventation",
  "version": "1.0",
  "description": "By Baka Nobita",
  "manifest_version": 2
}
  • Import thử vào trong chrome xem thế nào. Cách import xem ở đây Thấy nó hiện ra như thế này.

Thấy có tên mình ở trên chrome rồi. :sunglasses: Thế là lại có hứng làm tiếp
  • Tạo file background.js (Thật ra đối với cái extension này cũng không cần phải background.js làm gì cả, thế nhưng mà cứ copy vào xem cái flow chạy của nó thế nào)
vi background.js
  • Paste đoạn sau vào background.js và save lại.
chrome.runtime.onInstalled.addListener(function() {
  chrome.storage.sync.set({color: "#3aa757"}, function() {
    console.log("The color is green.");
  });
});
  • Thêm thuộc tính permissions và background vào trong manifest.json
{
  "name": "Miss action preventation",
  "version": "1.0",
  "description": "By Baka Nobita",
  "permissions": ["storage"],
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "manifest_version": 2
}
  • Click vào nút reload extension thì thấy hiện ra cái này

  • Tiếp đến là tạo giao diện popup khi click vào icon trên browser Về ý tưởng giao diện thì khi click vào icon trên browser sẽ hiển thị 2 cái textarea. 1 để lưu các link của production, 2 là để lưu các selector mà muốn disable đi
Để làm được như thế, tạo file popup.html với nội dung như sau:
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Miss action preventation</title>
  </head>
  <body>
    <h3>Production URLS</h3>
    <textarea rows="4" cols="50" id="production-urls"></textarea>

    <h3>Selectors (どんなElementを操作するとき、アラートを出したいのか)</h3>
    <textarea rows="4" cols="50" id="elements"></textarea>

    <button id="save">Save</button>
    <script src="popup.js"></script>
  </body>
</html>
  • Để ý trong file html trên có đoạn <script src="popup.js"></script>. Do đó, chúng ta phải tạo file popup.js với nội dung như sau:
console.log("popup.js");

// Đoạn này xử lý khi click vào nút `Save`
document.getElementById("save").onclick = function() {
  console.log("on click save")
  var urls = document.getElementById("production-urls").value;
  var elements = document.getElementById("elements").value;

  // Đoạn này là để save giá trị của 2 textarea vào storage 
  chrome.storage.sync.set({
    "production_urls": urls,
    "elements": elements
  }, function() {
    console.log("Settings saved");
  });
}

// Đoạn này xử lý để load giá trị đã lưu trong storage vào 2 text_area product-urls và elements
chrome.storage.sync.get(["production_urls", "elements"], function(items) {
  var default_elements = ["input[type=submit]", "button"]

  document.getElementById("production-urls").value = items["production_urls"] || "";
  document.getElementById("elements").value = items["elements"] || default_elements.joins("\n");
});
  • Chỉnh sửa permissions, thêm thuộc tính browser_action trong manifest.json
{
  "name": "Miss action preventation",
  "version": "1.0",
  "description": "By Baka Nobita",
  "permissions": ["declarativeContent", "storage"],
  "browser_action": {
    "default_popup": "popup.html"
  },
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  },
  "manifest_version": 2
}
  • Reload lại rồi chạy thử. :triumph:

Sau khi click save → đã chạy được như thế này

Ok. Vậy là cái vấn đề lưu dữ liệu cho extension là ok.
Vấn đề tiếp theo là làm thế nào để lấy dữ liệu ấy ra và áp dụng để chạy cho các trang urls đã nhập đó.
  • Trong file manifest.json, định nghĩa thêm như sau:
{
  "name": "Miss action preventation",
  ...
  "content_scripts": [
  {
    "matches": ["https://*/*"],
    "js": ["content_script.js"]
  }],
  ...  
}
Cái này theo bần tăng nó có ý nghĩa là: Chạy cái file content_script.js cho bất kì 1 cái link nào có pattern là https://*/*
  • Tạo file content_script.js
// Lấy data đã lưu trong storage ra để xử lý

console.log("content_script.js");
chrome.storage.sync.get(["production_urls", "elements"], function(items) {
  var production_urls = (items["production_urls"] || "").split("\n");
  var uk_elements = (items["elements"] || "").split("\n");

  console.log(production_urls);
  console.log(elements);

  var isProductionUrl = false;

  // Loại trừ trang /admin/sign_in
  if (window.location.href.indexOf("/admin/sign_in") > 0) {return;}

  // Check kiểm tra xem là cái address hiện tại có phải là url production không
  for (var i = 0; i < production_urls.length; i++) {
    if (production_urls[i].indexOf(window.location.host) >= 0) {
      isProductionUrl = true;
    }
  }

  // Nếu  link hiện tai là production
  if (isProductionUrl) {
    var elements = [];

    for (var i = 0; i < uk_elements.length; i++) {
      var selectors = "";
      if (uk_elements[i]) {
        selectors = document.querySelectorAll(uk_elements[i]);
        elements.push(Array.from(selectors));
      }
    }

     // Disable và hiển thị message bên dưới.
    addAlertWhenClickToElement(elements.flat());
  }
});

function addAlertWhenClickToElement(elements) {
  for (var i = 0; i < elements.length; i++) {
    var e = elements[i];
    e.disabled = true;
    var alertNode = document.createElement("p");
    alertNode.style = "color: red";
    alertNode.innerHTML = "Mày đang thực hiện với tài khoản Root đấy, cấm động đến mấy nút submit của form!!! Đứa nào lỡ động đến phát 1000¥.";
    e.parentElement.appendChild(alertNode);

    e.onclick = function(){
      alert("Mày đang thực hiện với tài khoản Root đấy, cấm động đến mấy nút submit của form!!! Đứa nào lỡ động đến phát 1000¥.");
    }
  }
}
  • Reload lại và chạy thử thì được như sau:

Thế là xong.

Kết

Đây cũng chỉ là 1 cái extension sơ sinh, so với cái Checker Plus for Gmail hay các extension khác thì cũng không đáng nói gì.
Cái quan trọng là đã hiểu được qua cấu trúc viết chrome extension như thế nào. Và nó cũng tạm thời giải quyết được cái vấn đề bần tăng đề cập lúc ban đầu. Với lại từ giờ lúc đi với các đồng đạo thì cũng có thể có cái chém gió được rồi.
:smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp::smiling_imp:
Bần tăng xin được phép kết thúc ở đây.
Xin chào và hẹn gặp lại các hạ ở các bài viết tiếp theo.

alt text