コンテンツへスキップ

PWAの基礎知識(その4)「Web App Manifest」

PWA(Progressive Web App)という単語がトレンドになっています。PWAは特定の技術を指す言葉ではなく、様々な技術が組み合わさったスマートフォンにおけるWebアプリのベストプラクティスと言えます。

今回はそんなPWAの基礎になる、Web App Manifestについて紹介します。

利用できるブラウザについて

Web App Manifestはまだ仕様が確定しておらず、一部のWebブラウザでしか使えません。Google Chromeはデスクトップ(アイコンのみ)、スマートフォンともに利用できます。また、iOSのSafariでは11.3からサポートを開始しています。デスクトップで利用できるのはEdgeくらいのようです。

機能について

Web App ManifestはWebアプリケーションをデスクトップまたはスマートフォンアプリのようにインストールできる機能になります。HTML5、JavaScriptが高機能化することによって、ローカルアプリケーションと変わらないレベルで動作するようになっています。そこで生み出されたのがWeb App Manifestになります。

同様の技術としてはCordovaやElectronが知られています。ただしWeb App ManifestはあくまでもWebアプリケーションなので、ローカルファイルにアクセスしたり、OS独自の機能は利用できません。

ブックマークとの違い

これまでもスマートフォンではホーム画面に追加する機能としてブックマークが用意されていました。しかし、Web App Manifestの場合はより能動的にユーザへインストールを呼びかけられるのが特徴です。

バナーは2回目以降の訪問者に対して表示が可能です(表示は自動で行われます)。このバナーからWebアプリケーションをインストールできます。

インストールされたアプリケーションは他のスマートフォンアプリと同じように扱われます。Androidの場合はAPKファイルとして提供されるようです。

技術要件

Web App Manifestの提供法としてはマニュフェスト用のJSONファイルをWebサイトに置くところからはじめます。これはmanifest.jsonというファイルのことが多いようです。また、アプリとしてインストールされた際のアイコンを用意します。manifest.jsonは次のような内容になります。


{
"lang": "ja",
"display": "standalone",
"short_name": "hifiveTodo",
"name": "hifiveTodo",
"orientation": "portrait",
"icons": [
{
"src": "launcher-icon-1x.png",
"type": "image/png",
"sizes": "48×48"
},
{
"src": "launcher-icon-2x.png",
"type": "image/png",
"sizes": "96×96"
},
{
"src": "launcher-icon-3x.png",
"type": "image/png",
"sizes": "192×192"
}
],
"start_url": "./index.html",
"background_color": "#e1efde",
"theme_color": "#e1efde"
}

view raw

index.json

hosted with ❤ by GitHub

そして、Web App ManifestはWebブラウザがService Workerに対応していなければなりません。アプリと同様に動くので、オフラインでも問題なく利用できる必要があるためです。そして最後にサイトがHTTPSになっている必要があります。これらの条件が整ってはじめてインストール用のバナーが表示されるようになります。

インストール後の動作について

インストールされたWebアプリケーションは他のスマートフォンアプリと同じようにタップで開きます。

最初に表示するURLは指定可能です。さらにインテントにも対応しているので、特定のリソースを直接開くこともできます。

メリット

Web App Manifestを使うことで、これまでのようにGoogle PlayやApp Storeを使わなくともアプリの配布ができるようになります。Webアプリはネイティブアプリに比べて軽量であり、すぐにインストールできます。そしてオフラインでもある程度利用し続けられるのがメリットです。

アプリによってはアプリ内課金での30%の手数料であったり、配布できるアプリの種類や自由度のなさに不満を感じていることもあるかと思います。Web App Manifestによって、そういった制限がなくなるメリットは大きいでしょう。

デメリット

ネイティブアプリに比べると利用できる機能に制限があるのは否めません。また、DOMによる表示速度はネイティブに比べると遅く感じますが、グラフィックスを徹底的に活用するものでもなければ十分でしょう。JavaScriptのAPIに依存しますので、例えばiOSではgetUserMediaを使えないなど、作れるアプリ、作れないアプリの差があると言えます。


Web App ManifestはWebアプリケーションが広まっていく大きなきっかけになると考えられます。企業においてもスマートフォンアプリを社内向けに開発していたケースにおいて、その配布の管理をWeb App Manifest化することによって、より素早く手軽にできるようになる可能性があります。

IndexedDB1系と2系の違いについて

IndexedDBはまだ普及していると言える段階にはありませんが、それでも開発者のフィードバックを受けてバージョンアップが行われています。最新のブラウザで実装されているのは IndexedDB2系になります。

