• 虹色ミツバチ
  • freoカスタマイズメモ、テンプレート・プラグイン配布/officeTIPS
検索プラグイン

> Entry

ExcelのリストをJSONに変換するマクロ

JSONとはJavaScript Object Notationの略で、配列で書かれたテキスト形式のファイルです。
JSONファイルをサーバーに上げるとHTMLに出力することもできるので、HTMLタグを知らない人でもJSONファイルを変更できれば簡単にサイトを更新することができます。

JSONファイルには決まった書式ルールがあるので、不慣れな人が作成するのは少し難しいかもしれません。
今回はExcelのリストからJSONファイルを生成するマクロを作成してみました。

Excelリスト例

たとえばこんなリストがあるとします。

Excelリスト例

このリストをJSONに生成するマクロは下記の通りです。

VBA例
Sub make_json()

ThisWorkbook.Activate
'変数定義
    Dim fileName, fileFolder, fileFile As String
    Dim isFirstRow As Boolean
    Dim i, u As Long
    
'最終行取得
    Dim maxRow, maxCol As Long
    If Len(ActiveSheet.Range("A2").Value) = 0 Then
        maxRow = 0
    ElseIf Len(ActiveSheet.Range("A3").Value) = 0 Then
        maxRow = 1
    Else
        maxRow = ActiveSheet.Range("A1").End(xlDown).Row
    End If
'最終列取得
    maxCol = Range("A1").End(xlToRight).Column
    

'JSONファイル定義
    fileName = "list.json"                 'JSONファイル名を指定
    fileFolder = ThisWorkbook.Path         '新しいファイルの保存先フォルダ名
    fileFile = fileFolder & "\" & fileName '新しいファイルをフルパスで定義
    
    '同名のJSONファイルが既にある場合は削除する
    If Dir(fileFile) <> "" Then
        Kill fileFile
    End If

'JSON作成
    'オブジェクトを用意する
    Dim txt As Object
    Set txt = CreateObject("ADODB.Stream")
    txt.Charset = "UTF-8"
    txt.Open
    
    'JSON開始タグ
    isFirstRow = True
    txt.WriteText "[" & vbCrLf, adWriteLine
    
    'リストをオブジェクトに書き込む
    For i = 2 To maxRow
        '1行目か確認して2行目以降の場合は行頭に","を挿入
        If isFirstRow = True Then
            isFirstRow = False
        Else
            txt.WriteText "," & vbCrLf, adWriteLine
        End If
        
        '行の開始タグを挿入
            txt.WriteText vbTab & "{" & vbCrLf, adWriteLine
        
            For u = 1 To maxCol
                '最終列でない場合は","を挿入
                If u = maxCol Then
                    txt.WriteText vbTab & vbTab & """" & Cells(1, u).Value & """" & ":" & """" & Cells(i, u).Value & """" & vbCrLf, adWriteLine
                Else
                    txt.WriteText vbTab & vbTab & """" & Cells(1, u).Value & """" & ":" & """" & Cells(i, u).Value & """" & "," & vbCrLf, adWriteLine
                End If
            Next u
        
        '行の閉じタグを挿入
        txt.WriteText vbTab & "}", adWriteLine
    Next
    
    'JSON終了タグ
    txt.WriteText vbCrLf, adWriteLine
    txt.WriteText "]" & vbCrLf, adWriteLine
    
    'オブジェクトの内容をファイルに保存
    txt.SaveToFile fileFile
    
    'オブジェクトを閉じる
    txt.Close
    

    MsgBox ("ファイルを生成しました。")
    

End Sub

最終行と最終列を自動取得し、JSONを生成します。
JSONのファイル名はVBA側で指定しています。

同名のファイルがある場合は削除して新規に保存します。
項目名として利用するため、リストシートの1行目にはそれぞれの項目名を入力してください。

JSON生成

上記のマクロを実行すると、下記のようなJSONが生成されます。

[
    {
        "num":"1",
        "name":"イチゴ",
        "url":"http://hoge.net"
    },
    {
        "num":"2",
        "name":"リンゴ",
        "url":""
    },
    {
        "num":"3",
        "name":"バナナ",
        "url":"http://hoge.com"
    }
]

閉じタグの位置や区切り文字の位置・有無など、ルールに沿った書き方をしないといけないので、不慣れな人が触る場合は、このように自動生成するマクロなどを活用するといいかもしれません。

続きを読む

jQueryでJSONをHTMLに出力する方法

JSONとは、JavaScript Object Notation の略で、XMLのような配列形式のデータフォーマットです。
XMLよりも書き方がシンプルなので初心者でも簡単にデータを作成でき、また軽量なので大きなデータの読み込みにも順応できます。

CMSなどを利用していないスタティックなサイトであっても、JSONに入っているデータをHTML出力する設定にしていれば、JSONさえ編集できれば、HTMLタグを知らない人でも簡単にサイト上の情報を更新できます。

独自のJSON以外にも、facebookのAPIやGoogleのfirestoreなど、JSONでデータを発行しているケースが増えてきました。
JSONからデータを読み込んでHTMLに出力する方法を紹介します。

JSON例

たとえば、こんなJSONがあるとします。

[
    {
        "num":"1",
        "name":"イチゴ",
        "url":"http://hoge.net"
    },
    {
        "num":"2",
        "name":"リンゴ",
        "url":""
    },
    {
        "num":"3",
        "name":"バナナ",
        "url":"http://hoge.com"
    }
}

URLが入っていた場合はリンク付きのテキストを、URLが入っていなかったときはテキストのみを出力してみます。

HTML例
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<ul id="list">
<!-- ここに出力します -->
</ul>


出力にjQueryを利用するので読み込んでおいてください。
他に、JSONから出力したデータの受け皿を予め用意しておきます。

JavaScript例
$(function(){
    json = "./list.json";
    target = $('#list');
    $.getJSON(json, function(data,status){
        for(var n in data) {
            var text = '<li>';
            if (data[n].url){
                line = '<a href="'+data[n].url+'" target="_blank"><span>'+data[n].name+'</span></a>';
            }else{
                line = '<i><span>'+data[n].name+'</span></i>';
            }
            text = text+line+'</li>';
            $(target).append(text);
        }
    });
});

JSONをgetJSONで取得し、取得したdataをforで回します。
最後にappendしてHTMLに出力しています。

最後に

JSONファイルを作成できない人のために、Excelの表からJSONを作成するExcelマクロ例も作ってみました。
よろしければご活用ください。

続きを読む

Gitのよく使うコマンドと基本的な使い方

Gitはコマンドプロンプトなどのコマンドラインで使用します。
私はVisual Studio Codeで作業しているのでaddやcommitなどは基本的にVSCodeがうまいことやってくれますが、だからこそコマンドラインを使う時に忘れてしまいがちなので、使う頻度の高いコマンドをメモするついでにそれぞれどんな作業をしているのかメモしておきます。

リポジトリを作成する
$ git init

空のローカルリポジトリを作成します。

リポジトリをクローンする
$ git clone [url]

既存の Git リポジトリのコピーをローカルリポジトリに作成します。
クローンしたリポジトリは自動的にリモートリポジトリに設定されます。

ワーキングツリーの状況を確認する
$ git status

コミットするべきファイルがあるかどうか、ワーキングツリー(作業ディレクトリ)の状況を確認します。
前回のコミットから変更されたファイルがある場合、一覧が表示されます。

コミットする

ファイルの変更履歴を記録(コミット)するには段階を踏む必要があります。

ファイルをインデックスにステージングする
//ファイルやディレクトリをインデックスに登録する
$ git add [filename]

//変更された全てのワークツリーの内容をインデックスに追加する
$ git add -A

//以前コミットしたことがあるファイルだけインデックスに追加する
$ git add -u

ワーキングツリーにある変更されたファイルやディレクトリをコミットする前に、まずはインデックス(ステージングエリア)に追加します。
インデックスに追加することを『addする』や『ステージする』や『ステージング』と言います。
Gitは差分を記録できるシステムなので、ワーキングツリーの全てのファイルをステージングするのではなく、変更されたものだけステージングすると良いです。

インデックスをコミットする
//インデックスに追加されたファイルをコミットする
$ git commit

//コミットメッセージを予め指定した上でコミットする
$ git commit -m "[comment]"

