balidrop-chrome开发概述

2021年1月4日 作者 陈益
  1. 定义应用的描述信息和插件的图标以及权限和prop说明。

  2. 动态的加载background.js。

    这里采用动态的加载background.js主要是有助于代码的修改,默认是服务器下面的 “/borwser-google/background.js”,在background.js中主要执行了以下初始化:

  3. 根据版本号缓存将要注入页面的JS脚本,例如,淘宝,天猫,京东,速卖通等JS脚本。

    owner.asyncAjaxResources = function(func) {
        //想获取本地的版本控制名称
        owner.getLocalStorage(owner.CACHE_JS_VERSION_DATAS_NAME, function(storeData) {
            //从后台服务器获取版本文件
            owner.requestServer4StaticResource("/borwser-google/browser-json.json?random=" + Math.random(), "json", function(callbackData) {
                //查看当前的版本文件是否为空
                if(callbackData && (typeof callbackData == 'object') && (!jQuery.isEmptyObject(callbackData)) && (!(callbackData instanceof Array))) {
                    //开始比较数据
                    var newStoreData = {};
                    var nameVersionDatas = callbackData;
                    if(jQuery.isEmptyObject(storeData)) { //清空
                        //开始存入值
                        for(var key in nameVersionDatas) {
                            var nameVersionDataArray = nameVersionDatas[key];
                            var newVersionDataArray = [];
                            for(var i = 0; i < nameVersionDataArray.length; i++) {
                                var nameVersionData = nameVersionDataArray[i];
                                var newVersionData = {
                                    'ov': 0,
                                    'path': nameVersionData['path'],
                                    'nv': nameVersionData['version'],
                                    'js': "",
                                    'match': nameVersionData['match']
                                }
                                newVersionDataArray.push(nameVersionData);
                            }
                            //保存数据
                            newStoreData[key] = newVersionDataArray;
                        }
                    } else { //只更新部分
                        for(var key in nameVersionDatas) {
                            if(key in storeData) { //之前已经存在,那么只写入版本更新
                                var sd = storeData[key];
                                var newVersionDataArray = [];
                                var nameVersionDataArray = nameVersionDatas[key];
                                for(var i = 0; i < nameVersionDataArray.length; i++) {
                                    var nameVersionData = nameVersionDataArray[i];
                                    var path = nameVersionData['path'];
                                    //之前的数据
                                    var oldData = owner.findMatchJsByKeyFromArray(sd, path, "path");
                                    //说明之前没有事新增的
                                    if((!oldData) || (jQuery.isEmptyObject(oldData))) {
                                        var newVersionData = {
                                            'ov': 0,
                                            'path': nameVersionData['path'],
                                            'nv': nameVersionData['version'],
                                            'js': "",
                                            'match': nameVersionData['match']
                                        }
                                    } else {
                                        var newVersionData = {
                                            'ov': oldData['ov'],
                                            'path': nameVersionData['path'],
                                            'nv': nameVersionData['version'],
                                            'js': oldData['js'],
                                            'match': nameVersionData['match']
                                        }
                                        newVersionDataArray.push(newVersionData);
                                    }
                                }
                                newStoreData[key] = newVersionDataArray;
                            } else { //新增的
                                for(var key in nameVersionDatas) {
                                    var nameVersionDataArray = nameVersionDatas[key];
                                    var newVersionDataArray = [];
                                    for(var i = 0; i < nameVersionDataArray.length; i++) {
                                        var nameVersionData = nameVersionDataArray[i];
                                        var newVersionData = {
                                            'ov': 0,
                                            'path': nameVersionData['path'],
                                            'nv': nameVersionData['version'],
                                            'js': "",
                                            'match': nameVersionData['match']
                                        }
                                        newVersionDataArray.push(nameVersionData);
                                    }
                                    //保存数据
                                    newStoreData[key] = newVersionDataArray;
                                }
                            }
                        }
                    }
                    //开始进行存储
                    owner.setLocalStorage(owner.CACHE_JS_VERSION_DATAS_NAME, newStoreData, function() {
                        //开始进行执行调用
                        if(typeof func == 'function') {
                            func();
                        }
                    });
                }
            });
        });
    }
  4. 监听页面的变化根据链接向页面中注入对应的执行脚本。

    app.asyncAjaxResources(function() {
    //增加监听操作
    chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
        //查看当前是否已经登录了,如果登录了,那么
        app.getLocalStorage("customer", function(customer) {
            if(customer && customer['id']) {
                var status = changeInfo.status;
                var currentUrl = tab.url;
                //获取Storage数据
                app.getLocalStorage(app.CACHE_JS_VERSION_DATAS_NAME, function(storeData) {
                    //开始加入自定义脚本
                    var hostDataMap = app.findExecuteJSByUrl(currentUrl, storeData);
                    if(hostDataMap && (!jQuery.isEmptyObject(hostDataMap))) {
                        var hostData = hostDataMap['value'];
                        var hostKey = hostDataMap['key'];
                        var index = hostDataMap['index'];
                        if(status == 'loading') {
                            chrome.tabs.insertCSS(tabId, {
                                "file": "css/iziToast.min.css"
                            });
                            chrome.tabs.insertCSS(tabId, {
                                "file": "css/loaders.css"
                            });
                            chrome.tabs.insertCSS(tabId, {
                                "file": "css/balidrop-modal.css"
                            });
                            chrome.tabs.executeScript(tabId, {
                                "file": "js/iziToast.min.js"
                            });
                            chrome.tabs.executeScript(tabId, {
                                "file": "js/loaders.js"
                            });
                            chrome.tabs.executeScript(tabId, {
                                "file": "js/balidropModal.js"
                            });
                            //如果category 存在且数组大于0
                            if(customer['category'] && customer['category'].length > 0) {
                                var datas = [{
                                    id: 'root',
                                    pid: -1,
                                    title: '不分类'
                                }];
                                var categorys = customer['category'];
                                app.reCategorys(datas, categorys);
                                var balidropTreeDatas = {
                                    'title': "选择产品类型",
                                    'closeBtnText': '关闭',
                                    'okBtnText': '确定',
                                    'datas': datas
                                }
                                chrome.tabs.executeScript(tabId, {
                                    "code": "var balidropTreeDatas=" + JSON.stringify(balidropTreeDatas)
                                });
                                chrome.tabs.executeScript(tabId, {
                                    "file": "js/blidropTree.js"
                                });
                            }
                            //添加loadding等待框
                            var executeCode = "function waitLoadding(){var isLoadingInteval=window.setInterval(function(){if(window.pageLoader){window.clearInterval(isLoadingInteval);var containerDiv=document.getElementById('loader-container-2018');if(!containerDiv){window.pageLoader.show(15000)}}},500)}waitLoadding();";
                            var serchaDataJson = app.getParameters(currentUrl);
                            if(serchaDataJson && serchaDataJson['items']) {
                                var loadText = '批量选品中<span class=batchLoadding >0/' + (serchaDataJson['items'].split(",")).length + '</span>,请不要关闭该窗口';
                                executeCode = "function waitLoadding(){var isLoadingInteval=window.setInterval(function(){if(window.pageLoader){window.clearInterval(isLoadingInteval);var containerDiv=document.getElementById('loader-container-2018');if(!containerDiv){window.pageLoader.show(600000,'" + loadText + "', 'line-scale-pulse-out')}}},500)} var containerDiv=document.getElementById('loader-container-2018'); if(!(containerDiv&&containerDiv.style.display=='none')){waitLoadding()};";
                            }
                            chrome.tabs.executeScript(tabId, {
                                "code": executeCode,
                                "runAt": "document_end",
                                "matchAboutBlank": true
                            });
                        } else if(status == 'complete') {
                            //获取最新脚本数据
                            if((hostData['ov'] != hostData['nv']) || (!hostData['js']) || (app.isDebug)) {
                                app.requestServer4StaticResource(hostData['path'] + "?random=" + Math.random(), "text", function(callbackData) {
                                    if(callbackData) {
                                        hostData['ov'] = hostData['nv'];
                                        hostData['js'] = callbackData;
                                        //开始进行保存
                                        storeData[hostKey][index] = hostData;
                                        //保存且执行监听
                                        app.setLocalStorage(app.CACHE_JS_VERSION_DATAS_NAME, storeData, function() {
                                            chrome.tabs.executeScript(tabId, {
                                                "code": hostData['js'],
                                                "runAt": "document_end",
                                                "matchAboutBlank": true
                                            });
                                        });
                                    }
                                });
                            } else {
                                if(hostData['js']) {
                                    chrome.tabs.executeScript(tabId, {
                                        "code": hostData['js'],
                                        "runAt": "document_end",
                                        "matchAboutBlank": true
                                    });
                                }
                            }
                        }
                    }
                });
            }
        });
    });
    });
  5. 监听页面上提交 代购、刊登等 商品的信息将其提交到balidrop服务器。

    //监听,且提交到服务器
    chrome.extension.onMessage.addListener(function(request, sender, callback) {
    
    if(typeof request == 'string') {
        request = JSON.parse(request);
    }
    
    var dataType = request.dataType;
    var datas = request.datas;
    var tabId = sender.tab.id;
    
    //校验
    if(!datas) {
        chrome.tabs.executeScript(tabId, {
            "code": "iziToast.error({title: 'Balidrop',message: 'Please wait for loading to complete',position: 'topRight',transitionIn: 'bounceInLeft'})"
        });
        callback("success");
        return true;
    } else {
        try {
            var validate = (typeof datas == 'string') ? JSON.parse(datas) : JSON.stringify(datas);
        } catch(e) {
            chrome.tabs.executeScript(tabId, {
                "code": "iziToast.error({title: 'Balidrop',message: 'Please wait for loading to complete',position: 'topRight',transitionIn: 'bounceInLeft'})"
            });
            callback("success");
            return true;
        }
    }
    switch(dataType) {
        case 'Product':
    
            chrome.tabs.executeScript(tabId, {
                "code": "pageLoader.show();"
            });
    
            app.requestServer4JSON("/products", (typeof datas == 'string') ? datas : JSON.stringify(datas), function(response) {
                if(response && response.flag) {
                    chrome.tabs.executeScript(tabId, {
                        "code": "pageLoader.hide();iziToast.success({title: 'Balidrop',timeout:2000,message: '" + (response.msg || 'success') + "',position: 'topRight',transitionIn: 'bounceInLeft'})"
                    });
                } else {
                    var message = (response && response.msg) || 'unknown msg';
                    chrome.tabs.executeScript(tabId, {
                        "code": "pageLoader.hide();iziToast.error({title: 'Balidrop',timeout:2000,message: '" + message + "',position: 'topRight',transitionIn: 'bounceInLeft'})"
                    });
                }
            });
            break;
        case 'Purchasing':
            chrome.tabs.executeScript(tabId, {
                "code": "pageLoader.show();"
            });
            app.requestServer("/agentOrder/save", 'post', (typeof datas == 'string') ? JSON.parse(datas) : datas, function(response) {
                if(response && response.flag) {
                    chrome.tabs.executeScript(tabId, {
                        "code": "pageLoader.hide();iziToast.success({title: 'Balidrop',timeout:2000,message: '" + (response.msg || 'success') + "' || 'success',position: 'topRight',transitionIn: 'bounceInLeft'})"
                    });
                } else {
                    var message = (response && response.msg) || 'unknown msg';
                    chrome.tabs.executeScript(tabId, {
                        "code": "pageLoader.hide();iziToast.error({title: 'Balidrop',timeout:2000,message: '" + message + "',position: 'topRight',transitionIn: 'bounceInLeft'})"
                    });
                }
            });
            break;
        case 'batchProduct':
            app.requestServer4JSON("/products/batch", (typeof datas == 'string') ? datas : JSON.stringify(datas), function(response) {
                if(response && response.flag) {
                    chrome.tabs.executeScript(tabId, {
                        "code": "iziToast.success({title: 'Balidrop',timeout:10000,message: '" + (response.msg || 'success') + "' || 'success',position: 'topRight',transitionIn: 'bounceInLeft'});pageLoader.hide();"
                    });
                } else {
                    var message = (response && response.msg) || 'unknown msg';
                    chrome.tabs.executeScript(tabId, {
                        "code": "iziToast.error({title: 'Balidrop',timeout:5000,message: '" + message + "',position: 'topRight',transitionIn: 'bounceInLeft'});pageLoader.hide();"
                    });
                }
            }, function(xhr, type, errorThrown) {
                alert(type + ":" + errorThrown);
                var message = 'unknown msg';
                chrome.tabs.executeScript(tabId, {
                    "code": "iziToast.error({title: 'Balidrop',timeout:5000,message: '" + message + "',position: 'topRight',transitionIn: 'bounceInLeft'});pageLoader.hide();"
                });
            }, 50000);
            break;
    }
    callback("success");
    return true;
    });

    4、同步该用户下的balidrop的产品库存信息。

    //开始进行监控操作
    function watchProductProcess() {
    app.getLocalStorage("stockWindowObject", function(stockData) {
        //如果是运行的状态,开始进行循环处理了
        var datas = stockData.datas || [];
        var executeIndex = stockData.executeIndex || 0;
        try {
            if(stockData.running == true) {
                if(executeIndex > (datas.length - 1)) {
                    stockData.message = "<span class='glyphicon glyphicon-off' style='width: 18px;height: 18px;'></span> Completed..." + (datas.length);
                    stockData.running = false;
                } else {
                    stockData.message = "<span class=\"mui-spinner\" style='width: 18px;height: 18px;'></span> Processing..." + (stockData.executeIndex + 1) + "/" + (datas.length);
                    //ajax进行处理
                    var processData = datas[executeIndex];
                    var watchUrl = stockData.watchUrl;
                    if(!watchUrl) {
                        stockData.message = "<span class='glyphicon glyphicon-exclamation-sign' style='width: 18px;height: 18px;'></span> Error...The WatchUrl Not Found";
                        stockData.running = false;
                    } else {
                        //加1
                        stockData.executeIndex = executeIndex + 1;
                        var intervalTime = (stockData.formData && stockData.formData.singleIntervalTime) || 1;
                        var itemId = processData['platformProductId'];
                        if(itemId) {
                            watchUrl = watchUrl.replace(/\{platformProductId\}/gi, itemId);
                            app.requestServer4StaticResource(watchUrl, "text", function(returnData) {
                                try {
                                    //-----解析数据-----start
                                    function mtopjsonp1(data) {
                                        return data;
                                    }
                                    var resultData = eval(returnData);
    
                                    //开始同步服务器。。。。。。。。。。。。。...开始解析数据
                                    if(resultData.data.trade) {
                                        var redirectUrl = resultData.data.trade.redirectUrl;
                                        //说明已经下架了
                                        if(redirectUrl && redirectUrl.toLowerCase().indexOf('expired') >= 0) {
                                            console.info("xxxxx下架了");
                                            var submitData = {
                                                "productId": processData['id'],
                                                "skuIds": []
                                            }
                                            //提交到服务器进行更新
                                            app.requestServer4JSON("/products/downProductByMonitor", JSON.stringify(submitData), function(response) {
                                                if(response && response.flag) {
                                                    console.info("type 1 down success..." + processData['id']);
                                                };
                                            });
                                        }
                                    } else if(resultData.data.apiStack) {
                                        var apiStack = resultData.data.apiStack;
                                        if(apiStack && (apiStack instanceof Array) && apiStack.length > 0) {
                                            var value = apiStack[0]['value'];
                                            var jsonValue = JSON.parse(value);
                                            if(jsonValue && jsonValue.trade) {
                                                var trade = jsonValue.trade;
                                                if(trade.buyEnable == false) {
                                                    var submitData = {
                                                        "productId": processData['id'],
                                                        "skuIds": []
                                                    }
                                                    //提交到服务器进行更新
                                                    app.requestServer4JSON("/products/downProductByMonitor", JSON.stringify(submitData), function(response) {
                                                        if(response && response.flag) {
                                                            console.info("type 2 down success..." + processData['id']);
                                                        };
                                                    });
                                                } else {
                                                    var skus = resultData.data.skuBase && resultData.data.skuBase.skus;
                                                    //说明含有sku的商品,只下架部分
                                                    if((skus instanceof Array) && skus.length > 0) {
                                                        var sku2info = (jsonValue.skuCore && jsonValue.skuCore.sku2info) || {};
                                                        var skuIdPropMapping = {};
                                                        for(var i = 0; i < skus.length; i++) {
                                                            var skuId = skus[i]['skuId'];
                                                            if(skuId in sku2info) {
                                                                var sku2InfoItem = sku2info[skuId];
                                                                var propPath = skus[i]['propPath'];
                                                                skuIdPropMapping[propPath] = sku2InfoItem['quantity'];
                                                            }
                                                        }
                                                        //开始进行比较
                                                        if(Object.getOwnPropertyNames(skuIdPropMapping).length > 0) {
                                                            //开始进行处理
                                                            var deleteSkuIdsArray = []
                                                            var serverSkuIds = (processData['skuIds'] && JSON.parse(processData['skuIds'])) || [];
                                                            if(serverSkuIds.length > 0) {
                                                                for(var i = 0; i < serverSkuIds.length; i++) {
                                                                    if(!(serverSkuIds[i] in skuIdPropMapping)) {
                                                                        deleteSkuIdsArray.push(serverSkuIds[i]);
                                                                    }
                                                                }
                                                            }
                                                            //开始进行删除
                                                            if(deleteSkuIdsArray > 0) {
                                                                var submitData = {
                                                                    "productId": processData['id'],
                                                                    "skuIds": deleteSkuIdsArray
                                                                }
                                                                //提交到服务器进行更新
                                                                app.requestServer4JSON("/products/downProductByMonitor", JSON.stringify(submitData), function(response) {
                                                                    if(response && response.flag) {
                                                                        console.info("type 3 down success..." + processData['id']);
                                                                    };
                                                                });
                                                            }
                                                        }
                                                    };
                                                }
                                            }
                                        }
                                    }
                                    //继续递归调用上面的
                                    app.setLocalStorage("stockWindowObject", stockData, function() {
                                        //通知
                                        notifyStockMonitor(stockData, 'RUNNING');
                                        //继续执行
                                        window.setTimeout(function() {
                                            watchProductProcess();
                                        }, intervalTime * 1000);
                                    });
                                } catch(e) {
                                    console.error("process ajax data error")
                                    //继续递归调用上面的
                                    app.setLocalStorage("stockWindowObject", stockData, function() {
                                        //通知
                                        notifyStockMonitor(stockData, 'RUNNING');
                                        //继续执行
                                        window.setTimeout(function() {
                                            watchProductProcess();
                                        }, intervalTime * 1000);
                                    });
                                }
                            }, function(a1, a2, a3) {
                                console.error("reqeust url failed:" + a2); //继续递归调用上面的
                                app.setLocalStorage("stockWindowObject", stockData, function() {
                                    //通知
                                    notifyStockMonitor(stockData, 'RUNNING');
                                    //继续执行
                                    window.setTimeout(function() {
                                        watchProductProcess();
                                    }, intervalTime * 1000);
                                });
                            });
                        } else {
                            app.setLocalStorage("stockWindowObject", stockData, function() {
                                notifyStockMonitor(stockData, 'RUNNING');
                                //继续执行
                                window.setTimeout(function() {
                                    watchProductProcess();
                                }, intervalTime * 1000)
                            });
                        }
                    }
                }
    
                if(stockData.running == false) {
                    app.setLocalStorage("stockWindowObject", stockData, function() {
                        notifyStockMonitor(stockData, 'RUNNING');
                    });
                }
            };
        } catch(e) {
            console.error("execute " + executeIndex + " Error....");
            if(stockData.running == true) {
                if(executeIndex <= (datas.length - 1)) {
                    //重试
                    if(stockData.executeIndex == executeIndex) {
                        stockData.executeIndex = executeIndex + 1;
                        stockData.message = "<span class=\"mui-spinner\" style='width: 18px;height: 18px;'></span> Processing " + (executeIndex + 1) + "/" + (datas.length) + " Error:" + e;
                    }
                } else {
                    stockData.message = "<span class='glyphicon glyphicon-off' style='width: 18px;height: 18px;'></span> Completed..." + (datas.length);
                    stockData.running = false;
                }
                app.setLocalStorage("stockWindowObject", stockData, function() {
                    var intervalTime = (stockData.formData && stockData.formData.singleIntervalTime) || 1;
                    //通知
                    notifyStockMonitor(stockData, 'RUNNING');
                    //继续执行
                    window.setTimeout(function() {
                        watchProductProcess();
                    }, intervalTime * 1000);
                });
            }
        }
    });
    }
    function notifyStockMonitor(stockData, status) {
    var showWinId = stockData.id;
    if(showWinId) {
        try {
            chrome.tabs.query({
                'active': true,
                'windowId': showWinId
            }, function(tabs) {
                if(tabs && tabs.length > 0) {
                    chrome.tabs.sendMessage(tabs[0]['id'], status, function(response) {
                        console.info(response);
                    });
                }
            });
        } catch(e) {
            console.info(e);
        }
    }
    }

    5、注意事项:插件生效的前提是用户已经登录了,否则不会注入脚本的。