今回は1系と2系で何が変わったのかを紹介します。

オブジェクトストアとインデックス名を変更できるようになった

db.objectStore であったり、 objectStore.index で作成済みのオブジェクトを取得し、nameメソッドで名前を変えられるようになりました。これは構造変更を伴うので、 onupgradeneeded の中で行う必要があります。

 

 

 

 


let db;
let version = 2;
const request = indexedDB.open("database", version);
request.onerror = function(event) {
alert("IndexedDB が使えません");
};
request.onupgradeneeded = function(event) {
let txn = event.target.transaction;
debugger;
const objectStore = txn.objectStore("items", { keyPath: "id" });
objectStore.name = 'products';
const index = objectStore.index('name');
index.name = 'title';
};
request.onsuccess = function(event) {
db = event.target.result;
}

view raw

index.js

hosted with ❤ by GitHub

クローズ時のイベントが追加

IndexedDBを閉じた時にoncloseイベントが発火するようになりました。が、筆者環境で試す限りコールされませんでした。

 

 

 


let db;
let version = 2;
const request = indexedDB.open("database", version);
request.onerror = function(event) {
alert("IndexedDB が使えません");
};
request.onsuccess = function(event) {
db = event.target.result;
db.onclose = function(event) {
console.log('IndexeDBを閉じました');
}
}

view raw

index.js

hosted with ❤ by GitHub

バイナリインデックス対応

インデックスは文字列や数字だけでしたが、ArrayBufferやDataViewなどもインデックスに指定できるようになりました。

トランザクションに objectStoreNames メソッドが追加

トランザクションに、対象となっているオブジェクトストアを一覧する objectStoreNames が追加されました。通常のトランザクションであれば指定したオブジェクトストアの一覧、IndexedDBのバージョンが変わった場合には全オブジェクトストアの一覧が返ってきます。

 

 


let db;
let version = 4;
const request = indexedDB.open("database", version);
request.onerror = function(event) {
alert("IndexedDB が使えません");
};
request.onupgradeneeded = function(event) {
db = event.target.result;
let txn = event.target.transaction;
console.log(txn.objectStoreNames);
// -> DOMStringList {0: "items", 1: "products", length: 2}
};

view raw

index.js

hosted with ❤ by GitHub

getAll/getAllKeysの追加

対象になっているオブジェクトストアで全データを取得するgetAllと、全データのキーを取得するgetAllKeysが追加されました。

IDBKeyRange.includes の追加

IndexedDBでデータを絞り込む際に使う IDBKeyRange で、ある値が対象範囲にあるかどうかを判定する IDBKeyRange.includes が追加されました。

 


let openRange = IDBKeyRange.bound(5, 10, true, true);
openRange.includes(5); // false
openRange.includes(10); // false
openRange.includes(7); // true

view raw

index.js

hosted with ❤ by GitHub

これは数値に限らず利用できます。

 

 

 

 

 


today = new Date;
weekago = new Date – 24 * 1000 * 60 * 60 * 7;
openRange = IDBKeyRange.bound(weekago, today, true, true);
openRange.includes(today); // false
openRange.includes(new Date(today – 1)); // true
openRange.includes(weekago); // false
openRange.includes(new Date(weekago – 1 + 2)); // true

view raw

index.js

hosted with ❤ by GitHub

まとめ

IndexeDB 1系はEdge/IE11でも利用できます(IE11ではサポート対象外であったり、EdgetではWeb Workersの中では動かないという制限もあります)。IndexedDB 2 についてはIE/Edgeともに利用できません。実際に利用する際には、こういった点にも注意して使う必要があるでしょう。

Can I use… Support tables for HTML5, CSS3, etc

IndexedDBでデータをまとめて取得するには?

IndexedDBはWebブラウザ上で使えるJSONをサポートしたKVSです。KVSなのでデータの取得はキー単位なのですが、それだとキーを知らないとデータにアクセスできなくなりますので若干不便です。

そこで使えるのがカーソルとインデックスになります。

カーソルの使い方

カーソルはオブジェクトストアに対して openCursor メソッドで実行します。まず IndexedDB を開きます。


let db;
let version = 1;
const request = indexedDB.open("database", version);
request.onerror = function(event) {
alert("IndexedDB が使えません");
};
request.onsuccess = function(event) {
db = event.target.result;
}

view raw

index.js

hosted with ❤ by GitHub

