ninjinkun's diary

ninjinkunの日記

アゴタ・クリストフの『悪童日記』から始まる三部作を読んだ

以前rebuild.fmでNさんがおすすめしていたので気になっていたアゴタ・クリストフの『悪童日記』を読んだ。面白すぎて、続く『ふたりの証拠』『第三の嘘』も一気に読んでしまった。

フィクションだが、第二次大戦中と戦後のハンガリーという舞台が凄まじく、暴力と死が身近にある暮らしが淡々と語られる。国境や隣国という概念があるのも、島国で育った人間から見ると新鮮に感じた。

母国に居られなくなって国を出て行ったり、友人や身内と離ればなれになるというのはどれだけ辛いことなのだろう。戦争や治安的な理由で日本から出て行かざるをえない状況を考えるだけでも、かなりしんどいものがある。

悪童日記

悪童日記

ふたりの証拠

ふたりの証拠

第三の嘘

第三の嘘

暴力沙汰を起こした警官になって追われる夢

朝起きて書き留めたのだけれど、後から読むと自分でも全くなんだかわからない。


自分は現役の警官で、交番に勤めている。 どうもコミュニケーション能力に難があり、頭も余り良くないが心は優しくて正義感はあるという設定。

かくまってくれた女性となぜか逃げる。 女性はトラウマがあり、それがどうもこの会場で結婚式を挙げる人と関係があるようだ

誓いの言葉が聞こえてきたとき、女性が取り乱し大声を上げてしまう。 自分は必死で宥めるが、途中で人が来る気配を感じて外に逃げる。 女性の友だちたちが「もしかしてと思ったら来てくれてたのね」みたいに女性に近寄るが、女性は叫びながら泣き続けている。

自分は外に出て逃げつつあるが、取り乱した女性が足を滑らせて川に落ちてしまう。 みんなも川を追いかけるが、流れが早くて追いつけない。

見かねた急流に飛び込んで追いかける。 マリオみたいに急流をぴょんぴょん飛んで、ようやく捕まえることができた。

派出所の隠れ家に帰るが、帰り道をつけられたようで、特に親しくない昔の同級生が隠れ家にたどり着いて、そこで万事休すとなる。記者会見を1時間後にやることにする。

リバーズエッジ。 オザケンが流れる。 自分は記者会見の準備をして終わる。

数年後

整形外科の医師を探す。 うまく見つかる。 女の子が自分の顔を見つけることができて終わった。

mozaic bootcampに参加して気づいた、自分に欠けていたWeb技術の知識メモ

mozaic bootcampとは?

mozaic.fmリスナー向けの勉強会。mozaic.fmはJxck氏が主催するPodcastで、Web標準やブラウザ、プロトコルなどWeb技術をターゲットにしており、自分も愛聴している。

今回行われたbootcampはゴールデンウィークの4日間を使い、「Webを正しく理解し、正しく使う」ことを目的として行われた。

参加者はざっくり言うとそこそこ経験のあるWebエンジニアが6名、主催のJxck氏、mozaic.fmでお馴染みの矢倉氏の計8名。参加にあたってはビデオ通話による選考もあった。

会場は自分が所属する一休のリフレッシュスペースを利用した。

当日どんな感じだったかは、以下のエントリで紹介されている。

主催のJxck氏のエントリ

参加者のisyumi_net氏のエントリ

自分に欠けていたWeb技術の知識

自分はWebに関わるエンジニアとして一応10年間働いてきたが、このbootcampに参加して自分がWebの基本的なことを全然理解していなかったことに気づいて愕然とした。なんとなく知っているつもりでも、人に説明できるほど理解していないことが多かった。

しかし今回のbootcamp参加で自分の穴に気づくことができ、ある程度知識を埋めることができたので、特に自分が理解してなかったトピックについて当日のメモを書き出し、まとめてみようと思う。あくまで自分のメモで、何か新しい発見があるわけではないことはお断りしておく。

  1. Cookie
  2. Cross Origin Resource Sharing (CORS)
  3. TLS