インデックス(ステージングされたファイルたち)をコミットする時には、コミットの内容がわかりやすいように任意のメッセージを入力できます。

リモートリポジトリを操作する
//リモートリポジトリを追加する
$ git remote add [追加するリモートリポジトリ名] [追加したいリポジトリURL]

//リモートリポジトリ名を確認する
$ git remote

//リモートリポジトリ名とURLを確認する
$ git remote -v

//リモートリポジトリを削除する
$ git remote rm [削除したいリモートリポジトリ名]

//リモートリポジトリ名を変更する
$ git remote rename [変更前リモートリポジトリ名] [変更後リモートリポジトリ名]

//リモートリポジトリのURLを変更する
$ git remote set-url [変更したいリモートリポジトリ名] [変更先のURL]

既存のリポジトリをクローンするのではなく、ローカルリポジトリをある程度進めた時に他の端末でプロジェクトを共有するために後付でリモートリポジトリを設定することがよくあるので、覚えておいて損はないと思います。

 作業を分岐する
//現在ブランチ一覧を確認する
$ git branch

//新たなブランチを作成する
$ git branch [新しいブランチ名]

//ブランチを削除する
$ git branch -d [削除するブランチ名]

//ブランチの名前を変える
git branch -m [変更するブランチ名] [新しいブランチ名]

//現在のブランチの名前を変える
git branch -m [新しいブランチ名]

//ブランチを切り替える
$ git checkout [移動先のブランチ名]

//現在のブランチの変更内容を他のブランチにも適用する
$ git merge [branch]

作業を分岐する時はブランチを利用します。
ブランチを切り替えるときはチェックアウトします。
現在のブランチでの変更内容を他のブランチにも適用させたい場合はマージします。

リモートリポジトリの状況をローカルリポジトリの状況に同期する

リモートリポジトリの状況とローカルリポジトリの状況を同期するには、フェッチマージを行うかプルを行います。
フェッチ+マージとプルは同じ作業です。
段階を踏んで行うか、一気に行うかの違いになります。

フェッチしてリモートリポジトリの状況を取得してから統合する
//リモートリポジトリの状況を取得する
$ git fetch

//リモート追跡ブランチのコミットを現在のブランチに統合する
$ git merge origin/master

fetch(フェッチ)をすると、リモートリポジトリの状況の取得だけを行うことができます。
取得したコミットは、[origin/master]などのリモート追跡ブランチに取得されます。
取得しただけではマージ(統合・上書き)はされません。
リモート追跡ブランチはあくまでリモートの状態を示したものなので、リモート追跡ブランチ上でファイルの内容を変更することはできません。
フェッチでリモート追跡ブランチの状況を確認し、ローカルリポジトリの状況と競合(コンフリクト)する場合はマージする際に競合を解決します。
競合しない場合はそのままマージまたはプルしてOKです。

※fetchで取得したリモートリポジトリの最新のコミットは[FETCH_HEAD]で指定されます。これに対してローカルリポジトリの最新のコミットは[HEAD]で指定されます。[FETCH_HEAD]と[HEAD]が競合する場合もあるので、プルを行う前にフェッチを行うと良いです。

リモートリポジトリの状況をローカルリポジトリに同期する
//リモートリポジトリの状況をローカルリポジトリに同期する
$ git pull [リモートリポジトリ上のブランチ名]

リモートリポジトリ上の状況をローカルリポジトリに同期します。
競合(コンフリクト)が発生する場合はリモートリポジトリとローカルリポジトリのどちらの変更を選択するか解決する作業が必要です。
そういった状況を回避するために、一度フェッチしてからプルするのが安全だと思います。

ローカルリポジトリの状況をリモートリポジトリの状況に同期する
//ローカルリポジトリの現在のブランチをリモートリポジトリ上の上位のブランチに同期する
$ git push

//ローカルリポジトリの状況をリモートリポジトリに同期する
$ git push [リモートリポジトリ名] [ローカルリポジトリのブランチ名]
//例)$ git push origin master

リポジトリやブランチを指定しなくても、git push を使用すれば自動的に上位のブランチに同期できます。
リモートリポジトリがローカルリポジトリよりも進んでいるなど、変更が競合(コンフリクト)する場合は、自動的にプッシュがエラーで停止します。
その場合は一度フェッチを行って競合を解決してからプッシュしましょう。

続きを読む

サイトの開発するならGitを使うと便利だよという話

自分はHTMLやCSSやJavascriptやPHPでサイトを作る系フロントエンドエンジニアなのですが、こんなことで困ったりしていました。

  • 過去のある時点の状況に戻りたいことがある
  • なんらかの問題への対処としてAルートとBルートの対応を行って相互を比較したいことがある
  • 時間が経つと過去にどんな内容の開発をしていたのかわからなくなる
  • 複数人で同時に一つのプロジェクトを開発する時、どのファイルが最新かわからなかったり同時に同じファイルを触れなくて不便

こんな状況を解決したいと考えていて、git(ギット)を使えば便利ですよと教えてもらったら本当に便利だったので書いておきます。

gitとは

gitとは、もともとはLinuxで開発された分散型バージョン管理システムの一つです。
とか言われてもピンと来なかったので自分が理解して活用した範囲で紹介します。

変更したファイルとその変更内容を記録できる

特定のファイル群の中で、どのファイルを変更したのか、どう変更したのか、細かく記録(コミット)することができます。
しかも、記録する際には任意のメッセージ(コミットメッセージ)を記載しておけるので、 どんな作業を行ったのか自分の言葉でメモしておくことができます。

※上記スクリーンショットはVisual Studio Codeを使用して差分を閲覧した時のものです。

以前の状態に立ち戻ることができる

git reset コマンドを使うと、以前のコミットの状態まで戻すことができます。
やってみたけどうまくいかなかった時などに最初からやり直すこととかにならずに済むので便利です。

ファイル群の状態を分岐できる

例えば背景色が白のサイトを、背景色を赤に変えたAルートと、背景色を青に変えたBルートとに分岐する(ブランチを切る)ことができます。
AルートとBルートのブランチをそれぞれ切ったとき、元々の本ルートとAルートとBルートの3本のルートが並行して進行することになります。

切り分けた分岐をブランチと言います。
ブランチは他のブランチに合流(マージ)することもできます。
背景黒のAブランチと背景白のBブランチを切ってみて、相互を比較して、背景白が良かった場合は本ルートにBルートをマージすれば本ルートの背景色が白になります。

ブランチを切り替える(チェックアウトする)と、ファイルの中身だけでなく個数まで変わります。
たとえば、Aルートに画像を追加した場合、Aルートにチェックアウトすれば画像が増えた状態になり、逆にBルートで画像を削除した場合、Bルートにチェックアウトすれば画像が削除された状態になります。

いらなくなったブランチは削除することもできます。

※上記スクリーンショットはBitbucketでリモートリポジトリのコミット履歴を見た時のものです。

複数の端末で同じプロジェクトを進捗状況も含めて共有できる

デスクトップとノートパソコン、自分と誰かなど、同じプロジェクトを異なる環境で同時に開発したい時、使用するファイルそのものをクラウドや大容量保存メモリなどで共有することもできますが、Gitを使えば同じ状態のファイルを複数の環境で同期することができます。

Gitで差分管理すると設定したディレクトリをリポジトリと呼びます。
自身のPCで使用するリポジトリをローカルリポジトリ、ネット上で公開・共有しているリポジトリをリモートリポジトリと呼びます。
リモートリポジトリは共有相手を限定することもでき、限定公開されたリモートリポジトリをプライベートリポジトリと呼びます。

リモートリポジトリからローカルリポジトリを作成(クローン)したり、既存のローカルリポジトリにリモートリポジトリを指定したりして紐付けを行った上で、リモートリポジトリにローカルリポジトリの状況を反映することをプッシュ、ローカルリポジトリにリモートリポジトリの状況を同期することをプルと言います。

巨大なプロジェクトであればあるほどファイルの共有は時間がかかるものですが、プッシュとプルは差分データのみをやり取りするので時間が短くて済みます。
作業開始時にリモートリポジトリからプルして、作業終了時にリモートリポジトリにプッシュする。
それを複数の環境で行えば、常に最新のファイルを複数の環境で共有できます。

同じファイルであっても変更箇所が違っていれば同期作業はGitが勝手にやってくれるので問題はありませんが、同じファイルの同じ箇所に異なる変更を加えるなどして競合(コンフリクト)してしまった場合、競合箇所を確認してどちらの変更を優先して反映させるかを解決する作業が必要な場合があります。