次にトランザクションを開き、オブジェクトストアに対して openCursor メソッドを実行します。 データはイテレーションになっており、 continue メソッドで次のデータを取得します。


const transaction = db.transaction(["items"]);
const objItem = transaction.objectStore("items");
objItem.openCursor().onsuccess = function(event) {
const row = event.target.result;
if (row) {
console.log(row.value);
row.continue();
}
}

view raw

index.js

hosted with ❤ by GitHub

すべてのデータを取得する getAll

IndexedDB2 からは全データをまとめて取得する getAll メソッドが追加されました。こちらはイテレーションではなく、すべてのデータが配列になって取得できます。大量のデータではない時にはこちらのが簡単でしょう。同じようにキーだけを取得できる getAllKeys メソッドも用意されています。


const transaction = db.transaction(["items"]);
const objItem = transaction.objectStore("items");
objItem.getAll().onsuccess = function(event) {
const rows = event.target.result;
console.log(rows);
}

view raw

index.js

hosted with ❤ by GitHub

インデックスを使う

データに対してインデックスを設けている場合には、それを使ってデータを指定できます。データがnameをキーにして昇順で取得できます(0が最初)。


const transaction = db.transaction(["items"]);
const objItem = transaction.objectStore("items");
const Index = objItem.index('name');
Index.openCursor().onsuccess = function(event) {
const row = event.target.result;
if (row) {
console.log(row.value);
row.continue();
}
}

view raw

index.js

hosted with ❤ by GitHub

こちらもまた、getAllや getAllKeysが使えます。

まとめ

IndexedDBはあくまでもWebブラウザ内で使う軽量なデータベースになりますので、大量のデータを保存していることはほとんどないでしょう。そのため、openCursorを使って全データを取得したとしても遅くなるようなことはないかと思います。

なお、getAll や getAllKeysはIndexedDB2 からになり、執筆時点ではEdgeは未対応になります。

IndexedDB を使用する – Web API インターフェイス | MDN

Can I use… Support tables for HTML5, CSS3, etc

Webブラウザ上で使えるデータベース、IndexedDBを使ってみよう

Webブラウザ上でデータを保存しておくための仕組みは幾つか用意されています。

  • Cookies
  • localStorage/sessionStorage
  • IndexedDB
  • Web SQL

昔から使われているのはCookiesですが、長い文字列を保存するのには向いていません。一般的に4096バイトくらいまでとなっています。localStorageはそれよりも大きいですが、10MBくらいまでになります(localStorageは恒久、sessionStorageはセッション中のみ保存されます)。Web SQLはSQLが使える高度なデータベース機能ですが、実装がSQLiteを前提としていることで、中立性に欠けるという声もあり、開発は停止しています(via HTML5 – Web SQLデータベース)。現在は IndexedDBの利用が推奨されています。

今回は IndexedDB の使い方について紹介します。

IndexedDB の概要

IndexedDBはlocalStorageなどと同じく、キーバリュー型のストレージになります。localStorageは文字列しか保存できないのでJSONなどを保存しようと思った場合には文字列に変換する必要がありますが、 IndexedDBはJSONのまま保存できます。かといって一般的なデータベースのように検索を行える訳ではありません。

localStorageは同期型で、getItem/setItemを実行できましたが、IndexedDBは非同期型の技術になっています。また、オブジェクト(テーブル相当)をあらかじめ定義しておく必要があり、構造をバージョンという形で管理します。構造を作る際にはユニーク指定も可能です。オブジェクトの構造は自由に変えられますが、キーは定義しておく必要があります。

DBを開く

まず最初にデータベースを開きます。この時、バージョン番号を指定します(整数のみ)。このバージョン番号があがると onupgradeneeded が呼ばれますので、この際にスキーマを変更します。通常時は onsuccess が呼ばれます。


let db;
let version = 1;
const request = indexedDB.open("database", version);
request.onerror = function(event) {
alert("IndexedDB が使えません");
};
request.onupgradeneeded = function(event) {
db = event.target.result;
const objectStore = db.createObjectStore("items", { keyPath: "id" });
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("description", "description", { unique: true });
};
request.onsuccess = function(event) {
db = event.target.result;
}

view raw

index.js

hosted with ❤ by GitHub

データの追加

データを追加する際には、オブジェクトストア(テーブル相当)を指定して readwrite でトランザクションを作成します。そしてオブジェクトストアに対して add を実行します。各データ追加時に onsuccess が呼ばれますが、すべての処理が完了した際には oncomplete が呼ばれます。


