Google+ Followers

2018年5月14日 星期一

iOS App 開發筆記 - 關於 case sensitive 檔案系統與 CocoaPods 和 Ruby/Gems 的處理

某天重灌 macOS 時,把主硬碟弄成 case sensitive ,想說自己都很熟悉 linux server 的行為,殊不知替自己埋了很多坑。首先,如果用 CocoaPods 維護套件時,別人的程式碼不見得會依照此規範,同理在 Ruby/Gems 更是如此。

解法?先切一個 partition 不區分大小寫吧!在把 Xcode build code 的暫存區也移至該 partition 來解!

未來還是推薦用 case insensitive 的 partition 吧 Orz 想搞龜毛還是用另一區,千萬別要跟青春過意不去啊。

將 Xcode build 臨時區移走:

$ mv ~/Library/Developer/Xcode/DerivedData/ ~/Library/Developer/Xcode/DerivedData-bak/
$ mkdir /Volumes/Data/XcodeDerivedData
$ ln -s /Volumes/Data/XcodeDerivedData ~/Library/Developer/Xcode/DerivedData


如此一來,關於 Xcode 與 CocoaPods 的大小寫問題應當可以解了,但...如果 build code 過程還有搭配 ruby script 做事,很抱歉,請自己改 ruby 了 Orz 例如:

require 'CFPropertyList'

很抱歉,在 Ruby 的 CFPropertyList 套件中,他實際是小寫的 Orz 請看:

https://github.com/ckruse/CFPropertyList/tree/master/lib

最重要的,這類還可能搞壞 gem 的管理狀態,這時解法就有點痛苦了,絕對不是三兩句就解掉的。

2018年5月9日 星期三

Alexa Skill 開發筆記 - 使用 AWS Lambda 與 OAUTH2 帳號連結

公司的產品是 WiFi Display 設備,結合智慧音箱後,透過音控請裝置執行動作,如播放影片。在此就順便紀錄這些開發過程。此處不會提及 OATUH2 開發項目。

首先,Alexa Skill 是個滿好玩的點子,說穿了就像 Apple TV 可以安裝 Apps 的概念,他是個市集,可以讓開發者提供更多智慧音箱的技能擴充,大約是 2015 年開始的。上頭也可以有簡單的變現機制,可以參考:Alexa開發者也可以賺錢了!看亞馬遜怎麼開啟語音應用的全新獲利模式

首先,沒有 Alexa device 也可以開發,善用模擬器即可。開發流程:

  1. 建立 Alexa Developer 帳號(過程就是建立 Amazon 帳號)
  2. 建立 AWS 帳號(綁定信用卡,有免費額度可善用)
  3. 使用模擬器服務 - https://echosim.io/


01-skill01

02-skill02

先在 Alexa Developer 先建立一個 Skill project,此例是 Video ,接著就要填寫 AWS Lambda 的位置,就切換到 AWS Lambda 創建流程,而 Alexa Skill 預設是提供 English (US) 的語系,參照 To create a Lambda function 第三步有強調 AWS Region 的部分:
Make sure you’ve selected the N.Virginia for English (US) skills or the EU (Ireland) region for English (UK) and German skills. The region is displayed in the upper right corner. Providing your Lambda function in the correct region prevents latency issues.
就要在 N.Virginia 創建 AWS Lambda function,不然亂建立根本無法互動。建立完 AWS Lambda function 後,替他增加 Alexa Skill Kit 和 Alexa Smart Home ,並且設定它的 Skill ID,並且把對應的程式碼上傳,且要記得發布出去!

03-aws01

04-aws02

05-aws03

如此一來,此 Skill 作者基本上已經可以測試了,而測試的條件包括要有一個 Alexa 音控裝置,這時就要靠模擬器,可以先到 https://echosim.io/ 登入 Amazon 帳號,即可獲得一台虛擬器。

15-simulator

