How to create a Google Chrome Extension : case study "Chatwork Emoticons Plus"

Chapter 0: Reversing Chatwork for fun

Nếu bạn chỉ quan tâm đến cách tạo extension, bạn có thể bỏ qua phần này và tiến thẳng đến Chapter 1
  • Chatwork là một công cụ dùng để liên lạc, chhat voice hoặc video, chia sẻ file, tạo và quản lý tác vụ giúp cho việc trao đổi giữa các thành viên trong team trở lên thông suốt và nâng cao hiệu suất công việc. Chatwork có đi kèm sẵn một bộ emoticons tuy nhiên nó hơi nghèo nàn (và có thể bạn thích bộ emoticon của skype hơn 😄). Hãy thử xem chúng ta có thể thêm bộ emoticon vào cho Chatwork hay không 😉

  • Việc đầu tiên đó là bật Chatwork lên và ... view source 😄. Chưa thấy gì hay ho ngoài đoạn tin tuyển dụng bằng comment HTML và một đống biến javascript với nhiều thông tin, chắc chắn để dùng cho file javascript khả nghi ở gần cuối trang:

<script src="./javascript/new/chatwork_all_ja.min.js?1406104323"</script>

Save file về và mở ra, ô la la, file javascript min 😐. Dùng plugin Javascript Beautify của Sublime Text hoặc http://jsbeautifier.org/, mọi thứ trông đã sáng sủa hơn. Pha 1 cốc cà phê và ngồi đọc code thôi.

Ngay đầu file là biến L chứa các emo của Chatwork và sau một hồi search với vài keyword như "emo", "smile", và đọc hiểu code, ta có thể tóm tắt như sau:

  • Có một biến global là CW được khởi tạo bởi:
CW = new ChatWork(client_ver),
  • Trong hàm này, ta thấy có hàm prepare với biến a.emoticon là một mảng chứa các thông tin của các emoticon, lấy ví dụ:
{
    title: L.emoticon_smile,
    key: ":)",
    regex: /:\)/g,
    src: "emo_smile.gif"
}

được sử dụng ở đoạn code sau trong hàm prepare_replace_code bên trong hàm CW.prepareRegExp():

for (var e = 0, f = a.length; e < f; e++) {
    var g = d(a[e].key);
    b.reg_cmp.push({
        key: a[e].regex,
        rep: '<img src="./image/emoticon/' + a[e].src + '" title="' + a[e].title + " " + g + '" alt="' +
            g + '" class="ui_emoticon"/>',
        reptxt: a[e].key
    })
}

mỗi khi ta gõ một emo thì javascript của Chatwork sẽ dùng regex ở trong biến reg_cmp của b (chính là biến CW global) để so sánh và thay thế text emo bằng đoạn HTML để hiện thị ảnh emo tương ứng. Test thử trên console của browser:

> CW
> Object { version: "1.80a", api_version: 4, announce_id: 38, is_first_load: false, init_loaded: true, stop_watching: false, watching: false, watch_timer: 49, watch_lasttime: 0, online_status_timer: null, 95 more… }
> CW.reg_cmp
> Array [ Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, 58 more… ]
CW.reg_cmp[44]
> Object { key: /8-\)/g, rep: "![8-)](./image/emoticon/emo_lucky.gif "やったね 8-)")", reptxt: "8-)" }

Thử push thêm một emo mới vào mảng xem sao:

CW.reg_cmp.push({
	key: /:-L/g,
	rep: '<img src="https:\/\/s.yimg.com/lq/i/mesg/emoticons7/62.gif" title=":-L" alt=":-L" class="ui_emoticon"/>',
	reptxt: ":-L"
})

gõ vào Chatwork test thử.

test_emo.png

Hurray, It works like a charm. Tuy nhiên, nếu mỗi lần bật Chatwork lên lại phải gõ code thì hơi thủ công. Ta cần chạy đoạn script tự động mỗi lần bật Chatwork lên. Đây là lúc ta cần đến Extension.

Chapter 1: Build emoticon extension with Chrome

Về cơ bản thì extension là một html đoạn javascript nhỏ được nhúng vào trong trang web mà ta muốn để thực hiện một công việc nào đó, ví dụ như ở đây là chèn thêm một phần tử và trong biến global của trang web. Về hướng dẫn cụ thể và các thông tin chi tiết, bạn có thể xem thêm tại trang https://developer.chrome.com/extensions/getstarted, ở đây mình sẽ chỉ đề cập đến những thứ thiết yếu để build được một extension.

File tất yếu cần thiết cho một extension là file manifest.json. File này chứa các thông tin khai báo về extension của bạn, dưới đây là file manifest.json của extension Chatwork Emoticon Plus:

manifest.json