const transaction = db.transaction(["items"], "readwrite");
transaction.oncomplete = function(event) {
console.log("完了しました");
};
transaction.onerror = function(event) {
console.log("エラーです", event);
};
const objItem = transaction.objectStore("items");
for (let i = 1; i < 10; i += 1) {
objItem.add(
{id: i, name: `テスト商品 ${i}`, description: `テスト商品 ${i} の説明`, add: '追加'}
).onsuccess = function (event) {
console.log(event.target.result);
}
}

view raw

index.js

hosted with ❤ by GitHub

データの取得

データの取得は get を使います。この際には書き込みは発生しませんのでトランザクションはオブジェクトストアを指定するだけで使えます。


db.transaction(["items"])
.objectStore("items")
.get(3)
.onsuccess = function(event) {
console.log("取得しました", event.target.result);
}

view raw

index.js

hosted with ❤ by GitHub

データの更新

データの更新を行う際にはトランザクションを readwrite で作成します。そして取得したデータに対して put を使って更新します。


const Items = db.transaction(["items"], "readwrite").objectStore("items");
Items.get(3)
.onsuccess = function(event) {
const row = event.target.result;
row.name = "商品3の名前を変更";
Items
.put(row)
.onsuccess = function(event) {
console.log("更新しました");
}
}

view raw

index.js

hosted with ❤ by GitHub

データの削除

データの削除はdeleteメソッドに対してキーを指定するだけです。


db.transaction(["items"], "readwrite")
.objectStore("items")
.delete(2)
.onsuccess = function(event) {
console.log("削除完了しました");
}

view raw

index.js

hosted with ❤ by GitHub

まとめ

IndexedDB をそのまま使おうとすると若干の癖があります。作成、更新はオブジェクトストアが必須ですが、取得や削除は不要という処理の違いも面倒だったり、トランザクションの作成も手間に感じます。ラッピングしてくれるORM的なライブラリを使って操作するのが良さそうです。

IndexedDB 自体はlocalStorage以上に便利に使えそうなので、ぜひ活用してみてください。

Can I use… Support tables for HTML5, CSS3, etc

JavaScriptのapplyをどこで使うのか

JavaScriptの関数によっては引数を幾つでも設定できるものがあります。例えばMath.maxです。この関数は引数で与えた数字の内、最大のものを返してくれる関数です。


Math.max(1, 3, 5, 11, 7, 9) -> 11

view raw

index.js

hosted with ❤ by GitHub

しかし引数を動的に変更したい場合はどうしたらいいでしょうか。そこで使えるのがapplyになります。applyは引数を配列で与えられるようになります。


ary = [1, 3, 5, 11, 7, 9];
Math.max.apply(null, ary) -> 11

view raw

index.js

hosted with ❤ by GitHub

最初の引数 null はthisとして何を与えるかになります。nullを与えた場合、thisはwindowオブジェクトになるようです。

自作関数で使う

自作関数の場合、多くは最後の引数をまとめたいと思うかも知れません。


function test(a, b, c, …args) {
console.log(a, b, c);
console.log(args);
}

view raw

index.js

hosted with ❤ by GitHub

このようにしておくと、最初の3つの引数(a/b/c)には値が入り、残りの引数はargsにまとまります。


test.apply(null, [1, 2, 3, 4, 5]);
-> 1,2,3
-> [4, 5]

view raw

index.js

hosted with ❤ by GitHub


引数を配列で準備しておくと、変数の定義が減るので扱いが楽になりそうです。元々配列で受け取れるようにしたり、オブジェクトで受け取ることで解決できそうですが、拡張していく中で徐々に引数が追加されてしまった場合(良くはないですが)はapplyを使えば解決できるでしょう。

PWAの技術要素について

PWA(Progressive Web App)という言葉が良く聞かれるようになってきました。今回はこのPWA全般と、各技術要素について紹介します。

PWAとは

PWAはその名の通り、Web技術の上に構築されています。PWAという技術名がある訳ではなく、幾つかの技術の組み合わせを総称してPWAと言います。

Service Worker

Service Workerはバックグラウンドで動作するJavaScriptの技術です。DOM操作はできないので、重たい処理をバックグラウンドで行ったりします。特に大事なのはキャッシュ用のリソースを取得したり、プッシュ通知を受け取る際に使われると言うことです。

リモートのリソースに対する処理において、オンライン/オフラインの判定によって処理を変えたり、同期処理にも使えます。Service WorkerはXMLHTTPRequestではなく、Fetchしか使えません。

