Bläddra i källkod

新增谷歌浏览器插件

kuaifan 5 år sedan
förälder
incheckning
239e4de4fc

+ 9 - 0
resources/notify/README.md

@@ -0,0 +1,9 @@
+# chrome
+
+# TEAM消息通知 chrome扩展
+
+WookTeam的通知提醒, 需要先在网站中登录. 有新通知后会在右下角弹出
+
+有两种提示方式:
+  1. 显示详细通知(默认)(右上角新任务通知数字会消失)
+  2. 只显示通知(不提示具体任务)(试验性质,不建议使用)

BIN
resources/notify/chrome/images/icon-google-white.png


BIN
resources/notify/chrome/images/icon-google.png


BIN
resources/notify/chrome/images/icon-message.png


+ 210 - 0
resources/notify/chrome/js/base.js

@@ -0,0 +1,210 @@
+const $A = {
+    /**
+     * 获取缓存
+     * @param key
+     * @returns {string}
+     */
+    getStorage(key) {
+        return localStorage.getItem(key);
+    },
+
+    /**
+     * 设置缓存
+     * @param key
+     * @param value
+     */
+    setStorage(key, value) {
+        return localStorage.setItem(key, value);
+    },
+
+    /**
+     * 删除缓存
+     * @param keys
+     */
+    removeStorage(keys) {
+        return localStorage.removeItem(keys);
+    },
+
+    /**
+     * 清空缓存
+     */
+    clearStorage() {
+        return localStorage.clear();
+    },
+
+    /**
+     * 显示通知
+     * @param title
+     * @param options
+     * @param link
+     */
+    showNotify(title, options, link) {
+        var notification = new Notification(title, Object.assign({
+            dir: "rtl",
+            lang: "zh-CN",
+            icon: "images/icon-message.png",
+        }, options));
+        notification.onclick = function () {
+            if (link) {
+                window.open(link);
+            }
+        };
+    },
+
+    /**
+     * 地址获取域名
+     * @param url
+     * @returns {string}
+     */
+    getHostname(url) {
+        if (/^chrome:\/\//.test(url)) {
+            return "";
+        }
+        try {
+            var info = new URL(url);
+            return info.hostname;
+        } catch (err) {
+            console.log(err);
+        }
+    },
+
+    /**
+     * 删除地址中的参数
+     * @param url
+     * @param parameter
+     * @returns {string|*}
+     */
+    removeURLParameter(url, parameter) {
+        if (parameter instanceof Array) {
+            parameter.forEach((key) => {
+                url = $A.removeURLParameter(url, key)
+            });
+            return url;
+        }
+        var urlparts = url.split('?');
+        if (urlparts.length >= 2) {
+            //参数名前缀
+            var prefix = encodeURIComponent(parameter) + '=';
+            var pars = urlparts[1].split(/[&;]/g);
+
+            //循环查找匹配参数
+            for (var i = pars.length; i-- > 0;) {
+                if (pars[i].lastIndexOf(prefix, 0) !== -1) {
+                    //存在则删除
+                    pars.splice(i, 1);
+                }
+            }
+
+            return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : '');
+        }
+        return url;
+    },
+
+    /**
+     * 连接加上参数
+     * @param url
+     * @param params
+     * @returns {*}
+     */
+    urlAddParams(url, params) {
+        if (typeof params === "object" && params !== null) {
+            url+= "";
+            url+= url.indexOf("?") === -1 ? '?' : '';
+            for (var key in params) {
+                if (!params.hasOwnProperty(key)) {
+                    continue;
+                }
+                url+= '&' + key + '=' + params[key];
+            }
+        }
+        return url.replace("?&", "?");
+    },
+
+    /**
+     * 将一个 JSON 字符串转换为对象(已try)
+     * @param str
+     * @param defaultVal
+     * @returns {*}
+     */
+    jsonParse(str, defaultVal) {
+        if (str === null) {
+            return defaultVal ? defaultVal : {};
+        }
+        if (typeof str === "object") {
+            return str;
+        }
+        try {
+            return JSON.parse(str);
+        } catch (e) {
+            return defaultVal ? defaultVal : {};
+        }
+    },
+
+    /**
+     * 将 JavaScript 值转换为 JSON 字符串(已try)
+     * @param json
+     * @param defaultVal
+     * @returns {string}
+     */
+    jsonStringify(json, defaultVal) {
+        if (typeof json !== 'object') {
+            return json;
+        }
+        try {
+            return JSON.stringify(json);
+        } catch (e) {
+            return defaultVal ? defaultVal : "";
+        }
+    },
+
+    /**
+     * 转数字
+     * @param str
+     * @param fixed
+     * @returns {number}
+     */
+    runNum(str, fixed) {
+        var _s = Number(str);
+        if (_s + "" === "NaN") {
+            _s = 0;
+        }
+        if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) {
+            _s = _s.toFixed(fixed);
+            var rs = _s.indexOf('.');
+            if (rs < 0) {
+                _s += ".";
+                for (var i = 0; i < fixed; i++) {
+                    _s += "0";
+                }
+            }
+        }
+        return _s;
+    },
+
+    /**
+     * 消息内容取描述
+     * @param content
+     * @returns {*}
+     */
+    getMsgDesc(content) {
+        var desc;
+        switch (content.type) {
+            case 'text':
+                desc = content.text;
+                break;
+            case 'image':
+                desc = '[图片]';
+                break;
+            case 'taskB':
+                desc = content.text + " [来自关注任务]";
+                break;
+            case 'report':
+                desc = content.text + " [来自工作报告]";
+                break;
+            default:
+                desc = '[未知类型]';
+                break;
+        }
+        return desc;
+    }
+}