{
  "name": "Chatwork Emoticons Plus",
  "version": "0.2.3",
  "manifest_version": 2,
  "description": "Add Skype and custom emoticons to Chatwork. feedback to [email protected]",
  "content_scripts": [
    {
      // Change 'matches' attribute to load content
      // script only in pages you want to.
      "matches": ["https://www.chatwork.com/*", "https://kcw.kddi.ne.jp/*"],
      "js": ["jquery-1.11.1.min.js", "emo.js", "contentscript.js"],
      "run_at": "document_end"
    }
  ],
  "web_accessible_resources": ["emo.js"],
  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "icons": {
    "48": "icon.png"
  },
  "update_url": "http://cwep.thangtd.com/update-xml/updates.xml",
  "homepage_url": "http://cwep.thangtd.com"
}

Chúng ta sẽ lần lượt đi các mục trong file này:

  • manifest_version : chỉ định phiên bản schema của file manifest.

  • name: tên của extension

  • version : phiên bản hiện tại của extension, trường này sẽ cần thiết về sau khi bạn muốn cập nhật extension hoặc thiết lập tự động cập nhật cho extension của mình

  • description : miêu tả về extension

  • content_scripts : đây là mục khai báo các thành phần javascript sẽ được sử dụng trong extension của bạn. nếu file js không có trong này, nó sẽ ko chạy được

  • matches : mảng các regex chỉ định domain mà bạn muốn chạy extension trên đó. Trường này rất quan trọng, nếu chỉ định ko tốt, các đoạn javascript của bạn có thể chạy không như ý và gây lỗi cho các trang web khác. Như ở đây, ta chỉ muốn javascript chạy trên domain của Chatwork và KDDI Chatwork.

  • js : khai báo các file js sẽ được sử dụng trong extension. File js chạy chính sẽ là file contentscript.js. Ta sẽ nói cụ thể hơn ở sau đây.

  • run_at : chỉ định js sẽ được chạy vào lúc nào.

  • web_accessible_resources : chỉ định các file mà extension có thể truy cập đến.

  • browser_action : Mục này sẽ tạo thêm một icon ở trên thanh tool-bar của Google Chrome để ta có thể click và xem thông tin. Xem thêm tại đây

    • default_icon: icon được hiển thị trên tool bar của Google Chrome
    • default_popup: phần pop-up sẽ hiển thị khi bạn click vào icon của extension
  • icons: các icon sẽ được sử dụng tron extension đi kèm với kích cỡ tương ứng.

  • update_url: thiết lập cho phần auto-update (sẽ nói ở phần sau)

  • homepage_url: Trang chủ của extension Ngoài ra có còn nhiều thuộc tính khác nữa, bạn có thể xem thêm tại đây. Tóm lại, ta sẽ có cấu trúc của thư mục extension như sau.

