ラベル JavaScript の投稿を表示しています。 すべての投稿を表示
ラベル JavaScript の投稿を表示しています。 すべての投稿を表示

2020年6月16日

データベース不要! ReactとGoogle Drive APIで任意のデータを保存する

前回はReactアプリにGoogle認証を組み込む方法を試しました。

今回はそのコードを使いながら、さらにGoogle Drive APIを使って任意のデータをログインしているユーザーのGoogle Driveに保存することに挑戦してみました。


完全にブラウザのみで動作するReactアプリケーションなので、バックエンドサーバは不要です。Netlifyなど静的サイトのホスティングサービスを使ってデプロイすることが可能です。


Screen shot on a PC

Screen shot on a phone


  • Google Drive内の所定のフォルダからファイル一覧を表示。
  • ファイル名がクリックされるとその内容(テキストデータ)を表示。
  • テキストを入力・編集して保存ボタンを押し、ファイル名を入力するとGoogle Driveにテキストデータを保存。

ということを行っています。



2つのContextプロバイダー


認証状態とアプリケーション固有の状態を分けて2つのContextプロバイダーで別々に共有するようにします。

import React from 'react';
import { FileList } from './FileList';
import { FileContent } from './FileContent';
import { AuthProvider } from './auth-state';
import { AppStateProvider } from './app-state';
import { Header } from './Header';
import './App.css';

function App() {
return (
<AuthProvider>
<AppStateProvider>
<div className="App">
<Header />
<FileList />
<FileContent />
</div>
</AppStateProvider>
</AuthProvider>
);
}
export default App;



Google Drive APIの有効化とAPI Keyの作成


下のドキュメントにしたがってGoogle Drive APIを有効化し、API Keyを作成しておいてください。

https://developers.google.com/drive/api/v3/quickstart/js



Google DriveへのCRUD処理


次にGoogle Driveにアクセスする処理を「google-api.js」に記述します。

google-api.js
---
---

前回作成した認証関係の関数に加えて、

 getFiles ファイル一覧取得  
 getFileContent     ファイル内容取得
 uploadFile ファイル保存
 deleteFile ファイル削除

などの関数をエクスポートしています。



アップロード処理がなかなか動かず苦労しましたが、最終的には下のサイトの情報にしたがってMultipart形式でPOST(更新のときはPATCH)リクエストを送ることで解決しました。


またファイルの内容を取得する方法も最初は分からず試行錯誤しました。こちらは下のURLにGETリクエストを送ることでダウンロード出来ました。

https://www.googleapis.com/drive/v3/files/${fileId}?alt=media&source=downloadUrl

もちろんリクエストヘッダーにはGoogle認証で得られたアクセストークンを付加する必要があります。


ファイルの保存先フォルダは現状では APP_FOLDER という定数で定義していますが、これはユーザーが任意のフォルダ名を指定出来るようにした方が便利ですね。


以上、今回のサンプルを作成してみて、バックエンドサーバ無しでもブラウザ上のJavaScriptだけでかなり柔軟にGoogle Driveへのデータ保存が出来るということが分かりました。


単純なテキストではなく例えばJSONを保存すれば、ある意味簡易的なデータベースとしても使えるかも知れませんね。何よりアプリの開発者側で保存先のデータベースやストレージを用意する必要が無いのが魅力的です。



今回作成したアプリケーションのソースコードはこちらにあります。






 

2020年6月14日

ReactのSPAアプリケーションでContextとHooksを使ってGoogle認証を実装する

ReactのSPAアプリケーションにおいて、ContextとHooksを使うことでGoogle認証の処理をラップして利用するというサンプルを作成しました。


Google Cloud Consoleでアプリケーションを登録する




OAuth consent screenを設定する  







OAuth client IDを作成する








Create React AppでReactアプリケーションを作成する


npx create-react-app project-name
cd project-name


public/index.html を編集する


WebアプリケーションでGoogle認証を利用するための最もシンプルなサンプルコードがこちらにあります。

Google Sign-In for Websites  |  Google Developers 

<html lang="en">
  <head>
    <meta name="google-signin-scope" content="profile email">
    <meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">
    <script src="https://apis.google.com/js/platform.js" async defer></script>
  </head>
  <body>
    <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
    <script>
      function onSignIn(googleUser) {
        // Useful data for your client-side scripts:
        var profile = googleUser.getBasicProfile();
        console.log("ID: " + profile.getId()); // Don't send this directly to your server!
        console.log('Full Name: ' + profile.getName());
        console.log('Given Name: ' + profile.getGivenName());
        console.log('Family Name: ' + profile.getFamilyName());
        console.log("Image URL: " + profile.getImageUrl());
        console.log("Email: " + profile.getEmail());
        // The ID token you need to pass to your backend:
        var id_token = googleUser.getAuthResponse().id_token;
        console.log("ID Token: " + id_token);
      }
    </script>
  </body>
</html>

これを参考に public/index.htmlを編集します。

上のサンプルコードの中でReactアプリで必要になるのはGoogleのスクリプトを読み込んでいる行だけなので、その部分をコピーしてheadタグ内に挿入します。

    <script src="https://apis.google.com/js/platform.js"></script>

Googleのサンプルコードでは「async defer」という属性が付いていますが、Reactアプリケーションの場合これがあると上手く動かないので削除しています。



.env.localファイルを作成する


