素のJSでタイマー機能を作ってみた④ firebaseからデータを取得して時間の処理

はじめに

昨日に続きJSタイマーアプリを作っていきます。

素のJSでタイマー機能を作ってみた③ firebaseからデータを取得して時間の処理

✅今回したいこと

 

・詳細表示&編集機能を実装

・ログイン機能実装

リファクタリング

・index.htmlとrecord/index.htmlのjsファイルの切り分け

Firebaseのロジック部分を同一のファイルにすると、not foundの出るので修正。

・firebase保存時のテキスト修正(赤線部分いらない。。。)

これで来ていたと思ったらケアレスミス。

mktxt.replace("※#1~#3の3つまで保存できます", '');
mktxt.replace("OKで保存が完了します", '');

修正。確認したところ値を代入しそびれていました。笑

mktxt = mktxt.replace("※#1~#3の3つまで保存できます", '');
mktxt = mktxt.replace("OKで保存が完了します", '');

詳細表示・編集機能

こんな感じで実装。詳細表示は取得したデータを表示するだけなので簡単。

更新(ここでは編集)機能はtextareaタグのtextContentを再度取得してfirebaseにupdate。

で行けると思いきや。。。

textContentの値が更新されない!!再取得できていない。。。

参考:JavaScript テキストエリアの値を取得/設定するサンプル

調べたところvalueタイプだと取得できました。不思議な挙動。。。

editBtn.addEventListener('click',() => {
  db.ref('/'+(Number(lenDB))).update({
    txt: lastLog.value
  },l(lastLog.value))
},false);

🙄これで実装できました◎

ここで、学習時間を全体のテキスト側で修正しても時間のキーは別だから合計時間に連動しないことに気付く。。。

⇒時間の修正の実装がセレクトタグ入れたりちょっと大変なので、時間の修正はできない仕様に変更

⇒かつ、記録(前回の記録)に時間は保存せず表示もされないように変更

🙄他にも厳密には、タイマー画面上の前回の課題とも連動しないなど編集機能入れると仕様面が要注意になりますね。。。

前回の学習時間を取得

let lastTime = timeDB.slice(timeDB.length-1);

※合計時間取得の際に用いたpushやsplitメソッドは配列追加したあと元の配列を壊してしまうので、破壊する前に記述。

こんな感じになりました。改行がfirebaseに保存時しっかりエスケープ処理などされていて感動しました。

ログイン機能

参考:JavaScript で Google ログインを使用して認証する

firebaseからAuthenticationのGoogleを有効にします。

index.htmlに以下追記

<script src="https://www.gstatic.com/firebasejs/7.15.1/firebase-auth.js"></script>

以下をfirebaseConfig.jsに追記。

let provider = new firebase.auth.GoogleAuthProvider();

公式に則り以下をコピペ

firebase.auth().signInWithPopup(provider).then(function(result) {
  // This gives you a Google Access Token. You can use it to access the Google API.
  var token = result.credential.accessToken;
  // The signed-in user info.
  var user = result.user;
  // ...
}).catch(function(error) {
  // Handle Errors here.
  var errorCode = error.code;
  var errorMessage = error.message;
  // The email of the user's account used.
  var email = error.email;
  // The firebase.auth.AuthCredential type that was used.
  var credential = error.credential;
  // ...
});

するとポップアップウィンドウが一瞬で消えてしまう。。。

コンソールログで確認すると以下とのこと。

auth/operation-not-supported-in-this-environment
This operation is not supported in the environment this application is running on. “location.protocol” must be http, https or chrome-extension and web storage must be enabled.

訳)この操作は、このアプリケーションが実行されている環境ではサポートされていません。 “location.protocol”はhttp、https、またはchrome-extensionで、Webストレージが有効になっている必要があります。

当ブログのドメインにディレクトリを作って本番環境で試すもこれまた一瞬で消える現象。

Uncaught jo {code: "auth/unauthorized-domain", message: "This domain (chobimusic.com) is not authorized to …se console -> Auth section -> Sign in method tab.", a: null}

他の直下で余っているドメインでも同様にエラー

nexttick.js:37 Uncaught jo

ためしにfirebaseのサーバーを立ち上げてみると成功◎

firebase serve

リダイレクトだとアクセスするとこうなる。

firebase.auth().signInWithRedirect(provider);

ポップアップだとこうなる。

firebase.auth().signInWithPopup(provider);

🙄試してみないとわからないことが沢山ありますね。

ログインマストで使うアプリではないので、クリックイベントでポップアップが起動する仕様にしました。