+ 30 - 0
resources/notify/chrome/js/content.js

@@ -0,0 +1,30 @@
+const ___WOOKTEAM_CONTENT_OBJECT = {
+    username: 0,
+    interval: function () {
+        try {
+            var config = JSON.parse(window.localStorage.getItem("__::WookTeam:config"));
+            if (typeof config.username === "string") {
+                if (config.username !== ___WOOKTEAM_CONTENT_OBJECT.username) {
+                    ___WOOKTEAM_CONTENT_OBJECT.username = config.username;
+                    chrome.runtime.sendMessage({
+                        act: 'config',
+                        config: config
+                    }, function (response) {
+                        //console.log(response);
+                    });
+                }
+            }
+        } catch (e) {
+
+        }
+    }
+};
+
+if (window.localStorage.getItem("__::WookTeam:check") === "success") {
+    ___WOOKTEAM_CONTENT_OBJECT.interval();
+    setInterval(___WOOKTEAM_CONTENT_OBJECT.interval, 6000);
+}
+
+
+
+

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 4 - 0
resources/notify/chrome/js/jquery-3.1.1.min.js


+ 132 - 0
resources/notify/chrome/js/notify.js

@@ -0,0 +1,132 @@
+/**
+ * 连接已有会员
+ * @type {*}
+ */
+var tagId = 0;
+const instances = {};
+const updateBadgeNum = function () {
+    var badgeNum = 0;
+    for (var index in instances) {
+        if (!instances.hasOwnProperty(index)) {
+            continue;
+        }
+        badgeNum+= instances[index].unread;
+    }
+    if (badgeNum == 0) {
+        chrome.browserAction.setBadgeText({text: ''});
+    } else {
+        chrome.browserAction.setBadgeText({text: (badgeNum > 99 ? '99+' : badgeNum) + ''});
+    }
+    chrome.runtime.sendMessage({
+        act: 'instances',
+        instances: instances
+    });
+}
+const getBadgeNum = function () {
+    var configLists = $A.jsonParse($A.getStorage("configLists"), {});
+    for (var index in configLists) {
+        if (!configLists.hasOwnProperty(index)) {
+            continue;
+        }
+        const key = index;
+        const config = configLists[key];
+        if (typeof instances[key] === "undefined") {
+            instances[key] = {};
+        }
+        //
+        if (instances[key].username !== config.username) {
+            instances[key].username = config.username;
+            instances[key].token = config.token;
+            instances[key].unread = 0;
+            instances[key].open = false;
+            updateBadgeNum();
+            if (typeof instances[key].ws !== "undefined") {
+                instances[key].ws.config(null).close();
+            }
+            instances[key].ws = new WTWS({
+                username: config.username,
+                token: config.token,
+                url: config.url,
+                channel: 'chromeExtend'
+            }).setOnMsgListener('notify', ['open', 'unread', 'user'], function (msgDetail) {
+                let body = msgDetail.body;
+                if (['taskA'].indexOf(body.type) !== -1) {
+                    return;
+                }
+                switch (msgDetail.messageType) {
+                    case 'open':
+                        instances[key].open = true;
+                        break;
+                    case 'unread':
+                        instances[key].unread = msgDetail.body.unread;
+                        break;
+                    case 'user':
+                        instances[key].unread++;
+                        chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
+                            if ($A.getHostname(tabs[0].url) != key) {
+                                var url = 'http://' + key + '/#/todo?token=' + encodeURIComponent(instances[key].token) + '&open=chat';
+                                $A.showNotify(key, {
+                                    body: $A.getMsgDesc(body),
+                                    icon: body.userimg
+                                }, url);
+                            }
+                        });
+                        break;
+                }
+                updateBadgeNum();
+            }).sendTo('unread', function (res) {
+                if (res.status === 1) {
+                    instances[key].unread = $A.runNum(res.message);
+                    updateBadgeNum();
+                }
+            });
+        }
+    }
+    //
+    tagId++;
+    const tmpID = tagId;
+    setTimeout(function () {
+        if (tmpID === tagId) {
+            getBadgeNum();
+        }
+    }, 5000);
+}
+getBadgeNum();
+
+/**
+ * 监听来自网站的会员信息
+ */
+chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
+    var configLists;
+    if (request.act === "config") {
+        if (sender.tab) {
+            var hostname = $A.getHostname(sender.tab.url);
+            if (hostname) {
+                configLists = $A.jsonParse($A.getStorage("configLists"), {});
+                if (typeof configLists[hostname] !== "object") {
+                    configLists[hostname] = {};
+                }
+                configLists[hostname] = Object.assign(request.config, {
+                    hostname: hostname,
+                });
+                $A.setStorage("configLists", $A.jsonStringify(configLists));
+                sendResponse(configLists);
+            }
+        }
+    } else if (request.act === "getInstances") {
+        sendResponse(instances);
+    } else if (request.act === "delInstances") {
+        configLists = $A.jsonParse($A.getStorage("configLists"), {});
+        if (typeof configLists[request.index] === "object") {
+            delete configLists[request.index];
+            $A.setStorage("configLists", $A.jsonStringify(configLists));
+        }
+        if (typeof instances[request.index] === "object") {
+            if (typeof  instances[request.index].ws !== "undefined") {
+                 instances[request.index].ws.config(null).close();
+            }
+            delete instances[request.index];
+        }
+        updateBadgeNum();
+    }
+});