次にプロジェクトのルートフォルダに「.env.local」 ファイルを作成して値を設定しておきましょう。

REACT_APP_CLIENTID=41702000-xxxxxxxxxxxxx.apps.googleusercontent.com

この環境変数の名前は、Create React Appの規約上「REACT_APP_」から始まる必要があるので注意してください。

ClientIdを環境変数に持たせておくことによって、本番用、ステージング用など、ビルド環境に応じて柔軟に変更することが出来るようになります。


src/google-auth.jsファイルを作成する


Google APIを実際に呼び出す際に使う関数をこのファイルにまとめておきます。

init(), signIn(), signOut()の3つの関数をエクスポートしています。
------


src/auth-state.jsファイルを作成する


上のgoogle-auth.jsに定義した関数をReactアプリ内で使うためのContextプロバイダーとフックを提供するファイルになります。

内部的にはuseReducerを使って認証状態を管理していますが、それはファイル内に隠蔽して外部へはAuthProviderコンポーネントとuseAuthStateフックをエクスポートしています。

useAuthStateフックからの戻り値は、認証状態を表す state オブジェクトとサインイン、サインアウト処理を呼び出すための関数を提供する actions オブジェクトの2要素配列となっています。

---
---


ここまで準備が出来れば、あとは

1. AuthProviderコンポーネントでアプリをラップする。
2. 任意のコンポーネント内でuseAuthStateフックを使って認証機能にアクセスする。

という形になります。


App.js
function App() {
  return (
    <AuthProvider>
      <div className="App">
        <header className="App-header">
          <h1>Google Auth React Examle</h1>
          <SignInOutButton />
        </header>
        <div className="App-main">
          <UserInfo />
        </div>
      </div>
    </AuthProvider>
  );
}


SignInOutButton.js
export function SignInOutButton() {
  const [state, actions] = useAuthState();
  const { isSignedIn } = state;
  const { signIn, signOut } = actions;
  if (isSignedIn === undefined) {
    return null;
  }
  return (
    <div>
      {isSignedIn ? (
        <button onClick={signOut}>Sign Out</button>
      ) : (
        <button onClick={signIn}>Sign In</button>
      )}
    </div>
  );
}




今回作成したアプリケーションのソースコードはこちらにあります。





以上、ReactのSPAアプリケーションにおいて、ContextとHooksを使ってGoogle認証の処理をラップするサンプルでした。



次回は、認証した後さらにGoogle Drive APIを使ってアプリケーションのデータを(バックエンドサーバー無しで)ブラウザから直接Google Driveに保存するということに挑戦したいと思います。





 

2020年5月7日

Next.jsで静的サイトを出力してFirebase Hostingでホスティングする

まずは公式のドキュメントにしたがってNext.jsのアプリケーションを作成しましょう。

Create a Next.js App | Learn Next.js 


package.jsonファイルのscriptsセクションに下記を追加しておきます。

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "export": "next export"
  },


npm run build && npm run export を実行して outフォルダに結果が出力されるのを確認します。



次にFirebase CLIを初期化します。


firebase login

firebase init



カーソルキーで「Hosting」を選択してスペースキーを押し、Enterで確定です。

Firebaseの管理画面でまだ「プロジェクト」を作成していない場合は、プロジェクトIDと名称を入力して新規作成します。既に使われているIDを入れてしまうとエラーになるので、ユニークなIDを入力しましょう。

アップロード対象のフォルダを聞かれるので、デフォルトの「public」ではなく「out」を指定します。


firebase deploy を実行して、「Deploy complete!」と表示されれば完了です!


慣れれば3分もかからずに出来てしまいますね。


バックエンド処理にCloud Functionsを使ったりするような場合も、下のドキュメントの通り設定すれば決して難しくはありません。

本当に便利な世の中になったものです。^^)


あ、特にFirebase Hostingでなくても良いのであれば、もちろんNext.jsの開発元であるVercel(旧Zeit)社のサービスを使うのが一番簡単でオススメです。



こちらであれば、静的サイトでなくSSRありのサイトであっても、またバックエンドのAPIが必要な場合でも、何も考えずにデプロイしてしまえばあとはサービス側で上手くやってくれるみたいなので、こちらも素晴らしいと思います。







 

2020年5月4日

ReactでSVGファイルをコンポーネントとしてレンダリングする

egghead.ioのレッスンを観ていて良さそうなものがあったので、メモしておきます。


Add SVGs as React Components with Create React App 2.0
https://egghead.io/lessons/react-add-svgs-as-react-components-with-create-react-app-2-0

Create React App のドキュメントにも記述があります。
https://create-react-app.dev/docs/adding-images-fonts-and-files


Create React Appで作ったReactアプリケーションでは画像ファイルを下のようにインポートしてコンポーネント内で参照することが出来ます。

import logo from './logo.png';
function Header() {
  return <img alt="Logo" src="{logo}" />;
}

もちろん画像の形式がSVGであっても同様です。

import logo from './logo.svg';
function Header() {
  return <img alt="Logo" src="{logo}" />;
}

ただ、SVG形式の場合はインポートの仕方を下のように変えるとReactコンポーネントとしても使えるようになるそうです。

import { ReactComponent as Logo } from './logo.svg';
function App() {
  return (
    <div className="App">
<header className="App-header">
<Logo className="App-logo" />
</header>
</div>);
}


このようにすると、HTMLとしてレンダリングされる際のタグが imgタグではなく、svgタグになります。

