Deno が面白そうだったので、試しに Twitter API を使ってみました。
本当は TypeScript を実行できますが、ここでは JavaScript で記述しています.
1. コード
以前自分が書いたコードを少し改良して、さらに Deno 向けに変更を加えました。
参考「ブラウザ上のピュア JavaScript で OAuth 認証して Twitter API を使う」
import Twitter from './twitter.js';
//
const twitter = new Twitter({
'api_key' : '...',
'api_secret_key': '...',
'access_token' : '...',
'access_token_secret': '...'
});
//
const json = await twitter.get('friends/list', {
'screen_name': 'TwitterJP'
});
console.log(json);
import hmacSha1 from './hmac-sha1.js';
import * as base64 from "https://denopkg.com/chiefbiiko/base64/mod.ts";
import * as hex from "https://deno.land/std/encoding/hex.ts";
export default class Twitter {
#options
constructor(options) {
this.#options = options;
}
get(path, paramsObj) {
return this.#request('GET', path, paramsObj);
}
async #request(method, path, paramsObj) {
const url = this.#getRestUrl(path);
const params = this.#objToArray(paramsObj);
// 認証情報
const authHeader = await this.#getAuthHeader(method, url, params);
const headers = {'Authorization': authHeader};
// 通信
const query = this.#percentEncodeParams(params).map(pair => pair.key + '=' + pair.value).join('&');
const response = await fetch((! params || method === 'POST' ? url : url + '?' + query), {method, headers});
return response.json();
}
async #getAuthHeader(method, url, params) {
// パラメータ準備
const oauthParamsObj = {
'oauth_consumer_key' : this.#options['api_key'],
'oauth_nonce' : this.#getNonce(),
'oauth_signature_method': 'HMAC-SHA1',
'oauth_timestamp' : this.#getTimestamp(),
'oauth_token' : this.#options['access_token'],
'oauth_version' : '1.0'
};
const oauthParams = this.#objToArray(oauthParamsObj);
const allParams = this.#percentEncodeParams([...oauthParams, ...params]);
this.#ksort(allParams);
// シグネチャ作成
const signature = await this.#getSignature(method, url, allParams);
// 認証情報
return 'OAuth ' + this.#percentEncodeParams([...oauthParams, {key: 'oauth_signature', value: signature}]).map(pair => pair.key + '="' + pair.value + '"').join(', ');
}
#getSignature(method, url, allParams) {
const allQuery = allParams.map(pair => pair.key + '=' + pair.value).join('&');
// シグネチャベース・キー文字列
const signatureBaseString = [
method.toUpperCase(),
this.#percentEncode(url),
this.#percentEncode(allQuery)
].join('&');
const signatureKeyString = [
this.#options['api_secret_key'],
this.#options['access_token_secret']
].map(secret => this.#percentEncode(secret)).join('&');
// シグネチャ計算
const signatureUint8Array = hmacSha1(signatureBaseString, signatureKeyString);
return base64.fromUint8Array(signatureUint8Array);
}
#getRestUrl(path) {
return 'https://api.twitter.com/1.1/' + path + '.json';
}
/**
* RFC3986 仕様の encodeURIComponent
*/
#percentEncode(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, char => '%' + char.charCodeAt().toString(16));
}
#percentEncodeParams(params) {
return params.map(pair => {
const key = this.#percentEncode(pair.key);
const value = this.#percentEncode(pair.value);
return {key, value};
});
}
#ksort(params) {
return params.sort((a, b) => {
const keyA = a.key;
const keyB = b.key;
if ( keyA < keyB ) return -1;
if ( keyA > keyB ) return 1;
return 0;
});
}
#getNonce() {
const array = new Uint8Array(32);
window.crypto.getRandomValues(array);
return hex.encodeToString(array);
}
#getTimestamp() {
return Math.floor(Date.now() / 1000);
}
#objToArray(object) {
return Object.entries(object).map(([key, value]) => ({key, value}));
}
}
import hmac from 'https://raw.githubusercontent.com/denolibs/hmac/master/lib/mod.ts';
import { Hash, encode } from 'https://deno.land/x/checksum@1.2.0/mod.ts';
const hashSha1 = new Hash('sha1');
const hash = bytes => hashSha1.digest(bytes).data;
const hmacSha1 = (data, key) => hmac(encode(data), encode(key), hash, 64, 20);
export default hmacSha1;
2. Deno の準備
自分は Windows Subsystem for Linux を使用しているので、Linux の方法でインストールしました。
curl -fsSL https://deno.land/x/install/install.sh | sh
環境変数 PATH に deno のインストールディレクトリを追加します。
export DENO_INSTALL="/home/<ユーザー名>/.deno"
export PATH="$DENO_INSTALL/bin:$PATH"
参考「Installation - The Deno Manual」
3. Deno での実行
Deno で通信をしたい場合には、パーミッションのオプション --allow-net
を使用します。
deno run --allow-net main.js
よりセキュアにするために、ホスト名で制限をかけることも可能です。
参考「Permissions - The Deno Manual」
4. コードの説明
4.1. Deno での HMAC-SHA1 の計算
Deno では Crypto
は実装されていますが SubtleCrypto
はありませんので、他の方法で計算します。
Deno 向けの HMAC を計算するライブラリと SHA1 を計算するライブラリがそれぞれあるので、組み合わせたらできました。
参考「GitHub - denolibs/hmac: A faster HMAC module for Deno using TypeScript and WebAssembly」
参考「GitHub - manyuanrong/deno-checksum: Sha1、MD5 algorithms for Deno」
SHA1 のブロック長は 512 ビット ( = 64 バイト) 、出力長は 160 ビット ( = 20 バイト) です。
参考「SHAシリーズの比較 - SHA-1 - Wikipedia」
参考「US Secure Hash Algorithm 1 (SHA1)」
4.2. OAuth 認証とシグネチャの計算
別記事にしました。
参考「OAuth 1.0a 認証の実装 (Twitter API 用) - Qiita」
参考「Authorizing a request — Twitter Developers」
参考「Creating a signature — Twitter Developers」