1. Cookie

  • サーバがSet-Cookieヘッダを付与すると、ブラウザは次回のアクセス時もその値を返す
    • クレデンシャルとして広く使われている
  • 有効期限について、Expire はローカルで時計を弄られる可能性があるので、今は Max-Ageを使う
  • Max-Ageがないと自動的にSession Cookieになる
    • SessionはUA依存だが、だいたいはブラウザのタブを閉じるまで
  • Max-Ageが設定されていても、Cookieはブラウザの都合で消されたりするので、必ず保存される保証はない
  • 最近はMax-Ageを長めにする傾向にある
  • Domain属性はあまり使わない方が良い

  • Pathはだいたい/だけ指定する
    • Pathを指定しないと、そのcookieを発行したpath以下でしか値が読み出せない
      • 例えば/loginで付与すると /login/hogehogeなどでしか読み出せず、/では読めなくなってしまう
  • Secure, HttpOnly は設定しておくこと
    • Secure HTTPSでのみ送信
    • HttpOnly JSから弄れなくなる
  • Clear-Site-Data `Clear-site-Data: "*"` とか指定するとログアウト時に全部消してくれる
    • Edge, Safariはまだ未対応
    • 今までは明示的に消す仕組みがなかった
  • クレデンシャルとして使われているのに、セキュアに使うのが難しいデザインになってしまっているのがCookieの問題点
  • SameSite属性
    • CookieはSame Origin Policyの範疇外の困った子なので、これをまともにする
    • 既存の挙動を変えるとサイトが壊れるので、新しいattributeで対応する
    • SameSite: Strict;
    • SameSite: Lax;
      • トップレベルナビゲーションとサイト内だけでcookieが送られる
      • 使いやすい
    • CookieをRead cookieとWrite cookieに分けるのが望ましい
      • そしてWrite cookieにStrictを付けるのが一番いい
  • Secが付いているヘッダーはJSから付与できない
  • 話はこの後Intelligent Tracking Prevention (ITP) に展開したが、割愛

2. Cross Origin Resource Sharing (CORS)

  • xhrで他サイトにリクエスト送信が可能になったが、他サイトにリクエストする際にCookieやヘッダも一緒に送られてしまう
    • 他サイトの情報を無制限に読み取り可能
  • xhrでリクエスト可能な範囲をOriginという概念で制限する
    • scheme、host、portが一致しないと別originとして扱われ、リクエストが制限される
  • Access-Contol-Allow-Origin
    • 指定したOriginからのリクエストを許可する
    • 同一組織内の別サービスからの読み出しなどで使う
    • POST/PUT/DELETEなど副作用があるメソッドを投げる場合は、OPTIONSでpreflightリクエストを投げる
  • Formは遷移するので他のサイトにCORSもできる。originヘッダでどこから来たかわかる
    • Formが自分のサイトから来たものかを検証しないとCSRFになる

3. TLS

