skip to content

Text-To-Play Games with Twilio (Part 2)!

with Nathaniel Okenwa

Building interactive experiences that don’t require people to download an app or be present in-person doesn’t have to be hard — in fact, it can be *fun* Nathaniel Okenwa teaches us how in part two!

Topics

Transcript

Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

Jason Lengstorf: Hello, and oh, no, I missed your video. I lost your video.

Nathaniel Okenwa: This is a brilliant way to start.

Jason Lengstorf: No, we got this. This is going to be great. This is how the sausage is made, everybody.

Nathaniel Okenwa: It's the behind-the-scenes footage. Clip it, quick. (Laughter)

Jason Lengstorf: So hello, everybody. Welcome to another episode of learn With Jason. We're bringing back Nathaniel Okenwa. How are you doing?

Nathaniel Okenwa: Good, thank you. And yourself?

Jason Lengstorf: I'm doing great. I thought that this show happened an hour later than I scheduled it for, so in my head, I thought I had a lot of time. I sat down like, oh, my god, we go live in four minutes.

Nathaniel Okenwa: That's classic. The number of times I've been saved by the meeting notifications just ten minutes before my meetings is ridiculous. The worst one is when I mute them and I'm like, yeah, let me just finish what I'm doing. I'll get ready. Then it's like, oh, wait, it's been three minutes. I'm late, even though I knew it was coming.

Jason Lengstorf: The one that kills me is Google always gives you a ten minute until it starts notification. I get that and go, great. Then I look at the meeting. I'm like, well, I'm not just going to stare at this meeting for ten minutes. Then I go to check Twitter. Then it's been like 15 minutes. Now I'm definitely late for this meeting. Like somebody on the meeting pings me like, you coming? Now I've taken to when I get to the ten-minute mark, I will actually go and set another timer for nine minutes.

Nathaniel Okenwa: Very nice. That's a smart way to do it. Giving the tips to productivity.

Jason Lengstorf: It's the only way I can function. Yeah, Chris, 13 months as a sub. Thank you so much. Hello, hi. How are you? Good to see everyone. So Nathaniel, at this point, this is the third time you've been on the show, right?

Nathaniel Okenwa: Third, yeah.

Jason Lengstorf: Great to have you back. For those meeting you for the first time today, you want to give a background?

Nathaniel Okenwa: Cool. So my name is Nathaniel Okenwa, also known as chatter Box Coder, as you'll find out, because I love to talk and write code. I work for a company called Twilio. I'm a Developer Evangelist at Twilio. What we try to provide is an API and a couple other tools that answer all the communications developers need. All sorts of communications -- emails. You can do that with Twilio and some really cool APIs. We try to make it as straight forward to integrate those things into your apps. And yeah.

Jason Lengstorf: Excellent, yeah. I'm super excited about this. This one is a continuation, right. So we did an episode earlier where -- let me find the actual link to it. I'll drop it in chat. We did an episode where we started building a text-based game. So a game where you could text a vote, and that would -- I think we got it to the point where it was saving.

Nathaniel Okenwa: So what we got is we got the audience, so all the people in the chat were spending texts. We kind of, in our sync document, were able to keep track and sort of document the votes as they went out. Now we're going to try and incorporate that into an overlay of some sort. So people in the chat can actually vote and stuff and watch it change and update live on stream.

Jason Lengstorf: Yes, I'm super excited about this too. I think we'll have to do this a couple ways. There are a few ways that you can do realtime. So what we're looking to do is -- like, this poll isn't mission critical. It doesn't need to be up to the millisecond accurate. Setting up subscriptions is kind of a pain or can be depending on what tools you're using. But what's not a pain is what's called polling, where we can -- and that's going to be confusing that we're running a poll and we're using polling to get the results of the poll. In front end dev, if you want something that's mostly up to date, you can do polling, which is where you call that a function to load the results every ten seconds or one minute or however often and get the latest results. You look like you're going to tell me something.

Nathaniel Okenwa: I was. So that's actually the way I would have done it before, using Twilio Sync. The thing about Twilio Sync is it will send events to all the other clients attached to it and update them in real time. So every time a text comes in, when we get our client hooked up to, it'll say client over here, there's new data, and it will update it. The whole idea is Twilio is in this weird position where a lot of times we build something for a very specific use case and realize we can use this for other things. So it was really built first of all for programmable chat. When you've got the little text boxes and you're chatting with someone on the internet, you need to be able to update every single person's chat the moment one person types something in. But you can use this for all sorts of stuff. What we did was we basically took that technology and opened it up so you can create your own sort of document structure or object notation. You can sort of update the sync, and it goes everywhere as well. So yeah.

Jason Lengstorf: Interesting. Okay. So the thing I thought we were doing is actually not what we're doing at all. That makes it even more exciting because we might be able to do this much faster, which means we can play with next steps, right? Because what I would love to do by the end of this is actually -- and this might be a little ambitious, but I would love to get this poll up on the stream and run some kind of a vote before we shut down today. So with that being said, let's switch over so that we can see what we're working on, which is this screen. While we do that, I'm going to do a quick shoutout to -- what screen am I looking at right now? That's the wrong screen. What are you sharing?

Nathaniel Okenwa: Some sort of heartbeat saying, "I ain't dead." That is very reassuring.

Jason Lengstorf: Oh, no. Guess what I did. I pushed a change that includes a hard-coded background. So everybody hold, please, while I fix this CSS that I pushed today. That is embarrassing. Here we go. Let's go back over here. Let's talk for a minute while I fix my code.

Nathaniel Okenwa: No worries. So I've just started streaming as well. I just started streaming on Twitch. The whole technical mishaps thing, like the number of times -- the worst one is showing credentials. I need to figure out a really good way. One thing, I didn't realize how good you are at doing this, how good you are at not doing this, until I made the same mistakes like four, five times. I remember being at the start of my stream saying, all right, I'm not going to show any credentials today. Halfway through I'll be scrolling down, see the credentials in the corner.

Jason Lengstorf: I will say, that is very hard earned. My ability to not show credentials, which I still do sometimes. If you watch earlier episodes of this show, every single episode I would show somebody my credentials.

Nathaniel Okenwa: Apparently there's a VS code extension which apparently obscures your credentials any time during stream.

Jason Lengstorf: Yes. It's called Cloak, but it only works for .env files. That's kind of the way to make it work. You throw things into a .env.

Nathaniel Okenwa: Still good for me. Especially when I'm doing small projects, I try to use .env files. I think one of the annoying things whenever I'm using someone else's repo is when there's so many things that happen in the background that they didn't document. So having a file where I can see clearly these are all the things you need to have and bring to the table before this project works is always super useful.

Jason Lengstorf: Yes, I'm throwing it in the chat, too. Oh, no. What happened? Oh, Google does this thing where it highlights text now, which is helpful until you try to share a link in Twitch chat, apparently. So that's a clean link. The other has a text fragment. So that's the VS code thing. Once you install this and enable it, any .env files, it will show you the variable name and equals and blanks out everything else. So you can do credentials, like actually add them on screen without giving away your secrets.

