tag:crieit.net,2005:https://crieit.net/tags/Firebase/feed 「Firebase」の記事 - Crieit Crieitでタグ「Firebase」に投稿された最近の記事 2022-08-31T09:39:51+09:00 https://crieit.net/tags/Firebase/feed tag:crieit.net,2005:PublicArticle/18288 2022-08-31T09:35:31+09:00 2022-08-31T09:39:51+09:00 https://crieit.net/posts/Web-ankeyto 【個人開発】作るのも、答えるのも簡単なWebアンケート「ankeyto」を作りました! <h1 id="2つ目の個人開発サービス公開"><a href="#2%E3%81%A4%E7%9B%AE%E3%81%AE%E5%80%8B%E4%BA%BA%E9%96%8B%E7%99%BA%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E5%85%AC%E9%96%8B">2つ目の個人開発サービス公開</a></h1> <p>アプリケーションのモダン化を勉強しようと個人開発を始めて、以下の記事のように公開していました。もう振り返ると1年経つんですね… こういった個人開発活動を続けることにより、少しずつ知識も身についてきて、開発スピードも上がってきていました。</p> <ul> <li><p>Github Issuesをキレイに外部公開するサービス「2go」作ってみた</p> <ul> <li>https://qiita.com/nice2have/items/28449ae4ef45fef2c671</li> </ul></li> <li><p>海外進出を目指して、ProductHuntへ個人開発サービスを投稿するまでにやったこと&やった結果を全面的にシェアする</p> <ul> <li>https://qiita.com/nice2have/items/f59a27c266efb9b8821c</li> </ul></li> </ul> <p>実際には公開していないのですが、atodyという「Twitterでいいねしたツイートに含まれるリンク・画像だけを自動でまとめてブックマーク化できるサービス」を開発したのですが、いまいちサービスの完成度に納得がいっていなく、開発したサービスとしては3つとなったのですが、公開したサービスは2つとなります。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997184/3f2dbeed-c2cb-8437-164e-78a8797ea9df.png" alt="ogp.png" /></p> <h1 id="簡単Webアンケート"><a href="#%E7%B0%A1%E5%8D%98Web%E3%82%A2%E3%83%B3%E3%82%B1%E3%83%BC%E3%83%88">簡単Webアンケート</a></h1> <p>今回は会社の一定の範囲で簡単にアンケートを取りたいときに、Google FormsやMicrosoft Formsを作るのは手間だな、もっとライトな感じで反応を知りたいなと思ったのがきっかけです。</p> <p>また、アンケートの回答を得るまでのハードルが結構高いと思ったんです。アンケートURLにアクセスして、回答を入力して、、、となると、答えてくれる人が少なくなってしまって、アンケートの価値が薄まってしまうような気がしたんですよね。</p> <h1 id="機能を最小限に絞ったアンケートをリリースしよう"><a href="#%E6%A9%9F%E8%83%BD%E3%82%92%E6%9C%80%E5%B0%8F%E9%99%90%E3%81%AB%E7%B5%9E%E3%81%A3%E3%81%9F%E3%82%A2%E3%83%B3%E3%82%B1%E3%83%BC%E3%83%88%E3%82%92%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%97%E3%82%88%E3%81%86">機能を最小限に絞ったアンケートをリリースしよう</a></h1> <p>これらの課題を解決しようと、ankeytoというアンケート機能を最小限に絞ったサービスを開発しようということに至りました。ankeytoはアンケートのモジりで「アンキート」と名付けることにしました。ちなみに、先程のatodyも「あとで」のモジりで「アトディ」と名付けています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://ankeyto.com">https://ankeyto.com</a></p> <p>開発する前の大まかなコンセプトは、A3ノートにまとめる用にしているのですが、汚い字で申し訳ないですが、公開してみます。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997184/8871aa10-08b9-76f6-447b-42611f8a02a4.jpeg" alt="IMG_1337.jpg" /><br /> サービスのコンセプトは以下の2つと設定しました。<br /> + 最大3つまでの選択肢<br /> + 選択肢ごとのURLにアクセスすればもう回答済みになる<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997184/ec5dcfde-7952-6f6b-2aa7-679b603afbd2.png" alt="image.png" /><br /> そのため、選択肢を表示して回答ができるページをSNSでシェアするのではなく、アクセスしたら回答できるURLをSNSでシェアするだけで良くなるのです。例えば、「これが良いと思ったら、このURLにアクセスして!」と言えば良くなり、アンケート回答のステップとハードルを減らすことができるのです。つまり、個人個人で対象への「いいね」を調査・獲得できるイメージです。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997184/29d7fd65-e416-566f-3b3c-9345e146f755.png" alt="image.png" /></p> <h1 id="画面イメージ"><a href="#%E7%94%BB%E9%9D%A2%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8">画面イメージ</a></h1> <p>こちらのようにアンケート回答がリアルタイムにグラフやUIに反映されるので、回答が多いと変化を楽しめるかもしれません。例えば、プレゼン最中の意見を軽く聞きたいときや、勉強会のフィードバックを簡単に得たいときなどに利用できるかもしれません。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997184/12cb36a5-f89b-8aad-dded-dffcc5f7c223.gif" alt="demo.gif" /></p> <p>アンケートを作るときの画面も極力シンプルにしています。こちらもログインが必要だと手間が増えてしまったり、入力項目数が多いと途中で離脱してしまう可能性が高くなってしまうので、最小限にすることを意識しています。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997184/737aa90d-06ef-4ebc-a439-1dcfc31e9e66.png" alt="image.png" /></p> <h1 id="利用技術"><a href="#%E5%88%A9%E7%94%A8%E6%8A%80%E8%A1%93">利用技術</a></h1> <p>個人開発者としてはまだまだペーペーなので、利用技術も運用負荷が最小限になることを意識しています。<br /> - Firebase(Firestore/Functions/Hosting)<br /> - Vue.js<br /> - Tailwindcss<br /> - Slack</p> <p>サービス自体がとてもシンプルなので、HostingでWebページを表示し、質問作成や回答をFunctionsで処理し、結果をFirestoreでリアルタイムに反映しているだけです。一応、モチベーション維持のために、質問が作成されたり、回答されたりしたら、FuncitonsからSlackに通知されるようにしています。</p> <h1 id="将来に向けて"><a href="#%E5%B0%86%E6%9D%A5%E3%81%AB%E5%90%91%E3%81%91%E3%81%A6">将来に向けて</a></h1> <p>最後に、将来は以下のロードマップを考えています。<br /> - URLにアクセスするよりもさらにハードルを下げるために、URLをQRコード化できるように。<br /> - Cookie等を利用した重複回答者の防止機能<br /> - 質問IDをランダムでなく、わかりやすいIDを提供できる機能<br /> - API機能(UIだけでなく、いいねAPIを提供)<br /> - 英語対応</p> <h1 id="最後に。"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB%E3%80%82">最後に。</a></h1> <p>まさにこのankeytoを利用して、このWebサービスってどうですか?とアンケートを取って終われればと思います。以下が質問ページから、SNS共有用にコピーできる文章になります。「いい!」か「まあまあ」のURLにアクセスすれば回答が完了しますので、ぜひよろしくお願いします!</p> <blockquote> <p>Q. このWebサービスってどうですか?<br /> https://ankeyto.com/q/BbDJA3P11</p> <p>いい! : https://ankeyto.com/BbDJA3P11/1<br /> まあまあ : https://ankeyto.com/BbDJA3P11/2</p> </blockquote> <p>また、個人開発の進捗などを垂れ流すTwitterもやっていますので、こちらもよろしければフォローお願いします。<br /> <a target="_blank" rel="nofollow noopener" href="https://twitter.com/jnakajima1982">https://twitter.com/jnakajima1982</a></p> jnakajima1982 tag:crieit.net,2005:PublicArticle/18244 2022-07-17T11:29:34+09:00 2022-07-17T11:29:34+09:00 https://crieit.net/posts/Windows-nvm-node-firebase-tools-Unexpected-token Windowsのnvmのnodeでfirebase-toolsを入れようとしたらUnexpected token '.' エラー <p><code>npm install -g firebase-tools</code> を実行してインストールしようとしたら下記のようなエラーが出た。</p> <pre><code class="plain">npm ERR! Unexpected token '.' </code></pre> <p>調べてみたらどうもWindowsのnvmのバージョン1.1.7以下を使っている場合に発生してしまうらしい。</p> <p>ということで</p> <ol> <li>nvm最新をインストール</li> <li>Nodeの該当バージョンをアンインストール</li> <li>Nodeの該当バージョンを再度インストール</li> </ol> <p>で解決するとのこと。nvmだけでなくNodeも入れ直さないといけないっぽい。</p> <p>参考)<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/firebase/firebase-tools/issues/4134">https://github.com/firebase/firebase-tools/issues/4134</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/17765 2021-11-17T09:36:55+09:00 2021-11-17T09:36:55+09:00 https://crieit.net/posts/102949946b0e6197f103d453a781a1fc 数日で有料サービスをリリースしてみた話 <p>有料サービスを2,3日位で作ってリリースしてみました。多分計10時間ほど? 急にぱっと思いついて次の日の夜くらいにはだいたい完成していました。作ったのは下記のHand Refactorerというプログラムのコードを手動的にリファクタリングしてくれるサービスです。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">130円でプログラムのコードを手動的にリファクタリングしてくれるサービスをリリースしました。プログラミングを始めたばかりの方にはもしかしたら役立つ場合もあるかもです。よろしければお試しください!!<a target="_blank" rel="nofollow noopener" href="https://t.co/bHlNXJFbTh">https://t.co/bHlNXJFbTh</a> <a target="_blank" rel="nofollow noopener" href="https://t.co/VhPI80a6Cl">pic.twitter.com/VhPI80a6Cl</a></p>— だら@Flutterもやってる (@dala00) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/dala00/status/1459816338050859008?ref_src=twsrc%5Etfw">November 14, 2021</a></blockquote> <p>ちなみにこれ自体は単に自動的じゃなくて手動かよというツッコミがほしいだけのために作ったネタクソアプリです。</p> <p>色々やるべきことを削ったりなどで考えたりしたので書いておきます。</p> <h2 id="サービスの流れ"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AE%E6%B5%81%E3%82%8C">サービスの流れ</a></h2> <p>そもそもサービスの流れですが、まずユーザーがリファクタリングしてほしいコードを入力して登録します。</p> <p>それを僕が人力でリファクタリングし、回答として登録するとユーザーがそれを確認できる、という形です。</p> <h2 id="課金関連の開発を極限まで削る"><a href="#%E8%AA%B2%E9%87%91%E9%96%A2%E9%80%A3%E3%81%AE%E9%96%8B%E7%99%BA%E3%82%92%E6%A5%B5%E9%99%90%E3%81%BE%E3%81%A7%E5%89%8A%E3%82%8B">課金関連の開発を極限まで削る</a></h2> <p>課金となると、結構色々考えなければなりません。ユーザー登録し、そのユーザーに対して課金ログを保存し、購入履歴などを用意する必要があります。ただそこまでするとどうしても工数がかかってしまいます。そのため下記のようにして開発事項を削りました。</p> <h3 id="オーソリを使う"><a href="#%E3%82%AA%E3%83%BC%E3%82%BD%E3%83%AA%E3%82%92%E4%BD%BF%E3%81%86">オーソリを使う</a></h3> <p>オーソリというのは、仮売上です。即課金確定するのではなく、注文が確定した時にキャプチャという処理を行って決済を確定させる方法です。ユーザーが注文してきた時に仮売上とし、僕が処理を行って納品でき、ユーザーがそれを意図的に閲覧した場合に納品確定としキャプチャを行うようにしました。</p> <p>この方法には色々メリットがありました。</p> <h4 id="ユーザーの心理的不安を取り除ける"><a href="#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E5%BF%83%E7%90%86%E7%9A%84%E4%B8%8D%E5%AE%89%E3%82%92%E5%8F%96%E3%82%8A%E9%99%A4%E3%81%91%E3%82%8B">ユーザーの心理的不安を取り除ける</a></h4> <p>個人サービスですので、そこに課金するのにはちょっと不安があります。ただ、ちゃんと納品が行われないと課金が確定しないということが事前にわかっていればその不安をある程度取り除くことができます。</p> <p>確定する場合には、ユーザーに回答画面のURLが貼られたメールが送られます。ここでも実際に見て課金を確定するかどうかをユーザーが意図的に選ぶ事ができます。興味がなければ見ずに終わらせればいいですし、お金を払ってでも見たいと思っていれば閲覧して確定できます。</p> <h4 id="運営側の不安も取り除ける"><a href="#%E9%81%8B%E5%96%B6%E5%81%B4%E3%81%AE%E4%B8%8D%E5%AE%89%E3%82%82%E5%8F%96%E3%82%8A%E9%99%A4%E3%81%91%E3%82%8B">運営側の不安も取り除ける</a></h4> <p>個人サービスで課金機能を提供するのは、運営者としてもなかなか気持ち的に不安になることが多いです。うまくサービスを提供できなかったらどうしよう、大きなミスに気づかず迷惑をかけてしまったらどうしよう、など。</p> <p>しかしオーソリを利用してユーザーがお金を払って結果を見たいと確信して行動する時に初めて課金が行われるようにすれば、なかなか間違えたり問題が起こる可能性は低くなります。そうなると運営側としても問題が発生しにくく安心です。</p> <p>また、最悪僕が怠けたりなにか急なトラブルで全然手を付けられず放置したとしても一切課金されることはありません。また、そんなにリファクタリングすることがないコードであれば確定しないことでキャンセル扱いにすることもできます。</p> <p>このようにしてお互いの不安をなるべく少なくすることでユーザーは購入を、運営はリリースをしやすい気持ちにできるようにしました。これができなければ色々と複雑なテストをしたり入念にいろいろな機能をつくったりしてなかなかリリースはできなかったでしょう。</p> <h3 id="チェックアウトを使わない"><a href="#%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%82%A2%E3%82%A6%E3%83%88%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84">チェックアウトを使わない</a></h3> <p>Stripeにはチェックアウトというプログラムを書かなくても商品を売れる機能があります。すごく簡単なのですが今回はそれは使いませんでした。というのも売上の結果を知るためにWebhookでチェックする必要があり、ちょっとそれが面倒でした。プログラムで決済してしまえば登録時に一緒にやってしまえばすべてが終わるためそちらの方が処理的にもわかりやすくテストもしやすかったためです。</p> <h2 id="メールアドレスを使う"><a href="#%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86">メールアドレスを使う</a></h2> <p>メールアドレスを使うことでログイン機能を削りました。ユーザー登録をすると、やはり離脱率は上がります。ちょっと投稿して利用するだけのサービスでユーザー登録は面倒くさいです。</p> <p>また、ログイン機能を作るとそれはそれで結構面倒です。Next.js と Firebase を使っているのですが、それで認証を入れるとFirestoreのセキュリティルールも設定しなければなりませんし、その状態だとNext.jsのサーバーサイドレンダリング時にエラーが発生することもあり、結構確認事項が増えます。ログイン機能を作らずメールアドレスのみの照合とすることでそれら全ての開発を削りました。</p> <p>メールアドレスだけでは勝手に決済されて危険なのでは? と思われるかもですが、クレジットカード自体は操作している人自身のものを入れなければならないためなりすましは難しいですし、できたとしても結局オーソリまでしか進めないため、利用者が最後まで進まないとお金は一切動きません。</p> <p>またこれにより結果のURLはメールで本人しか知ることができないため漏洩の心配などもありません。</p> <p>最初はユーザーもメールアドレス登録するのはいやかな…とも思ったのですが、そもそも大量の依頼がくるとそれはそれで僕のスケジュールがパンクしてしまうのである意味抑制になっていいかなと思っています。</p> <h2 id="極限までハードルを下げる"><a href="#%E6%A5%B5%E9%99%90%E3%81%BE%E3%81%A7%E3%83%8F%E3%83%BC%E3%83%89%E3%83%AB%E3%82%92%E4%B8%8B%E3%81%92%E3%82%8B">極限までハードルを下げる</a></h2> <p>このハードルというのはユーザー、運営者両方のことです。</p> <p>まず費用は130円という自販機でジュースを買うのと同じ(?)ような料金にしました。また、説明分にはレジャー感覚で使ってほしいということ、意図した結果が帰ってくるとは限らないということを書いておき、運営者的にもちゃんとした結果をださなければ、という不安をなくし、利用者としてもさほど結果に期待しない状態を作っておきました。</p> <p>ハイクオリティになったり、ハードルの認識の齟齬があると開発もそれに対処するためにちょっとやることや考えることが増えたりなど、すぐにリリースはできません。とにかくすぐに作るレベルのサービスに連動したクオリティの商品をそれに見合った低価格で提供することによってリリース時の負担を下げました。</p> <h2 id="管理機能を公開しない"><a href="#%E7%AE%A1%E7%90%86%E6%A9%9F%E8%83%BD%E3%82%92%E5%85%AC%E9%96%8B%E3%81%97%E3%81%AA%E3%81%84">管理機能を公開しない</a></h2> <p>リファクタリング結果の送信ですが、Firestoreのダッシュボードだけではできません。そのためそこは返答用の画面を作ってあげる必要がありました。</p> <p>しかし前述の通り認証もありませんし、URLを作ってしまうと万が一アクセスされてしまった時に情報が漏洩してしまいます。</p> <p>そのため管理画面は公開せず、別プロジェクトとして作り、ローカルのデバッグ実行だけでできるようにしました。絶対に他の人にアクセスされることもありませんので安全です。また、作っていたプログラムをコピーするところから始めたのでベース部分はまるまる流用でき、特に大変でもありませんでした。</p> <h2 id="Firebaseクライアントを使わず全部APIのみにする"><a href="#Firebase%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%9A%E5%85%A8%E9%83%A8API%E3%81%AE%E3%81%BF%E3%81%AB%E3%81%99%E3%82%8B">Firebaseクライアントを使わず全部APIのみにする</a></h2> <p>前述の通り、セキュリティルールを設定するのが面倒だったためFirebaseのクライアントはアクセス解析にしか使っていません。セキュリティルールは全部のデータへのアクセス不許可にしています。</p> <p>普通に単なるAPIとしてfirebase-adminを利用してサーバーサイドのDBのようにして作っています。なにもかんがえることもなく非常に楽です。勝手に変なデータにアクセスされてしまうこともありません。</p> <p>また、Firestoreを使うことによってデプロイする場所を選ばなくてもよいようにもなっています。</p> <h2 id="Cloud Runにデプロイ"><a href="#Cloud+Run%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">Cloud Runにデプロイ</a></h2> <p>今回はCloud Runにデプロイしました。前述の通りDBはFirestoreなのでどこにでもデプロイできます。本来はVercelが一番楽なのですが、この無料プランは個人的な利用のみに限られますので、今回の場合は利用できません。ということでCloud Runを使いました。GitHubにpushしたのに連動して自動的にデプロイもできますし。Herokuでも良いと思いますが頻繁に利用されるサービスではありませんし、あまりアクセスして遅いのもあれかなと思いCloud Runにしました。</p> <p>せっかくなのでDockerfileを使わないでデプロイできるBuildpackというものを試してみたかったというのがあったのですが、どうもビルド無しで実行されてしまうようでエラーになったので今回は諦めてDockerfileを使うことにしました。だいたいDockerfileならどっかーにあると思いますので。</p> <p>今回は下記を使いました。<br /> <a target="_blank" rel="nofollow noopener" href="https://zenn.dev/kazumax4395/articles/427cc791f6145b">【Node.js/Next.js】Cloud Runで動作する軽量なDockerを構築してみた</a></p> <p>これの真ん中のやつでだいたいデプロイに8分ほどかかります。それほどパフォーマンスが必要ではないので一番最初の簡易的なやつでも良かったのかもしれません。なんにしろVercelよりはやはりどうしても遅くなってしまいますね。</p> <p>あと日本リージョンにしたのでちょっとネットワーク料金がかかります(アクセスがちょっと多ければ月何十円とか100円とか?)</p> <h2 id="CSSも使わない"><a href="#CSS%E3%82%82%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84">CSSも使わない</a></h2> <p>今回はChakra UIを使いました。とにかくデフォルトのデザインパーツで、カスタマイズは全部Chakraコンポーネントのプロパティのみで、ぶわっと作りました。非常に楽です。</p> <h2 id="DIFFの表示"><a href="#DIFF%E3%81%AE%E8%A1%A8%E7%A4%BA">DIFFの表示</a></h2> <p>リファクタリングということで、DIFFを表示したほうがわかりやすいかなと思いました。</p> <p>React Diff Viewerというのを使いました。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/praneshr/react-diff-viewer">https://github.com/praneshr/react-diff-viewer</a></p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>最初に書いたとおりクソアプリですので、ものすごい依頼が増えない限りは特にアップデートする予定はありません(依頼がきたらちゃんと対応します。リファクタリングは結構好きなのでわりと楽しんでやっています)。</p> <p>ただ数日で作れたので、これを機に低価格で同じように自分で提供できることを売るのも面白いかなとは思います。どうしても手がかかるので規模が大きくなるとスケールもしないし限界がありますので、あくまでも今回のようにジョーク交じりかつすごく負担の少ない作業だけになるとは思いますが。</p> <p>逆に言うと、低価格でちょっとなにか提供できる、というひとはたくさんいると思いますので、みんながこういうサービスを各々作れたら面白いのにな、とも作ってる途中に思いました。例えば小説の手直し、絵の手直し、もしくはちょっとした絵を書いてあげたり、サービスの感想を伝えてあげたり、なにか写真をとってあげたり、ロゴをつくってあげたり。皆自分の得意なことを提供して少しでもお金になれば楽しいのでは、と思いました。</p> <p>もちろんそういうモール的なサービスはいくつかあると思いますが、あくまでも自分の個人商店的な感じで運用するのも面白いと思います。</p> <p>自分でみんながそういうことを提供できるサービスを作ればよいのでは、とも一瞬思ったのですが、やはり価格的に運営者に入るお金はかなり薄利になり、儲けを出すにはかなり大量に集客しないといけませんし個人だと逆に大変なことが多そうなので、無理かなと思いました。Skebのもっと安くて気軽バージョン的な感じかもです。</p> <p>なので可能な方は是非同じ様なサービスを作ってなにか提供してみると面白いのではないかと思います。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/17739 2021-11-04T13:27:28+09:00 2021-11-04T13:27:28+09:00 https://crieit.net/posts/QnQ-Firebase QnQ開発日誌 Firebaseで管理者用画面はみんなどうしているのかな <p>相変わらず発起人の友人を含む3人でQnQの開発を続けている。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qnqtree.com/">Search | QnQ</a></p> <p>友人が今細かな使い勝手のブラッシュアップをしてくれているので、僕はチーム制導入にあたって、管理者用のコマンドラインツールを作成している。本当は管理者用の画面を作ろうと思っていたんだけれど、セキュリティルールが面倒臭いことに気づいた。</p> <h2 id="面倒臭い"><a href="#%E9%9D%A2%E5%80%92%E8%87%AD%E3%81%84">面倒臭い</a></h2> <p>このWebサービスはFirebaseを使っている。Firebaseは何かと便利ではあるものの、メンテナンスを考えた時にちょいちょい引っかかる。前にも「一時メンテナンスとかどうやるんだろう」と思ったが、調べてみると「意図的にしばらく落とす」みたいなのはなさそうで、強引なリダイレクト処理を入れている人なんかが引っかかった。</p> <p>今回は管理者用の画面いい加減必要だね、という話をしていて、それ用の画面を作ろうかなと思っていたんだが、そうするとけっこうセキュリティルールが面倒臭い、ということに気づいた。</p> <p>多分、一部のユーザを「管理者」として、セキュリティルールで都度管理者用の穴を開ける、ということになるだろう。別にやってできないことはないが。</p> <p>とはいえ、ただでさえうんざりするようなセキュリティルールの記述がさらにややこしくなるのか、とか、管理者特権持っちゃうと、デバッグを兼ねて触っているのに、エラーに気づけなくなりそうだな、とか、色々思うところがあって、うーん、どうしようかなぁと考えた末に、例によってコマンドラインの管理者ツールをシコシコ作ったのだった。</p> <p>コマンドラインならばAdmin SDKで気にせず書けるし、node.jsじゃなくてPythonでも書ける。入出力を整えてやれば、パイプを使ってそれなりに複雑な操作もできるし。結局こうなるのか。</p> <h2 id="使い勝手の向上"><a href="#%E4%BD%BF%E3%81%84%E5%8B%9D%E6%89%8B%E3%81%AE%E5%90%91%E4%B8%8A">使い勝手の向上</a></h2> <p>後は友人が細かな使い勝手の向上に繋がるところをやってくれているようだ。たとえば通知欄で、スレッドAの#20番とあったとして、これを開くとスレッドAの#20番まで飛ぶんだけれど、これまでスレッドAを開いていると#20番に飛んでくれないという謎の状態になっていた。</p> <p>致命的ではないという感じで放っていたんだけれど、やっぱり不便だった、ということでなおしてくれたんだが、なおってみるとだいぶありがたく思えた。</p> <p>やはりUXはこういう泥臭い改善の積み重ねなんだなぁと思う次第。全体最適を見失った局所最適は問題だけれど、全体ばかり見て細部を疎かにするのもやはりよくない。</p> <p>最近「サービスが終わるソシャゲ」をあえていじったりしてみたが、共通して「ゲームのシステム部分がまどろっこしい」というのがあった。同じ作業を嫌がらせのように繰り返させられる、みたいな。できないことはないけど、面倒、という感じ。</p> <p>今のこのサービスもそういう状態だよなぁ、と反省する。僕は過去「そもそも方向性おかしい」問題に振り回され続けた苦い経験があって、全体最適の重要性については身に沁みているのだが、一方で局所最適の重要性を過小に評価しているのかもしれない。</p> <p>全体最適と局所最適のバランスは難しく、きっと永遠の課題なのだろうけれど、自分なりの距離感を見つけていきたいものだ。</p> tama tag:crieit.net,2005:PublicArticle/17698 2021-10-05T17:05:54+09:00 2021-10-05T17:05:54+09:00 https://crieit.net/posts/Firebase-SSSAPI-Web FirebaseとSSSAPIでプレスリリースを送ったサイトを記録できるWebサービスを作ってみた <p>開発しているGoogleスプレッドシートのAPI化サービス「<a target="_blank" rel="nofollow noopener" href="https://sssapi.app">SSSAPI</a>」をβ版リリースしたときに、<br /> プレスリリースをいろんなメディアに送ってみてた。</p> <p>せっかく調べてまとめたし、次も使う機会があるだろうと思ったので、<br /> Nuxt/Firebase/SSSAPIの構成でWebサービス化してみた。</p> <p>プレスリリースを受け付けてるサイトを調べるのは大変なので、<br /> どこに送れるのかな〜と思った方は使ってみてもらえるとうれしいです(<em>´ω`</em>)</p> <h3 id="作ったサービス"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">作ったサービス</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://press-done.memory-lovers.com">https://press-done.memory-lovers.com</a></p> <p>送付するプレスリリースごとに、どのサイトへ送ったかの状態管理ができます。<br /> それだけのシンプルなWebサービス。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/b52d95a8-788f-5625-c536-4ecd6cc147b5.png" alt="スクリーンショット 2021-10-04 23.35.26.png" /></p> <h3 id="システム構成"><a href="#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E6%A7%8B%E6%88%90">システム構成</a></h3> <p>基本構成はこんな感じ。</p> <ul> <li>フロントエンド: Nuxt+Buefy</li> <li>認証: Firebase Authentication</li> <li>ユーザデータ: Firestore</li> <li>マスターデータ: <a target="_blank" rel="nofollow noopener" href="https://sssapi.app">SSSAPI</a></li> </ul> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/d9f069c4-7d32-5ea8-aea5-91300fea7030.png" alt="スクリーンショット 2021-10-04 23.40.41.png" /></p> <p>ユーザが更新しないマスターデータはSSSAPIを利用している感じ。</p> <p>メディアサイトのデータをFirestoreに入れておく形もあるけど、<br /> Firebaseコンソールで追加や編集するより、<br /> スプレッドシートを更新するほうが楽なので、この形にしている。</p> <p>スプレッドシートの中身はこんな感じ。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/e130e233-915d-e105-0e75-48249f002a13.png" alt="スクリーンショット 2021-10-04 23.43.50.png" /></p> <p>SSSAPIはこんなJSONを返してくれる。</p> <pre><code class="json">[ { "id": 1, "サイト名": "TechCrunch Japan", "サイトURL": "https://jp.techcrunch.com", "送付方法": "メール", "isActive": true }, { "id": 2, "サイト名": "日経クロステック", "サイトURL": "https://xtech.nikkei.com", "送付方法": "メール", "isActive": true }, { "id": 3, "サイト名": "ITmedia", "サイトURL": "https://www.itmedia.co.jp/", "送付方法": "メール", "isActive": true }, ] </code></pre> <p>※「行数をIDとして追加」オプションを使ってるので、idが自動で付与されています。</p> <p>このスプレッドシートは登録依頼のGoogleフォームと連動しているので、<br /> 追加依頼があったら内容を確認して<code>isActive</code>を<code>true</code>にすれば反映されます。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/39ce3a05-cd7b-2ed8-81e1-af76025e6175.png" alt="スクリーンショット 2021-10-04 23.56.34.png" /></p> <p>こういった構成は割と多く、本の貸し出し管理と備品管理とかでも使える。<br /> Webサービスからスプレッドシートを編集できないので、<br /> 管理者権限とか不要なので、その分フロント側も楽に作れた(<em>´ω`</em>)</p> <p>プレスリリースってどこに送れるのかな〜と思った方は、<br /> ぜひ使ってみてもらえるとうれしいです(<em>´ω`</em>)</p> <p><a target="_blank" rel="nofollow noopener" href="https://press-done.memory-lovers.com">https://press-done.memory-lovers.com</a></p> <h3 id="SSSAPIはβ版ユーザを募集しています!!"><a href="#SSSAPI%E3%81%AF%CE%B2%E7%89%88%E3%83%A6%E3%83%BC%E3%82%B6%E3%82%92%E5%8B%9F%E9%9B%86%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%21%21">SSSAPIはβ版ユーザを募集しています!!</a></h3> <p>β期間中はプレミアムプランの内容を無料で使えるのでお得!<br /> ぜひぜひ、この機会にお試しいただければ!</p> <p>わからないところやこんなのあったらいいなぁ〜などあれば、<br /> Twitterやお問い合わせなどからお気軽にご連絡ください〜</p> <p>GoogleスプレッドシートのAPI化サービス<br /> 『<a target="_blank" rel="nofollow noopener" href="https://sssapi.app">SSSAPI</a>』<br /> Twitter: <a target="_blank" rel="nofollow noopener" href="https://twitter.com/sssapi_app">@sssapi_app</a></p> <p><a target="_blank" rel="nofollow noopener" href="https://sssapi.app">https://sssapi.app</a></p> <p>開発の励みや記事を書くモチベにもなるので、<br /> いいねやシェア、はてブなどしてもらえるとかなりよろこびます!!</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/17546 2021-07-30T02:39:33+09:00 2021-07-30T02:39:33+09:00 https://crieit.net/posts/QnQ QnQ開発日誌 単体テストなしで大丈夫か? <p>デプロイしたらサインインしていない状態でサイトが見られなくなった、QnQの開発日誌ですこんにちは。今は修正している。</p> <p>Firebaseのセキュリティルールをミスっていたせいなんだが、Seleniumを使ったUIテストしかなく、匿名ユーザーの挙動がテストケースから漏れていたために気づかなかった。</p> <h2 id="単体テストがない"><a href="#%E5%8D%98%E4%BD%93%E3%83%86%E3%82%B9%E3%83%88%E3%81%8C%E3%81%AA%E3%81%84">単体テストがない</a></h2> <p>この程度のことは単体テストでやるのが良いのだろうと思うのだが、開発メンバーで真面目にフロントエンドをやってきた者がおらず、なかなかハードルが高い。まずVue.jsという時点で経験がないのに、そのうえFirebaseが絡むので、どうにも手を付けられていない。せめて、新しい知識がどちらか片方ならまだなんとかなったと思うのだが……。</p> <h2 id="UIテストだけ"><a href="#UI%E3%83%86%E3%82%B9%E3%83%88%E3%81%A0%E3%81%91">UIテストだけ</a></h2> <p>そんなわけで、せめてこれくらいは……とSelenium IDEを使ってUIテストだけなんとか実行している、というのが現状である。</p> <p>正直これがあるだけでも実は頑張った。さすがに何のテストもないのはまず過ぎる、と思って作ったのだが、大きな修正のたびに思いもよらないところでテストで引っかかってくれているので、実際役に立っている。</p> <p>ランスルーテストを流すと普通に数分かかるが、まぁその間はコーヒーでも入れて飲めばいいのだ。</p> <h2 id="単体テスト入れたいというより"><a href="#%E5%8D%98%E4%BD%93%E3%83%86%E3%82%B9%E3%83%88%E5%85%A5%E3%82%8C%E3%81%9F%E3%81%84%E3%81%A8%E3%81%84%E3%81%86%E3%82%88%E3%82%8A">単体テスト入れたいというより</a></h2> <p>しかしまぁ、さすがにそろそろ単体テスト入れなあかんよなぁと思うのだが、それにしてもハードルが高い……。</p> <p>というかコードがスパゲッティで非常に汚いし、そもそも抜本的にこのUIどうなの?と非常に思うところあるので、ぶっちゃけフルスクラッチで最初から書き直したほうがいいのかもしれない、とか思う。</p> <h2 id="ダイエットとバグ修正の話を一緒にできる、そうQnQならね"><a href="#%E3%83%80%E3%82%A4%E3%82%A8%E3%83%83%E3%83%88%E3%81%A8%E3%83%90%E3%82%B0%E4%BF%AE%E6%AD%A3%E3%81%AE%E8%A9%B1%E3%82%92%E4%B8%80%E7%B7%92%E3%81%AB%E3%81%A7%E3%81%8D%E3%82%8B%E3%80%81%E3%81%9D%E3%81%86QnQ%E3%81%AA%E3%82%89%E3%81%AD">ダイエットとバグ修正の話を一緒にできる、そうQnQならね</a></h2> <p>ちなみにこのバグ修正は、友人とWebサービス上でやりとりしていた。GitHubでもやりとりしたけど。<strong>俺の日記帳状態だったが、再び友人との交換日記状態に復帰している</strong>。やっぱり複数で使うと楽しい。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qnqtree.com/tree/O65tm6rVyeu0rLPVuXTo">もくもく22 2021-07-28 | QnQ</a></p> <p>で、上記のスレなのだが、このサービスの特徴が出ている。</p> <p><img src="https://hack-le.com/wp-content/uploads/2021/07/9bf24c3324495cb8d1a1835f2e053603.png" alt="" /></p> <p>上図のように、<strong>「ダイエットの話」と「バグ修正」の話を並行している</strong>。こういう体験は他ではないはずで、自分たちでは「面白いと思うんだけどなぁ」と言いつつ、閑古鳥が鳴いている僕らのサービスである。</p> <p>なんか面白そうだなと思ったら、覗いてやってください。</p> tama tag:crieit.net,2005:PublicArticle/15924 2020-06-07T19:47:07+09:00 2020-06-07T20:04:24+09:00 https://crieit.net/posts/d18d57017a47b6f0774f4e7cbcea2b94 週末の時間を使って「みんなのきょうしつ」をアップデートしました。 <p>promitsuです。</p> <p>今週の土日の空いた時間を使って、先日リリースした「みんなのきょうしつ」というウェブサービスのアップデート作業を行いました。</p> <p><a href="https://crieit.now.sh/upload_images/8ae825a52f93537d282260b5dff649f25edcc4d28a7d5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8ae825a52f93537d282260b5dff649f25edcc4d28a7d5.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="http://class-room.fun/"><strong>http://class-room.fun/</strong></a></p> <p>このアプリは、リアルタイム同期な共同編集機能により、オンライン上の別々の場所やデバイスから、友達と一緒に好き勝手に教室(という名の座席表)を作成し、その画像をシェアできるサービスです。</p> <p>開発の経緯などについては下記の記事を参照ください。</p> <p><a href="https://crieit.net/posts/620b85ee075fd93545b20ccfb92d3491">https://crieit.net/posts/620b85ee075fd93545b20ccfb92d3491</a></p> <h2 id="アップデート"><a href="#%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88">アップデート</a></h2> <p>先日のリリースは、複数人でリアルタイムに「教室」(座席表)を作成できるという目的のもとに、最低限の機能のみに絞った仕様になっていました。その後、友人などに使ってもらい、フィードバックをもらった内容について、以下のような機能追加や修正などを行いました。</p> <p>※ 前提としてこのサービスはNuxtjs(Vuejs)とFirebaseを使って開発・運用されています。</p> <h2 id="主な内容"><a href="#%E4%B8%BB%E3%81%AA%E5%86%85%E5%AE%B9">主な内容</a></h2> <h3 id="ステータスのタイプライター表示"><a href="#%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%81%AE%E3%82%BF%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%82%BF%E3%83%BC%E8%A1%A8%E7%A4%BA">ステータスのタイプライター表示</a></h3> <p>黒板に表示される生徒のつぶやきをタイプライター風に1文字ずつ表示するようにしました。<br /> 普通に文字列を配列化して、一定間隔おきにv-modelの配列にpushする感じでJavascriptで実装しました。</p> <h3 id="教室ページのデザイン・レイアウト修正"><a href="#%E6%95%99%E5%AE%A4%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%BB%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E4%BF%AE%E6%AD%A3">教室ページのデザイン・レイアウト修正</a></h3> <p>VuejsもFirebase(firestore)もまともに使ったことがなかった3月ぐらいに一日間の突貫作業で作った時のままだったので、HTML/CSSの構造から、Vueコンポーネントの構成などもリファクタリングしました。</p> <h3 id="いいね機能"><a href="#%E3%81%84%E3%81%84%E3%81%AD%E6%A9%9F%E8%83%BD">いいね機能</a></h3> <p>現状ユーザー認証がないので、SessionStorageに自分がいいねした教室の情報を保持し、多重いいねを防ぐようにしました。<br /> 実装にはvuex-persistedstateを使っています。<br /> これ以外のデータの読み込みなどはvuexfireを使って、vuexのstoreとfirestoreをシームレスにつないでいるので、moduleとして分けてindex.jsから読み込む構成にしています。</p> <pre><code>```jsx store/ | +-- index.js | +-- like.js ``` </code></pre> <pre><code class="jsx">store/index.js import like from './like' import localStorage from 'vuex-persistedstate' ... export const plugins = [ localStorage({ key: 'like', paths: ['like'], storage: window.sessionStorage }) ] </code></pre> <p>vuex-persistedstate<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/robinvdvleuten/vuex-persistedstate">https://github.com/robinvdvleuten/vuex-persistedstate</a><br /> vuex-persistedstate<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/posva/vuexfire">https://github.com/posva/vuexfire</a></p> <h3 id="公開教室のトップページでのリスト表示"><a href="#%E5%85%AC%E9%96%8B%E6%95%99%E5%AE%A4%E3%81%AE%E3%83%88%E3%83%83%E3%83%97%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%A7%E3%81%AE%E3%83%AA%E3%82%B9%E3%83%88%E8%A1%A8%E7%A4%BA">公開教室のトップページでのリスト表示</a></h3> <p>これがないと他にどんな教室があるかわからんし、アクセスもできないという指摘。ごもっともですね😓</p> <h3 id="座席削除"><a href="#%E5%BA%A7%E5%B8%AD%E5%89%8A%E9%99%A4">座席削除</a></h3> <p>作成・編集ができるのに、削除できないとは何事か!?というご指摘。さーせん。</p> <h3 id="公開・非公開設定"><a href="#%E5%85%AC%E9%96%8B%E3%83%BB%E9%9D%9E%E5%85%AC%E9%96%8B%E8%A8%AD%E5%AE%9A">公開・非公開設定</a></h3> <p>トップページにリストが表示される様になったので、他の人にアクセスしてほしくない人は非公開設定にできるようにしました。秘密の言葉による簡易認証を入れようかとも思ったのですが、ユーザーから見た複雑度が増す気がしたので一旦ペンディングにしました。</p> <h3 id="教室背景変更機能"><a href="#%E6%95%99%E5%AE%A4%E8%83%8C%E6%99%AF%E5%A4%89%E6%9B%B4%E6%A9%9F%E8%83%BD">教室背景変更機能</a></h3> <p>だだっ白い無味乾燥な背景が寂しかったので、教室の背景画像を変更できるようにしました。アップロード時の画像リサイズ・クロップなどはまだ実装できていないのでIssue積んどきました。</p> <p><a href="https://crieit.now.sh/upload_images/9dbfdbf890225ceaff9757a8e6fe561f5edcc4accecfa.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9dbfdbf890225ceaff9757a8e6fe561f5edcc4accecfa.png?mw=700" alt="image" /></a></p> <h3 id="ドラッグ&amp;ドロップ編集機能"><a href="#%E3%83%89%E3%83%A9%E3%83%83%E3%82%B0%26amp%3B%E3%83%89%E3%83%AD%E3%83%83%E3%83%97%E7%B7%A8%E9%9B%86%E6%A9%9F%E8%83%BD">ドラッグ&ドロップ編集機能</a></h3> <p>vue-draggableを使って実装しました。(Vuejsは便利なコンポーネント・ライブラリが充実してるので、個人開発者にはとても嬉しい😭)<br /> もちろん編集結果はリアルタイムに他のデバイスに反映されるようになっています。(挙動をもうちょっとかっこよくしたい😓)</p> <h4 id="動作ムービー"><a href="#%E5%8B%95%E4%BD%9C%E3%83%A0%E3%83%BC%E3%83%93%E3%83%BC">動作ムービー</a></h4> <p><a target="_blank" rel="nofollow noopener" href="https://www.youtube.com/watch?v=2RCYmpFSae8">https://www.youtube.com/watch?v=2RCYmpFSae8</a></p> <p>vue-draggable<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/SortableJS/Vue.Draggable">https://github.com/SortableJS/Vue.Draggable</a></p> <h3 id="その他の修正(レイアウト変更、バグフィックス等)"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E4%BF%AE%E6%AD%A3%EF%BC%88%E3%83%AC%E3%82%A4%E3%82%A2%E3%82%A6%E3%83%88%E5%A4%89%E6%9B%B4%E3%80%81%E3%83%90%E3%82%B0%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E3%82%B9%E7%AD%89%EF%BC%89">その他の修正(レイアウト変更、バグフィックス等)</a></h3> <p>背面黒板を廃止して、全面の黒板につぶやきを表示するようにしたり、その他微妙なデザイン・レイアウト調整、及びバグ修正を行いました。</p> <hr /> <p>まだまだバグも多いし、実装したい機能や修正したいUX、インタラクションなどもたくさんありますが、地道に少しずつ良いプロダクトにしていこうと思います。</p> <p>ではまた。</p> ぷろみつ tag:crieit.net,2005:PublicArticle/15923 2020-06-05T22:29:21+09:00 2020-06-05T22:31:45+09:00 https://crieit.net/posts/620b85ee075fd93545b20ccfb92d3491 自分の理想の教室を、友達とリアルタイムで一緒に作れるウェブサービス「みんなのきょうしつ」をリリースしました。 <p>さっき投稿した記事で、4日前に「musico」というウェブアプリをリリースしたことについて書きましたが、</p> <p>好きな楽曲について語り合うウェブサービス「musico」を作ってみました。<br /> <a href="https://crieit.net/posts/musico">https://crieit.net/posts/musico</a><br /> <a target="_blank" rel="nofollow noopener" href="https://musi-co.fun">musico | find track you like and talk about it</a></p> <p>実は昨日、もう一つウェブアプリをリリースしましたw<br /> その名も「みんなのきょうしつ」。</p> <p><a href="https://crieit.now.sh/upload_images/8ae825a52f93537d282260b5dff649f25eda3eaa27764.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8ae825a52f93537d282260b5dff649f25eda3eaa27764.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://class-room.fun">みんなのきょうしつ</a></p> <h2 id="開発の動機"><a href="#%E9%96%8B%E7%99%BA%E3%81%AE%E5%8B%95%E6%A9%9F">開発の動機</a></h2> <p>このアプリは、もともと3月のコロナ禍による一斉休校をきっかけに、いきなり会えなくなった友達とオンラインで再開して貰い、小中高生のみなさんに、社会の混乱からのストレスを一時的にでも忘れてもらえたら良いなという思いで、二日間ぐらいでガッと基本的な機能を作って公開しようとしていたものでした。ですが、その後なかなか着手する時間が取れず、限定的な公開にとどめて、ほぼ動いていない状態が続いていました。</p> <h2 id="開発頓挫、からの突貫リリースへ"><a href="#%E9%96%8B%E7%99%BA%E9%A0%93%E6%8C%AB%E3%80%81%E3%81%8B%E3%82%89%E3%81%AE%E7%AA%81%E8%B2%AB%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%B8">開発頓挫、からの突貫リリースへ</a></h2> <p>そんななか、先日の「musico」の開発と公開をきっかけに、テンションとモチベーションがひさびさに爆上がりしたので、この機を逃す手はないと思い、一念発起して昨日・一昨日をかけて最低限の機能を実装し、公開していみました。</p> <h2 id="どんなアプリ?"><a href="#%E3%81%A9%E3%82%93%E3%81%AA%E3%82%A2%E3%83%97%E3%83%AA%EF%BC%9F">どんなアプリ?</a></h2> <p>このアプリでは、教室を模した座席表的なものに、自分の好きな人やモノの名前と写真を登録していき、自分の理想の教室が作れます。</p> <p><a href="https://crieit.now.sh/upload_images/3cd6bc3848f85dbd1abedf7a1cfeb3cc5eda46c7d816b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3cd6bc3848f85dbd1abedf7a1cfeb3cc5eda46c7d816b.png?mw=700" alt="image" /></a></p> <p>さらに、URLを共有することで、別の場所にいる友だちとオンラインでリアルタイムで共同編集することが出来ます。(動画は開発中に撮影したものです)</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.youtube.com/watch?v=N7qo5popZ3Q">動作ムービー</a></p> <p>また、「記念撮影」機能で作った教室の画像を作成・ダウンロードしたり、各種SNSに手軽にシェアすることが出来ます。(写真は友人が作った「あつまれ どうぶつの森」のキャラクターが集まる教室です)</p> <p><a href="https://crieit.now.sh/upload_images/43103ea48631f57b3237c5df0b282b575eda46f1109e2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/43103ea48631f57b3237c5df0b282b575eda46f1109e2.png?mw=700" alt="image" /></a></p> <p><a target="_blank" rel="nofollow noopener" href="https://class-room.fun/classrooms/8jVBr7suU0In1vbE3Ze8">https://class-room.fun/classrooms/8jVBr7suU0In1vbE3Ze8</a></p> <p>地味にインスタ映えしたりしますw</p> <p><a href="https://crieit.now.sh/upload_images/1780742f62e146dcfd91693883f4363d5eda47b04c5d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1780742f62e146dcfd91693883f4363d5eda47b04c5d4.png?mw=700" alt="image" /></a></p> <h3 id="技術的なこと"><a href="#%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AA%E3%81%93%E3%81%A8">技術的なこと</a></h3> <p>技術スタック的には「musico」と同じくFirebase+Vuejs(Nuxtjs)という、構成です。「musico」はUniversalモード(SSR)で作りましたが、こっちはSPAとして開発し、OGP取得部分のみCloud Functionsで作りました。</p> <p>しかし、Firebaseめっちゃ便利ですね〜。</p> <p>こっちもあんまり凝ったことはしていないのですが、OGPの動的生成あたりとか、記念写真機能あたりについて、あとで技術的なことを書こうかなと思っています。</p> <p>それではまた。</p> ぷろみつ tag:crieit.net,2005:PublicArticle/15920 2020-06-05T19:26:08+09:00 2020-06-05T19:26:08+09:00 https://crieit.net/posts/musico 好きな楽曲について語り合うウェブサービス「musico」を作ってみました。 <p>突然ですが、先々週に行われたweb1weekという1週間でウェブアプリを作って公開するオンラインハッカソンに参加しました。</p> <p><a href="https://crieit.net/boards/web1week-202005">(5/18~5/24)1週間でWebサービスを作るイベント - お題「Like」</a></p> <p>と、言いたいところだったのですが、実は2日目以降、家庭内のインシデント対応やそれに起因する自身の体調不良で、全く開発を進めることができませんでした(汗)</p> <p>昨日あたりから、体調含めて状況が落ち着いてきたので、遅ればせながら開発を進め、ガっと最低限の所まで作り込み、勢いでドメイン取って公開してみました。</p> <p>以下、今回開発したウェブアプリについて。</p> <p>お題は「Like」でしたので、好きな楽曲についてあーだこーだ語り合えるサービスを作ってみました。</p> <p>「<a target="_blank" rel="nofollow noopener" href="https://musi-co.fun">musico | find track you like and talk about it</a>」(何故か英語w)</p> <p><a href="https://crieit.now.sh/upload_images/cbe4c4b65476b6a84552bce4ea4da34f5eda1a8c5fd28.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cbe4c4b65476b6a84552bce4ea4da34f5eda1a8c5fd28.png?mw=700" alt="画面" /></a></p> <p>フリーワードで楽曲を検索して、その楽曲にLikeやコメントをすることができます。</p> <p>現時点での機能は以下、</p> <h3 id="未ログイン時"><a href="#%E6%9C%AA%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E6%99%82">未ログイン時</a></h3> <ul> <li>楽曲を検索、またはコメントがある楽曲リストを閲覧できる</li> <li>楽曲のコメントページを閲覧できる</li> <li>楽曲のプレビュー再生ができる(ファイルがあるもののみ)</li> <li>Spotifyで楽曲を聞くことができる(別タブでウェブ版を表示)</li> </ul> <h3 id="ログイン時"><a href="#%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E6%99%82">ログイン時</a></h3> <p>上記に追加して、<br /> - 楽曲にコメントできる<br /> - 楽曲にLikeできる</p> <h4 id="動作ムービー"><a href="#%E5%8B%95%E4%BD%9C%E3%83%A0%E3%83%BC%E3%83%93%E3%83%BC">動作ムービー</a></h4> <p><a target="_blank" rel="nofollow noopener" href="https://youtu.be/sDVK6O61_S0">https://youtu.be/sDVK6O61_S0</a></p> <p>各楽曲、コメントごとのページをSNSにシェアすることも出来ます。</p> <p><a href="https://crieit.now.sh/upload_images/f3869d86a81591bf66abf5f407661d895eda1b78e57e3.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f3869d86a81591bf66abf5f407661d895eda1b78e57e3.png?mw=700" alt="image" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://musi-co.fun/tracks/6K53GM9W6Vle5KBwGFVnZM">https://musi-co.fun/tracks/6K53GM9W6Vle5KBwGFVnZM</a></p> <p>開発時の様々や動機はなどは下記のハッカソンのボードに書きました。<br /> <a href="https://crieit.net/boards/web1week-202005/musico">https://crieit.net/boards/web1week-202005/musico</a></p> <h2 id="技術的なこと"><a href="#%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AA%E3%81%93%E3%81%A8">技術的なこと</a></h2> <p>技術スタック的にはFirebase+Vuejs(Nuxtjs)という、マネージドなサーバレス環境とコンポーネント思考なリアクティブフレームワークを組み合わせたJAMstackな流行りの構成です。</p> <p>最初はホスティングにNetlifyを選択したのですが、SSR周りやFirestoreなど他に使ってるサービスとの兼ね合いも考えて、Firebase全乗っかりな構成にしました。</p> <p>UIは最近良く使ってるBulmaのコンポーネントをほぼほぼそのまま使ってる感じです。</p> <p>本業ではAWSがっちり使って、コンテナとかサーバレスとかマネージドサービスを組み合わせたサーバサイド寄りの開発が多いので、個人開発ではフロントエンド寄りの技術を選定しがちですね(汗)</p> <p>あんまり凝ったことはしていないのですが、開発時に得た知見は今後すこしずつ書いていこうと思っています。</p> <p>それではまた。</p> <hr /> <h3 id="PS."><a href="#PS.">PS.</a></h3> <p>祝Crieit初投稿!!</p> ぷろみつ tag:crieit.net,2005:PublicArticle/15909 2020-05-24T17:18:35+09:00 2020-05-24T17:18:35+09:00 https://crieit.net/posts/Nuxt-Firebase-Cloud-Messaging-FCM-Web Nuxt+Firebase Cloud Messaging(FCM)でWebプッシュ通知を送る <p>WebでもFCMが使えるようになったので、試してみたときの備忘録。<br /> これでSafari以外には、通知が送れるようになる(<em>´ω`</em>)</p> <h3 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h3> <p>構成は、@nuxtjs/pwaでPWA化している感じ。</p> <h4 id="コンソール側"><a href="#%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E5%81%B4">コンソール側</a></h4> <p>Settingsでウェブプッシュ証明書を作成して、鍵ペアを取得する。</p> <p><img width="973" alt="スクリーンショット_2020-05-24_17_06_33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/d17f3a0e-e6df-73a7-d57b-a6ff36e7c79f.png"></p> <p>これを、PUBLIC_VAPID_KEYという環境変数に設定しておく。</p> <h4 id="クライアント側"><a href="#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E5%81%B4">クライアント側</a></h4> <h5 id="firebaseの初期化"><a href="#firebase%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96">firebaseの初期化</a></h5> <p><code>~/plugins/firebase.ts</code>というファイルを用意し、firebaseを初期化</p> <pre><code class="typescript">// ~/plugins/firebase.ts import * as firebase from "firebase/app"; import "firebase/auth"; import "firebase/firestore"; import "firebase/messaging"; if (!firebase.apps.length) { // まずは、firebaseの初期化 firebase.initializeApp({ apiKey: process.env.API_KEY, authDomain: process.env.AUTH_DOMAIN, databaseURL: process.env.DATABASE_URL, projectId: process.env.PROJECT_ID, storageBucket: process.env.STORAGE_BUCKET, messagingSenderId: process.env.MESSAGING_SENDER_ID, appId: process.env.APP_ID, measurementId: process.env.MEASUREMENT_ID }); // Push通知をサポートしているかをチェック // サポートしていないと、firebase.messaging()を呼んだときに例外が発生 const isSupported = firebase.messaging.isSupported(); // コンソールで発行した、ウェブプッシュ証明書の鍵ペアを取得 const publicVapidKey = process.env.PUBLIC_VAPID_KEY; if (!!publicVapidKey && process.client && !!isSupported) { // FCMの初期化。鍵ペアを設定する const messaging = firebase.messaging(); messaging.usePublicVapidKey(publicVapidKey); // @nuxtjs/pwaが生成するsw.jsと、 // 後で作成するFCM受信処理用のsw-firebase-messaging.jsを統合するための設定 navigator.serviceWorker .register("/sw.js") .then(registration => messaging.useServiceWorker(registration)) .catch(err => console.error(err)); } } export default firebase; </code></pre> <h5 id="トークンの取得"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%8F%96%E5%BE%97">トークンの取得</a></h5> <p>Push通知を送る際の宛先として、トークンを指定しないといけないので取得。<br /> トークンは端末ごとに取得する必要があるので、注意が必要。<br /> ※PCとスマホだとそれぞれトークンが違う</p> <pre><code class="typescript">import firebase from "~/plugins/firebase"; // トークンの取得 public async getToken(user: User) { const isSupported = firebase.messaging.isSupported(); if (!isSupported) return; const token = await firebase.messaging().getToken(); // firestoreにトークンを保存しておく処理(中身は略) await saveToken(user, token); } </code></pre> <p>Firestoreなどへユーザごとにトークンを保存しておく。</p> <p><code>firebase.messaging().getToken();</code>を呼んだ際に、<br /> 通知の設定が「確認」だと、許可を求めるダイアログが表示される。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/e9976414-8418-09c1-8846-25211a949776.png" alt="スクリーンショット 2020-05-24 16.30.23.png" /></p> <p>これが許可されていないと、トークンも取得できない。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/c21078c8-38d0-04c7-133a-b5402611443d.png" alt="スクリーンショット 2020-05-24 16.30.35.png" /></p> <h5 id="メッセージを受け取ったときの処理"><a href="#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E5%87%A6%E7%90%86">メッセージを受け取ったときの処理</a></h5> <p>メッセージを受信したときに受け取る関数は2つあり、</p> <ul> <li>フォアグラウンド(画面を見ている時) ... onMessage</li> <li>バックグラウンド(画面を見ていない時) ... setBackgroundMessageHandler</li> </ul> <p>・【参考】<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/receive#handle_messages_when_your_web_app_is_in_the_background">JavaScript クライアントでメッセージを受信する  |  Firebase</a></p> <p>今回はバックグラウンドのときに通知を送りたいので、<br /> <code>setBackgroundMessageHandler</code>を設定していく。</p> <p>こんな感じ。</p> <p>ドキュメントを見ると、『<code>firebase-messaging-sw.js</code>というファイル名で作成』と書かれているけど、<br /> その名前にすると、自動で読み込まれてしまう。。</p> <p>開発用と本番用で切り替えたいときなどもあるので、@nuxtjs/pwaが生成するsw.jsと統合できるように、<br /> <code>sw-firebase-messaging.js</code>という名前でファイルを作成しておく。</p> <p>ファイル名を変更したので、上で書いている「firebaseの初期化」の部分で、<br /> <code>messaging.useServiceWorker(registration)</code>を呼んでいる形。</p> <pre><code class="javascript">// ~/static/sw-firebase-messaging.js importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js"); importScripts("https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js"); // Firebaseの初期化 firebase.initializeApp({ apiKey: "...", authDomain: "...", databaseURL: "...", projectId: "...", storageBucket: "...", messagingSenderId: "...", appId: "...", measurementId: "...", }); // [START background_handler] const isSupported = firebase.messaging.isSupported(); if (!!isSupported) { const messaging = firebase.messaging(); // バックグラウンド時の処理 messaging.setBackgroundMessageHandler(function(payload) {  // 受け取ったFCMの内容を取得 const notificationTitle = payload.notification.title; const notificationOptions = { body: payload.notification.body, icon: payload.notification.icon, }; // 通知を作成する return self.registration.showNotification(notificationTitle, notificationOptions); }); } // [END background_handler] </code></pre> <h5 id="nuxt.config.tsでPWA関連の設定をする"><a href="#nuxt.config.ts%E3%81%A7PWA%E9%96%A2%E9%80%A3%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E3%81%99%E3%82%8B">nuxt.config.tsでPWA関連の設定をする</a></h5> <p>作成した<code>sw-firebase-messaging.js</code>を取り込む設定と、<br /> @nuxtjs/pwaが生成するmanifest.jsonに、gcm_sender_idを追加する設定を追加</p> <pre><code class="typescript">import { Configuration } from "@nuxt/types"; const config: Configuration = { // 略 modules: [ "@nuxtjs/pwa", ], workbox: { // sw-firebase-messaging.jsをimportするように追加 importScripts: [ "sw-firebase-messaging.js" ] }, pwa: { manifest: { // manifest.jsonにgcm_sender_idを追加 gcm_sender_id: process.env.MESSAGING_SENDER_ID || "" } }, }; export default config; </code></pre> <p>これでクライアント側はOK!</p> <h4 id="サーバ側: メッセージを送信する"><a href="#%E3%82%B5%E3%83%BC%E3%83%90%E5%81%B4%3A+%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E9%80%81%E4%BF%A1%E3%81%99%E3%82%8B">サーバ側: メッセージを送信する</a></h4> <p>メッセージの送信は、firebase-adminでできる。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/send-message?hl=ja">アプリサーバーからの送信リクエストを作成する  |  Firebase</a></p> <p>firestoreを利用しているので、Cloud Functionsのfirestoreトリガーを使い、<br /> ドキュメントが追加されたら通知するようにしている例。</p> <pre><code class="typescript">import * as functions from "firebase-functions"; import admin from "../common/firebaseAdmin"; // 初期化済みのfirebase-admin export default functions .firestore.document("ドキュメントのパス") .onCreate(async (snap, context) => { // getTokenで保存しておいたトークンを取得(中身は略) const token = getToken(); // 通知の送信 const title = "通知するタイトル"; const body = "通知する本文"; const icon = "通知で表示するアイコン画像のURL" const link = "通知をタップしたときに開くURL" await admin.messaging().send({ // 送信先の端末のトークン token: token, // 通知する内容 notification: { title: title, body: body }, // Web Push向けの通知内容 webpush: { notification: { icon: icon }, fcmOptions: { link: link } } }); }); </code></pre> <p>送信はこれだけ!</p> <h3 id="ほかの小ネタ"><a href="#%E3%81%BB%E3%81%8B%E3%81%AE%E5%B0%8F%E3%83%8D%E3%82%BF">ほかの小ネタ</a></h3> <h4 id="トークンを削除する"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B">トークンを削除する</a></h4> <p><code>firebase.messaging().deleteToken();</code>でトークンを無効化できる。</p> <pre><code class="typescript">// トークンの削除 public async deleteToken(user: User) { const isSupported = firebase.messaging.isSupported(); if (isSupported) { const token = await firebase.messaging().getToken(); await firebase.messaging().deleteToken(token); } } </code></pre> <h4 id="フォアグラウンドで通知を受け取ったときになにかする"><a href="#%E3%83%95%E3%82%A9%E3%82%A2%E3%82%B0%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89%E3%81%A7%E9%80%9A%E7%9F%A5%E3%82%92%E5%8F%97%E3%81%91%E5%8F%96%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E3%81%AA%E3%81%AB%E3%81%8B%E3%81%99%E3%82%8B">フォアグラウンドで通知を受け取ったときになにかする</a></h4> <p><code>onMessage</code>を使うと、通知を受け取ったときに呼び出してくれる。</p> <pre><code class="typescript">firebase.messaging().onMessage(async (payload) => { // 受け取ったときの処理 }); </code></pre> <h4 id="トークンが変更されたときになにかする"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%8C%E5%A4%89%E6%9B%B4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E3%81%AA%E3%81%AB%E3%81%8B%E3%81%99%E3%82%8B">トークンが変更されたときになにかする</a></h4> <p><code>onTokenRefresh</code>を使うと、トークンが更新されたときに呼び出してくれる。<br /> 新しいトークンは再度<code>getToken()</code>を呼ばないといけない。</p> <pre><code class="typescript">firebase.messaging().onTokenRefresh(async () => { // トークンが更新されたときの処理 }); </code></pre> <h4 id="通知の許可状態を確認する"><a href="#%E9%80%9A%E7%9F%A5%E3%81%AE%E8%A8%B1%E5%8F%AF%E7%8A%B6%E6%85%8B%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">通知の許可状態を確認する</a></h4> <p>通知の状態は、<code>Notification.permission</code>で確認できるらしい。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/notification/permission">Notification.permission - Web API | MDN</a></p> <pre><code class="typescript">if (Notification.permission === "default") { // 確認(デフォルト) } else if (Notification.permission === "granted") { // 許可 } else if (Notification.permission === "denied") { // 拒否 } </code></pre> <p>以上!!</p> <h3 id="【PR】これをつかって、こんなのつくりました!"><a href="#%E3%80%90PR%E3%80%91%E3%81%93%E3%82%8C%E3%82%92%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%A6%E3%80%81%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%82%8A%E3%81%BE%E3%81%97%E3%81%9F%EF%BC%81">【PR】これをつかって、こんなのつくりました!</a></h3> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/2f71c56a-0ab2-5910-f203-e1fca191ad69.png" alt="スクリーンショット 2020-05-24 12.44.28.png" /></p> <p>こんな通知を受け取れます!<br /> <img width="359" alt="スクリーンショット_2020-05-24_13_34_17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/1d4037bf-e662-a15a-4567-91f845ef5142.png"></p> <p>1週間でWebサービスを作るイベント <a href="https://crieit.net/boards/web1week-202005">web1week</a>への投稿作品です!<br /> よかったら、遊んでみてください(<em>´ω`</em>)</p> <p>■エアで投げ銭できるWebサービス「エア銭」<br /> URL: <a target="_blank" rel="nofollow noopener" href="https://air-money.netlify.app/">https://air-money.netlify.app/</a></p> <h1 id="参考にしたサイトさま"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%95%E3%81%BE">参考にしたサイトさま</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developers.cyberagent.co.jp/blog/archives/9662/">FRESH! における Web プッシュ通知機能 〜実装編〜 | CyberAgent Developers Blog</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/firebase/quickstart-js/blob/bcce38ebc1e5602560e2b76b20f19b7834b8279e/messaging/firebase-messaging-sw.js#L15-L37">quickstart-js/firebase-messaging-sw.js at bcce38ebc1e5602560e2b76b20f19b7834b8279e · firebase/quickstart-js</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/reference/js/firebase.messaging.Messaging#deletetoken">Messaging | JavaScript SDK  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/send-multiple#%E3%83%88%E3%83%94%E3%83%83%E3%82%AF-http-post-%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">複数のデバイスにメッセージを送信する  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages#WebpushConfig">REST Resource: projects.messages  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/send-message">アプリサーバーからの送信リクエストを作成する  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/first-message">バックグラウンド アプリにテスト メッセージを送信する  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/cloud-messaging/js/client">JavaScript Firebase Cloud Messaging クライアント アプリを設定する</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ryo_hisano/items/1171beca22d5a04ed802">Firebase Cloud Messagingで始めるWebプッシュ通知 - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15732 2020-02-24T15:30:09+09:00 2020-02-24T15:30:09+09:00 https://crieit.net/posts/iPhone-Web iPhoneだけでWeb アプリの動作がおかしいときのデバック環境 <p><a href="https://crieit.now.sh/upload_images/d4096aa352d4455039031a400bacdf075e536ce444545.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d4096aa352d4455039031a400bacdf075e536ce444545.png?mw=700" alt="rectangle_large_type_2_47e47e82499be268d30e2d4624842eec.png" /></a></p> <p>先日React + Firebaseで簡単なWebアプリを開発していたのだが、なぜかiPhoneのみでバグがでてしまうという苦しい状態が続いていた。遊びがてらでHeadmap Calendarを追加したのだが、Headmap Calenderがうまく動かないという状態であったのです</p> <p><a target="_blank" rel="nofollow noopener" href="https://alis-programming-event.firebaseapp.com/">https://alis-programming-event.firebaseapp.com/</a></p> <h2 id="開発環境"><a href="#%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83">開発環境</a></h2> <p>開発ではGoogle Chromeで動作確認を行いDeployを行っていたのだがDeployしてiPhoneで確認してみたらちゃんとうごいていない。</p> <p>iPhoneでSafariではなくChromeを使って動作確認してみても同様にうごかない。Safari依存ではなくiOS依存?結構困ってしまった。</p> <p>AndroidやPCでみるとなんにも問題がないのです。結構こまってしまいました。だいたいiPhoneのSafariにはConsoleはないのだからデバックの手法がかぎられてしまう。</p> <p>なんとかならないかいろいろやってみたらバグがPCで再現できました。</p> <p>その方法は...</p> <h2 id="MACのSafariの開発モードを使用する"><a href="#MAC%E3%81%AESafari%E3%81%AE%E9%96%8B%E7%99%BA%E3%83%A2%E3%83%BC%E3%83%89%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B">MACのSafariの開発モードを使用する</a></h2> <p>MACのSafariのSafari -> 環境設定 ->詳細で「メニューバーに”開発”を表示する」の最下部にあるチェックボックスをオンにすると開発モードが使えるようになります。</p> <p><a href="https://crieit.now.sh/upload_images/59cf55d83b679f6a9c4c38aa7d123e0a5e536b6777c62.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/59cf55d83b679f6a9c4c38aa7d123e0a5e536b6777c62.png?mw=700" alt="スクリーンショット 2020-02-23 14.49.31.png" /></a></p> <p>これで開発がバーに表示されます</p> <p><a href="https://crieit.now.sh/upload_images/71b9e88b0e6ca5b22a6d47f8df7bddb65e536b833137b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/71b9e88b0e6ca5b22a6d47f8df7bddb65e536b833137b.png?mw=700" alt="スクリーンショット 2020-02-23 14.52.55.png" /></a></p> <p>Webインスペクターを表示を選択するとGoogle Chromeで用意されているのと類似の開発者用の機能が開きます。最初はもしかしたら下部にでてくるかもしれませんが、表示の仕方のボタン[][]みたいなのをおせば右に移動できるばすです</p> <p><a href="https://crieit.now.sh/upload_images/ccf9cd1e69618738ca7acbfe245b252a5e536b9e3f650.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ccf9cd1e69618738ca7acbfe245b252a5e536b9e3f650.png?mw=700" alt="スクリーンショット 2020-02-23 14.53.37.png" /></a></p> <h2 id="これで問題を追求するツールを得ることができました"><a href="#%E3%81%93%E3%82%8C%E3%81%A7%E5%95%8F%E9%A1%8C%E3%82%92%E8%BF%BD%E6%B1%82%E3%81%99%E3%82%8B%E3%83%84%E3%83%BC%E3%83%AB%E3%82%92%E5%BE%97%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%97%E3%81%9F">これで問題を追求するツールを得ることができました</a></h2> <p>直面したバグは、少し寝かして本日解決に至りました。<strong>iPhoneでの日付の扱い方に起因するバグのようでした</strong>。iPhoneにバグがあるというわけではなく、データの扱い方の違いなのかもしれませんが、やるせなかったです。</p> <p>ちなみにレスポンシブモードというものがあり、開発のメニューから選択するとデザインのチェックがとても効率的に行えます。Xcodeのシュミレータみたいで便利ですね。</p> <p><a href="https://crieit.now.sh/upload_images/1d5e72557795dd1baf8bf379d46637a35e536c164c0f8.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/1d5e72557795dd1baf8bf379d46637a35e536c164c0f8.png?mw=700" alt="スクリーンショット 2020-02-23 15.05.36.png" /></a></p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>iPhoneだけでうまくいかない。そういうときはSafaliの開発モードを活用しましょう。機種依存はWebアプリでも起こりうるようです。フロントエンドも奥が深いですね。</p> はるか先生🍊クリプト界隈に漂う癒し tag:crieit.net,2005:PublicArticle/15686 2020-01-16T22:04:03+09:00 2020-01-16T22:04:03+09:00 https://crieit.net/posts/AV-AV スワイプで気になるAV女優を探すことができる「AVキャッチャー」というサービスをリリースしました。 <p>crieitでの初めての投稿になります。<br /> 今回、スワイプで気になるAV女優を探すことができる「AVキャッチャー」というサービスをリリースしたので、集客も含めて知見の共有ができればなと思い、記事を書いてみました。</p> <h1 id="どんなサービス?"><a href="#%E3%81%A9%E3%82%93%E3%81%AA%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%EF%BC%9F">どんなサービス?</a></h1> <p>自分の好みを入力し、気になる女優をスワイプで探すことができるサービスです。<br /> スワイプすることによって、女優が出演する作品も簡単に探すことができるようになっています。<br /> <a href="https://crieit.now.sh/upload_images/38a082137c43e7a4c62bac88cc2324c75e205f3689bae.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/38a082137c43e7a4c62bac88cc2324c75e205f3689bae.jpg?mw=700" alt="av_catcher_ogp.jpg" /></a><br /> <a target="_blank" rel="nofollow noopener" href="https://av-catcher.web.app/">https://av-catcher.web.app/</a></p> <p>登録不要なので、気軽に触ってみてください!</p> <h1 id="なんで作ったの?"><a href="#%E3%81%AA%E3%82%93%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%AE%EF%BC%9F">なんで作ったの?</a></h1> <p>最近のAV女優って、めちゃくちゃ可愛い人が多いです!<br /> もっとたくさんの人に知ってもらいたいと思い、作ってみました。</p> <h1 id="どんな構成?"><a href="#%E3%81%A9%E3%82%93%E3%81%AA%E6%A7%8B%E6%88%90%EF%BC%9F">どんな構成?</a></h1> <p>アプリ自体はReactで作成し、firebaseでhostingしています。<br /> また、データの永続化にはfirebaseのfirestoreを使用し、アプリからデータを取得する際はfirebaseのcloud functionsを通して取得するような構成になっています。</p> <h1 id="データはどのように取得しているの?"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AF%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E5%8F%96%E5%BE%97%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%AE%EF%BC%9F">データはどのように取得しているの?</a></h1> <p>今回は、FANZAが公開している情報を元に、アプリを作成しています。<br /> FANZAでは、月間のDVD売り上げランキングを公開しているので、そちらのページから人気の女優名を取得し、FANZAが公開しているAPIを使って、詳細情報と商品情報を取得するようにしています。<br /> スクレイピングにはpuppeteerを使用し、firebaseのfunctionsを定期実行することによって、日時でデータを更新するようにしています。</p> <h1 id="作ってみた感想"><a href="#%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F%E6%84%9F%E6%83%B3">作ってみた感想</a></h1> <p>やはり自分の好きなことだと、夢中になって作れます。<br /> また、firebaseが非常に便利なため、簡単なアプリであれば短期間で作りきることができるようになりました。<br /> これも、近年の個人開発ブームを加速させている要因の一つになっているのではいかと思います。<br /> 自分は個人開発が大好きなので、この記事がどなたかの個人開発の背中を押せたら幸いです。</p> <p>リリースしてからのPVや売り上げ等は、今後の動きをみて随時更新していこうと思います。</p> AVキャッチャー tag:crieit.net,2005:PublicArticle/15666 2020-01-07T21:31:01+09:00 2020-01-07T21:31:01+09:00 https://crieit.net/posts/SPA-Nuxt-Firebase-Hosting-Netlify SPAなNuxtアプリをFirebase HostingからNetlifyに移行してみた <p>Netlifyがいいと聞いて、Firebase Hostingから移行してみたときの備忘録。</p> <p>NetfilyのプレレンダリングがあるのでOGP芸が楽。<br /> 昔、<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/08/07/150000">こんな記事</a>を書いたけど、Functionsで頑張らなくてもよくなった。</p> <p>使ってみてよかったので、移行したときにやったことをまとめてみる。</p> <h2 id="移行するときにやったこと"><a href="#%E7%A7%BB%E8%A1%8C%E3%81%99%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AB%E3%82%84%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">移行するときにやったこと</a></h2> <h4 id="nuxt.config.tsなどの変更"><a href="#nuxt.config.ts%E3%81%AA%E3%81%A9%E3%81%AE%E5%A4%89%E6%9B%B4">nuxt.config.tsなどの変更</a></h4> <h5 id="1. Nuxtなどの公式サイトを見て設定"><a href="#1.+Nuxt%E3%81%AA%E3%81%A9%E3%81%AE%E5%85%AC%E5%BC%8F%E3%82%B5%E3%82%A4%E3%83%88%E3%82%92%E8%A6%8B%E3%81%A6%E8%A8%AD%E5%AE%9A">1. Nuxtなどの公式サイトを見て設定</a></h5> <p>Firebase Hostingで設定したのとかぶっているところもあり、nuxt.config.tsは変更なし。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/faq/netlify-deployment/">Netlify へデプロイするには? - NuxtJS</a></p> <h5 id="2. _redirectsファイルの作成"><a href="#2.+_redirects%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">2. _redirectsファイルの作成</a></h5> <p>Netlifyでは、index.html以外は404になるので、<br /> SPAの場合は<code>static/_redirecs</code>というファイルを作成して、<br /> 以下の内容を記載しておく。</p> <pre><code>/* /index.html 200 </code></pre> <p>参考] <a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps">Rewrites and proxies | Netlify Docs</a></p> <h5 id="3. Netlify側の設定"><a href="#3.+Netlify%E5%81%B4%E3%81%AE%E8%A8%AD%E5%AE%9A">3. Netlify側の設定</a></h5> <p>あとは、Netlifyにアクセスして、 「<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/faq/netlify-deployment/#spa-%E3%83%A2%E3%83%BC%E3%83%89%E3%81%A7%E7%94%9F%E6%88%90%E3%81%95%E3%82%8C%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AE%E5%A0%B4%E5%90%88">SPA モードで生成されたサイトの場合</a>」にも書いてあるとおりの内容をNetlifyの設定に入力すればOK。</p> <p>迷わなければ、5分くらいでできて、masterにpushと更新される(<em>´ω`</em>)</p> <h4 id="プレレンダリング"><a href="#%E3%83%97%E3%83%AC%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0">プレレンダリング</a></h4> <p>ベータ版だけど、Netlifyでプレレンダリングしてくれる機能がある。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/08/07/150000">この記事</a>に書いてある感じで、<br /> 「FunctionsでheadだけのHTMLを生成→リダイレクト」<br /> としていたのが不要になる感じ。</p> <h5 id="Netlifyの画面で設定"><a href="#Netlify%E3%81%AE%E7%94%BB%E9%9D%A2%E3%81%A7%E8%A8%AD%E5%AE%9A">Netlifyの画面で設定</a></h5> <p>設定は簡単で、Netlify上で設定画面で有効化すればOK。<br /> 参考: <a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/site-deploys/post-processing/prerendering/#set-up-prerendering">Prerendering | Netlify Docs</a></p> <h5 id="その他の修正点"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E4%BF%AE%E6%AD%A3%E7%82%B9">その他の修正点</a></h5> <p>あとは、以下の修正。</p> <ol> <li>OPGのためにrouterでリダイレクトしていた設定を削除</li> <li>各ページでメタ情報を適切になるように変更</li> </ol> <p>また、上で書いたとおり、<code>static/_redirecs</code>で</p> <pre><code>/* /index.html 200 </code></pre> <p><strong>これを設定しておかないとボットがアクセスしたときに404になってOGPがでない</strong>。。</p> <p>(これに気づかずかなりハマった。。)</p> <h5 id="小ネタ: プレレンダリングの結果を確認する"><a href="#%E5%B0%8F%E3%83%8D%E3%82%BF%3A+%E3%83%97%E3%83%AC%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AE%E7%B5%90%E6%9E%9C%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">小ネタ: プレレンダリングの結果を確認する</a></h5> <p>プレレンダリングするかどうかは、UAを見て判断しているらしく、</p> <ul> <li>UAを指定してすると、プレレンダリングされた結果を確認でき、</li> <li>UAを指定してないと、プレレンダリングなしの結果になる</li> </ul> <p>curlだとこんな感じで、<code>-A twitterbot</code>を指定するとよい。</p> <pre><code class="shell">$ curl -A twitterbot https://example.com/hoge/fuga -o fuga.html </code></pre> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://community.netlify.com/t/common-issue-understanding-and-debugging-prerendering/150">[Common Issue] Understanding and debugging prerendering</a></p> <p>毎回、<a target="_blank" rel="nofollow noopener" href="https://cards-dev.twitter.com/validator">Twitter Card Validator</a>で確認するのはめんどくさいので、この方法だと便利。</p> <h3 id="ページが存在しないURLの対応"><a href="#%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%8C%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84URL%E3%81%AE%E5%AF%BE%E5%BF%9C">ページが存在しないURLの対応</a></h3> <p>積読ハウマッチでは、少し特殊なことをしていて、<br /> 一部のURLはheadだけのHTMLしか存在しないページがいくつかある。</p> <p>なので、その部分を修正していく。方法としては以下の感じ。</p> <ol> <li>nuxt.config.tsでrouterに、クエリパラメタ付きでリダイレクトを追加する <ul> <li><a target="_blank" rel="nofollow noopener" href="https://router.vuejs.org/ja/guide/essentials/redirect-and-alias.html">リダイレクトとエイリアス | Vue Router</a></li> </ul></li> <li><code>asyncData()</code>でクエリパラメタを受け取り、<code>created()</code>で削除 <ul> <li><code>this.$router.push({ query: {} });</code></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.kimullaa.com/entry/2018/01/27/183743">Vuejs vue-router クエリパラメータの一部だけを取り除く - SIerだけど技術やりたいブログ</a></li> </ul></li> </ol> <h4 id="動的なサイトマップの対応"><a href="#%E5%8B%95%E7%9A%84%E3%81%AA%E3%82%B5%E3%82%A4%E3%83%88%E3%83%9E%E3%83%83%E3%83%97%E3%81%AE%E5%AF%BE%E5%BF%9C">動的なサイトマップの対応</a></h4> <p>書籍やユーザのページなどは日々増えていくので、<br /> サイトマップも動的に生成するようにしている。</p> <p>Hostingの場合はFunctionsを呼び出せたけれど、<br /> Netlifyに移行すると、それができないので対応が必要。</p> <p><strong>netlifyの_redirectsを使って直接Functionsへリダイレクトする方法で対応した</strong>。</p> <p>こんな感じで各URLを該当のFunctionsにリダイレクトさせておく。</p> <pre><code>/sitemap https://us-central1-<project-id>.cloudfunctions.net/sitemap /* /index.html 200 </code></pre> <h5 id="sitemap-moduleだとダメだった..."><a href="#sitemap-module%E3%81%A0%E3%81%A8%E3%83%80%E3%83%A1%E3%81%A0%E3%81%A3%E3%81%9F...">sitemap-moduleだとダメだった...</a></h5> <p>sitemap-moduleで動的なページの対応方法も乗っているが、<br /> SPAなのでgenerateされてしまう...</p> <ul> <li>参考 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/nuxt-community/sitemap-module#readme">nuxt-community/sitemap-module: Sitemap Module for Nuxt.js</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/bucchi49/items/d271c4010a3f6c900926">Nuxt.jsで静的ファイル生成時にサイトマップも自動生成する方法 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/cortyuming/items/b4640b371e4ceb37ae47">Nuxt.js + Firestore で動的サイトマップ - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sauzar18/items/2ea958043eb6758c4f83">Nuxt.jsでサイトマップを動的に設定する方法 - Qiita</a></li> </ul></li> </ul> <h4 id="カスタムドメインの設定"><a href="#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A">カスタムドメインの設定</a></h4> <p>Netlifyの設定画面に従い、カスタムドメインを設定していく。</p> <p>ALIASを設定するようにいわれるが、<br /> お名前ドットコムはALIASを設定できないらしいので、<br /> NetlifyのDNSを使う形で対応</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/hitochan777/items/7acce5b4398af7f4c358">参考にした記事</a>でも書いてあるとおり、<br /> Aレコードでもできるが、CDNを活用できないらしいので同様の対応</p> <p>あとは、設定画面にも書いてあるとおり、<br /> <code>*.netlify.com</code>から設定したドメインへリダイレクトを追加しておく。</p> <pre><code>[https://tsundoku.netlify.com/*](https://tsundoku.netlify.com/*) https://tsundoku.site/:splat 301! /sitemap https://us-central1-<project-id>.cloudfunctions.net/sitemap /* /index.html 200 </code></pre> <p>これでリダイレクトされるようになるので、あとはDNSの反映を待てばOK!</p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a href="https://crieit.net/posts/Netlify-404">Netlifyを使ってたらルートパス以外が404になった話とその解決方法 - Crieit</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/routing/redirects/rewrites-proxies/#limitations">Rewrites and proxies | Netlify Docs</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://prerender.io/documentation/best-practices">Prerender - Dynamic Rendering for JavaScript Website SEO</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://community.netlify.com/t/common-issue-understanding-and-debugging-prerendering/150">[Common Issue] Understanding and debugging prerendering - Support / Common Issues - Netlify Community</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/NaokiIshimura/items/64e060ccc244e38d0c15#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A">【Netlify】カスタムドメインを設定する - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/don-bu-rakko/items/8eb30c7e9a3f9531ba16">Netlifyにお名前.comで取得した独自ドメインを設定する - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15665 2020-01-06T09:30:57+09:00 2020-01-06T09:52:45+09:00 https://crieit.net/posts/2 めけぽんビンゴがバズって3万円請求された件について <p><a href="https://crieit.now.sh/upload_images/dc6663c05a88015787ddefe46748ba0f5e10719cc9eab.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/dc6663c05a88015787ddefe46748ba0f5e10719cc9eab.jpg?mw=700" alt="従量課金0.jpg" /></a></p> <h1 id="従量課金地獄へようこそ!!!"><a href="#%E5%BE%93%E9%87%8F%E8%AA%B2%E9%87%91%E5%9C%B0%E7%8D%84%E3%81%B8%E3%82%88%E3%81%86%E3%81%93%E3%81%9D%EF%BC%81%EF%BC%81%EF%BC%81">従量課金地獄へようこそ!!!</a></h1> <p>まいど、弓矢です。<a target="_blank" rel="nofollow noopener" href="https://bingo.mekepon.com/">めけぽんビンゴ</a>を作った、個人開発大好きエンジニアです。</p> <p>今日の話は従量課金です。<br /> 従量課金とは、使った分だけ課金する仕組みです。上限はありません。<br /> サービスを作る際、人気が出る確証はないけど一発狙っていきたい場合に大変ありがたい仕組みです。</p> <p>しかしながら、設計を一つミスすると恐ろしいことになります。<br /> 課金は青天井。際限なく請求されます。</p> <p>その結果、<strong>1日で3万円請求</strong>されました。<br /> ちなみに広告をつけてなかったので<strong>収益は0円</strong>です。純粋に3万円の赤字です。</p> <p>というわけで今回は従量課金のちょっと辛い話をします。</p> <h2 id="めけぽんビンゴ、リプレイス計画"><a href="#%E3%82%81%E3%81%91%E3%81%BD%E3%82%93%E3%83%93%E3%83%B3%E3%82%B4%E3%80%81%E3%83%AA%E3%83%97%E3%83%AC%E3%82%A4%E3%82%B9%E8%A8%88%E7%94%BB">めけぽんビンゴ、リプレイス計画</a></h2> <p>Twitterをやりこんでいるオタクなら、「めけぽんビンゴ」を見たことがあるのではないでしょうか。<br /> このサービスは私が高専生だった頃に作ったサービスです。<br /> 学生時代に作っただけあって、ソースコードはぐちゃぐちゃで、もはや解読するのが難しいレベルです。<br /> お見せするとこんな感じです。そのまんまコピペしてきました。</p> <pre><code><?php class TopPage extends View { public function Contents() { echo ' <div class="Container"> '; echo ' <div class="BigBox"> <h1>' . SITE_TITLE . 'へようこそ!</h1> <div class="Contents"> ' . SITE_TITLE . 'は簡単にビンゴが作成・プレイできるサービスです。作成にはツイッターのアカウントが必要になります。<br> 公式ツイッターアカウントは<a href="https://twitter.com/mekemeke_pon">@mekemeke_pon</a>です。人気のビンゴをツイートしています。 <span style="color:red;">現在サイト移行準備中です。新規でカードは作成できません。</span><br><br> <div style="text-align:center;"> <!--<a href="' . $login_url . '"><div class="Button">作成する</div></a>--> </div> </div> </div>'; $this->Adsense_square(); ...... </code></pre> <p>エンジニアなら、これがクソコードであることがわかるでしょう。<br /> 生PHP、しかもechoでHTML書いてました。当時の私にはそういう実装しかできなかったのです。</p> <p>おまけにサーバの負荷がものすごく高くなる実装がされていました。<br /> さくらのクラウドを使用していたので、普段は月4000円くらいのスペックで動かして、バズったときは手動でスペックを高いほうに変更する、という運用をしていました。</p> <blockquote> <p><strong>さくらのクラウド:</strong><br /> IaaS型クラウドサービス。<br /> つまるところ、あとからスペックを自由に変えられるレンタルサーバーです。スペックが高いとかかる料金も高くなります。</p> </blockquote> <p>このアナログな運用では、以下のような問題がありました。<br /> * バズに感知できないとサーバが落ちてしまう<br /> * スペック変更時にサーバの電源を切る必要があり、手間がかかる</p> <p>解読不能のままでは修正や更新もままならないですし、突発的なバズが発生するめけぽんビンゴでは手動スペック変更での運用は厳しいものがありました。</p> <p>というわけで、先月 <strong>Firebase</strong> + <strong>Netlify</strong> + <strong>SPA(Nusx.js)</strong> で大幅リプレイスを行いました。</p> <blockquote> <p><strong>Netlify:</strong><br /> 静的サイト専用のホスティングサービスです。つまるところ、難しいこと(PHPとかCGIとか)はできない<strong>基本無料</strong>のレンタルサーバーです。ある程度は無料で、あんまり使いすぎると費用が発生します。<br /> <strong>Firebase:</strong><br /> 今回の主人公の<strong>従量課金</strong>バックエンドサービスです。つまるところ、データを保存できる<strong>基本無料</strong>のサービスです。ある程度は無料で、一定回数以上の処理は、処理回数分費用が発生します。<br /> <strong>SPA:</strong><br /> index.html一枚だけで作るWebサイトです。JavaScriptというプログラミング言語で、ページの中身を書き換えています。</p> </blockquote> <p>Netlifyで、いくらアクセスが発生しても大丈夫になりました。<br /> Firebaseで、どれだけデータ書き込み(めけぽんビンゴの場合はプレイ)がされても重くならなくなりました。<br /> SPAで、プレイ後のページ遷移が重いなんてことがなくなりました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/organizations/admin-guild">運営者ギルド</a>の皆様にバグバッシュをしてもらい、公開します。ちょっと事故を起こしたので、ちゃんと公開できたのは12月30日のことでした。</p> <p>その時点ではAdsenseのサイト審査が通ってなかったので、Adsense広告を貼るのは後にすることにしました。<br /> あの時の私は、こんなにすぐバズるとは思っていなかったのです……</p> <blockquote> <p><strong>Adsense:</strong><br /> Googleの自動広告。つまるところ、アフィみたいなモンです。</p> </blockquote> <h2 id="来たる、バズ ~リアルタイム閲覧者数8000人~"><a href="#%E6%9D%A5%E3%81%9F%E3%82%8B%E3%80%81%E3%83%90%E3%82%BA%E3%80%80%EF%BD%9E%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E9%96%B2%E8%A6%A7%E8%80%85%E6%95%B08000%E4%BA%BA%EF%BD%9E">来たる、バズ ~リアルタイム閲覧者数8000人~</a></h2> <p>公開できたので、今までのビンゴの中でも人気が高かった都道府県ビンゴの完全版「<a target="_blank" rel="nofollow noopener" href="https://bingo.mekepon.com/card/32s0e93NVvq1RuTaFIhC/">行ったことある都道府県47! </a>」をサクラで投稿し、年越しをしました。</p> <p>そして年明け。<br /> 毎年正月にバズる<a target="_blank" rel="nofollow noopener" href="http://utabami.com/sitatametter/">したためったー</a>は、ツイッターのトレンド30位くらいまで浮上する程度に健闘してくれました。(余談ですが、私はしたためったーを心の中で「お年玉」と呼んでいます。)<br /> 正月はみんなテレビを見ているため、トレンドがほぼテレビ番組関連です。とにかく母数が大きいので、他のワードはなかなかトレンド浮上できません。<br /> めけぽんビンゴも特に浮上しないだろうと高をくくっていました。<br /> しかし駅伝の復路が盛り上がる1月3日のこと。</p> <p>2日時点でトレンド入りしない程度のバズだっためけぽんビンゴが、トレンド3位にまで浮上しました。<br /> <a href="https://crieit.now.sh/upload_images/e4b63cabf8532768a2c319144d1e9aec5e10586ce356e.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e4b63cabf8532768a2c319144d1e9aec5e10586ce356e.jpg?mw=700" alt="従量課金2.jpg" /></a></p> <p>その時のリアルタイムの閲覧者数がこちら。<br /> <a href="https://crieit.now.sh/upload_images/27427c4a7ba258a6b3fe91ee69c3f46d5e10546646338.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/27427c4a7ba258a6b3fe91ee69c3f46d5e10546646338.jpg?mw=700" alt="従量課金1.jpg" /></a></p> <p>私が知ってる中では、過去最高記録です。過去トレンド1位を取ったときも、ここまで人は来ていません。<br /> 正月休みでやることがない人が多かったことが、一番の要因でしょう。<br /> それに加えて『いくらアクセスが発生しても重くならなくなった』『どれだけデータ書き込みがされても重くならなくなった』ということも、爆発的なアクセス増加に加担しているかと思われます。</p> <p>見たことのない数字に対して、多少不安になりながらも興奮して眺めていました。</p> <h2 id="立ち込める、暗雲 ~読み取り1.5億回~"><a href="#%E7%AB%8B%E3%81%A1%E8%BE%BC%E3%82%81%E3%82%8B%E3%80%81%E6%9A%97%E9%9B%B2%E3%80%80%EF%BD%9E%E8%AA%AD%E3%81%BF%E5%8F%96%E3%82%8A1.5%E5%84%84%E5%9B%9E%EF%BD%9E">立ち込める、暗雲 ~読み取り1.5億回~</a></h2> <p>爆発的バズが収まった1月4日。<br /> 1月3日分のFirebase(Firestore)の利用状況を確認しました。<br /> <a href="https://crieit.now.sh/upload_images/7fcb74c5040f78b02029f56c96d6ff975e105efc513e1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7fcb74c5040f78b02029f56c96d6ff975e105efc513e1.png?mw=700" alt="従量課金3.png" /></a><br /> 書き込み80万に対して、読み取り1.5億回。億という数字に対してイマイチイメージがわきませんが、読み取りが多すぎるような感じがします。書き込みが1回行われる間に読み取りが187.5回行われる計算です。</p> <p>設計を見直して、ある事に気が付きます。<br /> <strong>プレイ後に、更新データをリアルタイムで取得するようにしていたのです。</strong><br /> 「プレイ後のパーセンテージがリアルタイムで変わったらおもろいやろ」程度の気持ちで実装したものでした。<br /> しかしバズっているビンゴは1秒に何回もプレイされて、そのたびにデータが更新されます。さらに、ページを移動してもリアルタイム取得は止まりません。<br /> その結果、大量のリアルタイム取得が発生し、読み取り回数が億を突破するという馬鹿げたことになったのでしょう。<br /> (この不具合は現在は修正済みです。)</p> <h2 id="襲い来る、請求 ~意外と安かった~"><a href="#%E8%A5%B2%E3%81%84%E6%9D%A5%E3%82%8B%E3%80%81%E8%AB%8B%E6%B1%82%E3%80%80%EF%BD%9E%E6%84%8F%E5%A4%96%E3%81%A8%E5%AE%89%E3%81%8B%E3%81%A3%E3%81%9F%EF%BD%9E">襲い来る、請求 ~意外と安かった~</a></h2> <p>そして気になる料金。<br /> 1月2日時点のPV数が7万、1月3日時点でのPV数が72万。一体どんな恐ろしいことが起きるのか……<br /> 1月3日の請求額は2日後の1月5日に確定します。</p> <p>念のためにさらに1日置いて1月6日朝。震えながら確認したところ……<br /> <a href="https://crieit.now.sh/upload_images/0de6f0b852d56e734b7c15064d5a50065e127464819bb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0de6f0b852d56e734b7c15064d5a50065e127464819bb.png?mw=700" alt="スクリーンショット 2020-01-06 8.31.32.png" /></a><br /> い、意外と安い……!?(2万円の赤字だけど)<br /> ちょっと信じられなかったのでFirebaseの使用量から計算してみたのですが、だいたいあっているようです。</p> <p>しかし安いと言っても2万円です。1日で2万の請求は個人開発史上最大のマイナス。<br /> 中間層のお年玉総額程度の赤字ですが、学生の頃なら痛かったと思います。</p> <p>ここまでFirebaseの話ばかりしましたが、めけぽんビンゴで使用している従量課金サービスはもう一つあります。<br /> <strong>Netlify</strong>です。<br /> 無料の上限がかなり高いので無料のサービスあつかいされていますが、これだけPVがあると流石に有料になるようです。<br /> Netlifyから「無料枠超えたよ!」と言うメールが来たので、確認してみました。<br /> <a href="https://crieit.now.sh/upload_images/7a442dc0de3d031d35bdbd4c348d3aea5e12812d851bc.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a442dc0de3d031d35bdbd4c348d3aea5e12812d851bc.png?mw=700" alt="スクリーンショット 2020-01-06 9.34.23.png" /></a><br /> 80ドル。現在のレートだと8643円です。</p> <p>と言うわけで合計で約3万円の請求がきました。<br /> 富裕層のお年玉総額程度の赤字ですが、学生の頃なら超痛かったと思います。</p> <h2 id="反省、そしてまとめ"><a href="#%E5%8F%8D%E7%9C%81%E3%80%81%E3%81%9D%E3%81%97%E3%81%A6%E3%81%BE%E3%81%A8%E3%82%81">反省、そしてまとめ</a></h2> <p>今回の反省点は、<strong>バズったときのことを考えない設計をしてしまったこと</strong>でしょう。<br /> 不幸な点として、<strong>Adsenseの審査が間に合わなかったこと</strong>もあげられます。Adsense広告があったのなら、このくらい取り返してくれていたでしょう。</p> <p>よかった点としては、<strong>Firebaseは普通に使えば割と安上がり</strong>と言う学びがありました。<br /> 1日72万PVでゴリゴリに画像も生成をしなおかつ不具合で無駄な呼び出しが大量に発生しているのに、たったの2万です。不具合修正後はもうちょっと安く済むでしょう。<br /> Netlifyについてはプリレンダリングが強いので、このまま使いたいと思います。(なんだかんだ無料枠が大きいので、請求が発生するのはそう滅多にないはず。)</p> <p>エンジニア界隈ではFirebaseがもてはやされていますが、ひとつ設計を見誤ると地獄が発生します。<br /> エンジニアの皆様、とくにお金を持っていない個人開発者の学生の方。設計には気を付けましょう!!!</p> <p>そしてサービス利用者の皆様。いつもありがとうございます。<br /> 今回私はヘマをしでかしてますが、バズったことは悪いことではありません。いいことです。<br /> 素敵なサービスを見つけたらバズらせに加担していきましょう!!!</p> <h2 id="脚注・リンク"><a href="#%E8%84%9A%E6%B3%A8%E3%83%BB%E3%83%AA%E3%83%B3%E3%82%AF">脚注・リンク</a></h2> <p>お年玉総額について:<br /> 想像です。ソースはありません。</p> <p>地獄の大先輩:<br /> <a target="_blank" rel="nofollow noopener" href="https://gigazine.net/news/20180803-spent-much-money-in-firebase/">Firebaseの設定を間違えて72時間で300万円以上請求されてしまったウェブサービス</a></p> 弓矢🏹 tag:crieit.net,2005:PublicArticle/15658 2019-12-30T14:15:44+09:00 2019-12-30T14:15:44+09:00 https://crieit.net/posts/Firebase-Vue-js-Cloud-Functions-docker-compose Firebase フロントエンド(Vue.js)/Cloud Functions を同一docker-composeで実行する際に感じたこと <h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2> <p>2019/12/30現在において、Firebaseを開発する際に生じる不都合の一つとして、node version管理があると思います.</p> <p>Cloud Functionsの開発において、nodeのバージョンはversion8、あるいは10がサポートされています.</p> <blockquote> <p>Cloud Functions ランタイムに関数をデプロイするには Firebase CLI が必要です。Node.js バージョン 8 と 10 がサポートされています。Node.js と npm をインストールする場合は、Node Version Manager をおすすめします。<br /> <a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/functions/get-started?hl=ja">https://firebase.google.com/docs/functions/get-started?hl=ja</a></p> </blockquote> <p>つまり、こんなことが頻発します.</p> <pre><code class="bash">[dorakueyon]% yarn build yarn run v1.19.2 error functions@: The engine "node" is incompatible with this module. Expected version "8". Got "10.15.3" error Commands cannot run with an incompatible environment. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command. </code></pre> <p>このように、異なるversionが依存する開発では、その依存性をDockerに閉じ込めることで幸せになれます.</p> <p>参考として、githubにcodeを置かせていただきました.<br /> - <a target="_blank" rel="nofollow noopener" href="https://github.com/dorakueyon/vue-firebase-cloud-function-docker-starter">github.com/dorakueyon/vue-firebase-cloud-function-docker-starter</a></p> <h2 id="Vue.js + Cloud Functionsのフォルダ構成"><a href="#Vue.js+%2B+Cloud+Functions%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E6%A7%8B%E6%88%90">Vue.js + Cloud Functionsのフォルダ構成</a></h2> <p>Docker化する前に、プロジェクトを作成した上でフォルダ構成をみてみます.</p> <ul> <li>Vue.js + Firebaseプロジェクト作成</li> </ul> <pre><code class="bash">$ vue create project-name $ firebase init </code></pre> <ul> <li>フォルダ構成 (node_modules配下を除いています)</li> </ul> <pre><code class="bash">. ├── README.md ├── babel.config.js ├── firebase.json ├── functions │   ├── node_modules │   ├── package-lock.json │   ├── package.json │   ├── src │   └── tsconfig.json ├── node_modules ├── package.json ├── public │   ├── favicon.ico │   └── index.html ├── src │   ├── App.vue │   ├── assets │   ├── components │   ├── main.ts │   ├── router │   ├── shims-tsx.d.ts │   ├── shims-vue.d.ts │   └── views ├── tsconfig.json └── yarn.lock </code></pre> <p>FrontとCloud Functionsを開発する場合、プロジェクト直下と./funcions直下とで開発環境が分かれます.</p> <p>それぞれにDockerfileを配置した上で、プロジェクト直下にdocker-compose.ymlを配置します.</p> <ul> <li>Dockerファイルを追加したフォルダ構成 (node_modules配下を除いています)</li> </ul> <pre><code class="bash">. ├── Dockerfile <- ├── README.md ├── babel.config.js ├── docker-compose.yml <- ├── firebase.json ├── functions │   ├── Dockerfile <- │   ├── node_modules │   ├── package-lock.json │   ├── package.json │   ├── src │ │ ├── index.ts │ │ └── services │ │ └── project-name -> ../../../src/services/project-name <- シンボリックリンク │   └── tsconfig.json ├── node_modules ├── package.json ├── public │   ├── favicon.ico │   └── index.html ├── src │   ├── App.vue │   ├── assets │   ├── components │   ├── main.ts │   ├── router │ ├── services │ │   └── project-name <- シンボリックリンク先 │ │   └── constants.ts │   ├── shims-tsx.d.ts │   ├── shims-vue.d.ts │   └── views ├── tsconfig.json └── yarn.lock </code></pre> <h2 id="Docker化"><a href="#Docker%E5%8C%96">Docker化</a></h2> <p>Docker化にむけて、下記の要件があります.</p> <ul> <li>プロジェクト直下のfirebase関連ファイル(firebase.json, .firebaserc)を、./functions側でも参照したい</li> <li>./functions側からプロジェクト直下の定数やtypeをシンボリックリンクで参照する場合がある</li> </ul> <p>上記を念頭に、<code>Dockerfile/docker-compose.yml</code>を作成します.</p> <h3 id="プロジェクト直下のDockerfile"><a href="#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E7%9B%B4%E4%B8%8B%E3%81%AEDockerfile">プロジェクト直下のDockerfile</a></h3> <ul> <li>こちらは特に考えることはありません</li> </ul> <pre><code class="Docker">FROM node:12-alpine ENV WORKDIR /work WORKDIR $WORKDIR COPY package.json $WORKDIR RUN yarn COPY tsconfig.json $WORKDIR COPY *.config.js $WORKDIR/ COPY public $WORKDIR/ COPY src $WORKDIR/src EXPOSE 8080 CMD yarn serve </code></pre> <h3 id="./functionsのDockerfile"><a href="#.%2Ffunctions%E3%81%AEDockerfile">./functionsのDockerfile</a></h3> <p>./functions/Dockerfileと、./docker-comose.ymlを比較しながらご確認ください.</p> <ul> <li>WORKDIRを<code>/work/functions</code>とする</li> <li>./functions直下のファイルCOPYは、コピー元をプロジェクト直下からの相対パスで指定する</li> <li>プロジェクト直下のファイルCOPYは、コンテナの<code>/work</code>配下に設置する</li> </ul> <pre><code class="Docker">FROM node:8-alpine ENV WORKDIR /work/functions WORKDIR $WORKDIR COPY ./functions/package.json $WORKDIR RUN yarn # firebase RUN yarn global add firebase-tools COPY ./functions/tsconfig.json $WORKDIR COPY ./functions/lib $WORKDIR/lib COPY ./functions/src $WORKDIR/src # # for symbolic link (./functions/src/services/project-name) # COPY ./src/services/project-name /work/src/services/project-name # firebase COPY firebase.json /work COPY .firebaserc /work # COPY ./functions/.runtimeconfig.json $WORKDIR # if needed # COPY ./functions/credentials $WORKDIR/credentials # if needed # settings for runtime emulator ENV HOST 0.0.0.0 EXPOSE 5000 EXPOSE 9005 </code></pre> <h3 id="プロジェクト直下のdocker-compose.yml"><a href="#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E7%9B%B4%E4%B8%8B%E3%81%AEdocker-compose.yml">プロジェクト直下のdocker-compose.yml</a></h3> <ul> <li>上記Cloud FunctionsのDockerfileにあわせるため<code>build:</code>のcontext, dockerfileを別に指定する</li> <li>symbolic linkされている(プロジェクト直下にある)実体ファイルの変更もfunctions側のdockerに反映させるため、<code>./src:/work/src</code>を追加</li> </ul> <pre><code class="Docker">version: '3' services: main: build: . container_name: front volumes: - ./public:/work/public - ./src:/work/src ports: - 8080:8080 tty: true command: yarn serve functions: build: context: ./ dockerfile: ./functions/Dockerfile container_name: functions volumes: - ./functions/lib:/work/functions/lib - ./functions/src:/work/functions/src - ./src:/work/src # for symbolic link # environment: # - GOOGLE_APPLICATION_CREDENTIALS=./credentials/firebase-adminsdk.json ports: - 5000:5000 - 9005:9005 tty: true </code></pre> <h2 id="開発の場合"><a href="#%E9%96%8B%E7%99%BA%E3%81%AE%E5%A0%B4%E5%90%88">開発の場合</a></h2> <p>開発環境立ち上げます</p> <pre><code class="bash">$ docker-compose build && docker-compose up </code></pre> <p>その後の開発は下記のようにすすめます</p> <h3 id="front"><a href="#front">front</a></h3> <p><code>http://localhost:8080</code>にVue.jsプロジェクトがホットリロードされます</p> <h3 id="Cloud Functions"><a href="#Cloud+Functions">Cloud Functions</a></h3> <p>こちらは多少面倒ではあります</p> <ol> <li>dockerコンテナに入る</li> <li>firebase loginしていなければfirebase loginを実施</li> <li>firebase serveやfirebase functions:shellなどでdebugging</li> </ol> <p>以下 <code>docker exec functions -it sh</code> してコンテナ内で</p> <pre><code>$ firebase login # if you are not logged in. $ firebase serve $ firebase functions:shell </code></pre> <p>Cloud Functionsのデバッグについてはまだペインが多いです..</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/functions/local-shell?hl=ja">関数をインタラクティブにテストする(Firebase official)</a></li> </ul> <p>firebase loginの認証情報を永続化(都度<code>fireabase login</code>したくない)人は下記のサイトが参考になるかもしれません(試していません..)</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/pannpers/items/244a7e3c18d8c8422e4f">[Firebase] Cloud Functionsで消耗したくない人のために、開発環境のベストプラクティスをまとめていったらDockerに行き着いた話</a></li> </ul> <h2 id="deploy"><a href="#deploy">deploy</a></h2> <ul> <li>github actions/ circleCIなど利用したほうがよいでしょう.</li> </ul> <h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2> <p>少し強引な形となってしまいましたが、Dockerの恩恵をうけることができる状態までできました.<br /> ただし、<code>./functions/Dockerfile</code>のフォルダのコンテキストが親階層直下にある点に気持ち悪さがあります.</p> <p>ここの気持ちわるさの解消は今後の課題とします.</p> dorakueyon tag:crieit.net,2005:PublicArticle/15656 2019-12-29T19:07:03+09:00 2019-12-29T19:07:50+09:00 https://crieit.net/posts/Nuxt-Firebase-PWA-Service-Worker Nuxt+Firebaseでセッション管理: PWA(Service Worker)編 <p>FirebaseとSSRなNuxt.jsでアプリを作っていて、<br /> クライアント側で認証チェックするとFirebaseの初期化などでラグが...<br /> サーバ側で認証情報とかを取得してもう少しなんとかできないかなと。</p> <p>まだベータっぽい?けど、公式の以下の内容を試してみたときの備忘録。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">Service Worker によるセッション管理  |  Firebase</a></li> </ul> <h3 id="よく出てくる言葉"><a href="#%E3%82%88%E3%81%8F%E5%87%BA%E3%81%A6%E3%81%8F%E3%82%8B%E8%A8%80%E8%91%89">よく出てくる言葉</a></h3> <p>単語はよく聞くけど、ちゃんと見てなかったので、ざっくりとしたまとめ</p> <ul> <li>PWA: ネイティブアプリみたいなUXを提供するWebアプリ <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/progressive-web-apps/">Progressive Web Apps  |  Google Developers</a></li> </ul></li> <li>Service Worker: PWAを実現するための基盤技術。独自のライフサイクルを持ってる <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/fundamentals/primers/service-workers?hl=ja">Service Worker の紹介  |  Web Fundamentals  |  Google Developers</a></li> </ul></li> <li>Workbox: PWAでよく使うコード(ボイラープレート)やベストプラクティスを提供するライブラリ <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/tools/workbox/">Workbox  |  Google Developers</a></li> </ul></li> <li>Nuxt PWA: NuxtでPWAするときに使うプラグイン。workboxを使ったService Worker(sw.js)とかを生成してくれる <ul> <li><a target="_blank" rel="nofollow noopener" href="https://pwa.nuxtjs.org/">⚡ Nuxt PWA</a></li> </ul></li> </ul> <h3 id="Nuxt PWAを使ってみる"><a href="#Nuxt+PWA%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">Nuxt PWAを使ってみる</a></h3> <h4 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h4> <pre><code class="shell">$ npm install @nuxtjs/pwa </code></pre> <h4 id="設定"><a href="#%E8%A8%AD%E5%AE%9A">設定</a></h4> <p>設定は簡単。modulesに<code>@nuxtjs/pwa</code>を追加するだけ。</p> <pre><code class="typescript">// nuxt.config.ts import { Configuration } from "@nuxt/types"; const config: Configuration = { // ...略 modules: [ // ... 略 "@nuxtjs/pwa", ], // ...略 }; export default config; </code></pre> <h3 id="Firebase Authと組み合わせる"><a href="#Firebase+Auth%E3%81%A8%E7%B5%84%E3%81%BF%E5%90%88%E3%82%8F%E3%81%9B%E3%82%8B">Firebase Authと組み合わせる</a></h3> <p>このあたりを見つつ、Firebase Authの情報を扱えるようにする。<br /> - <a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">Service Worker によるセッション管理  |  Firebase</a><br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/daishinkawa/items/915d918aba6bf7849b21#auth%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF">Firebase (Hosting × Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅 その2 - Qiita</a></p> <p>流れ的には、以下の通り。</p> <ol> <li>Firebase Auth用のService Workerの作成して、リクエストにIDトークンを付与するように変更</li> <li>作成したService Workerを使うよう、nuxt.config.tsに設定を追加</li> <li></li> </ol> <h4 id="Firebase Auth用のService Workerの作成"><a href="#Firebase+Auth%E7%94%A8%E3%81%AEService+Worker%E3%81%AE%E4%BD%9C%E6%88%90">Firebase Auth用のService Workerの作成</a></h4> <p>長めだけど、ほぼ<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">公式ドキュメント</a>のまま。</p> <pre><code class="javascript">// ~/static/sw-firebase-auth.js // firebaseを初期化 firebase.initializeApp({ apiKey: /* API_KEY */, authDomain: /* AUTH_DOMAIN */, databaseURL: /* DATABASE_URL */, projectId: /* PROJECT_ID */, storageBucket: /* STORAGE_BUCKET */, messagingSenderId: /* MESSAGING_SENDER_ID */, appId: /* APP_ID */, measurementId: /* AUTH_DOMAIN */ }); // onAuthStateChanged()で現在のuserからidTokenを取得 const getIdToken = () => { return new Promise((resolve, reject) => { const unsubscribe = firebase.auth().onAuthStateChanged(user => { unsubscribe(); if (user) { user.getIdToken().then( idToken => resolve(idToken), error => resolve(null) ); } else { resolve(null); } }); }); }; // URLからルートのURLを取得する処理 const getOriginFromUrl = url => { // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript const pathArray = url.split("/"); const protocol = pathArray[0]; const host = pathArray[2]; return protocol + "//" + host; }; /** * Service Workderのライフサイクルでfetchしたときの処理 */ self.addEventListener("fetch", event => { // リクエストをラップして、ヘッダにFirebase AuthのIdTokenを追加する処理 const requestProcessor = idToken => { let req = event.request; // URLを取得して、httpsもしくはlocalhostかなどをチェック if (self.location.origin == getOriginFromUrl(event.request.url) && (self.location.protocol == "https:" || self.location.hostname == "localhost") && idToken ) { // ヘッダ情報をクローンする const headers = new Headers(); for (let entry of req.headers.entries()) { headers.append(entry[0], entry[1]); } // クローンしたヘッダにFirebase AuthのIdTokenを追加 headers.append("Authorization", "Bearer " + idToken); try { req = new Request(req.url, { method: req.method, headers: headers, mode: "same-origin", credentials: req.credentials, cache: req.cache, redirect: req.redirect, referrer: req.referrer, body: req.body, bodyUsed: req.bodyUsed, context: req.context }); } catch (e) { console.error(e); } } return fetch(req); }; // 上の関数を使って、全リクエストでIdTokenの取得し、Firebase AuthのIdTokenを追加ようにする event.respondWith(getIdToken().then(requestProcessor, requestProcessor)); }); /** * Service Workderのライフサイクルでactivateしたときの処理 */ self.addEventListener("activate", event => { event.waitUntil(clients.claim()); }); </code></pre> <p>やっていることは、以下のような感じ。</p> <ol> <li>リクエストするときに、</li> <li>現在のユーザからIDトークンを取得して</li> <li>IDトークンをリクエストヘッダーに追加する</li> </ol> <p>ヘッダーにIDトークンが付与されているので、<br /> サーバ側でそれを見て、認証済みかをチェックする。</p> <h3 id="nuxt.config.tsへの取り込み"><a href="#nuxt.config.ts%E3%81%B8%E3%81%AE%E5%8F%96%E3%82%8A%E8%BE%BC%E3%81%BF">nuxt.config.tsへの取り込み</a></h3> <p>作成したサービスワーカを使うように、nuxt.config.tsに設定を追加する。<br /> ドキュメントだと<a target="_blank" rel="nofollow noopener" href="https://pwa.nuxtjs.org/modules/workbox.html#adding-custom-service-worker">このあたり</a>を参照。</p> <pre><code class="typescript">// nuxt.config.ts import { Configuration } from "@nuxt/types"; const config: Configuration = { // ...略 modules: [ // ... 略 "@nuxtjs/pwa", ], workbox: { // 追加するスクリプトを指定。 // バンドルされないので、CDNのfirebase-appを追加しておく。 importScripts: [ "https://www.gstatic.com/firebasejs/7.6.1/firebase-app.js", "https://www.gstatic.com/firebasejs/7.6.1/firebase-auth.js", "sw-firebase-auth.js" ], // 開発中でもsw.jsが生成されるように設定。 dev: process.env.MODE != "production", }, // ...略 }; export default config; </code></pre> <h4 id="nuxtServerInitなどで認証状態をチェックする"><a href="#nuxtServerInit%E3%81%AA%E3%81%A9%E3%81%A7%E8%AA%8D%E8%A8%BC%E7%8A%B6%E6%85%8B%E3%82%92%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF%E3%81%99%E3%82%8B">nuxtServerInitなどで認証状態をチェックする</a></h4> <p>IDトークンもJWTデコードすると、UIDを取得できるけれど、<br /> 有効かどうかをfirebase-adminでチェックする必要がある。</p> <p>なので、まずは、firebase-adminのインスタンスを初期化するファイルを用意。</p> <pre><code class="typescript">// ~/utils/firebaseAdmin.ts let admin; if (process.server) { admin = require("firebase-admin"); if (!admin.apps.length) { const serviceAccount = require("./path/to/your/key.json"); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: "https://your-database-url.firebaseio.com" }); } } export default admin; </code></pre> <p>次にこれを使って、以下をしていく。</p> <ol> <li>リクエストヘッダからIDトークンを取得し、</li> <li>firebase-adminを使ってIDトークンの有効性を確認</li> </ol> <p>以下は、<a target="_blank" rel="nofollow noopener" href="https://github.com/championswimmer/vuex-module-decorators">vuex-module-decorators</a>を使ったnuxtServerInitでチェックするサンプル。</p> <pre><code class="typescript">// ~/store/index.ts import { Context } from "@nuxt/types"; import { ActionContext } from "vuex/types"; import { ActionTree, Store } from "vuex"; import { initialiseStores } from "~/utils/store-accessor"; export const state = () => ({}); export type RootState = ReturnType<typeof state>; const initializer = (store: Store<any>) => initialiseStores(store); export const plugins = [initializer]; export const actions: ActionTree<any, any> = { async nuxtServerInit( context: ActionContext<RootState, RootState>, server: Context ) { // requestのAuthorizationからIDトークンを取得 const authorizationHeader = req.headers.authorization || ""; const components = authorizationHeader.split(" "); const token = components.length > 1 ? components[1] : ""; if (!token) return; // firebase-adminの初期化 const admin = require("~/utils/firebaseAdmin").default; if (!admin) return; // IDトークンの検証: 有効期限などをFirebaseでチェック const decodedClaims = await admin.auth().verifyIdToken(token); // 検証結果からUIDを取得 const uid = decodedClaims.uid; // TODO: 認証状態に応じてなにかする } }; export * from "~/utils/store-accessor"; </code></pre> <p>firebase-adminを取得する部分を</p> <pre><code class="typescript">const admin = require("firebase-admin"); </code></pre> <p>としていたり、<code>if (process.server)</code>や<code>if (!admin.apps.length)</code>などのチェックをせずにいたら、<br /> クライアント側でもバンドルされていて、うまく動かいない状態に...</p> <h4 id="注意点"><a href="#%E6%B3%A8%E6%84%8F%E7%82%B9">注意点</a></h4> <p>これで認証が必要なページでもいい感じSSRできた気がする(<em>´ω`</em>)</p> <p>ただ、課題が残っていて、スーパーリロード/ハードリロードすると、<br /> Service Workderを介して、リクエストされないので、ヘッダに認証情報が付与されない...</p> <p>ログを見ていると、スーパーリロード時には、描画されたあとに、Service Workderの登録されているよう。</p> <p>そういった場合でも、利用したい場合には、従来のセッションCookieを利用する方法がよいかも?</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/admin/manage-cookies?hl=ja">セッション Cookie を管理する  |  Firebase</a></li> </ul> <p>(もし良い方法があれば、教えてほしいです...)</p> <hr /> <h4 id="おまけ: Service Workerのライフサイクル"><a href="#%E3%81%8A%E3%81%BE%E3%81%91%3A+Service+Worker%E3%81%AE%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB">おまけ: Service Workerのライフサイクル</a></h4> <p>ここに書いてあった。<br /> - <a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/fundamentals/primers/service-workers?hl=ja">Service Worker の紹介  |  Web Fundamentals  |  Google Developers</a></p> <p><img src="https://developers.google.com/web/fundamentals/primers/service-workers/images/sw-lifecycle.png?hl=ja" width="400px"/></p> <hr /> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト様"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E6%A7%98">参考にしたサイト様</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/web/service-worker-sessions?hl=ja">Service Worker によるセッション管理  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/daishinkawa/items/915d918aba6bf7849b21#auth%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF">Firebase (Hosting × Functions) × Nuxt.js (universal) で ユーザ認証のベストプラクティスを探る旅 その2 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/basho/items/acd6a17bb6e2a2f7a932#section3%E6%96%B0%E8%A6%8F%E7%99%BB%E9%8C%B2google%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%AE%E5%AE%9F%E8%A3%85">SSRモードのNuxtでのFirebase認証 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/y_fujieda/items/f9e765ac9d89ba241154#service-worker">Service Workerの基本とそれを使ってできること - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/horo/items/175c8fd7513138308930">ServiceWorkerとCache APIを使ってオフラインでも動くWebアプリを作る - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle?hl=ja">Service Worker のライフサイクル  |  Web Fundamentals  |  Google Developers</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/admin/manage-sessions">ユーザー セッションの管理  |  Firebase</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth/admin/manage-cookies">Manage Session Cookies  |  Firebase</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15651 2019-12-29T11:21:19+09:00 2019-12-29T11:21:19+09:00 https://crieit.net/posts/Nuxt-Firebase-Auth-Cookie NuxtでFirebase AuthのトークンをCookieに入れたり出したりする <p>NuxtのSSRで認証したいなと思ったら、こんな記事を見つけたので、試してみたときの備忘録。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/basho/items/acd6a17bb6e2a2f7a932#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">SSRモードのNuxtでのFirebase認証 - Qiita</a></p> <p>結果、UIDは取得できるけど、firebase-adminで検証が必要っぽい...</p> <h3 id="インストール"><a href="#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">インストール</a></h3> <p>cookieを扱うライブラリはいろいろあるけど、<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/reactivestack/cookies/tree/master/packages/universal-cookie#readme">universal-cookie</a>がよさそうだったので、これを使ってみる。</p> <pre><code class="shell">$ npm install universal-cookie jwt-decode </code></pre> <h3 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h3> <pre><code class="typescript">import { IncomingMessage } from "http"; import firebase from "firebase import jwtDecode from "jwt-decode"; import Cookies from "universal-cookie"; const KEY_TOKEN = "access_token"; /** * firebase.Userからトークンを取得して、Cookieに保存 */ export async function setCookie(currentUser: firebase.User) { // firebase.UserからidTokenを取得 const token = await currentUser.getIdToken(true); // 新規追加するときは、引数なしでnew Cookies() const cookies = new Cookies(); // Cookieに保存 cookies.set(KEY_TOKEN, token); } /** * Cookieにあるトークンをデコードして、UIDを取得 */ export function getCookie(req: IncomingMessage) { if (process.server && process.static) return; if (!req.headers.cookie) return; // requestヘッダーのCookieを取得する場合は、引数に追加 const cookie = new Cookies(req.headers.cookie); const token = cookie.get(KEY_TOKEN); if (!token) return; // jwtDecodeでトークンをデコードする const decodedToken = jwtDecode(token); if (!decodedToken) return; return decodedToken.user_id; } </code></pre> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト様"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E6%A7%98">参考にしたサイト様</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/basho/items/acd6a17bb6e2a2f7a932#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">SSRモードのNuxtでのFirebase認証 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/microcipcip/cookie-universal/tree/master/packages/cookie-universal-nuxt">cookie-universal/packages/cookie-universal-nuxt at master · microcipcip/cookie-universal</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/js-cookie/js-cookie">js-cookie/js-cookie: A simple, lightweight JavaScript API for handling browser cookies</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/S64/items/943e41f39e65dd3f4681">Vue.js / Nuxt.js で Cookies を isomorphic に扱えるライブラリを作った - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sauzar18/items/6eb3fe0218e3cf6badbc">Nuxt.jsでCookieを使って閲覧したデータを取得する方法 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/ooharabucyou/items/ec0163a099125e037f34#%E3%83%A6%E3%83%8B%E3%83%90%E3%83%BC%E3%82%B5%E3%83%AB%E3%81%AAcookie%E3%81%AE%E5%88%A9%E7%94%A8">Nuxt.js プロジェクトで便利だったモジュール・テクニックなど - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/shintarogit/items/794c6b97c7aa977c944e">Nuxt.js SSR で Cookieが送られない - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://logmi.jp/tech/articles/321246">Nuxt.jsを用いたプロダクト開発を通して得た知見 - ログミーTech</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://gist.github.com/isaumya/bfa2f5fc20beee897938c099762cc3e1">Nuxt JS: Keep user logged in with Firebase Auth and also fetch the necessary User data from Cloud Firestore and put it in your Vuex store if the user is already logged in before rendering the page</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15601 2019-12-12T22:32:10+09:00 2019-12-12T22:32:10+09:00 https://crieit.net/posts/Google-API GoogleからAPIキーが公開されているよとアラートが来た <p>Googleから怪しげなメールが届いた。本文を見てみると「Google Cloud Platform または API プロジェクト(プロジェクト名)の認証情報が不正使用された可能性があります」とのこと。</p> <p><a href="https://crieit.now.sh/upload_images/9b96c3d4b9b876ae035fecd6b003731b5df237526b2ec.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9b96c3d4b9b876ae035fecd6b003731b5df237526b2ec.png?mw=700" alt="" /></a></p> <p>ドキッとした。クラウド破産…!?</p> <h2 id="調査"><a href="#%E8%AA%BF%E6%9F%BB">調査</a></h2> <p>メール内の最初の方に、ここで一般公開されている、というURLが書かれていた。そこにアクセスしてみる。GitHubだった。するとたしかに僕のサイトっぽい名前のファイルがある。中身はバイナリファイルだったが、どうもここにAPIキーが含まれているらしい。</p> <h3 id="料金の確認"><a href="#%E6%96%99%E9%87%91%E3%81%AE%E7%A2%BA%E8%AA%8D">料金の確認</a></h3> <p>ちなみにプロジェクトはこのCrieitのプロジェクト。メールに色々と確認手順が書かれていたが、とりあえず読まずに料金がどうなっているだろうかを急いで確認してみた。今月の料金は……10円。とりあえず問題はなさそうでホッとした。が、そうこうしている間に何かしら利用されて今後料金が加算されていってしまう可能性がある。</p> <h3 id="何のAPIキーかを確認"><a href="#%E4%BD%95%E3%81%AEAPI%E3%82%AD%E3%83%BC%E3%81%8B%E3%82%92%E7%A2%BA%E8%AA%8D">何のAPIキーかを確認</a></h3> <p>細かくメールを見たかったがメール内にAPIキーが書かれていたので、ひとまずGCPのダッシュボードでそれが一体何のAPIキーなのかを調べてみることにした。</p> <p>確認方法はGCPコンソールの「APIとサービス」→「認証情報」。ここでAPIキーの一覧を見ることができる。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5df237a24bc53.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5df237a24bc53.png?mw=700" alt="" /></a></p> <p>問題となったのはこの画像の右下端に表示されている <code>Browser key (auto created by Google Service)</code> というもの。なんとなく見覚えがある。だいたいFirebaseのプロジェクトに関連付けた時に勝手に作られているものがこんな感じだったような気がした。Browser keyということなのでそれっぽい。</p> <h3 id="どこで使っているかを確認"><a href="#%E3%81%A9%E3%81%93%E3%81%A7%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%81%8B%E3%82%92%E7%A2%BA%E8%AA%8D">どこで使っているかを確認</a></h3> <p>とにかくどこで使っているかを確認してみることに。本番サーバーにログインし、.envを開いて検索してみた。しかし見つからない。</p> <p>ということでローカル上でプロジェクトをVSCode開いて検索してみる。すると見つかった。やはりFirebaseのAPIキーだった。これは公開しないとどうしようもないのと、ちょっとした機能で使っているだけのため環境分けをするのも面倒だったので直接コミットしてある。ということでそれを見つけた。</p> <h2 id="そもそもFirebaseのAPIキーとは"><a href="#%E3%81%9D%E3%82%82%E3%81%9D%E3%82%82Firebase%E3%81%AEAPI%E3%82%AD%E3%83%BC%E3%81%A8%E3%81%AF">そもそもFirebaseのAPIキーとは</a></h2> <p>Firebaseのプロジェクトを作ったことがある方であればよく見るものだと思うが、こういった設定。これのapiKeyというところが今回問題となったAPIキー。</p> <p><a href="https://crieit.now.sh/upload_images/9b96c3d4b9b876ae035fecd6b003731b5df23cb242b37.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9b96c3d4b9b876ae035fecd6b003731b5df23cb242b37.png?mw=700" alt="" /></a></p> <p>僕が普段良く作っているWebアプリケーションの場合、この設定はブラウザで動作するもののため、そもそも公開しなければならない。そのため後悔自体は問題がないはず。</p> <h3 id="何が問題か"><a href="#%E4%BD%95%E3%81%8C%E5%95%8F%E9%A1%8C%E3%81%8B">何が問題か</a></h3> <p>何が問題かというと、適切なセキュリティ設定が行われていない状態だと問題となる可能性がある。無料のSparkプランや月額制のFlameプランであればさほど問題にはならない。あるとしてもリソースを利用しつくされてサービスが止まるだけ。</p> <p>問題となるのは従量制のBlazeプラン。今回のCrieitのアプリケーションはこれだった。この場合、従量制なのでいたずらで無限ループなどをされると恐らくとんでもない料金になってしまう可能性がある。とはいえ僕の場合Realtime Databaseと匿名認証を有効化していただけなのでさほど問題にはなりにくかったかもしれない。Firestoreを利用している場合は危険な可能性がある。</p> <h3 id="セキュリティの設定"><a href="#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%81%AE%E8%A8%AD%E5%AE%9A">セキュリティの設定</a></h3> <p>恐らく、SNS認証+Firestoreのセキュリティルール設定済み、であれば問題にはならないだろう。問題になりそうなのは、匿名認証+Firestoreという場合。SNS認証は認証可能なURLを設定できるため、その設定さえしておけば不正利用は難しい。しかし匿名認証はURL指定ができないため、APIキーを利用すれば誰でもアプリケーションを動かすことが出来てしまう。このパターンだとまずそう。ちなみに今回はこのパターン。</p> <p>ではこの場合どうすればいいのか。メールにも書かれていた。</p> <blockquote> <p>API キーに API キー制限を追加します(該当する場合)。</p> </blockquote> <p>そう、先程スクショで説明した認証情報のページで、該当のAPIキーをクリックすると認証情報の設定ができる。ここでAPIキーの利用制限の設定ができる。GCPで直接自分で作ったキーの場合はできることを知っていたのだが、なんとなくFirebaseが勝手に作ったものは同じものという認識がなかったので気づかなかった。</p> <p>とりあえず、リファラーの設定をしておいた。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5df2405e67e64.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5df2405e67e64.png?mw=700" alt="" /></a></p> <p>何か間違っていたら機能が止まってしまうかもしれないが、今回は大したことのない機能のために使っているので気にせず設定した。これで一覧ページに元々出ていたアラートアイコンも消えた。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>そもそもこの設定はFirebaseの方で出来た方がいいんじゃないか…と常々思っていた。わりとこのあたりの事を知らない方もいるのでは。</p> <p>とにかくクラウドの利用は気をつけよう。そういえば料金アラートは設定していたので何にしろ何かあってもすぐ気づけたかもしれない。</p> <p>細かく試して書いたわけではないため、もしこの記事で間違えている情報などあれば情報提供いただけるとありがたいです。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15518 2019-10-30T21:18:16+09:00 2019-10-30T21:18:53+09:00 https://crieit.net/posts/Firestore-Nuxt-js Firestoreの簡易管理ツールをNuxt.jsでつくってみた <p>Firestore、とっても便利ですが、Firebaseのコンソールがイケてないので、<br /> ローカルで動かせる簡易の簡易ツールを作ってみました。Nuxt.js製です。</p> <p>Firebase Admin SDKを使ってるので、<strong>秘密鍵を配置すればOK</strong>。<br /> <strong>セキュリティルールの変更も不要</strong>です。</p> <p>動いているところはこんな感じ。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/e4d2f323-d2d7-39fb-e178-dc135f2e21f2.gif" width="680"/></p> <p>GitHubで公開してます。ただ書きなぐりなので、ソースはイケてないです。。<br /> まだα版くらいなので、機能は限定的です。。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/memory-lovers/simple-firestore-admin">memory-lovers/simple-firestore-admin: Simple Firestore Admin</a></p> <h3 id="なんで作ったか"><a href="#%E3%81%AA%E3%82%93%E3%81%A7%E4%BD%9C%E3%81%A3%E3%81%9F%E3%81%8B">なんで作ったか</a></h3> <p>Firestoreを使った<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">Webサービス</a>を作ってみたところ、<br /> ちょこっとしたDBの変更にもデータが多かったり、1ドキュメントのサイズが大きいと、<br /> <strong>Firebaseコンソールだと重い感じに</strong>。。</p> <p>いろいろ見ていると、Webサービスなどもあるのですが、<br /> セキュリティールールを変えないといけないなど、設定がめんどくさいなと。。</p> <p>なので、 <strong>設定が少なくて、軽い管理ツールがほしい</strong>、と思い、作ってみました。</p> <h3 id="できること"><a href="#%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%93%E3%81%A8">できること</a></h3> <p>とりあえず、いま自分が必要な機能だけなので、最小な感じ。</p> <ol> <li>コレクションの検索: <code>collection().where().orderBy()</code></li> <li>ドキュメントの更新: <code>doc().update()</code></li> <li>ドキュメントの削除: <code>doc().delete()</code></li> </ol> <h3 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h3> <h4 id="1. インストール"><a href="#1.+%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">1. インストール</a></h4> <p>インストールは、GitHubからクローンしてください。</p> <pre><code class="console">$ git clone [email protected]:memory-lovers/simple-firestore-admin.git $ cd simple-firestore-admin </code></pre> <h4 id="2. セットアップ"><a href="#2.+%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97">2. セットアップ</a></h4> <h5 id="2-a. 秘密鍵の取得"><a href="#2-a.+%E7%A7%98%E5%AF%86%E9%8D%B5%E3%81%AE%E5%8F%96%E5%BE%97">2-a. 秘密鍵の取得</a></h5> <p>Firebaseコンソールから秘密鍵を生成して配置してください。</p> <p>左上の歯車から「プロジェクトの設定」をクリックし、</p> <p><img width="306" alt="スクリーンショット_2019-10-30_19_50_30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/8d3f5f8e-a641-3f6e-95e7-0889f86b9262.png"></p> <p>「サービス アカウント」タブの下にある、「新しい秘密鍵を生成」をクリックすると、<br /> 秘密鍵のjsonファイルをダウンロードできます。</p> <p><img width="680" alt="スクリーンショット_2019-10-30_19_50_20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/8297e57c-12e6-b63e-c46b-3fbd0b8af8a9.png"></p> <p>この例では、<code>./credential.json</code>としてます。</p> <h5 id="2-b. 設定ファイル(.env)の作成と設定"><a href="#2-b.+%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%28.env%29%E3%81%AE%E4%BD%9C%E6%88%90%E3%81%A8%E8%A8%AD%E5%AE%9A">2-b. 設定ファイル(.env)の作成と設定</a></h5> <p><code>env_sample</code>という設定ファイルの雛形があるので、<code>.env</code>にコピーしてください。</p> <pre><code class="console">$ cp env_sample .env </code></pre> <p>コピーしたら、<code>.env</code>を開いて、配置した秘密鍵の相対パスを設定してください。</p> <pre><code class="diff"> # Copy this file with file name '.env' ## ex. CREDENTIAL_PATH=./credential.json - CREDENTIAL_PATH=YOUR_CREDENTIAL_PATH + CREDENTIAL_PATH=./credential.json </code></pre> <p>これで設定はOK。</p> <h4 id="3. 起動"><a href="#3.+%E8%B5%B7%E5%8B%95">3. 起動</a></h4> <p>Nuxt.js製なので、こんな感じでビルドと起動をしてもらえればOK。</p> <pre><code class="console"># パッケージのインストール $ npm install # ビルド $ npm run build # 起動 $ npm run start </code></pre> <p><code>http://localhost:3000</code>で起動するので、<br /> ブラウザで操作できるようになります。</p> <h3 id="開発時のポイント: serverMiddlewareでfirebase-admin"><a href="#%E9%96%8B%E7%99%BA%E6%99%82%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%3A+serverMiddleware%E3%81%A7firebase-admin">開発時のポイント: serverMiddlewareでfirebase-admin</a></h3> <p>開発時にハマったポイントとしては、<br /> firebase-adminがNode.js上でしか使えなかったことです。。</p> <p>当初はNuxtアプリから直接firebase-adminを呼ぼうとしていましたが、<br /> <strong>Node.js上でしか扱うことができない</strong>ことをあとから知り。。</p> <p>Expressなどからだと呼ぶことができるので、<br /> Nuxtの<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/api/configuration-servermiddleware/#servermiddleware-%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3">serverMiddleware</a>を使って、<br /> <strong>APIサーバ的なものを内部で持たせるようにしてみました</strong>。</p> <p>serverMiddlewareの使い方はこんな感じ。</p> <h4 id="Express関連のコード"><a href="#Express%E9%96%A2%E9%80%A3%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89">Express関連のコード</a></h4> <p>簡易化してますが、こんな感じのコードを、<code>server/index.ts</code>に配置</p> <pre><code class="typescript">import { Request, Response } from "express"; import * as firestore from "./modules/firestore"; import bodyParser from "body-parser"; import express from "express"; const app = express(); // setup body-parser app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); // select app.post("/api/select", async (req: Request, res: Response) => { // firestore.collection("users").get()的な処理をする const result = await firestore.fetchSelect(req.body); res.send(result); }); export default app; </code></pre> <h4 id="nuxt.config.tsで設定"><a href="#nuxt.config.ts%E3%81%A7%E8%A8%AD%E5%AE%9A">nuxt.config.tsで設定</a></h4> <p>先ほど作成した<code>server/index.ts</code>をserverMiddlewareとして利用するように設定</p> <pre><code class="typescript">import { Configuration } from "@nuxt/types"; const config: Configuration = { serverMiddleware: ["~/server"], }; export default config; </code></pre> <p>別にサーバを建てると、開発時にCORS対策でaxiosでproxyしないといけなかったりしますが、<br /> serverMiddlewareを使うとそういった設定も不要に(<em>´ω`</em>)</p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>とりあえず、必要な部分だけを作っただけなので、<br /> バグもイケてないところもまだまだありますが、よかったら遊んでみてもらえれば。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/memory-lovers/simple-firestore-admin">memory-lovers/simple-firestore-admin: Simple Firestore Admin</a></p> <p>Firestoreの管理画面作りたいな。。と思ったら、<br /> こんな感じでserverMiddlewareを使うのが良さそうです(<em>´ω`</em>)</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>冒頭で話していた開発しているWebサービスです!<br /> だいぶデータも増えてきたので、こういう管理画面が欲しくなってきました。。</p> <p>積読用の読書管理アプリ 『積読ハウマッチ』<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイトさま"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%95%E3%81%BE">参考にしたサイトさま</a></h1> <ul> <li><a href="https://crieit.net/posts/Nuxt-Express">Nuxt+Expressのプロジェクト作成で良さそうなのは? - Crieit</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sauzar18/items/6255877457b29c5f7421">Nuxt.js+Express.jsにTypeScriptを導入してみた - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sauzar18/items/d68c13f200b3bc671679">Nuxt.js v2.9にTypeScriptとExpress.jsを対応してみた - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15506 2019-10-26T09:06:51+09:00 2019-10-26T09:06:51+09:00 https://crieit.net/posts/Nuxt-js-SPA-Firebase-Web-5db38e1b32bf6 Nuxt.js(SPA)+FirebaseのWebアプリで初マイグレーションをしてけど、いろいろ失敗した話。。 <p>これはただの失敗談です。。役に立つかわからないけど、誰かの参考になるといいな。。</p> <p>Nuxt.js+Firebaseで開発した<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">Webサービス</a>をリリースして2ヶ月目くらい。<br /> サービス止めて、初マイグレーションしてときの失敗談です。</p> <h2 id="サービスを止めてやりたかったこと"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E6%AD%A2%E3%82%81%E3%81%A6%E3%82%84%E3%82%8A%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">サービスを止めてやりたかったこと</a></h2> <p>いままではちょこっとした変更が多かったため、<br /> サービスを止める必要なく、Nuxtアプリの更新だけで十分でしたが、<br /> 新しい機能を追加する際に、Firestoreのスキーマを変える必要が出てきました。</p> <p>やりたいことは、<br /> 「 <strong>今までのデータを読み取って、別の形式に再構成する</strong> 」<br /> という感じのことで、止めないとダメかなと。</p> <h2 id="メンテンナンスのときの手順"><a href="#%E3%83%A1%E3%83%B3%E3%83%86%E3%83%B3%E3%83%8A%E3%83%B3%E3%82%B9%E3%81%AE%E3%81%A8%E3%81%8D%E3%81%AE%E6%89%8B%E9%A0%86">メンテンナンスのときの手順</a></h2> <p>手順はこんな感じ。</p> <ol> <li>メンテナンス画面に遷移するようにmiddlewareを変更</li> <li>firestoreのルールを全部ブロックするように変更</li> <li>スキーマ変更処理を実行</li> <li>メンテナンス画面を解除し、hostingに最新版をアップロード</li> <li>firestoreのルールをもとに戻す</li> </ol> <p>ポイントとしては、</p> <ul> <li>ページを移動したら、<strong>middlewareでメンテナンス画面に遷移</strong>する</li> <li>クライアントから変更されないように、<strong>全部ブロックするルールに変更</strong>する</li> </ul> <p>の2点くらい。</p> <p>結果、かなりの失敗をしました。。</p> <h1 id="やらかした失敗と反省点"><a href="#%E3%82%84%E3%82%89%E3%81%8B%E3%81%97%E3%81%9F%E5%A4%B1%E6%95%97%E3%81%A8%E5%8F%8D%E7%9C%81%E7%82%B9">やらかした失敗と反省点</a></h1> <p>ダメだったのは以下の2点。</p> <ol> <li><strong>時間がかかりすぎ</strong>(予想5h以内→実際11h)</li> <li><strong>メンテナンス画面がちゃんと出ていなかった</strong></li> </ol> <h2 id="1. 時間がかかりすぎ問題"><a href="#1.+%E6%99%82%E9%96%93%E3%81%8C%E3%81%8B%E3%81%8B%E3%82%8A%E3%81%99%E3%81%8E%E5%95%8F%E9%A1%8C">1. 時間がかかりすぎ問題</a></h2> <p>一番の失敗は時間かかりすぎです。。<br /> 予想以上に時間が。。予想5h以内→実際11h</p> <p>対象のスキーマが、</p> <pre><code>本 - 履歴 - ユーザ </code></pre> <p>多対多の関係を持つ構造を持っていて、<br /> 本を起点に履歴データの構造を変更するような処理だった。</p> <p>規模としては、本の件数が47670ドキュメントで、履歴が58144ドキュメント。</p> <p>実際に実行してみたところ、速度が上がったり下がったりで、<br /> 1時間に5000冊位な感じ。。これだけで10時間近く。。</p> <h3 id="振り返ってみて"><a href="#%E6%8C%AF%E3%82%8A%E8%BF%94%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6">振り返ってみて</a></h3> <p>やってみていくつか良くない感じだったなと。。<br /> 振り返ってみて、改善できそうなのは以下の2点な感じ。。</p> <h4 id="a. メンテナンスでは差分部分のみ反映する"><a href="#a.+%E3%83%A1%E3%83%B3%E3%83%86%E3%83%8A%E3%83%B3%E3%82%B9%E3%81%A7%E3%81%AF%E5%B7%AE%E5%88%86%E9%83%A8%E5%88%86%E3%81%AE%E3%81%BF%E5%8F%8D%E6%98%A0%E3%81%99%E3%82%8B">a. メンテナンスでは差分部分のみ反映する</a></h4> <p>全量を読み込んで、反映していたので、かなりの時間が。。</p> <p>とはいえ、1日の変更であれば、そこまで多くないので、<br /> <strong>事前に全量の変更を用意して</strong>おき、メンテナンスにしてから、<br /> <strong>差分があるところだけを更新</strong>をするなどのほうが良かった気がしてる。。</p> <p>次はそうしよう。。</p> <h4 id="b. 処理の並列実行"><a href="#b.+%E5%87%A6%E7%90%86%E3%81%AE%E4%B8%A6%E5%88%97%E5%AE%9F%E8%A1%8C">b. 処理の並列実行</a></h4> <p>内部の処理も<code>Promise.all</code>を使ってない部分がいくつか。。</p> <p><code>foreach</code>で実行している部分を置き換えて、<br /> <strong>並列実行にするだけでも改善されそうな感じ</strong>。。</p> <p>この記事がわかりやすい感じだった。<br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/euxn23/items/b9bd8bf28fe3ca707fe3#%E4%B8%A6%E5%88%97%E3%81%A8%E7%9B%B4%E5%88%97%E3%81%AE%E4%BD%BF%E3%81%84%E5%88%86%E3%81%91%E3%81%A8%E6%B3%A8%E6%84%8F%E7%82%B9">Node.js で Promise の直列実行と並列実行、同時実行数の制御 - Qiita</a></p> <h3 id="2. メンテナンス画面が出てない"><a href="#2.+%E3%83%A1%E3%83%B3%E3%83%86%E3%83%8A%E3%83%B3%E3%82%B9%E7%94%BB%E9%9D%A2%E3%81%8C%E5%87%BA%E3%81%A6%E3%81%AA%E3%81%84">2. メンテナンス画面が出てない</a></h3> <p>メンテナンスをはじめてから、GoogleAnalyticsを見てると、リアルタイムユーザがちらほら。<br /> 終わってからも、メンテナンス画面じゃないとの報告もちらほら。。</p> <p>Firebase Hostingにアップロードしてもキャッシュが残っているとだめだったもよう。。</p> <h3 id="振り返ってみて"><a href="#%E6%8C%AF%E3%82%8A%E8%BF%94%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%A6">振り返ってみて</a></h3> <p>振り返ってみて、改善できそうなのは以下の2点な感じ。。</p> <h4 id="a. PWAモジュールを使ってnetworkファーストで取得する"><a href="#a.+PWA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6network%E3%83%95%E3%82%A1%E3%83%BC%E3%82%B9%E3%83%88%E3%81%A7%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">a. PWAモジュールを使ってnetworkファーストで取得する</a></h4> <p>以下の記事の前半で書かれている通り、<strong>キャッシュ戦略を設定することで解決</strong>できそう。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://qiita.com/hecateball/items/29f726430d204c964f04">Nuxt.js(SPA)とFirebaseで強制リビジョン(バージョン)アップするならPWAモジュールを使おう - Qiita</a></p> <h4 id="b. Remote Configを使って切り替える"><a href="#b.+Remote+Config%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E5%88%87%E3%82%8A%E6%9B%BF%E3%81%88%E3%82%8B">b. Remote Configを使って切り替える</a></h4> <p>同じく上記の記事で書かれている通り、<strong>Remote Configを使う</strong>のが良さそう。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/remote-config">Firebase Remote Config  |  Firebase</a></p> <p><strong>Remote Configの値によって、メンテナンス画面の表示を切り替えれるようにしておけば</strong>、<br /> Firebaseのコンソール上での変更で、切り替えれるようにできる感じ。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>やってみてわかることも多いですが、なかなかつらい感じ。。</p> <p>Firebase/Firestore関連の運用系の話はあまり見ないので、<br /> とりあえず書いてみましたが、誰かの役に立てば。。</p> <p>こんな方法もあるよ!こっちのほうがいいよ!などあれば、<br /> コメントもらえるとうれしいです(<em>´ω`</em>)</p> <h2 id="ほかの失敗談"><a href="#%E3%81%BB%E3%81%8B%E3%81%AE%E5%A4%B1%E6%95%97%E8%AB%87">ほかの失敗談</a></h2> <p>以前にもこんなのを書きました。<br /> 初級向けの話が多いですが、実際にやってみての体験談的な話。<br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/8c1d1240c3aa200cbec0">Nuxt.js(SPA)+Firebaseで積読用の読書管理サービスを作ってみたときにハマったこと... - Qiita</a><br /> - <a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/ef7dd47519403cd9bcf2">Firebaseで作ったWebサービスを3ヶ月運用してみて、ハマったこと・知っておきたかったこと - Qiita</a></p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>やらかしたWebサービスはこちら。。<br /> 積読用の読書管理アプリ 『積読ハウマッチ』<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてください〜</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> きらぷか@積読ハウマッチ/SSSAPIなど