Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【React Native】React Native + Firebase + Expo 環境を構築する

React Native + Firebase + Expo

前回の記事では、React NativeにFirebaseを導入する方法を紹介しました。

rennnosukesann.hatenablog.com

しかしながら、通常のreact-native initによる開発は、実機デバッグをしたい場合に億劫です。一方、Expoを使った実機デプロイであれば楽に検証が可能です。

rennnosukesann.hatenablog.com

今回は会社の先輩にExpo対応可能であることを教えていたいたので、ExpoによるFirebase導入をやってみたいと思います。

Usage

今回はExpo公式のドキュメントを参考にしました。

Using Firebase - Expo Documentation

まずはcreate-react-native-appでプロジェクトを作成。

$ create-react-native-app my-app
$ cd my-app

npmでfirebaseをインストールします。

npm install --save firebase .

次に、前回の記事に従い2.3まで実施し、プロジェクト名とAPIキーを取得します。 その後、プロジェクトダッシュボードから「ウェブアプリにfirebaseを追加」をクリック。

f:id:rennnosukesann:20180530232737p:plain

するとプロジェクト設定を反映したfirebaseConfigJavascriptオブジェクトのテンプレートが取得できるので、コピーしてsrc/App.jsに貼り付けます。

インストールしたfirebaseモジュールもインポートしてください。

f:id:rennnosukesann:20180530232855p:plain

import * as firebase from 'firebase';

const firebaseConfig = {
  apiKey: "<YOUR-API-KEY>",
  authDomain: "<YOUR-AUTH-DOMAIN>",
  databaseURL: "<YOUR-DATABASE-URL>",
  storageBucket: "<YOUR-STORAGE-BUCKET>"
};

firebase.initializeApp(firebaseConfig);

これで準備完了です!簡単ですね!

Demo

先にExpo Cliantをインストールします。

rennnosukesann.hatenablog.com

ローカルをサーバーでアプリ立ち上げ、コンソールに表示されたQRコードを実機で読み込むとExpo上でアプリが起動します。

$ npm start

エラーが無く起動が完了したら成功です!

f:id:rennnosukesann:20180530235224j:plain:w300

続き

rennnosukesann.hatenablog.com

参考

Using Firebase - Expo Documentation

Installation & Setup in JavaScript  |  Firebase Realtime Database  |  Firebase

【React Native】React Native + Firebase環境を構築する(iOS)

React Native + Firebase環境を構築する際のメモ。

Firebase

firebase.google.com

FirebaseはGoogleが提供するモバイルプラットフォームであり、クラウドDBや認証、アナリティクス、クラッシュレポートなど多くのサービスをサポートしています。今回はReact NativeによるモバイルアプリにこのFirebaseを導入したいと思います。

Usage

1. プロジェクト作成

1.1 $ react-native initでReact Nativeプロジェクトを作成します。
$ react-native init my-app
1.2 firebaseのインストール

作成したプロジェクト内でreact-native-firebaseをインストールします。

$ cd my-app
$ npm install --save react-native-firebase

2. FirebaseのiOS/Android設定ファイルを取得する

2.1 Firebaseにプロジェクトを作成

GoogleのFirebase公式ページへ遷移し、「使ってみる」を押す(Googleにログインしていない場合はログインする)

firebase.google.com

f:id:rennnosukesann:20180529210629p:plain

2.2 プロジェクト追加をクリック

f:id:rennnosukesann:20180529211041p:plain

2.3 プロジェクト名とIDを入力し、「次へ」を押す

f:id:rennnosukesann:20180529221556p:plain:w500 f:id:rennnosukesann:20180529221601p:plain:w500 f:id:rennnosukesann:20180529221604p:plain:w500

2.4 プロジェクトのダッシュボードに移動、「iOSアプリにFirebaseを追加」を押す

f:id:rennnosukesann:20180529223143p:plain

2.5 アプリを登録

iOS バンドル ID」は作成したプロジェクトと同名にします。

f:id:rennnosukesann:20180529223211p:plain

アプリ設定ファイル(iOS)(GoogleService-Info.plist)をダウンロードします。