svgタグになってうれしい点は、SVG内の個々の要素(図形)をCSSでコントロール出来るようになることです。


例えば、線の色や太さを変えたり、アニメーションを付け加えたりすることが可能になります。


下の例では、Reactロゴのpathエレメントの部分のみ点線にして、さらに点線のオフセットをアニメーションで動かしています。

.App-logo path {
  stroke: palegoldenrod;
  stroke-width: 10px;
  fill: none;
  stroke-dasharray: 35px 15px;
  animation: orbit 1s infinite linear;
} @keyframes orbit {
  to {
    stroke-dashoffset: 50px;
  }
}

実行するとこんな感じになります。









2020年4月20日

AWS AmplifyとDynamoDBでサーバーレスなREST APIを構築する

前回はユーザー認証機能を付けましたが、まだデータの永続化が出来ていないのでTodoアプリとしては未完成です。



  1) Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする

  2) AWS AmplifyでReactアプリにユーザー認証機能を追加する

  3) AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する




今回はサーバーレスなREST APIを追加してデータをデータベースに保存出来るようにします。

amplify status で現状を確認すると、下のようになっています。



下のコマンドでAPIカテゴリを追加します。

amplify add api 

まず GraphQL か REST かを選ぶように言われます。

Web上で見つかる情報にはGraphQLを使ったものが圧倒的に多いように思いますが、ここではあえてRESTを選びます。



DynamoDB上に作成するテーブル名まで入力すると、次に作成するカラム情報の入力になります。

ドキュメントをざっと読んだところ、DynamoDBでは基本的に1アプリケーションで使うテーブルは出来るだけ少ない方が望ましいらしく、「1テーブルで済ませられればそれが最も良い」との事です。



長年リレーショナルデータベースに馴染んだ身としてはなかなか目から鱗な考え方です。

どうやらDynamoDBでのスキーマ設計の勘所は、「パーティションキー」と「ソートキー」および「グローバル・セカンダリインデックス」「ローカル・セカンダリインデックス」を上手く使うことにあるようです。

DynamoDBのテーブル設計をするとき、自分に問いかけていること – 或る阿呆の記 ( https://hack-le.com/dynamodb-query/ )

この辺り、非常に奥が深そうで面白いのですがとりあえず今はシンプルなTodoリストアプリを作りたいだけなので、極力簡単な方法で行きたいと思います。



パーティションキーを「pk」、ソートキーを「sk」とし、3つ目のカラムはmap型で「data」としておきました。map型にはJSONを格納できるので、こうしておけば後からの仕様変更にもある程度柔軟に対応することが可能になるかと思います。

パーティションキーとソートキーを何にするかというのは、アプリケーションの要件によって大きく変わる部分で、開発効率にも大きく関わってきます。


今回は、pkカラムには認証されたユーザーのidに "user:" というプリフィックスを付加して格納し、skカラムにはTodoアイテムのid(ランダムに生成されたもの)の先頭に "todo:"というプリフィックスを付加して格納することにしました。

こうすることでユーザーのidが分かればそのユーザーに属するタスクの一覧を簡単に取得することが出来るようになります。

実際のクエリーとしては、
- pkが "user:" + ユーザーid に一致する
- skが "todo:" で始まる
という条件で検索することになります。

DynamoDBではパーティションキーは完全一致でしか検索出来ませんが、ソートキーは部分一致や範囲検索が可能なので、このようなクエリーが可能になります。

また、ユーザーに属するデータでTodo項目以外のデータを保存したい場合には、skに付けるプリフィックスを "todo:" 以外のものにすれば対応出来ます。

例えば、ユーザーごとのアプリ設定を保存したい場合は、pkは同じでskを "pref:" として保存すれば、簡単にそのレコードを一意に指定して読み出す事が出来ます。

1アプリケーションで一つのテーブルしか使わない、というのはこのように2つのキー(およびセカンダリインデックス)を上手く使って複数の種類のデータを保存する、ということになるのかなと思います。


カラム設定の入力が終わると、テーブルへのアクセス権の設定をするか聞かれるので、認証していないユーザーはAPIへのアクセスが出来ない様に設定しておきます。




次に amplify push を実行して、追加した設定をAWS側に反映します。


完了するとAWS側では下記のリソースが作成されています。

- API Gateway
- Lambda
- DynamoDB


これらのうち、Lambda関数は自動生成されたコードだと上手く動かず、何をしているのかを理解した上でアプリケーションの要件に合わせてそこそこ手を加える必要がありました。


/amplify/backend/function/(Function名)/src/app.js (一部のみ抜粋)
-----

-----


アプリケーション側で扱うModelとしては

    {      
        id: 'todo:12312-312132',      
        text: 'ミルクを買う',      
        done: false 
    }

のような形になっているのですが、DynamoDBに保存されるのは、

    {
        pk: 'user:aaaaaaaaaaa',
        sk: 'todo:12312-312132',
        data: {
            text: 'ミルクを買う',
            done: false
        }
    }

という形になっています。

このため app.js の内部でAPI Gatewayから受け取ったオブジェクトをDynamoDBのスキーマに合った形式に適宜変換してから保存しています。



さて、次はReactアプリの方を変更して行きます。


この辺りのドキュメントを見ながらAPIへのアクセス処理を実装します。

Amplify Docs –– Fetching Data 



基本的には、

import { API } from 'aws-amplify';

でAPIクラスをインポートした後、

await API.post(apiName, path, options);  // CREATE
await API.get(apiName, path, options);  // READ
await API.put(apiName, path, options);  // UPDATE
await API.del(apiName, path, options);  // DELETE

でLamdaで作成してあるAPIにアクセスすることが出来るので、各アクションからこれらを適宜呼び出しています。


React側の実装としては、Hooks(useEffect, useReducer)を使って作っています。


/services/todo-service.js
-----

-----



ここまででなんとかシンプルなTodoリストアプリが完成しました。



  • Amplify Consoleによるスタティックなサイトのホスティングは簡単・超便利!

  • Amplify CLIでのユーザー認証機能の追加はパラメータの設定が最初ちょっと大変だけど、何回か試行錯誤して分かってしまえばこちらも簡単で素晴らしい!

  • Amplify CLIでのREST APIの作成は、DynamoDBの特性を理解して設定する事が超重要!(*



* もちろんDynamoDB以外の任意のデータベースを使うことも可能。


現在のソースコードは下記から確認可能です!

https://github.com/mikehibm/amplify-test01




  1) Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする

  2) AWS AmplifyでReactアプリにユーザー認証機能を追加する

  3) AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する






 