通知

スマートフォンアプリでよく見られるプッシュ通知機能です。PWAでは二つの技術が用意されています。

Push API

Push APIはローカル通知のような機能で、クライアントサイドのJavaScriptだけで実装できます。簡単に実装できるのが魅力ですが、Webページが開かれていないと使えません。ユーザが他のタブを見ている時に新しい通知が来たことを促すといった使い方に向いています。

Notification API

アプリと同じようにリモートから送信できるプッシュ通知機能です。Service Workerを使って実装する必要があります。そのため、モダンなWebブラウザでないと使えません。

ストレージ

データを蓄積しておくストレージは主に2種類あります。

localStorage/sessionStorage

KVSとして使えます。同期型にデータを取得、更新できるので手軽です。ただし5MBくらいまでしかデータを保存できないので注意が必要です。また、保存できるのはテキスト限定で、JSONなどは文字列に変換してから保存する必要があります。

localStorageは恒久的、sessionStorageはそのセッション中だけ使えるストレージです。Cookieなどのように有効期限がないのが魅力でしょう。

IndexedDB

基本はKVSですが、インデックスを設けたりできるのでlocalStorageに比べると多少高機能です。JSONをそのまま保存できます。ただし検索したりする機能はなく、すべてのデータを取り出してフィルタする必要があります。

localStorageに比べると大きなサイズで利用できます。

HTTPS

PWAはHTTPS(localhostは除く)でしか提供できません。現在、サイトのHTTPS化は必須とも言える状態ですが、PWA化を考える上では必須になります。HTTPS自体、無料で使えるようにもなっています。セキュリティと存在証明と切り離して適用するのが良さそうです。

まとめ

PWAはスマートフォン向けの技術になります。元々ハイブリッドアプリと呼ばれるHTML5とネイティブを合わせた技術がありましたが、HTML5が高機能化するのに伴って、HTML5だけでアプリのような機能が実現できるようになってきたということでしょう。

iOSのSafariがPWA必須技術を幾つも実装していないため、率先して導入する企業は多くありませんが、今後の時流としてはPWA化が必須になってくるでしょう。

Progressive web apps – アプリセンター | MDN

パスワードレスの時代が来る!Web authentication APIの仕組みについて

Web authentication API(WebAuthnとも呼ばれます)がついにFirefox 60で対応し、本格的な普及に向けて進み始めました。Google ChromeやEdgeでも次期バージョンから対応が予定されています。残るはSafariですが、こちらは未定となっています。ただ、MacBook Proには指紋認証も付いており、iPhoneでも指紋や顔認証が付いていることを考えると将来的にはWeb authentication API互換で使えるようになることでしょう。

今回はWeb authentication APIの仕組みについて紹介します。

肝は登録と認証

パスワードレス認証を可能にするためには、二つのイベントに対応する必要があります。一つはデバイスの登録で、これはすでにログインした状態で行われる処理です。もう一つは認証で、これはデバイスがユーザに変わって入力を行います。Yubikeyのようなデバイスがよく使われますが、これはキーボードとして認識されるデバイスです。つまりユーザに変わって何かの文字列を入力しています。

公開鍵認証

Web authentication APIは公開鍵認証を使っています。また、認証された結果自体はサーバ側で処理されます。そのため、HTML5のAPIではありますが、サーバの存在が必須という点が特徴です。

登録処理について

登録処理では navigator.credentials.create() を使います。サーバとの流れをフローで描くと次のようになります。全部で6つのステップになります。

まずサーバからチャレンジ、ユーザ情報、PR(Relying party info)の3つを送ります。チャレンジ、PR辺りはOpenID 2.0で使われるものと似ているようです。そして、Webブラウザはそれらの情報に加えてクライアントのデータをAuthenticatorと呼ばれるデバイス(Yubikeyなど)に送ります。デバイスは新しい公開鍵、証明書ID、認証情報を返します(これを認証オブジェクトと呼びます)。Webブラウザは認証オブジェクトとクライアントのデータを合わせてサーバに送ります。そしてサーバで検証して完了となります。

送信する仕組みはfetchまたはXMLHTTPRequestになるようです。

認証処理について

認証処理では navigator.credentials.get() を使います。サーバの流れをフローで書くと次のようになります。全部で6つのステップになります。

まずサーバからチャレンジが送られてきます。WebブラウザではチャレンジとRP、クライアントデータのハッシュをAuthenticatorに送ります。Authenticatorでは値の検証とアサーションの作成を行います。Webブラウザには認証データと署名が送られてきます。Webブラウザは認証データと署名、そしてクライアントデータのJSONをサーバに送ります。サーバはそれらの値を検証します。