Nathaniel Okenwa: I think one of my favorite ones is also when like some sort of developer interface is automatically obscured, and you have to click or interact to show them. I think those are the best ones. Twilio does it on their home page, when it has your account, which anyone can see, which is fine. Then your token is hidden, and you have to click on it and specifically choose to reveal it if you want.

Jason Lengstorf: Nice.

Nathaniel Okenwa: Hey, look, it's me.

Jason Lengstorf: I fixed it. So yeah, I was doing some development. I added some things that I'm pretty excited about. I updated the stampede and also added a new and improved boop drop. So things should function a little better on here. We're cleaning this thing up. What I'm using as the overlay is something called stream blitz, which is a tool I've been building. The hope is I can make this tool available to other streamers because it's got subscriptions for chat and commands and overlays and stuff like that. So I've been doing a lot of work trying to clean that up. As you can see, I left some of the debugging code in as I was doing that. So let's do a quick shoutout to the sponsors. If you want to watch live captions today, you can go to lwj.dev/live and see the show is playing here, chat over here, and live captioning are happening here. The captions are provided by White Coat Captioning. Thank you so much for being here today. Really appreciate it. That captioning is made possible by our sponsors, Netlify, Sanity, Fauna, and Auth0, who all kick in to make this show more accessible. So with that being said, let's swap over. Make sure you follow Nathaniel. Did my boops break? What's going on? Oh, no. My boops.

Nathaniel Okenwa: I remember being buried in boops. There's been a couple of times your audience has drowned.

Jason Lengstorf: Did I break it? What have I done? Hold up. I think I might have broken something else, too. Oh, no. This is going great today.

Nathaniel Okenwa: Jason's like the god of the stream. Quite literally because he has broken physics literally with the boops. Remember when they were coming sideways? It's the weirdest. It's like "Inception," just falling down sideways. So interesting.

Jason Lengstorf: Yeah, I did fix that. I just realized I was tied to a local server instead of the public. The problem with building things for yourself is that you take all these shortcuts that you would never do if you were working on a team. So I've done all of that. Here momentarily, in about 30 seconds, this should be up and live. I'll be able to refresh the page, and we should get our boops back. I also noticed the chat is broken. That should have been my first clue. Let's pull this over and look. Yeah, these are my scenes. I build them on Netlify. So it's just like a website, right, which I think is really fun.

Nathaniel Okenwa: I love Netlify. What streaming software do you use?

Jason Lengstorf: I use stream labs OBS. Although, I've been considering going back to regular OBS. I don't use any of the Stream Labs features.

Nathaniel Okenwa: Fair enough. I've been playing around with OBS' browser. It's actually really cool how you can open it in debugging mode because I was trying to basically build a thing that allowed me to take frame cores on stream, so have someone call a number and me being able to literally answer it and my audience can hear it, I can speak to them, which would have been super awesome. I mean, it will be super awesome when I eventually get it working. So I've been building that on Fridays, which has been pretty cool. That's when I figured out how much actual documentation there was on the OBS browser. That was really nice to see. Every time I'm trying a new project and I don't know if it's possible then I see lots of documentation, that helps me. It's the best. It's the best.

Jason Lengstorf: (Laughter). Excellent. Okay. I fixed it. I can see the chat, and it actually showed up. There we go. So let's make sure this thing actually works. You can flap, you can grow. There it goes. It's actually doing things.

Nathaniel Okenwa: Come on, bring on the boops.

Jason Lengstorf: All right. We should focus because I would love to actually write some code here. So we've talked about sponsors. We've introduced Nathaniel. Now we're going to look at what we did last time. So the app we wrote last time is here. We did this SMS function. This function pulls in Twilio, and then we sync to this service and this document, and we save whatever the vote was and increment the total by one.

Nathaniel Okenwa: Yep. Then we update it. This is the function that's connected to like our Twilio phone number. I don't remember what our phone number actually was, but Jason bought a phone number and basically made it so that if you sent an SMS to it, and I think the answer was yes or no, then it would go and trigger this function. So basically now we have a sync document. That's what they're called. Our sync document holds -- I can't remember what our two options were, but it has a yes and a no vote. Every time someone texts in, that gets updated. Either the yeses go up or the nos go up. So what we need to do is have a client build sort of a web client that will basically listen to that document almost. Well, listen to events that are fired off. We'll sort of update something in a browser based on that.

Jason Lengstorf: So let me pull up the terminal over here. We're going to go into Twilio text-to-vote. So let's open this up, and we can start building. So here is our function that's listening.

Nathaniel Okenwa: Yeah, that's our function that was attached to our text, to our SMS.

Jason Lengstorf: Yes. Or wait, did I just -- Twilio CDN. So we kind of, sort of started doing this. Then I think we ran out of time. So let me get rid of this, and we'll get rid of -- I guess we can just kind of put whatever we want here. So what I would like -- if I'm picturing this kind of in the context of a stream, what we really want to build is just like a basic visualization. So I'm thinking like a horizontal bar chart or something where we could show the options and then show the bar getting longer or, you know, staying the same if you're not voting for it. Then we can just drop that in one of the corners of the stream. So when somebody votes, we can show it. Whenever the poll is active, we can -- what I'd like to show is the active poll and the number to text to vote.

Nathaniel Okenwa: Cool.

Jason Lengstorf: That way we can kind of drop this anywhere, and it'll sit on screen, and it can be something that happens during the whole show, or it can be something that happens in a particular moment where we're like, hey, what should we work on next? And we can do this. That might be something for another time because we're going to need to be able to clear the poll and like set a new one up with different votes and stuff, but I'm happy if we don't do that today. I'm happy if we just focus on getting the visualization of what's there. So with that being said, I'm going to add just a heading so that we know what's on the screen.

Nathaniel Okenwa: Cool.

Jason Lengstorf: Then we're going to have --

Nathaniel Okenwa: Some sort of like text here, text this number.

Jason Lengstorf: So let's call this -- we'll give this an ID of, like -- well, let's give it a class. No need to use an ID. We'll call it a poll. Then we'll have -- this is going to be a to-do, if I can -- where's my hyphen? There it is. I've got to turn my keyboard because I'm apparently twisted sideways here. Then we can add -- we'll use an H3 and say text. What was our Twilio number? I'm going to have to look this up. Let's go to Twilio.com. We'll get logged in. This one, I think. So we'll pull these off screen. Is it going to cooperate with me? Here we go. All right. Two factor, good.

Nathaniel Okenwa: Two factor is always the best. Oh, wait. I think Ximena might have posted it. But I'd still double check.

Jason Lengstorf: Oh, yeah. Thank you for that.

Nathaniel Okenwa: I feel like I completely butchered that name.

Jason Lengstorf: I believe it's --

Nathaniel Okenwa: Oh, that makes sense.

