僕はとりあえず dayjs !

こんにちは、自意識過剰な正義のヒーローでお馴染みの株式会社SCOUTERの石岡 将明( @masaakikunsan )です。

皆さんは、 JavaScript の日付時刻操作をどうやっていますか?

僕は、 JavaScript の Date は罠が多くそのまま使うことをあまり好まず、今までは日付時刻の操作を行うとなれば黙って moment.js をぶっこんでいました。

フロント界隈でも少し前までは、男は黙って moment.js !って流れがありましたが、最近だと dayjs が主流になりつつあります。 今回はその dayjs について書いていきます。

そもそも

JavaScript の Date はいろいろしんどいんですが、個人的にサービス開発においてブラウザの対応をいちいちするのがだるいなと思っています。 そこで前述の通り、これまでは moment.js をぶっこんでいたのですが、 moment.js はファイルサイズが大きく、バンドルサイズが肥大化するというデメリットがありました。

ですがある日、Twitter を見ていたら dayjs と出会い心を奪われてしまい今後は黙って dayjs やなとなりました。

dayjs について

github.com

Day.js は日付と時刻をパース・検証・操作・表示する最小のモダンブラウザ向け JavaScript ライブラリであり、 Moment.js の API との広い互換性を持ちます。 Moment.js を使ったことがあればすぐにでも Day.js を使い始めることができます。

上記はドキュメントの引用なのですが、この通り、dayjs は moment.js を使えれば直ぐ使えるという魅了があります。 そして、 moment.js でのデメリットであったファイルサイズが大きいという問題を day.js では解決してくれています。

つまり、moment.js のような使用感でファイルサイズがめちゃくちゃ小さくなった最強ライブラリといった感じです。 (これまで moment.js を使って人にとっての話)

使い方

ここでは、みなさんの大好きな Nuxt に day.js をぶっこんで sampleを書いていきます

最初に dayjs をいれていきましょう。

$ yarn add dayjs

次にpluginsを定義していきます。

plugins/dayjs.js

import 'dayjs/locale/ja'
import dayjs from 'dayjs'
import Vue from 'vue'

dayjs.locale('ja')

Vue.prototype.$dayjs = dayjs

あとは、nuxt.config.js に下記を追記でglobalでdayjsが使えるようになります。

plugins: ['@/plugins/dayjs']

それでは、さっそくdayjsで今日の日付が正しく表示されるかみてましょう。 index.vue を書き換えて日付を表示してみます。

<template>
  <section class="container">
    <div>
      <logo />
      <h1 class="title">
        nuxt-dayjs-sample
      </h1>
      <p>現在の時刻: {{ now }}</p>
    </div>
  </section>
</template>

<script>
import Logo from '~/components/Logo.vue'

export default {
  components: {
    Logo
  },
  data: () => ({
    now: null
  }),
  mounted() {
    this.now = this.$dayjs().format('YYYY年MM月DD日 HH:mm:ss')
  }
}
</script>

f:id:masaakikunsan:20190320142048p:plain

今日の日付の表示ができました。 あとは、やりたいことをドキュメントを見ながら実装していくだけです。

ファイルサイズ

f:id:masaakikunsan:20190320143203p:plain

※ moment との比較はちょっと用意できなかったので使ってる人は自分で確認してみてください。

さいごに

dayjs は、moment.js をこれまで使っていた人にとっては最高のライブラリです。 moment と書き方が基本的に変わらず、ファイルサイズがめちゃくちゃ小さいので使わない理由がほぼないです。

これまで moment を使ってみた人は移行コストも学習コストもほぼないので乗り換えてみてはいかがでしょうか?

現在、back check チームでは、エンジニアの募集をしております。 僕と一緒にフロントエンドを朝まで語り尽くしたいフロントエンドエンジニアやレベルの高いフロントエンドチームと最高のプロダクトを作って行きたいサーバーサイドエンジニアは是非ご応募お願いいたいします!

www.wantedly.com

www.wantedly.com

Laravel/Vue.js勉強会#8 オールスターズを開催しました

こんにちは、自意識過剰な正義のヒーローでお馴染みの株式会社SCOUTERの石岡 将明( @masaakikunsan )です。

皆様のおかげで、Laravueも第8回目を迎えました。 そこで今回は、今までのLaravue勉強会で協賛頂いた企業様をあつめ、各社からLT登壇していただくという形を試みました。

これまでの協賛頂いた企業様によるLTということもあり、今まで以上に濃い回となりました。

発表内容

グローバルstoreを利用する際はMixinではなくPlugin使った方がいいんじゃないか?説 (田原一樹さん)

f:id:masaakikunsan:20190319162257j:plain

