Creating Your Own Image Format in the Browser

A presentation at JSConf Colombia in November 2018 in Medellin, Antioquia, Colombia by Trent Willis

Slide 1

Slide 1

CREATING YOUR OWN IMAGE FORMAT IN THE BROWSER

Slide 2

Slide 2

ATWOOD’S LAW

Slide 3

Slide 3

ATWOOD’S LAW "ANY APPLICATION THAT CAN BE WRITTEN IN JAVASCRIPT, WILL EVENTUALLY BE WRITTEN IN JAVASCRIPT.”

Slide 4

Slide 4

Slide 5

Slide 5

Slide 6

Slide 6

Slide 7

Slide 7

Slide 8

Slide 8

CREATING YOUR OWN IMAGE FORMAT IN THE BROWSER

Slide 9

Slide 9

@TRENTMWILLIS

Slide 10

Slide 10

LOVE THE WEB LOVE JAVASCRIPT @TRENTMWILLIS #JSCONFCO

Slide 11

Slide 11

JAVASCRIPT CONTINUES TO EVOLVE @TRENTMWILLIS #JSCONFCO

Slide 12

Slide 12

WEB HYPERTEXT APPLICATION TECHNOLOGY WORKING GROUP @TRENTMWILLIS #JSCONFCO

Slide 13

Slide 13

WHATWG @TRENTMWILLIS #JSCONFCO

Slide 14

Slide 14

STREAMS SPEC @TRENTMWILLIS #JSCONFCO

Slide 15

Slide 15

STREAMS SPEC “IF INSTALLED INSIDE THE FETCH HOOK OF A SERVICE WORKER, THIS WOULD ALLOW DEVELOPERS TO TRANSPARENTLY POLYFILL NEW IMAGE FORMATS.” @TRENTMWILLIS #JSCONFCO

Slide 16

Slide 16

@TRENTMWILLIS #JSCONFCO

Slide 17

Slide 17

WEB CRYPTO API @TRENTMWILLIS #JSCONFCO

Slide 18

Slide 18

CRYPTOGRAM @TRENTMWILLIS #JSCONFCO

Slide 19

Slide 19

Encrypted Image @TRENTMWILLIS #JSCONFCO

Slide 20

Slide 20

Encrypted Image <img src=“./encrypted-image.epng”> @TRENTMWILLIS #JSCONFCO

Slide 21

Slide 21

Encrypted Image <img src=“./encrypted-image.epng”> *Can Display PNG/JPEG, Not EPNG @TRENTMWILLIS #JSCONFCO

Slide 22

Slide 22

Encrypted Image Service Worker <img src=“./encrypted-image.epng”> *Can Display PNG/JPEG, Not EPNG @TRENTMWILLIS #JSCONFCO

Slide 23

Slide 23

Encrypted Image .epng Service Worker .png <img src=“./encrypted-image.epng”> *Can Display PNG/JPEG, Not EPNG @TRENTMWILLIS #JSCONFCO

Slide 24

Slide 24

FETCHEVENT @TRENTMWILLIS #JSCONFCO

Slide 25

Slide 25

FETCHEVENT .RESPONDWITH() @TRENTMWILLIS #JSCONFCO

Slide 26

Slide 26

ALL CODE WILL BE AVAILABLE ONLINE @TRENTMWILLIS #JSCONFCO

Slide 27

Slide 27

navigator.serviceWorker.register(‘./service-worker.js’); @TRENTMWILLIS #JSCONFCO

Slide 28

Slide 28

self.addEventListener('fetch', (event) => { }); @TRENTMWILLIS #JSCONFCO

Slide 29

Slide 29

self.addEventListener('fetch', (event) => { if (isEncryptedImageRequest(event.request)) isEncryptedImageRequest { } }); @TRENTMWILLIS #JSCONFCO

Slide 30

Slide 30

const isEncryptedImageRequest = request => request.url.endsWith('.epng'); @TRENTMWILLIS #JSCONFCO

Slide 31

Slide 31

self.addEventListener('fetch', (event) => { if (isEncryptedImageRequest(event.request)) isEncryptedImageRequest { }; }); @TRENTMWILLIS #JSCONFCO

Slide 32

Slide 32

self.addEventListener('fetch', (event) => { if (isEncryptedImageRequest(event.request)) { event.respondWith(pngFromEncryptedImageRequest(event.request)); pngFromEncryptedImageRequest }; }); @TRENTMWILLIS #JSCONFCO

Slide 33

Slide 33

const pngFromEncryptedImageRequest = async (request) => {; }; @TRENTMWILLIS #JSCONFCO

Slide 34

Slide 34

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); }; @TRENTMWILLIS #JSCONFCO

Slide 35

Slide 35

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const encryptedData = await response.arrayBuffer(); }; @TRENTMWILLIS #JSCONFCO

Slide 36

Slide 36

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const encryptedData = await response.arrayBuffer(); const decryptedData = await decryptData(encryptedData); }; @TRENTMWILLIS #JSCONFCO

Slide 37