クライアントの実装デモ

Web Authentication API – Web APIs | MDNにクライアントのコードサンプルがあります。

RPはサービス名を指定しています。また、この例ではチャレンジを生成していますが、本来はサーバから送られるランダムな文字列になります。


// sample arguments for registration
var createCredentialDefaultArgs = {
publicKey: {
// Relying Party (a.k.a. – Service):
rp: {
name: "Acme"
},
// User:
user: {
id: new Uint8Array(16),
name: "john.p.smith@example.com",
displayName: "John P. Smith"
},
pubKeyCredParams: [{
type: "public-key",
alg: -7
}],
attestation: "direct",
timeout: 60000,
challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
0x8C, 0x0A, 0x26, 0xFF, 0x22, 0x91, 0xC1, 0xE9, 0xB9, 0x4E, 0x2E, 0x17, 0x1A, 0x98, 0x6A, 0x73,
0x71, 0x9D, 0x43, 0x48, 0xD5, 0xA7, 0x6A, 0x15, 0x7E, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0F, 0xEF
]).buffer
}
};
// sample arguments for login
var getCredentialDefaultArgs = {
publicKey: {
timeout: 60000,
// allowCredentials: [newCredential] // see below
challenge: new Uint8Array([ // must be a cryptographically random number sent from a server
0x79, 0x50, 0x68, 0x71, 0xDA, 0xEE, 0xEE, 0xB9, 0x94, 0xC3, 0xC2, 0x15, 0x67, 0x65, 0x26, 0x22,
0xE3, 0xF3, 0xAB, 0x3B, 0x78, 0x2E, 0xD5, 0x6F, 0x81, 0x26, 0xE2, 0xA6, 0x01, 0x7D, 0x74, 0x50
]).buffer
},
};
// register / create a new credential
navigator.credentials.create(createCredentialDefaultArgs)
.then((cred) => {
console.log("NEW CREDENTIAL", cred);
// normally the credential IDs available for an account would come from a server
// but we can just copy them from above…
var idList = [{
id: cred.rawId,
transports: ["usb", "nfc", "ble"],
type: "public-key"
}];
getCredentialDefaultArgs.publicKey.allowCredentials = idList;
return navigator.credentials.get(getCredentialDefaultArgs);
})
.then((assertion) => {
console.log("ASSERTION", assertion);
})
.catch((err) => {
console.log("ERROR", err);
});

view raw

index.js

hosted with ❤ by GitHub

試してみた結果

現在、一番分かりやすいデモは https://webauthn.org/ でしょう。ユーザ名だけ指定して登録し、さらにユーザ名を指定してログインできます。パスワードの入力は一切なく、認証できます。

GoogleやFacebook、DropboxなどのサイトではU2F(Universal Second Factor)にYubikeyなどが利用できます。これは多要素認証であって、ログインIDとパスワードで認証後、アプリやSMSで認証コードを送る代わりにYubikeyなどに認証コードを入力してもらう方法になります。Web authentication APIとは異なるのでご注意ください。

まとめ

Web authentication APIが流行っていけば、パスワードが不要なものになります。その結果として、パスワードが漏洩して不正アクセスを受けることもなくなります。デバイスを紛失した場合、管理画面から無効化することで安全に対応できるでしょう。また、ユーザ名などの情報と紐付けて用いますので、ユーザ名が分からない状態ではデバイスは使えないというメリットがあります。

問題はYubikeyなどが使えないスマートフォンやタブレットでしょう。iOSの指紋認証がWeb authentication APIに準拠してくれれば良いですが、アプリの認証には使えない可能性があります(SDKがリリースされれば良いですが)。そうなるとWebブラウザで認証後、コードを生成するような仕組みが必要になりそうです。また、専用デバイスが必要という点も面倒です。Web authentication APIに準拠した仕組みをデスクトップ向けのソフトウェアとスマートフォンアプリで開発すると使いやすくなりそうです。

Web Authentication API – Web APIs | MDN

PWAの根幹技術、Notifications APIの使い方

アプリでは当たり前になっているプッシュ通知ですが、Webブラウザ向けにはNotifications APIが用意されています。スマートフォンではまだ実装されていませんが、デスクトップ向けであれば十分使える状態になっています(Notifications APIはPush APIとは別です)。

今回はNotifications APIの実装方法を紹介します。