slides.com

レアジョブさんからは、田原さんがMixinの辛さと代替案について話してくれました。 この辺の話をちゃんとしてくれてる資料・スライドはあまりないので是非一度見てみてください。

Laravelを本番環境にデプロイするまで (@yukure3)

f:id:masaakikunsan:20190319162402j:plain

speakerdeck.com

うるるさんからは、@yukure3 さんがLaravelを本番環境にデプロイするまでの話をしてくれました。 @yukure3 さんは新卒でかつ今回初LTだったらしいのですが、初とは思えないぐらいしっかりとLTをしていて良かったなと思いました。 内容もデプロイまでを細かくしっかりと解説していて良いLTでした。

SPAリリース後の問題とその対策 (@frostndays)

f:id:masaakikunsan:20190319162429j:plain

ITプロパートナーズさんからは、@frostndays さんがリリース後に発覚したSPAの問題とその対策でなにをしたかを話してくれました。 SSRをやめVueのライフサイクルに乗っ取りAPIを叩くようにした等色々話してくれました。

アイスタイル特設サイトにおけるVue.jsの導入事例 (@kubotak_public)

f:id:masaakikunsan:20190319162427j:plain

www.slideshare.net

アイスタイルさんからは @kubotak_public さんがVue.jsの導入事例の話をしてくれました。 Vue.js 関連の話はあまりなかったですが、全体的に面白かったので是非スライドを見てみてください。

Laravel Queueの運用管理 (@migrs)

f:id:masaakikunsan:20190319162428j:plain

オープンロジさんからは @migrs さんがCQRSについてどうやっているかの話をメインでしてくれました。 Laravel Telescope は初めて知ったので今度調べて見ようと思いました。

Monolith→MultiRepo→MonoRepoでのリポジトリ戦略 @kotamat

f:id:masaakikunsan:20190319162648j:plain

slides.com

弊社発表枠では CTO の松本がリポジトリ戦略について発表をしました。 弊社では、Monolith→MultiRepo→MonoRepoといった感じにリポジトリ戦略が変わってきており、各々の話やMonoRepoについて話してくれました。

懇親会

f:id:masaakikunsan:20190319162120j:plain

f:id:masaakikunsan:20190319162135j:plain

発表後は、株式会社うるるさんが提供してくれた飲食を囲み懇親会をしました! 株式会社うるるさんありがとうございます。

まとめ

過去協賛企業様はどこも Laravel + Vue をがっつり使用しており非常に内容の濃い回でした。

次回は、株式会社カオナビさんに会場をお借りし 5/22 に開催します。

最後に

SCOUTER社では一緒に頑張ってくれる方を募集しております。 デザイン、エンジニアの皆さん興味のある方はご応募お願いします!

www.wantedly.com

www.wantedly.com

Web猫ブログを TypeScript化しました

先月よりジョインさせていただいている Web屋さんで猫好きな @jiyuujin 今回は度々登場となって申し訳ないですが、自身のWeb猫ブログを TypeScript化した話をさせていただきます。

webneko.info

nuxt-tsを採用します

結論を先に申し上げると nuxt-ts を採用した件で、その導入周りに留まります。導入にあたってかれこれ1年以上、メンテストップ中の公式テンプレート。。 一切頼りにはできません。今年のどこかで必ず更新してほしさあり、新たに出していただけると大変嬉しいですね😊

github.com

Web猫ブログでは個人的なアナウンスこそしていませんでしたが、今回この場を借りて TypeScript化しましたという経緯です。つい最近、 nuxt-ts 不要になるかもという Issue (下記を参照)を見たのですが、とりあえず nuxt-ts をインストールするしかありません。ビルドコマンドも nuxt から nuxt-ts を使うよう変更してあげる必要があります。

yarn add nuxt-ts
yarn add typescript @types/node ts-loader -D

nuxt-ts 不要になるの?

上記でも触れた nuxt-ts 不要になるかも、というのが以下 Issueを見たことでした。 tslint から @typescript-eslint に移行する一環と思いますが、気になる話でした。

github.com

Nuxt.configを TypeScriptで書く

Web猫ブログでは Headless CMSに Contentfulを採用。 Sitemapの生成処理や Makdownのパース処理など単純な設定に縛られず比較的コードを書く箇所が多く存在すると思いますが、これらの実装箇所が全て型安全に書けるようになったことは今までと大きく違う点だと思います。

APIを使うために型定義を設定する

asyncDatafetch などのAPIをを型安全に利用するために自分自身でVueのInterfaceを拡張してあげる必要があります (/types/nuxt.d.tsを作成すると良いでしょう) この辺りはさすがVue界隈と思う部分ですが、丁寧に書いてくださっていることに対しては感謝しかありません。Nuxt公式ページに Context一覧が存在します。そのページと照らし合わせながらぐりぐり型定義を設定します。