Jason Lengstorf: Verify it here. In my most hacker voice, we're in. Now we have active numbers. Here is our active number. So that is the one that Ximena sent us. Now we can just drop this in here. We can say text to vote.

Nathaniel Okenwa: Cool.

Jason Lengstorf: So this is our basic setup. I'm going -- actually, I'll use the VS code terminal for this so we're all in the same place. Let's run ntl dev. (Laughter). Ximena has it saved on her phone. That's great. Now we have local host 8888. All right. So this is the basic structure of our poll. We want to get the polling data kind of stuck up above this text notice here.

Nathaniel Okenwa: Cool.

Jason Lengstorf: And what I have included thus far is just the sync SDK.

Nathaniel Okenwa: Yeah. Now, we're going to need to set something else up, one more thing before this will work. So what happens is for our SDK to work, it needs to have some form of authentication. It's not going to necessarily have the account and token because those are permanent. So what Twilio has is we have access tokens. We use them with video. It's actually the same one. They're basically temporary credentials we can create and hand over to our client so that our client can subscribe to our sync document. So we're going to need to create a second function, kind of like our SMS, but this function is going to use -- it's going to make a core to the Twilio API to basically say, can I have a new access token that connects -- that gives a client permission to listen to this sync document.

Jason Lengstorf: So my headphones broke this morning. As a result, I'm wearing Marissa's headphones. If you've ever seen a picture of my standing next to Marissa, she's like under five foot tall and has this tiny, cute little head. And I am not tiny or cute. So these headphones are for her ear, and they will not stay in.

Nathaniel Okenwa: Is that like one of those ones where they have the different air bud sizes, right? It's just not compatible.

Jason Lengstorf: It's not. It feels like I'm just like precariously hanging it off the side of my ear.

Nathaniel Okenwa: It's like you've got the small headphone jack and you're plugging it into the bigger aux.

Jason Lengstorf: (Laughter). Yeah, and as you can imagine, it is not going well over here. I don't know if you've seen me struggling with these headphones. Okay. So yeah, they absolutely are, Tony. I figured out my headphones were broken one minute after we were supposed to be live. (Laughter) so I didn't have time to actually fix this. We're laying the track as we fly across it.

Nathaniel Okenwa: I was able to get a whole rap off. Like, wow, his headphones weren't working.

Jason Lengstorf: Oh, my god. So quick aside. When Nathaniel joined the call, I couldn't hear him, but I could see that audio was coming through OBS. So I had a hunch it was my headphones. As I was testing that, he said he would just keep talking while I looked for headphones. So I ran, got some headphones, came back, and plugged in. As I'm doing that, he's doing, flawlessly, Maui's entire rap verse from "You're Welcome" in "Moana." If you haven't heard it, it's one of the finest pieces of musical theater that's happened this millennium.

Nathaniel Okenwa: It is beautiful. Like, you know what, Lin Manuel Miranda, his raps. But like in musicals, especially growing up in different worlds. I had rap and hip-hop, and I had musicals and animation in another world. Having them together, woo.

Jason Lengstorf: Is it on YouTube? Probably.

Nathaniel Okenwa: The actual "Moana" song? Definitely.

Jason Lengstorf: Let's find it. Here it is. Okay. Let me stop this immediately before I get a takedown. So this is -- we're going to get nothing done today.

Nathaniel Okenwa: I'm going to stop. I'm going to behave. This is what happened last time. This is why we're on part two. I talk too much. So now what we need to do is we need to create a function that calls the API, gets us an access token.

Jason Lengstorf: Is this a serverless function or client side?

Nathaniel Okenwa: It can be a serverless function. Yeah, it is a serverless function. I thought you were going to say -- yeah, it's a serverless function. I don't know where my brain went.

Jason Lengstorf: So we've got the basics of a serverless function here. Boy, I cannot type today. Oh, my goodness. I'm going to get there. Just everybody bear with me. And we're going to return something. All right. I'm ready. What do we need here?

Nathaniel Okenwa: So we're going to require Twilio.

Jason Lengstorf: We have that installed already, right?

Nathaniel Okenwa: I believe so, because we used it in SMS.

Jason Lengstorf: So I'm just going to copy this out because I imagine that we need it the same way, right?

Nathaniel Okenwa: Yep.

Jason Lengstorf: Okay.

Nathaniel Okenwa: We're going to need to do one more thing. We're going to need to generate an API key in secret over on Twilio's website. API key in secret is basically -- so if your account token is for your whole account, this allows you to authentication keys for a project. That way it's easier to revoke access to certain things.

Jason Lengstorf: Okay.

Nathaniel Okenwa: It's almost like you've done this before. You know exactly where it is.

Jason Lengstorf: I'm not going to lie, I was 100% guessing.

Nathaniel Okenwa: You like went to the right place straightaway.

Jason Lengstorf: So this is -- what was this? Twilio poll. And do I need -- it's a standard key?

Nathaniel Okenwa: Yeah, standard key should be fine.

Jason Lengstorf: And when I click, is this going to show it? Do I need to pull it off screen?

Nathaniel Okenwa: Oh, I always forget. I would say pull it off screen.

Jason Lengstorf: Okay. It did show it. So what I'm going to get over here is I can go to -- I don't have a dot-n file, which means we were doing this through Netlify. I'm going to split this terminal, which I found out you can do.

Nathaniel Okenwa: Whoa!

Jason Lengstorf: That changed my life the other day. (Laughter) So then I can go into my settings, build and deploy environment, and I've got my -- this is a plug-in from Sarah which hides environment variables. So now we can add these. I'm going to call this Twilio API SID. Then we'll call it Twilio API secret.

Nathaniel Okenwa: It's API key and API secret.

Jason Lengstorf: Oh, got it.

Nathaniel Okenwa: If any of you watched last time we had to stream, I just saw Blitz Jackson, the rover, doing what we were doing. Just context, it's a bot that asked the same question. I must have tried to answer it three times in the same stream. Just trolling me. Okay. So now that that's cool, now that's all good --

Jason Lengstorf: We are talking about Twilio. The plug-in that does this is called Netlify masker. It's a Chrome extension. It will allow you to do screen shares with your Netlify settings and actually hide them as you edit your environment variables. Now that this is added, what we'll notice is when I go back to here -- then I'm going to close this one. I think that'll do it. Yes. Then I'm going to stop this. Run Netlify dev. Then check this out. We're local, but it goes to Netlify and grabs these environment variables for us so we don't have to set a local environment variable up. We can just use them here, which means that in here now, we have access to those environment variables.

Nathaniel Okenwa: Netlify is one of my favorite dev tools to work with. They just make it so easy to sort of make the gap between your production environment and your development environment as close together as possible.

Jason Lengstorf: I always feel -- it's funny because I work at Netlify. I feel very much like those Hair Club for Men commercials. I'm not only the President, I'm also a member. (Laughter)

Nathaniel Okenwa: Okay. So what we want to do is we've got Twilio, so we can go -- let's call it access token. So we want to generate an access token. We'll call it access token. We go Twilio, from the library that we have, and then dot-jwt-dot access token.