loginBtn.onclick = () => firebase.auth().signInWithPopup(provider);

🙄ログインだけなら1行で実装できちゃうの凄い。

ついでにログアウトボタンも。

logoutBtn.onclick = () => {
  firebase.auth().signOut().then(() => {
    l("Sign-out successful.")
  }).catch(function(error) {
    l("An error happened.")
  });
}

名前の取得

user = result.user.displayName;

メールアドレスの取得

email = result.user.email;

登録されたユーザーは以下のように確認可能。

公式を参考にuidでIDを取得できることも確認。

uid = result.user.uid;

以上を元にログイン後にユーザー名が表示されるようにしました。

ログイン状態保持

次はページを更新してもログイン状態を保持できるようにしていきます。

参考:Vue vuexでfirebaseのログイン保持認証状態の永続性(公式)

firebase.auth().setPersistence(xxxxx)で認証状態の永続性を決める。

デフォルトは更新すると消えるのでfirebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE)になっていると思われる。

上記を踏まえて以下をこの部分に追記。

firebaseConfig = {
apiKey: "~~~~
};
firebase.initializeApp(firebaseConfig);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION); // add
firebase.analytics();
let provider = new firebase.auth.GoogleAuthProvider();

以下でログイン中のユーザーを取得して確認。

firebase.auth().currentUser

リロードしても保持されていることを確認◎

ってことで保持されている場合はリロードしてもログインユーザー名が表示されるようにしていく。

と思いきやスクリプトでは確認できないのにブラウザでは保持されるという事象が発生。

firebaseの認証が非同期でされているのが原因でした。当たり前ですね。。。取得後に実行するように修正したところ無事機能しました。以下備忘録です。

スクリプトでこれやるとnull

l(firebase.auth().currentUser);

ブラウザでやるとしっかり出力される。。。

デプロイしても同じ挙動。

ログイン状態でリロードしてもnullになる。コンソール上で直接検証するとログイン・ログアウトに伴ってしっかりcurrentUserが変わるのに。

セッション上にはリロードしても残るので、これを上手くJS側から取得してあげないといけないのかもしれない。

試しにローカルストレージに保存されるように永続性のタイプを変更

firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL);

ローカルストレージとセッション

ローカルストレージだと明示的に消さないと消えない。

セッションはブラウザを消して、再度アクセスすると残らない。

また、セッションだと値が明示的にブラウザに保持されるけど、ローカルストレージだとURLになる。

コンソールでは以下を入力することで確認できる。

localStorage
sessionStorage

✅セッションストレージからログイン状態の取得

セッションの方が扱いやすいので、データを取得しようとするも、セッションストレージに保存されたキーをコンソールに打つと:が入っていて出力できませんとなる。

調べた末、これで実装。

if(sessionStorage.key(0) != null ){
  sessKey = sessionStorage.key(0)
  l(sessKey);
  sessValue = sessionStorage.getItem(sessKey);
  l(sessValue);
  sessValue = JSON.parse(sessValue);
  l(sessValue);
  userName.textContent = `ログインユーザー名:${sessValue.displayName}`;
  userPhoto.src = sessValue.photoURL;
  userPhoto.style = "border-radius: 50%;width: 34px;";
}

ついでにログアウトすると非表示になるようにクリックイベントを修正。

logoutBtn.onclick = () => {
  firebase.auth().signOut().then(() => {
    l("Sign-out successful.")
    userName.textContent = ``;
    userPhoto.src = "";
    userPhoto.style = "";
  }).catch(function(error) {
    l("An error happened.")
  });
}

🙄セッションストレージのデータがJSON形式なので変換するのがポイントですね!!

今回は、ページの描画とログイン状態がずれるのは嫌なので永続化はセッションまでとします。

データベースにアクセス権を付ける

データベースのルールをfirebase上から変更します。

誰でも読み書きできる状態

デフォルト

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

※falseだとデータが一切読み取れない。

これを認証したら誰でもデータが取得できる状態に変更します。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

ログイン後リロードしないとデータが読み込まないことに気付いたので、ログイン&ログアウト共に処理後に、リロードされるように以下を追記。

※asyncの処理を順番通りきれいに並べればおそらくできるけど楽だったので。。。

location.reload();

🙄これセッションじゃないと成立しない処理ですね。。。

ちなみにローカルストレージでも以下のようにデータの取得はできました。

と、ここで。。。

firebase.auth().currentUserが機能しないのは、firebaseの処理が非同期だからということにいまさら気付きました。。。