ja.nuxtjs.org

declare module 'nuxt' {
  import { Store } from 'vuex';
  import { Route } from 'vue-router';

  export class Builder {
    constructor(nuxt: Nuxt);
  }

  export class Nuxt {
    constructor(config?: NuxtConfig);
  }

  export interface NuxtConfig {
    dev: boolean;
    [key: string]: any;
  }

  export interface NuxtContext<S = any> {
    app: NuxtApp;
    isClient: boolean;
    isServer: boolean;
    isStatic: boolean;
    isDev: boolean;
    isHMR: boolean;
    route: Route;
    req: any;
    res: any;
    store: Store<S>;
    env: any;
    params: any;
    query: any;
    redirect(path: string): void;
    error(params: { statusCode: number; message: string }): void;
    nuxtState: any;
    beforeNuxtRender(fn: Function): any;
  }
}

上記はあくまでミニマムな構成になっています。当ブログではこの構成に加えて、 contentful 用のModelも定義しています。このように必要に応じて随時追加してあげなければならず少々面倒ではありますが、後々を考えると良い「投資」になってくれるかもしれません。

今回はミニマムな構成の下で動作確認をとることができたのでこの辺で。

リポジトリ公開中

ブログでは随時お問い合わせコメント受付中、PRも絶賛受付中です。

Web猫ブログ 本番運用中

今までの TypeScriptを採用していない版です。

github.com

Web猫ブログ β版 (いずれ本番運用予定)

既に切りました、本番運用開始されたら再度アナウンスしたいと思います。

github.com

最後に、

SCOUTERではエンジニア、デザイナーともに募集しております! 新規事業、絶賛グロース中の事業ともにLaravel, Vue.jsで開発しておりますので、 興味のある方はお声がけください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

Laravel+NuxtでLIFFアプリを作ってみた

はじめに

こんにちは、株式会社SCOUTERの開発責任者の小平(@ryotakodaira )です。 業務では、SARDINEという人材紹介会社向けの業務管理システムを開発しています。

日常的な業務とはそこまで関係がありませんが、LINE社が2018年にリリースした LIFF が気になっていたため、普段使っている技術を用いてLIFFを触ってみたので構築内容を紹介していきます。

LIFFとは

LIFFはLINE Front-end Frameworkの略で、LINEのトークルーム内で動作するウェブアプリの実装を可能にするプラットフォームとなっています。

LINEのトークルーム内でLIFFに登録したウェブアプリを開くと、LINEのユーザーIDやユーザー名などを取得することが可能となっています。

そのためウェブアプリでLINEのコンテキストを利用した機能の提供が可能となり、今までのMessagingAPIだけでは出来なかった体験をユーザーに提供することが出来ます。

linecorp.com

流れ

  • LINE Botの準備
  • LIFFで表示するwebページを用意(Nuxt)
  • LINE Botを操作するためのアプリケーションの準備(Laravel)
  • LIFFアプリを登録

Image from Gyazo

LINE Botの準備

LINE Botの作成

  • LINE Developersのアカウントを作成
  • 新規プロバイダーを作成
  • Messaging API で新規チャネルを作成する
    • アプリ名
      • 適当に入力
      • ここでは「サンプルBot」を指定
    • プラン
      • 「Developer Trial」を選択

LINE Botの設定

  • アクセストークン(ロングターム)を発行
    • テストのため失効までの時間を「0時間」で発行する
    • 本番で使うときには失効時間を設定した方が安全です
  • Webhook送信を「利用する」に変更
    • Webhook URL は後ほど設定します
  • 自動応答メッセージを「利用しない」に変更

ここまででLINE Botの基本的な設定は完了となります。

設定画面の一番下にQRコードが表示されていますので、QRコードを読み取って友だち追加を行います。追加後に友だち追加時のメッセージが送られてきていれば成功となります。

LIFFで表示するwebページを用意(Nuxt)

今回はNuxtで作ったwebページをNetlifyで公開することを前提に進めます。

(本来はvue-cliを使ったVueアプリケーションでも良いですが、Nuxtの方が諸々の設定が楽だっったため今回はNuxtを利用しています。)

LIFFのテストを行うためだけで合ってもhttps接続が可能なwebサイトであることをもとられますので、Netlifyやherokuでサクッと静的ファイルをホスティングするのがおすすめです。

JSでLIFFを扱うためのSDKを読み込む

SDKはLINE社がCDNで公開しているため、それを読み込む。