接著,若你人在美國或是擁有美國的 iTunes / Google play 帳號可以下載得到 Amazon Alexa app ,那就直接用,不然只好跟我一樣用用網頁版: https://alexa.amazon.com/spa/index.html ,只要有先把 Amazon 帳號配對過 Alexa 音箱就可以正常切換到 Skill 頁面,點選右上角的 Your skill 即可切換到 Dev skill 來查看,並把自己開發的 skill 給 enable,過程就會觸發 OAUTH2 服務帳號的綁定並授權 Skill 服務使用等等,後續的過程先挑選哪個硬體裝置要被智慧音箱控制(AWS Lambda 要實作 device discovery 等機制),接著再挑哪個智慧音箱來搭配,整個過程用瀏覽器開發工具查看到 api response:

  • https://alexa.amazon.com/api/phoenix/discovery
    • 得知哪些硬體裝置要被控制
  • https://alexa.amazon.com/api/devices-v2/device
    • 得知你的帳號有哪些智慧音箱裝置


06-skill03

07-skill04

08-skill05

09-skill06

10-skill07

11-skill08

12-skill09

最後,自己測試完後,可以再開啟 Beta Test 機制,邀請其他人來測試。而要開啟 Beta Test 只需要把資料填一填,並處於 ready for submission 即可(送審前一步),關鍵的地方就是補一補 icon、描述、甚至一些網址等等,若只要一直內測就可以先亂填

13-skill10

14-skill11

最後,有問題真的要好好看文件 XD 而 AWS Lambda 則可以善用 Cloud Watch 看 logs ,以此確保真的有連線

2018年5月7日 星期一

處理 Google Cloud Messaging for Android (GCM) token 重複問題 (canonical_ids)

事情是這樣的,拖了很久才開始處理這段 XD 拖到連 GCM 都要下線了:

As of April 10, 2018, Google has deprecated GCM. The GCM server and client APIs are deprecated and will be removed as soon as April 11, 2019. Migrate GCM apps to Firebase Cloud Messaging (FCM), which inherits the reliable and scalable GCM infrastructure, plus many new features. See the migration guide to learn more.

為何要處理?主因是一隻 android mobile device 很有可能因為隱私狀態改變、重新安裝軟體後,導致 GCM token 變更,如果當年沒有設計 global uuid 來辨識移動裝置的話(像是綁個人帳號、用 wifi mac_address 等),那 token 肯定會收到一卡車多,這時要發送訊息時,就可能出現多個 token 對應到同一位使用者,造成用戶被 Push  轟炸 (這些在 Firebase 設計理念上都有改善了,例如可以單純對全部 android user 發訊息等等),特別是 QA 常常安裝反安裝程式...

原先我們依賴著 wifi mac_address ,但在某版 Android 發現他是可變的 Orz (忘了是隱私提升還是某OEM廠的bug) 對於維護 GCM token ,就只剩下一條路:試著對他發送訊息,檢查回應是不是有變化。

連續動作:

$check_failed_db_ids = array();
$tokens = array(
array( 'id' => 'db record id', 'token' => 'GCM token'),
// ...
);

$payload = array(
'registration_ids' => array(),
'dry_run' => true,
'data' => array(
'body' => 'test'
),
);

while(count($tokens) > 0) {
//
// Step 1: init
//
$payload['registration_ids'] = array();
$current_task = array_splice($tokens, 0, 900);
foreach($current_task as $items) {
array_push($payload['registration_ids'], $items['token']);
}

//
// Step 2: call gcm api
//
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://android.googleapis.com/gcm/send");
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Authorization: key=".$api_key,
'Content-Type: application/json'
));
curl_setopt($ch, CURLOPT_POST , true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_VERBOSE, true);
$ret = curl_exec($ch);
curl_close($ch);

$ret_json = @json_decode($ret, true);
if (isset($ret_json['results'])) {
$echo_cnt = count($ret_json['results']);
for ($i=0;$i<$echo_cnt; ++$i) {
// 通常有 error 都可以考慮去掉了;當有 registration_id 則是代表已轉換 token
// 完整範例跟解釋:https://developers.google.com/cloud-messaging/http#example-responses
if (isset($ret_json['results'][$i]['error']) || isset($ret_json['results'][$i]['registration_id'])) {
array_push($check_failed_db_ids, $i);

}
}
}

//
// Step 3: update db record & reset $check_failed_db_ids
//
// ...

}

2018年4月20日 星期五

[SEO] Google search console 與使用 wget 建立 sitemap @ macOS 10.13.14

最近公司換了網域,導流量過程中需要安置個 sitemap 來增加曝光,就先查了一下 Google 牌的定義:

