今回のアップデート(2016/05/08 の定期メンテナンスにおけるkintone API、User API更新情報)で難解なのがこのkintone.proxy.upload()です。kintone JavaScript APIとして外部APIをコールできるkintone.proxy()のファイルアップロード版なのですが、試行錯誤の結果、「transfer.sh」というファイル共有サービスで利用可能なことが分かったのでご紹介したいと思います。

transfer.sh

transfer.sh(http://transfer.sh)は、コマンド・シェルでアップロードできて、10GBまでのファイルが14日間ストアされるファイル共有サービスです。公式サイトを見ると売り文句の通り、cURLコマンドやシェルの実行例が記載されています。
スクリーンショット_2016-05-09_0_51_45

次のようなコマンドでローカルファイル(logo.jpg)をアップロード出来ます。メソッドとしてはPUTで実行されています。

$ curl --upload-file ./logo.jpg https://transfer.sh/logo.jpg # リクエスト
https://transfer.sh/66nb8/logo.jpg # レスポンス

レスポンスとして、共有されたファイルのURLが発行されます。

kintone.proxy.upload() による添付ファイルの共有

kintone.proxy.upload()を使えば、このcURLコマンド相当の処理をkintone JavaScript APIで実装し、kintoneに添付されたファイルをオープンに共有することが簡単にできるというわけです。早速設定していきましょう。

アプリ

まず、次のようなフィールドを持ったアプリを準備しましょう。

フィールド名 フィールドコード(フィールドタイプ) 利用目的
タイトル Title(文字列1行) タイトル
ファイル File(ファイル) 管理しているファイル ※今回transfer.shで共有できるファイルは最上段の1個です。
(スペースフィールド) space(スペース) ボタン設置用スペースフィールド
共有URL FileShareUrl(リンク:Web) transfer.shで共有されたファイルリンク
共有終了日 EndDay(日付) ファイル共有が終了する日

スクリーンショット_2016-05-09_1_00_24

JavaScriptのコーディング例

/*
* fileShare.js
*
* Dependencies:
*   jQuery( https://js.cybozu.com/jquery/1.11.3/jquery.min.js )
*   SweetAlert( https://js.cybozu.com/sweetalert/v1.1.0/sweetalert.min.js / https://js.cybozu.com/sweetalert/v1.1.0/sweetalert.css)
*   spin.js( https://js.cybozu.com/spinjs/2.3.2/spin.min.js )
*   Moment.js( https://js.cybozu.com/momentjs/2.10.6/moment-with-locales.min.js )
*/
jQuery.noConflict();
(function($) {
  "use strict";

  // kintoneの添付ファイルをBlobで取得する
  function getBlob(fileKey, fileName, callback) {
    var apiurl = '/k/v1/file.json?fileKey=' + fileKey;
    var xhr = new XMLHttpRequest();
    xhr.open('GET', apiurl, true);
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); //これが無いとIE,FFがNG
    xhr.responseType = "blob";
    var blob = xhr.responseType;

    xhr.onload = function() {
      var blob = xhr.response;
      callback(blob);
    };
    xhr.send();
  }

  // transfer.shにファイルをアップロードする
  function uploadToService(fileName, blob) {
    var url = 'https://transfer.sh/' + encodeURIComponent(fileName);
    return kintone.proxy.upload(
      url,
      'PUT', {}, {
        'format': 'RAW',
        'value': blob
      }
    );
  }

  // transfer.shの公開URLと共有終了日をkintoneのレコードにREST APIでセットする関数
  function updateRecord(url) {
    url = url.replace('\n', '');
    return kintone.api(kintone.api.url('/k/v1/record', true), 'PUT', {
      app: kintone.app.getId(),
      id: kintone.app.record.getId(),
      record: {
        "FileShareUrl": {
          "value": url // 共有URL
        },
        "EndDay": {
          "value": moment().add(13, 'day').format('YYYY-MM-DD') // 共有終了日
        }
      }
    });
  }

  kintone.events.on(['app.record.detail.show'], function(event) {
    var record = event.record;
    var attachments = record['File'].value;

    // スペース要素
    var el = kintone.app.record.getSpaceElement('space');
    // 親要素のサイズを調整
    $(el).parent().width('200px');
    $(el).parent().height('100px');
    $(el).css({
      'margin-left': '10px'
    });
    // 正味のコンテナ要素
    var $container = $('<div>').prop({
      'id': 'container'
    }).appendTo(el);
    // ボタン等追加
    $container.append(
      $('<button>').prop({
        id: 'joyzo-button'
      }).css({
        'margin-top': '30px'
      }).addClass('kintoneplugin-button-normal').text('transfer.shで共有'),
      $('<div>').text('※最上段の添付ファイルのみが共有されます').css({
        'font-size': '8pt',
        'margin': '3px'
      })
    );

    // ボタンクリックイベント
    $('#joyzo-button').bind('click', function() {
      showSpinner();
      //console.log(attachments);
      // 添付ファイル(Blob)の取得 -> transfer.shへのアップロード -> レコード更新
      new kintone.Promise(function(resolve, reject) {
        getBlob(attachments[0].fileKey, attachments[0].name, function(blob) { // 添付ファイルの取得
          resolve(blob);
        });
      }).then(function(blob) {
        //console.log(blob);
        return uploadToService(attachments[0].name, blob); // transfer.sh へのアップロード
      }).then(function(resp) {
        //console.log(resp);
        var url = resp[0];
        return updateRecord(url); // レコード更新
      }).then(function() {
        hideSpinner();
        swal({
          title: 'ファイルアップロード完了',
          text: '',
          type: 'success'
        }, function() {
          location.reload(true);
        });
      }).catch(function(e) {
        hideSpinner();
        //console.log(e);
        var errmsg = 'ファイルアップロード失敗。';
        if (e.message !== undefined) {
          errmsg += e.message;
        }
        swal({
          title: 'ファイルアップロード失敗',
          text: errmsg,
          type: 'error'
        }, function() {
          return;
        });
      });
    });

    function showSpinner() {
      // 要素作成等初期化処理
      if ($('.kintone-spinner').length == 0) {
        // スピナー設置用要素と背景要素の作成
        var spin_div = $('<div id ="kintone-spin" class="kintone-spinner"></div>');
        var spin_bg_div = $('<div id ="kintone-spin-bg" class="kintone-spinner"></div>');

        // スピナー用要素をbodyにappend
        $(document.body).append(spin_div, spin_bg_div);

        // スピナー動作に伴うスタイル設定
        $(spin_div).css({
          'position': 'fixed',
          'top': '50%',
          'left': '50%',
          'z-index': '510',
          'background-color': '#fff',
          'padding': '26px',
          '-moz-border-radius': '4px',
          '-webkit-border-radius': '4px',
          'border-radius': '4px'
        });
        $(spin_bg_div).css({
          'position': 'absolute',
          'top': '0px',
          'z-index': '500',
          'width': '150%',
          'height': '150%',
          'background-color': '#000',
          'opacity': '0.5',
          'filter': 'alpha(opacity=50)',
          '-ms-filter': "alpha(opacity=50)"
        });

        // スピナーに対するオプション設定
        var opts = {
          'color': '#000'
        };

        // スピナーを作動
        new Spinner(opts).spin(document.getElementById('kintone-spin'));
      }

      // スピナー始動(表示)
      $('.kintone-spinner').show();
    };

    // スピナーを停止させる関数
    function hideSpinner() {
      // スピナー停止(非表示)
      $('.kintone-spinner').hide();
    };

    return event;
  });
})(jQuery);

