iOS 企業證書過時填坑記

入坑篇

前線客服傳來消息 — 「用戶反饋一打開咱們的 App,就直接閃退了」,剛聽到這個消息,我很吃驚,上一期發的新版本 QA 都有驗證過。難道是由於功能權限的問題致使的,趕忙跟客服確認具體狀況。原來是客戶前幾天都能正常使用 App,今天一打開就莫名閃退了。剛瞭解清楚具體狀況,一會兒閃退的消息,就如滔滔江水一涌而來,隨後也就開始了 iOS 證書過時填坑之旅。javascript

咱們公司的產品有幾十個客戶,但並非每一家客戶都有反饋,而只是其中的幾家。反饋閃退的幾家客戶中,都是同時使用 Android 和 iOS 兩個平臺,但反饋閃退問題的都是使用 iOS 平臺的用戶,Android 平臺並無出現閃退問題。html

梳理完思路後,咱們就想到了是否是反饋閃退的客戶使用的 App 證書或描述文件出問題了,所以立馬登錄蘋果開發者後臺,登錄後發現果真是幾個客戶使用的證書,今天就過時了。那是否是證書過時致使閃退的呢?原生開發人員,立刻更新一下證書,打了個包進行驗證。果真,用新的證書打出來的包,就能正常使用,不會出現閃退了。網上找了相關的資料,也不少小夥伴遇到一樣的問題 —— 「企業版證書過時,App 出現閃退」。問題是已經定位了,但客戶那邊怎麼解決呢?客戶一打開咱們的 App 就立馬閃退了,沒有辦法進行強制更新。此後,在網上繞了一大圈,看了不少文章,發現咱們最終的方案,只能從新打包讓用戶重裝。java

蒼天啊!大地啊!爲何蘋果企業證書即將過時,沒有發郵件通知,這真是一個大坑!!!事情居然已經發生,只能嚥下苦水,乖乖地接受外部的 「轟炸」 了。接下來咱們當即針對閃退的客戶從新打包,而後讓公司客服與客戶溝通,說明狀況...git

這個問題之後要如何避免?難道要安排專人,天天定時檢查證書的有效性?最初的這個想法,其實我是拒絕的。這種髒活累活,確定要請咱們吃飯的傢伙 —— ?(Computer)來幫咱們處理咯。前陣子恰好偶遇谷歌出品的一個神器 —— GoogleChrome/puppeteer (Headless Chrome Node API),接下來咱們就先來介紹這款神器。github

跳坑篇

puppeteer 簡介

puppeteer 是一個 Node.js 的庫,支持調用 Chrome 的 API 來操縱 Web,相比較 Selenium 或是 PhantomJS,它最大的特色就是它的操做 DOM 能夠徹底在內存中進行模擬既在 V8 引擎中處理而不打開瀏覽器,並且關鍵是這個是 Chrome 團隊在維護,會擁有更好的兼容性和前景。json

puppeteer 的神技:api

  • 對網頁進行截圖保存爲圖片或 pdf
  • 抓取單頁應用 (SPA) 執行並渲染(解決傳統 HTTP 爬蟲抓取單頁應用難以處理異步請求的問題)
  • 作表單的自動提交、UI的自動化測試、模擬鍵盤輸入等
  • 用瀏覽器自帶的一些調試工具和性能分析工具幫助咱們分析問題
  • 在最新的無頭瀏覽器環境裏作測試、使用最新瀏覽器特性
  • 寫爬蟲作你想作的事情

是否是感受 puppeteer 棒棒噠。其實還有其它一些無頭瀏覽器,好比:瀏覽器

  • phantomjs:Scriptable Headless WebKit 【Star - 24218】
  • slimerjs:A scriptable browser like PhantomJS, based on Firefox 【Star - 2396】
  • Splash:Lightweight, scriptable browser as a service with an HTTP API 【1617】
  • trifleJS:Headless automation for Internet Explorer 【Star - 740】

簡單介紹完 puppeteer,接下來咱們就來稍微介紹一下思路。其實實現思路很簡單,只須要使用 puppeteer 模擬登陸?開發者網站,進入證書管理的頁面,獲取全部證書的有效期,而後設置計算出即將過時的天數。微信