gitは色々難しいけど確かに便利だ

色々専門用語が多かったりgitを利用するためのツールがコマンドプロンプトなどのコマンドラインだったりして敷居が高いように感じますが、さわりだけでも使ってみると確かに今まで難しいと思っていたことが出来るようになって便利なので、チャレンジしてみて損はないと思います。

gitが気になった方は当サイトの他記事や下記のサイトなどでぜひgitについて調べてみてください。

サルでもわかるGit入門:https://backlog.com/ja/git-tutorial/

続きを読む

Visual Studio Code のオススメ拡張機能

 Visual Studio Code で自分が使っている拡張機能をまとめておく記事です。

入力支援系

Auto Rename Tag

開始タグまたは閉じタグの属性を変更すると自動的にペアのタグの属性を変更してくれる。
開始タグと閉じタグがペアになってない状態でリネームしようとすると別の位置のタグを勝手にペア認定してしまうのでちょっと扱いに注意が必要。

配布元:
https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-rename-tag

Auto Rename Tag

Auto Close Tag

開始タグの閉じカッコを入力すると自動的に閉じタグを生成してくれる。

配布元:
https://marketplace.visualstudio.com/items?itemName=formulahendry.auto-close-tag

Auto Close Tag

EvilInspector

全角スペースを強調表示してくれる。

配布元:
https://marketplace.visualstudio.com/items?itemName=saikou9901.evilinspector

EvilInspector

indent-rainbow

インデントの個数に応じて色をつけてくれる。

配布元:
https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow

indent-rainbow

Trailing Spaces

行末の不要な空白を強調表示・削除してくれる。

配布元:
https://marketplace.visualstudio.com/items?itemName=shardulm94.trailing-spaces

Multiple clipboards for VSCode

Ctrl+Shift+Vでクリップボードのコピー履歴をさかのぼれる。

配布元:
https://marketplace.visualstudio.com/items?itemName=slevesque.vscode-multiclip

Relative Path

Ctrl+Shift+Pで[Relative Path]指定後、ファイル名を入力検索してファイルを指定すると選択したファイルへの相対パスが表示される。

配布元:
https://marketplace.visualstudio.com/items?itemName=jakob101.RelativePath

Git関係

Git Lens

行ごとに誰が更新したのか表示してくれたりする。(画像赤線部分)

配布元:
https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens

Git Lens

Git History

Gitのコミット履歴表示してくれたり前バージョンとの差分やコミット内容表示してくれたりする。

配布元:
https://marketplace.visualstudio.com/items?itemName=donjayamanne.githistory

Git History

 

続きを読む

サイトの開発するのに Visual Studio Code を使っているよという話

自分の開発環境について書いていなかったので書いておく記事です。
Visual Studio Code について詳しくは公式やもっと細かく説明してくれている他のサイトさんをご確認ください。

Visual Studio Code とは

Visual Studio Code(VSCode)は、Microsoftによって開発されているソースコードエディタです。
HTML・PHP・Javascriptなど様々な言語に対応しており、配色テーマや各種詳細設定の変更など、フロントエンドの開発環境としてオーダーメイドのポテンシャルを発揮します。

HTML・CSS・Javascript などフロントエンドのみならず、PHP・Python・C+などバックエンドの開発でも活躍します。

VSCodeのいいなって思う所

無料で使える

タダより高いものはないですがタダより安いものもないです。
ありがたさの極み。

Microsoftが開発している

IEやEdgeには散々泣かされてますがなんだかんだMicrosoftは好きです。

フォルダ・プロジェクトごとにウィンドウを開ける

フォルダを指定することでそのフォルダ以下のファイルにすぐにアクセスすることができます。
下記画像の左部分で指定したフォルダ以下のファイルツリーが表示されています。
ファイル名をクリックすれば右部分にファイルの中身が表示されます。

フォルダ・プロジェクトごとにウィンドウを開ける

配色テーマが豊富

様々な配色テーマから自分の好きなデザインを選択できます。

Light(Visual Studio)
Light(Visual Studio)
Quiet Light
Quiet Light
Dark(Visual Studio)
Dark(Visual Studio)
Monokai
Monokai
Red
Red
Tommorow Night Blue
Tommorow Night Blue
シンタックスハイライトが優秀

ソースの可読性を上げるシンタックスハイライトが各言語についているし、プロパティが間違っていたりするとひと目でわかる。

シンタックスハイライト

1行目…正しい表示
2行目…右辺の単位がおかしい(正しくは「rem」)
3行目…プロパティが間違っている(正しくは「display」)

構文エラーを適宜表示してくれる

文法上間違っている部分などがあれば理由付きで表示してくれるので、エラーを回避しやすい。

構文エラー

画面下部がエラー表示部分。
1行目…[disploy]なんてプロパティないよ
2行目…[display: inline-block]に[float: left]は効きません

拡張機能が豊富

色んないい感じの拡張機能がついているので自分好みのエディタにできる。

  • 各言語のサポート
  • 配色テーマ
  • Todoリスト
  • ブラウザプレビュー
  • CSVビューア
  • 入力サポート

など、様々な拡張機能が無料で配布されています。

Gitが使える

Gitのコマンドラインツール(ターミナル)をソフト内で使えたり、GUIのように使うこともできます。
コミット履歴などを確認するGit関連の拡張機能もあるので、Git初心者でも簡単にGitを活用できます。

Git

VSCodeをダウンロードする

Visual Studio Code が気になったらぜひダウンロードしてみてください。
ダウンロード・インストール・日本語化までは公式を見て言われたとおりにやればそんなに苦労しません。

Download:Visual Studio Code – コード エディター | Microsoft Azure

便利な使い方などは今後UPしていこうと思います。

続きを読む

PWAでプッシュ通知を実装してみる(4)実際にプッシュ通知を送信・受信する

前回までの記事では、プッシュ通知に必要なトークンを閲覧者に発行させ、購読状況とともにデータベースに記録させるところまでやりました。
今回からはデータベースに登録させた情報を元に、実際にプッシュ通知を送信・受信できるところまでやってみます。

Firebase Cloud Messaging でプッシュ通知を受信する

Google Firebase Cloud Messaging(FCM)でプッシュ通知許可時・購読時に発行されるトークン処理については前回までの記事で解説しているので、今回は受信・送信機能部分を解説します。

PWA化されたサイトでプッシュ通知を受信するにはserviceworker.jsへの記載が必要ですが、FCMを利用する時は、serviceworkerの名前をfirebase-messaging-sw.jsにする必要があります。
既にserviceworkerを利用している場合、serviceworkerの記述をfirebase-messaging-sw.jsに統合する必要があります。

複数のserviceworkerを設置する方法はなくはないようですが、分散させるよりは統合した方が処理が散漫にならず良いと思います。

キャッシュ機能についてのserviceworkerの記載は既に解説していますが、それに追加するプッシュ通知受信に関する記載は下記の通りです。

// Firebase利用準備
importScripts('https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/6.2.0/firebase-messaging.js');
firebase.initializeApp({
    'messagingSenderId': 'YOUR-SEND-ID'
});
const messaging = firebase.messaging();

// フォアグラウンドでのプッシュ通知受信
messaging.onMessage(function(payload) {
    var notificationTitle = payload.data.title; // タイトル
    var notificationOptions = {
      body: payload.data.body, // 本文
      icon: '/pwa_512.png', // アイコン
      click_action: 'https://xxxx.sample.com/' // 飛び先URL
    };

    if (!("Notification" in window)) {
        // ブラウザが通知機能に対応しているかを判定
    } else if (Notification.permission === "granted") {
        // 通知許可されていたら通知する
        var notification = new Notification(notificationTitle,notificationOptions);
    }
});

// バックグラウンドでのプッシュ通知受信
messaging.setBackgroundMessageHandler(function(payload) {
    console.log('[firebase-messaging-sw.js] Received background message ', payload);
    // Customize notification here
    var notificationTitle = payload.notification.title; // タイトル
    var notificationOptions = {
            body: payload.notification.body, // 本文
            icon: payload.notification.icon, // アイコン
    };

    return self.registration.showNotification(notificationTitle,
    notificationOptions);
});