コードの内容は、「transfer.shで共有」ボタンのクリックで、
①添付ファイルフィールドの1つ目の添付ファイルをXMLHttpRequestを用いてBlob形式で取得(getBlob関数)
②kintone.proxy.upload()でtransfer.shにリクエスト(uploadToService関数)
③kintone.proxy.upload()のリクエストが成功し、transfer.shでファイルが公開できたら、アクセスURLと共有終了日をレコード更新でセットする(updateRecord関数)
という感じの内容です。

アプリの動作とファイル共有

①レコード新規登録・編集画面でファイルをレコードに添付し、保存後に②「transfer.sh」ボタンをクリック
スクリーンショット_2016-05-09_1_00_24_2
③リクエスト(アップロード)が成功すると、(ダイアログ表示後リロードされて)共有URLがセットされれる
スクリーンショット_2016-05-09_1_29_25
④共有URLにアクセスすると、ファイルが共有され、ダウンロード出来る状態になっていることが確認できる。第3者に共有したい場合にもこの共有URLを伝えることで、アクセスできる
スクリーンショット_2016-05-09_1_30_10

kintone.proxy.upload()の考察

今回この記事でわかることをひとつまとめておくと、『cURLコマンドにおける「–upload-file」オプションでのリクエストとkintone.proxy.upload()/PUTに等価性がある』ということです。

まとめ

kintoneに添付されたファイルをパブリックな場に共有したい際に便利なtransfer.shへの連携を例にkintone.proxy.upload()のリクエスト例をお届けしました。冒頭に「試行錯誤した結果」と書かせて頂きましたが、実はこの記事は色々調べてみた結果から辿ってまとめたものです。この経緯を詳しく知りたい方は長編版をまとめていますので、こちらを御覧ください。

 


株式会社ジョイゾー