f:id:rennnosukesann:20180529223240p:plain

取得した設定ファイルをios/[project]/配下に置きます。
その後、ios/[project]/配下でpod initを使用し、Podfileを作成します。

$ pod init

Podfileを以下のように書き換えます。

target '[Project name]' do
  pod 'Firebase/Core'
end

書き換えたら

$ pod install
2.6 AppDelegate.mを書き換え

[プロジェクト名].xcworkspaceXCodeで立ち上げ、以下の記述を追加

Swiftの場合
import Firebase // 追加

 ...

  func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?)
    -> Bool {
    FirebaseApp.configure() // 追加
Objective-Cの場合
@import Firebase; // 追加

...

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [FIRApp configure]; // 追加

}

デプロイ

$ react-native run-ios

参考

Using Firebase - Expo Documentation

【React Native】react-native-swiperで画面スワイプを実装する

react-native-swiper

React Nativeでスワイプ画面遷移が可能になるreact-native-swiperを試してみました。

github.com

Usage

1. 前回の記事に従い、create-react-native-appをインストール

rennnosukesann.hatenablog.com

2. create-react-native-appプロジェクトを作成
$ create-react-native-app react-native-swiper-app
$ cd react-native-swiper-app
3. react-native-swiperをインストール
$ npm install --save react-native-swiper
4. デプロイ
$ npm start

Source

サンプルコードをもとにApp.jsを修正しました。

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Swiper from 'react-native-swiper';

const styles = StyleSheet.create({
  slide: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#9DD6EB',
  },
  text: {
    color: '#fff',
    fontSize: 30,
    fontWeight: 'bold',
  }
})

export default class App extends React.Component {
  render() {
    return (
      <Swiper showsButtons={true}>
        <View style={styles.slide}>
          <Text style={styles.text}>First Page</Text>
        </View>
        <View style={styles.slide}>
          <Text style={styles.text}>Second Page</Text>
        </View>
        <View style={styles.slide}>
          <Text style={styles.text}>Third Page</Text>
        </View>
      </Swiper>
    );
  }
}

Swiperの小要素としてView複数設定することで、設定した小要素間をスワイプで遷移可能になります。

Demo

f:id:rennnosukesann:20180528223553g:plain:w300

Options

スワイプ方向を垂直にする

horizontal属性でスワイプ方向を変更できます。
デフォルトはtrue

<Swiper horizontal={false}>
 ...
</Swiper>

f:id:rennnosukesann:20180528225303g:plain:w300

スワイプをループさせない

loogfalseにすると、末尾ページから先頭ページへのスワイプを抑止できます。デフォルトはtrue

<Swiper showButton={true} loop={true}>
 ...
</Swiper>
自動スワイプ

autoplaytrueにすると、スワイプが自動化されます。
デフォルトはfalse

 <Swiper autoplay={true}>
</Swiper>

参考

github.com

【React Native】Expo + react-native-gifted-chat でチャットアプリを作る

最近React Nativeの開発を始めたのですが、いろいろなReact NativeのUIコンポーネントが配布されているようなので 適当にいじって遊んでみることにしました。

今回は、チャットUIとして提供されているreact-native-gifted-chatを試してみました。

react-native-gifted-chat

github.com

React NativeのUIコンポーネントです。
簡単にMessengerライクなチャット画面を作成することができます。
商用でも全然いけそうな見た目してます。

f:id:rennnosukesann:20180527153735p:plain

Expo

今回は実機(iOS)で開発するため、実機開発用のデプロイツールも使いました。

expo.io

ExpoオープンソースのReact Nativeビルドツールチェーンです。
Expoを使うことで、iOS/Android端末上へのアプリのビルド/デプロイが簡単に行なえます。
またソースコードの変更を即座に端末内のアプリに反映することができます。

Usage

早速作っていきます!

  1. モバイル端末にExpo Clientをインストールします。

f:id:rennnosukesann:20180527150455j:plain:w300

  1. モバイル端末とお使いのPCを同一のWi-fiに接続します(テザリングでも構いません)。

  2. セットアップ(テザリングしている人は先にnpm installをWi-fiでやったほうが通信量的にいいです)