Slide 37

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], {;type: 'image/png' }); }; @TRENTMWILLIS #JSCONFCO

Slide 38

Slide 38

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], {;type: 'image/png' }); return new Response(pngBlob); }; @TRENTMWILLIS #JSCONFCO

Slide 39

Slide 39

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], {;type: 'image/png' }); return new Response(pngBlob); }; @TRENTMWILLIS #JSCONFCO

Slide 40

Slide 40

const pngFromCustomImageRequest = async (request, transform) => {; const const const const response = await fetch(request); rawData = await response.arrayBuffer(); pngData = await transform(rawData); pngBlob = new Blob([pngData], {;type: 'image/png' }); return new Response(pngBlob); }; @TRENTMWILLIS #JSCONFCO

Slide 41

Slide 41

WEB CRYPTO API @TRENTMWILLIS #JSCONFCO

Slide 42

Slide 42

SUBTLECRYPTO @TRENTMWILLIS #JSCONFCO

Slide 43

Slide 43

SUBTLECRYPTO “IT IS NAMED SUBTLECRYPTO TO REFLECT THE FACT THAT MANY OF THESE ALGORITHMS HAVE SUBTLE USAGE REQUIREMENTS IN ORDER TO PROVIDE THE REQUIRED ALGORITHMIC SECURITY GUARANTEES.” @TRENTMWILLIS #JSCONFCO

Slide 44

Slide 44

CRYPTOKEY @TRENTMWILLIS #JSCONFCO

Slide 45

Slide 45

const pngFromEncryptedImageRequest = async (request) => { const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData decryptData(encryptedData); pngBlob = new Blob([decryptedData], { type: 'image/png' }); return new Response(pngBlob); }; @TRENTMWILLIS #JSCONFCO

Slide 46

Slide 46

const decryptData = async (encryptedData) => {; }; @TRENTMWILLIS #JSCONFCO

Slide 47

Slide 47

const decryptData = async (encryptedData) => {; const cryptoKey = await getCryptoKey(); };; @TRENTMWILLIS #JSCONFCO

Slide 48

Slide 48