送信された通知の内容はjson形式で受け取ります。
受け取ったjsonをプッシュ通知に変換してブラウザの状態にあわせて表示します。

参考URL

Firebase ドキュメント:JavaScript クライアントでメッセージを受信する

プッシュ通知受信テスト

Google Chromeを使えば、プッシュ通知の受信テストができます。

Firebase Cloud Messaging でプッシュ通知を送信する

プッシュ通知を受信する準備ができても、肝心の送信手続きについては全くの未知数でした。
いろんなサイトを見ても、「サーバーからcURLコマンドを叩けばおk」とかしか書いてなくて、「サーバーってなんだ? サイトを置いてあるWEBサーバーのこと??」とか「cURLってなんだよ」「どうやって叩くのよ」とかいろんな疑問点が噴出していました。

プッシュ通知を送信するためにしたいこと

プッシュ通知を送信するためにしたいことをまとめておきます。

  1. データベースに登録されたトークンをトピックに紐付ける
  2. 通知メッセージの内容は送信する度に変更する
  3. 通知は自動で送信するのではなく通知したいタイミングで手動で送信する

トピックメッセージング(トピック)とは、Firebase Cloud Messagingの機能で複数のトークンに対し一括で通知メッセージを送信する仕組みです。
同じような機能でデバイスグループメッセージングという機能がありますが、こちらはさまざまなスマートフォンモデルに応じて異なるメッセージを送信する場合に使用するものです。

今回はデバイスに応じた処理を行う予定はないので、トピックを利用することにします。

参考URL

Firebase ドキュメント:複数のデバイスにメッセージを送信する
Firebase ドキュメント:ウェブ / JavaScript でデバイス グループにメッセージを送信する

プッシュ通知の送信機能はアプリサーバーに持たせる

当初は、まさに素人考えですが、プッシュ通知を送信するためのPHPなりなんなりをWEB上に上げて、もしくはサイトに組み込んで、そこからプッシュ通知を送信できればベストだなと思っていました。
しかし、Firebase ドキュメントを読むとサーバーキーの機密性を維持するため、プッシュ通知の送信やトピックへの登録などのリクエストをクライアントから送信しないようにと書いてありました。

プッシュ通知の送信に必要なサーバーキー・トークン一覧などを誰でも閲覧可能にすると、たしかに悪意のあるユーザーから偽装されたプッシュ通知を送信される可能性があります。
よって、プッシュ通知はアプリサーバーから送信することにしました。

アプリサーバーといっても、

送信ロジック(認証、送信リクエストの作成、応答の処理などを行うロジック)を作成するために、Admin SDK を使用するか、またはサーバー プロトコルの 1 つを使用するかを決定します。次に、信頼できる環境でロジックを構築します。クライアント アプリケーションからのアップストリーム メッセージングを使用する場合には、XMPP を使用する必要があります。XMPP で必要となる永続的な接続が Cloud Functions ではサポートされないことに注意してください。
Firebase ドキュメント:Firebase Cloud Messaging

ということなので、コマンドプロンプトNode.jsをインストールしてFirebase Admin SDKを利用することにします。

Firebase Admin SDKを利用するにはNode.jsだけではなくJAVA・Python・Go・C#でもOKなようですが、Node.jsが一番なんでもできそうだったのでNode.jsにしました。

参考URL

Firebase ドキュメント:サーバーに Firebase Admin SDK を追加する

コマンドプロンプトにNode.jsをインストールする

そもそもNode.jsってなにかっていうと、サーバーでJavaScriptを利用できるようにするためのもののようです。
FirebaseはJavaScriptで構成されているので、サーバーでNode.jsが必要だというのもうなずけます。

参考URL

エンジニアの入り口:初心者向け!3分で理解するNode.jsとは何か?
Qiita:Windows版 Node.js環境構築方法まとめ

Node.jsのインストールされたコマンドプロンプトにFirebase Admin SDKを追加する
  1. Windowsでサーバーアプリとして利用するディレクトリをどこかに作っておく。
    例えば[C:\webpush]など適当でいいと思います。
  2. Node.js command promptを起動して[1]で作成したディレクトリに移動する。
  3. 下記コマンドを実行する
    $ npm install firebase-admin --save
    
  4. コマンドプロンプトが色々頑張ってこんな感じの画面になる
    2019080601.jpg
  5. [1]で作成したディレクトリを見に行くと、なんかファイルが出来ている
    2019080602.jpg
  6. firebaseのコンソールから[設定]>[サービスアカウント]を開く
  7. [新しい秘密鍵の生成]をクリックする
    2019080603.jpg
  8. [キーを生成]をクリックする
    2019080603.jpg
  9. 秘密鍵ファイルのDLが開始するので[1]とは別のディレクトリに保存する。
    別に[1]と同じでもいいけど、要は公開されないようにすればOK。
    [1]をリポジトリにしてGitで管理する場合などは公開されないように注意すること。
    2019080605.jpg

生成した秘密鍵への紐付けは、コード内に記載して行うこともできます(自分は今回そうしています)が、システムの環境変数を設定する方法もあります。
環境変数を設定したほうが安全ですが、複数のプロジェクトを扱う場合、新しいセッションを開く場合はセッションを再度設定しなければなりません。
環境変数[GOOGLE_APPLICATION_CREDENTIALS]への設定方法は下記Firebaseドキュメントを参考にしてください。

Firebase ドキュメント:サーバーに Firebase Admin SDK を追加する-SDKの追加
Firebase ドキュメント:サーバーに Firebase Admin SDK を追加する-SDK の初期化

プッシュ通知送信処理用のJS

Firebase Admin SDK のインストールが終わったら、これを使ってサーバー側でのプッシュ通知送信処理を作成します。
今回はJavaScriptで作成しました。
下記にサンプルを用意したので、[send-push.js]などのように名前をつけて、Firebase Admin SDKをインストールしたフォルダに保存してください。

// 設定
var admin = require("firebase-admin");
var serviceAccount = require("./YOUR-SERVICE-ACCOUNT.json");//Admin SDK利用のため作成した秘密鍵の場所

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://XXXXXXXXXXXXXXXXXXXXXXXXXX.firebaseio.com"//[Firebaseコンソール→設定→サービスアカウント]で確認できるデータベースのURL
});

// 宛先のトピックを指定
var topic = 'XXXXXX';

var db = admin.firestore();

// DBから取得したトークンを配列に格納
db.collection('users').get()
.then((snapshot) => {
    const entryTokens = [];
    const removeTokens = [];
    snapshot.forEach((doc) => {
        var data = doc.data();
        if(data.subscribe == true){
            entryTokens.push(data.token);
        } else {
            removeTokens.push(data.token);
        }
        
    });

// トピック購読処理
    admin.messaging().subscribeToTopic(entryTokens, topic)
    .then(function(response) {
        // See the MessagingTopicManagementResponse reference documentation
        // for the contents of response.
        console.log('Successfully subscribed to topic:', response);
    })
    .catch(function(error) {
        console.log('Error subscribing to topic:', error);
    });
// トピック購読解除
    admin.messaging().unsubscribeFromTopic(removeTokens, topic)
    .then(function(response) {
        // See the MessagingTopicManagementResponse reference documentation
        // for the contents of response.
        console.log('Successfully unsubscribed to topic:', response);
    })
    .catch(function(error) {
        console.log('Error subscribing to topic:', error);
    });
})
.catch((err) => {
  console.log('Error getting documents', err);
});

// 通知を定義
var message = {
  topic: topic,
  webpush: {
    notification: {
      title: "タイトル",
      body: "通知メッセージ",
      requireInteraction: "true",
      badge: "https://YOUR-DOMAIN/pwa_512.png",
      icon: "https://YOUR-DOMAIN/pwa_512.png",
    },
    fcm_options: {
      link: "https://YOUR-DOMAIN/"
    }
  }
};

// 通知を送る
admin.messaging().send(message)
  .then((response) => {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) => {
    console.log('Error sending message:', error);
  });

変更箇所はいくつかあります。

最初の設定部分は、[firebaseコンソール>設定>サービスアカウント]でも確認できます。
Admin SDK利用のため作成した秘密鍵の場所を明示しておいてください。

送信先トピック名はなんでも構いません。
わかりやすいものを設定しましょう。

Usersテーブルから登録済みトークン一覧を取得し、WEB通知の購読状況に応じてトピックを購読したり削除したりします。

