6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PouchDBを改造してReact Nativeでも動くようにした

Posted at

本稿はHacking PouchDB to Use It on React Nativeの日本語訳

CouchDBと同期が必要なReact Nativeアプリを開発している。PouchDBはJSで組まれたCouchDBと同期可能なデータベースライブラリで、ブラウザを主眼としているが、React Nativeでも動かすことが出来る。
そのために自分はreact-native-sqlite-2pouchdb-adapter-react-native-sqliteを作った。

しかしそれだけではRNで動かすには不十分だった。
例えばRNはFileReader.readAsArrayBufferをまだサポートしていないためAttachmentsの取扱いに難があった。将来的に彼らが対応してくれることを願う。

そしてpouchdb-react-nativeプロジェクトの活動が最近見られないようだ。なので自分で何とかすることにして、今回動かすことに成功した。本稿ではどうやったか説明する。

React Native上でPouchDBを動かす手順

まずは実際にどうやってPouchDBを動かすか、手順を説明したい。
自分は今回、RN用にPouchDBのコアモジュールをハックしたパッケージを2つ作成した。
こちらがデモアプリ。
これを拙作アプリInkdropにて採用するつもり。

依存モジュールのインストール

まずはPouchDBのコアパッケージをインストールする:

npm i pouchdb-adapter-http pouchdb-mapreduce

次に、React Native用に手を加えたPouchDBのパッケージをインストールする:

npm i @craftzdog/pouchdb-core-react-native @craftzdog/pouchdb-replication-react-native 

そしてSQLite3エンジンのモジュールをインストールする:

npm i pouchdb-adapter-react-native-sqlite react-native-sqlite-2
react-native link react-native-sqlite-2

PouchDBで必要なpolyfill用のパッケージを入れる:

npm i base-64 events

Polyfillを作る

PouchDB内で使用される関数群をpolyfillするjsファイルを作る:

import {decode, encode} from 'base-64'

if (!global.btoa) {
    global.btoa = encode;
}

if (!global.atob) {
    global.atob = decode;
}

// node依存のコードが実行されないようにする
process.browser = true

それをindex.jsの先頭でimportしてやる。

PouchDBを初期化する

以下のようなpouchdb.jsを作る:

import PouchDB from '@craftzdog/pouchdb-core-react-native'
import HttpPouch from 'pouchdb-adapter-http'
import replication from '@craftzdog/pouchdb-replication-react-native'
import mapreduce from 'pouchdb-mapreduce'

import SQLite from 'react-native-sqlite-2'
import SQLiteAdapterFactory from 'pouchdb-adapter-react-native-sqlite'

const SQLiteAdapter = SQLiteAdapterFactory(SQLite)

export default PouchDB
  .plugin(HttpPouch)
  .plugin(replication)
  .plugin(mapreduce)
  .plugin(SQLiteAdapter)

必要に応じてpouchdb-findとかのプラグインも追加する。

PouchDBを使う

あとはいつもの通り使用する:

import PouchDB from './pouchdb'

function loadDB () {
  return new PouchDB('mydb.db', { adapter: 'react-native-sqlite' })
}

PouchDBにどのように手を加えたか

React Nativeで動作させるためには、PouchDBのコアモジュールからFileReader.readAsArrayBufferが呼び出されるのを阻止しなければならない。
それは、全てのattachmentsをBlobの代わりにBase64エンコードで取り扱うという事だ。
それはごく少量の行への変更で実現できる

readAsArrayBufferが呼ばれている場所

PouchDBは各ドキュメントのMD5ダイジェストを計算しようとする。その際にreadAsArrayBufferが必要となる。

pouchdb-binary-utils/lib/index-browser.jsにて:

72 function readAsBinaryString(blob, callback) {
73   if (typeof FileReader === 'undefined') {
74     // fix for Firefox in a web worker
75     // https://bugzilla.mozilla.org/show_bug.cgi?id=901097
76     return callback(arrayBufferToBinaryString(
77       new FileReaderSync().readAsArrayBuffer(blob)));
78   }
79
80   var reader = new FileReader();
81   var hasBinaryString = typeof reader.readAsBinaryString === 'function';
82   reader.onloadend = function (e) {
83     var result = e.target.result || '';
84     if (hasBinaryString) {
85       return callback(result);
86     }
87     callback(arrayBufferToBinaryString(result));
88   };
89   if (hasBinaryString) {
90     reader.readAsBinaryString(blob);
91   } else {
92     reader.readAsArrayBuffer(blob);
93   }
94 }

この関数は以下のpouchdb-md5/lib/index-browser.jsから呼ばれる:

24 function appendBlob(buffer, blob, start, end, callback) {
25   if (start > 0 || end < blob.size) {
26     // only slice blob if we really need to
27     blob = sliceBlob(blob, start, end);
28   }
29   pouchdbBinaryUtils.readAsArrayBuffer(blob, function (arrayBuffer) {
30     buffer.append(arrayBuffer);
31     callback();
32   });
33 }

どうすればこれを避けられるか?

Attachmentsの保存部

getAttachmentメソッドのbinaryオプションを常に無効にする。
pouchdb-core/src/adapter.js を以下のように変更する:

714     if (res.doc._attachments && res.doc._attachments[attachmentId]
715       opts.ctx = res.ctx;
716       // force it to read attachments in base64
717       opts.binary = false;
718       self._getAttachment(docId, attachmentId,
719                           res.doc._attachments[attachmentId], opts, callback);
720     } else {

この変更により、attachmentsは常にbase64で取得されることに注意されたい。

Pull Replication

リモートデータベースからのレプリケーションの際、取得されたattachmentsを blobからbase64に変換してやる必要がある。
pouchdb-replication/lib/index.jsを以下のように変更してやる:

function getDocAttachmentsFromTargetOrSource(target, src, doc) {
  var doCheckForLocalAttachments = pouchdbUtils.isRemote(src) && !pouchdbUtils.isRemote(target);
  var filenames = Object.keys(doc._attachments);

  function convertBlobToBase64(attachments) {
    return Promise.all(attachments.map(function (blob) {
      if (typeof blob === 'string') {
        return blob
      } else {
        return new Promise(function (resolve, reject) {
          var reader = new FileReader();
          reader.readAsDataURL(blob);
          reader.onloadend = function() {
            const uri = reader.result;
            const pos = uri.indexOf(',')
            const base64 = uri.substr(pos + 1)
            resolve(base64)
          }
        });
      }
    }));
  }

  if (!doCheckForLocalAttachments) {
    return getDocAttachments(src, doc)
      .then(convertBlobToBase64);
  }

  return target.get(doc._id).then(function (localDoc) {
    return Promise.all(filenames.map(function (filename) {
      if (fileHasChanged(localDoc, doc, filename)) {
        return src.getAttachment(doc._id, filename);
      }

      return target.getAttachment(localDoc._id, filename);
    }))
      .then(convertBlobToBase64);
  }).catch(function (error) {
    /* istanbul ignore if */
    if (error.status !== 404) {
      throw error;
    }

    return getDocAttachments(src, doc)
      .then(convertBlobToBase64);
  });
}

これで動く。
@craftzdog/pouchdb-core-react-native@craftzdog/pouchdb-replication-react-nativeが必要なのはそのため。
もし問題を見つけたらここにPRを送って欲しい

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?