Nuxtは nuxt.config.js でheadタグの設定をすることができるのでNuxtの書き方に従って記述します。

// nuxt.config.js

module.exports = {
  /*
  ** Headers of the page
  */
  head: {
    script: [{ src: 'https://d.line-scdn.net/liff/1.0/sdk.js' }]
  },
}

LIFFで使うページを作成

// pages/index.vue

<template>
  <section class="container">
    <p class="line-id">LINE ID:{{ lineId }}</p>
    <div class="form">
      <div class="control">
        <input class="input" type="text" placeholder="お名前" v-model="formData.name">
      </div>
      <button class="button is-info is-fullwidth" @click="onSubmit()">送信する</button>
      <button class="button is-light is-fullwidth" @click="handleCancel()">キャンセル</button>
    </div>
  </section>
</template>

<script>
export default {
  data() {
    return {
      formData: {
        name: ''
      },
      lineId: null
    }
  },
  mounted() {  
    if (!this.canUseLIFF()) {
      return
    }

    window.liff.init(data => {
      this.lineId = data.context.userId || null
    })
  },
  methods: {
    onSubmit() {
      if (!this.canUseLIFF()) {
        return
      }

      window.liff
        .sendMessages([
          {
            type: 'text',
            text: `お名前:\n${this.formData.name}`
          },
          {
            type: 'text',
            text: '送信が完了しました'
          }
        ])
        .then(() => {
          window.liff.closeWindow()
        })
        .catch(e => {
          window.alert('Error sending message: ' + e)
        })
    },
    handleCancel() {
      if (!this.canUseLIFF()) {
        return
      }
      window.liff.closeWindow()
    },
    canUseLIFF() {
      return navigator.userAgent.indexOf('Line') !== -1 && window.liff
    }
  }
}
</script>

<style>
.container {
  margin: 0 auto;
  padding: 20px;
  min-height: 100vh;
}

.line-id {
  margin-bottom: 30px;
}

.form > * {
  margin-bottom: 10px;
}
</style>
  • mounted()
    • LINEのコンテキストを受け取ってコンポーネントのデータプロパティーに入れています
    • ここではLINEIDをコンテキストから取得してページに表示している
    • 取得できるコンテキストの一覧はこちらを御覧ください
  • onSubmit()
    • でフォームに入力されたテキストをLINEのトークにメッセージを送信している
    • liff.sendMessages() でメッセージを送信することができる
    • 送信が成功したら liff.closeWindow() でLIFFを閉じる
  • canUseLIFF()
    • LIFFを使える状態にあるかをチェックする
    • ユーザーエージェントがLINEか、LIFFのSDKが適切に読み込まれていることを確認している

yarn dev で開発環境を立ち上げると以下の画像のような表示になります。 ここでは、LINEのコンテキストを取得出来ませんので、LINEIDは表示されておらず、「送信する」ボタンをクリックしても何も表示されません。

LIFFアプリを登録

Image from Gyazo

  • LIFFアプリの設定画面より新規作成をします
    • エンドポイント URL に上で作ったNuxtアプリをNetlifyなどで公開したときのURLを入力してください
  • LIFF URL が発行されればLIFFアプリの作成は完了です
    • エンドポイント URLサイズ は後から編集可能です

LINE Botを操作するためのアプリケーションの準備(Laravel)

Laravelアプリケーションを用意

普段からLaravelを使っており、慣れている分素早くアプリケーションを作れるためLaravelを使用しました。 Laravelのインストール方法や初期設定は今回は割愛します。

以下のURLを御覧ください。

qiita.com

ENVの設定

.env ファイルに以下のLINEの情報を追加します。

# .env

LINE_ACCESS_TOKEN={アクセストークン(ロングターム)}
LINE_CHANNEL_SECRET={Channel Secret}

webhookを受けるエンドポイントを用意

// routes/api.php

<?php
/** @var Illuminate\Routing\Router $router */
$router = app('Illuminate\Routing\Router');

$router->post('/line/webhook', 'LineWebHookController@webHook')->name('line.webhook');

飛んでくるリクエスト処理してBotを動作させる処理の作成

// app/Http/Controllers/Api/LineWebHookController.php

<?php
/**
 * Class LineWebHookController
 * @package App\Http\Controllers\Api
 */
class LineWebHookController extends Controller
{