最終的流程以下:app

  • 訪問?開發者官網
  • 進入證書管理頁面,獲取指定類型證書(All、Pending、Development 或 Production)
  • 取得證書列表,以當天的時間點爲每一個證書計算即將過時的天數
  • 基於處理完的數據,進行預警通知(郵件、短信或微信)

puppeteer 實戰

基於 puppeteer API 的版本爲: 0.11.0
const puppeteer = require('puppeteer');

(async() => {
    const browser = await puppeteer.launch({
        headless: false // 開發調試階段,設置爲false
    });
    const page = await browser.newPage();
    page.setViewport({
        width: 1376,
        height: 768,
    });
    page.on('response', async(response) => {
        if (response && response.status == 200) {
            // 判斷是否加載完概覽視圖,而後再次進入證書頁面
            if (response.url.indexOf('tpl.overview-view.html') != -1) {
                await getCertsInfo();
            }
            // 判斷是否爲生產環境證書列表請求
            if (response.url.indexOf('status=4&certificateStatus=0&type=distribution') 
                != -1) {
                const res = await response.json();
                if (res && res.certRequests) {
                    console.dir(calcCertsDays(res.certRequests));
                    await browser.close();
                }
            }
        }
    });

    // 跳轉到蘋果官網並等待頁面資源加載完成
    await page.goto('https://developer.apple.com/cn/', {
        waitUntil: 'load'
    });

    // 跳轉到登陸頁面
    await page.click('.ac-gn-account > a');
    await page.waitForSelector('#accountname', {
        timeout: 50000
    });
    await login();

    // 執行登陸操做
    async function login() {
        await page.focus('#accountname');
        await page.type('開發者帳號', { // 此處替換爲真實帳號
            delay: 100
        });
        await page.focus('#accountpassword');
        await page.type('開發者帳號密碼', { // 此處替換爲真實密碼
            delay: 100
        });
        await page.click('#submitButton2');
    }

    // 獲取證書信息(等待模板加載完成後,才進入證書管理頁面)
    async function getCertsInfo() {
        const CERT_ITEM_SELECTOR = '#main section.getting-started > a:nth-child(2)';
        await page.waitForSelector(CERT_ITEM_SELECTOR);
        await page.click(CERT_ITEM_SELECTOR);
        const PROD_CERT_SELECTOR = 'li.subitem > a[href*="certificate/distribution"]';
        await page.waitForSelector(PROD_CERT_SELECTOR);
        await page.click(PROD_CERT_SELECTOR);
        await page.waitForSelector(PROD_CERT_SELECTOR);
    }

    // 計算每一個證書的天數
    function calcCertsDays(certs) {
        if (Array.isArray(certs)) {
            const today = new Date();
            return certs.map(cert => {
                return {
                    name: cert.name,
                    type: cert.typeString,// Apple Push Services || iOS Distribution
                    expirationDay: dateDiff(today, new Date(cert.expirationDate))
                };
            });
        }
    }

    // 計算兩個日期的間隔天數
    function dateDiff(today, expirationDate) {
        return parseInt((Math.abs(expirationDate.getTime() - today.getTime())) 
          / 1000 / 60 / 60 / 24);
    }
})();

總結

經過 puppeteer 這款神器提供強大的 API,咱們只是實現了基本功能,後面還有一些功能須要優化和開發,好比異常處理、帳號信息靈活配置和預警機制等,目前初步打算優先使用 wechaty 微信我的號?開發框架,來實現消息通知。我的感受 puppeteer 在往後的工做中,還有不少用武之地,好比此前本人使用 puppeteer 實現了簡單的頁面功能測試。

此外在填坑過程當中,偶遇了另外一款神器 —— fastlane (The easiest way to automate building and releasing your iOS and Android apps) ,感受真是相見恨晚啊(前陣子部門剛花大力氣,實現App自動打包)。有興趣的小夥伴,能夠試試 puppeteerfastlane 這兩款神器。

相關文章
相關標籤/搜索