Notifications APIとPush APIの違い

Push APIはサーバからブラウザに対してプッシュ通知を送る仕組みで、Service Workerを利用します。Notifications APIはサーバサイドが不要で使えますが、Webブラウザがそのサイトを表示している時にしか使えません。

許可状態を知る

まず大事なのがユーザが通知を受け取ると許可しているかどうかです。それを調べるには Notification.permission を使います。値は3つあります。

  • default
    まだ許可を求めていません(初期値)
  • granted
    通知を許可しています
  • denied
    明確に拒否しています

そのため、deniedの時には処理を行わないというのが基本になるでしょう。

許可を得る

許可を得るのは Notification.requestPermission(); で行います。


Notification.requestPermission(permission => {
if (permission === "granted") {
// 許可した場合
}
});

view raw

index.js

hosted with ❤ by GitHub

grantedが得られれば許可を得た状態です。

通知を作成する

通知はこんな形で簡単に作成できます。


var options = {
body: 'メッセージ本文',
icon: 'img/icon.png'
}
var n = new Notification('メッセージタイトル', options);

view raw

index.js

hosted with ❤ by GitHub

通知を閉じる

表示した通知を閉じる場合は、通知オブジェクトのcloseを使います。


n.close.bind(n)

view raw

index.js

hosted with ❤ by GitHub

通知のイベント

通知には幾つかのイベントが設定されています。

  • onclick

    通知をクリックした時に呼ばれます

  • onerror

    エラーが出た時に呼ばれます

  • onclose

    閉じた際に呼ばれます

  • onshow

    表示した時に呼ばれます

使いどころ

Notifications APIはWebページを表示している時にしか使えません。そのため、アプリのプッシュ通知のように使っていないユーザを呼び起こすのには向きません。むしろ時間がかかる処理でユーザを待たせてしまう時に完了をお知らせしたり、チャットなどで別ページを開いている時にお知らせすると言った使い方になるでしょう。

まとめ

WebSocketなどと組み合わせればサーバから通知を出したい時に命令を出すと言った使い方はできそうです。とは言え、基本的にはWebページ上で起きた変化をユーザに気付かせると言った目的で使うのが良さそうです。

JavaScriptの新しいAPI、ジェネレータの使い方

ES6で新しく追加されたJavaScript APIにジェネレータがあります。使い方次第で新しいJavaScriptの書き方が可能です。ES6 GeneratorはIE11を除くモダンなブラウザではサポートされていますので、ES6が利用できるのであれば使わない手はないでしょう。

ジェネレータの基本形

ジェネレータは function* という形で記述します。そしてyieldという形で値を返却します。このyieldが呼ばれる度にジェネレータの呼び出し元に値が送られます。


function* gen(i) {
i++;
yield i;
i++;
yield i;
i++;
yield i;
}

view raw

index.js

hosted with ❤ by GitHub

呼び出す際には関数を実行します。


const g = gen(3);

view raw

index.js

hosted with ❤ by GitHub

さらに このジェネレータ(g)のnextメソッドを呼び出すとyieldと書かれた部分まで処理が進みます。返り値はオブジェクトで、valueキーに値が、doneキーにyieldが終了したかどうかが入ってきます。


console.log(g.next());
-> {done: false, value: 4}
console.log(g.next());
-> {done: false, value: 5}
console.log(g.next());
-> {done: false, value: 6}
console.log(g.next());
-> {done: true, value: undefined}

view raw

index.js

hosted with ❤ by GitHub

nextメソッドではなく、for of を使うこともできます。この場合はvがvalueキーの内容になります。


for (let v of g) {
console.log(v);
-> 4、5、6が順番に出力されます
}

view raw

index.js

hosted with ❤ by GitHub

オブジェクトを返せば複数の値を渡すこともできます。


function* gen(i) {
yield {o: i, n: ++i};
yield {o: i, n: ++i};
yield {o: i, n: ++i};
}
let g = gen(3);
for (let {o, n} of g) {
console.log(o, n);
-> 3, 4
-> 4, 5
-> 5, 6
}

view raw

index.js

hosted with ❤ by GitHub

nextを使う場合、yieldに対して引数を送ることができます。この値は yield の返り値として受け取れるものになります。


function* gen(i) {
let m = yield {o: i, n: ++i};
m = yield {o: m, n: ++m};
m = yield {o: m, n: ++m};
}
let g = gen(3);
let {o, n} = g.next().value;
console.log(o, n);
-> 3, 4
let {o: a, n: b} = g.next(n).value;
console.log(a, b);
-> 4, 5
let {o: c, n: d} = g.next(n).value;
console.log(c, d);
-> 5, 6