2020年4月17日

AWS AmplifyでReactアプリにユーザー認証機能を追加する

前回はNext.jsでエクスポートしたWebアプリをAmplify Consoleでホスティングするところまで行いました。

今回はこのアプリにAWS Cognitoによるユーザー認証を追加したいと思います。



まず amplify status で現在の状態を確認しておきます。



次に、 amplify auth add を実行。




最初に「Do you want to use the default authentication and security configuration?」と聞かれます。

ここでデフォルト設定を使って試したところ、サインアップの際にメールアドレスの他に電話番号の入力も必須になってしまったので、それを回避するために今度は「Manual configuration」を選択して次に進みました。


次に、amplify push を実行して追加した設定をAWS側に反映します。


コマンドが完了すると、AWS側では必要なリソース(Cognito User Pool、Lambdaファンクションなど)が作成されているのが分かります。


次にReactアプリケーションの方でCognitoに接続するために、必要なライブラリを追加します。

npm i aws-amplify @aws-amplify/ui-react


ログイン画面の表示には「Amplify UI Components」を使います。

/pagesフォルダ内にlogin.jsを作成し下のコードを入力しました。

----------

----------

これだけで、http://localhost:3000/login を開くと下のようなログイン画面が表示されるようになります。



もちろん最初はユーザー登録が必要なので、「Create account」のリンクをクリックします。(この辺りの文言はカスタマイズ可能です。)


ここで、電話番号の入力欄が必須となってしまっています。
これを非表示にするためのカスタマイズ方法がドキュメントには載っているのですが、その通りにやってみても上手く行きませんでした。

検索したところ、UI ComponentのバグとしてGitHubのIssueに上がっているようです。ちょうどこの記事を書いている数時間前にFixがマージされているみたいなので、近日中には解決しそうです。今はとりあえずダミーの電話番号を入力して次へ進みます。


登録フォームをSubmitすると、確認コードの入力画面に移ります。




入力したメールアドレスに確認コードが送られているので、そのコードを入力して「Confirm」をクリックすれば登録完了です。



この時点でAWSコンソールでCognitoのUser Poolを見てみると、確かに1件のユーザー情報が登録されているのが分かります。


バックエンド側を何も作り込んでいないのにここまで自動的に出来上がってしまうのはすごいですね!


最後に上で作成したlogin.jsと同様に認証用のUIをindex.jsページにも追加します。

-----

-----

ここではサインアウトボタンは要らないので削除し、代わりに右上のヘッダー内に「Sign Out」リンクを追加ししました。

サインアウトの処理を実装するには、

import { Auth } from 'aws-amplify';

でAuthクラスをインポートし、リンクがクリックされたら

Auth.signOut();

を実行すればOKです。



次回はバックエンド側のREST APIをAmplifyのサーバーレスな機能を使って追加します!

AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する 




ここまでのソースコードはこちらで確認出来ます!






 

2020年4月16日

Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする

今回は、Next.jsから(SSR無しの)スタティックなSPAとしてエクスポートしたサイトを、Amplify Consoleでホスティングしてみます。


サンプルReactアプリの作成


npm init -y
npm i -S react react-dom next
 


package.jsonはこんな感じになっています。




npm run dev を実行すると開発サーバーが立ち上がるのでブラウザで http://localhost:3000 を開きます。


npm run export を実行すると out フォルダにindex.htmlや404.html などのファイルが出力されます。今回はこれらの静的なHTMLファイルを Amplify Consoleでホスティングすることまで出来れば目的達成です。


amplify-cliのインストール


npm i -g amplify-cli

Amplifyの初期化


amplify configure








amplify init



Amplify Consoleでのホスティング設定を追加


amplify hosting add


上のコマンドを実行して「Continuous deployment...」を選ぶと、ブラウザが開いて接続するGitリポジトリを聞かれます。




amplify.yml という設定ファイルを手動で変更する必要があるので、ダウンロードしてプロジェクトのルートフォルダに保存します。


ここではNext.jsでスタティック・エクスポートした成果物をデプロイしたいので、下のように一部を変更します。(commandsとbaseDirectory)




