 Hello, good morning, good evening, good night, whatever day time or night time it may be. Welcome to this workshop, a burbling tour through Project Fugu APIs. My name is Tom and I'm your host today. And yeah, we will walk through this code lab together. All right, so let's get going. We have 45 minutes. The code lab is on the resources. So again, if you haven't seen this before, there's a session resources link. And you will be landing on this page. So yeah, what we'll build today is a greeting card application. So the idea is imagine you were at a physical Google I.O. And there were a big terminal where you could just send a greeting card to your loved ones at home. Telling them, hey, I'm in California. I'm in the Mountain View Amphitheater. And yeah, I'm attending Google I.O. So you would write this greeting card and then send that it would be shared with your loved ones via email. We're not a physical I.O., unfortunately, but we're here in your physical I.O. at home. So that's awesome too. So this is my home. Welcome to my home. It's my bedroom actually. So my bed is right here. Should I fall asleep? I'm probably just on my right hand side. The people on the production team will then ping me and wake me up. Yeah, hopefully I won't fall asleep. Hopefully you won't fall asleep, even if you're joining at night. So yeah, let's get cracking. What we will do is we will write this greeting card application. And for this, what you need to know is you should be familiar with modern JavaScript. So a lot of these new APIs deal with promises with async await. If you're not, I try to make it as accessible as possible. But you should hopefully be familiar with that a little bit at least. And yeah, what you'll build, I told you about this. We will build a greeting card application. I'll show it in a second. And what you will need is, you will need a browser, very obviously so. We recommend using Chrome or the Chromium-based Edge. But other browsers should work as well. And you will see actually that I'll be using other browsers. So for the full code lab, you will definitely need one of the Chromium-based ones. But this is a progressive enhancement thing as well. We will make something that works on more than just Chrome, or more than just Chromium, if you know. This is called Project Fugu. So a lot of these APIs that we've built or developed are built with new capabilities in mind. So we want to enable more things on the browser, which means we got to extend the browsers with new features. And the code name for this that we chose is Fugu. Fugu is a fish that is amazing. It's super delicate. The only downside with this fish is if you cut it wrong, it will kill you, or it might kill you. We sort of chose this as a funny code name just because in the end, what we'll build is making more things possible that haven't been possible before. So we try to make everything as safe as possible, and no one will die from using our APIs. But certainly, let's take, for example, files. If you share your et cetera passwords file on Linux, that's probably not a good idea. So the danger in this case is you sharing stuff that you wouldn't be sharing with the web page usually. And some of these APIs are a little dangerous, and some of these APIs are just not so dangerous. But as I said, it's all secure, it's all safe. Nothing will break, nothing will kill you. You can see the steps on the left-hand side here. There's an iconography here. The harmless freshwater fish means something is stable, something is running as expected for a long time. You will see some of the steps have the blowfish here, so the fugu fish. And this means handle with care. It's an API that may still change a little bit, maybe an early trial version of an API, so some of the final shape of the API may change. But yeah, again, nothing will break. And if you look at the steps, there's a lot of steps. And during this code lab, I won't have time to go through all of them. And if you go to the Getting Started section, you will see that some of the first, one of the first steps that you need to take is you will have to enable a flag. The way you do this is you open a new tab in your browser and then just type chrome, colon, slash, slash, slash, flags, flags. Flag is not what you should type, flags. And you line here, and then you can search here for experimental web platform features. And I can see I have this enabled already because I couldn't live start my browser because the stream would end if it did. But for you, just toggle this to enabled, restart your browser, and then you should be good to go. This is the same, actually, on mobile devices. So if you use a mobile device, definitely make sure that you also set the flag there. And let me go back to here, actually. Yeah, so just make sure that you set this flag. But as I said before, we will build something in a sense of progressive enhancement. So you will end up with a product that runs on more than just Chromium-based browsers. And we will use a platform called Glitch. There's a link in the code lab. If you open that, you will land on glitch.com. And you can see I have a couple of projects already. One of them is the one that we will work on today. And I'll show you in a second what you should do. This is glitch, so it has this funny glitch that it always takes a little to load. But here we are. It's a code editor, as you may expect. It's web-based. And the cool thing is whatever you do is reflected on the actual demo version that you built. So you can click Show here and then show your application in a new window. This is what I've done here. So you can see the application is running in a new window. We will talk about this in a couple of seconds. First, let's go back to here. What we will build is this greeting card application. I call it Fubo Paint. Because you can paint on this. Let me quickly just show you. You can make amazing drawings. I'm not an artist and I'm doing this on a trackpad without a stylus. But you get the idea. So you could use this to write whatever, nor... It's really hard to draw this, especially with nervous fingers. So north... That's just the first word that came into my mind. What not? Anyway, north with a little bit of imagination. So it's an application that's pretty bare bones. You can choose the color. So paint in red if you like. You can change the thickness of your brush. And that's it. You can clear the canvas. That's all we have so far. But we will make this a lot more advanced. One last tip for the code editor. You can see I have my code editor in dark mode. You can change this in here. You can change themes. So if you prefer light mode or dark mode, that's definitely supported by Glitch. And something else. You will end up with a product that you can take home with you. Glitch is a platform that allows you to export your creations to GitHub or just a regular Git repository. So if you click import and export, you can just copy your projects, git URL, and then check out locally or import or export to GitHub. So there's no vendor lock-in. If you don't like Glitch, you can use it today. But then the next time, you can just work with your local copy. All right. So let's go back to here. What we will do is we will remix the existing application. What is remixing? Well, remixing essentially means you take what I have and you make a copy that you can edit. My copy is here. You can see it has a URL fugu-paint.glitch.me that is reflected here, fugu-paint. And when you go there and click the button on the code lab remix, you will create your own remix of this. And as I do this now, you can see I get a new URL, so minus power extreme orbit. Wow, that's a nice URL. You might have something else, of course, but it will be an exact copy of the thing that I have over here. All right. So let me quickly walk you through it. So we have a package JSON that we don't really need. It's based on Snowpack, but it's also not really important. The core things that we look at are in the public folder. In public, we have the index HTML. It's pretty bare bones. All we have is a body with a toolbar, a number of inputs and buttons, and then down here, the canvas. And it's a progressive web app, which means we, of course, have a service worker. You don't need to worry about that. It's built already. So it's a progressive web app from the start. We have a web manifest. I can see a couple of things in here, like the name, the short name, the start URL, and so on. This is not about PWAs today. This is about Fugu APIs. So I just mentioned it. It's a PWA. I won't go into much detail of what it is. We have some CSS, but really not much. Just some basic styling. I'm not a designer. It's pretty ugly, but it just is forgetting the point across what I wanted to do. The only thing, well, we have assets for some images that are statically loaded, like, for example, the welcome image that you see if you reload. This is one of the static assets, so it's a Fugu fish. That's a pretty harmless one, actually. It doesn't look dangerous, this one. So let's go back here. The only thing that we actually will need is the JS folder. And you can see there's a lot of files, and all of them are empty, or almost all of them are empty. We will fill them with content a little later. The main file that we look at right now is ScriptMJS. Nothing super exciting happening in here. This is mostly just dealing with the painting, so that when you have pointer down events, that then you can paint and blah. Again, it's not rocket science. It's just something that I quickly want to glance over. And then down there, we have the Fugu features. And you can see, for all of those, there's conditional loading. So for example, here, if share is in the navigator object, and can share is in the navigator object, only then I dynamically import share.MJS. And share.MJS is a file that we will need a little later, so you can see it's empty, but that's a core idea. If you use a browser that doesn't support a given API, what we want is not load a file that the browser will never need. So this pattern here, progressive enhancement based on feature detection, is something that I highly recommend you do. Just because, yeah, in the end, when you build a production application, not just a tiny toy application like this one, you don't want to waste your user span width with stuff that they never gonna need because the browser doesn't support it. All right, so as I said, it's pretty bare bones. And as I said, some of the features are not working on all browsers. So if you open the console, you will see some logging here. So you will see the support matrix of what is supported on this particular browser and whatnot. And you can see I have, for example, two red marks down here for web share and web share target, that's just because my drum that I have here on macOS doesn't support the web share API quite yet. It will in the future, but so far it doesn't support it. The good news is I have a setup that is more flexible. So let me just quickly show you what I have here. You can see I got more. So this is my desktop browser here. And then I have here a pixel device. I hold it in the camera so that you can see this is actually a real device. That's my device. If I paint something on this device, you can see it's reflected on my screen. I'm using an application called Wiser, by the way. So if you're interested, Wiser is a super cool app for streaming real devices onto a physical connected screen. And the cool thing is it's B directional. So I can actually paint here and I can see everything I do gets reflected on my physical device as well. And my other device that I have is an iPhone device. And the same here. I'm using QuickTime, by the way. So here, QuickTime Player. If you do a new, just quickly go there, if you do a new movie recording, you can choose to reflect your device on the screen and then just project directly here. Same story, what I paint on the iPhone is visible on the physical screen here. So we will have two devices. One Android, one Android, one iOS, and then one Chrome device. Or Chrome, sorry, Chrome browser on Mac OS. So let's get into the code lab. So by now you have remixed your local clone of the application. Let me just move this away. You can see here the remix application. If you run this locally, the support matrix will of course look differently. But yeah, whatever device you try this on, be sure to make sure that whatever you see on the screen is actually working as intended. And one last reminder. This is a progressive web app, which means it's using a service worker. So if for whatever reason you make it change and it's not reflected immediately, just be sure to do a hard reload or just clear all the data. You can do this on the application tab and then there's the storage section where you can just say clear all the site data. So just in case something, when something is really bored, reset the service worker and give it another shot. But hopefully it will work most of the time. It's using a network-first strategy here. So it should hopefully see all your changes. But yeah, I just wanted to point it out. Just in case something looks stuck, it might be the service worker. Okay, so let's dive into our first API. As I said, there's a lot of APIs here. We won't have time to cover all of them. You have the code lab at home. So yeah, whatever steps you want to take after the guided tour, absolutely feel free to do so. I want to start with the WebShare API. And a WebShare, as you have seen on my support matrix here, is something that is not supported on my desktop browser. So I will have to show this on my two mobile browsers. So what is WebShare? WebShare essentially is a way how you can get stuff to other applications very easily. So my idea here is I make an amazing painting and I want to share it with the world, maybe send it to someone via messages on iOS or to someone as an email, by Gmail or whatnot. So how does this work? As I said before, we will work with progressive enhancement. So in our script, we will have this logic here. If share is a navigator or can share as a navigator, only then we will load share. And now in the second step, what we will do is we will actually fill the share.mjs file with content. So for this, let me just copy the entire code block here, go back to Glitch, find the file share right here, and then paste the content in. The structure of these files is always the same. So the script main file exports the UI elements. So here I have a share button. And in this case, I have a helper function called to blog, which just is responsible for converting whatever is on the canvas into a block so that I can get this to the API. The first thing we always need to do is we will need to display the button element. So share button style display to blog. And then we will add an event listener that will connect the share button that will appear in a second here with the function that is here, the share function. So share expects a title, a text, and a blob in this case. The blob is just whatever is on the canvas. And then we have a files object. So we have an object with a property called files, which is an array. And to this array, we will give a new file, which is a blob. We can give it a name if you want. So for greeting.png, we will just use the blobs type. It will be a PNG because my to blob function exports everything as a PNG. And we will use the title and the text from here. So full greetings and from Fulu with love. Then we will start the try and catch block here. So you will first check if the navigator is confident that it can share the data object here. Something could always break. So for whatever reason, maybe something might be too big, or maybe you try to share a file that is in a format that is not supported by the browser. So always make a habit of wrapping the actual share function in a try catch block. And then all we need to do is we need to just call navigator.share as an asynchronous function. So I have used a wait here. It's not really needed. But just in case you wanted to do something after that, you could wait here. Actually, it is needed because I need to catch it. So I need to wait here. So catch the error just in case something happens. My error handling strategy is I log to the console. Woohoo. So it looks relatively simple. And it actually is relatively simple. So let's test it on an actual device. I reload here and nothing is happening. That's great. What's going on? Interesting. Let's see. Is it caching? Let me try the iOS device meanwhile. Oh, no. I have tempted the demo gods and the demo gods. Oh, no, I'm stupid. What I did was look, I took my remixed clone instead of my actual application. So let me just quickly take it from here into my actually shared application. You can see I'm a little nervous, but that's fair. I'm not doing this every day. So if you reload here, nothing should happen because this browser doesn't support the API. If you reload here, let's do this now. Also nothing happens. What is going on? Let's try iOS. Oh, no. That's really interesting. So this wasn't planned. So let's see what's going on here. I'm using the right URL this time for the paint glitch. I am saving and Wiser is giving me a hard time, but it's loading again. Interesting. OK, so you will see me debug this life. What is the problem? Of course, it was working all the time completely fine. Interesting. So let's try Safari because Safari on desktop supports the Web Share API too. And it's also not loading here. Very interesting. Does it lock anything to the console? No. And I swear this was working. Come on. There's my button here. Share button is here. This is even refreshing at all. OK, let me do something very stupid. Let me just alert high. OK, so we do see changes. That's good. In my script file, I do the feature test for share. Share button is here. The share button is also here. If share and then here. This looks all good. And we should be importing the file. Do we import the file? Let's have a look. Yes, share is being imported. Is it empty? No. Oh, it was service recognition. I talked about it before. OK, we have the share button here. Oh, my God. I'm getting very nervous right now. But I'm slightly more calm now that at least I can see that it's loading. So let's quickly test this. I'm sharing two messages. It's loading messages. And you can see it's loading from football with love. But I guess messages doesn't really do file sharing here. OK, so let's get back to the other browsers. Safari was fine. So let's get back to Wiser. And Mirroring got disconnected. Looks like the demo bots are not on my side today. Reload? Yeah, still a service worker. OK, interesting. Let me just quickly connect to the device in fact. And it should be it. And then let me clear the storage. And then reload here. Oh, very interesting. OK, service workers, always great for a surprise. Always awesome. Anyway, so with this out of the way, let's do some amazing painting here and eight and then share it. And now you can see the share sheet pops up. I can show you different applications. Let's just take Gmail here. The Gmail app opens. And you can see, oh, smart compose. Yes, I want this. And you can see here it's opening the Gmail app right in the compose view. Subject is full of readings. The email body is from full of love. And I can see there is a little attachment. I can send this to anyone now, to anywhere or any who now. I don't actually want to do that. You've seen this is the Gmail application. So let's quickly go back to Chrome here. Message was saved to draft. So this was my little lifesaver here. You have seen, we struggle with this too here. So service workers still hard, even in 2021. All right. So with that, let's go to the next one. I'm going to skip Web Share Target because it's relatively involved. There's a couple of steps that you need to do. But you have all the instructions in the code if you want to. Let me go next to adding image support. So I want to import an image because maybe when I'm seeing here, the stock image that came from here is not really great. So this will use the draw image function from the canvas. If you're not familiar with that, just click through to this link. And we will use an input type file element to open the application. And you can see here import image legacy suggests that I'm doing something that is not state of the art anymore, which is true. I first want to show the legacy approach. So as before, I'm just copying the entire thing here. I'm trying to find the application. And this time I'm opening the right one. And I want to go to import image, not this one, but the legacy one, import image legacy. And I paste everything in here. Let's close the search widget. So as before, we're importing the import button and the draw block function. And then I'm going to add some helper functions that are exported from script and the UI element. As before, I make it style this by block and attach an event listener, which essentially just gets a file using the import image function and draws this file onto the canvas. So let's look at the import image function, which is the actual thing here. It's returning a new promise. As before, a lot of this stuff is asynchronous. I'm creating an input element here. I'm setting its type to file. I'm setting its accept to be image PNG, image JPEG, and essentially image in general, just in case some of those are not supported, because sometimes it's mine types. Sometimes it's the notation with the starter that works. I'm using all of them. And then we have an input at event listener and it's on change. And what you can see is taking just the first file. I could use multiple, but in this case, I'm just using the first one. This is actually not needed. That's testing when I attach this to the DOM. And then you can see I'm resolving the promise with the file that I get from the input. And the last thing that remains is clicking this input element programmatically. It is as weird as it sounds. You do something by virtually clicking. This is why I called this the legacy method. So let's reload here. And I guess it will not work again. Let's see. Yeah. Oh, OK. So this time, for whatever reason, maybe I had a first network first service worker that was not cleared, because I changed it to be network first. So hopefully you won't run into the struggle. OK. So let me import an image here. Let's see if this works. Yes, sure. Chrome may access my images. Let's use the camera. And no, it's disconnecting. Let's try again. I'm going to allow the camera. Yes, sure. And now you can see my setup here. So I have this amazing setup. Let's talk to it. That's fine. And we load it onto the canvas. So now you have to behind the scenes. I can add my stuff to it, my drawing, what not. And I can now share this as before. YG mail. And oh, it's opening my other message. Yeah. So I guess it doesn't really because I haven't discarded it. Oops. Let's try again. I could share this now. Yeah. So I could send this now to my parents and tell them, look, I'm on YouTube and everything is live. And I have this amazing setup. Anyway, let's not go there. And let's not go there either. I want to go here. So you can see it's working. It's doing its job. I can import an image. OK. But if you have import, you probably also want to get export. So let's do the same thing. Oh, that's some ugly line breaking here. Anyway, so what you will do is we will create an anchor element with a download attribute and do this in export image legacy. So let me just copy the whole thing. I will talk you through it, what it does in a sec. We will find the right file. Go here. And yeah, that's it. So we use the export button. We use the to blob utility function. I will glance over this now because it's the same all the time. We have the style display block and we have the event listener and dive straight into the function here, which is export image. And you can see we create an anchor element. We set the download attribute to focal greeting.png. We set the href to an object URL that will create from the blob that we get from the utility function here to blob. So this will create a blob URL. We add an event listener that listens on click. So again, it's as weird as it sounds. We need to same here. It's not needed. We need to set a time out to revoke the blob URL in order not to create any memory leaks. I'm doing this 30 seconds after the click has happened. And then finally, we have this click on the thing that isn't even in the DOM that is simulated. And it's using a time out because on some browsers, apparently it's not really working if you do it directly. So this super strange set time out with a number of zero milliseconds. It's stupid, but it's a legacy thing. So let's click a check if it actually is working. So let's do this. And now we get the button. I'm really happy this is working now. So let me make my quick drawing. And now I export this. And you can see it's downloading the file. And if I open it, you can see it's the amazing Fugu greeting card that I've just created. It has this one little lineup here. And yeah, it's an image. It's something that is now in the file manager on my Android system here. And just because I promised it, let me actually show this then in Safari. OK, it's working in Safari now as well, which is great. OK, let's make a quick drawing, export it. Yes, that's fine. I will allow this application to download. And you can see it has downloaded my drawing right here. So it's a download. It's not really a file safe. But it's sort of conceptually the same. The problem is if I modify this painting now and I export again, it's just doing another download. And you can see already where this is headed. This was Fugu greeting. This is Fugu greeting 2. It's super annoying. I'm filling up my downloads folder with essentially the different iterations of my drawing here. So yeah, it's not great, but it's working. So let's go back here. I've shown it to you on the Android device on Safari. I think the iPhone would do the same. Service worker debugging on an iPhone is even more annoying than doing it on Safari. So I refrain from doing it just quickly testing if it refreshes, which it should. On the iPhone, we're still stuck. I would have to manually go into the console and kick out the old service worker. It's annoying, but yeah, you've seen it on Safari on desktop. Okay, so if there is something legacy, there must be a better way. And let me show you the better way, which is by using the file system access API. For this, we will do something weird because I've on purpose broken my feature detection. So in my script file, you can see I've inserted emojis here into my feature detection so that even on my browser that would support the file system access API, I break the feature detection. So the first thing we need to do is go into a script and unbreak feature detection. And we do this by removing the emojis here. So now the feature detection should kick in. If show open file picker is in window, then we will load the new methods, import image, export image, and in all other cases. So on browsers that don't support it, we will load the legacy methods. So you can see here that's relatively clever because it's also using promise.all. So it's loading them in parallel and not serially. And with that, we can fill our import image function with content. So it should look like this now. I've unbroken feature detection. So let's dive into the import image function. I go back to the application. I paste it in here. And as before, this is just attaching the event listener, making sure the button is visible. And I then have this function here, which is import image. What you can see here is I have window show open file picker, which gives me back an array of file handles. I just want the first one in this case. So I do this little destruction operation here where I just get the first element. And everything else is then just making sure as before, what kind of files I want to accept. So you can see I'm accepting description image files. And then here you can see I'm using the mind type image slash star, so everything. And then as before, I'm also mentioning some extensions because not all operating systems support the filtering with the mind type or with the extension. So we always need to do both. And the next step is from the handle that we get from this operation, we will get the actual file and return it to the application, which will then just as before use the draw block utility function to draw the thing that we get back from the file. As before, I'm wrapping this into try catch because something might always be problematic, not always work. This, by the way, does not work on Android yet. There is a bug open to adding support for Android. But right now this is something that we need to test on desktop. So let's go back to desktop. Reload it here. Let me go back to here. Clear site data somehow so stuck. Service workers. Why is it not clearing? I really dislike the demo gods today. I've cleared everything. Re clearing everything. Cash should be really empty now. Oh, I'm stupid. You can see I'm really nervous because as before I told you, this browser does not support the share API. So there is no but to expect. It was hidden in plain sight. So my import button is right here. I'm a little stupid because I'm a little nervous. Anyway, so let's test this. Let's clear the cameras. Let's import an image. Let's go to downloads. Let's just use one of the existing ones that I created in Safari. And you can see it's not. I think it's using a cover like CSS cover strategy. So you can see it's not the entire thing, but it definitely is the thing that I had before. So that's the number two for reading two. So it's loaded on the canvas and it looks all good and fine. Cool. So this is working the same with the same effect as before. Now we actually, if you do this on Safari and reload the thing, you can see on Safari we're loading import image legacy and export image legacy. If you do the same thing here, you can see hopefully if I give this enough space and do a reload, you can see that we're not MP3 filtered. You can see that we load. Where is it? Should be. Yeah. Here you can see it. Reloading import image MGS. So not the legacy function, but the real thing. Okay. I'm a little late. So let's quickly do the export as well. We go to export. Do the same thing. Find the... Where is it? Export. Paste this in. So let's have a look what export does. Export makes the button visible. That's the event listener. And then we are in the function. So here what we have is not a show open file, save picker, sorry, show open file picker, but we have a show safe file picker. And it gives us back a handle. And this handle has a suggested name. Full greetings.png. We will tell it that this will be a png. We will tell it that it will be an image file. And then what we do is we use the to blob function, the utility function from before. We from the handle get a writable stream that we can then write the blob to. We need to close this because, yeah, it's only physically written once it's closed. And then we wrap the entire thing and try catch. And with that we should have a true file safe option. So let's go back to here. Let's reload. And now the export button does appear. So if I make a drawing now and I export this, you can see it's using the file name. And it's not a download. It's actually just a real safe operation. So I can just save this here. And if I make changes now, I can click export again. Just use this and over save it. So the browser is asking me, do I want to over save it? Actually, if I were to store and not just forget the handle here. So now I'm getting it fresh each time. But if I were not to get it fresh each time, I could actually implement a true save operation that would just save over the same file all the time. So this is kind of the operations that you expect in the normal world. All right. And we had a little bit of a hiccup before because of the service worker because of things just not working as I expected. But nevertheless, we saw a couple of things already. We saw progressive enhancement. We saw how this was working in browsers. It was other than Chrome. It was using a different method, but it was sort of doing the same thing. It was saving and opening files. And there's a lot more that you can test. If you have time today at home, tomorrow, whatever, go through the rest of the code lab. There's a lot of fun things you can do. Let me just quickly show you the finished product, which would be Fugu Greetings. Again, Glitch is waking up. Taking some time. Come on, Glitch. Be faster. Preparing. No. The demo gods are really not on my side today. Glitch, be faster. I think this is not loading. The URL is FuguGreetings. Glitch.me. Let me just create a QR code. You can scan this QR code. Play with it at home. FuguGreetings. Glitch.me is the full thing. We get it now. Let me very, very quickly just show you what you can do. We have a copy function now. You can copy this to the clipboard. I can clear the whole thing and then paste it. You will see it's asking me, is this application allowed to use the clipboard? I say yes. It's copied back in. We have a new wallpaper function. We have a reminder. We have insomnia. We have ephemeral. There's a couple more things that you can do with this. It will look a little different on a different browser just because not all of the APIs are available on all browsers. With that, I need to wrap it up. Thank you very much for watching. I hope you go to step 17 in the end where you will see there's a lot more we have in stock in store with capabilities with the project Fugu. The link here to see what is going on and what we have more for you with that. Thanks again for watching and sorry for the hiccups. It was a lot of fun, but it was also nerve wracking. So enjoy the rest of file. This was a true IO experience. See you next time. Bye bye.