Jason Lengstorf: Like that?

Nathaniel Okenwa: Yeah. It should be a capital A on the access token.

Jason Lengstorf: Okay.

Nathaniel Okenwa: Perfect. Then if you go down one -- so this is interesting.

Jason Lengstorf: Wait, hold up. Oh, we're going to do it like this?

Nathaniel Okenwa: Exactly, yeah. Now we're going to actually use it. You need a new access token, and it's going to take in your account, your API key, and your API secret.

Jason Lengstorf: So as like three different arguments.

Nathaniel Okenwa: Yep.

Jason Lengstorf: So my account SID first. Then my API key. And then the last one is secret.

Nathaniel Okenwa: Mh-hmm.

Jason Lengstorf: Okay.

Nathaniel Okenwa: And that will generate a token for us. We need to grant this token access to our sync service. So the way we'll do that is we would basically go -- if you go -- let's call it a sync grant. That will be a new. And we're going to actually need to do the same thing we did with access token, const access token.sync grant. So you sync grant will be a new access token.sync grant.

Jason Lengstorf: Oh, I understand. So access token -- is it capital S?

Nathaniel Okenwa: Yes. Cool. And it takes in an object.

Jason Lengstorf: Okay, so we're going to do it here.

Nathaniel Okenwa: We can do it either. Yeah, it takes an object, and object will have one property, which is the service SID.

Jason Lengstorf: Like this?

Nathaniel Okenwa: Yep, just like that. If you go to your SMS.js --

Jason Lengstorf: This one?

Nathaniel Okenwa: Yeah, that one.

Jason Lengstorf: We should probably put this into environment, right?

Nathaniel Okenwa: Oh, yeah. You've even got it under to-do.

Jason Lengstorf: Why don't we just go ahead and do that since it's already open. We'll call this Twilio service SID and save that. Then I can come back out here, and we'll change this one to process.env Twilio service SID. Then we can come over here and do that.

Nathaniel Okenwa: Perfect.

Jason Lengstorf: We'll restart this so we get that new SID. There it is.

Nathaniel Okenwa: What we want to do is we want to add this sync grant to our token. So if you go token.addgrant, and it will be just sync grant.

Jason Lengstorf: Just straight up like that?

Nathaniel Okenwa: Perfect. Now we need to send that over back to our client. So we're going to return that token. You want token to Jwt.

Jason Lengstorf: Excellent. So now we should be able to get a jot. This, I assume, we're going to call from out here.

Nathaniel Okenwa: Perfect.

Jason Lengstorf: We're doing this now, so let's get rid of that note. We'll say our token -- or I guess it'll be an async function, get token. That will get a token, which will be await fetch. We called this Netlify functions, get Twilio token. I don't need to send anything into it, right? Because we already know.

Nathaniel Okenwa: Yep.

Jason Lengstorf: Then when it comes back, we're going to get a response. And that response -- is it just plain text?

Nathaniel Okenwa: It's Jwt, which can be decrypted. It's not plain text.

Jason Lengstorf: But does it get sent back as a string or like a stringified JSON object?

Nathaniel Okenwa: I don't know off the top of my head.

Jason Lengstorf: Let's dump it and see what we get. I assume this is okay since we can get client to share.

Nathaniel Okenwa: Yeah.

Jason Lengstorf: So let's just call this get token.

Nathaniel Okenwa: So this should come up as encrypted. It's possible to decrypt.

Jason Lengstorf: Function invocation failed, which means I did something wrong, and the thing they did wrong is -- not read property access token of undefined. So it doesn't like this.

Nathaniel Okenwa: Oh, one thing. If you scroll up, get rid of the -- no, right to the top. Just require Twilio, get rid of the authentication. So we don't need to auth because we're not using that part of the API. That actually returns a different --

Jason Lengstorf: I understand, I understand. All right. Let's try this one more time. Here we go. There's our token. Then we can take this over to jot.io and take a look at what's inside. This is my favorite thing, by the way. If you see a token, you can just come out here and play with it. This is put together by the Auth0 team. It is great. We can now see that we've got some basic information like when the token expires and things like that, but here's our grant. We've got a grant for sync, and we can see the service SID, which is the one we want to use. Now I'm assuming when we send this to Twilio, it's going to say, hey, your app has been given the appropriate access to get updates from sync.

Nathaniel Okenwa: Yeah, you have permission to read, to write, to listen. What's really cool is the access tokens that we use work across all of Twilio's other products. So you can basically give a client -- you can say, you have access to sync and chat and video, but not phone calls or other things. Which is pretty cool.

Jason Lengstorf: Right. And now, Tony is asking in the chat, is this for each user or each session?

Nathaniel Okenwa: This would be per session, yeah. So every session.

Jason Lengstorf: Yeah, but what we could do, I assume, is if we had somebody logged in, we could tie this -- like this grant could be tied to their permissions.

Nathaniel Okenwa: Yeah, this grant is tied to their permissions. So what you may do in our function, where we sort of gave them access, is we can be more specific about what type of access we want to give them. For example, if you remember earlier, there was a master key. This is a standard key. What master keys can do is some extra powerful things you would want only a very, very, very powerful user to have. Also what's really cool is with the API, you can revoke tokens at any point in time or basically kill someone off -- kill someone's connection off.

Jason Lengstorf: If we think about this from how you could do this with identity, if you had a user logged in and that user had a role of user versus editor versus admin, then you could also do something like check the user roles, if it includes viewer. Then they could view sync. Right? You would add this sync grant. Maybe if they were an editor, they'd have -- they can change the phone number or write the poll or something.

Nathaniel Okenwa: Exactly.

Jason Lengstorf: So those are ways you could control this as well that aren't like -- so it's not directly tied to Twilio anymore. We're getting a token, then we would check our permissions on a different service and add different grants based on that user's roles, if that makes sense. Excellent. I'm into it. So we've got our function. We're able to get a token, which means, I believe, we can start loading our sync data, right?

Nathaniel Okenwa: Yeah. So let's just use the library. Now you have a global Twilio object. So we can go -- which we got from there. So let the sync client be new. We created new. Twilio.sync.client.

Jason Lengstorf: Like that?

Nathaniel Okenwa: Yep. Then it takes in a token. And that's all we need. Now we have a client that's connected, and one thing which I think is always gooded to to make sure it's always connected is to go client.on. So let's create an event. We have an event called connection state changed.

Jason Lengstorf: Is it all lower case?

Jason Lengstorf: Camel case. You're good. That returns a state. Then we can go state. Then we can basically go does the state equal connected, that means it's great. If it doesn't equal connected, we know something went wrong.

Jason Lengstorf: Cool. For now, we can just straight up log this and see what it says. Denied.

Nathaniel Okenwa: Can you click on it?

Jason Lengstorf: I don't think there's anything in it. I can look for more information. Let's see what it says.

