Wantedly Advent Calendar 2015の15日目です!
最近Electronをやりたくて、SoundCloudクライアントを作り始めました。
認証~SDKの初期化しか作っていない絶賛WIPですが、ElectronでSoundCloud SDKを使うまでのハマリポイントをまとめます!
TL;DR
- ElectronでSoundCloud SDKを使うときハマったのは 認証 と SDKの初期化
- 認証は通常のWebブラウザ同様にはいかないので、 Electronのprotocol APIを使う
- SDKの初期化は レンダラープロセス側でやる
SouncCloud SDK
SoundCloudはAPIが公開されていて、JavaScript用のSDKも用意されています。APIをラップしたインタフェースになっていて、Promiseが返ってくるいい感じのSDKです。
ただこのSDK、普通のWebブラウザでの使用を前提にしているので、Electronで使うのには、認証・SDKの初期化でハマりました...
SoudCloud SDK or 自前実装
ちなみに、Electron製のSoundCloudクライアントとしてgillesdemey/Cumulusがあるのですが、こちらはSDKは使わず、自前でAPIラッパーを書いています。
どちらを選ぶかはあなた次第...
認証
Webブラウザでの流れ
SoundCloud APIの認証は通常、↓の流れになります。
- SDKを初期化、新規ウィンドウが開く
- 開かれたウィンドウ内で、SDK初期化時に設定したコールバックURLにリダイレクト
- 2のウィンドウからTokenを取得する
HTTP API Guideによると、2のコールバック先に↓のようなhtmlをおいておく、ということになっています。
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Connect with SoundCloud</title>
</head>
<body onload="window.opener.setTimeout(window.opener.SC.connectCallback, 1)">
<b style="width: 100%; text-align: center;">This popup should automatically close in a few seconds</b>
</body></html>
window.opener.SC.connectCallback
がキモなんですが、ElectronのBrowserWindowから開くと、 window.openerがnullになるのです...
調べてみると、Electronのissueにコメントが...、バグなのか?
Electronのprotocol APIを使う
普通なブラウザ同様の流れは使えないので、APIからのコールバックを Electronのprotocol APIでインターセプトすることで、APIからのコールバックをElectronでうけとります。
↓のように実装して、APIのコールバック先をsc-fav://oauth/callback
で指定しておくと、コールバックを受け取れるようになります。
setupProtocol() {
let protocol = require('protocol');
protocol.registerHttpProtocol('sc-fav', (req) => {
let uri = url.parse(req.url);
switch(uri.host) {
case 'oauth':
if (uri.pathname !== '/callback'){
return;
}
let hash = uri.hash.substr(1);
// トークン取得
let token = querystring.parse(hash).access_token;
// TODO: ログイン処理
console.log('token', token);
break;
default:
}
});
}
この部分はCumulusの実装を参考にしました。
SDKの初期化
API Documentationを見ながら実装します。
SDKの初期化はこんなインタフェース
SoundCloudSDK.initialize(
{
client_id: 'CLIENT_ID',
redirect_uri: 'http://example.com/', //オプション項目
oauth_token: 'TOKEN' // オプション項目
}
);
'getUserMedia' of undefined
認証をElectronのメインプロセス(Node.jsのほう)でやったので、SDKの初期化もメインプロセスでやろうとすると、SDKから"'getUserMedia' of undefined"のエラーが...
SDK内の該当箇所を見ると、音声録音ができる機能があるので、navigator.getUserMediaでデバイスへのアクセスを求めるところがあるよう。メインプロセスの方では、navigatorがとれないので、undefinedになる様子...
認証からの流れ
悩んだ挙句、以下の流れで実装することに
- 認証はメインプロセスで行う
- **SDKの初期化はレンダラーブロセス(BrowserWindow側)**で行う
- 認証で取得したOAuthのトークンは **IPC(プロセス間通信)**でメインプロセスからレンダラープロセスに渡す
メインプロセス
// ログイン完了でTokenが帰ってきた後のコールバック
loginCallback: () => {
this.mainWindow = new BrowserWindow({ width: 350, height: 640 });
this.mainWindow.on('closed', () => {
app.quit();
});
// 画面の読み込み完了を待つ
this.mainWindow.webContents.on('did-finish-load', () => {
this.auth.getToken().then((token) => {
// IPCでトークンを送信
this.mainWindow.webContents.send('token', token);
});
});
this.mainWindow.loadURL(`file://${__dirname}/..${options.indexPath}`);
}
レンダラープロセス
import React from 'react';
import ReactDom from 'react-dom';
import {Main} from './components/main';
import electron from 'electron';
import SoundCloud from 'soundcloud';
electron.ipcRenderer.on('token', (event, token) => {
event.sender.send('token', 'success'); // 送信元へレスポンスを返す
SoundCloud.initialize({
client_id: 'CLIENT-ID',
oauth_token: token
});
SC.get('/me/favorites').then(function(tracks){
console.log('Latest track: ' + tracks[0].title);
});
SoundCloud.stream('/tracks/293').then((player) => {
player.play();
});
});
まとめ
認証~SDKの初期化さえできれば、曲の再生、フォローの取得やLikeなど、一通りの動作が簡単に実装できるようになります。
SoundCloudユーザの方は、Electronでクライアントを作ってみてはいかがでしょう?