タグ: JavaScript

Amazon PA-API v5.0をJavaScript(Chrome拡張)で実装してみる

Amazonの製品カタログデータにアクセスできる公式API「Product Advertising API」(以下、PA-API)のバージョン5.0が昨年公開され、来月にも完全移行(旧バージョンでの利用が不可になる)が予定されています。

ブログ等を書く際の使い勝手向上のため自分本位に開発・公開しているChrome拡張「Associates Link Generator」も今のままでは使用出来なくなってしまうので新版リリースの準備を始めたのですが、Amazonが用意してくれているScratchpad(≠実働サンプル)にはJAVA、PHP、cURLあたりの実装例しか載っていなかったのでPHP版を参考にChrome拡張で使えるJavaScriptのClassを作成。

お約束ですが…参考にされる場合、自己責任でお願いします。(フォローやサポートは致しかねます)

'use strict';

export class AwsV4 {
  constructor(accessKey, secretKey) {
    this.accessKey       = accessKey;
    this.secretKey       = secretKey;
    this.path            = null;
    this.regionName      = null;
    this.serviceName     = null;
    this.httpMethodName  = null;
    this.queryParametes  = [];
    this.awsHeaders      = [];
    this.payload         = "";

    this.HMACAlgorithm   = 'AWS4-HMAC-SHA256';
    this.aws4Request     = 'aws4_request';
    this.strSignedHeader = null;
    this.xAmzDate        = this._getTimeStamp();
    this.currentDate     = this._getDate();
  }

  setPath(path) {
    this.path = path;
  }

  setServiceName(serviceName) {
    this.serviceName = serviceName;
  }

  setRegionName(regionName) {
    this.regionName = regionName;
  }

  setPayload(payload) {
    this.payload = payload;
  }

  setRequestMethod(method) {
    this.httpMethodName = method;
  }

  addHeader(headerName, headerValue) {
    this.awsHeaders[headerName] = headerValue;
  }

  _prepareCanonicalRequest() {
    let canonicalURL = '';
    canonicalURL += this.httpMethodName + "\n";
    canonicalURL += this.path + "\n" + "\n";
    let signedHeaders = '';
    for (let key in this.awsHeaders) {
      signedHeaders += key + ";";
      canonicalURL  += key + ":" + this.awsHeaders[key] + "\n";
    }
    canonicalURL += "\n";
    this.strSignedHeader = signedHeaders.slice(0, - 1);
    canonicalURL += this.strSignedHeader + "\n";
    canonicalURL += this._generateHex(this.payload);
    return canonicalURL;
  }

  _prepareStringToSign(canonicalURL) {
    let stringToSign = '';
    stringToSign += this.HMACAlgorithm + "\n";
    stringToSign += this.xAmzDate + "\n";
    stringToSign += this.currentDate + "/" + this.regionName + "/" + this.serviceName + "/" + this.aws4Request + "\n";
    stringToSign += this._generateHex(canonicalURL);
    return stringToSign;
  }

  _calculateSignature(stringToSign) {
    const signatureKey    = this._getSignatureKey (this.secretKey, this.currentDate, this.regionName, this.serviceName);
    const signature       = this._hash_hmac(stringToSign, signatureKey);
    const strHexSignature = CryptoJS.enc.Hex.stringify(signature).toLowerCase();
    return strHexSignature;
  }

  _ksort(obj) {
    const keys = Object.keys(obj).sort();
    let sortedObj = {};
    for (let i in keys) {
      sortedObj[keys[i]] = obj[keys[i]];
    }
    return sortedObj;
  }

  getHeaders() {
    this.awsHeaders['x-amz-date'] = this.xAmzDate;
    this.awsHeaders = this._ksort(this.awsHeaders);

    const canonicalURL = this._prepareCanonicalRequest();
    const stringToSign = this._prepareStringToSign(canonicalURL);
    const signature    = this._calculateSignature(stringToSign);

    if (signature) {
      this.awsHeaders ['Authorization'] = this._buildAuthorizationString(signature);
      return this.awsHeaders;
    }
  }

  _buildAuthorizationString($strSignature) {
    return this.HMACAlgorithm + " " + "Credential=" + this.accessKey + "/" + this._getDate () + "/" + this.regionName + "/" + this.serviceName + "/" + this.aws4Request + "," + "SignedHeaders=" + this.strSignedHeader + "," + "Signature=" + $strSignature;
  }

