$30 off During Our Annual Pro Sale. View Details »

Webでできる体験を考える会 / On the possibilities of PWA ex...

Webでできる体験を考える会 / On the possibilities of PWA experience

Webでできる体験を考える会 - PWA Night CONFERENCE 2020
https://conf2020.pwanight.jp/speaker/leonardo/

デモ動画への URL は以下のとおりです

・2つの音楽をクロスフェードする
 https://youtu.be/EYwOWXbbn7k

・音楽にエフェクトを加えて、AnalyzerNode で視覚化する
 https://youtu.be/XhD6Pc1TRus

・アンロックサウンド
 https://youtu.be/GPDnsCefVhE

・画面の転換
 https://youtu.be/HpnuqN8E78Y

・postprocessing でエフェクトがかかるタイトル
 https://youtu.be/DmYzUa2go3s

・Bluetooth と DJミキサーでのデモ
 https://youtu.be/v0q6NsNo9FY

・手元で画像・動画をリサイズ・エンコード
 https://youtu.be/oeXq8BjWK5Y
 https://youtu.be/b3qRqh5tKPI

・超ザコい botnet
 https://youtu.be/H-gqNwghlsw

LeonardoKen Orihara

January 31, 2020
Tweet

More Decks by LeonardoKen Orihara

Other Decks in Technology