Nathaniel Okenwa: We've never had denied. Why were we denied?

Jason Lengstorf: It only comes back with that. So I'm doing something wrong. Let's log just the token.

Nathaniel Okenwa: Oh, wait. We need to stringify the token. My bad.

Jason Lengstorf: So the token is a string.

Nathaniel Okenwa: Oh, is it already?

Jason Lengstorf: Yeah, because I just brought it back as text, right? So it's just a string.

Nathaniel Okenwa: Oh, yeah, you do.

Jason Lengstorf: It doesn't have to be passed as, like, as an object, like a token property?

Nathaniel Okenwa: It shouldn't. It should be just passed as a string. I'm going to quickly get the docs up as well.

Jason Lengstorf: We added in the sync grant. We saw that when we checked the Jwt.

Nathaniel Okenwa: Interesting. I'm just going to quickly get this up. So what's happened, which is a good thing, is we've been denied for some reason, and we need to figure out why. I'm trying to figure out why that -- why we're getting denied. When we get the token, can you -- instead of doing response.text, I'm trying to remember what comes in response. I think it might be a JSON object.

Jason Lengstorf: If it's a stringified JSON object -- that doesn't like that.

Nathaniel Okenwa: That's not working.

Jason Lengstorf: And given that it's coming back as text, that makes me think that, you know, because we were able to decode it here, it seems to be -- it's well formed. So now I'm wondering if maybe --

Nathaniel Okenwa: If you go to where it says client need, Twilio sync.client, do you mind putting a second argument, which is an object. Object has a property log level.

Jason Lengstorf: Like that?

Nathaniel Okenwa: Yep, and just give it info as a string.

Jason Lengstorf: Okay. Let's try it. Rejected by the server. Token does not contain identity.

Nathaniel Okenwa: Ah, there we go. If we go back to where we give our token, one thing we need to do as well is to give our token an identity. So this identity would be however you identify your user. So for example, let's give this just a random string, or we can just give it a string which is called, like, overlay. Because we can't just generate a token without attaching it to a personal identity. That is up to you and your application.

Jason Lengstorf: Okay, I got you.

Nathaniel Okenwa: So that was what I missed. So we need to go token.identity=, and then we can give it anything we want.

Jason Lengstorf: That's what it will eventually be, so we can just call it that. So now overlay ready.

Nathaniel Okenwa: And it's connected. Yes!

Jason Lengstorf: So I might just keep that log level up for the rest of this so we can see what's going on because that's kind of cool. What's next?

Nathaniel Okenwa: So now what we want to do is we would like to -- so we've got the client connection state changed. What we want to do is actually go get our document. So we would like to -- we can do it separate from that client, we can do it underneath. We go client.document, and document takes in a string. That string is whatever the name of our document was or the document SID.

Jason Lengstorf: Is it this one here?

Nathaniel Okenwa: Yep.

Jason Lengstorf: Okay. I can't use environment variables without configuring something goofy, so I'm just going to hard code that.

Nathaniel Okenwa: What you may want to use -- when we created our document, we gave it a unique name, which is probably something that's much more person friendly. I don't remember what it was. We could use the SID. It allows you to use both.

Jason Lengstorf: Okay. So let me pull this back over. I'm going to go to sync, which was in here somewhere.

Nathaniel Okenwa: If you go into circle dot, dot, dot. There's like so many things. Scroll down. It should be towards the bottom. There we go. You've gone past it.

Jason Lengstorf: Then I'm going to use this so it's up there all the time. Now when I go back out here, there it is. So then I have my services, and this is the unique name.

Nathaniel Okenwa: That's the unique name of your service. We want the unique name of your document.

Jason Lengstorf: Oh, document.

Nathaniel Okenwa: Which won't be there. It will be -- actually, the way we find it is through the CLI, if you remember last time.

Jason Lengstorf: Vaguely.

Nathaniel Okenwa: Should be able to go Twilio -- do you still have the CLI?

Jason Lengstorf: I do.

Nathaniel Okenwa: So Twilio API -- I'm trying to remember. Twilio API code on sync, code on V1:services:documents. And then list. And you need the service SID.

Nathaniel Okenwa: And that was this one.

Nathaniel Okenwa: I think you spelled SID wrong.

Jason Lengstorf: There we go.

Nathaniel Okenwa: LWJ text To Vote. So that's what we called it.

Jason Lengstorf: So then out here, I should be able to use this instead?

Nathaniel Okenwa: Exactly.

Jason Lengstorf: Okay. I'm ready.

Nathaniel Okenwa: .then. That will return your document.

Jason Lengstorf: Do you mind if I unwrap this?

Nathaniel Okenwa: Yeah.

Jason Lengstorf: Hey, thank you beginbot, for the raid. Welcome, everybody. So now we have the document. To catch everybody who just showed up, we are working on a Twilio text-to-vote kind of thing. This is what we built last time, which is we connect to Twilio, and we're able to store a vote. So whenever you text a number, that calls this function. We look for this, and we then store the result of your vote, either a yes or no, into a document on Twilio. What we're doing this time around is we are getting a read token using another serverless function so that we can get those results out and get them on screen, which is what we're doing here. So right now we have a wrapper that we're going to put our results in, and we've loaded the Twilio sync API from the CDN. We load our token from this serverless function that we built over here. Then we use that token -- YAML, YAML, YAML, JSON.

Jason Lengstorf: (Laughter) Yes. So we use that to get a document that we're loading. That's as far as we've gotten thus far. At this point, is this the actual result? I can log this and we'll see the data?

Nathaniel Okenwa: Yeah, so you can go document.value and see the data.

Jason Lengstorf: Like this?

Nathaniel Okenwa: Yep.

Jason Lengstorf: Okay. So let's just log it. Console.log -- we'll do it like that so we can see what comes out. Let's take a look. Here's our value, and it gives us 28 no votes and 13 yes votes.

Nathaniel Okenwa: So that gives you the data when you first open the document. Essentially when the page gets loaded. But when you want to do is you want to listen out for every time that's been updated. So if you go back to your code and just put document.on and it's got an event for updated. So you always have it straightaway. If you put updated, that event has a value. Or you could call it document value.

Jason Lengstorf: Got it, cool. So this should update every time, which then means, I believe, we can build out our function to actually load this thing.

Nathaniel Okenwa: Precisely.

Jason Lengstorf: So I think what I'm going to do for the sake of time, because we've got about 35 minutes left, is I'm going to -- instead of trying to write charting library -- unless you have a charting library you know because I don't know any.

Nathaniel Okenwa: I don't know any that I can use off the top of my head.

Jason Lengstorf: Yeah. So let's maybe not try that. Instead, we will just show a list. So we're going to say -- let's make this an actual unordered list. We'll call it --

Nathaniel Okenwa: Results?

Jason Lengstorf: Result, that's a good one. I'm going to hard code this. We can fix this later. You'll have to look at the source code for how this makes it into the show when we do the live update part. Then we're going to have a yes and a no because those are our two values. So I want a function that will take the document value and then update these two pieces.

