Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Box UI ElementsのContent OpenWithでファイルの更新に反応してみた クライアントサイド編

$
0
0

この記事のシリーズ:

Box UI ElementsのContent OpenWithでBox Editをつかってみた
Box UI ElementsのContent OpenWithでG Suiteを開いてみた
Box UI ElementsのContent OpenWithでファイルの更新に反応してみた
Box UI ElementsのContent OpenWithでファイルの更新に反応してみた クライアントサイド編← この記事

コードは、Githubでも確認いただけます。

前回の内容と今回試すこと

前回、Box UI ElementsのContent OpenWithでファイルの更新に反応してみたという記事の中で、Box UI ElementsのOpenWithを使って、カスタム画面から変更した際に、サーバーサイドでロングポーリングをおこない、変更を検知して再描画するという内容を書きました。

@daichiiiiiiiさんから、再度コメント以下のようなコメントいただきました。ありがとうございます!

BoxのWebアプリの場合、ClientサイドでLong PollingとEvent logチェックしています。
同じAppUserを複数ユーザが使う場合には、データ流出等につながる可能性があるので適しませんが、1:1(AppUser:実User)の場合であればクライアントサイドに実装しても良い気もします。

確かにアクセストークンが漏れても構わない場合、クライアントサイドで行ったほうが効率が良さそうです。
とはいえ、OpenWithやPreviewを表示するためにすでにダウンスコープしたアクセストークンを露出させています。
同じアクセストークンでEventを購読できるなら問題なさそうです。→ 同じトークンでEventを取得できます。
というわけで、早速こちらもためしてみました。

変えたところ

変更の検知をクライアントサイドに寄せるので、再びサーバー側のロジックはシンプルなものになります。

app_client_long_polling.js
constexpress=require("express");constboxSDK=require("box-node-sdk");constconfig=require("./config.js");constapp=express();app.set("views",".");app.set("view engine","ejs");/**
 * setup.jsで作成したファイルとユーザー
 */constUSER_ID="12771965844";constFILE_ID="665319803554";app.get("/",async(req,res)=>{try{constsdk=boxSDK.getPreconfiguredInstance(config);// AppUserの権限でClientオブジェクトを作成constauClient=sdk.getAppAuthClient("user",USER_ID);// トークンをダウンスコープする// APIリファレンスには載っていないが、UI Elementsの説明には書いてあるAPI// ここでは、OpenWithで必要なものと、Previewで必要なものを両方スコープにいれてトークンをダウンスコープするconstdownToken=awaitauClient.exchangeToken(["item_execute_integration","item_readwrite","item_preview","root_readwrite",],`https://api.box.com/2.0/folders/0`);// テンプレートにパラメータを渡して、HTMLを返すres.render("index_client_long_polling",{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}`);});

次に、クライアントコードでイベントを購読するようにします。

index_client_long_polling.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://cdn.polyfill.io/v2/polyfill.min.js?features=es6,Intl"></script><script src="https://cdn01.boxcdn.net/polyfills/core-js/2.5.3/core.min.js"></script><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><style>.openwith-container{margin-left:250px;}.preview-container{height:800px;width:100%;}</style></head><body><h3>File Id: <%=fileId%></h3><divid="container"><divclass="openwith-container"></div><divclass="preview-container"></div></div><script>// app.jsから渡されたパラメータconstfileId="<%= fileId %>"consttoken="<%= token %>"constopenWith=newBox.ContentOpenWith();openWith.show(fileId,token,{container:".openwith-container"})letpreview=newBox.Preview();preview.show(fileId,token,{container:".preview-container",autoFocus:false});openWith.addListener("execute",async()=>{// openWithが開かれたので、ロングポーリング開始// リアルタイムサーバーのURLを取得constoptionsRes=awaitfetch("https://api.box.com/2.0/events",{method:"OPTIONS",headers:{"Content-Type":"application/json; charset=utf-8","Authorization":`Bearer ${token}`,}});constoptionsJRes=awaitoptionsRes.json();letlastSequenceId=0;// 最後のSequenceIDconstsubscribe=async(streamPosition="now")=>{// リアルタイムサーバーに対してロングポーリングを行う// CORSエラーを避けるため、シンプルなリクエストを使うletrtsRes=awaitfetch(optionsJRes.entries[0].url,{method:"GET",mode:"no-cors",headers:{"Content-Type":"application/x-www-form-urlencoded"}})// ロングポーリングからはtype: "opaque"というのが帰ってくるのだけど、、よくわからない・・・。// とはいえ、応答が帰ってくると、何かのイベントが発生したことはわかる。// console.log("rtServer res", rtsRes) // 何かのイベントが発生したので、EventAPIをGETで叩き、詳細情報を取り出す。// 上のロングポーリングだけに反応して再描画すると、再描画のプレビューイベントを拾ってしまい、// 無限ループしてしまうので、イベントを更新に絞って確認する必要がある。constqs=newURLSearchParams();qs.set("event_type","ITEM_UPLOAD");// 更新だけに絞る。qs.set("stream_type","sync");qs.set("stream_position",streamPosition);// 初回は"now", 2回め以降はnext_stream_positionが入るconstgetRes=awaitfetch(`https://api.box.com/2.0/events?${qs.toString()}`,{method:"GET",headers:{"Content-Type":"application/json; charset=utf-8","Authorization":`Bearer ${token}`,}});constgetJRes=awaitgetRes.json();// 複数もどってくるイベントを、対象ファイルの更新のもので、最新のものに絞るconstlatestEvent=getJRes.entries.reduce((acc,cur)=>{if(cur.event_type==="ITEM_UPLOAD"&&cur.source&&cur.source.type==="file"&&cur.source.id===fileId){if(!acc){returncur;}return(cur.source.sequence_id>acc.source.sequence_id)?cur:acc;}returnnull;},null)// 前回処理したイベントより進んでいるときだけリロードする// 初回にstreamPosition === "now"でイベントを取得すると、イベントが何も帰ってこない。// 対象のファイルの更新イベントじゃないかもしれないけどリフレッシュかける。ここはもうちょっとうまくやれそうな気もする。if(streamPosition==="now"||latestEvent&&latestEvent.source.sequence_id>lastSequenceId){// 今回処理するイベントのsequence_idを保存if(latestEvent){lastSequenceId=latestEvent.source.sequence_id;}// previewだけを描画し直す。preview=newBox.Preview();// 毎回プレビューの位置までスクロールされたくないのでautoFocus:falsepreview.show(fileId,token,{container:".preview-container",autoFocus:false});}// 次のイベントへawaitsubscribe(getJRes.next_stream_position)}// イベントの購読開始awaitsubscribe();})</script></body></html>

まとめ

コードはもうすこし改善の余地はありそうですが、とりあえずクライアントサイドからポーリングし、画面を再描画することができました。

クライアントサイドだけだと、SDKが利用できないので、少しコードを書くのが大変です。


Viewing all articles
Browse latest Browse all 8691

Trending Articles