Transcript

  1. 8FC"VEJP"1*͜ͱ͸͡Ί const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)(); const

    sourceNode: AudioBufferSourceNode = context.createBufferSource(); const gainNode: GainNode = context.createGain(); const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter(); const analyserNode: AnalyserNode = context.createAnalyser(); biquad Filter Node source Node gain Node analyser Node
  2. 8FC"VEJP"1*͜ͱ͸͡Ί const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)(); const

    sourceNode: AudioBufferSourceNode = context.createBufferSource(); const gainNode: GainNode = context.createGain(); const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter(); const analyserNode: AnalyserNode = context.createAnalyser(); biquad Filter Node source Node gain Node analyser Node "VEJP#VGGFS4PVSDF/PEF TUBSU XIFO PGGTFU EVSBUJPO  TUPQ XIFO  CVGGFSԻָσʔλ BT"VEJP#VGGFS MPPQUSVF ԻݯΛઃఆɾૢ࡞
  3. 8FC"VEJP"1*͜ͱ͸͡Ί const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)(); const

    sourceNode: AudioBufferSourceNode = context.createBufferSource(); const gainNode: GainNode = context.createGain(); const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter(); const analyserNode: AnalyserNode = context.createAnalyser(); biquad Filter Node source Node gain Node analyser Node (BJO/PEF HBJOWBMVF ʙ  ԻྔΛίϯτϩʔϧ
  4. 8FC"VEJP"1*͜ͱ͸͡Ί const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)(); const

    sourceNode: AudioBufferSourceNode = context.createBufferSource(); const gainNode: GainNode = context.createGain(); const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter(); const analyserNode: AnalyserNode = context.createAnalyser(); biquad Filter Node source Node gain Node analyser Node #JRVBE'JMUFS/PEF UZQF ༻ҙ͞ΕͨΤϑΣΫλʔΛֻ͚Δ GSFRVFODZWBMVF  ʙ  2WBMVF ʿ ʙ ʿ ʞMPXQBTTʟ ʞIJHIQBTTʟ ʞCBOEQBTTʟ ʞMPXTIFMGʟ ʞIJHITIFMGʟ FUD
  5. 8FC"VEJP"1*͜ͱ͸͡Ί const context: AudioContext = new (window.AudioContext || window.webkitAudioContext)(); const

    sourceNode: AudioBufferSourceNode = context.createBufferSource(); const gainNode: GainNode = context.createGain(); const biquadFilterNode: BiquadFilterNode = context.createBiquadFilter(); const analyserNode: AnalyserNode = context.createAnalyser(); biquad Filter Node source Node gain Node analyser Node "OBMZTFS/PEF ԻͷσʔλΛऔಘ͢Δ const analyzerParams = { times: new Uint8Array(analyserNode.frequencyBinCount), freqs: new Uint8Array(48), }; analyserNode.getByteTimeDomainData(analyzerParams.times); analyserNode.getByteFrequencyData(analyzerParams.freqs);
  6. ֤ /PEFͷ഑ઢ biquad Filter Node source Node gain Node analyser

    Node sourceNode.buffer = SOUND_BUFFER; sourceNode.connect(gainNode); gainNode.connect(biquadFilterNode); biquadFilterNode.connect(analyserNode); analyserNode.connect(context.destination); connect connect connect context.destination connect SOUND_BUFFER
  7. 8FC(-͜ͱ͸͡Ί import * as THREE from 'three'; const scene =

    new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true }); document.getElementById('screen').appendChild(renderer.domElement); scene camera γʔϯͱΧϝϥ͕Ͱ͖Δ ͜ΕΛ DBOWBTͱͯ͠ %0.΁௥Ճ
  8. ΦϒδΣΫτΛੜ੒͢Δ const geometry = new THREE.BoxGeometry(1, 1, 1); const material

    = new THREE.MeshBasicMaterial({ color: 0x000000 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); ཱํମ͕ઃஔ͞Εͨ scene cube
  9. ϏδϡΞϥΠβʔͷத਎ ٖࣅίʔυ const rectShape = new THREE.Shape(); const OFFSET =

    円の中心から付け根の距離; rectShape.moveTo( OFFSET * cos(角度) + (幅 / 2) * cos(角度 + π / 2), // X座標 OFFSET * sin(角度) + (幅 / 2) * sin(角度 + π / 2) // Y座標 ); rectShape.lineTo( OFFSET * cos(角度) + (幅 / 2) * cos(角度 - π / 2), OFFSET * sin(角度) + (幅 / 2) * sin(角度 - π / 2) ); rectShape.lineTo( 振幅 * cos(角度) + (幅 / 2) * cos(角度 + π / 2), 振幅 * sin(角度) + (幅 / 2) * sin(角度 + π / 2) ); rectShape.lineTo( 振幅 * cos(角度) + (幅 / 2) * cos(角度 - π / 2), 振幅 * sin(角度) + (幅 / 2) * sin(角度 - π / 2) ); ← OFFSET→
  10. ϏδϡΞϥΠβʔͷத਎ ٖࣅίʔυ const rectShape = new THREE.Shape(); const OFFSET =

    円の中心から付け根の距離; rectShape.moveTo( OFFSET * cos(角度) + (幅 / 2) * cos(角度 + π / 2), // X座標 OFFSET * sin(角度) + (幅 / 2) * sin(角度 + π / 2) // Y座標 ); rectShape.lineTo( OFFSET * cos(角度) + (幅 / 2) * cos(角度 - π / 2), OFFSET * sin(角度) + (幅 / 2) * sin(角度 - π / 2) ); rectShape.lineTo( 振幅 * cos(角度) + (幅 / 2) * cos(角度 + π / 2), 振幅 * sin(角度) + (幅 / 2) * sin(角度 + π / 2) ); rectShape.lineTo( 振幅 * cos(角度) + (幅 / 2) * cos(角度 - π / 2), 振幅 * sin(角度) + (幅 / 2) * sin(角度 - π / 2) ); ← OFFSET→ ͜͜ͷৼ෯̍ຊ
  11. ը໘ͷస׵ const material = new THREE.ShaderMaterial({ ... }); const curtain

    = new THREE.Mesh(geometry, material); ShaderMaterial Λ࢖͏ͱɺ(-4-͕ॻ͚Δ
  12. ը໘ͷస׵ uniform float mixRatio; uniform sampler2D tDiffuse1; uniform sampler2D tDiffuse2;

    varying vec2 vUv; void main() { vec4 tex1 = texture2D(tDiffuse1, vUv); vec4 tex2 = texture2D(tDiffuse2, vUv); gl_FragColor = mix(tex2, tex1, mixRatio); } const material = new THREE.ShaderMaterial({ }); material.uniforms.mixRatio.value = transition; mixRatio ΛมԽͤ͞Δ͜ͱͰ ̎ͭͷγʔϯΛ੾Γସ͑Δ
  13. QPTUQSPDFTTJOH 5ISFFKT import { EffectComposer, RenderPass, EffectPass, GlitchEffect } from

    'postprocessing'; const composer = new EffectComposer(renderer); composer.addPass(new RenderPass(scene, camera)); const glitchEffect = new GlitchEffect(); const glitchPass = new EffectPass(camera, glitchEffect); glitchPass.renderToScreen = false; composer.addPass(glitchPass); ͜ΕͰɺ͹Γ͹Γ͢Δԋग़͕ SFOEFSFSશମʹ͔͔Δ
  14. ΞϯνΤΠϦΞεॲཧ import { EffectPass, SMAAEffect } from 'postprocessing'; const smaaEffect

    = new SMAAEffect(); smaaEffect.setEdgeDetectionThreshold(0.08); const smaaPass = new EffectPass(camera, smaaEffect); smaaPass.renderToScreen = false; composer.addPass(smaaPass); SMAAEffect Λ࢖͏͜ͱͰ SFOEFSFSશମʹΞϯνΤΠϦΞε͕͔͔Δ
  15. 8FC#MVFUPPUI const device = await navigator.bluetooth.requestDevice({ filters: [{ services: [UUID.SERVICE]

    }], }); const server = await device.gatt.connect(); const service = await server.getPrimaryService(UUID.SERVICE); const characteristic = await service.getCharacteristic(UUID.CHARACTERISTIC); await characteristic.startNotifications(); const UUID = { SERVICE: '03b80e5a-ede8-4b33-a751-6ce34ec4c700‘, CHARACTERISTIC: '7772e5db-3868-4112-a1a9-f2669d106bf3', }; .*%*σόΠεͷ 66*%
  16. .*%*#MVFUPPUI navigator.requestMIDIAccess({ sysex: true }) .then(onMIDISuccess, onMIDIFailure); ͦͷͨΊ .*%*ͷΞΫηε΋΋Β͓ͬͯ͘ ը૾Ҿ༻ɿ"NB[PODPN

    #MVFUPPUIΛ௨ͯ͠ .*%*ͷ௨৴͕Ͱ͖ΔΑ͏ʹͳΔϞϊ .*%*ϙʔτ΍ 64#ϙʔτ͕ͳ͍ σόΠεͰ΋ .*%*௨৴͕Ͱ͖Δ
  17. ΤϑΣΫλʔΛૢ࡞͢Δ characteristic.addEventListener( 'characteristicvaluechanged‘, (event) => { const data = event.target.value;

    てきとうに処理... } ) biquad Filter Node ૢ࡞ .*%*͸ $-0$,γάφϧ͕͋ΔͨΊ characteristicvaluechanged ͸ΊͬͪΌൃՐ͢Δ ஫ҙ
  18. FNTDSJQUFO % emcc -O3 file.c -o file.wasm ୯ҰϑΝΠϧʹରͯ͠ % ./emconfigure

    ./configure % ./emmake make % emcc -O3 project.bc -o project.wasm ϓϩδΣΫτʹରͯ͠ -O3 ͸ϦϦʔεϏϧυʹదͨ͠࠷దԽ େจࣈͷΦʔͱ θϩ͡Όͳ͍Α
  19. $ˠ XBTN ͷಓͷΓ #include <emscripten.h> EMSCRIPTEN_KEEPALIVE int add(int x, int

    y) { return x + y; } EMSCRIPTEN_KEEPALIVE ࢀর͕ͳ͍ؔ਺͕࠷దԽͰ ফ͞Εͯ͠·͏ Λ͚ͭͳ͍ͱɺ % emcc -O3 add.c -o add.wasm add.c ͬͦ͘͞Ͱ͖ͨ
  20. +BWB4DSJQUͰಈ͔͢ const { instance } = await WebAssembly.instantiateStreaming(fetch('add.wasm'), {}); const

    result = instance.exports.add(1, 1); console.log(result); // -> 2 WebAssembly ʹ͸ଞͷಡΈࠐΈํ΋༻ҙ͞Ε͍ͯΔ instantiateStreaming ͕Ұ൪ޮ཰త ͚Ͳ
  21. ݟ͔ͭΒͳ͍ NPEVMF await WebAssembly.instantiateStreaming(fetch('add.wasm'), imports); ͖ͬ͞͝·͔͍ͯͨ͠ୈҾ਺Λ͔ͭͬͯɺ ͦΕͧΕͷ NPEVMFΛఆ͍ٛͯ͘͠ඞཁ͕͋Δ const imports

    = { env: { __memory_base: 0, memory: new WebAssembly.Memory({ initial: 512 }), wasi_snapshot_preview1: (args) => { console.log(args); } } }; ͜Μͳ͔Μ͡
  22. "TTFNCMZ4DSJQU export function add(a: i32, b: i32): i32 { return

    a + b; } 5ZQF4DSJQUͷײ֮Ͱॻ͚Δ % npm install --save-dev assemblyscript % npx asc add.ts -b add.wasm -O3 ɾͪΐͬͱ +4ͷॲཧૣ͍ͨ͘͠ͱ͜Ζ͕͋Δ ɾܭࢉΛଟ͘ͷ৔ॴͰ࢖͍ճ͢ $$ ͱ͔͸ա৒͚ͩͲ ͜͏͍͏ͱ͖ʹศར
  23. 8FC8PSLFSΛ૊ΈࠐΉ const worker = new Worker('worker.js'); ͜ͷ࣌఺͔Β ͸ಈ͖࢝ΊΔ worker.js 8PSLFSࣗମ͸ී௨ͷ

    +BWB4DSJQU ͨͩɺσʔλͷड͚౉͠͸ QPTU.FTTBHF Ͱߦ͏ worker.postMessage('Hello from client');
  24. σʔλͷड͚౉͠ const worker = new Worker('worker.js'); worker.postMessage('Hello from client'); worker.addEventListener('message',

    (message) => { console.log(message.data); // -> 'Hello from worker' }); DMJFOUKT self.postMessage('Hello from worker'); self.addEventListener('message', (message) => { console.log(message.data); // -> 'Hello from client' }); XPSLFSKT self ͸ͳͯ͘΋ಈ͘ const worker: Worker = self; Ͱ΋ඍົʹܕ͕ҧ͏͔Β 5ZQF4DSJQUͳΒɺ ͱͯ͠΋Α͍
  25. 5SBOTGFSBCMF0CKFDU const video = new Uint8Array(500 * 1024 * 1024);

    worker.postMessage({ type: 'SEND_VIDEO', video }, [video.buffer]); QPTU.FTTBHF ͷୈҾ਺ʹ "SSBZͰ "SSBZ#VGGFS ͔ .FTTBHF1PSU ͔ *NBHF#JUNBQ Λ౉͢ 8PSLFS DMJFOU video
  26. 5SBOTGFSBCMF0CKFDU const video = new Uint8Array(500 * 1024 * 1024);

    worker.postMessage({ type: 'SEND_VIDEO', video }, [video.buffer]); QPTU.FTTBHF ͷୈҾ਺ʹ "SSBZͰ "SSBZ#VGGFS ͔ .FTTBHF1PSU ͔ *NBHF#JUNBQ Λ౉͢ 8PSLFS DMJFOU video video ͸ DMJFOU͔Βॴ༗ݖ͕ 8PSLFSʹҠΔ QPTU.FTTBHF ޙ DMJFOUͰࢀরͰ͖ͳ͍
  27. ଎͞ͷҧ͍ DMJFOUଆ worker.postMessage({ type: 'START_TIMER' }); const video = new

    Uint8Array(500 * 1024 * 1024); worker.postMessage({ type: 'SEND_VIDEO', video }); DMJFOUKTʢͦͷ··ૹΔʣ worker.postMessage({ type: 'START_TIMER' }); const video = new Uint8Array(500 * 1024 * 1024); worker.postMessage({ type: 'SEND_VIDEO', video }, [video.buffer]); DMJFOUKTʢ5SBOTGFSBCMF0CKFDUʣ
  28. ଎͞ͷҧ͍ XPSLFSଆ let startTime = null; self.addEventListener('message', (message) => {

    const { type, video } = message.data; switch (type) { case 'START_TIMER': startTime = performance.now(); break; case 'SEND_VIDEO': console.log(performance.now() - startTime); break; } }); XPSLFSKT
  29. <script type="module"> import * as Magick from 'https://knicknic.github.io/wasm-imagemagick/magickApi.js'; const call

    = async function() { const fetchedSourceImage = await fetch('sample.png‘); const arrayBuffer = await fetchedSourceImage.arrayBuffer(); const sourceBytes = new Uint8Array(arrayBuffer); const files = [{ name: 'srcFile.png', content: sourceBytes }]; const command = ['convert', 'srcFile.png', '-rotate', '90', '-resize', '200%', 'out.png']; const processedFiles = await Magick.Call(files, command); const firstOutputImage = processedFiles[0]; document.getElementById('rotatedImage').src = URL.createObjectURL(firstOutputImage['blob']); console.log('created image ' + firstOutputImage['name']); }; call(); </script> XBTNJNBHFNBHJDL *NBHF.BHJDL Λ XBTN Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
  30. <script type="module"> import * as Magick from 'https://knicknic.github.io/wasm-imagemagick/magickApi.js‘; const call

    = async function() { const fetchedSourceImage = await fetch('sample.png‘); const arrayBuffer = await fetchedSourceImage.arrayBuffer(); const sourceBytes = new Uint8Array(arrayBuffer); const files = [{ name: 'srcFile.png', content: sourceBytes }]; const command = ['convert', 'srcFile.png', '-rotate', '90', '-resize', '200%', 'out.png']; const processedFiles = await Magick.Call(files, command); const firstOutputImage = processedFiles[0]; document.getElementById('rotatedImage').src = URL.createObjectURL(firstOutputImage['blob']); console.log('created image ' + firstOutputImage['name']); }; call(); </script> XBTNJNBHFNBHJDL *NBHF.BHJDL Λ XBTN Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ ͜Εܥɺσʔλ͸͍͍ͩͨ "SSBZ#VGGFS Ͱ΍ΓͱΓ͢Δ
  31. <script type="module"> import * as Magick from 'https://knicknic.github.io/wasm-imagemagick/magickApi.js‘; const call

    = async function() { const fetchedSourceImage = await fetch('sample.png‘); const arrayBuffer = await fetchedSourceImage.arrayBuffer(); const sourceBytes = new Uint8Array(arrayBuffer); const files = [{ name: 'srcFile.png', content: sourceBytes }]; const command = ['convert', 'srcFile.png', '-rotate', '90', '-resize', '200%', 'out.png']; const processedFiles = await Magick.Call(files, command); const firstOutputImage = processedFiles[0]; document.getElementById('rotatedImage').src = URL.createObjectURL(firstOutputImage['blob']); console.log('created image ' + firstOutputImage['name']); }; call(); </script> XBTNJNBHFNBHJDL *NBHF.BHJDL Λ XBTN Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ *NBHF.BHJDL Ͱ࢖͑ΔίϚϯυ͸͍͍ͩͨಈ͘
  32. const ffmpeg = new Worker('/workers/ffmpeg-worker-webm.js'); const call = async ()

    => { const fetchedSourceMov = await fetch('sample.mov'); const arrayBuffer = await fetchedSourceMov.arrayBuffer(); const item = new Uint8Array(arrayBuffer); await new Promise((resolve, reject) => { ffmpeg.postMessage({ type: 'run', MEMFS: [{ name: 'input', data: item }], arguments: ['-ss', '2', '-i', 'input', '-f', 'image2', '-vframes', '1', 'out.jpg'] }); }); }; call(); GGNQFHKT GGNQFH Λ XBTN ͱ XPSLFSͰಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
  33. const ffmpeg = new Worker('/workers/ffmpeg-worker-webm.js'); const call = async ()

    => { const fetchedSourceMov = await fetch('sample.mov'); const arrayBuffer = await fetchedSourceMov.arrayBuffer(); const item = new Uint8Array(arrayBuffer); await new Promise((resolve, reject) => { ffmpeg.postMessage({ type: 'run', MEMFS: [{ name: 'input', data: item }], arguments: ['-ss', '2', '-i', 'input', '-f', 'image2', '-vframes', '1', 'out.jpg'] }); }); }; call(); GGNQFHKT GGNQFH Λ XBTN ͱ XPSLFSͰಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ ઌʹशͬͯ "SSBZ#VGGFS ʹͯ͠Δ͚ͩ
  34. const ffmpeg = new Worker('/workers/ffmpeg-worker-webm.js'); const call = async ()

    => { const fetchedSourceMov = await fetch('sample.mov'); const arrayBuffer = await fetchedSourceMov.arrayBuffer(); const item = new Uint8Array(arrayBuffer); await new Promise((resolve, reject) => { ffmpeg.postMessage({ type: 'run', MEMFS: [{ name: 'input', data: item }], arguments: ['-ss', '2', '-i', 'input', '-f', 'image2', '-vframes', '1', 'out.jpg'] }); }); }; call(); GGNQFHKT GGNQFH Λ XBTN ͱ XPSLFSͰಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ .&.'4 8FC"TTFNCMZͰ࣋ͬͯΔϑΝΠϧγεςϜͷ̍ͭ ϝϞϦۭؒΛ࢖ͬͨϑΝΠϧγεςϜ
  35. ffmpeg.onmessage = (event) => { switch (event.data.type) { case 'done':

    if (msg.data.MEMFS[0]) { const textDecoder = new TextDecoder(); const thumbUnit8 = msg.data.MEMFS[0].data; const img = textDecoder.decode(thumbUnit8); document.getElementById('thumb').src = img; } break; } }; GGNQFHKTʢσʔλͷडऔʣ
  36. ffmpeg.onmessage = (event) => { switch (event.data.type) { case 'done':

    if (msg.data.MEMFS[0]) { const textDecoder = new TextDecoder(); const thumbUnit8 = msg.data.MEMFS[0].data; const img = textDecoder.decode(thumbUnit8); document.getElementById('thumb').src = img; } break; } }; GGNQFHKTʢσʔλͷडऔʣ #ZUF4USFBNΛ 4USJOHʹ͢ΔͨΊ
  37. 8FC$SZQUP"1* ·ͣɺݤΛͭ͘Δ const cryptoKey: CryptoKey = await crypto.subtle.generateKey( { name:

    'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); const jwk = await crypto.subtle.exportKey('jwk', cryptoKey); ࡞ΓऴΘͬͨΒ FYQPSUͯ͠ *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  38. 8FC$SZQUP"1* ·ͣɺݤΛͭ͘Δ const cryptoKey: CryptoKey = await crypto.subtle.generateKey( { name:

    'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ҉߸Խར༻Ϟʔυ const jwk = await crypto.subtle.exportKey('jwk', cryptoKey); ࡞ΓऴΘͬͨΒ FYQPSUͯ͠ *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  39. 8FC$SZQUP"1* ·ͣɺݤΛͭ͘Δ const cryptoKey: CryptoKey = await crypto.subtle.generateKey( { name:

    'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ݤ͕ΤΫεϙʔτՄೳ͔Ͳ͏͔ ҉߸Խར༻Ϟʔυ const jwk = await crypto.subtle.exportKey('jwk', cryptoKey); ࡞ΓऴΘͬͨΒ FYQPSUͯ͠ *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  40. 8FC$SZQUP"1* ·ͣɺݤΛͭ͘Δ const cryptoKey: CryptoKey = await crypto.subtle.generateKey( { name:

    'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ҉߸Խͱ෮߸Խ͕ڐՄ͞Εͨݤ ݤ͕ΤΫεϙʔτՄೳ͔Ͳ͏͔ ҉߸Խར༻Ϟʔυ const jwk = await crypto.subtle.exportKey('jwk', cryptoKey); ࡞ΓऴΘͬͨΒ FYQPSUͯ͠ *OEFYFE%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  41. 'JMFΛ҉߸Խ͢Δ·Ͱ const textEncoder = new TextEncoder(); const reader = new

    FileReaderSync(); const blob = new Blob([file as File], { type: file.type }); const stringItem: string = reader.readAsDataURL(blob); const encodedBlob: Uint8Array = textEncoder.encode(stringItem); const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12)); const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, cryptoKey, encodedBlob ); const cryptedItem = new Uint8Array(cryptedBuffer);
  42. 'JMFΛ҉߸Խ͢Δ·Ͱ const textEncoder = new TextEncoder(); const reader = new

    FileReaderSync(); const blob = new Blob([file as File], { type: file.type }); const stringItem: string = reader.readAsDataURL(blob); const encodedBlob: Uint8Array = textEncoder.encode(stringItem); const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12)); const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, cryptoKey, encodedBlob ); const cryptedItem = new Uint8Array(cryptedBuffer); ΍΍͍͚͜͠Ͳ 8FC$SZQUP"1*ʹ͔͚ΕΔܗʹ͍ͯ͠Δ͚ͩ
  43. 'JMFΛ҉߸Խ͢Δ·Ͱ const textEncoder = new TextEncoder(); const reader = new

    FileReaderSync(); const blob = new Blob([file as File], { type: file.type }); const stringItem: string = reader.readAsDataURL(blob); const encodedBlob: Uint8Array = textEncoder.encode(stringItem); const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12)); const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, cryptoKey, encodedBlob ); const cryptedItem = new Uint8Array(cryptedBuffer); ͕͜͜҉߸ԽΛͯ͠Δͱ͜Ζ
  44. 'JMFΛ҉߸Խ͢Δ·Ͱ const textEncoder = new TextEncoder(); const reader = new

    FileReaderSync(); const blob = new Blob([file as File], { type: file.type }); const stringItem: string = reader.readAsDataURL(blob); const encodedBlob: Uint8Array = textEncoder.encode(stringItem); const iv: Uint8Array = crypto.getRandomValues(new Uint8Array(12)); const cryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, cryptoKey, encodedBlob ); const cryptedItem = new Uint8Array(cryptedBuffer); ॳظԽʢΠχγϟϧʣϕΫτϧ ಉ͡ΞΠςϜΛಉ͡ݤͰ҉߸Խͯ͠΋ ݁Ռ͕มΘΔΑ͏ʹ͢ΔͨΊʹඞཁ
  45. ͪͳΈʹ෮߸Խ const textDecoder = new TextDecoder(); const rawBuffer : ArrayBuffer

    = await crypto.subtle.decrypt( { name: 'AES-GCM', iv }, cryptoKey, cryptedItem ); const rawItem = new Uint8Array(rawBuffer); const thumb: string = textDecoder.decode(rawItem); crypto.subtle.decrypt Λ࢖͔ͬͯΒ҉߸Խͷ൓ରͷॱংͰ TUSJOH·Ͱ͍࣋ͬͯ͘
  46. 'JSFCBTF$MPVE.FTTBHJOH { name: 'app-name‘, short_name: 'app-shrt‘, ..., gcm_sender_id: 'xxxxxxxxxxxx' };

    'JSFCBTFͰϓϩδΣΫτΛ࡞੒ gcm_sender_idΛݟ͚ͭͯ NBOJGFTUKTPO ʹ௥Ճ NBOJGFTUKTPO <link rel=manifest href=/manifest.json> )5.-ʹ௥Ճ
  47. 'JSFCBTF͔Βͷ௨஌Λड͚Δ ͜ΕͰ 8FCϖʔδ͕ όοΫάϥ΢ϯυʗλεΫΩϧঢ়ଶͰ΋௨஌͕ड͚औΕΔ import * as firebase from 'firebase/app';

    import 'firebase/messaging'; const messaging = firebase.messaging(); messaging.setBackgroundMessageHandler((payload) => { console.log(payload); }); TFSWJDFXPSLFSKT
  48. 8FCϖʔδʹ͍Δͱ͖ͷ௨஌ import * as firebase from 'firebase/app'; import 'firebase/messaging'; const

    messaging = firebase.messaging(); messaging.onMessage((payload) => { console.log(payload); }); Ͱ΋ड͚औΕΔ͚Ͳ addEventListener('push', () => {}); 'JSFCBTFͰߦ͘ͳΒ PO.FTTBHF Ͱ 0, DMJFOUKTʢ8PSLFS͡Όͳ͍ํͰʣ
  49. import * as firebase from 'firebase/app'; import 'firebase/messaging'; const messaging

    = firebase.messaging(); const registration = await navigator.serviceWorker.register('/service-worker.js'); try { await messaging.requestPermission(); messaging.usePublicVapidKey(FIREBASE_PUBLIC_VAPID_KEY); messaging.useServiceWorker(registration); const token = await messaging.getToken(); console.log(token); } catch (e) { console.log('rejected!'); } ௨஌ͷڐՄͱ UPLFOͷऔಘ
  50. import * as firebase from 'firebase/app'; import 'firebase/messaging'; const messaging

    = firebase.messaging(); const registration = await navigator.serviceWorker.register('/service-worker.js'); try { await messaging.requestPermission(); messaging.usePublicVapidKey(FIREBASE_PUBLIC_VAPID_KEY); messaging.useServiceWorker(registration); const token = await messaging.getToken(); console.log(token); } catch (e) { console.log('rejected!'); } ௨஌ͷڐՄͱ UPLFOͷऔಘ 'JSFCBTFͷίϯιʔϧ͔Β࣋ͬͯ͜ΕΔ ௨஌ͷڐՄΛऔΓʹߦ͘ ͜͜Ͱ UPLFOΛखʹೖΕΔ
  51. ಛఆͷϢʔβʔʹ௨஌͢Δ https://fcm.googleapis.com/v1/projects/{project-id}/messages:send ͜͜ʹ 1045͢Δ headers: { 'content-type': 'application/json', authorization: `Bearer

    ${VALID_OAUTH_2_0_TOKEN}` }, body: JSON.stringify({ message: { token, notification: { title: 'これはタイトル', body: 'こっちは本文' } } })
  52. ಛఆͷϢʔβʔʹ௨஌͢Δ https://fcm.googleapis.com/v1/projects/{project-id}/messages:send ͜͜ʹ 1045͢Δ headers: { 'content-type': 'application/json', authorization: `Bearer

    ${VALID_OAUTH_2_0_TOKEN}` }, body: JSON.stringify({ message: { token, notification: { title: 'これはタイトル', body: 'こっちは本文' } } }) (PPHMF"1*ΫϥΠΞϯτϥΠϒϥϦͰ +85Λऔಘ
  53. ಛఆͷϢʔβʔʹ௨஌͢Δ https://fcm.googleapis.com/v1/projects/{project-id}/messages:send ͜͜ʹ 1045͢Δ headers: { 'content-type': 'application/json', authorization: `Bearer

    ${VALID_OAUTH_2_0_TOKEN}` }, body: JSON.stringify({ message: { token, notification: { title: 'これはタイトル', body: 'こっちは本文' } } }) (PPHMF"1*ΫϥΠΞϯτϥΠϒϥϦͰ +85Λऔಘ ͖ͬ͞ͷ UPLFO
  54. message: { token, data: { ... } } message: {

    token, notification: { title: 'これはタイトル‘, body: 'こっちは本文‘ } } ௨஌ϝοηʔδ σʔλϝοηʔδ ௨஌Λग़͞ͳͯ͘΋ྑ͍ 4FSWJDF8PSLFS ͕ಈ࡞͢Δ
  55. 4FSWJDF8PSLFS ͰͰ͖Δ͜ͱ WorkerNavigator Navigator battery connection geolocation lock permissions serviceWorker

    storage xr vibrate clipboard usb bluetooth credentials :Battery Status API :Network Information API :Geolocation API :Web Lock API :Permission API :ServiceWorkerContainer :Storage API :Web XR Device API :Vibration API :Clipboard API :Web USB :Web Bluetooth :Credentials Container connection lock permissions storage :Network Information API :Web Lock API :Permission API :Storage API 'FUDI"1*͕ಈ͘ ͋ͱ
  56. 4FSWJDF8PSLFS ͰͰ͖Δ͜ͱ WorkerNavigator Navigator battery connection geolocation lock permissions serviceWorker

    storage xr vibrate clipboard usb bluetooth credentials :Battery Status API :Network Information API :Geolocation API :Web Lock API :Permission API :ServiceWorkerContainer :Storage API :Web XR Device API :Vibration API :Clipboard API :Web USB :Web Bluetooth :Credentials Container connection lock permissions storage :Network Information API :Web Lock API :Permission API :Storage API 'FUDI"1*͕ಈ͘ ͋ͱ ʮ͋ͬʯ
  57. 0 5 10 15 20 25    

                          TMPXH H H H ຊ౰ͩΖ͔ ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ ࣌ؒ ਓ਺
  58. 0 5 10 15 20 25    

                          TMPXH H H H ຊ౰ͩΖ͔ ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ ࣌ؒ ਓ਺ ͍͍ͩͨɺ͍ͭ΋଎͍
  59. 0 5 10 15 20 25    

                          TMPXH H H H ຊ౰ͩΖ͔ ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ ࣌ؒ ਓ਺ ͍ͯ͠ݴ͑͹ ͜ͷ΁ΜࠞΜͰΔ
  60. 0 5 10 15 20 25    

                          TMPXH H H H ຊ౰ͩΖ͔ ௐࠪ݁Ռ Ұ೔தͱ͓ͯ͠ͷ୺຤ͷ଎౓ͬͯͲͷ͘Β͍ʁ ࣌ؒ ਓ਺ ਂ໷͔Βૣே͸ ͪΐͬͱۭ͍ͯΔʁ
  61. 3PVOE5SJQ5JNF         

                             3PVOE5SJQ5JNF SUU <NT> ࣌ࠁ
  62. 3PVOE5SJQ5JNF         

                             3PVOE5SJQ5JNF 0 5 10 15 20 25                           TMPXH H H H ҰͭલͷάϥϑͷԼͷํ SUU <NT> ࣌ࠁ
  63. https://fcm.googleapis.com/v1/projects/{project-id}/messages:send headers: { 'content-type': 'application/json', authorization: `Bearer ${VALID_OAUTH_2_0_TOKEN}` }, body:

    JSON.stringify({ message: { token, data: { ... } } }) 1VTI"1*ͷ࿩ʹཱͪฦΔͱ ୭ʹૹΔ͔ αʔόʔͷೝূΩʔ ͜ͷ͕̎ͭ͋Ε͹ 8FC1VTI͸ Ͳ͔͜ΒͰ΋୭͔ΒͰ΋ૹΕΔ
  64.          

                Ԡ౴࣌ؒ<ඵ> ࣌ࠁ ฏۉԠ౴࣌ؒ ͦΕͬͯݱ࣮తʁ ௐࠪ݁Ռ 1VTI"1*ͬͯΈΜͳͲΕ͘Β͍Ͱฦͯ͘͠ΕΔͷʁ ஗͍