针对具体平台的脚本开发

  1. 在脚本的JS中主要包含两个步骤:商品的直接采购和商品的刊登。
    采购:

    {
    "listingTitle": "商品名称",
    "orignalUrl": "该商品的链接",
    "currencyName": "CNY",//币种
    "imageUrl": "图片的链接",
    "fromType": "BROWSER_APP"
    "price":200.00,//金额
    "skuMapKeyName":"蓝色/xl",
    "skuCnt":1,//数量
    "freightPrice":0,//运费
    }

    2.刊登含SKU商品

    {
    "productName":"商品名称",
    "hasSku":false,//是否是sku的商品
    "platform":"JD",//平台的名称,
    "productUrl":"产品的原始链接",
    "desc":"商品的详情",
    "image":{
        "ShopWindowImg":["a.jpg","b.jpg"],//橱窗图
        "DescImg":["c.jpg","d.jpg"],//详情图
        "AttrImg":["e.jpg","f.jpg"]//属性图
    }
    "attrNames":[
        {
            "name":"颜色",
            "attrNo":"A1"//名字任意取,不重复即可
        },
        {
            "name":"尺码",
            "attrNo":"B1"//名字任意取,不重复即可
        }
    ],
    "attrValues":[
        {
            "value":"红色",
            "attrNo":"AA1",//名字任意取,不重复即可
            "attrNameNo":"A1"/** 必须和上面的attrNmaes中的attrNo一样  */
        },
        {
            "value":"黄色",//名字任意取,不重复即可
            "attrNo":"AA2",
            "attrNameNo":"A1"/** 必须和上面的attrNmaes中的attrNo一样  */
        },{                                             {
            "value":"XL",
            "attrNo":"BB1",//名字任意取,不重复即可
            "attrNameNo":"B1"/** 必须和上面的attrNmaes中的attrNo一样  */
        },
        {
            "value":"XXL",
            "attrNo":"BB2",
            "attrNameNo":"B1"/** 必须和上面的attrNmaes中的attrNo一样  */
        }
    ],
    "productSku":[
        {
            "skuAttr":"A1:AA1;B1:BB1",//顺序很关键:名称:值;名称:值....... 且都是attrNo
            "skuId":"skuid"//真实的skuId
            "stock":"100",//库存
            "price":100,//价格
    
        }
    ]
    }

    3、不含SKU

    {
    "productName":"商品名称",
    "sku":false,//是否是sku的商品
    "platform":"JD",//平台的名称,
    "productUrl":"产品的原始链接",
    "desc":"商品的详情",
    "image":{
        "ShopWindowImg":["a.jpg","b.jpg"],//橱窗图
        "DescImg":["c.jpg","d.jpg"],//详情图
        "AttrImg":["e.jpg","f.jpg"]//属性图
    }
    "noSkuProp":{
        "platformPrice":100,//价格
        "stock":1000,//库存,
        "price":999,//金额
    
    }
    }

浏览器登录的逻辑代码:

  1. 先判断用户是否登录balidrop的网站,如果登录,那么插件里面直接显示已经登录。

    2.如果客户没有登录toppgo的网站应用,那么执行登录的信息。


    根据手机APP的逻辑将用户的 用户名和密码,进行加密,然后直接请求用户信息。