$ npm install -g react-native create-react-native-app
$ create-react-native-app my-app
$ cd my-app
$ npm start

出現したQRコードをモバイル端末で読み取ります。
するとExpoアプリを起動できる通知が来るので、通知をタップ。

アプリが起動したらOKです!

あとは react-native-gifted-chatnpm経由でインストールしていきます。

$ npm install react-native-gifted-chat --save

src

公式のリポジトリを参考に、App.jsを下記のように書き換えてみました。

import React from 'react';
import { GiftedChat } from 'react-native-gifted-chat';

export default class App extends React.Component {

    componentWillMount() {
        this.setState({messages : []});
    }

    reply(){
        return {
            _id: 1,
            text: 'Hello!',
            createdAt: new Date(),
            user: {
                _id: 2,
                name: 'React Native',
                avatar: 'https://placeimg.com/140/140/any',
            }
        };
    }


    onSend(messages = []) {
        this.setState(previousState => ({
            messages: GiftedChat.append(GiftedChat.append(previousState.messages, messages), this.reply()),
        }))
    }

    render() {
        return (
                <GiftedChat
                messages={this.state.messages}
                onSend={messages => this.onSend(messages)}
                user={{
                    _id: 1,
                    name: 'you',
                    avater: 'https://placeimg.com/140/140/any'
                }}
                />
        );
    }
}

GiftedChatが今回利用するチャットのUIコンポーネントになります。 messagesにはメッセージの履歴が渡され、 onSendにはSendボタンタップ時の挙動が渡されます。 userには操作する人のユーザ情報(名前やサムネイルなど)が渡されます。

Stateの持つmessagesにメッセージオブジェクトを追加していくことでどんどん履歴を追加できます。
メッセージオブジェクトのフォーマットは公式Githubreply()メソッドが返すオブジェクトを参照してください。

Demo

f:id:rennnosukesann:20180527154732j:plain:w300

うまくできました! GiftedChatをそのままつかうだけでも結構リッチなChatUIを使うことができました。

参考

github.com

github.com

expo.io

【JavaScript】async/await

非同期処理

JavaScriptでは非同期処理を行うような関数を実行するとPromiseオブジェクトが返ってくることパターンが多いです。
返ってきたPromiseオブジェクトに対してthenメソッドを読んで非同期処理終了後の処理も記述できるので、呼び出す側がコールバックまみれになりにくく、良い感じです。

function caller(){
  let promise = http(); // Promiseオブジェクトを返す関数
  promise.then(response => console.log(response));
}

caller();
console.log("caller() was called.");

この非同期処理はasync``awaitでより簡単に記述することができます。

async/await

async

asyncは非同期処理を完結に記述するための修飾子です。
asyncを関数定義に修飾することで、非同期関数として定義することができます。

async function http(){
 // ...
}

関数にasyncを付加することで、呼び出し時にPromiseオブジェクトを返すようになります。

let promise = http(); 
console.log(promise); // Promise

返したPromiseオブジェクトは、asyncを付加した関数が終了するタイミングでresolveされます。 またasyncを付加した関数が例外を送出した場合はrejectされます。

await

awaitはPromiseを返す式に修飾することで、Promiseに紐付いた非同期処理が終了するまでasync関数内の処理が一時停止します。

async function http(){
  await setTimeout(()=>("timeout."),3000);
  return "http response."
}

一時停止したasync関数の処理は、await式の処理が終了して値が返されてから再開されます。

Usage

async function http(){
  await setTimeout(()=>("timeout."),3000);
  return "http response."
}

function caller(){
  let promise = http();
  promise.then(response => console.log(response)); // 3秒後に出力
}

console.log(caller());
console.log("caller() was called."); // 先に出力

参考

developer.mozilla.org

async/await 入門(JavaScript)

【AngularJS】ng-repeatの要素に一意な識別子を付加する

メモ。

AngularJSのng-repeatによってHTML内の繰り返し要素を生成できますが、

HTML
<div ng-app="myApp">
  <div ng-controller="MyAppCtrl">
    <div ng-repeat="msg in messages">
      <p>{{msg}}</p>
    </div>
  </div>