    /**
     * @param Request $request
     * @throws LINEBot\Exception\InvalidSignatureException
     */
    public function webHook(Request $request)
    {
        // Botの印象情報をENVから受け取る
        $lineAccessToken = config('line.access_token');
        $lineChannelSecret = config('line.channel_secret');

        // 署名をチェックする
        // 署名が正しくなければ不正なアクセスとみなして何も行わない
        $signature = $request->headers->get(HTTPHeader::LINE_SIGNATURE);
        if (!SignatureValidator::validateSignature($request->getContent(), $lineChannelSecret, $signature)) {
            return;
        }

        $lineBot = new LINEBot(new CurlHTTPClient ($lineAccessToken), ['channelSecret' => $lineChannelSecret]);

        try {
            // イベントをパースする
            /** @var LINEBot\Event\BaseEvent[]|LINEBot\Event\MessageEvent\TextMessage[] $events */
            $events = $lineBot->parseEventRequest($request->getContent(), $signature);

            foreach ($events as $event) {
                // LINEから送られてきたメッセージの内容を抽出
                $receiveText = '';
                if ($event->getType() === ActionType::MESSAGE && $event->getMessageType() === MessageType::TEXT) {
                    $receiveText = $event->getText();
                }

                // 「liff」と投稿されたら、LIFFアプリを開く
                if ($receiveText === 'liff') {
                    $message = new TextMessageBuilder('line://app/1554604976-anxJ8ogY');
                } else {
                    $message = new TextMessageBuilder('送信ありがとうございます!🙇<200d>');
                }

                // メッセージを送信
                $replyToken = $event->getReplyToken();
                $lineBot->replyMessage($replyToken, $message);
            }
        } catch (Exception $e) {
            logger($e->getMessage());
            return;
        }

        return;
    }
}

webhookURLをLINE Developersの設定画面で設定

LINE Developersで設定を行う前にwebhookURLを公開する必要があります。

今回はサンプルの開発のため、ngrokというサービスを使ってwebhookURLを公開しました。

ngrokの導入についてはこちらの記事を御覧ください。

qiita.com

webhookURLが公開出来たら、Developersの設定画面の「webhook URL」欄に公開したエンドポイントのURL文字列を入力します。

URLの設定後に「接続確認」をクリックして成功すれば設定は完了です。

完成物

LINEのトークルームで「liff」と入力すると、LIFFアプリのURLがBotから送られて来ます。

そのURLをクリックしてLIFFアプリを起動するとLINEユーザーIDが表示されています。

「お名前」フォームにテキストを入力して、送信後、LINEトークルームに入力した内容が投稿されていれば成功です!

Image from Gyazo

まとめ

LIFFという新しい技術を初めて試してみましたが、想像よりも簡単にLIFFアプリを作成することが出来ました。ただ、当然普通のウェブアプリと比べると開発中のデバックが大変でした。LINE上だとコンソールが確認できないため、ウェブアプリ上にプリントしてデバックを行うしかなさそうでした。

LIFFアプリを使えばウェブアプリでLINEのコンテキストを扱うことができるため、LINEBotの可能性が一気に広がったのではないかという印象を受けたため、様々なサービスがLIFFを利用してより良いユーザー体験を提供してくれるのではないでしょうか。

今後、弊社のプロダクトでも機会があれば取り入れたいなと思える機能でした。

最後に

弊社では最新技術のキャッチアップ、プロダクション導入を積極的に行っています!

そして、事業・サービスが成長していくにあたって、これからもメンバーを増やしていきたいと思っています。

興味のある方は下記からご応募いただくか、@ryotakodairaにご連絡ください!!

www.wantedly.com

www.wantedly.com

www.wantedly.com

JWT認証と流れのやわらかい解説

こんにちは。 SCOUTERフロントエンドエンジニア、現在Laravel勉強中の匠平@show60です。

弊社のプロダクトはフロントエンドをVue.js 、サーバーサイドをLaravelで実装しています。 私が携わっている新規事業のback checkでは、LaravelでAPIを実装し、フロントエンドからリクエストを送ることでアプリケーションを動作させています。

APIを実装する際に必要になるのがセキュアな認証。そこで広く使われるJWT認証について学んだことを今回お話したいと思います。

なぜ必要なのか、どのように使っているのかを焦点にやさしくお話しします。

ユーザー認証とは

ユーザー認証とは、アクセスしてきたユーザーが正当なユーザーかどうかをチェックすることです。 チェックに使用される要素としては以下のようなものがあります。

  • 知る要素
    • パスワード
  • 持つ要素
    • トークン
  • 備える要素
    • 指紋
    • 虹彩 (目)

JWTはその中にユーザー認証情報を含ませることができるため、正当なユーザーかどうかをチェックできるということですね。

余談、よくパスワードを忘れた際に、母親の旧姓やペットの名前、出身校名を聞かれますが、これらは「知る要素」に分類されますね。 とはいえ、これらは他人にも知られる情報であるため、安全ではなく推奨されていません。

実家のペットの名前がモカちゃんであるとここに書くことで、私のアカウントはもはや安全ではなくなってしまいます。