Nathaniel Okenwa: Yeah.

Jason Lengstorf: So I think the way that we can do that is we can create a function called update values, or update results. That makes more sense. That'll take a value --

Nathaniel Okenwa: Do you want it to take one or two values?

Jason Lengstorf: Well, I was just going to pass in the document value since we know what this is. It's going to have the no and the yes. Then what I -- actually, what we can do is just say -- no, I don't want to do that because I want the keys. So I'm going to get the values. Then I can do an object.entries of values. That's going to give us an array of arrays. So if I run object. -- let me save this one as a double variable. If I do object.entries of temp 1, what it's going to give us is an array. Then for each array, we get the first key is the actual key of the object, and the second key is the value.

Nathaniel Okenwa: One of the most useful little tricks in java script.

Jason Lengstorf: So, so helpful. Then what I can do is for each, and I'm going to destructure this. I'm going to take the argument and get the key and the value. What I want to do is document.queryselector for the key. No, I need to make it into an actual query selector. So we'll make is the key.

Nathaniel Okenwa: This is really well done.

Jason Lengstorf: Then we'll make the inner text the value. So what I should be able to do now is every time the thing updates, this should just live update our list items. So let's give this a try. The first thing we'll do is we will just call results.

Nathaniel Okenwa: You'll probably want to call it on document.value. Yeah, okay, cool.

Jason Lengstorf: On document.value, right? I called it update results, yes? Then what I can do is do the same thing here on this value. So now, assuming all went well, if you all start texting this phone number here with either yes or no in the response, then we should start to see results.

Nathaniel Okenwa: Drum roll. Oh, there's an error happening down there.

Jason Lengstorf: Uh-oh. Cannot set property inner text of null, which means something is not coming in the way I expected it to. So let's try it. Console.log value. Let's see what happens when we get a new -- oh, does it come in as two pieces? Yes, okay. So what we're actually going to get out of here is we want this. Not set inner text of null. No, you should have worked.

Nathaniel Okenwa: All right. So it's updated already.

Jason Lengstorf: Let's see it update live.

Nathaniel Okenwa: Drum roll. Yes!

Jason Lengstorf: There it goes. Look at it go. So now we have the ability to do a live updating poll. So the next step -- and I guess, actually, that happened faster than I expected, so maybe what we should do is try to get this on a chart. You want to wade into uncharted territory together?

Nathaniel Okenwa: Yes! That was so good. When you paused, I was like, something is coming. What's coming? Then when you said -- All right, I think I need to get in here.

Nathaniel Okenwa: Uncharted.

Jason Lengstorf: Oh, good. Good, good.

Nathaniel Okenwa: That was good.

Jason Lengstorf: Drills it suggesting we use D3. I think D3 is probably a great idea because we can most likely find an example of what we want. So let's look for a straight bar chart. I don't need a fancy one. Is there like a -- is there just the simplest possible bar chart? Because that's really what I'm after. Bar chart. Oh, wait, I want a horizontal bar chart. Horizontal bar chart. That's what we're after, right, is exactly this. Oh, we're using observable. If you're interested in learning more, there's an episode of Learn With Jason with Anjana Vakil. You should check that episode out. It's a very good one. In the meantime, let's figure out how to make this work. So I need to get D3. Then we're going to -- let's see. View box. Then the data will be the data. It just repeats. Is that right? This looks like the background. What's all this? This looks like the -- so I think this part is the labels.

Nathaniel Okenwa: That makes sense.

Jason Lengstorf: And this part is the bars. Then that X axis, Y axis, what are those? I don't think we care about those. Maybe we just go for the simpler thing. Oh, there's a lot in here. Okay. Bar height. Woof. All right. So I need to get D3. Can I get D3 from a CDN, though? Maybe I'll just get it from UNPKG. I want D3.

Nathaniel Okenwa: Oh, that's nice.

Jason Lengstorf: So we can get D3. Oh, Jackie, thank you so much, for gifting us those subs. That is wonderful. I really, really appreciate that. Welcome to the -- what do we call everyone who watches this show? I feel like everybody's got a name, right? Like a group name. I don't have that. Lifers. (Laughter) Whatever you are. The Haxxors. No, I don't think that's right. That's not the message we want to send. The Boop Crew.

Nathaniel Okenwa: Oh, Boopers.

Jason Lengstorf: Welcome to the Boop Crew. You can now spam those boops. If you are ambitious, you can actually bury us in boops.

Nathaniel Okenwa: I am right at the top. I am all down for you. I might do a bit of booping myself.

Jason Lengstorf: Wait, no, hold on. You can't work against me.

Nathaniel Okenwa: You're going to get buried first.

Jason Lengstorf: That is uncool. Okay.

Nathaniel Okenwa: Come on, come on.

Jason Lengstorf: All right. So I'm going to see if I can -- (Laughter)

Nathaniel Okenwa: This is so much fun.

Jason Lengstorf: I'm just doing work over here. I'm just going to get the one line up at the top that I have. There we go. All right.

Nathaniel Okenwa: This is the closest I'm going to get to swimming in lockdown. I need to make the most of it.

Jason Lengstorf: (Laughter) you Know we need to just get the "Titanic" sound track on right now.

Jason Lengstorf: We're lagging it out.

Nathaniel Okenwa: Dang.

Jason Lengstorf: We have overtaxed the system.

Nathaniel Okenwa: All right, folks. Thank you so much. Let's actually finish a project. This is actually the closest I've been on Jason's show to finishing a project.

Jason Lengstorf: We're going to do it. Today it's going to happen. All right. Here we go. I think I need adult supervision here.

Jason Lengstorf: Now that I have D3 -- let's see. That's the D3 object. I don't particularly care about most of this. So what I really want -- all I really want to do is create a chart. That chart I want to attach -- let's see, D3 scale linear. How does it actually attach to the document? Because that's the part I care about. Somewhere in here. We have data, so we can figure out that part, I hope. And this data is just what we would expect. It's name and value. We can parse that out if we need to. Then where does this attach? It creates an SVG. But then it never -- I don't see where this gets attached to. Does anybody see it? Or is it just like this is the chart and that's how observable works?

Nathaniel Okenwa: Just add the node to the document, Drills suggested.

Jason Lengstorf: All right. Let's do it. This is the only downside to observable. There's a tiny bit of magic, so you can't quite see what's happening. That's the wrong doc. This is the one we want. So let's add -- I'm going to add a div up here. We'll call it chart. Down here we're going to function, update chart. For now, let's just see if we can get some data on there. So we have D3. I want to do a D3 create. So const svg. That is going to have an attribute of view box. We want this to be, I don't know. We'll just code it. It starts at zero, zero. We'll give it an X length, which will be its width. We can make it 350 pixels wide. We'll make it -- it's not very tall. It's just the two things. So then we're going to append. Svg.append a group. And in that group, we want an attribute of fill. That's the color. We'll just use steel blue because that's the one they used.

