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

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

39b48efe3422d1c8a48f28aad53e209a?s=128

LeonardoKen Orihara

January 31, 2020
Tweet

Transcript

  1. 8FCͰͰ͖ΔମݧΛߟ͑Δձ 折原 レオナルド賢

  2. ڈ೥ͷ͍·͝Ζ

  3. None
  4. None
  5. None
  6. ࿩͢͜ͱ ͍ΖΜͳ 8FC"1*Λ৮ͬͯΈΔ ࢖ͬͨ͜ͱͳ͍ਓͰ΋Θ͔Γ΍͘͢ Ͱ΋ʮߟ͑ΔձʯͳͷͰൃలܕ΋ఴ͑ͯ

  7. ࿩͢τϐοΫ  ໐Δ΋ͷɾಈ͘΋ͷͷ"1*  8FC"TTFNCMZͱ 8FC8PSLFS  1VTI"1*

  8. 8FC"VEJP"1*8FC(- 8FC#MVFUPPUI8FC.*%*"1*  ໐Δ΋ͷɾಈ͘΋ͷͷ"1*

  9. ໐Δ΋ͷ

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

    Λ࡞੒͢Δͱ͜Ζ͔Β͸͡·Δ
  11. 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
  12. 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 ԻݯΛઃఆɾૢ࡞
  13. 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 ʙ  ԻྔΛίϯτϩʔϧ
  14. 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
  15. 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);
  16. getByteTimeDomainData ͰऔಘͰ͖Δσʔλ getByteFrequencyData ͰऔಘͰ͖Δσʔλ

  17. ֤ /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
  18. 4PVOE8BMLFSͷ഑ઢ

  19. 4PVOE8BMLFSͷ഑ઢ IUUQTZPVUVCF&:X089CCOL

  20. 4PVOE8BMLFSͷ഑ઢ IUUQTZPVUVCF9I%1D53VT

  21. 4PVOE8BMLFSͷ഑ઢ IUUQTZPVUVCF(1%OT$FG7I&

  22. ಈ͘΋ͷ

  23. 8FC(-͜ͱ͸͡Ί ͞ΘͬͯΈΔͳΒϥΠϒϥϦ͕γϯϓϧ

  24. 8FC(-͜ͱ͸͡Ί IUUQTEFNPTMJUUMFXPSLTIPQGSUSBDL IUUQTJNNFSTJWFHDPNXJTIFT

  25. 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.΁௥Ճ
  26. ΦϒδΣΫτΛੜ੒͢Δ 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
  27. ϏδϡΞϥΠβʔͷத਎ ٖࣅίʔυ 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→
  28. ϏδϡΞϥΠβʔͷத਎ ٖࣅίʔυ 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→ ͜͜ͷৼ෯̍ຊ
  29. ը໘ͷస׵ IUUQTZPVUVCF)QOVR/&:

  30. ը໘ͷస׵ 5ISFFKT DBOWBT 3FBDUKT $PNQPOFOUT 5ISFFKT DBOWBT ͕͜͜ τϥϯδγϣϯ ͍ͯ͠Δ

  31. ը໘ͷస׵ const material = new THREE.ShaderMaterial({ ... }); const curtain

    = new THREE.Mesh(geometry, material); ShaderMaterial Λ࢖͏ͱɺ(-4-͕ॻ͚Δ
  32. ը໘ͷస׵ 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 ΛมԽͤ͞Δ͜ͱͰ ̎ͭͷγʔϯΛ੾Γସ͑Δ
  33. QPTUQSPDFTTJOH TDFOFʹඳը͞ΕͯΔϞϊͨͪશͯʹӨڹΛ༩͑ΔΤϑΣΫτ IUUQTZPVUVCF%N:[6BHPT

  34. 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શମʹ͔͔Δ
  35. ΞϯνΤΠϦΞεॲཧ 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શମʹΞϯνΤΠϦΞε͕͔͔Δ
  36. දݱʗମݧΛߟ͑Δ

  37. ԕ͘ͳ͍ະདྷͰ͸ 8FCͷήʔϜ 18" ͰΞϓϦฒΈʹ༡΂Δ͸ͣ

  38. ΞϓϦͱൺ΂ͯͷϝϦοτ ɾ63-Λ౿Ί͹༡΂Δ ɾ8FC͍ͬͯ͏Πϯελϯτੑ ɾγΣΞͷ͠΍͢͞ ͜ΕΛ׆͔ͯ͠ɺ༡ͼํ΋ 1SPHSFTTJWFʹͪ͠Ό͑͹

  39. ήʔϜηϯλʔͬͯͲ͏͍͏࣌ʹߦ͘ʁ Ո͡ΌͰ͖ͳ͍༡ͼํΛ͍ͨ࣌͠ ͨͱ͑͹ ݸਓͷ੠

  40. ࣗ෼Ͱଗ͑ΒΕͳ͍ػࡐ͕͋Δͱ͜ΖͰ༡Ϳ

  41. ྟ৔ײͨͬ΀ΓͳεϐʔΧʔ ͕͋ͬͨΓ େഭྗͷϏδϡΞϥΠβʔ͕͋ͬͨΓ ը૾Ҿ༻ɿ %+."9 NPTFT

  42. ໘ന͔ͬͨήʔϜΛ 18"Ͱ࣋ͪؼΔ

  43. %&.0 %+ϛΩαʔͷ .*%*௨৴Λ #MVFUPPUIʹͷͤͯ 8FCͰड͚औͬͯΤϑΣΫλʔΛ͔͚ͯΈΑ͏ IUUQTZPVUVCFWR/T/P':

  44. 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*%
  45. .*%*#MVFUPPUI navigator.requestMIDIAccess({ sysex: true }) .then(onMIDISuccess, onMIDIFailure); ͦͷͨΊ .*%*ͷΞΫηε΋΋Β͓ͬͯ͘ ը૾Ҿ༻ɿ"NB[PODPN

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

    てきとうに処理... } ) biquad Filter Node ૢ࡞ .*%*͸ $-0$,γάφϧ͕͋ΔͨΊ characteristicvaluechanged ͸ΊͬͪΌൃՐ͢Δ ஫ҙ
  47. 8FC"TTFNCMZ8FC8PSLFS 8FC$SZQUP"1*  8FC"TTFNCMZͱ 8FC8PSLFS

  48. 8FC"TTFNCMZ

  49. 8FC"TTFNCMZ ϒϥ΢β༻ͷόΠφϦίʔυΛ༻ҙͯ͠ ͦΕΛ +BWB4DSJQUଆ͔Βಈ͔ٕ͢ज़ XBTN

  50. XBTN ͷ࡞Γํ $$ --7. 3VTU wasm-pack (P ,PUMJO/BUJWF ͜ΕΒͷࢿ࢈Λ 8FCʹ࣋ͪࠐΉ͜ͱ͕Ͱ͖Δ

  51. FNTDSJQUFO % emcc -O3 file.c -o file.wasm ୯ҰϑΝΠϧʹରͯ͠ % ./emconfigure

    ./configure % ./emmake make % emcc -O3 project.bc -o project.wasm ϓϩδΣΫτʹରͯ͠ -O3 ͸ϦϦʔεϏϧυʹదͨ͠࠷దԽ େจࣈͷΦʔͱ θϩ͡Όͳ͍Α
  52. $ˠ 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 ͬͦ͘͞Ͱ͖ͨ
  53. +BWB4DSJQUͰಈ͔͢ const { instance } = await WebAssembly.instantiateStreaming(fetch('add.wasm'), {}); const

    result = instance.exports.add(1, 1); console.log(result); // -> 2 WebAssembly ʹ͸ଞͷಡΈࠐΈํ΋༻ҙ͞Ε͍ͯΔ instantiateStreaming ͕Ұ൪ޮ཰త ͚Ͳ
  54. ݟ͔ͭΒͳ͍ NPEVMF ͍ΖΜͳ NPEVMF͕ͳ͍ΑͱݴΘΕΔ ελϯυΞϩϯͰಈ͔ͳ͍ܗࣜͰ XBTN ʹίϯύΠϧ͖ͯͯ͠Δ Ͱ΋ɺͦͬͪͷ΄͏͕ܰྔͩ͠ແବ͕ͳ͍

  55. ݟ͔ͭΒͳ͍ 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); } } }; ͜Μͳ͔Μ͡
  56. XBTN ͔Β JNQPSUͨؔ͠਺ console.log('instance.exports.add →', instance.exports.add); OBUJWFDPEFʹͳ͍ͬͯΔ

  57. "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ͷॲཧૣ͍ͨ͘͠ͱ͜Ζ͕͋Δ ɾܭࢉΛଟ͘ͷ৔ॴͰ࢖͍ճ͢ $$ ͱ͔͸ա৒͚ͩͲ ͜͏͍͏ͱ͖ʹศར
  58. 8FC8PSLFS 8FC8PSLFS

  59. 8FC8PSLFS +BWB4DSJQUΛϝΠϯεϨου͔Β֎ͯ͠ɺ όοΫάϥ΢ϯυʹ͢Δٕज़ +BWB4DSJQU ϝΠϯεϨου 8PSLFS εϨου ॏ͍ॲཧ͸ ϝΠϯεϨουΛࢭΊͯ͠·͏ 8PSLFSʹ·͔ͤͯ

    ऴΘͬͨΒڭ͑ͯ΋Β͏ ݟͯͷ௨Γ ॏ͍ॲཧ
  60. 8FC8PSLFSΛ૊ΈࠐΉ const worker = new Worker('worker.js'); ͜ͷ࣌఺͔Β ͸ಈ͖࢝ΊΔ worker.js 8PSLFSࣗମ͸ී௨ͷ

    +BWB4DSJQU ͨͩɺσʔλͷड͚౉͠͸ QPTU.FTTBHF Ͱߦ͏ worker.postMessage('Hello from client');
  61. σʔλͷड͚౉͠ 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ͳΒɺ ͱͯ͠΋Α͍
  62. σʔλͷड͚౉͠ͷࠔΔͱ͜Ζ ී௨ʹ QPTU.FTTBHF ͰσʔλૹΔͱίϐʔ͞ΕΔ ॏ͍ॲཧ͕Ͱ͖ΔͬͯݴͬͯΔ͚Ͳ .# ͘Β͍ͷಈըϑΝΠϧͳΜͯૹͬͨΒɺ ίϐʔ͞Εͯ (# ΋ϝϞϦΛͻͬഭͯ͠͠·͏

    5SBOTGFSBCMF0CKFDUΛ࢖͏ʂ
  63. 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
  64. 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ͰࢀরͰ͖ͳ͍
  65. ଎͞ͷҧ͍ 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ʣ
  66. ଎͞ͷҧ͍ 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
  67. ଎͞ͷҧ͍ ݁Ռ DMJFOUKTʢͦͷ··ૹΔʣ DMJFOUKTʢ5SBOTGFSBCMF0CKFDUʣ 866.2800000165589 <NT> 0.9349999600090086 <NT> ര଎ ͍ͭͰʹলϝϞϦ

  68. දݱʗମݧΛߟ͑Δ

  69. ը૾ʗಈըΞϓϦ͕ΊͬͪΌߴػೳʹͳͬͯΔ ࡱͬͨΒ͙͢ʹ $MPVEʹ͕͋ͬͯ ୭͕ࣸͬͯΔ͔ɺͲ͜ʹ͍͔ͨ·ͱΊͯ͘ΕΔ ৔ॴผ ਓ෺ผ ͱͯ΋΂ΜΓʂ

  70. ҰํͰ ࣗ෼ͷࣸਅɾಈըΛʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓ͍ͩͬͯΔ ʮͰ΋ɺ$MPVEʹ͋͛ΕͨΒɺखݩͷ༰ྔݮΒͤΔ͠ͳʔʜʯ ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ ʮϓϥΠϕʔτΫϥ΢υΈ͍ͨͷ࢖͑͹ʁʯ ఢɿ ʮͦ͏͍͏ͷͬͯຊ౰ʹ๣ௌ͞Εͯͳ͍͔ͱ͔ɺ ෼ੳʹ࢖ΘΕͯͳ͍͔ͱ͔Θ͔Μͳ͘ͳ͍ʁʁʯ ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ

  71. ࣗ෼ͰʢϩʔΧϧͰʣ΍Δ͔͠ͳ͍͡ΌΜʂ ը૾͸αϜωΠϧ੾Γग़͠Λͯ͠ ಈը͸Τϯίʔυ αϜωΠϧ੾Γग़͠Λͯ͠ ҉߸Խͨ͠΋ͷ͚ͩΛ֎ʹग़͢Α͏ʹ͠Α͏ ΍Δ͔͠ͳ͍ ʢʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓஊ ʣ

  72. ΍Δͧ ҉߸Խ͞Εͯͳ͍σʔλΛͲ͔͜ʹૹΔͳΜͯͰ͖ͳ͍ʂ ը૾ͷαϜωΠϧԽ *NBHF.BHJDL ͕࢖͑ͦ͏ ಈըͷΤϯίʔυ ''.1&(͕͍͍ͳ

  73. <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 Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
  74. <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 Ͱ΍ΓͱΓ͢Δ
  75. <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 Ͱ࢖͑ΔίϚϯυ͸͍͍ͩͨಈ͘
  76. 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Ͱಈ࡞Ͱ͖ΔΑ͏ʹͯ͘͠ΕͯΔϞδϡʔϧ
  77. 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 ʹͯ͠Δ͚ͩ
  78. 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Ͱ࣋ͬͯΔϑΝΠϧγεςϜͷ̍ͭ ϝϞϦۭؒΛ࢖ͬͨϑΝΠϧγεςϜ
  79. 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ʢσʔλͷडऔʣ
  80. 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ʹ͢ΔͨΊ
  81. ͋ͱ͸҉߸Խ αʔόʔΛ࢖Θͣʹɺ +BWB4DSJQU͚ͩͰαϜωΠϧɺΤϯίʔυ͕Ͱ͖ͦ͏ʂ ҉߸Խ΋ࣗલͰ΍͔ͬͯΒαʔόʔʹσʔλΛૹΖ͏ XBTN ͨͪ

  82. 8FC$SZQUP"1*

  83. 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%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  84. 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%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  85. 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%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  86. 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%# ͱ͔ʹอଘ͓ͯ͘͠ͱྑ͍
  87. '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);
  88. '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*ʹ͔͚ΕΔܗʹ͍ͯ͠Δ͚ͩ
  89. '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); ͕͜͜҉߸ԽΛͯ͠Δͱ͜Ζ
  90. '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); ॳظԽʢΠχγϟϧʣϕΫτϧ ಉ͡ΞΠςϜΛಉ͡ݤͰ҉߸Խͯ͠΋ ݁Ռ͕มΘΔΑ͏ʹ͢ΔͨΊʹඞཁ
  91. ͪͳΈʹ෮߸Խ 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·Ͱ͍࣋ͬͯ͘
  92. %&.0 <input type="file"> Ͱ౉͞ΕͨࣸਅɾಈըΛ ੈքʹ͹Εͳ͍Α͏ʹखݩͰॲཧ͔ͯ͠Β ҉߸ԽΛͯ͠Ξοϓϩʔυͯ͠ΈΑ͏ IUUQTZPVUVCFPF9R#K8,: IUUQTZPVUVCFCR3RIU,1*

  93. 1VTI"1*͍··Ͱͷ "1*  1VTI"1*

  94. 1VTI"1* ΞϓϦͷΑ͏ʹ௨஌͕ग़ͤΔ࢓૊Έ ͍͔ͭ͘ͷ࣮૷ͷ࢓ํ͕͋Δ͚ΕͲ 'JSFCBTF$MPVE.FTTBHJOH͕؆୯ͰԠ༻·ͰͰ͖Δ ˞"OESPJEͷΈରԠ

  95. '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.-ʹ௥Ճ
  96. 4FSWJDF8PSLFSͷ௥Ճ 4FSWJDF8PSLFSɿϖʔδΛดͯ͡΋ಈ͍ͯ͘ΕΔ 8PSLFS navigator.serviceWorker.register(‘/service-worker.js') navigator ͔Β௥Ճ͢Δ service-worker.js ผͷϥΠϑαΠΫϧͰ ಈ͖ग़͢ XJOEPX

  97. 'JSFCBTF͔Βͷ௨஌Λड͚Δ ͜ΕͰ 8FCϖʔδ͕ όοΫάϥ΢ϯυʗλεΫΩϧঢ়ଶͰ΋௨஌͕ड͚औΕΔ import * as firebase from 'firebase/app';

    import 'firebase/messaging'; const messaging = firebase.messaging(); messaging.setBackgroundMessageHandler((payload) => { console.log(payload); }); TFSWJDFXPSLFSKT
  98. 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͡Όͳ͍ํͰʣ
  99. 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ͷऔಘ
  100. 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ΛखʹೖΕΔ
  101. ௨஌ڐՄͷμϝͳྫ ϖʔδ͸͍͙ͬͯ͢ʹ௨஌ڐՄΛऔΖ͏ͱ͢Δ 8FCαΠτ ͱͯ΋ةݥ ͲΜͳίϯςϯπ͕ͦ͜ʹ͋Δ͔Θ͔Βͳ͍Ϣʔβʔ͕ ڐՄ͢ΔϝϦοτ͕ͳ͍ ͳΜͷؾͳ͠ʹɺϒϩοΫΛ͞ΕΔͱυϝΠϯͰڋ൱͞ΕΔͷͰɺ ೋ౓ͱϢʔβʔʹ௨஌ΛૹΔνϟϯε͕ͳ͘ͳΔʹͻͱ͍͠

  102. ഑ྀͨ͠ྫ ࣗલͷμΠΞϩάͰαϯυϘοΫεతʹଅ͢ͷ͕ྑ͍ $MJDL Ϣʔβʔ͕௨஌Λ΄͍͠ͱײͦ͡͏ͳ৔໘Ͱ΍Δͱ͍͍ΑͶ ͓ؾʹೖΓͷϢʔβʔͷ৽ண͕ؾʹͳΓ·ͤΜ͔ʁ ΠϕϯτͷεέδϡʔϧΛ͓஌Βͤ͠·͢Α ͋ͳ͕ͨ޷͖ͦ͏ͳهࣄ͕ͰͨΒ͓ಧ͚ͯ͠ྑ͍ʁ ͨͱ͑͹͜Μͳ

  103. ಛఆͷϢʔβʔʹ௨஌͢Δ 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: 'こっちは本文' } } })
  104. ಛఆͷϢʔβʔʹ௨஌͢Δ 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Λऔಘ
  105. ಛఆͷϢʔβʔʹ௨஌͢Δ 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
  106. දݱʗମݧΛߟ͑Δ

  107. ͔͜͜Β͸ڠྗͯ͠΋Βͬͨௐࠪ݁Ռ΋దٓग़͍ͯ͘͠Α ͝ڠྗ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ

  108. ੈքதηϯαʔͰҲΕ͔͑ͬͯΔ Թ౓͕Θ͔Γ·͢ ࣪౓Λײ͡·͢ Ի͕ฉ͑͜·͢

  109. ੈքதηϯαʔͰҲΕ͔͑ͬͯΔ ۙ͘ͷ #MVFUPPUI͕ݟΕ·͢ ໌Δ͞Λ൑ఆ͠·͢ Ի͕ฉ͑͜·͢ Ҡಈ଎౓͕Θ͔Γ·͢ Χϝϥ͋Γ·͢ ͍ͭͰʹɺ͍͍ͩͨΦϯϥΠϯ

  110. ͍͕ͭ͜൓ضΛ຋͢ͱාͦ͏ ৐ͬऔΒΕΔͱ·͍ͣ

  111. 23ΛಡΈࠐΉ͚ͩͰ 63-ʹδϟϯϓ service-worker Πϯετʔϧ͞ΕΔ ͋ͱ͸௨஌ڐՄ͑͞΋Β͑Ε͹ λεΫΩϧ͞Εͯͯ΋ಈ͔ͤͦ͏ 1VTI"1*Λߟ͑Δͱ

  112. ಈ͔ͤͦ͏ʁ ௨஌ग़ͤΔ͚ͩ͡Όͳ͍ͷʁ ௨஌ϝοηʔδͱσʔλϝοηʔδ͕͋Δ ௨஌ϝοηʔδɿ ௨஌Λग़͚ͩ͢ͷϝοηʔδ σʔλϝοηʔδɿ σʔλΛड͚औͬͯ 4FSWJDF8PSLFS ಺Ͱॲཧ͢Δ

  113. ಈ͔ͤͦ͏ʁ ௨஌ग़ͤΔ͚ͩ͡Όͳ͍ͷʁ ௨஌ϝοηʔδͱσʔλϝοηʔδ͕͋Δ ௨஌ϝοηʔδɿ ௨஌Λग़͚ͩ͢ͷϝοηʔδ σʔλϝοηʔδɿ σʔλΛड͚औͬͯ 4FSWJDF8PSLFS ಺Ͱॲཧ͢Δ ʮΉΉʯ

  114. message: { token, data: { ... } } message: {

    token, notification: { title: 'これはタイトル‘, body: 'こっちは本文‘ } } ௨஌ϝοηʔδ σʔλϝοηʔδ ௨஌Λग़͞ͳͯ͘΋ྑ͍ 4FSWJDF8PSLFS ͕ಈ࡞͢Δ
  115. 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*͕ಈ͘ ͋ͱ
  116. 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*͕ಈ͘ ͋ͱ ʮ͋ͬʯ
  117. %&.0 ࢦఆͨ͠ 63-Λୟ͖ʹߦͬͯ΋Β͓ IUUQTZPVUVCF)HR/XHIMTX

  118. #PUOFUͷΑ͏ͳৼΔ෣͍ service-worker ߈ܸϓϩάϥϜೖΓ ߈ܸର৅ 63-Λ̍ͭ౿Ή͚ͩͰ #PUOFUʹͳΔةݥੑ %%04߈ܸʂ

  119. ΩϟϦΞճઢͱ 8*'*ซ༻Ͱ *1มΘͬͪΌͬͯ ϒϩοΩϯά΋Ή͍͔ͣ΋ʁ ௐࠪ݁Ռ Ұ೔ͷ͏ͪʹͲͷ͘Β͍ *1มΘΔʁ Ұਓ͋ͨΓ <ݸ> ೔Ͱ࢖༻͢Δ

    *1ΞυϨε Մ΋ͳ͘ෆՄ΋ͳ͘
  120. ϗϯτ͸ͦΜͳ৺഑͡Όͳ͍ ͍͍ͩͨ͜Ε͕कͬͯ͘ΕΔ

  121. ྑ͍࢖ΘΕํ΋ߟ͍͑ͨ

  122. දݱʗମݧΛߟ͑Δ ͖͠Γͳ͓͠

  123. ΠέͯΔ 8FCΞϓϦ࡞͚ͬͨͲ ӡӦ͍ͯ͘͠ҡ࣋අ͕͖͍ͭ

  124. ΠέͯΔ 8FCΞϓϦ࡞͚ͬͨͲ ӡӦ͍ͯ͘͠ҡ࣋අ͕͖͍ͭ ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ

    ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ ޿ࠂ
  125. Ծ૝௨՟Λ۷ͬͯΈΔʁ $PJOIJWF Ͱѱ໊ΛͻΖΊͨʠϒϥ΢βϕʔεϚΠχϯάʡ ແڐՄͰ΍Δͷ͸μϝͳͷ͸΋ͪΖΜͷ͜ͱ ޿ࠂ͸ग़ͨ͘͠ͳ͍ ҡ࣋අ͸୲อ͍ͨ͠ ͜͏͍͏৚݅ԼͰ͸ྑ͍ΞΠσΞͰ͸͋Δ

  126. Ͱ΋ڐՄΛऔͬͨ·ͱ΋ͳར༻Ͱ΋ 69ΘΔ͍͘ͳ͍ʁ 8FCΞϓϦ࢖ͬͯΔ࠷தʹ ೤͘ͳΔ ి஑৯͏ ॏ͍ Ϣʔβʔ཭Εͦ͏

  127. ࠒ߹͍Λݟͯ΍Δ Ұ൪࢖ͬͯͳͯ͘ɺόοςϦʔ΋৺഑ͳͯ͘ ΦϯϥΠϯͰ͔ͭߴ଎Ͱ઀ଓ͞ΕͯΔঢ়ଶ ॆి͞Εͯͯɺ8J'Jʹͭͳ͕ͬͯͳ͍ʁ

  128. 0 5 10 15 20 25    

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

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

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

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

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

                             3PVOE5SJQ5JNF 0 5 10 15 20 25                           TMPXH H H H ҰͭલͷάϥϑͷԼͷํ SUU <NT> ࣌ࠁ
  134. ݴ͍͔ͨͬͨ͜ͱ͸ ৸ͯͦ͏ͳ࣌ؒ͸֨ผ଎͍͸ͣʂ ͚ͩͬͨͲ ͍͍ͩͨ଎͍ͳΒɺ·͊ 0,Ͱ͠ΐ͏

  135. දݱʗମݧΛߟ͑Δ ̏ͭΊ

  136. ʮ͖ͬ͞ϩʔΧϧͰը૾ʗಈըΛ҉߸Խ͚ͨ͠Ͳɺ ΋͏ϩʔΧϧʹ࣋ͬͯΔͷ΋ෆ҆ʯ ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ ʮͩͬͯ 4FSWJDF8PSLFS͍ͬͯ͏ͷΈͨ͠ʯ ʮͳʹ͞ΕͯΔ͔Θ͔ͬͨ΋Μ͡Όͳ͍ΑͶʯ ʮͦ΋ͦ΋҆৺ͬͯͳʹΛ࣋ͬͯͦ͏͍͏;͏ʹݴͬͯ

  137. ʮ͋ɺ҉߸Խͯ͠όϥͯ͠ੈքதʹ෼ࢄͤ͞Αʯ ʠͱ΍͔͘ʡ͞Εͨ͘ͳ͍ਓɿ ڕΛӅ͢ͳΒւͩΑͶ εϚϗͷւ΁

  138. ݤ" ݤ# ݤ$ ݤ% ҉߸Խͨ͠ϑΝΠϧΛ෼ׂͯ͠഑෍ɺ഑෍ઌͷݤͰߋʹ҉߸Խ ΋͏ͪΐͬͱ۩ମతʹ͸

  139. ·ͨ͸ ݤ" ݤ# ݤ$ ݤ% ద౰ͳ਺ϗοϓͤͯ͞ɺ҉߸Խͱෳ߹Λ܁Γସ͑͢ ࠷ऴతʹ %ʹ࣋ͬͯͯ΋Β͏

  140. 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*ͷ࿩ʹཱͪฦΔͱ
  141. 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͸ Ͳ͔͜ΒͰ΋୭͔ΒͰ΋ૹΕΔ
  142. ฦͯ͠΄͍࣌͠ʢෳ߹࣌ʣ ฦͯ͠ʂ ͍ͬͯ͏ 1VTI௨஌ΛૹΔ ෳ߹ ෳ߹ ෳ߹ ෳ߹ ݟΕΔʂ ݤ"

    ݤ# ݤ$ ݤ%
  143. ·ͨ͸ ݤ" ݤ# ݤ$ ݤ% ฦͯ͠ʂ ฦͯͩͬͯ͠ ฦͯͩͬͯ͠ ͓ͬ

  144. ·ͨ͸ ݤ" ݤ# ݤ$ ݤ% ฦͯ͠ʂ ฦͯͩͬͯ͠ ฦͯͩͬͯ͠ ͓ͬ ෳ߹

    ෳ߹ ෳ߹ ෳ߹ ݟΕΔʂ
  145.          

                Ԡ౴࣌ؒ<ඵ> ࣌ࠁ ฏۉԠ౴࣌ؒ ͦΕͬͯݱ࣮తʁ ௐࠪ݁Ռ 1VTI"1*ͬͯΈΜͳͲΕ͘Β͍Ͱฦͯ͘͠ΕΔͷʁ ஗͍
  146. ͦΕͬͯݱ࣮తʁ ௐࠪ݁Ռ 1VTI"1*ͬͯΈΜͳͲΕ͘Β͍Ͱฦͯ͘͠ΕΔͷʁ Ԡ౴଎౓ʢฏۉʣ <ඵ> Ұ൪ૣ͔ͬͨԠ౴ʢฏۉʣ <ඵ> Ұ൪஗͔ͬͨԠ౴ʢฏۉʣ <ඵ> Ԡ౴཰ʢฏۉʣ

    <>
  147. 1VTIͰͲͷ͘Β͍ૹ৴Ͱ͖Δ ͬͪ͜ͷλΠϓͳΒ ,#ҎԼʹͳΔ·Ͱ෼ׂͨ͠Βཧ࿦্͸͍͚Δ

  148. 1VTIͰͲͷ͘Β͍ૹ৴Ͱ͖Δ ͬͪ͜ͷλΠϓ͸αʔόʔʹ͚͋ͣͯɺ ࡏΓॲΛڭ͑Δͱ͔͠ͳ͍ͱ͍͚ͳ͍ ͜͜ʹૹͬͨΑ Cc2H5BfqPf+L/Z Cc2H5BfqPf+L/Z ͜͜ʹૹͬͨΑ Cc2H5BfqPf+L/Z ͜͜ʹૹͬͨΑ Cc2H5BfqPf+L/Z

  149. ·ͱΊ 8FCͰͰ͖ΔମݧΛߟ͑Δձ

  150. ̏ͭͷδϟϯϧʹ෼͚ͯ 8FC"1*ͷ࢖͍ํΛ঺հ ૊Έ߹Θͤͯ࢖͏ͱ໘ന͍Αͱ͍͏࿩Λఴ͑ͯ 8FC8PSLFS 8FC"TTFNCMZ 1VTI"1* 8FC(- UISFFKT 8FC"VEJP"1* ͳʹ͔໘ന͍͜ͱͷ͖͔͚ͬʹͳͬͯ͘ΕΔͱྑ͍Ͱ͢

  151. ંݪ ϨΦφϧυݡ !-FPOBSEP@FOHS ϑϩϯτΤϯυΤϯδχΞɺͨ·ʹ69σβΠφʔ LINE 株式会社 UIT 室 User Interface

    + Technology team @LINE corp
  152. ͋Γ͕ͱ͏͍͟͝·ͨ͠