参考: [Google Online Security Blog | New Research: Some Tough Questions for ‘Security Questions’ 2015年5月] (https://security.googleblog.com/2015/05/new-research-some-tough-questions-for.html)

今回の題材であるJWTは「持つ要素」に分類されるものです。

JWTとは

JWTとはJSON Web Tokenの略です。

JSONとはJavaScript Object Notationの略で、JavaScriptのオブジェクトの構造を持ったデータフォーマット。

Tokenとはユーザーを識別するための認証情報。

つまりJWTとは、JavaScriptのオブジェクトの形をした認証情報のことです。

JWTの構成

JWTは大きく3つの要素で構成されます。

  • ヘッダー
  • クレーム情報
  • 署名

ヘッダー

この後の署名に使われている暗号化アルゴリズムや、トークンタイプなどのメタ情報が入ります。

クレーム情報

任意の情報を含ませることができ、ここにユーザー認証情報を記述します。

私の携わるbackcheckというプロダクトには、企業、候補者、推薦者、管理者と4つのユーザータイプがあります。それぞれのページを行き来できないようアクセスに制限をかけるため、このクレーム情報内にユーザータイプを表すキーを実装しています。

署名

ヘッダーに記述した暗号化アルゴリズムにより、ヘッダーとクレーム情報をBase64urlに変換したものから生成されます。

なぜJWT認証を使うのか

JWTを使うメリットは以下のようなものが挙げられます。

  • 安全
    • JWTに署名が含まれているため、改ざんがあってもチェックできるようになっている。
  • 実装のしやすさ
    • セキュアなToken発行が楽に実装できる。
  • 管理のしやすさ
    • URLに含むことができる文字で構成されているから、HTTPリクエストでの取り扱いが楽。
    • 認証に必要となる情報はすべてJWTの中に入っているため、ユーザー認証情報をサーバーで管理する必要がない。DB問い合わせを行う必要がない。

JWT認証を使えば、安全かつ管理がしやすいセキュアな認証を簡単に実装できるということですね。

サーバーサイドで行うこととフロントエンドで行うこと

サーバーサイドとフロントエンドで行うことを分類しながら、JWT認証を使用する流れを説明します。

ユーザー初回登録時

  • 【フロント】
    • ユーザー情報 (メールアドレス、パスワード等) を送信
  • 【サーバー】
    • リクエストされたユーザー情報を元にJWTでトークンを生成
    • トークンをフロントへ返す
  • 【フロント】
    • 返ってきたトークンがbase64urlでエンコードされているため、base64 に変換
    • 変換したものをLocalStorageに保存

このときトークン内のユーザー情報には、上に書いた4種類のユーザータイプ情報も含んでいます。

2回目以降のログイン・画面更新時

  • 【フロント】
    • Tokenの有無の確認
      • LocalStorageにTokenがあるかどうかを確認
        • ある場合は、HTTPヘッダに入れてサーバーにアクセスする
        • ない場合は、そのユーザータイプによって適切なページへリダイレクトさせる
  • 【フロント】
    • Tokenの有効期限の確認
      • LocalStorageにTokenがある場合、その有効期限を確認
      • 有効期限が切れている場合、サーバーに問い合わせし、Tokenを更新
  • 【サーバー】
    • Tokenの更新リクエストがきたらJWTをリフレッシュして返す(このときフロントでは、初回登録時と同じように返ってきたトークンをLocalStorageに保存します)
  • 【フロント】
    • サーバーにアクセス
      • 有効なアクセスであることがフロントで確認されたらサーバーにアクセスをします
  • 【サーバー】
    • トークンの認証を確認しデータを返す

初回登録時に発行したトークンをLocalStorageに保管しており、有効期限やユーザータイプの情報を持っているため、2回目以降のログイン・画面更新時のサーバーへのアクセスは多くても2回に抑えられています。

フロントエンドでの処理は、ユーザー情報がすべてトークン内に記述されており、JSONフォーマットなので処理もとても簡単に行えますね。

まとめ

実際にはサーバー内での処理を加えるとまだまだ言及しなくてはいけないことがありますが、流れをさっくりと説明させてもらいました。

さいごに

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記よりご応募ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

API仕様の出力プラグインをtraitにしてみた話

みなさんこんにちは
先月よりジョインしました、 niisan-tokyo です。
今後とも宜しくお願いします。

で、今回のテーマですが、以前にkotamatが公開していた、テストを通したAPIスペックの自動生成プラグインをちょいと改造したという話です。

APIスペック自動生成ツールの改造

TL;DR

  • API仕様の自動吐き出しがクラス継承式だったので、使いにくかった
  • traitでも使えるようにした
  • 一応後方互換性を確保した

対象のリポジトリ

https://github.com/kotamat/laravel-apispec-generator

ちょっと使いにくかった

このプラグインは、使い方は容易だったのですが、一つ欠点がありました。
それは、このプラグインをベースクラスとして継承しなければならないということです。

<?php

use ApiSpec\ApiSpecTestCase;

class TestCase extends ApiSpecTestCase
{

}

APIスペックを出力する必要のないところでは、このクラスを継承したくないため、別のベースクラスを使いたくなりますが、一方で、ベースクラスでアプリで特有の設定やら便利なテスト機能を実装することがあるでしょう。
結局、普通のベーステストクラスとともに、APIスペック出力用のクラスを継承したベーステストクラスを用意するという、少々厄介なことになります。

traitにする

そこで、ベースクラスの代わりにtraitを使うという技が考えられます。
traitは使用することで、そのクラスに単純にtraitの中のコードをコピーしたのと同じ状態を作ることができます。
ApiSpecTestCaseは、実際にはいくつかのメソッドにAPI仕様を吐き出すコードを入れているだけですので、traitで十分ということになります。

traitを使う場合のAPI仕様の出力は以下のようにすると可能になります。

<?php

use ApiSpec\ApiSpecOutput;

class SomeTestCase extends TestCase
{
    use ApiSpecOutput;

    //...
    
    /**
     * @test
     */
    public function API仕様を吐き出すテスト()
    {
        $this->isExportSpec = true;
        $this->getJson('/someone/status');
    }
}

こうして、必要なときにだけ、use ApiSpecOutputすることで、ベースクラスをいじることなく、API仕様を吐き出すことができます。

後方互換性の維持

こうして、traitにしようとしたときに、すでにベースクラスで使っているところはどうなるんだという話になります。
これは、プロダクト開発において、仕様を追加する場合にも発生する問題で、後方互換性をどうするのかということです。 今回は後方互換性は残すことにします。

後方互換性の確保の方法は簡単で、要するにテストが通っていればいいということです。
API仕様出力ベースクラスはテストクラスのベースクラスなので、テストの方法が厄介でした。

https://github.com/kotamat/laravel-apispec-generator/blob/0ade044a5a8f41d54a4b872978a1c3cbb2647bc9/test/ApiSpecTestCaseTest.php

テスト自体はかなり無理矢理なコードでとにかくテストできればいいやって感じになっています。

<?php

// ... 中略

    public function createApplication()
    {
        $app = new class extends Application {
            private $acceptor;
            public function __construct($basePath = null)
            {
                //
            }
            public function setAcceptor($acceptor)
            {
                $this->acceptor = $acceptor;
            }
            public function make($class, array $param = []) {
                $mock = m::mock(FilesystemAdapter::class);
                $mock->shouldReceive('drive')->andReturn($this->acceptor);
                return $mock;
            }
        };
        $app->setAcceptor($this->acceptor);
        $this->app = $app;
    }
    protected function setUp()
    {
        $this->acceptor = new class {
            public $filename;
            public $str;
            public function put($filename, $str) {
                $this->filename = $filename;
                $this->str = $str;
            }
        };
        $this->createApplication();
    }

これがテストの前提部分ですね。
無名クラスを連発しておきながら、思い出したようにMockeryを利用したりしています。
テスト部分は上のリンクで見てください。単純にpostJsonとかを投げたら、API仕様の出力ができていることを確認しているだけです。

このテストを作った上で、ApiSpecTestCaseにあったロジックの大半をApiSpecOutputに移し、ApiSpecTestCaseApiSpecOutputを使っているだけという状態にすることができました。

まあ、ベースクラスに手を入れずに、traitだけ作ればこんな苦労はないんですが、同じコードがあるって気持ち悪いので、やってしまいしました。

まとめ

というわけで、API仕様の出力をtrait化することに成功しました。
個人的にはもうちょい改造したいところですが、ひとまずはtraitで使えるようになったというところで満足しておきましょう。
今回はこんなところです。

最後に

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

Vueのライフサイクルフックまとめ

こんにちは!
株式会社SCOUTER開発部フロントエンドエンジニアの佐藤(@r_sato1201)です

弊社ではフロントエンドのフレームワークにVue.jsやNuxt.jsを採用しています。 業務中で開発をしてる際に、Vueのライフサイクルフックについて理解が乏しいまま実装していることに気がついたので 今回はVueのライフサイクルフックについてまとめたいと思います。

ライフサイクルとは?

f:id:ryonnsui1201:20190225221150p:plain:w250

ライフサイクルフックについて話す前に、まずライフサイクルについて。 Vueに触れている方なら、上の図を一度は見たことがあると思います。 ライフサイクルとは、Vueインスタンスが生成されてから削除されるまでの流れのことで、上の図はその流れを表した図です。

Vueのライフサイクルは大きく分けて以下の5つに分けられます。

① Vueインスタンスの生成
② DOMへのマウント
③ 画面の更新、リアクティブデータの変更
④ Vueインスタンスの破棄
⑤ エラー時

それでは次に、上記の分類に沿ってライフサイクルフックを紹介したいと思います。

ライフサイクルフック

ライフサイクルフックとはVueのライフサイクルにおける一連の処理の中の各タイミングで実行する関数のことをいいます。 各フック内に記述することで、任意の処理を行うことができます。

先程記述したライフサイクルの5つの分類に沿ってライフサイクルフックを説明していきます。

① Vueインスタンスの生成

実行されるフック:beforeCreate created
まずは Vueインスタンスが生成されます。この時点ではインスタンスがマウントされていないので、まだコンポーネントの内容は反映されていません。 Vueインスタンスが生成され、リアクティブデータが初期化される前にbeforeCreateが呼ばれ、リアクティブデータが初期化された後にcreatedが呼ばれます。

② DOMへのマウント

実行されるフック:beforeMount mounted 
生成された Vueインスタンスは、次にDOMにマウントされます。 マウント前にbeforeMountが呼ばれ、マウント後にmountedが呼ばれます。 したがって、DOMにアクセス可能なのはmounted以降になります。

③ 画面の更新、リアクティブデータの変更

実行されるフック:beforeUpdate updated 
Vueインスタンスdataが変更された場合や、propsで渡されているデータが親で変更された場合など、それらの値の変更のタイミングでDOMを自動的に更新し再描画します。その際に実行されるフックが上記2つです。 DOMが更新される前にbeforeUpdateが、DOMが更新された後にupdatedが呼ばれます。

④ Vue インスタンスの破棄

実行されるフック:beforeDestroy destroyed
v-ifなどの機能によりVueインスタンスが破棄された時、上記2つのフックが呼ばれます。 Vueインスタンスが破棄される直前にbeforeDestroyが呼ばれ、Vueインスタンスが破棄された直後にdestroyedが呼ばれます。

⑤ エラー時

実行されるフック:errorCaptured 任意の子孫コンポーネントからエラーをキャッチしたときに呼び出されます。 このフックは ・エラー ・エラーをトリガするVueインスタンス ・どこでエラーが捕捉されたかの文字列情報 これら3つの引数を受け取ります。 また、このフックではエラーが自分より上位に伝播するのを防ぐために、false を返すことができます。

表にしてまとめると以下のようになります。
ライフサイクルフックの一覧

フック名 説明
beforeCreate インスタンスが生成され、リアクティブデータが初期化される前
created インスタンスが生成され、リアクティブデータが初期化された後
beforeMount インスタンスがマウントされる前
mounted インスタンスがマウントされた後
beforeUpdate データが更新され、DOMに適用される前
updated データが更新され、DOMに適用される前
beforeDestroy インスタンスが破棄される直前
destroyed インスタンスが破棄された直後
errorCaptured 任意の子孫コンポーネントからエラーがキャプチャされたとき

createdとmounted

ライフサイクルフックで使用頻度が高いのはcreatedmountedだと思います。 それぞれのユースケースをまとめてみました。

created

非同期通信を使ってデータを初期化するとき

よく使われるパターンとしてAPIなどを叩いて非同期通信で取得したデータをdataに初期値として挿入する処理が挙げられると思います。 このケースでは初期化のタイミングで使うという面ではbeforeMountmountedで 非同期通信を行ってもよいのですが、DOMのマウントが行われデータを画面に反映するまでの時間を少しでも減らすためにはcreatedを使う必要があります。また、beforeCreateではリアクティブデータが初期化されていないため、dataに挿入することができません。

mounted

DOMにアクセスする必要がある場合

DOM要素にアクセスする必要がある場合や、HTML要素の高さや幅を取得する場合の初期化処理はmountedに記述するとよいでしょう。 ただし、mountedは 全ての子コンポーネントがマウントされていることを保証しないことに注意してください。Vue全体がレンダリングされた後に処理を行いときは、vm.$nextTickを使いましょう

mounted: function () {
  this.$nextTick(function () {
    // Vue全体がレンダリングされた後にのみ実行されるコード
  })
}

まとめ

Vueのライフサイクルについてまとめてみました。 今回触れなかったライフサイクルフックについても、分かりやすいユースケースなどあれば教えて頂けると幸いです!

さいごに

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

参考資料