20200702のReactに関する記事は5件です。

[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 3 -認証編-

この記事はこの記事の続きです
[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 2 -リッチメニュー切替-

ここまでのまとめ

さて、ここまでで「認証用のLINEグループを作る」「グループへの招待・退出」でメニューを切り替える、というところまでできました。
あとはLIFFで作った管理画面に飛ばせばOKですが、当然そのさきのAPIでも認証をかけないといけません。

構成図

ちなみに構成は次のようになっています。

  • LIFF : ユーザ用メニューと管理者メニュー (React)
  • bot : linebotのロジック(Go)
  • line_api : LIFFからLINEの認証などのLINE固有の処理をするAPI (Go)
  • omoinas : アプリケーションのメインロジックやmodel・repository層など (Rust)

image.png

LIFFでの認証の考え方

今回はログインしているユーザのLINE IDを見て管理者かどうかを判別します。

さて、LIFFでLINE IDを取得するにはsocial api 2.1 を使えば良さそうです。
が、公式マニュアルにもあるように、
LIFFでLINEのUserIDを取得して、それを直接サーバに送り付けてはいけません。
(直接、ユーザIDヲ指定してAPIを叩けてしまうとセキュリティ的によろしくない)

上記のドキュメントにある通り、LIFFではアクセストークンを取得し、サーバー側でプロフアイルを取得してユーザIDを確認します。

LIFFとWebページを作る

LIFFを使うには、LINE Developers から新しく「LINE Login」チャネルを作成します。
なお、現在はLine Messaging APIでLIFFアプリは作れません。

今回はReactで作りました。

Reac/TypescriptでLIFFを作るためのあれこれ

意外と面倒です。。。
今回は create-react-appで作りましたが、はじめGatsbyあたりを使おうとして色々読み込み方がわからず、というのがありました。。

LIFFを読み込む

public/index.html
<html>
  <head>
    <script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script>
  </head>
  ...
</html>

Typescript用の定義ファイルを追加する。

npm install --save liff-type

環境変数にLIFFのIDを書く

.env
# 開発用
REACT_APP_DEV_LIFF_ID="xxxx"

# 本番環境用
REACT_APP_PRD_LIFF_ID="yyyy"

環境変数の切り替えについて

LIFFの開発では、UIの確認程度ならローカルでもできますが、
結局はサーバー上で確認しないとけないため、開発環境と本番環境の構築と切り替えをしないといけません。

process.env.NODE_ENVは、 npm startではdevelopmentになりますが、
npm run build すると強制的に prodcutionになる為、
今回は REACT_APP_ENVという変数を使い、npm run buildするときに環境変数を指定することにしました。

# 開発環境用の変数でビルドする
REACT_APP_ENV=dev npm run build

LIFF初期化

LIFF初期化の注意点としては、liff.login()をかける前はURLがエンコードされており、そのままではルーティングされません。
そのため、Routerの外側でまずLIFFの初期化を行い、LIFFがURLをデコードするときちんとルーティングされます。

src/App.tsx
import React from 'react';
import {
  BrowserRouter as Router,
  Route,
  Switch,
} from 'react-router-dom';


... 何やらかんやら初期化

function App() {
  liff.init({ liffId: (process.env.REACT_APP_ENV === "prd"
    ? process.env.REACT_APP_PRD_LIFF_ID
    : process.env.REACT_APP_DEV_LIFF_ID)as string }).then(() => {})
  return (
    <MuiThemeProvider theme={theme}>
      <CssBaseline />
      <Router>
        <Switch>
          <Route path="/" exact>
            This is liff.
          </Route>
          <Route path="/:env/user/omise/:clientId/:omiseId" exact>
            <ユーザ用のページ />
          </Route>

          <Route path="/:env/staff/omise/:clientId/:omiseId" exact>
            <管理用ペーシ />
          </Route>
        </Switch>
      </Router>
    </MuiThemeProvider >
  );
}

export default App;

管理者用のページでアクセストークンを取得する

公式ドキュメントに従い、LIFFの初期化とアクセストークンの取得をします。

src/StaffOmise.rs
import React, { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom';
import { useForm, Controller } from "react-hook-form";

// ↓サーバサイドのAPI
import {getOmise, Omise, setOmise, OmiseForm} from 'utils/api/omise';

... 色々コンポーネント読み込みとか

function StaffOmise() {
  console.log("aaaa")
  const {env, clientId, omiseId, charaId} = useParams<RouteParams>();

   ...

   // ロード時にデータ読み込む処理
  const load = () => {
    getOmise(env, clientId, omiseId, (omise: Omise) => {
       ... // フォームへの値の設定とか
    })
  }

    // 初期化処理
    useEffect(() => {
    liff.ready.then(() => {
      let accessToken = ""
      if (!liff.isLoggedIn()) {
         // ログインしていなければログインさせる。(ローカル環境ではスルーする)
        if (process.env.NODE_ENV === "production") {
          liff.login({})
        }
      } else {
        accessToken = liff.getAccessToken()
        setToken(accessToken)
      }
      load()
    })
  },[env, clientId, omiseId, charaId])

  return {
    ...フォームとか
  } 
}
export default StaffOmise;

API呼び出す時にLINEのトークンを付与する

上記でアクセストークンを取得したので、APIのヘッダーにつけます。

src/utils/api.tsx
// 更新API
export function setOmise(token: string, ...パラメータ) {
  fetch (
   env === 'prd'
      ? `${process.env.REACT_APP_PRD_LINE_API_HOST}/line-api/omise/set`
      : `${process.env.REACT_APP_DEV_LINE_API_HOST}/line-api/omise/set`,
    {
      method: "POST",
      mode: "cors",
      cache: "no-cache",
      headers: { 'Authorization': 'Bearer: '+token},
      body: JSON.stringify({
        ...更新するデータ
      }),
    }
  )... // thenとかcatchとか
}

サーバサイドで認証する

これでLIFFからLINEのアクセストークンが送信されますので、これを元にLINEのユーザIDを取得します。

line_api/set_omise.go
func setOmise(request events.APIGatewayProxyRequest) (string, error) {
    // ちょっと雑ですが、ヘッダーからアクセストークンを取得します。
    accessToken := strings.TrimPrefix(request.Headers["Authorization"], "Bearer: ")
    if accessToken == "" {
        // 401
        return "", errors.New("access token is empty.")
    }

    // ユーザ名取得
    prof, err := client.GetUserProfile(accessToken).Do()
    if err != nil {
        log.Println(err)
        return "", err
    }

    param = {...}
    param.UserID = prof.UserID

    ... // ラムダでRustの更新APIを呼び出し、そこでDynamoDBからユーザIDをチェックする。
    payload, _ := json.Marshal(param)
    res, err := lambda.New(session.New()).Invoke(&lambda.InvokeInput{
        FunctionName:   aws.String(ARN + "hoge-" + os.Getenv("ENV") + "-setOmise"),
        Payload:        payload,
        InvocationType: aws.String("RequestResponse"),
    })
    if err != nil {
        log.Println(err)
        return "", err
    }
}

(Rustで書いたラムダは当然、このAPIからしか呼び出せないように設定しておきます)

まとめ

以上で、LIFFで管理画面を作ってリッチメニューを切り替えたり認証したりする方法を紹介しました。
システムを開発すると、何かにつけて管理画面が必要になりますし、
作ったら作ったで、やれスマホ対応しろだのメアド・パスワード機能をつけろ、というのが世の常です。
ですが、管理画面なんてそもそもシンプルな方が良いですし、パスワード管理なんてしたくありません。
(Auth系サービスもありますが、結局、招待やらアカウント管理やら手間がかかります)

できるだけシンプルにしたい! という発想からの試みでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

create-react-app+TypeScriptでeslintを実行する

自分のブログから持ってきました.
https://wally-ngm.hatenablog.com/entry/2020/07/02/131417

create-react-appには既にeslintが入っているようですが, eslintrcでルールを管理したいですよね.

どうやって回すかをまとめます.

1. eslintのダウンロードし

グローバルなところへeslintをインストールしてもいいですが, 今回はプロジェクト配下にダウンロードします.

yarn add --dev eslint

2. eslintの初期設定

yarn eslint init

ここで, eslint init とするとグローバルなeslintを使用することになります. どちらでも構いませんが今回はプロジェクト配下のeslintを使うようにします.

対話形式に答えていくと, .eslintrc.js or .eslintrc.json or '.eslintrc.yml' のどれかが自分が選んだ形式に従って作成されます.

また, 最後にnpmでパッケージを諸々ダウンロードするか聞かれます. 「yes」で大丈夫ですが, その後必ず package-lock.json は削除しておきましょう.

3. eslintのルールを設定する

こちらを参照にして, package.jsonから一部を削除して, eslintrc.jsには追記をしていきます.
[https://kic-yuuki.hatenablog.com/entry/2019/09/08/111817:embed:cite]

最終的な私のpackage.jsonとeslintrc.jsは以下のようになりました.

package.json
{
  "name": "app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "@types/jest": "^24.0.0",
    "@types/node": "^12.0.0",
    "@types/react": "^16.9.0",
    "@types/react-dom": "^16.9.0",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.1",
    "typescript": "~3.7.2"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "lint": "$(npm bin)/eslint -c ./.eslintrc.js 'src/**/*.{ts,tsx}'",
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "2.x",
    "@typescript-eslint/parser": "2.x",
    "babel-eslint": "10.x",
    "eslint": "6.x",
    "eslint-config-react-app": "^5.2.1",
    "eslint-plugin-flowtype": "4.x",
    "eslint-plugin-import": "2.x",
    "eslint-plugin-jsx-a11y": "6.x",
    "eslint-plugin-react": "7.x",
    "eslint-plugin-react-app": "^6.2.2",
    "eslint-plugin-react-hooks": "2.x"
  }
}
eslintrc.js
module.exports = {
  env: {
    browser: true,
    es2020: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 11,
    sourceType: "module",
  },
  settings: {
    "import/resolver": {
      node: {
        paths: ["src"],
        extensions: [".js", ".jsx", ".ts", ".tsx"],
      },
      "babel-module": {
        root: ["./src/"],
      },
    },
  },
  plugins: ["react", "react-app", "react-hooks", "@typescript-eslint"],
  rules: {
    semi: ["error", "always"],
    quotes: ["error", "double"],
    "react/prop-types": [0],
    "react-hooks/rules-of-hooks": "error",
    "@typescript-eslint/no-unused-vars": "error",
    "no-unused-var": 0,
    "no-undef": "off",
    "no-use-before-define": ["off"],
    "@typescript-eslint/no-use-before-define": ["off"],
    "@typescript-eslint/explicit-function-return-type": ["off"],
  },
};

いくつか必要なプラグインをダウンロードする必要があるので, package.jsonのdevDependencies の中身をすべてご自身のpackage.jsonへコピーし, yarn コマンドで必要なライブラリをダウンロードすると同じ環境になります.

4. eslintを回す

設定ファイルと参照先を指定してeslintを回します

yarn eslint -c ./.eslintrc.js 'src/**/*.{ts,tsx}'

毎回これを書くのは面倒なので, package.jsonにコマンドを書きましょう
package.json
"scripts": {
"start": "react-scripts start",
....,
"lint": "$(npm bin)/eslint -c ./.eslintrc.js 'src/**/*.{ts,tsx}'"
},

これでいい感じにeslintが回ってくれます.

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.js + TypeScript で path alias を使う

プロジェクトルートに alias を設定

next.config.js
const path = require('path')

module.exports = {
  webpack(config) {
    config.resolve.alias['@'] = path.join(__dirname)
    return config
  },
}
tsconfig.json
{
  "compilerOptions": {
    ...

    "baseUrl": "./",
    "paths": {
      "@/*": ["./*"]
    }

    ...
  },
  ...
}

使う

index.tsx
import Layout from '@/components/Layout'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

今からサービスを考えるなら、SE向けが堅い。少なくとも、男性向けにしよう。

最近、暇で以下のサービスを作った。
https://deau-project.herokuapp.com/

このサービスを、Heroku, React, Go Gin で実装した。
Heroku なので無料だ。
請求先の登録も不要である。

React 以下の、material-ui のテンプレートを使用した。
https://github.com/mui-org/material-ui/tree/master/docs/src/pages/getting-started/templates/album
https://github.com/mui-org/material-ui/tree/master/docs/src/pages/getting-started/templates/sign-in
https://react-hook-form.com/jp/

サービスを開始してから、Qiita でブログを始めた。
ブログを始めた次の日のPV, UUが、500弱だった。
9割は、Qiita からである。

以前の記事で書いたが、無料でサービスを開始できる。
ただ、利用者を増やすにはどうしようか考えていた。

無料で広告するなら、Qiita で記事を書くといい。
ちなみに、note でも同じく記事を書いているが、
note からは数人しか来てなかった。

Qiita からのアクセスは、広告に匹敵する。
もし、サービスを考える時に、この事実を知っていたら、SE向け、男性向けにしていただろう。
残念。

作ったサービスはメール送信するサービス。
SEの性かもしれないが、送信までしてくれる人が多い。

もし、あなたがサービスを考えている途中なら、是非、考慮してもらいたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Git 使い方 git push origin masterまで

Gitでのpushまでの道のり

gitの学習メモ
pushまでの手順。

githubのアカウントは持っている前提。

1、リポジトリの作成

・リモートリポジトリ

・GitHubにログイン
・画面右上+ボタンを押す。
・New repositoryを選択
・リポジトリ名を付ける
・Create Repositoryを押す

・ローカルリポジトリ

・ローカルのソースコードのあるディレクトリに移動
・git init でローカルリポジトリ作成

リモートリポジトリの指定

・git remote add origin https ~ .git
・https ~ .git は GitHubの作ったリポジトリのCodeのページのHTTPSのボタン押したところのやつ

add、commit、push

ソースコードの変更をローカルリポジトリに追加し、リモートリポジトリにローカルリポジトリを反映する。

・git add . ( . は全部のファイルって意味)
・git commit -m "何を変更したかとかのメッセージ"

・git push origin master
これでリモートリポジトリへの反映

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む