Nathaniel Okenwa: Is that the name of that -- no, what's the name of the pose?

Jason Lengstorf: Blue steel.

Nathaniel Okenwa: This reminds me of that being blue steel. I wonder if that was deliberate.

Jason Lengstorf: That would actually be really funny. I believe this color name predates Zoolander. So our data is an array. I'm going to hard code this for a second just to make sure we have it working before I start doing things. We'll say the value is 30. We'll have another one of name no and a value of, we'll say, ten. This is a positive show. More yeses than noes. Then once we have that, we've got our data. Then we're going to join the rectangles. They're going to have some values here so that X -- there's a function called X that we don't have. Where is that? The X function is f (n). Oh, that's just a function. (Laughter)

Nathaniel Okenwa: See, this is the problem with maths. I generally read that like maths f of n. I was like, oh, no. I haven't looked at equations directly in a long time.

Jason Lengstorf: Yeah, yeah. Okay. So this one is -- so I remember this. D3 scale linear. We want the biggest value to fill the full width, right? So if there's only one vote, that should be a full-width bar for one vote. As we get more, it should stay there.

Nathaniel Okenwa: That's really smart.

Jason Lengstorf: Yeah, D3 does a lot of cool stuff for you. This is what Andra and I figured out. So what we're doing here is we say we want a linear scale, and the domain is like the min/max. Minimum of zero. The max is whatever the highest value from the data is. Then the range -- I don't know. Let's see. Margin.left and width. Oh, that's the actual size. So we can hard code that. So up here, that got set as attribute X. Okay. I understand. So what we'll do here is we'll say x=, and we'll do this. I'm just going to set these to zero and 350. So basically, the minimum starting point is 0. The minimum ending point -- so we get our data, which apparently I need to put our data up here because we're going to need it more than once. Okay. Then that'll give us our scale. So we put the data in here. Then I need to add an attribute. No. Yes. Attribute of X. That was the value, right?

Nathaniel Okenwa: I think so. Yep.

Jason Lengstorf: X is always 0. Okay. Oh, oh!

Nathaniel Okenwa: X always starts at 0.

Jason Lengstorf: Yeah, okay, so the X is 0.

Nathaniel Okenwa: Then you've got width as d.value.

Jason Lengstorf: So we need another function for Y.

Nathaniel Okenwa: I think that's because if you have multiple options, they move by how many options you have.

Jason Lengstorf: Yep. Yep, yep, yep. So that'll be that. Let me get this one set up. So that scale band, as we add options, it'll bump down by that number. Our margin top is 0. Height is 100. And the data.length, yep, that'll be fine. We'll just hope that works out for us. Then that was --

Nathaniel Okenwa: Yi.

Jason Lengstorf: There's a whole bunch going on here. I'm just going to copy/paste that one. So we're taking the data item, its index, then we're setting the Y value to be its index. So really, this could be like an unused value. And I put a comma there, which I shouldn't have done. So then we're going to set its width to be -- you know what I'm going to do? I'm just going to copy the rest.

Nathaniel Okenwa: I think you should be able to.

Jason Lengstorf: I was going to do it so we could talk there what they are and why they exist, but I think we can do that without me having to type it. So we get the value. This time we're setting the width to be the far right value minus the starting value, which will give us whatever the largest value is. That'll be the largest value. The y.bandwidth I really hope is just coming out of here automatically, not something that we have to do.

Nathaniel Okenwa: I don't see a .bandwidth.

Jason Lengstorf: These are all labels, I think. We're going to get svg.node. What I'm going to do is document.queryselector.

Nathaniel Okenwa: I think you said results. Oh, chart, yeah.

Jason Lengstorf: And we're going to append child svg.node. All right. Let's try that and see if we can hard code a chart. I cannot. Because I didn't call it, is why. So here's --

Nathaniel Okenwa: It would be where we have update chart, yeah.

Jason Lengstorf: So why don't we for now throw it in update results.

Nathaniel Okenwa: But you've go the to parse it. Oh, there we go.

Jason Lengstorf: So the next thing, then, is we want to make our data come from here. That means I need to --

Nathaniel Okenwa: Don't you just want to --

Jason Lengstorf: You know, I might -- honestly, rather than trying to rewrite the D3 code, I might just change this object really quick. So let's make data into object.entries, value.map. What we're going to get out is a key and a value. We can just send back a name of key and a value. In fact, I can make this even simpler if I do it just like that. Now we've got our data. I can pass in the data. I can delete this, and theoretically speaking, what will happen is this will match. Screwed something up. Okay. So let's all vote no for a minute, and let's see if we can make this thing work. Oh, okay. So we've got to replace the node. Is there a way to just do that, though? That seems like something that should just happen in D3, which means I attached it wrong. But I don't know how to do that differently. Does anybody know D3? I do 100% want labels. However, I'm not going to do that right now because we have like 15 minutes left. So let's get this chart rendering properly first. Then we can start playing with the specifics of it. I've seen this done before. I think there's a way to do an update as opposed to re-creating it entirely. But I don't remember what that is. I wonder if I did it on my -- uh-oh. I should have one example I might be able to pull from. We've got some D3 stuff. Chart.update, ah.

Nathaniel Okenwa: So I guess we want to build it the first time and update it.

Jason Lengstorf: Okay. So then what we can do instead of this is we can change this one to like build chart. Return.svg.node. Right? And then --

Nathaniel Okenwa: But you're going to need to build chart when we get the document back.

Jason Lengstorf: So we'll get a chart that will be build chart, document.value. Then on this, it would be chart.update. Oh, crap. We're going to have to refactor this a little bit because we do the data up here. Maybe what I can do instead is build the chart.

Nathaniel Okenwa: Just put update results. Well, update results, rather than build chart, it updates chart. So if you fix line 32, you've got update chart.

Jason Lengstorf: Right. So we just need to -- well, okay. So this gives us the chart. Then what I need to do is update the chart. Just like this.

Nathaniel Okenwa: And do it in update results. So where you've got your update results function.

Jason Lengstorf: Yeah, so I just need to pass in the chart.

Nathaniel Okenwa: Oh, yeah.

Jason Lengstorf: Then I would just do like chart update data. Is that right?

Nathaniel Okenwa: Yeah, I just think line 33 doesn't cause any function.

Jason Lengstorf: It doesn't right now, yeah. This one doesn't exist. So what we're doing here is we just want to get -- so we'll build the chart the first time, when it gets created. Then we're going to pass that chart in here. That means I need to parse the data differently, which means, unfortunately, I need to do something like this. Ugh. This always -- it starts so easy. Then you get your values. Then we're going to return. Here we have to parse data values. Then down here, we can parse data document.value. Please work on the first try.

Nathaniel Okenwa: Dun-dun-dun.

Jason Lengstorf: It didn't work on the first time. Index results 185. That's not the right thing. Chart.update data. Where did I call update results?

Nathaniel Okenwa: Wait, wait, wait. You can't update results until the chart -- the chart doesn't exist yet.