CWEP/
├── contentscript.js`
├── emo.js
├── icon.png
├── jquery-1.11.1.min.js
├── manifest.json
└── popup.html

Extension sẽ dùng file contentscript.js để inject nội dung của file emo.js ngay sau khi trang Chatwork được chạy xong (lúc đã khởi tạo biến CW). (Tham khảo tại: https://stackoverflow.com/questions/9515704/building-a-chrome-extension-inject-code-in-a-page-using-a-content-script

File: contentscripts.js

var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.extension.getURL('emo.js');
s.onload = function() {
    this.parentNode.removeChild(this);
};
(document.documentElement).appendChild(s);

File này sẽ có nhiệm vụ append đoạn javascipt ở trong file emo.js vào trong trang web một cách "dynamic". File emo.js sẽ chứa đoạn code chính của chúng ta. File emo.js (lược bỏ bớt một số emoticon cho đỡ dài 😄)

emo = [
    {key: "(facepalm)", regex: /\(facepalm\)/g, src: "facepalm.gif", other_host: false},
    {key: "(facepalm2)", regex: /\(facepalm2\)/g, src: "facepalm2.gif", other_host: false},
    {key: "(dull)", regex: /\(dull\)/g, src: "dull.gif", other_host: false},
    {key: "(chopmat)", regex: /\(chopmat\)/g, src: "https:\/\/s.yimg.com/lq/i/mesg/emoticons7/5.gif", other_host: true},
    {key: "(cuoideu)", regex: /\(cuoideu\)/g, src: "https:\/\/s.yimg.com/lq/i/mesg/emoticons7/71.gif", other_host: true},
    {key: "(pray)", regex: /\(pray\)/g, src: "https:\/\/s.yimg.com/lq/i/mesg/emoticons7/63.gif", other_host: true},
    {key: "(bowbowbow)", regex: /\(bowbowbow\)/g, src: "https:\/\/s.yimg.com/lq/i/mesg/emoticons7/77.gif", other_host: true},
    {key: "(dancing)", regex: /\(dancing\)/g, src: "https:\/\/s.yimg.com/lq/i/mesg/emoticons7/69.gif", other_host: true},
];

function htmlEncode(value){
    //create a in-memory div, set it's inner text(which jQuery automatically encodes)
    //then grab the encoded contents back out.  The div never exists on the page.
    return $('<div/>').text(value).html();
}

function htmlDecode(value){
    return $('<div/>').html(value).text();
}

$(window).ready(function(){
    CW.prepareRegExp();
    for (var index = 0; index < emo.length; index++) {
        var encoded_text = htmlEncode(emo[index].key);
        if (emo[index].other_host) {
            img_src = emo[index].src;
        } else {
            img_src = 'http:\/\/cwep.thangtd.com/img/emoticons/' + emo[index].src;
        }

        CW.reg_cmp.push({
            key: emo[index].regex,
            rep: '<img src="' + img_src + '" title="' + encoded_text + '" alt="' +
                encoded_text + '" class="ui_emoticon"/>',
            reptxt: emo[index].key
        });
    }
});

Ta lưu các thông tin vào biến emo để sau này tiện cho việc thêm emo mới. Ngoài ra còn có hàm bổ trợ để encode html. Phần quan trọng nằm ở phía dưới. Ta chạy lại hàm CW.prepareRegExp để đảm bảo biến reg_cmp đã được khởi tạo và sau đó push thêm các emo vào. Easy as eat a candy phải ko nào ?

Ta thêm file popup.html là một file đơn giản có link đến trang chủ của emoticon.

File popup.html

<!DOCTYPE html>
<html>
<body style="width: 250px">
<strong>List all emoticons :</strong><br><a href="http://cwep.thangtd.com/#emoticons">http://cwep.thangtd.com/#emoticons</a>
</body>
</html>

OK, đến lúc test extension rồi. Đầu tiên là ta cần vào chrome://extensions/, bật Developer mode và Load unpacked extenssion..., chọn thư mục CWEP chứa các file của extension:

loaded_ext

Khi click vào icon của extension ở trên thanh công cụ:

emo_ext_click.png

Sau khi thỏa mãn với kết quả, ta có thể pack extension lại thành file crx (bản chất là một file zip) kèm theo đó là file private.pem là file private-key dùng để sign cho extension, ta sẽ cần file này khi muốn cập nhật phiên bản (sẽ nói sau đây). Đến lúc up lên Google Chrome Store (nếu bạn có $5 trả phí Developer registration) hoặc truyền tay cho bạn bè sử dụng.

Auto-update extension for your conveinent

Theo thời gian, ta sẽ cần cập nhật thêm các emo mới cho extension. Để đảm bảo các người dùng đang sử dụng extension đều nhận được bản cập nhật một cách tự động, ta cần sử dụng tính năng auto-update. Sau đây là các bước thiết lập cần thiết để extension có thể được cập nhật tự động:

1 . Thiết lập mục update_url trong file manifest.json, đường dẫn đến file updates.xml:

<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
    <app appid='hmjjnpcjannffhanjcoecmdbjehienhp'>
        <updatecheck codebase='http://cwep.thangtd.com/release/CWEP.crx' version='0.2.4' />
    </app>
</gupdate>

Ta cần chú ý các trường sau:

  • appid: là id của extension, hiển thị ở danh sách các extension của chrome ở trang chrome://extensions.
  • updatecheck codebase: đường dẫn đến file crx phiên bản mới
  • version: chỉ số phiên bản mới của extension. Ví dụ phiên bản hiện tại đang là 0.2.3, bạn muốn nâng cấp lên 0.2.4 thì cần đặt là "0.2.4".

2 . Build file crx cho phiên bản mới và sử dụng file private key trước đó để ký cho extension, và đặt vào đường dẫn được thiết lập bên trên. Trong file manifest.json của phiên bản mới cần để là "version": "0.2.4" (tương ứng ở trong file updates.xml)

Sau khi đã thiết lập xong xuôi, ta có thể test thử bằng cách chọn "Update extensions now" ở Developer mode. Ngay lập tức version sẽ được cập nhật từ 0.2.3 thành 0.2.4. That's all.

Vậy là ta đã xây dựng thành công một extension thêm một số emoticon cho Chatwork. Souce code có thể tham khảo tại: https://github.com/vigov5/cwep.

Bạn có thể tải về bản mới nhất của Chatwork Emoticon Plus tại: http://cwep.thangtd.com/

For Firefox's fan

Tại địa chỉ http://builder.extensionfactory.com/builder/home.html cho phép người dùng chuyển một extension từ Chrome sang Firefox. Việc phát triển 1 add-on tương tự trên Firefox thì phức tạp hơn nhiều so với Chrome nên sẽ được chuyển sang bài blog ở đây.

Some note about Google Chrome extension

Kể từ phiên bản Google Chrome 3, trình duyệt trên Windows sẽ chỉ cho phép cài đặt các extension được phân phối qua Google Chrome Web Store. Bởi vậy các duy nhất để chạy được các extension tự làm là phải unpack và load unpacked extension vào. Điều này chỉ ảnh hưởng đến các người dùng Windows, Mac và Linux vẫn hoạt động bình thường.


All Rights Reserved