変更したamplify.ymlファイルをGitリポジトリにコミット・プッシュします。

プッシュした時点で自動的にAmplify Console側でビルド処理が走ります。




ProvisionからVerifyまで全てグリーンになってから画面に表示されているURLをクリックすると、アプリケーションがデプロイされているのが確認出来ます。


今は「Hello!」と表示するだけで何もしないアプリケーションですが、後は通常のReactアプリケーションとして「npm run dev」で実行しながらローカルで開発します。




ひとまずブラウザ上だけで動くTodoアプリを作りました。





このままではブラウザを閉じるとデータが消えてしまうので意味が無いですが、次回以降のエントリではここに「ユーザー認証」と「REST API経由でのDynamoDBへのデータ永続化機能」を追加して行こうと思います。


AWS AmplifyでReactアプリにユーザー認証機能を追加する

AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する




現在のソースコードはこちらで参照出来ます!

https://github.com/mikehibm/amplify-test01/tree/blog-2020-04-15






 

2020年4月14日

AWS AmplifyでReactアプリ開発を試してみた

時間があったので前から気になっていた AWS Amplify を試してみることにしました。

とりあえず作ったのは、Todoリスト。React (Next.js) によるWebアプリです。



username / password でログインすると、DynamoDBに保存された自分のTodoリストが表示されます。



使った技術は、

フロントエンド
 - Next.js
 - Amplify Console (Hosting)

バックエンド
 - Lambda
 - API Gateway
 - DynamoDB
 - Cognito

という構成になりました。


至ってシンプルなTodoリストですが、Cognitoによるユーザー認証やDynamoDBなど慣れないモノについて使い方を調べながら作ったので結構時間がかかりました。

Amplify ConsoleによるフロントエンドのHosting機能は非常に便利で使いやすいと思いました。

ただ、今のところ全体的な感想としては 「Firebase の方が使いやすいかなあ」という感じです。特にドキュメントが(内容的には非常に多岐に渡って網羅されているのですが)今ひとつ分かりにくい気がします。


これからせっかくなのでもう少し機能追加をしながら使い込んで見たいと思います。


  1) Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする

  2) AWS AmplifyでReactアプリにユーザー認証機能を追加する

  3) AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する






 

2019年5月7日

初めてElmで何か作ってみた。- エニグマ(風)暗号機

最近子どもが学校の図書館かどこかで暗号に関する本を読んだらしく、

 「お友達のAちゃんと秘密のメッセージを交換したいの。エニグマみたいなので。」

とせがまれました。

いきなりエニグマ暗号機の名前が出てきたのでびっくりしましたが、

 「よし、エニグマ(風)暗号機を作ってあげよう!」

ということになりました。




この際なので、以前から試してみたかった、Elmを使うことにしました。

出来たモノはこちらに公開してあるのでもし良ければ試してみて下さい。

https://mysecretmsg.mikehi.now.sh/




伝えたいメッセージを打ち込んでEnterキーを押すと、「暗号化キー」の入力画面になります。



ランダムな4桁の数字を入力してもう一度Enterキーを押すと、暗号化されたメッセージが表示されます。



これを相手に伝えます。

暗号化されたメッセージを受け取った相手は、同じアプリを開いて暗号文を打ち込み、さっきと同じ4桁の「暗号化キー」を入力すると復号化されたメッセージを見ることが出来ます。


暗号化されたメッセージを同じキーで再度暗号化すると元の平文に戻る(暗号化と復号化のプロセスが同じ)」、というのが「エニグマっぽい挙動」ということになるかと思います。


これをどうやって実現しようかと考えて少し悩みました。

単純に文字コードのXORを取れば出来そうなのですが、それだと結果が文字として表示出来ない値になってしまう事があるので、別の方法を考えました。

その結果、

- 入力可能な文字を2つのグループに分けて、2文字単位で「置換ペア」を予め決めておく。
- このような置換ペアのテーブルを10種類作っておく。
- 「暗号化キー」の各桁(0〜9の数字)を置換ペアのテーブルに対応させて、順に置換する。

という簡単な方法で実装してみました。


このアプリを使って秘密のメッセージを送る際には、メッセージとキーは別々の方法で伝える必要があります。(キーだけは事前に合意しておく、メッセージはメール、キーは口頭で伝える、など)

メッセージ本文と暗号化キーの両方を知っている人は誰でも復号化して元のメッセージを見ることが出来るので、キーを秘密にしておくことはとても重要です。(当たり前ですが。)


今回のソースコードはこちらにあります。

https://github.com/mikehibm/mysecretmsg




さて、完成したアプリを子どもに使わせて見たところ、予想外の感想が返って来ました。


「やっぱり...本物のEnigmaが欲しい!」



追記:

Elmを初めて使った感想を何も書いていなかったので思い出しながら書きます。

- 最初慣れるまでは結構大変。アプリケーションのひな形も Sandbox, Element, Document, Application とあってどれを使ったら良いのか迷った。

- 特にキー入力をどうやって処理するのか、ドキュメントを見てもよく分からず。Githubのサンプルとにらめっこして何とか動かせた。Decoderとか今でも完全には理解してません。笑

- 関数の集まりとしてアプリケーションが宣言的に定義出来てしまうというのは、やはり目からウロコ的な体験で面白かった。

- 上位レベルの関数から順に追っていけばプログラム全体が見通せるので、誰が書いてもそこそこ読みやすくなりそうな気がする。