最新のトピック購読者リストが更新できたら、通知を送信します。
通知内容はここで編集します。
fcm_optionsの[link]は通知をクリックした時の飛び先URLです。
受信時のserviceworker側でなく、送信時に色々設定出来たほうがよいと思います。

最後の部分でプッシュ通知を送信します。

ワンポイント

ウェブアプリの場合トピックや送信処理をFirebaseコンソールで操作できない

色々調べたところ、Firebaseコンソールでトピックの登録についてやプッシュ通知の送信自体を操作できるようなことが書いてあったのですが、それはAndroidやiOSだけの話で、どうやらウェブアプリではCloud Messagingのコンソールが利用できないようです。
残念。将来的にできるようになることを期待したいです。

参考URL

Firebase ドキュメント:ウェブや JavaScript でメッセージをトピックに送信する

プッシュ通知の送信を行う

ようやくプッシュ通知の送信を行います。
上記作成した[send-push.js]をNode.jsで実行します。

  1. Node.js command promptを起動する
  2. [push.js]が保存されているフォルダに移動する。
  3. 下記コマンドを実行する
    node send-push.js
    
  4. プッシュ通知送信結果が表示される。
    2019080606.jpg

以上でプッシュ通知の送信作業は完了です。
お疲れさまでした。

2019080607.jpg

最後に

いかがでしたでしょうか。プッシュ通知をPWAでできるようになったことで、様々なサイトでオリジナルの通知を送信できるようになりました。
購読処理の設定を変更して好きなテーマについてのみ通知を受け取れるようにしたり、アプリサーバーの送信処理を変更して時間を指定して通知を送ったりすることもできるようになりそうです。

初心者が初心者なりになんとか実現にこぎつけただけのメモですので、もっといい方法なりやり方があれば教えてください。

続きを読む

ユーザー認証プラグイン

ログイン済でないユーザーがサイトにアクセスした時にログイン画面にリダイレクトするプラグインです。

ダウンロード

導入方法

  1. 上記ファイルをDLして解凍する。[freo]フォルダに[configs][libs]フォルダをアップロードする
  2. [freo]フォルダに[configs][libs]フォルダをアップロードする。
  3. [管理画面→システム→設定管理→プラグインの設定→ユーザー認証プラグイン]にアクセスし、設定値を「ブロックする」に変更する

カスタマイズ方法

本プラグインは、

  • freo/index.php/admin
  • freo/index.php/regist
  • freo/index.php/reissue
  • freo/index.php/login


以外の画面にアクセスした閲覧者がログイン済かどうか診断し、ログインしていなかった場合は[freo/index.php/login]にリダイレクトします。

トップページだけは全閲覧者に見せたい場合、たとえばトップページはログイン済みでないユーザーにも見せる場合、[freo/libs/plugins/freo/config.user_auth.php]の19行目を

    define('FREO_PLUGIN_USER_AUTH_UNLOAD_INIT', 'admin,reissue,regist,login,default');

に変更してください。

更新履歴

2019/07/30
配布開始

続きを読む

PWAでプッシュ通知を実装してみる(3)Firebaseでプッシュ通知に必要なトークンを処理する

前回までの記事で、PWA化にはmagnifest.jsonとserviceworker.jsが必要と解説し、そのサンプルを掲載しました。
今回からは目標だったプッシュ通知について解説します。

プッシュ通知の仕組みとFirebaseを利用することにした経緯

PWAを使ったプッシュ通知では、ブラウザで「プッシュ通知を受け取る」設定をした時に、「トークン」が発行されます。
これはブラウザ単位で発行されるので、PCのGoogle ChromeとスマホのAndroidでは別々のトークンが発行されます。

この「トークン」に対して、PWAを利用したサイト(ウェブアプリ)の管理者がサーバーからCurlコマンドを叩くことでプッシュ通知を送信します。
すなわち、「トークン」をどこかに保存しておく必要がありそうです。

トークンを取得したとしても、プッシュ通知を1件1件送っていくのは時間がかかりそうな気がします。
このサイトのような個人ブログであれば問題はないかもしれませんが、アクセスが多くプッシュ通知の購読者数も多いようなサイトの場合、受信者のタイムラグを減らす意味でも、複数人に一度にプッシュ通知を送信できる仕組みがほしいと感じます。

よって、今回はGoogle Firebase Cloud Messaging(FCM)を使用することにしました。
FCMでは、トークンをトピックというグループに関連付け、そのグループに対してプッシュ通知を送ることで、複数人に対し一度にプッシュ通知を送ることができます。
トークンの取得・トピックへの紐付け・プッシュ通知の受信・送信を、FCMと同じくFirebaseの簡易型データベースであるCloud Firestoreで実装します。

ワンポイント

トークンだけでなく、端末のUAや名前・メールアドレスなど、トークン以外のユーザー情報を取得しているサイトの場合は、データベースのユーザーテーブルにトークンのフィールドを増やすなり、プッシュ通知用のテーブルにユーザー情報を紐付けるなりしてください。
今回は通知を購読すると設定した全てのユーザー向けに通知を送ることを想定しています。
端末ごとに通知を送りたい、年齢ごとに送りたいなど、細かな設定が必要な場合はトークン以外のユーザー情報を取得して保存する別の仕組みが必要になるでしょう。

今回実装したプッシュ通知の概要

一番最初の記事で書いていますが、今回実装したプッシュ通知は、

  • アプリが起動されている状態で
  • ブラウザでプッシュ通知が許可されていて
  • プッシュ通知を受け取る設定になっている時

に受け取ることができるものです。

PWA化したサイトはPC・スマホにインストールすることが可能ですが、アプリを起動していない時にプッシュ通知を受信することはできません
起動していない時にまでプッシュ通知を受け取るのはそれはそれでうざったいという判断もできると思うので、現状は上記の状況で満足しています。

なお、アプリは起動してあるけれど別のアプリを使用している(アプリがバッググラウンドにある)場合は受信可能です。

プッシュ通知実装のためにFirebaseを利用する

プッシュ通知の受信・発信のために、今回は Google Firebase を利用しました。
Firebaseはウェブアプリ・ネイティブアプリのバックグラウンドで行われる様々な機能を担うサービスです。
プッシュ通知の送受信・SNSアカウントとの連携・データベースなど、多種多様な機能を利用することができます。

Firebase にプロジェクトを作成する

Firebaseの各種機能を利用するには、まずはFirebaseにログインし、プロジェクトを作成します。
Gmailアドレスが必要です。

  1. Gmailにログインした状態でFirebaseのコンソールにアクセスする。
  2. 「使ってみる」をクリックする
    20190730-01-01.jpg
  3. 「プロジェクトを作成」をクリックする
    20190730-01-02.jpg
  4. プロジェクト名などを入力して「プロジェクトを作成」をクリックする
    20190730-01-03.jpg
  5. プロジェクトが作成できたら「次へ」をクリックする
    20190730-01-04.jpg
  6. プロジェクト作成完了です。お疲れさまでした。
    20190730-01-05.jpg
Firebase のプロジェクトにアプリを設定する

Firebaseでプロジェクトを作成したら、WEBアプリ(サイト)を設定します。

  1. Firebaseコンソールで「</>」マークをクリックしてWEBアプリを登録します。
    20190730-02-01.jpg
  2. サイト名を入力し、「アプリを登録」をクリックする
    20190730-02-02.jpg
  3. サイトに埋め込むためのFirebase SDKが発行されるのでメモしておく。
    なお、このソースは後から編集するので今すぐ<body>タグ下部に埋め込まなくてOK。
    また、もしメモに失敗しても必要な情報はコンソールから確認することもできる。
    20190730-02-03.jpg
    メモが済んだら「コンソールに進む」をクリックする。
  4. コンソールの左側メニュー「Project Overview」の右にある歯車アイコンをクリックし、「プロジェクトの設定」を選択する。
    20190730-02-04.jpg
  5. 「プロジェクトの設定」の「全般」タブでアプリ情報を確認できる。
    また、同画面下部に(3)でメモしたFirebase SDKも随時確認できる。
    20190730-02-05.jpg

VAPID鍵を設定する