  _generateHex(data) {
    return CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(data)).toLowerCase();
  }

  _hash_hmac(s, k) {
    return CryptoJS.HmacSHA256(s, k);
  }

  _getSignatureKey(key, date, regionName, serviceName) {
    const kSecret  = "AWS4" + key;
    const kDate    = this._hash_hmac(date, kSecret);
    const kRegion  = this._hash_hmac(regionName, kDate);
    const kService = this._hash_hmac(serviceName, kRegion);
    const kSigning = this._hash_hmac(this.aws4Request, kService);
    return kSigning;
  }

  _getTimeStamp() {
    return new Date().toISOString().replace(/-|:|\.[0-9]{3}/g, "");
  }

  _getDate() {
    return new Date().toISOString().replace(/-/g, "").substr(0,8);
  }
}

動作には別途CryptoJSが必要です。

遊べる!学べる!ピンポン玉サイズのロボティックボール『Sphero Mini』

Sphero Mini スマートトイ / プログラミングできるロボティックボール グリーン 【日本正規代理店品】 M001GAS「世界最小のロボティックボール」との触れ込みで登場した『Sphero Mini』(スフィロ・ミニ)を購入しました。

プログラマブルでスマートフォンやタブレットでラジコン操作も可能な球形デバイス「Sphero」は本国アメリカでは教材としても広く活用されている知育玩具。そのバリエーションモデルとしてリリースされたピンポン玉サイズのSphero Miniはポップなカラーを採用し値段も手頃。出遅れていた日本市場での訴求も視野に入れた意欲的な商品となっています。

Sphero Miniの充電端子

本体外装を開けると内部にMicroUSB-B端子が用意されており、最初にここから充電。充電は1時間ほどで完了します。

[JavaScript] jQueryからネイティブJavaScriptへの置き換え

ECMAに準拠したモダンブラウザの普及に伴いjQueryの利用を止めてネイティブなJavaScriptに移行(回帰)しようという際に、jQueryで実装していた処理をどう置き換えるかは悩みどころ。

JavaScriptには似たような機能が沢山あって解決策はひとつではありませんが、ECMAScript 6(2015)での動作を前提とした代表的な置き換え方法を備忘録がてら残しておくことにします。

[JavaScript] jQuery無しでも$(ドル記号)で要素選択したい

昨今のモダンブラウザに於けるJava Scriptの標準仕様(ECMAScript)への準拠の流れを受けて、わたしもChrome拡張(エクステンション)など実行環境を制限出来るウェブアプリの開発に於いてはjQueryに頼らないコーディングを心がけるようになりました。

ただ、いくらネイティブJavaScriptが高度化したとはいえ現状ではまだまだ発展途上な感が否めず要素を選択するだけでもソースが冗長になりがち。

// jQuery
$(".class");

// 非jQueryだと長ったらしい
document.querySelector(".class");

jQueryの開発チームもECMAScript標準化には関与しているそうなので、将来的に「$()」が「document.querySelector()」のエイリアスになるんじゃね?なんて思っていたら、既にChromeブラウザのdevToolsはそのような実装をしている模様。ちなみに、「$$()」は「document.querySelectorAll()」と同様の結果を返してきます。

[JavaScript] setIntervalで0秒発火ロジックを用いる際のclearInterval制御

JavaScriptでタイマー処理を実現する際によく利用する「setInterval」。

指定した間隔(ミリ秒)で第1引数の処理が実行されるのだが、最初に処理実行される前にも待機時間が生じるためすぐに実行したい場合は即時関数を用いれば良い。

var timerID = setInterval((function () {
  /* 処理 */

  return arguments.callee;
}()), 500);

これは各所で紹介されているスマートなロジックですが、この処理の中に「clearInterval」を組み入れる場合は注意が必要。

WordPress用プラグイン「Head Cleaner」のDNSプリフェッチ出力不具合を回避

WordPressが吐き出すHTMLのheadタグ内を整理統合することでクライアント側の処理を最適化し表示の高速化を図る人気のプラグイン「Head Cleaner」ですが、現バージョン(1.4.2.12)に於いてheader.php等で外部CDNのJavaScriptやCSSを読み込むため以下のような記述をしていると…


wp_enqueue_style('font-awesome', '//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css');
wp_enqueue_script('jquery','//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js');

それぞれフルパスで「DNSプリフェッチ」(dns-prefetch)を出力してしまう模様。


<link rel="dns-prefetch" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css">
<link rel="dns-prefetch" href="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js">

別にエラーにはならないけれど、DNSプリフェッチのhref部はドメインだけで構わないのでドメインより後ろを取り除きつつ重複を除去するようhead-cleaner.phpを一部修正。