https://support.google.com/webmasters/answer/183668?hl=zh-Hant

不錯不錯,可以提交純文字,那就搭配個 wget 吧!

$ time wget --spider --recursive --no-verbose --output-file=log.txt https://example.com/
...
real 87m25.965s
user 0m1.836s
sys 0m4.081s


真久,接著再靠 grep 跟 awk 即可:

$ grep -op "URL:http[s]://\(.*\) " ~/log.txt | awk '{print(substr($1,5));}' > sitemap.txt

搞定!

2018年4月3日 星期二

[PHP] 微信開放平台開發筆記 - WeChat Web 應用與 OAuth2 client

今年才感到 Wechat app 崛起,得知用戶已破10億,甚至微信支付交易筆數早已超車支付寶1.5倍,已乃覺三十里 Orz 想要用境外身份申請,才發現很多都辦不成,像是境外要用公司身份才行,甚至騰訊公開平台的管理者都還得綁有中國銀行卡(金融卡)才搞得定 <囧> 多虧對岸同事的幫忙,可以小試身手一下。

騰訊公開平台的 OAuth2 文件: https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN

需要的留意的是騰訊公開平台的 Web 應用只允許填入一個 hostname/domain 作為 callback handler ,所以,也有人想得很快,透過在統一地方處理完畢後,在拋去其他地方,如:https://github.com/HADB/GetWeixinCode

在此利用一樣的架構,不過是在 backend 處理 XD 主要是服務常分成 production/alpha/dev site,就把它統一建構在 Production site ,但 Dev site 要登入時,跳去 production site 處理後,再把資料回拋到 dev site(當然也可以申請多個 WeChat Web 應用,每個 site 擁有自己的 Web app)

整個過程都算順利,唯一不太順的地方是 OAuth state 這個參數的使用,原先埋了要跳去其他地方的 callback url 或其他資訊,但測了幾輪發現 state 內若有一些敏感資訊(&)時,回來都會出錯,於是就多加個 base64_encode 來解了(也可以考慮把必要的資料儲存在 cookie)

發動處:

<?php

$init_url = 'https://open.weixin.qq.com/connect/qrconnect?'. http_build_query( array(
        'scope' => 'snsapi_login,snsapi_userinfo',
        'response_type' => 'code',
        'redirect_uri' => 'https://auth.my-domain/wechat/callback',
        'state' => base64_encode( http_build_query(array(
                'give_me_code' => 1,
                'callback' => 'https://service.my-domain/wechat/handle-code',
        )) ),
        'appid' => $weixin_app_id,
));


負責處理 WeChat OAuth2 回傳資料(code):https://auth.my-domain/wechat/callback

<?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $code,
'grant_type' => 'authorization_code',
)));
$ret = @json_decode(curl_exec($ch), true);
             
$state = $_GET['state'];
$state_param = array();
if (!empty($state)) {
$state = @base64_decode($state);
parse_str($state, $state_param);
}

isset($state_param['callback']) && (
preg_match('#http[s]://(.*?)\.my-domain/#i', $state_param['callback'], $match)
) ) {
if (!empty($_GET['code'])) {
if (isset($state_param['give_me_code']) && $state_param['give_me_code'] == '1')
header('Location: '.$state_param['callback'].'?'.http_build_query(array('code' => $_GET['code'])) );
// ...
} else {
header('Location: '.$state_param['callback'].'?'.http_build_query(array('error' => 'no code')) );
}
return;
}


處理 code:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $ret_code,
'grant_type' => 'authorization_code',
)));

$ret = @json_decode(curl_exec($ch), true);

if (isset($ret['access_token']) && isset($ret['refresh_token']) && isset($ret['openid']) && isset($ret['unionid'])) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/userinfo');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'access_token' => $ret['access_token'],
'openid' => $ret['openid'],
)));
$profile_ret = @json_decode(curl_exec($ch), true);
}

// $profile_ret['nickname']
// $profile_ret['headimgurl']


原先在 callback handler 上,順便做完 code 檢驗,但後來覺得這樣要傳出去的資料太多(且感到不安),所以還是統一把 code 拋出去,缺點是其他服務就必須都紀錄 WeChat Web 應用的資料(app_id/secret_key)等等,但可以避免受到 DNS 攻擊而把用戶的 access_token 給了出去。