+ 65 - 0
resources/notify/chrome/js/popup.js

@@ -0,0 +1,65 @@
+chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
+    if (request.act === "instances") {
+        showLists(request.instances);
+    }
+});
+
+chrome.runtime.sendMessage({
+    act: 'getInstances'
+}, function (response) {
+    showLists(response);
+});
+
+function onDelete(index) {
+    chrome.runtime.sendMessage({
+        act: 'delInstances',
+        index: index,
+    });
+}
+
+function showLists(lists) {
+    var html = '';
+    console.log(lists);
+    for (var index in lists) {
+        if (!lists.hasOwnProperty(index)) {
+            continue;
+        }
+        const item = lists[index];
+        html+= '<li class="message_box" data-index="' + index + '" data-token="' + item.token + '">';
+        html+= '<div class="message_username">' + item.username + '</div>';
+        html+= '<div class="message_host">' + index + '</div>';
+        html+= '<div class="message_unread">未读: ' + item.unread + '</div>';
+        html+= '<div class="message_delete">删除</div>';
+        html+= '</li>';
+    }
+    if (!html) {
+        html+= '<li class="message_box">';
+        html+= '<div class="message_loading">没有相关的记录!</div>';
+        html+= '</li>';
+    }
+    $("#message_div").html('<ul>' + html + '</ul>');
+    $("div.message_delete").click(function(){
+        if (confirm("确定要删除此记录吗?")) {
+            onDelete($(this).parents("li").attr("data-index"));
+        }
+    });
+    $("div.message_unread,div.message_host").click(function(){
+        const index = $(this).parents("li").attr("data-index");
+        const token = encodeURIComponent($(this).parents("li").attr("data-token"));
+        chrome.tabs.query({}, function (tabs) {
+            var has = false;
+            tabs.some(function (item) {
+                if ($A.getHostname(item.url) == index) {
+                    chrome.windows.update(item.windowId, {focused: true});
+                    chrome.tabs.highlight({tabs: item.index, windowId: item.windowId});
+                    chrome.tabs.update({url: $A.urlAddParams($A.removeURLParameter(item.url, ['open', 'rand']), {open: 'chat', rand: Math.round(new Date().getTime())})});
+                    return has = true;
+                }
+            });
+            if (!has) {
+                chrome.tabs.create({ url: 'http://' + index + '/#/todo?token=' + token + '&open=chat' })
+            }
+        });
+    })
+}
+