FCMでは「Voluntary Application Server Identification 鍵(VAPID 鍵)」と呼ばれるウェブ認証情報を利用して、ウェブプッシュ サービスへの送信要求が承認されます。
プッシュ通知を利用するには、VAPID鍵ペアをFirebaseプロジェクトに関連付ける必要があります。

  1. Firebaseコンソールからプロジェクトの設定→[クラウドメッセージング]をクリックし、ウェブ設定で「鍵ペアを生成」をクリックする。
    20190730-03-01.jpg
  2. 鍵が生成されるので、今後説明するfirebase.js内でCloud Messaging 利用時に鍵を利用する。
    20190730-03-02.jpg

Firestoreでusersテーブルを作成する

Cloud Firestore は Firebase で使用できる簡易的なデータベースです。
ここではトークンと購読状況を保存するだけの極シンプルなテーブルの作成方法を記載します。

  1. Firebaseコンソールから「開発」→「Database」をクリックする
    20190730-05-01.jpg
  2. 「データベースの作成」をクリックする
    20190730-05-02.jpg
  3. データベース作成用ダイアログが表示されるので、「テストモードで開始」を選択して「次へ」
    20190730-05-03.jpg
  4. ロケーションを設定する。多分どこでもいいとは思うけどなんとなく[asia-east2]を選んでみた。
    20190730-05-04.jpg
  5. 作成されたデータベースが表示される(少々時間がかかるのでおとなしく待つ)
    「コレクションを開始」をクリックする
    20190730-05-06.jpg
  6. コレクション作成ダイアログが表示される
    今回は[users]という名前のコレクションにデータを入れていくので、[users]と入力して「次へ」をクリックする
    20190730-05-07.jpg
  7. [users]コレクションに最初に入れるデータを挿入できる
    ドキュメントID部分で「自動ID」をクリックする
    20190730-05-08.jpg
  8. 自動に採番されたIDがドキュメントID部分に挿入される
    [subscribe]フィールドを[boolean]タイプで「true」に、[token]フィールドを[string]タイプで[適当になんか入れて]作成する
    20190730-05-09.jpg
  9. 挿入したデータが登録されていることを確認する
    20190730-05-10.jpg

Firestoreは、簡易型データベースです。
今回は[users]というテーブル(コレクション)に自動付与されたIDをキー(ドキュメント)にして[subscribe:true, token:任意の文字列]という配列を入れてみました。

このようにコンソールから直接登録値を挿入・編集・削除できますし、サイト(Webアプリ)側から内容を参照・編集することもできます。

参考サイト

Firebaseドキュメント:Cloud Firestore
Firebaseドキュメント:Cloud Firestore データモデル

 

では、実際にサイトでFirebaseを利用する準備をしてみます。

headタグにFirebase SDKを挿入する

サイトでFirebaseを利用するために、headタグにFirebase SDKを挿入します。
Firebaseコンソールでは基本的なスニペットしか取得できませんが、Firebaseの各種機能を使うために、それぞれのライブラリを追加で読み込む必要があります。

例えば、今回はCloud Messaging と Firebase Firestore を利用するので、Cloud Messaging と Firebase Firestore用の ライブラリを追加します。

そのため、headタグには

<script defer src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/6.2.0/firebase-messaging.js"></script>
<script defer src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
<script defer src="/firebase.js"></script>

のように記載します。

firebase-app.jsがfirebaseを利用するための基本ライブラリです。
firebase-messaging.jsとfirebase-firestore.jsはCloud MessagingとFirebase Firestore用のライブラリです。
個人的にheadタグに色々書き加えていくのが嫌なタイプなので、firebaseを利用する部分の記述はfirebase.jsにまとめ、それを読み込む形にします。

firebase.jsの中身はこんな感じです。

// Firebase設定
var firebaseConfig = {
    apiKey: "YOUR-API-KEY",
    authDomain: "project-XXXXXXXXXX.firebaseapp.com",
    databaseURL: "https://project-XXXXXXXXXX.firebaseio.com",
    projectId: "project-XXXXXXXXXX",
    storageBucket: "project-XXXXXXXXXX.appspot.com",
    messagingSenderId: "YOUR-MESSAGING-SENDER-ID",
    appId: "YOUR-API-ID"
};
firebase.initializeApp(firebaseConfig);

// FCM使用準備
const messaging = firebase.messaging();
messaging.usePublicVapidKey('XXXXXXXXXXXXXXXXXXXXXXXXXX');// VAPIDを設定

// Firestore使用準備
var db = firebase.firestore();
var usersRef = db.collection("users");

メモしたFirebase SDKまたはFirebaseコンソールのプロジェクトの設定→全般タブにFirebase SDKスニペットが記載されているので、そこからconfig設定部分を記載しています。
前回の記事で解説したPWA用のmagnifest.jsonではgcm_sender_idをCloud Messagingを利用するための固定値を挿入しましたが、firebase.jsの[messagingSenderId]はプロジェクト固有のIDを挿入してください。

FCM及びFirestoreを使用するための記載を加えています。
VAPID部分は自分で取得したVAPIDに変更してください。

参考URL

Firebaseドキュメント:FirebaseをJavaScriptプロジェクトに追加する

プッシュ通知購読処理のための領域をサイト内に用意する

当サイトのプッシュ通知は、「プッシュ通知を受信する」とブラウザで設定すれば即購読開始となるものではなく、プッシュ通知購読処理及び購読状況の表示領域をサイト内に用意しており、そのブロックで購読開始・購読終了の手続きを行うことができるようにしました。
プッシュ通知の購読は、「購読する」ボタンの押下をもって行い、プッシュ通知購読の終了は「購読終了する」ボタンの押下をもって行うものとします。

bodyタグ内に例えば下記のように記載してください。

<div id="notification">
    <h3>NOTIFICATION</h3>
    <p>当サイトの更新をプッシュ通知で受け取ることができます。</p>
    <p class="notice"></p>
    <button id="EntryButton" onclick="getSubscription()">プッシュ通知を受け取る</button>
    <button id="RemoveButton" onclick="removeSubscription()">プッシュ通知を解除する</button>
</div>

p.noticeは空でOKです。
プッシュ通知を受け取るボタン・解除するボタンが押下された時、処理結果を表示するための段落です。

getSubscription()は購読処理、removeSubscription()は購読解除処理ですが、こちらの内容は今後firebase.jsに記載していきます。

当サイトでは下画像のようにサブメニューにプッシュ通知用のブロックを設置しています。

20190730-04-01.jpg

プッシュ通知の購読開始・購読解除処理

ここまでの準備が整ったら、プッシュ通知の購読開始・購読解除を行えるようにjsに処理を書き込みます。
firebase.jsに下記のように加筆してください。

// 購読確認を行う
checkSubscription();

// 購読確認処理
function checkSubscription() {
    //通知の承認を確認
    messaging.requestPermission().then(function() {
        //トークンを確認
        messaging.getToken().then(function(token) {
            //トークン発行
            if (token) {
                //トークンがDBに入っているか確認
                usersRef.where('token', '==', token).get().then(function(oldLog){
                    if(oldLog.empty){
                    //入っていなければ購読ボタン表示
                        console.log('トークンは登録されていません。');
                        $('#notification p.caution').text('通知を購読していません。');
                        ShowEntryButton();
                    } else {
                    //入っていれば購読状況確認
                        console.log('トークンはすでに登録されています。');
                        oldLog.forEach(function(doc){
                            var data = doc.data();
                            if(data.subscribe == true){
                            //購読している(=停止ボタン表示)
                                $('#notification p.caution').text('通知を購読しています。');
                                ShowRemoveButton();
                            } else {
                            //購読していない(=開始ボタン表示)
                                $('#notification p.caution').text('購読を解除しました。');
                                ShowEntryButton();
                            }
                        });
                    }
                });
            } else {
                    console.log('通知の承認が得られませんでした。');
                    $('#notification p.caution').text('購読を開始できませんでした。');
                    ShowEntryButton();
            }
        }).catch(function(err) {
                console.log('トークンを取得できませんでした。', err);
                $('#notification p.caution').text('購読を開始できませんでした。');
                ShowEntryButton();
        });
    }).catch(function (err) {
        //プッシュ通知未対応
            console.log('通知の承認が得られませんでした。', err);
            $('#notification p.caution').text('プッシュ通知が許可されていません。ブラウザの設定を確認してください。');
            ShowEntryButton();
    });
}