view raw

index.js

hosted with ❤ by GitHub

配列にeachWithIndexを実装する

例えば配列を順番に処理する場合、for を使います。


const ary = [];
ary.push(1);
ary.push(2);
ary.push(3);
for (let i = 0; i < ary.length; i += 1) {
let value = ary[i];
}

view raw

index.js

hosted with ❤ by GitHub

この毎回 value = ary[i] というのを書くのが面倒です。逆に for of を使った場合にはインデックスがないので不便な時もあります(回避する方法としてentriesを使う手もあります)。


for (let value of ary) {
// インデックスがない
}

view raw

index.js

hosted with ❤ by GitHub

そこでeachWithIndexメソッドを作って、インデックスも返ってくるイテレータを作ってみます。これはArrayのprototypeに作ります。


Array.prototype.eachWithIndex = function* () {
for (let i = 0; i < this.length; i += 1) {
yield {index: i, value: this[i]};
}
};

view raw

index.js

hosted with ❤ by GitHub

後はこのメソッドをfor ofの中で呼び出します。 eachWithIndex は関数であり、それを実行すると処理が開始されるので注意してください。


const ary = [];
ary.push(1);
ary.push(2);
ary.push(3);
for (let {index, value} of ary.eachWithIndex()) {
console.log(index, value);
}

view raw

index.js

hosted with ❤ by GitHub

クラスにイテレーションを追加する

もし自作クラスにイテレーションを追加したい場合には、 Symbol.iterator を定義します。


class Hello {
constructor() {
this.ary = ['a', 'b', 'c'];
}
* [Symbol.iterator](){
for (let v of this.ary) {
yield v + v;
}
}
};

view raw

index.js

hosted with ❤ by GitHub

これを実行します。


const hello = new Hello();
const ary = [];
for (let v of hello) {
console.log(v);
-> aa, bb, ccと出ます
}

view raw

index.js

hosted with ❤ by GitHub

こちらはJSFiddleにデモを掲載しています。


イテレーションを使うことで、ループ処理であったり、段階を踏んで処理するようなところが書きやすくなります。JavaScriptはシングルスレッドで非同期処理が面倒ですが、イテレーションを使えば処理が完了してから次の処理に移動すると言ったことも書けるでしょう。

WebAssembly が書ける言語まとめ

Webブラウザ上でコンパイルされたコードが動作するWebAssembly。モダンなブラウザではほぼ実装されており、実用段階に入ってきています。現在の仕様ではDOM操作ができませんので、主にCanvasやWebGLを使う場合であったり、重たい計算処理を行う際に使われるでしょう。

そんなWebAssemblyの魅力の一つがJavaScript以外のプログラミング言語でWebブラウザ上で動くプログラムが書けるということです。今回はWebAssemblyを作ることができるプログラミング言語を紹介します。

C/C++

C言語やC++はEmscriptenを使ってWebAssemblyに変換できます。

asm.js

JavaScriptを高速にした(WebAssemblyの一世代前とも言えるかも知れません)asm.jsはWebAssembly/binaryen: Compiler infrastructure and toolchain library for WebAssembly, in C++でWebAssemblyに変換できます。

Rust

Mozillaの開発するプログラミング言語、Rustは元々Emscriptenが必要でしたが、開発版においてrustcだけでWebAssemblyにコンパイルできるようになっています。

C\

.NETのC#はlrz/mono-wasmを使ってWebAssemblyを生成します。また、UnityであればWebGLとして出力することでWebAssemblyを生成することも可能です。ただしランタイム全体が含まれていますのでC#の特定の機能だけをコンパイルできるという訳ではありません。

Swift/Objective-C

こちらは未検証なのでご注意ください Emscriptenを使った場合、LLVMであればWebAssemblyにできるそうです。そのためLLVMに対応した言語であればWebAssemblyに変換できるということになります。


現在、すべての言語においてすべてのライブラリが問題なく動作するという訳ではありません。サードパーティー製のライブラリはもちろん、言語標準の機能であっても動かないケースがあります。情報もまだ多くはないので、試行錯誤しながら実装していくことになるでしょう。

今後Webアプリケーションが大型化したり、リッチ化していく中でWebAssemblyは欠かせない技術になります。公開できないコード(秘密鍵など)を扱う場合にも有効な技術になります。ぜひお試しください。