SlackのWebAPIを使ってメッセージを投稿しようとしたときに、JSONで送るとなぜかエラーで送れないという話を聞いたので調査しました。
状況確認
リファレンスを見ると、確かに、JSONで送れると書いてあります。
そして下記に従う必要があるそうです。
There are some ground rules:
- You must explicitly set the
Content-type
HTTP header toapplication/json
. We won't interpret your POST body as such without it.- You must transmit your
token
as a bearer token in theAuthorization
HTTP header.- You cannot send your token as part of the query string or as an attribute in your posted JSON.
- Do not mix arguments between query string, URL-encoded POST body, and JSON attributes. Choose one approach per request.
- Providing an explicitly
null
value for an attribute will result in whichever default behavior is assigned to it.
これに合うメッセージ送信処理をJavaScriptで書いてみます。
const request = new XMLHttpRequest();
request.open('POST', 'https://slack.com/api/chat.postMessage', true);
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Authorization', `Bearer ${token}`);
let data = {};
data['channel'] = '投稿先のチャンネルID';
data['text'] = '送信メッセージ';
request.send(JSON.stringify(data));
ボタンをクリックしたら上記処理が実行されるようなWebページを作って試してみたところ、こんなエラーが表示されました。
クロスオリジン要求をブロックしました:
同一生成元ポリシーにより、https://slack.com/api/chat.postMessage にあるリモートソースの読み込みは拒否されます
(理由: CORS プリフライト応答からのヘッダー 'Access-Control-Allow-Headers' によりヘッダー 'authorization' が許可されていない)。
CORSのプリフライトでエラーとなっていますね。
CORSについては こちらを参考にして下さい。
Authorization
ヘッダーが許可されていないということですが、上記のSlackのマニュアルにはTokenはAuthorization
ヘッダーに書けって書いてあるしどうしろと。。。
ではどんなヘッダーなら許可されているのか、プリフライトのレスポンスの内容を見てみました。
うーん、なんだかこれ、外部からアクセスされることを想定していない設定のような…。
と、ここで一つの疑惑が。
application/json に対応しました(ブラウザからの実行に対応したとは言っていない)
ということなのではないかと。
検証
ではブラウザを使わずに実行してみましょう。
Node.jsを使ってターミナルから同じ処理を実行しました。
送れた!
ということで黒です。
対処法
ブラウザから送る場合はJSONは諦めて、 Content-Type
には application/x-www-form-urlencoded
を設定することになります。
const request = new XMLHttpRequest();
request.open('POST', 'https://slack.com/api/chat.postMessage', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
const channelID = '投稿先のチャンネルID';
const message = '送信メッセージ';
request.send(`token=${token}&channel=${channelID}&text=${message}`);
この仕様、Slack WebAPIのマニュアルには全く見当たりません。
フロントエンドから直接呼ばれるような使い方はあまり想定していないなのでしょうか。