// 購読処理
function getSubscription() {
    //通知の承認を確認
  messaging.requestPermission().then(function() {
        //トークンを確認
        messaging.getToken().then(function(token) {
            //トークン発行
            if (token) {
                //トークンがDBに入っているか確認
                usersRef.where('token', '==', token).get().then(function(oldLog){
                    if(oldLog.empty){
                    //トークン登録がなければトークン登録・購読設定
                        usersRef.add({
                            token: token,
                            subscribe: true
                        });
                        console.log('トークン新規登録しました。');
                    } else {
                    //トークン登録があれば購読に設定変更
                        oldLog.forEach(function(doc){
                            console.log('トークンはすでに登録されています。');
                            usersRef.doc(doc.id).update({
                                subscribe: true
                            })
                        });
                    }
                    //購読解除ボタン表示
                    ShowRemoveButton();
                });
                //購読状況表示更新
                $('#notification p.caution').text('通知を購読しています。');
            } else {
                console.log('通知の承認が得られませんでした。');
                $('#notification p.caution').text('購読を開始できませんでした。');
                ShowEntryButton();
            }
        }).catch(function(err) {
            console.log('トークンを取得できませんでした。', err);
            $('#notification p.caution').text('購読を開始できませんでした。');
            ShowEntryButton();
        });
  }).catch(function (err) {
        console.log('通知の承認が得られませんでした。', err);
        $('#notification p.caution').text('プッシュ通知が許可されていません。ブラウザの設定を確認してください。');
        ShowEntryButton();
    });
}

// 購読解除処理
function removeSubscription() {
    //通知の承認を確認
    messaging.requestPermission().then(function() {
        //トークンを確認
        messaging.getToken().then(function(token) {
            //トークン発行
            if (token) {
                //トークンがDBに入っているか確認
                usersRef.where('token', '==', token).get().then(function(oldLog){
                    if(oldLog.empty){
                    //トークン登録がなければ購読ボタン表示
                        console.log('トークンは登録されていません。');
                        ShowEntryButton();
                    } else {
                    //トークン登録があれば購読解除を行う
                        oldLog.forEach(function(doc){
                            usersRef.doc(doc.id).update({
                                subscribe: false
                            })
                            .then(function() {
                                console.log("購読を解除しました。");
                                ShowEntryButton();
                            }).catch(function(error) {
                                console.error("Error removing document: ", error);
                            });
                        });
                    }
                });
                //購読状況表示更新
                $('#notification p.caution').text('購読を解除しました。');
            } else {
                console.log('トークンを取得できませんでした。');
                $('#notification p.caution').text('購読を開始できませんでした。');
            }
        }).catch(function(err) {
            console.log('トークンを取得できませんでした。', err);
            $('#notification p.caution').text('購読を開始できませんでした。');
        });
    }).catch(function (err) {
        console.log('通知の承認が得られませんでした。', err);
        $('#notification p.caution').text('プッシュ通知が許可されていません。ブラウザの設定を確認してください。');
        ShowEntryButton();
    });
}

// トークン表示
function displayToken() {
  messaging.getToken().then(token => {
        if (token) {
            console.log(token);
        } else {
            console.log('トークンを取得できませんでした。');
        }
  }).catch(function (err) {
    console.log('トークンの取得時にエラーが発生しました。', err);
  });
}


//購読ボタン表示
function ShowEntryButton() {
    $('#EntryButton').show();
    $('#RemoveButton').hide();
}

//購読取消ボタン表示
function ShowRemoveButton() {
    $('#EntryButton').hide();
    $('#RemoveButton').show();
}

画面表示時にcheckSubscription()を行って、新規訪問者がプッシュ通知の購読を行えるようにしています。
既存の訪問者(トークン発行済)の場合は、プッシュ通知購読状況表示領域の表示内容を更新します。

プッシュ通知購読状況表示領域のボタンが押下された時、getSubscription()で購読処理またはremoveSubscription()で購読解除処理を行います。

処理結果はconsole.logで適宜チェックできるようにしていますが、DBに登録されるトークンの内容は記載していません。
生成されたトークンとFirestoreに登録されるトークンの値が合致しているかを確認する時などのために、トークンを表示するための関数(displayToken())も入れているので、適宜使用してみてください。

参考URL:

Qiita:Progressive Web Apps (PWA) 学習者のメモ その2 (プッシュ通知とFCM)
Firebaseドキュメント:JavaScript Firebase Cloud Messaging クライアント アプリを設定する

今回のまとめ

PWAを使えばプッシュ通知を受信することができるようになりますが、実際に受信するためには様々な設定・処理が必要です。
Firebaseの登録は簡単ですが、やりたいことを実現するためにはドキュメントの読み込みが重要で、頭の悪い私は何度「日本語でおk」と呟いたかわかりません。

FCMだけでは不十分だったところを、Firestoreとの連携で割と理想的なフローに落ち着いて良かったと思います。
次回はプッシュ通知の受信・送信について解説していきたいと思います。

続きを読む

PWAでプッシュ通知を実装してみる(2)とりあえずPWA化するためのmagnifest.jsonとserviceworker

前回の記事ではPWAでプッシュ通知を実装することにした経緯とかざっくりした方法と結果を説明しました。
今回はPWA部分に的を絞って書いていきます。

PWAとは

前回も解説しましたが、PWAとは Progressive Web Apps の略です。
Googleが推進しているウェブをアプリみたいに利用できる機能のことです。

詳しくは前回の記事をご覧ください。

PWA化に必要なもの

PWA化するにあたり、下記のものが必要になります。

  • サイトの全体SSL化
  • 192x192pxと512×512pxのアイコン画像
  • manifest.json
  • serviceworker.js
  • headタグの編集

全体SSL化とアイコン画像はまだしも、magnifest.jsonとserviceworker.jsは無いと思うので、作り方も含めて解説します。

サイト全体のSSL化

頑張ってください。

アイコン画像

192x192px、512x512pxのpng画像があればOKです。
サイトのルートディレクトリに設置してください。

magnifest.json

ウェブアプリのマニフェストをjson形式で記載したものです。
ウェブアプリをインストールする際にこちらの設定が参照されます。

magnifest.jsonはサイトのルートディレクトリに設置します。
また、サイトのheadタグ内にmagnifest.jsonの位置を記載しておく必要があります。

HTMLの記述

サイトのheadタグ内に下記のように記載してください。

    <link rel="manifest" href="/manifest.json">
 magnifest.jsonの記述

magnifest.jsonはjson形式で記載します。
AdobeXDのリファレンスを見ると様々な項目を設定することができますが、当サイトではとりあえず下記のように書いてみました。
適当に改変して使用してください。

{
    "name": "虹色ミツバチ-32877",
    "short_name": "32877",
    "description": "freoカスタマイズメモ、テンプレート・プラグイン配布/officeTIPS",
    "start_url": "/?utm_source=homescreen&utm_medium=pwa",
    "display": "standalone",
    "lang": "ja",
    "dir": "auto",
    "orientation": "any",
    "theme_color": "#bcb782",
    "background_color": "#fffce0",
    "icons": [
        {
            "src": "/pwa_192.png",
            "type": "image/png",
            "sizes": "192x192"
        },
        {
            "src": "/pwa_512.png",
            "type": "image/png",
            "sizes": "512x512"
        }
    ],
    "gcm_sender_id": "103953800507"
}

なお、それぞれの項目は

name サイト(ウェブアプリ)名
short_name ホーム画面に表示されるサイト(ウェブアプリ)名
description サイト(ウェブアプリ)の説明
start_url 起動時に表示されるURL。
PWA化するならこのままでOK。
display 表示モード。
fullscreen、fullscreen、standalone、minimal-uiの4つを設定可能。
通常はstandaloneでOK。
lang 日本語なら「ja」
dir テキストの方向。
ltr(左から右)、rtl(右から左)、autoの3つを設定可能。
日本語はltr(左から右)でOK。
theme_color サイト(ウェブアプリ)のテーマカラー。
OS/ブラウザによって使用方法は異なる。
background_color 背景色。
サイト起動時にこの色が表示されたりする。
icons アイコン用画像。
ホーム画面に表示されたりする。
192x192pxと512x512pxがあればOK。
gcm_sender_id プッシュ通知を受信する場合は必要です。
いらない場合は削ってOKです。
FirebaseのCloud Messagingでプッシュ通知を受信する場合は、挿入値は「103953800507」固定になります。
設置箇所