- parcel + Elmの組み合わせでホットリローディングが出来るので、これはオススメ。








.

2019年3月22日

React/TypeScriptでリバーシゲームを作る (5) - アニメーション

前回でようやくそれなりに強い思考ルーチンを実装することが出来たので、今回は仕上げとして石を裏返すときのアニメーションと、画面遷移時のアニメーションを実装します。


こちらを開くと実際に遊べます。
https://reversi-d1kqojbar.now.sh/


現時点のソースコードはこちらにあります。



1. 石をひっくり返す時のアニメーション


まず、石を置いた時には StoreクラスのsetStone()メソッドが呼ばれるようになっているので、その中で20msの間隔を空けて20回の「EV_BOARD_FLIPPING」イベントを発生するようにしました。

このイベントをBoardコンポーネントで受け取って、各セルを描画する際に、「もしひっくり返しアニメーションの実行中で、かつひっくり返し対象のセルであれば、Cellコンポーネントにflippingプロパティを通してその旨を伝える」ということを行っています。

Cellコンポーネントでは、flippingプロパティがnullでない場合は、

 flipping.count / flipping.total 

で現在のアニメーションの進行率が分かるので、それに応じて円を描画するときの横幅を変化させています。

さらに、アニメーションの進行率が 50% を越えた時点で石の色を反転させるようになっています。

これで石をひっくり返すアニメーションは上手く行きました。


石をひっくり返すアニメーション

---



ちなみに、CSSトランジションを使えば自分で石の横幅を変化させなくても、開始状態と終了状態を指定するだけでブラウザ側でアニメーションさせることが出来ます。その方法を使った方がパフォーマンス的には良かったかも知れません。




2. 画面遷移時のアニメーション


今回のアプリケーションでは、

 タイトル画面 → 設定画面 → ゲーム画面 

という流れで画面が切り替わるようになっています。この切り替えのタイミングで、次に進む場合は右から左へ、前の画面に戻る場合は左から右へとアニメーションする処理を入れました。

タイトル画面 → 設定画面 → ゲーム画面の遷移アニメーション

---

この部分は、CSSトランジションを使っています。

Reactでは 「React Transition Group」というライブラリを使うのが半ば公式に推奨されているみたいです。

https://github.com/reactjs/react-transition-group

これを使うのも悪くないのですが、今回は勉強のために全て自前で実装してみました。その結果、かなり試行錯誤して時間がかかりましたが、分かってみればそんなに難しくはないので、これぐらいの画面遷移であれば自前でやってしまうのもありだと思います。

基本的には、CSSで表示状態のスタイルと非表示状態のスタイルを定義しておいて、各画面のコンポーネントでCSSクラスを切り替えるだけです。

非表示状態の画面は透明(opacity:0)でかつ表示位置が画面の範囲外になるようにしていますが、それだけだとドキュメント全体のサイズが、表示されていない部分まで含んで認識されてしまって、余計な横スクロールが出来てしまう状態になったので、そこは一工夫が必要でした。

これに対処するために、一段上のAppコンポーネントのスタイルで、

