Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PWAの基本とTips

 PWAの基本とTips

Monaca UG OHMIYA #3 ~集まれMonaca ユーザー!~
https://monacaug.connpass.com/event/98537/

の発表資料です。

Atsushi Nakatsugawa

October 18, 2018
Tweet

More Decks by Atsushi Nakatsugawa

Other Decks in Technology

Transcript

  1. DAY 2018/09/10 # MOONGIFT 自己紹介 @goofmint fb.me/goofmint 中津川 篤司 株式会社MOONGIFT

    代表取締役 www.moongift.jp エバンジェリスト コミュニティ
  2. PAGE DAY 2017/11/01 # MOONGIFT X / 12 PWAとは? •

    Progressive Web Appsの略 • Webアプリケーションをよりスマートフォンアプリに近づけ る仕組み • 特定の技術のことではない • Monacaの近いうちに対応するらしいよ
  3. PAGE DAY 2017/11/01 # MOONGIFT X / 12 PWAの要素 1.

    App Manifest 2. Service Worker 3. プッシュ通知 4. indexedDB 5. その他
  4. PAGE DAY 2017/11/01 # MOONGIFT X / 12 必要な技術 •

    App Manifest • Service Worker • SSL/TLS • Android / Google Chromeは対応 • iOS Safariは微妙に対応
  5. PAGE DAY 2017/11/01 # MOONGIFT X / 12 App Manifest

    • アプリの仕様(マニフェスト)を記述したファイル • アプリ名 • アイコン • 最初に表示するURL • 背景色 • テーマ色
  6. PAGE DAY 2017/11/01 # MOONGIFT X / 12 manifest.jsonの例 {

    "short_name": "短いアプリ名", "name": "長いアプリ名", "icons": [ { "src": "アイコン.png", "sizes": "192x192", "type": "image/png" } ], "start_url": "./index.html", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } <link rel="manifest" href="/manifest.json">
  7. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Worker

    • WebブラウザのJavaScriptとは別プロセスで動くJavaScript • 主にキャッシュ、プッシュ通知の受信で利用 • ネットワーク(fetch)/indexedDBが利用可能 • DOMは利用不可 • メッセージでWebブラウザのJavaScript通信可
  8. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Workerの登録

    if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/serviceworker.js') .then(registration => { // 登録成功 }).catch(err => { // 登録失敗 }); }
  9. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Workerでの処理

    とりあえずキャッシュ処理 const CACHE_NAME = 'SALES_SYSTEM'; const urls = [ ‘/', ‘/styles/app.css', '/script/app.js' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll(urls); }) ); });
  10. PAGE DAY 2017/11/01 # MOONGIFT X / 12 ネットワークアクセス self.addEventListener('fetch',

    await (event) => { event.respondWith( const response = await caches.match(event.request) if (response) return response; return fetch(event.request); ); });
  11. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Tips 1

    : キャッシュ • URL と レスポンスのKVS • キャッシュを普通に使うとGETリクエストのみ • 署名生成とかは非現実的なのでWebブラウザから送るのがポ イント
  12. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Tips2:キャッシュ •

    キャッシュは二回目以降のアクセスで有効 • オンライン/オフライン関係なく利用される
  13. PAGE DAY 2017/11/01 # MOONGIFT X / 12 ハマった例 •

    NCMBでは署名処理 -> ヘッダーに追加してアクセス • キャッシュ(fetch)はヘッダーなしでアクセス -> 404 • 二回目以降のアクセスはオンラインでも404が返ってくる
  14. PAGE DAY 2017/11/01 # MOONGIFT X / 12 動的キャッシュ(Web) const

    channel = new MessageChannel(); navigator.serviceWorker.controller .postMessage({ action: 'cache', url: res.req.url, response: body }, [channel.port2]);
  15. PAGE DAY 2017/11/01 # MOONGIFT X / 12 動的キャッシュ(Service Worker)

    self.addEventListener('message', e => { if (e.data.action == 'cache') { caches.open(CACHE_NAME) .then((cache) => { const res = new Response(e.data.response); cache.put(e.data.url, res); }) } });
  16. PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知の種類 •

    ローカルプッシュ
 Notifications API • Chrome向けプッシュ
 Firebase必須 • macOS Safari向けプッシュ
 証明書必須 • Push API/VAPID
 Chrome/Firefox/Edge
  17. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Notifications API

    • サーバ不要 • ユーザの確認は必要 • 開いているタブに対してのみ • チャット、Webアプリの処理完了など
  18. PAGE DAY 2017/11/01 # MOONGIFT X / 12 許可を得る Notification.requestPermission(permission

    => { if (permission === "granted") { // 許可した場合 } // denied });
  19. PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知を送信 const

    options = { body: 'メッセージ本文', icon: 'img/icon.png' } const n = new Notification('メッセージタイトル', options);
  20. PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知を自動で閉じる const

    n = new Notification(‘title', options); setInterval(() => { n.close.bind(n) }, 3000);
  21. PAGE DAY 2017/11/01 # MOONGIFT X / 12 プッシュ通知のイベント •

    onclick
 通知をクリックした時に呼ばれます • onerror
 エラーが出た時に呼ばれます • onclose
 閉じた際に呼ばれます • onshow
 表示した時に呼ばれます
  22. PAGE DAY 2017/11/01 # MOONGIFT X / 12 クリックしたら消す const

    options = { body: 'メッセージ本文', icon: 'img/icon.png' } const n = new Notification('メッセージタイトル', options); n.onclick = (event) => { // クリックしたら通知を消す n.close.bind(n); }
  23. PAGE DAY 2017/11/01 # MOONGIFT X / 12 独自プッシュ Google

    ChromeのFirebase、macOS Safariのプッシュ通知 今回はスルー
  24. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Push API/VAPID

    • Firebase不要 • Android / Google Chrome / Firefox / Edge対応 • サーバ必要 • Service Worker必須
  25. PAGE DAY 2017/11/01 # MOONGIFT X / 12 受信状態を知る if

    (!('serviceWorker' in navigator)) return; 
 await navigator.serviceWorker.register('./serviceworker.js') 
 if (!('showNotification' in ServiceWorkerRegistration.prototype)) return; 
 if (Notification.permission === 'denied') return; 
 if (!('PushManager' in window)) return;
 const serviceWorkerRegistration = await navigator.serviceWorker.ready; const subscription = await serviceWorkerRegistration.pushManager .getSubscription(); if (subscription) { // すでに購読中 } else { // 未購読 }
  26. PAGE DAY 2017/11/01 # MOONGIFT X / 12 承認を得る const

    serviceWorkerRegistration = await navigator.serviceWorker.ready const subscription = await serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: convertedVapidKey }); console.log(subscription.toJSON());
  27. PAGE DAY 2017/11/01 # MOONGIFT X / 12 JSONが得られる {

    "endpoint":"https://fcm.googleapis.com/fcm/send/e7K...Hag", "expirationTime":null, "keys":{ "p256dh":"BK7...e5U", "auth":"DGt...D8g" } }
  28. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Service Workerの処理

    self.addEventListener('install', () => { console.log('ServiceWorker がインストールされました'); }); self.addEventListener('push', ev => { // payloadの取得 const {title, msg, icon} = ev.data.json(); self.registration.showNotification(title, { icon: icon, body: msg }); });
  29. PAGE DAY 2017/11/01 # MOONGIFT X / 12 URLを指定して実行 const

    webpush = require("web-push"); const keys = require("./application-server-keys.json"); webpush.setVapidDetails( "mailto:[email protected]", keys.publicKey, keys.privateKey ); const subscribers = [/* さっきのJSON */]; const icon = `app.png`; const params = { title: "プッシュ通知です!", msg: `これはサーバから送っています. 今は ${new Date().toLocaleString()} です。 メッセージとアイコンも送っています `, icon: icon }; const res = await Promise.all(subscribers.map(subscription => { return webpush.sendNotification(subscription, JSON.stringify(params), {}); }));
  30. PAGE DAY 2017/11/01 # MOONGIFT X / 12 受信(Service Worker)

    self.addEventListener('install', () => { console.log('ServiceWorker がインストールされました'); }); self.addEventListener('push', ev => { // payloadの取得 const {title, msg, icon} = ev.data.json(); self.registration.showNotification(title, { icon: icon, body: msg }); });
  31. PAGE DAY 2017/11/01 # MOONGIFT X / 12 便利なライブラリ •

    Node.js
 https://github.com/web-push-libs/web-push • Ruby
 https://github.com/zaru/webpush
  32. PAGE DAY 2017/11/01 # MOONGIFT X / 12 データベースの種類 $PPLJF

    MPDBM4UPSBHF JOEFYFE%# αΠζ ໿,# ໿.# ໿.# ࢖͍ํ ಉظ ಉظ ඇಉظ 8FCϒϥ΢β 㾎 㾎 㾎 4FSWJDF8PSLFS º º 㾎 ܕ จࣈྻͷΈ จࣈྻͷΈ ˓
  33. PAGE DAY 2017/11/01 # MOONGIFT X / 12 スキーマ 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; }
  34. PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ追加 const

    transaction = db.transaction(["items"], "readwrite"); transaction.oncomplete = (event) => { console.log("完了しました"); }; transaction.onerror = (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 = (event) => { console.log(event.target.result); } }
  35. PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ検索 db.transaction(["items"])

    .objectStore("items") .get(3) .onsuccess = (event) => { console.log("取得しました", event.target.result); }
  36. PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ更新 const

    Items = db.transaction(["items"], "readwrite")
 .objectStore("items"); Items.get(3) .onsuccess = (event) => { const row = event.target.result; row.name = "商品3の名前を変更"; Items .put(row) .onsuccess = (event) => { console.log("更新しました"); } }
  37. PAGE DAY 2017/11/01 # MOONGIFT X / 12 データ削除 db.transaction(["items"],

    "readwrite") .objectStore("items") .delete(2) .onsuccess = (event) => { console.log("削除完了しました"); }
  38. PAGE DAY 2017/11/01 # MOONGIFT X / 12 使うと便利なライブラリ •

    Lovefield by Google
 https://github.com/google/lovefield • JS Store
 http://jsstore.net
  39. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Credential Management

    API 認証情報をブラウザに保存、呼び出すAPI const form = document.querySelector('#reg-form') const cred = new PasswordCredential(form); const credential = await navigator.credentials.store(cred);
  40. PAGE DAY 2017/11/01 # MOONGIFT X / 12 認証情報取得 const

    credential = await navigator.credentials.get({ password: true }) console.log(credential) /*{ iconURL: "", id: "[email protected]", name: "atsushi", password: "demo", type: "password" }*/
  41. PAGE DAY 2017/11/01 # MOONGIFT X / 12 認証情報取得(外部サイト) const

    credential = await navigator.credentials.get({ password: true, federated: { providers: ['https://example.com'] } }) console.log(credential) /*{ iconURL: "https://lh5.goog.com/-y0...6-c/photo.jpg", id: "[email protected]", name: "中津川篤司", protocol: "", provider: "https://accounts.google.com", type: "federated" }*/
  42. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Web Authentication

    API ハードウェアや外部デバイスを用いた認証をサポート
  43. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Payment Request

    API 決済情報をWebブラウザ内に保存、取得するAPI const request = new PaymentRequest(supportedInstruments, details); const result = await request.show(); await fetch('/pay', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(result.toJSON()) });
  44. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Web Share

    API スマートフォンでネイティブアプリ風にコンテンツをシェア できるAPI navigator.share({ title: 'Web Fundamentals', text: 'Check out Web Fundamentals — it rocks!', url: 'https://developers.google.com/web', })
  45. PAGE DAY 2017/11/01 # MOONGIFT X / 12 自分でキャッシュを作る •

    navigator.onLine を使う • オンラインの時に取得して indexedDB に入れておく • POST/PUT/DELETEの時に便利
  46. PAGE DAY 2017/11/01 # MOONGIFT X / 12 Androidで確認する chrome://flags

    で Bypass user engagement checks を有効にする