magnifest.jsonの編集が終わったら、サイトのルートディレクトリに設置してください。

serviceworker.js

serviceworker.jsはウェブアプリをバックグラウンドで実行するスクリプトです。
オフラインのアプリの実現・サポートを行い、キャッシュ機能で画面の表示速度を上げます。

なお、serviceworker.jsのファイル名は[serviceworker]で固定である必要はありません
また、複数のserviceworkerを使用するより、一つのjsファイルにまとめたほうが良いようです。

serviceworker.jsはサイトのルートディレクトリに設置します。
また、サイトのheadタグ内にserviceworker.jsの位置や使用するタイミングなどを記載しておく必要があります。

serviceworker.jsの書き方

serviceworkerの書き方は色々調べましたが、ここが一番参考になる+理想通りに動きました。

Qiita:Service Workerの基本とそれを使ってできること

上記を参考に書いて当サイトで動いているserviceworkerの一部がこちら↓

const VERSION = "1";
const ORIGIN = location.protocol + '//' + location.hostname;

const STATIC_CACHE_KEY = 'static-' + VERSION;
const STATIC_FILES = [
    ORIGIN + '/',
    ORIGIN + '/images/logo.png',
    ORIGIN + '/css/bootstrap.min.css',
    ORIGIN + '/css/bootstrap-reboot.min.css',
    ORIGIN + '/css/common.css',
    ORIGIN + '/css/default.css',
    ORIGIN + '/js/jquery.js',
    ORIGIN + '/js/common.js',
];
const CACHE_KEYS = [
    STATIC_CACHE_KEY
];

self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(STATIC_CACHE_KEY).then(cache => {
            return Promise.all(
                STATIC_FILES.map(url => {
                    return fetch(new Request(url, { cache: 'no-cache', mode: 'no-cors' })).then(response => {
                    return cache.put(url, response);
                    });
                })
            );
        })
    );
});

self.addEventListener('activate', event => {
    event.waitUntil(
        caches.keys().then((cacheNames) => {
            return cacheNames.filter((cacheName) => {
                // STATIC_CACHE_KEYではないキャッシュを探す
                return cacheName !== STATIC_CACHE_KEY;
            });
            }).then((cachesToDelete) => {
            return Promise.all(cachesToDelete.map((cacheName) => {
                // いらないキャッシュを削除する
                return caches.delete(cacheName);
            }));
        })
    );
});

self.addEventListener('fetch', event => {
    // POSTの場合はキャッシュを使用しない
    if ('POST' === event.request.method) {
        return;
    }

    event.respondWith(
        caches.match(event.request)
        .then((response) => {
            // キャッシュ内に該当レスポンスがあれば、それを返す
            if (response) {
                return response;
            }

          // 重要:リクエストを clone する。リクエストは Stream なので
          // 一度しか処理できない。ここではキャッシュ用、fetch 用と2回
          // 必要なので、リクエストは clone しないといけない
            let fetchRequest = event.request.clone();

            return fetch(fetchRequest)
            .then((response) => {
                if (!response || response.status !== 200 || response.type !== 'basic') {
                    // キャッシュする必要のないタイプのレスポンスならそのまま返す
                    return response;
                }

                // 重要:レスポンスを clone する。レスポンスは Stream で
                // ブラウザ用とキャッシュ用の2回必要。なので clone して
                // 2つの Stream があるようにする
                let responseToCache = response.clone();

                caches.open(STATIC_CACHE_KEY)
                .then((cache) => {
                    cache.put(event.request, responseToCache);
                });

                return response;
            });
        })
    );
});

今回はあくまでプッシュ通知を目標にするため、上記タグだけでは不十分ですが、キャッシュの生成・削除までの一連の流れは上記でまかなえます。

上記サンプルserviceworkerのざっくりした解説

1行目がキャッシュの番号です。
サイトを更新するたびにこの番号を増やしていく必要があります。

PWAのサイトはだいぶキャッシュが強いです。
serviceworkerでサイトの更新を明示することにより、キャッシュを増やし古いキャッシュを削除します。
そのため、CMSなどを利用したサイトでは「コメントが増えた時」や「新規投稿を行った時」などにserviceworkerを自動的に更新するしくみが必要だと思います。

現時点で、当サイトでは手動でserviceworkerを更新しています。
そのうちfreo用のPWA化用プラグインを作ってみようとは思ってますが、そのうち。
WordpressではPWA化用プラグインがあるようなのでWordpressの方は検討してみてください。
Wordpress用のPWA化プラグインでこれ以降に解説するプッシュ通知が使えるかどうかは実証してません。

5~14行目でキャッシュ化するファイルを指定しています。
URLだけでなくjQueryやスタイルシート等も入れておくと読み込みが早くなるでしょう。

19行目以下でキャッシュの作成・利用・削除などを行っています。
この辺は先程紹介したQiitaの記事で解説されています。

serviceworker.jsを登録する

serviceworker.jsは作成しルートディレクトリに設置しただけでは動きません。
serviceworker.jsをサイトに登録するため、headタグ内に下記のように記載してください。

<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/serviceworker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}
</script>

このコードは、Service Worker APIが利用可能かどうかを確認し、利用可能な場合は、ページが読み込まれたときに 、 /service-worker.jsのService Workerが登録されます。(参考:はじめてのプログレッシブウェブアプリ

ただし、今回はあくまでプッシュ通知を目標にするため、上記タグだけでは不十分ですが、それでもserviceworkerが実際活躍するかどうかの確認をするなら上記タグだけでもOKです。

serviceworkerで作成したキャッシュの確認方法

キャッシュが正常に動いているかどうかは、GoogleChromeのデベロッパーツールで確認できます。
F12キーでデベロッパーツールを開き、ApplicationタブのCache Storageを見ると、現時点で使用されているキャッシュが表示されます。

2019072901.jpg

serviceworkerで[37]を指定しているため、キャッシュが[37]に登録されています。
管理者がserviceworkerを[38]に変更して更新すると、キャッシュが再生成されます。

正常にキャッシュの作成・削除ができていると、Cache Storageには1つのキャッシュしか保持されません。
[37][38][39]…のように複数のキャッシュが存在する場合、キャッシュの削除に失敗しています。
また、serviceworkerが指定している数字(例えば[37])と違う番号のキャッシュが作成されている場合(例えば[39]など)もキャッシュの作成に失敗しているので注意してください。

特定の画面をキャッシュしない処理

特定のURLをキャッシュしたくない場合、fetchイベント内(上記の例でいうと60行目と61行目の間)に下記のように記載してください。

    // 管理画面はキャッシュを使用しない
    if (/\/admin|\/login|\/user/.test(event.request.url)) {
        return;
    }

上記の例では、

  • http://サイトURL/admin
  • http://サイトURL/login
  • http://サイトURL/user

上記URLでキャッシュを使用しないようにしています。

管理者が作成したコンテンツを表示するのみの画面はキャッシュを利用して構いませんが、閲覧者の操作によってリアルタイムで動的に内容が変更する可能性のある画面はキャッシュを利用しないほうがベターです。

headタグの記載について

PWA化する時、magnifest.jsonとserviceworker.jsの箇所でちょろっと書きましたが、headタグ内にPWA化用のソースを記載する必要があります。

まとめるとこんな感じです。

<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#6a5f80"/>
<meta name="apple-mobile-web-app-title" content="32877">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon-precomposed" href="/pwa_512.png" sizes="512x512">
<script>
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/serviceworker.js')
        .then((reg) => {
          console.log('Service worker registered.', reg);
        });
  });
}
</script>

PWAはGoogleが主体となって広がっている新たな機能なので、iOSではまだ未実装な部分があり、iOSでPWAを使用する場合にはメタタグで各種設定を指定する必要があります。

今回のまとめ

今回はプッシュ通知のことはとりあえず置いておいてサイトをPWA化する時に最低限必要なものだけご紹介しました。
今回の記事にあるようにmagnifest.jsonとserviceworker.jsを設置したりして色々やれば、サイトをインストールできるようにしたり、表示を早めたりすることは可能です。

PWA化については他にもいろんなサイトや本で紹介されているので、そちらも併せて参考にしてください。
次回は、いよいよプッシュ通知を送るためのあれこれについて解説していきたいと思います。

続きを読む

エントリーページ移動

ユーティリティ

Twitter

記事検索

ページ上部へ