ってことで今までのスクリプトを思いっきり変更。

async().then(
  () => {
    l("firebaseのデータ取得完了");
    // ログインユーザーの取得
    if( firebase.auth().currentUser != null){
      user = firebase.auth().currentUser;
      userName.textContent = `ログインユーザー名:${user.displayName}`;
      userPhoto.src = user.photoURL;
      userPhoto.style = "border-radius: 50%;width: 34px;";
    }

これでローカルストレージでもセッションストレージでも同じ挙動が得られることを確認できました◎

ログインユーザーごとにデータを処理する

参考:Firebase Realtime Database ルールについてデータベース ルールを使ってみる

次に最後と言っても過言ではない認可の実装をしていきます。

認可実装するには諸々機能を実装し過ぎたため、一旦ステイしました。メモ帳なりで作り直します。。。

まずはクライアント側。

データの追加はタイマー側。

db.ref('/'+(Number(lenDB)+1)).set({
  user:firebase.auth().currentUser.uid,
  date:dat.toLocaleDateString(),
  time: report.value,
  txt: mktxt,
  task: task,
},l("firebaseSave"));

こんな感じでユーザーIDを保存できる仕様に変更。

次にfirebase側。現状のルール。

{
  "rules": {
    ".read": "auth.uid != null",
    ".write": "auth.uid != null"
  }
}

公式見るとルールはこんな感じ。

// These rules grant access to a node matching the authenticated
// user's ID from the Firebase auth token
{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }
  }
}

と、これに変更した途端何も読み込まなくなりました。。。笑

⇒書き方覚えないとこれはわからない。

ルールを戻してとりあえずデータを1から作り直し。

公式のここが参考になります。参考:基本的なセキュリティ ルール

{
  "rules": {
    "some_path": {
      "$uid": {
        // Allow only authenticated content owners access to their data
        ".read": "request.auth.uid == uid"
        ".write": "request.auth.uid == uid"
      }
    }
  }
}

ところがこれエラー。その前もカンマなしでエラーだし公式。。。Unknown variable ‘request’.とのこと。

色々探してこれに巡り合う。参考:firebase realtime database でよく使う rule

{
  "rules": {
    "user-data": {
      "$userId": {
        ".read": "auth.uid == $userId",
        ".write": "auth.uid == $userId",
      }
    }
  }
}

これを試すも変数を定義していないのでエラー。

変数の使い方がいまいちピンとこない。DB上にはuserという名前で保存した。

なんとなくDBの階層に一致するのかなと思って以下で保存。ルール自体の保存はできたが読み込まない。

{
  "rules": {
    "$userId": {
      ".read": "auth.uid == $userId",
      ".write": "auth.uid == $userId",
    }
  }
}

DBの読込側のURLから見直す必要がありそう。

DBを一旦こうする

ルールはこうする

{
  "rules": {
    "users": {
      ".read": "auth.uid != null",
      ".write": "auth.uid != null",
    }
  }
}

コードはこうする

db.ref('/users').on('value', (snapshot) => {

取得できた!!

🙄ルールはパスと一致している。usersをuserにしたら取得できなくなったのでそういうことですね!!

users/user_id/数字って感じで作っていこうと思います。

保存

db.ref('/'+(Number(lenDB)+1)).set({

修正

db.ref(`/users/${firebase.auth().currentUser.uid}/1`).set({
  date:dat.toLocaleDateString(),
  time: report.value,
  txt: mktxt,
  task: task,
},l(`firebaseSave${firebase.auth().currentUser.uid}`));

保存実行できてこうなりました。

🙄ここで、諸々の変数.プロパティなど含めて修正が必要なことに気付いたので一旦ここまでにします。

躓いた点

✅CSSが適用されない

classNameで適用されないのでとりあえずstyleを渡す形で実装しました。

✅ローカルストレージのURLからユーザーデータを取得できない

localstorageに保存されたホストドメインのJavascriptでしかアクセスできない 参考

上記の点が理由かもしれない。

おわりに

今回はfirebaseが非同期だということを忘れてとても初歩的なハマり方をしてしまいました。

逆にローカルストレージなどの理解が深まったのでよかったかも??笑

また、認可の仕組みを理解するには機能を実装し過ぎたので、一旦メモアプリなどミニマム実装で作り直す。

不具合

.lengthで数をカウントしてその値を用いて直前の値を取得していたが、数がずれた(削除してしまった)場合ずれる。

備忘録

LocalStorage, sessionStorage

Cookies(クッキー), document.cookie

コメントを残す