+ 367 - 0
resources/notify/chrome/js/wtws.js

@@ -0,0 +1,367 @@
+/**
+ * WTWS
+ * @param config {username, url, token, channel, logCallback}
+ * @constructor
+ */
+const WTWS = function (config) {
+    this.__instance = null;
+    this.__connected = false;
+    this.__callbackid = {};
+    this.__openNum = 0;
+    this.__autoNum = 0;
+
+    this.__autoLine = function (timeout) {
+        var tempNum = this.__autoNum;
+        var thas = this;
+        setTimeout(function () {
+            if (tempNum === thas.__autoNum) {
+                thas.__autoNum++
+                if (!thas.__config.token) {
+                    thas.__log("[WS] No token");
+                    thas.__autoLine(timeout + 5);
+                } else {
+                    thas.sendTo('refresh', function (res) {
+                        thas.__log("[WS] Connection " + (res.status ? 'success' : 'error'));
+                        thas.__autoLine(timeout + 5);
+                    });
+                }
+            }
+        }, Math.min(timeout, 30) * 1000);
+    }
+    this.__log = function (text, event) {
+        typeof this.__config.logCallback === "function" && this.__config.logCallback(text, event);
+    }
+    this.__lExists = function (string, find, lower) {
+        string += "";
+        find += "";
+        if (lower !== true) {
+            string = string.toLowerCase();
+            find = find.toLowerCase();
+        }
+        return (string.substring(0, find.length) === find);
+    }
+    this.__rNum = function (str, fixed) {
+        var _s = Number(str);
+        if (_s + "" === "NaN") {
+            _s = 0;
+        }
+        if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) {
+            _s = _s.toFixed(fixed);
+            var rs = _s.indexOf('.');
+            if (rs < 0) {
+                _s += ".";
+                for (var i = 0; i < fixed; i++) {
+                    _s += "0";
+                }
+            }
+        }
+        return _s;
+    }
+    this.__jParse = function (str, defaultVal) {
+        if (str === null) {
+            return defaultVal ? defaultVal : {};
+        }
+        if (typeof str === "object") {
+            return str;
+        }
+        try {
+            return JSON.parse(str);
+        } catch (e) {
+            return defaultVal ? defaultVal : {};
+        }
+    }
+    this.__randString = function (len) {
+        len = len || 32;
+        var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678oOLl9gqVvUuI1';
+        var maxPos = $chars.length;
+        var pwd = '';
+        for (var i = 0; i < len; i++) {
+            pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
+        }
+        return pwd;
+    }
+    this.__urlParams = function(url, params) {
+        if (typeof params === "object" && params !== null) {
+            url+= "";
+            url+= url.indexOf("?") === -1 ? '?' : '';
+            for (var key in params) {
+                if (!params.hasOwnProperty(key)) {
+                    continue;
+                }
+                url+= '&' + key + '=' + params[key];
+            }
+        }
+        return url.replace("?&", "?");
+    }
+    this.__isArr = function (obj){
+        return Object.prototype.toString.call(obj)=='[object Array]';
+    }
+
+    /**
+     * 设置参数
+     * @param config
+     */
+    this.config = function (config) {
+        if (typeof config !== "object" || config === null) {
+            config = {};
+        }
+        config.username = config.username || '';
+        config.url = config.url || '';
+        config.token = config.token || '';
+        config.channel = config.channel || '';
+        config.logCallback = config.logCallback || null;
+        this.__config = config;
+        return this;
+    }
+
+    /**
+     * 连接
+     * @param force
+     */
+    this.connection = function (force) {
+        if (!this.__lExists(this.__config.url, "ws://") && !this.__lExists(this.__config.url, "wss://")) {
+            this.__log("[WS] No connection address");
+            return this;
+        }
+
+        if (!this.__config.token) {
+            this.__log("[WS] No connected token");
+            return this;
+        }
+
+        if (this.__instance !== null && force !== true) {
+            this.__log("[WS] Connection exists");
+            return this;
+        }
+
+        var thas = this;
+
+        // 初始化客户端套接字并建立连接
+        this.__instance = new WebSocket(this.__urlParams(this.__config.url, {
+            token: this.__config.token,
+            channel: this.__config.channel
+        }));
+
+        // 连接建立时触发
+        this.__instance.onopen = function (event) {
+            thas.__log("[WS] Connection opened", event);
+        }
+
+        // 接收到服务端推送时执行
+        this.__instance.onmessage = function (event) {
+            var msgDetail = thas.__jParse(event.data);
+            if (msgDetail.messageType === 'open') {
+                thas.__log("[WS] Connection connected");
+                msgDetail.openNum = thas.__openNum;
+                msgDetail.config = thas.__config;
+                thas.__openNum++;
+                thas.__connected = true;
+                thas.__autoLine(30);
+            } else if (msgDetail.messageType === 'back') {
+                typeof thas.__callbackid[msgDetail.messageId] === "function" && thas.__callbackid[msgDetail.messageId](msgDetail.body);
+                delete thas.__callbackid[msgDetail.messageId];
+                return;
+            }
+            if (thas.__rNum(msgDetail.contentId) > 0) {
+                thas.sendTo('roger', msgDetail.contentId);
+            }
+            thas.triggerMsgListener(msgDetail);
+        };
+
+        // 连接关闭时触发
+        this.__instance.onclose = function (event) {
+            thas.__log("[WS] Connection closed", event);
+            thas.__connected = false;
+            thas.__instance = null;
+            thas.__autoLine(5);
+        }
+
+        // 连接出错
+        this.__instance.onerror = function (event) {
+            thas.__log("[WS] Connection error", event);
+            thas.__connected = false;
+            thas.__instance = null;
+            thas.__autoLine(5);
+        }
+
+        return this;
+    }
+
+    /**
+     * 添加消息监听
+     * @param listenerName
+     * @param listenerType
+     * @param callback
+     */
+    this.setOnMsgListener = function (listenerName, listenerType, callback) {
+        if (typeof listenerName != "string") {
+            return this;
+        }
+        if (typeof listenerType === "function") {
+            callback = listenerType;
+            listenerType = [];
+        }
+        if (!this.__isArr(listenerType)) {
+            listenerType = [listenerType];
+        }
+        if (typeof callback === "function") {
+            this.__msgListenerObject[listenerName] = {
+                callback: callback,
+                listenerType: listenerType,
+            }
+        }
+        return this;
+    }
+    this.triggerMsgListener = function (msgDetail) {
+        var key, item;
+        for (key in this.__msgListenerObject) {
+            if (!this.__msgListenerObject.hasOwnProperty(key)) {
+                continue;
+            }
+            item = this.__msgListenerObject[key];
+            if (item.listenerType.length > 0 &&  item.listenerType.indexOf(msgDetail.messageType) === -1) {
+                continue;
+            }
+            if (typeof item.callback === "function") {
+                item.callback(msgDetail);
+            }
+        }
+    }
+    this.__msgListenerObject = {}
+
+    /**
+     * 添加特殊监听
+     * @param listenerName
+     * @param callback
+     */
+    this.setOnSpecialListener = function (listenerName, callback) {
+        if (typeof listenerName != "string") {
+            return this;
+        }
+        if (typeof callback === "function") {
+            this.__specialListenerObject[listenerName] = {
+                callback: callback,
+            }
+        }
+        return this;
+    }
+    this.triggerSpecialListener = function (simpleMsg) {
+        var key, item;
+        for (key in this.__specialListenerObject) {
+            if (!this.__specialListenerObject.hasOwnProperty(key)) {
+                continue;
+            }
+            item = this.__specialListenerObject[key];
+            if (typeof item.callback === "function") {
+                item.callback(simpleMsg);
+            }
+        }
+    }
+    this.__specialListenerObject = {}
+
+    /**
+     * 发送消息
+     * @param messageType       会话类型
+     * - refresh: 刷新
+     * - unread: 未读信息总数量
+     * - read: 已读会员信息
+     * - roger: 收到信息回执
+     * - user: 指定target
+     * - team: 团队会员
+     * @param target            发送目标
+     * @param body              发送内容(对象或数组)
+     * @param callback          发送回调
+     * @param againNum
+     */
+    this.sendTo = function (messageType, target, body, callback, againNum = 0) {
+        if (typeof target === "object" && typeof body === "undefined") {
+            body = target;
+            target = null;
+        }
+        if (typeof target === "function") {
+            body = target;
+            target = null;
+        }
+        if (typeof body === "function") {
+            callback = body;
+            body = null;
+        }
+        if (body === null || typeof body !== "object") {
+            body = {};
+        }
+        //
+        var thas = this;
+        if (this.__instance === null || this.__connected === false) {
+            if (againNum < 10 && messageType != 'team') {
+                setTimeout(function () {
+                    thas.sendTo(messageType, target, body, callback, thas.__rNum(againNum) + 1)
+                }, 600);
+                if (againNum === 0) {
+                    this.connection();
+                }
+            } else {
+                if (this.__instance === null) {
+                    this.__log("[WS] Service not connected");
+                    typeof callback === "function" && callback({status: 0, message: '服务未连接'});
+                } else {
+                    this.__log("[WS] Failed connection");
+                    typeof callback === "function" && callback({status: 0, message: '未连接成功'});
+                }
+            }
+            return this;
+        }
+        if (['refresh', 'unread', 'read', 'roger', 'user', 'team'].indexOf(messageType) === -1) {
+            this.__log("[WS] Wrong message messageType: " + messageType);
+            typeof callback === "function" && callback({status: 0, message: '错误的消息类型: ' + messageType});
+            return this;
+        }
+        //
+        var contentId = 0;
+        if (messageType === 'roger') {
+            contentId = target;
+            target = null;
+        }
+        var messageId = '';
+        if (typeof callback === "string" && callback === 'special') {
+            callback = function (res) {
+                res.status === 1 && thas.triggerSpecialListener({
+                    target: target,
+                    body: body,
+                });
+            }
+        }
+        if (typeof callback === "function") {
+            messageId = this.__randString(16);
+            this.__callbackid[messageId] = callback;
+        }
+        this.__instance.send(JSON.stringify({
+            messageType: messageType,
+            messageId: messageId,
+            contentId: contentId,
+            channel: this.__config.channel,
+            username: this.__config.username,
+            target: target,
+            body: body,
+            time: Math.round(new Date().getTime() / 1000),
+        }));
+        return this;
+    }
+
+    /**
+     * 关闭连接
+     */
+    this.close = function () {
+        if (this.__instance === null) {
+            this.__log("[WS] Service not connected");
+            return this;
+        }
+        if (this.__connected === false) {
+            this.__log("[WS] Failed connection");
+            return this;
+        }
+        this.__instance.close();
+        return this;
+    }
+
+    return this.config(config);
+}