Jason Lengstorf: Okay. So that started working. I broke something. So we're almost there. But we're missing -- so update results. We have now crossed the line into like -- let's see. Now I'm just being disrespectful.

Nathaniel Okenwa: Why are we calling it there?

Jason Lengstorf: Because -- and that's not right either. Come on. So what we've done is we kind of -- let's just walk through this. I've lost myself now. We get the token. Once we get the token, we get the document. Once we get the document, we update the results, which is our list items.

Nathaniel Okenwa: Okay, yeah.

Jason Lengstorf: What's up? What did I do?

Nathaniel Okenwa: If you go to your update results function, you try to interact with the chart in it, but the chart doesn't exist yet.

Jason Lengstorf: Right. So what I was thinking of doing is just having that first run, skip it entirely.

Nathaniel Okenwa: Fair enough.

Jason Lengstorf: But that apparently isn't working. Just move it down one line. Do you think that'll work?

Nathaniel Okenwa: That's what I was thinking, but --

Jason Lengstorf: Let's try it. Okay. So let's try that and see if that does anything. Chart.update is not a function. So I'm doing something wrong. I'm wondering if it's this and I need to -- I'm looking, I'm looking.

Nathaniel Okenwa: So I just replaced --

Jason Lengstorf: Well, no, we don't want to do that. That's super heavy.

Nathaniel Okenwa: Apparently Mike wrote an update function.

Jason Lengstorf: He wrote an update function?

Nathaniel Okenwa: Let's see if we can find it.

Jason Lengstorf: Image labels.

Nathaniel Okenwa: Yeah, he created an update function, which has transitions.

Jason Lengstorf: Oh, no.

Nathaniel Okenwa: It's actually further down. I think it's further down, if I'm looking at the same page as you. Oh, on twitter-emoji-race.

Jason Lengstorf: But this is a different code. We got this running for something else. This is like our simplified version. I really don't want to do transitions, though. I don't want to deal with that at all.

Nathaniel Okenwa: I guess we can just replace it.

Jason Lengstorf: We're just going to get rid of this thing. Let's back it out. What we will do is update results, value. Svg node, let's keep it rolling. Where were we?

Nathaniel Okenwa: You need to query select. What we want to do is rather than append, can we just --

Jason Lengstorf: We can set the inner HTML, I think. That should delete everything. Let's try it. Update chart is not defined. I missed a step. Update chart.

Nathaniel Okenwa: That was the line that we commented on.

Jason Lengstorf: Okay. So instead of this, what we'll have to do is say charts. Then we can say like chart.removechild. Then that would be chart.queryselector svg. Then we can chart.appendchild, and it'll be svg.node.

Nathaniel Okenwa: Drum roll.

Jason Lengstorf: Do the thing I want you to do. Why? Ugh. Parameter one was not of type node. Okay. So then old chart = -- okay this is the one. It's going to happen this time. Everybody vote. Vote, vote, vote.

Nathaniel Okenwa: Lots of nos.

Jason Lengstorf: The number is on the screen.

Nathaniel Okenwa: Oh, did you see it move? It moved, it moved! It lives! It's alive. Drills. (Laughter)

Jason Lengstorf: Okay. So we can see it's moving. The chart is doing what we want. Look at that, y'all. Look at it go. Yeah, so this is kind of why I wasn't going to do charting. This is super hacky code. We're not doing D3 the right way here. But we did manage to get it working. This also shows me I need to bring a D3 expert on to teach us how to do this right so we can do some D3 with a little bit of adult supervision. So if you have a D3 expert you love, send them my way. Let's see if we can get a show put together.

Nathaniel Okenwa: Ladies and gentlemen, I just want to pause and just celebrate the fact we actually finished a project. This is the best time and we actually finished, like completely, and actually got what we wanted.

Jason Lengstorf: Oh, man, yeah. Oh, I should bring Shirley back. I should absolutely bring Shirley back. That would be awesome. I'm going to talk to her about that. That sounds like a lot of fun. So yeah, I think this is -- I'm very excited. We managed to get a thing done. Just virtual high five over here. I'm not going to lie, I had very little faith in you and I together to complete a project. (Laughter)

Nathaniel Okenwa: I'm not going to lie, I genuinely sat down like, Nathaniel, we need to try and stay focused today because last time I was just all over the place. I was just genuinely having too good a time.

Jason Lengstorf: The show is really fun. The chat is super fun. Chat, as always, thank you for hanging out with us today. It was really fun. Nathaniel, where should I send people? They're not going to be able to see it, but where should I send people if they want to learn more, if they want next steps?

Nathaniel Okenwa: Just head over to the Twilio docs. We have a blog post where somebody built a realtime sort of data visualization using node. I will drop that link into the chat.

Jason Lengstorf: Oh, wait. I think I found it. Implementing realtime data visualization.

Nathaniel Okenwa: That's it. That is really useful because it takes you through all the steps. The one thing is it wasn't serverless. So they used like a node server to handle a lot of stuff. We've kind of done it serverless. But that would be a great place to get started. They can start building all sorts of amazing things. And there's so many other projects on the blog. Just Google something, and you'll probably see it. Search in the blog and you can probably find it.

Jason Lengstorf: And don't forget, go follow Nathaniel on Twitter. Chatterbox Coder.

Nathaniel Okenwa: And Twitch.

Jason Lengstorf: Absolutely. You've got the Baby Developer Show.

Nathaniel Okenwa: I don't do it this week, but I'll be coming back with more guests.

Jason Lengstorf: Oh, I built a new thing. Let's see if it works.

Nathaniel Okenwa: Waited, I'm not hosting you. Damn it. I was supposed to be hosting you.

Jason Lengstorf: Come on, man. That hurts me.

Nathaniel Okenwa: I'm sorry.

Jason Lengstorf: Okay. So now we are -- I think we're at the end of the show. One more quick shoutout to the sponsors. Thank you so much to White Coat Captioning for doing the live captioning today. It means a lot to us. Thank you to Netlify, Sanity and Fauna for pitching in to make the show more accessible. If you want to see more of the show, we have so much fun stuff coming up. Please go check out the schedule. So later this week on Thursday, we've got Brian Robinson coming on. We're going to learn how to build a plug-in. Next week we have Nat Alison. She led the react docs organization. She's also done a lot of work for Gatsby. One of the leading experts on it. If you've ever been interested in making a site multilingual, this is a show for you. Then we have Stefan Judis coming on.

Nathaniel Okenwa: One of my very good friends. Looking forward to that one.

Jason Lengstorf: It's going to be super fun. So much good stuff coming on. It's going to be an absolutely wonderful time with more episodes coming up all the time. If you want to add the show to your calendar so that you automatically see new episodes, visit that link right there. With that, we are done. Nathaniel, thank you so, so, so much for coming on today. It was a blast. Chat, stay tuned. We are going to raid. We will see you next time.

Nathaniel Okenwa: Peace.