</div>
JavaScript
let mod = angular.module('myApp',[]);
mod.controller('MyAppCtrl', ['$scope', function($scope){
  $scope.messages = ["hello" , "ng-repeat"];
}]);

要素型を文字列要素にした場合、同一の内容をiterableオブジェクトに格納すると以下のエラーが発生する場合があります。

JavaScript
let mod = angular.module('myApp',[]);
mod.controller('MyAppCtrl', ['$scope', function($scope){
  $scope.messages = ["hello" , "hello"];
}]);
Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: node in tree, Duplicate key: string:hello

ng-repeatでは各要素に識別子を割り当てているが、デフォルトの識別子は$id(msg)の返戻値となるようです。
要素が文字列型の場合、この返戻値が同一値になるため、識別子が重複しエラーとなるようです。

この問題を解決するには、ng-repeatディレクティブに渡す値にtrack by $indexを追加し、
各要素の識別子をインデックスにします。

<div ng-app="myApp">
  <div ng-controller="MyAppCtrl">
    <div ng-repeat="msg in messages track by $index">
      <p>{{msg}}</p>
    </div>
  </div>
</div>

参考

qiita.com

【HTML】<input type="file" />における脆弱性対策

<input type="file" />

<input type="file" />は手軽にファイルアップロード機能を提供できるタグですが、安易に設定してしまうことで脆弱なアプリケーション・システムを作成できてしまいます。安易にファイルアップロード機能を実装するのではなく、予め潜在するリスクについて知り、その対策を出来る限り取っておく必要があります。
以下に、<input type="file" />を実装する上で気をつけるベきポイントを調査し列挙しました。

実装例はあくまで参考程度のものです。動作は保証しません。
また実装例はクライアントのみですが、以下に示すValidationチェックは原則クライアント・サーバ双方での実装を心がけてください。

アップロードファイルタイプを限定する

.exeなどの実行ファイルによって、悪意あるスクリプトが実行されてしまう可能性を防げます。

<!-- 拡張子を指定 -->
<input type="file" accept="image/jpeg,image/png" onchange="onChange(this)"/>

また念のため、スクリプト内でもチェックしましょう。

function onChange(this) {
    let file = this.target.files[0];
    if (file.type !== 'image/jpeg' && file.type !== 'image/png' ) {
         // assertion error
    }
}

アップロードファイルサイズを限定する

システム・サービスによっては別の仕様として決まっている場合もあると思いますが、
DOS攻撃を防ぐためにも、一定サイズ以上のファイルは弾くようにしてしまいましょう。
また複数ファイルアップロードをサポートする場合、一度にアップロードするファイル数も同様の理由で限定したほうが良いと思われます。
クライアント・サーバサイドでチェックします。

const MAX_FILE_SIZE = 1024 * 1024 * 10; // 10MB

function onChange(this) {
    let file = this.target.files[0];
    if (file.size >  MAX_FILE_SIZE) {
         // assertion error
    }
}

ディレクトリ構造を受け付けない

foo/bar/piyo.jpegのようなディレクトリ構造を受け付けず、ファイルのbasenameのみ受け付けるようにしましょう。
指定したファイルパスに対するレスポンス結果からサーバ側のディレクトリ構造を推測する、ディレクトリトラバーサル攻撃の標的になります。
許されるのであれば、ファイル名をハッシュ値やタイムスタンプに置き換えるのも良いと思います。

function onChange(this) {
    let file = this.target.files[0];

    let filePath = file.name.split('/');
    let tmpFilePath = file.tmp_name.split('/');

    let baseName = filePath[filepath.length - 1];
    let tmpBaseName = tmpFilePath[filepath.length - 1];
}

エスケープ処理

例えばSQLのクエリ引数に設定するなど、アップロードしたファイルの情報を別の処理系のパラメータに渡すときはそれらをエスケープ処理しましょう。
SQLの場合、バインド機構などがあればそれも利用しましょう。


本当は、多分やらなきゃいけないことはもっとあるはず。。。

参考

php.earth

worthliv.com

www.acunetix.com