knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

【PWA】シリーズPWA (8) 複数の端末に通知を送る

ここまでで、任意の内容でプッシュ通知を送ることはできました。

ところが現状では、今後複数のサブスクリプションが増えた時に対応ができません。

push.jsは配列にて複数のサブスクリプションを送れるようになっているため、サブスクリプション が増えること自体は問題ありません。

ところが現状push.jsにハードコーディングされているため、増えれば増えるほどpush.jsに追記をしていかなければいけません。

そこでここからさらに一歩踏み込み、サブスクリプションの情報を永続的に管理できる様に変えてゆきます。

基本的にはこちらで解説されているように、DBを利用してユーザー管理をする要領でサブスクリプション を管理します。

ajike.github.io

今回は、このデータベースとの連携部分を実装してゆきます。 サブスクリプションの登録・更新・プッシュの機能があるので、APIとして機能を提供できるようにします。

  1. push.jsを別環境に切り離してWebアプリ化し、登録・更新・プッシュのAPIを公開。今回はフレームワークとしてExpressを利用。
  2. APIを通じて受け取ったサブスクリプションの情報をDBで管理する。今回はPostgreSQLを使用。
  3. クライアントからはサブスクリプションの情報をヘッダーに含めて、push.jsにリクエストを送るようにする。

まずは、APIを提供するところから始めます。

ExpressでAPIを提供

まずはpush.jsを以下のような骨組みを以下のようにします。

ExpressというWebアプリケーションフレームワークを使用しますので、npmにてインストールください。

expressjs.com

const express = require('express');
const app = express();

// addでアクセスされたらサブスクリプションをDBに追加する
app.post('/add', function(req, res) {
});

// deleteでアクセスされたら古いサブスクリプションの削除フラグをtrueにする
app.post('/delete', function(req, res) {
});

// pushでアクセスされたら削除フラグがfalseのものに対して通知を送る
app.post('/push', function(req, res) {
});

// サーバ起動
const server = app.listen(process.env.PORT || 8000);

Expressの文法は割愛しますが、node push.jsでサーバーが立ち上がるので、その環境(ローカルだとhttp://localhost:8000になります)に対して「/add」「/delete」「/push」というURLでアクセスが会ったときにそれぞれ何かしらの処理を割り振ることができるようになりました。

アクセス時のリクエストヘッダーにサブスクリプションの情報が入っているため、reqオブジェクトのbodyを取得できるようにしなければいけません。

ところがExpressの仕様によると現状はbodyを取得するためにはいちどパーサーを通さないとundefinedになってしまうため、以下の記述を追加します。

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

// CORSを許可する
app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

// urlencodedとjsonは別々に初期化する
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// addでアクセスされたらサブスクリプションをDBに追加する
app.post('/add', function(req, res) {
  console.log(req.body);
});

// deleteでアクセスされたら古いサブスクリプションの削除フラグをtrueにする
app.post('/delete', function(req, res) {
  console.log(req.body);
});

// pushでアクセスされたら削除フラグがfalseのものに対して通知を送る
app.post('/push', function(req, res) {
  console.log(req.body);
});

// サーバ起動
const server = app.listen(process.env.PORT || 8000);

こうすることでリクエストヘッダーのbodyを取得できるようになります。

また、クライアントからはfetch APIを使ってリクエストを投げるようにする予定なので、今のうちにCORSエラーが起きないようにしておきます。

こちらで(1)の実装は完了しました。

テーブル定義

DBとの連携部分を実装する前に、テーブル定義を行います。

以下のようなテーブルでサブスクリプションのデータを管理します。

id registrationID publicKey auth endpoint deleted
INTEGER
AUTO INCREMENT
TEXT
NOT NULL
TEXT
NOT NULL
TEXT
NOT NULL
TEXT
NOT NULL
BOOLEAN
NOT NULL
デフォルトはFALSE
主キー 通知許可時にクライアントから送られる値を格納 同左 同左 同左 この値によって有効なサブスクリプションかどうかを判別

そして通知送信時にはdeletedカラムがFALSEのものに対して通知を送るため、このカラムに対してインデックスを生成します。

DB作成・DB接続・テーブル作成

DBはプログラムでは作成せず、あらかじめPostgreSQLをインストールし、「subscriptions」という名前でDBを作成します。

その前提で、DBに接続してテーブルを作成するところからプログラムで実装します。

生のSQLを発行しても良いのですが、ここではDBごとの方言を吸収できるよう、SequalizeというORマッパーを利用します。

www.npmjs.com

sequelize.org

const express = require('express');
const bodyParser = require('body-parser');
const Sequelize = require('sequelize');
const app = express();

// CORSを許可する
app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  next();
});

// urlencodedとjsonは別々に初期化する
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// DBに接続
const sequelize = new Sequelize('接続文字列', {
	logging: false,
	operatorsAliases: false
})

// テーブルを作成
const subscriptions = sequelize.define('subscriptions', {
	id: {
		type: Sequelize.INTEGER,
		autoIncrement: true,
		primaryKey: true
	},
	registrationID: {
		type: Sequelize.STRING,
		allowNull: false
	},
	publicKey: {
		type: Sequelize.STRING,
		allowNull: false
	},
	auth: {
		type: Sequelize.STRING,
		allowNull: false
	},
	endpoint: {
		type: Sequelize.STRING,
		allowNull: false
	},
	deleted: {
		type: Sequelize.BOOLEAN,
		allowNull: false,
		defaultValue: false
	}
}, {
	freezeTableName: true,
	timestamps: true,
	indexes:[{
		unique: false,
		fields:['deleted']
	}]
});

subscriptions.sync();

// addでアクセスされたらサブスクリプションをDBに追加する
app.post('/add', function(req, res) {
});

// deleteでアクセスされたら古いサブスクリプションの削除フラグをtrueにする
app.post('/delete', function(req, res) {
});

// pushでアクセスされたら削除フラグがfalseのものに対して通知を送る
app.post('/push', function(req, res) {
});

// サーバ起動
const server = app.listen(process.env.PORT || 8000);

こちらでサーバー起動時にDBに接続し、テーブルが存在しなかったらテーブルを作成するようになりました。

次回予告

次回は、サブスクリプションを登録・削除、そしてpushするAPIを作成します。