CREATING YOUR OWN IMAGE FORMAT IN THE BROWSER

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

CREATING YOUR OWN IMAGE FORMAT IN THE BROWSER

@TRENTMWILLIS

LOVE THE WEB LOVE JAVASCRIPT @TRENTMWILLIS #CityJSConf

JAVASCRIPT CONTINUES TO EVOLVE @TRENTMWILLIS #CityJSConf

WEB HYPERTEXT APPLICATION TECHNOLOGY WORKING GROUP @TRENTMWILLIS #CityJSConf

WHATWG @TRENTMWILLIS #CityJSConf

WHATWG STREAMS SPEC @TRENTMWILLIS #CityJSConf

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

@TRENTMWILLIS #CityJSConf

@TRENTMWILLIS #CityJSConf

PHOTOS ARE IMPORTANT @TRENTMWILLIS #CityJSConf

PHOTOS CAN BE EXPERIMENTED WITH @TRENTMWILLIS #CityJSConf

WEB CRYPTO API @TRENTMWILLIS #CityJSConf

@TRENTMWILLIS #CityJSConf

CRYPTO + INSTAGRAM @TRENTMWILLIS #CityJSConf

CRYPTOGRAM @TRENTMWILLIS #CityJSConf

Encrypted Image @TRENTMWILLIS #CityJSConf

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

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

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

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

ALL CODE WILL BE AVAILABLE ONLINE @TRENTMWILLIS #CityJSConf

FETCHEVENT @TRENTMWILLIS #CityJSConf

FETCHEVENT .RESPONDWITH() @TRENTMWILLIS #CityJSConf

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

self.addEventListener(‘fetch’, (event) => { }); @TRENTMWILLIS #CityJSConf

self.addEventListener(‘fetch’, (event) => { if (isEncryptedImageRequest(event.request)) isEncryptedImageRequest { } }); @TRENTMWILLIS #CityJSConf

const isEncryptedImageRequest = request => request.url.endsWith(‘.epng’); @TRENTMWILLIS #CityJSConf

self.addEventListener(‘fetch’, (event) => { if (isEncryptedImageRequest(event.request)) isEncryptedImageRequest { }; }); @TRENTMWILLIS #CityJSConf

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

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

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

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

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

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

IT’S BREAKING JUST AS EXPECTED @TRENTMWILLIS #CityJSConf

WEB CRYPTO API @TRENTMWILLIS #CityJSConf

SUBTLECRYPTO @TRENTMWILLIS #CityJSConf

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 #CityJSConf

CRYPTOKEY @TRENTMWILLIS #CityJSConf

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 #CityJSConf

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

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

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

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 #CityJSConf

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 #CityJSConf

CRYPTOGRAM-NAIVE.GLITCH.ME @TRENTMWILLIS #CityJSConf

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

Encrypted Data @TRENTMWILLIS #CityJSConf

Encrypted Data @TRENTMWILLIS Decrypted Data #CityJSConf

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 #CityJSConf

STREAM 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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

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 #CityJSConf

WRITABLESTREAM @TRENTMWILLIS #CityJSConf

WRITABLESTREAM READABLESTREAM @TRENTMWILLIS #CityJSConf

WRITABLESTREAM + READABLESTREAM = TRANSFORMSTREAM @TRENTMWILLIS #CityJSConf

TRANSFORMER @TRENTMWILLIS #CityJSConf

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

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

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

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

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 #CityJSConf

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

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

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

async transform(chunk, controller) {; let data = chunk; const length = data.length; const blocks = Math.floor(length / 16); };; @TRENTMWILLIS #CityJSConf

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 #CityJSConf

async transform(chunk, controller) {; let data = chunk; const length = data.length; const blocks = Math.floor(length / 16); const remainder = length % 16; if (remainder) { this.remainderData = data.slice(length - remainder); data = data.slice(0, 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 #CityJSConf

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) { this.remainderData = data.slice(length - remainder); data = data.slice(0, 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 #CityJSConf

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

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 #CityJSConf

CRYPTOGRAM-STREAMING.GLITCH.ME @TRENTMWILLIS #CityJSConf

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

RUNTIME STREAMING COMPILATION FOR DEVELOPMENT @TRENTMWILLIS #CityJSConf

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

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

RUNTIME VIDEO EFFECTS @TRENTMWILLIS #CityJSConf

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! #CityJSConf

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