const decryptData = async (encryptedData) => {; const cryptoKey = await getCryptoKey(); const decryptionOptions = {; name: ‘AES-CTR', counter: new Uint8Array(16), length: 128 }; };; @TRENTMWILLIS #JSCONFCO

Slide 49

Slide 49

const decryptData = async (encryptedData) => {; const cryptoKey = await getCryptoKey(); const decryptionOptions = {; name: ‘AES-CTR', counter: new Uint8Array(16), length: 128 }; return crypto.subtle.decrypt( decryptionOptions, cryptoKey, encryptedData ); };; @TRENTMWILLIS #JSCONFCO

Slide 50

Slide 50

const pngFromEncryptedImageRequest = async (request) => { const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], { type: 'image/png' }); return new Response(pngBlob); }; @TRENTMWILLIS #JSCONFCO

Slide 51

Slide 51

cryptogram-naive.glitch.me @TRENTMWILLIS #JSCONFCO

Slide 52

Slide 52

MAKE IT WORK MAKE IT RIGHT MAKE IT FAST @TRENTMWILLIS #JSCONFCO

Slide 53

Slide 53

Encrypted Data @TRENTMWILLIS #JSCONFCO

Slide 54

Slide 54

Encrypted Data @TRENTMWILLIS Decrypted Data #JSCONFCO

Slide 55

Slide 55

BATCH Batch digra PROCESSING Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #JSCONFCO

Slide 56

Slide 56

STREAM Batch digra PROCESSING Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #JSCONFCO

Slide 57

Slide 57

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #JSCONFCO

Slide 58

Slide 58

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #JSCONFCO

Slide 59

Slide 59

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #JSCONFCO

Slide 60

Slide 60

Encrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #JSCONFCO

Slide 61

Slide 61

Encrypted Data Encrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk @TRENTMWILLIS Decrypted Data Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Encrypted Chunk Decrypted Chunk Decrypted Chunk #JSCONFCO

Slide 62

Slide 62

const pngFromEncryptedImageRequest = async (request) => {; const const const const response = await fetch(request); encryptedData = await response.arrayBuffer(); decryptedData = await decryptData(encryptedData); pngBlob = new Blob([decryptedData], { type: 'image/png' }); return new Response(pngBlob); }; @TRENTMWILLIS #JSCONFCO

Slide 63

Slide 63

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #JSCONFCO

Slide 64

Slide 64

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #JSCONFCO

Slide 65

Slide 65

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #JSCONFCO

Slide 66

Slide 66

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #JSCONFCO

Slide 67

Slide 67

const pngFromEncryptedImageRequest = async (request) => {; const response = await fetch(request); const decrypter = new TransformStream(new Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #JSCONFCO

Slide 68

Slide 68

WRITABLESTREAM @TRENTMWILLIS #JSCONFCO

Slide 69

Slide 69

WRITABLESTREAM READABLESTREAM @TRENTMWILLIS #JSCONFCO

Slide 70

Slide 70

WRITABLESTREAM + READABLESTREAM = TRANSFORMSTREAM @TRENTMWILLIS #JSCONFCO

Slide 71

Slide 71

TRANSFORMER @TRENTMWILLIS #JSCONFCO

Slide 72

Slide 72

TRANSFORMER .START() .TRANSFORM() .FLUSH() @TRENTMWILLIS #JSCONFCO

Slide 73

Slide 73

const pngFromEncryptedImageRequest = async (request) => { const response = await fetch(request); const decrypter = new TransformStream(new Decrypter Decrypter()); const pngStream = response.body.pipeThrough(decrypter); return new Response(pngStream); }; @TRENTMWILLIS #JSCONFCO

Slide 74

Slide 74

class Decrypter {; async start() {; }; async transform() {; }; async flush() {; }; }; @TRENTMWILLIS #JSCONFCO

Slide 75

Slide 75

class Decrypter {; async start() {; this.counter = new Uint8Array(16); }; async transform() {; }; async flush() {; }; }; @TRENTMWILLIS #JSCONFCO

Slide 76

Slide 76

class Decrypter {; async start() {; this.counter = new Uint8Array(16); this.key = await getCryptoKey(); }; async transform() {; }; };; async flush() {; }; }; @TRENTMWILLIS #JSCONFCO

Slide 77

Slide 77

async transform(chunk, controller) {; let data = chunk; const length = data.length; const blocks = Math.floor(length / 16); const decryptedData = await decrypt(this.key, data, this.counter); controller.enqueue(decryptedData); for (let i = 0; i < blocks; i++) { incrementUint8ArrayCounter(this.counter); }; };; @TRENTMWILLIS #JSCONFCO

Slide 78

Slide 78

async transform(chunk, controller) {; let data = chunk; const length = data.length; const blocks = Math.floor(length / 16); const remainder = length % 16; if (remainder) { data = data.slice(0, length - remainder); this.remainderData = data.slice(length - remainder); }; const decryptedData = await decrypt(this.key, data, this.counter); controller.enqueue(decryptedData); for (let i = 0; i < blocks; i++) { incrementUint8ArrayCounter(this.counter); }; }; @TRENTMWILLIS #JSCONFCO

Slide 79

Slide 79

async transform( transform(chunk, controller) ) {; let data = chunk; if (this.remainderData) { data = concatUint8Arrays(this.remainderData, data); this.remainderData = null; } const length = data.length; const blocks = Math.floor(length / 16); const remainder = length % 16; if (remainder) { data = data.slice(0, length - remainder); this.remainderData = data.slice(length - remainder); }; const decryptedData = await decrypt(this.key, data, this.counter); controller.enqueue(decryptedData); for (let i = 0; i < blocks; i++) { incrementUint8ArrayCounter(this.counter); }; }; @TRENTMWILLIS #JSCONFCO

Slide 80

Slide 80

class Decrypter {; async start() {; this.counter = new Uint8Array(16); this.key = await getCryptoKey(); }; async transform() { {; // It's too long! }; async flush() {; }; }; @TRENTMWILLIS #JSCONFCO

Slide 81

Slide 81

class Decrypter {; async start() {; this.counter = new Uint8Array(16); this.key = await getCryptoKey(); }; async transform() {; // It's too long! }; async flush(controller) {; if (this.remainderData) { const decryptedData = await decrypt( this.key, this.remainderData, this.counter); controller.enqueue(decryptedData); } }; }; @TRENTMWILLIS #JSCONFCO

Slide 82

Slide 82

cryptogram-streaming.glitch.me @TRENTMWILLIS #JSCONFCO

Slide 83

Slide 83

WEBASSEMBLY FOR LIGHTNING FAST TRANSFORM STREAMS wasm-streams.glitch.me @TRENTMWILLIS #JSCONFCO

Slide 84

Slide 84

RUNTIME STREAMING COMPILATION FOR DEVELOPMENT @TRENTMWILLIS #JSCONFCO

Slide 85

Slide 85

HTML MODULE POLYFILL html-modules-polyfill.glitch.me @TRENTMWILLIS #JSCONFCO

Slide 86

Slide 86

PACKAGE NAME MAPS POLYFILL package-name-maps.glitch.me @TRENTMWILLIS #JSCONFCO

Slide 87

Slide 87

RUNTIME VIDEO EFFECTS @TRENTMWILLIS #JSCONFCO

Slide 88

Slide 88

RUNTIME STREAMING SERVICE WORKERS COMPILATION FOR DEVELOPMENT STREAMS HTML MODULE POLYFILL WEB CRYPTO PACKAGE NAME MAPS POLYFILL ESNEXT RUNTIME VIDEO EFFECTS @TRENTMWILLIS WEB ASSEMBLY WEB COMPONENTS AND MORE! #JSCONFCO

Slide 89

Slide 89

WRITE EVERYTHING IN JAVASCRIPT! * *WITHIN REASON, BUT DEFINITELY HAVE FUN! **ALSO, GLITCH IS GREAT FOR EXPERIMENTS! @TRENTMWILLIS #JSCONFCO