Box UI ElementsのContent OpenWithとは
Content OpenWithのまえに、そもそもBox UI Elementsとはなにかという話ですが、これはBox社が公式に提供しているJavascriptとCSSによるWeb用の部品です。自作のウェブサイトに組み込むことで、標準のBoxの様なUIを簡単に実現可能です。
Box UI Elementsには、現時点で以下の6個のコンポーネントが用意されています。
Content Explorer
Content OpenWith <= 今回利用
Content Picker
Content Preview
Content Sidebar
Content Uploader
この中で、Content OpenWithは、一番ややこしかったので、使いかたを備忘録として残します。
Content OpenWithは、カスタムアプリケーション上にボタンを配置し、BoxEditと連携して、ローカルでエディタを開き、保存時にBoxにデータを書き戻すことができるコンポーネントです。
このコンポーネントを利用すると、カスタムアプリケーションでもBoxからエディタを開くような滑らかな連携が可能になります。
Box UI Elements自体は、Reactで作られています。Reactを利用している場合、npm経由でライブラリをインストールして使うことが可能です。React以外の場合はCDN経由で提供されているjsファイルを利用できます。
基本的な部分は、ここに書いてあることをなぞっています。
https://ja.developer.box.com/guides/embed/ui-elements/
事前準備
このサンプルでは、以下の環境の利用を前提としています。実際にやってみたい人は予め準備が必要です。
- Boxのアカウント(管理者権限が利用可能なBoxテナント)
- Box Toolsのインストール(ローカルでエディタを開きBoxに同期するために必要)
- Herokuのアカウント
- Heroku CLIのインストール
- Gitのインストール
- Nodeおよびパッケージマネージャ(npm or yarn)のインストール
なお、コードは以下のgithubにもおいておきます。
https://github.com/kobay/box-ui-elements-openwith-example
Box環境の準備
Boxの開発者コンソールにアクセスし、「アプリの新規作成」を押します
開発者コンソールへのアクセスが初めての場合は、https://ja.developer.box.com/
にアクセスし、マイアプリボタンをクリックすることで、アクセスが可能となります。
アプリの種類はカスタムアプリを選択します。
今回は、認証方法に、「JWTを使用したOAuth 2.0 (サーバー認証)」を選択します。
アプリの名前に任意の文字を入れ、アプリの作成ボタンを押します。
マイアプリ > 構成を開き以下のように設定にします。
(多分こんな感じで大丈夫なはずです・・、アプリケーションアクセスやスコープがたらなければ変えてください)
Image may be NSFW.
Clik here to view.
公開キーの追加と管理のセクションで、「公開/秘密キーペアを生成」を押します。
ダウンロードされるファイルは、あとでコードから利用するので、config.json
とリネームしておきます。
CORSドメインは、公開するHerokuのURLを追加します。
Herokuでのアプリケーション作成が終わったら戻ってきて設定します。
コードの準備
以下の手順で、プロジェクトを用意します。
# 適当な名前のフォルダを作って、フォルダに入るmkdir box-openwith-sample
cd box-openwith-sample
# Nodeプロジェクトとして初期化 (npmでも)
yarn init -y# 必要なライブラリの追加
yarn add box-node-sdk express ejs axios
# 必要なファイルを追加touch app.js
touch index.ejs
# こちらは事前準備用のファイル(必須ではない)touch setup.js
package.json
に、startをスクリプトを追加します。(Herokuがstartを実行します)
{"scripts":{"start":"node app.js"},}
Boxでアプリの設定をしたときにダウンロードしたconfig.json
をプロジェクトフォルダ直下に配置します。
任意のエディタでsetup.js
を作成します。
このファイルは事前に(1)Appユーザーを作成し、(2)ファイルをアップロードし、(3)BoxEditの統合を使えるようにするという操作をするために作成し、実行します。これらの処理が事前にできればいいだけなので、ファイルとしてプロジェクトに追加する必要はありません。
実行前にSample.docx
というダミーのMS Wordのファイルを用意しておきます。config.json
が同じフォルダにある前提となっています。
ダミーのファイルはとくにWordである必要も、このファイル名である必要も有りませんが、変更する場合コードも修正してください。
constboxSDK=require("box-node-sdk");constconfig=require("./config.json");constaxios=require("axios");constfs=require("fs");/**
* app.jsで利用する、AppUserと、サンプルのWordファイルを予め登録する。
* また、OpenWithを利用するために必要な、統合の設定も行う。
*/constmain=async()=>{try{// 作成済みのJWT認証用のConfigファイルを読み込むconstsdk=awaitboxSDK.getPreconfiguredInstance(config);// ServiceAccountのClientオブジェクトを作成constsaClient=sdk.getAppAuthClient("enterprise");// app userを作成 (OpenWithはServiceAccountではつかえず、AppUserが必要なため)letappUser=awaitsaClient.enterprise.addAppUser("Sample App User");// app userのホームフォルダに、サンプルとしてSample.docをアップロードするsaClient.asUser(appUser.id);conststream=fs.createReadStream("./Sample.docx");constfiles=awaitsaClient.files.uploadFile("0","Sample.docx",stream);constfile=files.entries[0];saClient.asSelf();// 念の為、現在利用可能なWebApp統合を一覧する// 13418が入っていないなら、設定がまちがっているconstappIntegs=awaitsaClient.get("/app_integrations");console.log("利用可能なWebApp統合一覧",appIntegs.body);/*
{
next_marker: null,
entries: [
{ type: "app_integration", id: "10897" },
{ type: "app_integration", id: "1338" },
{ type: "app_integration", id: "13418" }, <= 13418がBox Editの統合
{ type: "app_integration", id: "3282" },
],
limit: 100,
};
*/// 作成したAppUserに、BoxEditのアプリ統合を利用できるようにする。// clientオブジェクトから何故かpostの実行(client.post)がうまく機能しなかったので、axiosで実行する// Authorizationにつけるアクセストークンは、ServiceAccountのものを利用する必要がある。constsaTokenInfo=awaitsdk.getEnterpriseAppAuthTokens();constsaAxios=axios.create({baseURL:"https://api.box.com/2.0",headers:{Authorization:`Bearer ${saTokenInfo.accessToken}`,},});// ここでは13418(BoxEdit)のみを登録するが、他のものも登録しておくと開くボタンから利用可能になるawaitsaAxios.post("/app_integration_assignments",{assignee:{type:"user",id:appUser.id,},app_integration:{type:"app_integration",id:"13418",},});// 以下のAppUserとFileのIDをapp.jsで利用するconsole.log("==================== 以下のIDをメモして、app.jsで利用する ====================");console.log(`const USER_ID = "${appUser.id}"`);console.log(`const FILE_ID = "${file.id}"`);}catch(e){console.error(e.toString());}};main();
config.json
とSample.docx
が同じフォルダにあることを確認して、以下のコードを実行し、USER_IDと、FILE_IDをとって置きます。app.jsでは、この部分を書き換えてください。
【注意】config.json
は、このサンプルでは簡易的にgitに入れていますが、本番環境ではファイルとしてgit等に入れるべきではなく、環境変数等に置くべきです。
任意のエディタでapp.js
を以下の内容で作ります。
constexpress=require("express");constboxSDK=require("box-node-sdk");constconfig=require("./config.json");constapp=express();app.set("views",".");app.set("view engine","ejs");/**
* setup.jsで作成したファイルとユーザーを書き換える
*/constUSER_ID="";constFILE_ID="";app.get("/",async(req,res)=>{try{if(!USER_ID||!FILE_ID){res.send("USER_IDとFILE_IDに値をいれてください。");return;}constsdk=awaitboxSDK.getPreconfiguredInstance(config);// AppUserの権限でClientオブジェクトを作成constauClient=awaitsdk.getAppAuthClient("user",USER_ID);// トークンをダウンスコープする// APIリファレンスには載っていないが、UI Elementsの説明には書いてあるAPI// ここでは、OpenWithで必要なものと、Previewで必要なものを両方スコープにいれてトークンをダウンスコープするconstdownToken=awaitauClient.exchangeToken(["item_execute_integration","item_readwrite","item_preview"],`https://api.box.com/2.0/files/${FILE_ID}`);// テンプレート(index.ejs)にパラメータを渡して、HTMLを返すres.render("index",{fileId:FILE_ID,token:downToken.accessToken,});}catch(e){console.error(e.toString());}});constport=process.env.PORT||3000;app.listen(port,()=>{console.log(`express started on port ${port}`);});
ダウンスコープについて
Box UI Elementsは、Javascriptによりブラウザ内実行され、Box APIを直接実行します。このため、アクセストークンをブラウザに渡す必要があります。
これは、ブラウザ上の開発者用コンソールを利用すると、アクセストークンがエンドユーザーに見えてしまうということを意味しています。セキュリティ上問題があるので、アクセストークンのダウンスコープという手順が必要になります。ダウンスコープをすることで、指定したファイルIDに指定した操作だけを許すというアクセストークンに変換可能です。
ダウンスコープ前のアクセストークンをブラウザ側に渡すことは避けるべきでしょう。
任意のエディタでindex.ejs
を以下の内容で作ります。
<!DOCTYPE html><htmllang="en-US"><head><metacharset="utf-8"/><title>Sample</title><linkhref="https://cdn01.boxcdn.net/platform/elements/11.0.2/ja-JP/openwith.css"rel="stylesheet"type="text/css"></link><linkhref="https://cdn01.boxcdn.net/platform/preview/2.34.0/ja-JP/preview.css"rel="stylesheet"type="text/css"></link><script src="https://cdn01.boxcdn.net/platform/elements/11.0.2/ja-JP/openwith.js"></script><script src="https://cdn01.boxcdn.net/platform/preview/2.34.0/ja-JP/preview.js"></script></head><body><divclass="openwith"style=""></div><divclass="preview-container"style="height:800px; width:100%;"></div><script>// app.jsから渡されたパラメータconstfileId="<%= fileId %>"// ダウンスコープされたアクセストークンconsttoken="<%= token %>"constopenWith=newBox.ContentOpenWith();openWith.show(fileId,token,{container:".openwith"})constpreview=newBox.Preview();preview.show(fileId,token,{container:".preview-container"});</script></body></html>
Herokuの準備
Content OpenWithはhttpsでしか動かないからherokuにデプロイしているだけです。
ローカルでhttpsで実行でも大丈夫です。
以下のコマンドを実行してHerokuにアプリを作成し、デプロイします。
# herokuにアプリを作成
heroku create
# gitを導入
git init
git add .
git commit -m"init"# herokuにデプロイ
git push heroku master
Boxの設定変更
CORS設定
Box UI Elementsはブラウザ上でAPIを呼び出すので、CORSの設定が必要になります。
もう一度Boxの開発者コンソールに戻り、CORSの設定をします。
HerokuのURLを登録します。
アプリの承認
JWTアプリは管理者に承認されている必要があるので、以下の手順で承認します。
OAuth 2.0資格情報の、クライアントIDをコピー。
管理者コンソール > アプリ(左ナビ) > カスタムアプリ (上部タブ)に移動し、「新しいアプリケーションを承認」というボタンを押します。
クライアントIDを聞かれるので、コピーしたクライアントIDをペーストして承認します。
ローカルのBoxEditの設定
続いて、ローカルマシンでのBoxEditのセーフリストの設定します。
詳細はこちらをご覧ください。
https://ja.developer.box.com/guides/embed/ui-elements/custom-domains/
Macの環境だと、OpenWith.sh
をダウンロードし、herokuのドメイン:xxx.heorkuapp.com
をコマンドから追加するイメージです。
エンドユーザーにContent OpenWithを利用させたい場合は、この操作を1回だけユーザーにやってもらう必要があるので、この点が少しハードルが高いかも、という懸念はあります。
ちなみにこれをやらないとエラーになるとかではなく、OpenWithで描画される開くボタンがdisable状態になるだけです。
実行してみる
(URLがわかっているならわざわざ叩く必要ないですが)以下のコマンドで、ブラウザが立ち上がります。
heroku open
以下のように開くボタンとプレビューが表示され、開くボタンを押すとBoxEditが呼び出され、ローカルのエディタがたちあがります。
Image may be NSFW.
Clik here to view.
MS Wordで内容を変更し、保存ボタンを押した後、Web画面をリロードするとプレビューで変更されていることが確認できます。
感想・まとめ
ちょっとめんどくさいけど、作ると結構スムーズに動くのでカスタムアプリからBoxに連携する際に強力な道具になりそうです。
エンドユーザーのコンピュータにBoxToolsのインストールと、セーフリストの設定をする必要がある部分が少しつらそうです。
あと、OpenWithは、以下が抜けていると動かないので注意。
- ローカルにBoxToolsが入っている必要がある
- ローカルでセーフリストにドメインを登録する必要がある
- HTTPS上でのみOpenWithが動く
- AppUserで動かす必要がある
- 統合を事前にAppUserに紐付ける必要あり (これはServiceAccountのトークンでやらないとだめ)
- BOXの開発者コンソールで適切なスコープとCORSの設定が必要
よくわからなかったこと
上記の一連の手順で基本的にOpenWithの実装ができましたが、以下の点についてはよくわからなかったので、記しておきます。いつか答えがみつかるといいな・・・。
MS Word(というか、どんなエディタでも)で保存した後に、標準のBoxであれば内容の変更を察知してRefreshボタンのポップアップが表示されるが、UI Elementではこの動作をしない。このため手動でリロードする必要があるが、ここはなんとかならないものか・・
もしかしたら、カスタムアプリに通知を受け取るAPIの口を用意しておけば、Webhookで更新をトリガーにAPI呼び出しをすることで、自動でリロードをかけるたり、それを促す通知をできるかも。