.App {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

とすることで、画面外の部分はドキュメント全体のサイズに影響をおよぼさないようになり、余計な横スクロールを抑止することが出来ました。



3. スマホ対応


AndroidやiOSの実機で動作確認していると、色々と気になる点が出てきました。

スマホ向けにPWAなどでゲーム的なアプリケーションを作る場合には常に出てくる問題だと思いますが、以下の3つの問題があります。

1. ダブルタップ問題
2. ピンチズーム問題
3. Pull to Refresh問題


これらになんとか対処するために色々と調べて、以下の変更を行いました。どれがどの問題への対策だったかよく覚えていないのですが、とりあえず下記の対策をしておけば良いのではないかと思います。


htmlタグに属性を追加

style="overflow-y: hidden;"


viewportメタタグの設定

meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,shrink-to-fit=no"

モバイルアプリ用メタタグの追加

meta name="apple-mobile-web-app-capable" content="yes"
meta name="mobile-web-app-capable" content="yes"



これで少なくとも手元のAndroid端末(Android 9.0)ではほぼ完璧になりました。

Chromeブラウザでページを開いてから、「ホーム画面に追加」を選んでホーム画面から起動すると、ブラウザのアドレスバーも無くなり、ダブルタップしようが上下にスワイプしようがびくともしません。ほぼネイティブアプリの感覚ですね。

手元のiOS端末(iPhone 5s / iOS 12.1.3)では、Pull to Refresh問題は解消しましたが、ダブルタップやピンチズームは反応しなかったり何かのタイミングで急に出来てしまったり、よくわからない感じです。(笑)


4. 終わりに


さて、ここまでで今回の「React/TypeScriptでリバーシゲームを作る」シリーズは無事終了です! 10月から3月まで、ほぼ半年もかかってしまいました。その間にGoogle発のクロスプラットフォーム開発環境であるFlutterが正式リリースされて、最近はかなり人気も高まってきているようです。


今度はFlutterでまたリバーシゲームを作ってみようかなあと思っているところです。




React/TypeScriptでリバーシゲームを作る

(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html

(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html

(3) - 思考ルーチンその1
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html

(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html

(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html






 

2019年3月7日

React/TypeScriptでリバーシゲームを作る (4) - 思考ルーチンその2

前回でコンピュータの思考ルーチンの枠組みは出来ましたが、まだ単純なルールで動いているだけなので全く強くありませんでした。

今回は、そこそこ強い思考ルーチンを作ることに挑戦してみました。

出来たソースコードはこちらにあります。
https://github.com/mikehibm/reversi-react/tree/blog-4


動いているものはこんな感じになりました。
---

---

下のURLで実際に動かせますのでぜひ試して見て下さい。

https://reversi-7kj2lcsw4.now.sh/


CPUのレベルは1から3まであります。レベル1は1手先、レベル2は2手先、レベル3は3手先まで打てる可能性のある場所を全て調べて、最も有利になりそうな場所に打つようになっています。

思考ルーチンの内容は長くなるので省略しますが、とても面白いテーマです。興味のある方には、次の本を強くおすすめしておきます。これからリバーシを自分で作ってみようと思う人には本当に役に立つ情報が詰まっています。
---

---

今回は、上の本では「用いないほうが良い」と書かれている「得点テーブル」による評価を使ってしまいましたが、それに加えて「確定石」の数による評価を組み合わせて実装したところ、意外とまあまあ強い思考ルーチンになったような気がしています。


さて、このアプリケーションではコンピュータの思考ルーチンを Web Worker を使って別スレッドで実行するようになっています。と言うと簡単に聞こえますが、実際にはかなり試行錯誤と苦労の連続でした。


なぜ苦労したかと言うと、それなりに複雑な処理をWebWorkerで実行しようとするとやはり TypeScript を使いたいし、複数のWorker間で共通に使える関数はモジュール化して import 出来るようにしたかったからです。


① WebWorkerもTypeScriptで書く、かつimport文を使えるようにする

②  Create React App で作成されたプロジェクトをejectせずに(Webpackの設定を変えずに)これを実現する


この①と②の目標を達成するためにいろいろと試した結果、前回の記事で使った「Workerの関数をtoString()で文字列化した上でBlobとして読み込んでからWorkerスレッドを生成する」という方法ではなく、シンプルな

  const worker = new Worker('my-worker.js');

という形式で単にpublicフォルダに置いたJSファイルを指定して読み込む方法を使うことにしました。

その上で、WebWorker関連のTypeScriptファイルだけをアプリケーション本体とは切り離して独自にトランスパイルする方法を考えました。


ただ、共通部分をモジュール化して import/export を使うというのは、結局WebWorkerとの組み合わせではいい感じで正しく動かすための設定方法を見つけることが出来ませんでした。(tscでトランスパイルするのではなくwebpack/babelとworker-loaderプラグインなどを使えばなんとかなるのかも知れません。詳細は末尾の参考URL参照)


その代わりに、

  importScripts('インポートされるJSファイル名') 

という記法はWebWorkerの中で問題なく使えたので、これを利用することにしました。


プロジェクトのルートに tsconfig.json がありますが、それとは別に「tsconfig.worker.json」ファイルを作成しました。

---

---


  "module": "none",

とすることでモジュールシステムを使わないようにしている点と、

  "outDir": "public"

でトランスパイル後のJSファイルを直接publicフォルダに保存している点に注目です。


これで、

  tsc -p tsconfig.worker.json

を実行すると
  public/reversi.worker.js
  public/players/cpu1.worker.js
  public/players/cpu2.worker.js
  public/players/cpu3.worker.js

という4つのJSファイルが出来るようになります。


cpu1〜3.worker.jsの先頭では、

  importScripts('../reversi.worker.js');

とすることで共通部分である reversi.worker.js を読み込んでいます。


あとは、アプリ内でWorkerを生成する必要があるときに

  new Worker('players/cpu1.worker.js');

のような感じで読み込めば良いということになります。




参考URL:
Workerを駆使するためのプロジェクト構成 with webpack - Qiita
https://qiita.com/_likr/items/d382dc120a942ba4c6fe
4パターンのWebWorker生成方法とインラインワーカーの技法 - Qiita
https://qiita.com/mohayonao/items/872166cf364e007cf83d
Two example projects which use WebWorker in TypeScript + Webpack environment.
https://github.com/Qwaz/webworker-with-typescript 





React/TypeScriptでリバーシゲームを作る


(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html

(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html

(3) - 思考ルーチンその1
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html

(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html

(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html






.

2019年2月16日

React StaticでGoogle Analyticsを使うには

React StaticでSPAアプリケーションを作ったときに、Google Analyticsでアクセス解析をするにはどうすればよいか、というメモです。

Reactだと react-ga というライブラリがよく使われているようです。これを使うとGoogle Analyticsへのトラッキング情報の送信が楽になるようですが、ブラウザ上でのページ遷移のタイミングは自分で検知して送信するようにコードを書かなければなりません。

React Routerを使う場合のサンプルがGitHubのdemoフォルダ内にありました。

https://github.com/react-ga/react-ga/blob/master/demo/app/withTracker.jsx

これを見ると、withTrackerというHigher Orderコンポーネントを作ってlocationの変更を検知してGAに送信するようになっています。


今回はReact Staticを使って作ったサイトだったので、ルーティングの仕組みが少し違ってこのサンプルコードをそのまま使うことが出来ず、少し手を加えたものを作って一応動くようにはなったのですが、なんとなくしっくり来ませんでした。


そこであらためてGoogle Analyticsのドキュメントを読み返してみると、下のような記述を見つけました。

https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications
--
(...) SPA の場合、サイトで新しいページを読み込むときに、ページ全体を読み込むのではなくコンテンツを動的に読み込むため、analytics.js スニペット コードが実行されるのは一度だけとなります。つまり、以降のページビュー(仮想ページビュー)は、新しいコンテンツが読み込まれるときに手動でトラッキングする必要があります。
--

青枠の注釈部分が特に重要です。

SPA用に、既にGoogleからurlChangeTrackerというプラグインが提供されているのです。これを使うことで、自分でページ遷移のタイミングを拾ってトラッキング情報を送信するという処理を書く必要が無くなります。

つまり、やるべきことは以下の通りになります。

1. 初回表示時にanalytics.jsを読み込んで初期化 & 初回トラッキング情報を送信。
2. urlChangeTrackerを読み込む。(以降のページ遷移は自動的に送信される。)

結局、「react-gaを使わずに最初から自分で書いた方がシンプルになるのでは?」と思ったので一から書き直しました

--

--

render()メソッド内で autotrack.custom.js というスクリプトファイルを読み込んでいますが、これは urlChangeTrackerのドキュメントにしたがって必要なプラグインだけをカスタムビルドして作成したものを public フォルダ内に配置したものです。

このGoogleAnalyticsコンポーネントをApp.js内で読み込んで下のように使うことで、React Staticで作ったサイトでGoogle Analyticsがちゃんと機能するようになりました。



最後に、GoogleAnalyticsコンポーネント内で参照している gaID というプロパティですが、これは React Static の withSiteData というHoCを使って自動的に挿入されます。実際の値の定義は下記のように static.config.js ファイル内で行っています。
--

--


2019年1月13日

React/TypeScriptでリバーシゲームを作る (3) - 思考ルーチンその1

前回はリバーシのルールを実装して、人対人の対戦が出来るようになりました。

今回は、一人でも遊べるようにコンピュータの思考ルーチンを実装します。


動くものはこちらです。
https://reversi-992nmioqt.now.sh/



タイトル画面でレベル1〜3が選べるようになっています。
(現段階ではどれを選んでも実際には難易度は同じです。)

ゲーム画面は前回と同じですが、コンピュータの手番になると自動的に思考ルーチンが呼び出され、
結果が出るとその座標に石を配置します。




現時点でのソースコードはこちらにあります。
https://github.com/mikehibm/reversi-react/tree/blog-3


さて、コンピュータとの対戦を実現するにあたって工夫する必要があるのは、「思考ルーチンを別スレッドで動かして、UIをブロックしないようにする」ことです。


通常ブラウザ上で動くJavaScriptアプリケーションでは一つのスレッドしか使えないので、時間がかかってCPUパワーを消費する処理を行うとその間UIが固まったりカクカクした感じになったりして操作性が悪くなります。

これを防ぐために、「Web Workers API」を使います。


ReactでWeb Workersを使う方法は、下の記事に書いたとおりです。

Create React AppでWeb Workerを使うには

この記事で調べた、「WorkerのJSファイルをBlobとして読み込んでからWorkerスレッドを生成する」という方法を使うことにしました。


ひとまず基本的な仕組みを実装することに主眼を置いたので、思考ルーチンの内容は単に「配置可能な座標のなかからランダムに選ぶ」だけの動作になっています。この処理だと実際には一瞬で終わってしまうのでWorkerスレッドを使う意味は全くありません。

Workerスレッドを使う意味があるような、もっとヘビーにCPUを使う思考ルーチンへの改良については、次回以降の記事で書くことにします。


playersフォルダ内のcomputerPlayer1.tsを見ると、thinkProc() という関数があり、そこに思考ルーチンの実態が入っているのが分かります。
---



この thinkProc関数を createWorker というユーティリティ関数に渡すことで thinkWorkerという名前のWorkerスレッドを作成しています。
const thinkWorker = createWorker(thinkProc);


対戦中にコンピュータの番が来たときには、

const result = await thinkWorker.execute({ board: { cells: board.cells } });

のように execute()メソッドを呼んで思考ルーチンを起動しています。このexecute()メソッドというのは、JavaScript標準のWorkerクラスを継承して作成した独自クラスで定義したメソッドで、 Workerクラスの postMessage()を呼んだ後のイベントハンドリングを抽象化したものになっています。


思考ルーチンの処理が終わると、戻り値のresult変数にはコンピュータが選択した座標が入っているので、後は人間がクリックした時と同じように store.setStone(result) を呼んでその座標を渡します。そうすると store内部で盤面の状態が適宜更新されてイベントが発行されるので、それを受けて画面の表示が自動的に更新されることになります。



ここまででコンピュータとの対戦機能を実現するための基本的な仕組みを実装することが出来ました。次回はそれなりに強い思考ルーチンを作ることにチャレンジしたいと思います!




React/TypeScriptでリバーシゲームを作る


(1) - ボードの描画と石の配置
https://blog.makotoishida.com/2018/10/reacttypescript.html

(2) - ゲームロジック
https://blog.makotoishida.com/2018/11/reacttypescript-2.html

(3) - 思考ルーチンその1
https://blog.makotoishida.com/2019/01/reacttypescript-3-1.html

(4) - 思考ルーチンその2
https://blog.makotoishida.com/2019/03/reacttypescript-3-2.html

(5) - アニメーション
https://blog.makotoishida.com/2019/03/reacttypescript-5.html