+ 43 - 0
resources/notify/chrome/manifest.json

@@ -0,0 +1,43 @@
+{
+    "manifest_version": 2,
+    "version": "1.0",
+    "name": "TEAM提醒",
+    "description": "TEAM消息提醒",
+    "icons": {
+        "16": "images/icon-google.png",
+        "48": "images/icon-google.png",
+        "128": "images/icon-google.png"
+    },
+    "browser_action": {
+        "default_icon": {
+            "19": "images/icon-google.png",
+            "38": "images/icon-google.png"
+        },
+        "default_title": "TEAM消息提醒",
+        "default_popup": "popup.html"
+    },
+	"content_scripts": [
+        {
+            "js": ["js/content.js"],
+            "matches": [ "https://*/*", "http://*/*" ],
+            "run_at": "document_end"
+        }
+    ],
+	"background": {
+        "scripts": [
+            "js/jquery-3.1.1.min.js",
+            "js/base.js",
+            "js/wtws.js",
+            "js/notify.js"
+        ]
+    },
+	"permissions": [
+        "notifications",
+		"cookies",
+        "storage",
+        "tabs"
+	],
+	"web_accessible_resources": [
+		"images/*.png"
+	]
+}

+ 82 - 0
resources/notify/chrome/popup.html

@@ -0,0 +1,82 @@
+<html>
+<head>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            border: 0;
+        }
+
+        body {
+            margin: 0;
+            padding: 0;
+            width: 380px;
+            border: 0;
+        }
+
+        ul,li {
+            list-style: none;
+        }
+
+        a {
+            text-decoration: none;
+        }
+
+        .message_loading {
+            padding: 6px;
+            font-size: 14px;
+        }
+
+        .message_box {
+            margin: 6px;
+            padding: 6px 10px;
+            border-radius: 4px;
+            color: #ffffff;
+            background-color: #000000;
+            position: relative;
+        }
+        .message_username {
+            font-size: 16px;
+            font-weight: 600;
+            margin-bottom: 8px;
+        }
+        .message_host {
+            color: #888;
+            font-size: 12px;
+            margin-bottom: 2px;
+            cursor: pointer;
+        }
+        .message_unread {
+            position: absolute;
+            top: 6px;
+            right: 6px;
+            height: 18px;
+            line-height: 18px;
+            color: #ffffff;
+            background-color: #ff0000;
+            text-align: center;
+            border-radius: 10px;
+            padding: 1px 6px;
+            font-size: 12px;
+            transform: scale(0.9);
+            z-index: 1;
+            cursor: pointer;
+        }
+        .message_delete {
+            position: absolute;
+            bottom: 6px;
+            right: 12px;
+            color: #cacaca;
+            cursor: pointer;
+        }
+    </style>
+</head>
+<body>
+<div id="message_div">
+    <div class="message_loading">Loading...</div>
+</div>
+<script src="js/jquery-3.1.1.min.js"></script>
+<script src="js/base.js"></script>
+<script src="js/popup.js"></script>
+</body>
+</html>