このトピックは最終日に時間が余っていたので、自分がリクエストした。いきなりJxck氏がXORについて話し始めて面食らったが、どんどん話が展開し、「この人TLSの仕様が頭に入ってるんだ…」とみんな唖然としていた。後から聞いたら自分で実装したことがあるとのこと。

  • XORは2回かけると元に戻る性質がある
    • A ^BB=A`
    • 共通鍵暗号の基本はこれ
    • 暗号化、復号化コストが低い
    • このBを共通鍵としてクライアントとサーバー間で共有して暗号化された通信をするのがTLSのざっくりした説明
  • TLS 1.2のハンドシェイク
    • 公開鍵暗号を使って共通鍵暗号の鍵を交換する
    • ClientHello
      • クライアントから必要な情報を渡す
      • 対応している暗号化方式を送信
    • ServerHello
      • 暗号化方式を決定して送信
    • ServerCertificate
    • ServerKeyExchange
      • サーバーの公開鍵を送信
    • CertificateRequest, ClientCertificate, CertificateVerify
      • クライアント証明書関係、略
    • ClientKeyExchange
      • サーバーの公開鍵でクライアントが生成した共通鍵を暗号化して送信
    • ChangeCipherSpec
      • ここから共通鍵を使った暗号化通信に移行する
  • なぜ認証局が必要か?
    • サーバーが送信してくる公開鍵がサーバーの運営者のものであることを保証するため
      • 中間者攻撃により通信経路が乗っ取られて、偽の公開鍵が送られていても署名で検証できる
      • 所謂オレオレ証明書をクライアントが許可して運用していると、中間者攻撃で証明書をすり替えられても本物かどうかを検証できなくなる
  • 認証局はさらに上位のルート認証局に認証されており、ルート認証局ルート証明書はOSやブラウザにインストールされている
    • これにより、ルートを辿っていって最後はローカルにインストールされている証明書で検証することで、CAの正当性を保証できる

終わりに、お気持ちの表明

bootcampではもっと膨大なトピックが扱われていたが、特に自分に欠けていたと感じたものを抜粋して書き出した。

自分は、bootcampのテーマである「Webを正しく理解し、正しく使う」とは、技術の様々な文脈や経緯を知った上でベストプラクティスを選択できることであり、その選択理由を明確に説明できることだと解釈した。

だが、bootcampを通して、Webを正しく使うことは本当に難しいし、Web開発者の全員にこれを求めるのは無理だろうと感じた。昨今ではフレームワークやPaaSがベストプラクティスを実装して、我々利用者は難しいことを意識しなくても、ある程度のセキュリティやアクセシビリティを担保しながら開発できるようになってきているという現状もある。

しかしそれでも、ある程度人数が居る開発組織ならば、少なくとも一人はWebを正しく使えるエキスパートが居るべきだと思う。自分が知っている「技術力がある」と見なされている会社には、そういったエキスパートが数人、もしくはもっと多く在籍している。

今回こういった機会を得たことで、一介のWebアプリケーション屋である自分も、「Webを正しく使えるWebアプリケーション屋」と名のれるようになりたいと強く思った。

Jxckさん、矢倉さん、参加者の皆さんありがとうございました。今後もmozaic.fm聞き続けます。

mozaic.fm

create-react-app 3.0にしたのでついでにTSLintからESLintに移行した

経緯

BlogFeedbackで使っているcreate-react-appを3.0に上げたらTypeScriptにもLintが効くようになっていたが、そのせいでいきなりビルドが止まるようになってしまった

  • 元々TSLintを入れてエディタとCIでTSLintを実行していた
  • create-react-app 3.0からyarn startyarn buildESLintが実行される
    • TypeScriptには@typescript-eslintで対応してるみたい
  • 新しく入ったESLintは無設定状態だとデフォルトのルール (eslint-config-react-app)で実行されるので、TSLintで設定していなかったいくつかのルールで引っかかるようになって、CIでyarn buildが止まっていた
    • eslint-config-react-appにはjsx-a11y react-hooks プラグインが入っていたので、その辺で落ちていた
  • 今後はESLintになる流れらしいので、この際なのでESLintに移行して、ルールもデフォルトに合わせることにした

ESLint対応

github.com

prettier対応だけして、あとはデフォルト。

$ yarn add eslint-config-prettier eslint-plugin-prettier

.eslintrc.json

{
  "extends": [
      "react-app", 
      "plugin:prettier/recommended"
  ]
}

package.json

scripts: {
    "eslint": "eslint './src/**/*.{ts,tsx}'",
    "eslint:fix": "eslint --fix './src/**/*.{ts,tsx}'"
}

あとはCIでtslint使ってる箇所を書き換えて終わり。

ブラクラ

const SignInPage: React.FC<Props> = props => {
  useEffect(() => {
    props.fetchUser(firebase.auth());
    return () => undefined;
  }, []);
  ...
}; 

というhooksをよく知らずに書いたコードで怒られたのでeslint --fixしたら

const SignInPage: React.FC<Props> = props => {
  useEffect(() => {
    props.fetchUser(firebase.auth());
    return () => undefined;
  }, [props]);
  ...
}; 

にされて、effectが無限に発火し続けてブラウザが固まるようになってしまった。 リリースしてから気づいたので焦って直した(fetchUser をキャプチャするようにしたら直った)。

おわりに

create-react-app 使ってると最新のベストプラクティスが勝手に突っこまれるので便利。その反面アップデート時の対応は毎回少し面倒。

毎朝空いている有楽町線下りの考察

東京の電車は混んでいて辛いという話を聞くが、自分が通勤で使っている有楽町線の東側から西側(新富町→永田町)へ向かう下り電車はかなり空いている。9:30くらいの電車ではほぼ座ることができ、早めに出勤しても1、2駅立っていればだいたい座れる。

なぜこんなに空いている電車が走っているのか考えてみた。

  • 有楽町線は西側で西部、東武と相互乗り入れしており、郊外からの乗客が多い
    • このため、朝の時間は10両編成車両が4, 5分おきにやってくる
  • 一方で東側は新木場でJRに乗り換えられるだけ
    • かつ接続する京葉線の乗客はほぼ乗り換えずに東京駅に向かうと思われる
    • 沿線の住宅地も月島、豊洲くらい
  • 鉄道はやってきた車両を車庫に戻す運用が必要なので、どちらかの列車本数を減らすことはしづらい

つまり人数が多い西側の乗客を運ぶためのキャパシティで人数が少ない東側の乗客を運んでいるので、ガラガラになっているわけだ(とドヤ顔するほどのことでもない)。

もちろんこの状態では経営効率が良くないことは明らかなので、有楽町線豊洲-住吉間に支線を作って延伸する計画が進行中らしい。

他にも片側だけ相互直通がない路線がないか調べてみたが、あったのは都営新宿線の東側くらいで、こちらは郊外まで地下鉄自体が伸びていた。

Mini Metroというゲームをやっていると、こういうことを延々考えてしまうのでおすすめです。

ミニメトロ

ミニメトロ

  • Dinosaur Polo Club
  • ゲーム
  • ¥480

play.google.com

Suica入りスマホ2台持ち生活

  • Suica契約済みのiPhoneSuica未契約のPixel 3を持ち歩いていた
  • 通勤で改札の前に来る度にPixelを取り出しまい、ああ違ったiPhoneじゃないとなっていた
  • PixelもSuica契約すればダブルSuicaで最強!と思ってPixelもSuica契約した
  • 今度は改札を出る際に、あれどっちのSuicaで入ったっけ?となって問題が余計面倒になった

ブログのSNSシェア数を毎朝メールでお知らせする機能をリリースしました

BlogFeedbackにシェア数の増加をメールでお知らせしてくれる機能を追加しました。

シェア数が昨日より増加していると、以下のようなメールが配信されます。

f:id:ninjinkun:20190120124338j:plain:w320

設定

設定はブログの追加時

f:id:ninjinkun:20190120125642p:plain:w320

または以下の設定画面から行うことができます。

f:id:ninjinkun:20190120125944p:plain:w320

配信する時間はとりあえず朝の9時にしてありますが、予告なく変更する場合があります。自分で時間や配信メールアドレスを選ぶ機能は今のところありません。

実装

  • クロールからメール送信まで全てFirebase Cloud Functionsで実装
  • Firebaseにはcronがなかったので、GCPにappengineをデプロイする方法で実装(これが一般的な方法らしい)
  • 動きは以下の通り
    • GCP cron→AppEngine→Cloud Pub/Sub→Cloud Functionsの対象者抽出Function→Cloud Pub/Subで対象ブログごとにFunctionsを起動
  • Firebase Cloud Functionsの環境にはGCP用の環境変数が設定されているらしく、Cloud Pub/SubのSDKが設定なしで動いてびっくり
    • Firebase Cloud Functionsの中身はGoogle Cloud Functionsなのでよく考えたら当たり前ではあるが
  • Cloud Pub/Subは1回以上実行保証で、2回実行されることが普通にあるのでロック処理を入れた
    • Firestoreにロック用テーブルを作ってjobごとにUUIDを入れる実装
  • フロント側のクローラとコードが共有できそうな気もしたが、細かい部分で差異が出てくるのでとりあえずはコピペで対応
  • HTMLメールのマークアップ辛すぎる
    • Gmailがflexboxタグを抜いてくるので、古き良きtableとfloatでマークアップした(やったことなかったけど)
  • 今はメールのSMTPサーバが自分のGmailなので、500通/dayしか送れない
    • ユーザーが増えたらSESなりSendGridなり検討する
  • 対象ブログ抽出の実装が1000ユーザーで上限決め打ち
    • 拡張する対応は簡単なので、とりあえずこれも今は放置

スマホ2台生活

右ポケットにiPhone X、左ポケットにPixel 3 XL。どう考えても無駄な気がするが、この生活を始めて2ヶ月くらい経ってしまった。

元々iPhone Xを持っていたところにPixel 3 XLを買ったのだが、iOSからAndroidへの環境移行がとても面倒で、特に支払い周りのApple Pay(iD)とMobile Suicaが直接移行できなかった(新規でセットアップ & 登録が必要。Mobile SuicaはなぜかAndroidだけ年会費もかかる)のが原因で、二台持ちが始まってしまった。

日常的なブラウジングやSlackのチェックはどちらか出てきた端末でやる。Netflixなどの動画は画面が大きいPixel、支払いはiPhoneとなんとなく使い分けがされてきている。

開発者としてはMobile SafariAndroid Chromeがすぐに試せるのが最大のメリット。両OSの進んでいる部分がちょっとずつ試せるのも悪くない。

ランニング、水泳、睡眠トラッキングApple Watchが手放せないのもあってiOSも捨てられない。

なんとなくこのまま二台持ちで行きそうな気がする。

create-react-appで作ったアプリのFetch as Google対応

結論から言うと以下のエントリを参考に

import 'babel-polyfill';

したらできた。

scrapbox.io

追記

yarn add babel-polyfill して埋め込んでいたが、その後 yarn start が遅くなったような気がしたので、CDNからの読み込みに修正した。

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.2.5/polyfill.min.js"></script>

経緯

  • 先日作ったPWAである BlogFeedbackGoogleにインデックスして欲しかった
    • 一応トップページのtitleとdescriptionは登録されている

f:id:ninjinkun:20190112210513p:plain

  • しかしbodyはインデックスされていない
    • create-react-app でSPAを作っただけは普通こうなると思われる
    • 大半のページはユーザー毎のprivateページなので、そんなにSEOをがんばってもうまみはない
    • でも規約とかキャンポリとか、もうちょっとは登録するページもあるし…
  • 職場ではServer Side Renderingが導入されているので、それなりに茨の道であることは知っていた
  • この辺りの話は以下のスライドが参考になります
  • Dynamic Rendering…するほどページ数もない(正直に言うとよくわからんのでやりたくない)のでとりあえずクローラが読めるJSにするぞ

挫折の歴史

とりあえずChrome 41対応したらええんやろ

  • "browserslist": ["chrome >= 41"] したらできるのでは?
  • pollyfill.io使ったらできるのでは?
    • なぜかfetch as googleで読めず…
  • react-app-polyfillIE対応すればChrome 41でも動くのでは?
    • なぜかfetch as googleで読めず…
  • 俺が悪かった import 'babel-polyfill' するわ
    • 読めた!
    • JSの容量は増えるが、まあ個人サービスなので目をつぶろう(11KB+だった)

f:id:ninjinkun:20190113101206p:plain