字幕列表 影片播放 列印英文字幕 BRAD GREEN: Good morning, everyone. I'm Brad Green. I'm the manager of the Angular project here at Google. And I'm joined by John and Darrel from TiKL. And they're going to talk to us about building Chrome Apps with AngularJS. And I'm sorry, you guys. For this holiday addition, John's actually wearing some spectacular Christmas pants, but you can't see it on camera. Maybe at the end, we can get a shot of that. So hey guys, tell us a little bit about what TiKL does before we get into the presentation about building apps. DARREL SUMI: Sure. JOHN FEIG: Sure, yeah. We're mainly a mobile app company. We have several communications apps. We have three on Android. And wait-- no, we have four on Android. DARREL SUMI: Four on Android, two on iOS. JOHN FEIG: Two on iOS. So we've got TiKL, which is like a walkie-talkie. It works just like a walkie-talkie. It's pretty fun. We have Talkray, which is our main focus these days, which is a unified communications app. You do texting, calling-- it does groups up to 25 people. KeeChat, which is what we're going to be talking about today, which is group texting with usernames. And then we have a new app out that we launched a couple weeks ago, which is a PTT recorded app, so you can send voice mails back and forth. It's called VoMessenger. BRAD GREEN: So you guys have been primarily doing Java and-- DARREL SUMI: Yes. BRAD GREEN: --maybe Objective-C development. And you've just recently come into the web way of developing things. And so how long have you been doing this? DARREL SUMI: Since we started as a company, it's probably-- I don't know, five months or so where we've been kind of jumped into this. JOHN FEIG: Yeah, we started over the summer doing the web stuff. DARREL SUMI: Yeah. I mean, both of us have obviously dabbled in a lot of web stuff before. But this is our first real, larger project. JOHN FEIG: Yeah, right. Definitely the first big project. And the company, as a company, doesn't really have much experience with web things. TiKL was started in 2010. They had the TiKL app for a while. And then we jumped into Talkray. And that was iOS and Android. And we've basically been focused on mobile exclusively until this product. BRAD GREEN: OK, very cool. All right. Well, then, let's jump into the presentation. And tell us about what you guys have learned about the environment. JOHN FEIG: Great. So we can start with the slides. So this is KeeChat. DARREL SUMI: Yeah. So these are just a couple screenshots of what the mobile app looks like. So basically, like what we said, it's a unified messaging app. So you can send regular texting images to anyone within our system. JOHN FEIG: And we recently built a Chrome App out of it. We started with a web version of this. And then we sort of transitioned to a Chrome App. And we'll talk a little bit about why we picked Angular and Chrome. DARREL SUMI: Yeah. Angular really fit with, like we said, our background in Java. We wanted the look and feel of the entire thing to be very much like an app, less a traditional web style of doing things. And everything, like the MVC architecture of Angular, really fit into the pre-existing model that we had built everything around on the mobile side. JOHN FEIG: Right. Yeah. It was very comfortable for us to look at because it just felt a lot like being able to build an app. And you can see here the structure that we chose for KeeChat, which is basically Angular's suggested structure for especially large projects. So we broke it out into controllers and services and models are the main components here, trying to pick apart with the MVC architecture and do this properly. And it was really nice to have services. For us, coming from Java, where they're sort of like singletons. They work in a similar way. There's a lot of nice stuff. And organizing your code like this really helps in larger projects. DARREL SUMI: Yes. So I guess one of the reasons, or one of the things, is why did we chose to do a Chrome App off the Angular app. Why not just keep it a website kind of thing? So obviously, the biggest one is the Chrome Web Store. That's a great way to get distribution for our app. The other one is that, like I said, we really wanted to make it feel like an app. We wanted users who came from our mobile to be able to experience the same kind of experience that we provided on their computers. So one of the big things is notifications. That makes it feel like a much more rich experience. And obviously, raw sockets were pretty important for us because we're a communication app. JOHN FEIG: Yeah. We had played around with a couple different socket clients and decided that for us to get out the door, raw sockets was going to be the easiest way for us to do it. So that was something that was available in Chrome Apps that really you can't do in HTML5. There's WebSockets, but it's not the same thing. Java can use WebSockets in Java 7, but we're not running Java 7 on the server. DARREL SUMI: So some of the tools we use are-- we'll just briefly go over this-- Yeoman, Grunt, Bower, and Karma. Karma's our testing suite. The other three really help us organize our code a lot, bring in all the dependencies, keep everything clean. JOHN FEIG: Yeah. It's pretty standard stuff. And it was really nice with Grunt, actually. You can run Grunt server. And it has listeners for things. And so it was really funny. Because when we were really running through this stuff, it was faster developing on the web than it was for Android. Because you'd change something, and then your browser would get updated right away. I don't know. It was kind of funny. DARREL SUMI: Yeah, we're just used to waiting, compiling, and doing all this stuff for Android development. JOHN FEIG: Yeah. And we didn't even have to hit Refresh on the browser window here. So it's kind of nice. DARREL SUMI: So one of the main concerns, when you're thinking about building a Chrome App, is what are all the dependencies in your project that you're going to need to re-work around the Chrome Apps. And one of them, the big one, was RequireJS. So we used Require a lot. It's a great modularization tool that you can use-- or library. A lot of people use it. It's a very common thing. But they kind of conflict a little bit with the module system in Angular. They don't necessarily directly-- they're not against each other. In fact, that they're orthogonal ideas of the way they treat modularization. But they are going to step over each other a little bit, so you've got to understand if you really need it and how to deal with it. JOHN FEIG: For us, we had all these third-party libraries that we just had to use. And some of those required Require. So it's like, OK, we're stuck using Require. It's kind of painful to get implemented properly with Angular. If you can avoid it, it's probably better to avoid it, but-- DARREL SUMI: But it's still a great tool. And if you need it, there are several resources online that show examples of how exactly to do it. So it's not-- there's ways around it. JOHN FEIG: And I have read that Angular is supposed to be getting better about trying to integrate with Require. BRAD GREEN: We do want to work on that more. So like you say, these are two orthogonal things. One is about what does my JavaScript class need in order to run. The other is, what order should I load vals. DARREL SUMI: Right. Dependency order and then lazy loading, and things like that. BRAD GREEN: It would be nice if these worked well together. This is some future thing we'd like to get right. JOHN FEIG: So, yeah. I guess now we're just going to dive into what the process of converting a web Angular app to a Chrome App was. DARREL SUMI: So just kind of the quick few basics that every kind of Chrome App needs is that it's going to need a manifest.json file that pretty much describes all the permissions and the app name and things that go into the store. There's going to be a background.js file that describes several entry points into the app, like what happens when you install the app, what happens on launch. And you're going to need here, obviously, your index files, your main index file. But there's a trick to this one is that it sometimes differs from what you normally would have in your Angular project. JOHN FEIG: OK. So we can look at the manifest here for a little demo app that I did. And it's really pretty basic. You've got your name, description. You have the entry point of the JavaScript for your app to find, which is background.js. That's a pretty standard thing within Chrome Apps. And then permissions-- so for example, I need to be able to talk to the ajax.googleapis.com site. And I also need to have storage permissions. And then you also specify your icons. DARREL SUMI: Yeah, pretty straightforward. And like I said, for the background, you can pretty much describe generic things about your Chrome App. So in this case, all we said is the width is going to be 1,200, the height's going to be 700. JOHN FEIG: Yeah. And then your index is chomeindex.html. And you can name it whatever you want. But I think it's easy just to call it Chrome Index. And your regular one's Index. OK. DARREL SUMI: So once you add all these things in, and you think, OK, this is going to-- things are going to look good. And you try running it. Of course, nothing works, right? JOHN FEIG: Yeah. You get a lot of red. DARREL SUMI: Right, right. And so one of the main, first ways to approach this is there's a thing called a Content Security Policy that web browsers implement. It basically restricts what you can do so that third-party external code doesn't maliciously run some hacks on your website and cause all kinds of problems. So this is something that's already out in most web browsers. But then Chrome Apps have a tighter restriction on this Content Security Policy. So the three main things that differ from traditional web are is that it's going to restrict any inline scripting. It's going to restrict any external resources. So if you link to some extra library somewhere else, you can't necessarily just link to that to bring that script in. And it's going to restrict anything that pretty much converts strings into JavaScript. So things that use the eval or new Function methods are not going to work in Chrome Apps. So the second thing that I talked about, the external resources, so the first thing you're going to look at is what third-party libraries you bring in that you might actually reference from a CDN or something. These you can't bring into your app directly, so you're going to have to download these libraries or any styles, or fonts, images, any other kind of static content that you might be able to just bring into your app and have it hosted straight from there. And so you're going to have to modify your index file, obviously, because that's where all these things get referenced. So here's a quick example of how the index that you might have for your regular Angular projects will differ from the Chrome one. So first one, when we reference the app, is going to be a ngCsp. JOHN FEIG: I don't think it highlights. DARREL SUMI: Yeah, it doesn't highlight. There's going to be a ngCsp Angular tag that you're going to put there. What this describes is that it tells Angular to be in a CSP mode, essentially. Angular has a lot of optimizations that use things like the eval and new Function, I believe, basically just to optimize Angular. But for it to run in the Chrome App, you have to restrict those kinds of things. So it has a CSP mode that'll get around that. BRAD GREEN: It's actually only a few little things. You don't lose very much at all. DARREL SUMI: OK. Well, yeah. Great. BRAD GREEN: It's one small optimization. But yes, you do lose it. DARREL SUMI: Right, right. And so you do need to specify that. And obviously, now that you downloaded-- say, in this example it's a Socket.IO. We were referencing it from the CDN. And so now we just have to bring it into your own project and reference it that way. Pretty straightforward. JOHN FEIG: Yep. And then you try running it again, and it's still broken. So-- DARREL SUMI: OK, so here's a little demo-- let me switch. Here's a little demo of a socket app that I made. So basically, all this is-- it's just a small little test demo that connects to a Node.js server. The Node.js server starts counting every one, two, three-- every second. And it would reply back on Socket.IO to the client and display what the current count is, essentially. Really simple-- that's all that's happening. And so here, you'll see that the counter's incrementing. But at this point, the Angular project doesn't know anything about what the count was previously. It doesn't know anything about who its friends are. These are all initialized and given to us by the Node server. JOHN FEIG: Yeah. You don't want to dive into-- OK. DARREL SUMI: Whoa. Whoops. There we go. OK. So one of the key components to Angular as well as Chrome Apps is that the way you think about web servers traditionally is switched to be using an API server. So the web server you need to treat as an API server. And what that means is that the web server is not going to hand down HTML or things like that, where it's traditionally-- they might have injected information, say. You grab information from your database, and you put it in your friends list or something, for example. And it's not going to inject [? a string ?] into the HTML and hand you back the HTML. Rather, all that's going to exist in your Angular app already. And really, what your server should be doing is sending back just the data, none of the UI components. So in this example, this is how the HTML looks like for the example I just showed you. Pretty much all we know on the Angular side is that there's going to be a count. There's going to be a count listed here. Whoops. That doesn't do that. JOHN FEIG: [LAUGHTER] No. DARREL SUMI: OK. So there's going to be a count referenced there. And we're going to be repeating over a list of friends. But we don't know who the friends are at this point. There might be one friend. There might be hundreds. And we don't know. But really, you need to treat your web server to give you those things as an API server. BRAD GREEN: And this is the great thing about Angular is you're going to build your app this way anyway. So there's not much in the transition. JOHN FEIG: Right. DARREL SUMI: Exactly. JOHN FEIG: So we're going to look at Socket.IO here. DARREL SUMI: OK. JOHN FEIG: Whoops. Uh-oh. That's not good. DARREL SUMI: OK. So here is some of the code for this example. And this is all, by the way, {public} GitHub {repress}, so that you can look it later on your time. JOHN FEIG: Yeah. We have links at the end. DARREL SUMI: Right, right. So this is the main controller for the example I was just [? saying ?]. So basically, there is going to be a count that's initialized at zero and an empty friends list. Like I said, you don't really know anything about what's happening. And the way we have it structured is that initially, you just connect to the server. The server is, like I said, a Node.js server that's running locally. And here is a service that controls the communication to that Node server. So in server connect, where you start off, essentially we just connect to-- I have a hard-coded thing to look a host right here. And at this point, we don't know any kind of data that's been passed through. We just know that we started with nothing. So what we do is we tell it to connect. And when we connect, we event an emit back to our controller, saying, hey, we've connected. And at this point-- BRAD GREEN: Actually, Darrel, maybe just bump the font up a point or two. DARREL SUMI: Sure. What was it? Command-Plus? JOHN FEIG: Yeah. DARREL SUMI: There we go. BRAD GREEN: That'll be good. JOHN FEIG: Sorry about that. DARREL SUMI: So in our controller, when you do connect, we're going to tell it to initialize, initialize our state. So at this point, your API server doesn't really know anything about where you've left off or what it is. It's just going to start counting from some random-- from zero, if the server doesn't know anything. So it helps if you pass in some kind of initialization data to tell it, hey, this is where we left off. And this leads into a key point of how you want to treat offline and online mode. But we'll get to that in a second. And so we initialize it with our current count, which at this point is zero. And when we get a response back from the server here, and it responds here, we'll get back some data. And in the data-- like I just know it's going to contain the current count and just a list of friends, which doesn't really change. And then from that point onward, the socket's going to connect-- it's going to listen for a counter update event that's going to get pushed down once every second from the Node server. And it's just going emit the count right here. So the count is actually incremented on the server side. So all that kind of logic-- obviously, this is a contrived example of what you might want to pass down, what information you want to pass down from your server. But essentially, the server's going to be handling all that kind of data management, a lot of that stuff that goes on. But you'll notice here that this is all just standard Socket.IO stuff if you have worked with it before. [INAUDIBLE] All right. JOHN FEIG: There we go. DARREL SUMI: OK. Right. And so one of the things that you have to consider when you build your Chrome app, and for Angular as well, is how this is going to get treated in an offline mode or in a state where you're not sure about the connectivity of things. So you do need to handle things like disconnections or crappy connections. And a lot of that needs to go into any kind of socket code that you write. So luckily, Socket.IO handles a lot of the disconnect and auto-reconnect logic. But if you're working with raw sockets, you're going to need to handle these kinds of things, as well. But more than just the actual connectivity, you need to think about how your app is going to work from the user's standpoint when you don't have connectivity. If you start up the app, will you be showing a blank page? If you lose connection in the middle, what's going to happen to, let's say, in our example, the counter that you have going on? JOHN FEIG: Right. You don't want to just throw up a Refresh button and make the user think about that stuff. You want to handle it in a smart way so that it's as automatic as it can be. DARREL SUMI: Right, right. So in our example-- can we go-- what's it called? Terminate the server. JOHN FEIG: Oh, the-- OK. Um, yeah. There we go. OK. Server's up there. DARREL SUMI: OK. So we just-- JOHN FEIG: Server's dead. DARREL SUMI: Yeah, killed our server, essentially. And when you go back to this, you'll notice that the counter has stopped. You'll probably want to do something to signify that you're in an offline mode, maybe. That might help, so the user doesn't get confused, like, why isn't anything working? But you notice that the count is just stopped at here. But if you were to redo the server, or reconnect it, after some retrial logic, it's going to essentially start back from where we previously were counting. So you don't want to necessarily start from scratch every time you reconnect. You want to leave the user off where they were before and [? smoothen ?] the transition between these things. JOHN FEIG: You can see it worked. We were stuck at 419, and now we're counting again. DARREL SUMI: Right, right. So you don't necessarily want to start at zero every time. JOHN FEIG: OK. So go here. DARREL SUMI: So this whole example was, [? obviously, ?] just a regular Angular app. And the same thing works for-- JOHN FEIG: Kill this one. DARREL SUMI: Yeah. Same thing works-- the transition from bringing Socket.IO into Chrome Apps is really, really simple, pretty much like what we showed earlier where you reference the Socket.IO, not from CDN but from your own project. After you do that, that's pretty much it. There isn't a whole lot of things that you need to do to get Socket.IO working. It actually just works out of the box. JOHN FEIG: Yeah, which was really surprising to me because I thought that this might be restricted or it might be really difficult. But Socket.IO just works. Where do you want to go? DARREL SUMI: Yeah, there we go. And so on top of Socket.IO, Chrome Apps provides a really great Sockets API. It gives you access to raw TCP and UDP sockets. It's through the chrome.socket API. I have an example on the GitHub that is the same exact demo that we just showed but just rewritten for TCP. So if you want to look at that, here's a link for that. But we necessarily won't go into that code. JOHN FEIG: It's a little bit more verbose, but it's also a lot more powerful. Like I mentioned at the top, you get access to real raw sockets with this, which, like I said, we just needed for our app. BRAD GREEN: Just so folks can think about, which one would I need? What did you guys need the raw sockets for? JOHN FEIG: So we're sending binary data back and forth to the server. Socket.IO sends back and forth strings. So if you want to send that sort of data over Socket.IO, you have to encode it as a string first. And then you have to decode it on the server side. You also have to have server-side components that can handle Socket.IO. Depending on the server that you're running the sockets, you might have a good Socket.IO library available to you. Like if you're running Node, it's really easy to just pull in Socket.IO. But if you're running Java, there's not really a great solution for Socket.IO. So it's a lot easier to use raw sockets there. DARREL SUMI: It just depends on what your current architecture is and structures. For us, since we came from a mobile app with a pre-existing system, it was easier to plug into that rather than recreate everything on a different system. JOHN FEIG: Right. And there's also WebSockets, too, like we mentioned. And those are maybe a little bit more available on servers. So it just depends on what your needs are. But it's really nice to have Chrome sockets available. So the next thing that you want to think about, in terms of how your app behaves when it's offline, is your local storage stuff. And this is actually something that I started with in my demo, which is a little podcast app, before I even thought about converting this into a Chrome App. The main reason is because I wanted it to behave like an app. I wanted people to be able to subscribe to podcasts and then listen to episodes. I wanted it to keep track of how much they had listened to so far. One thing that really drives me nuts is I have a couple tools that I use, aside from my app, for listening to podcasts, and they always forget where I left off. And some of the podcasts I listen to are multiple hours long. And if the thing goes down or you accidentally click Refresh, and you start back at zero when you're an hour and a half in, that's not too fun . So this stuff is really important. It's really useful. And storing stuff on the client side in certain cases-- like for KeeChat for example, all of your chats are stored locally-- your contacts list. You want all that stuff local. Especially if it's just going to be for you, there's no reason to have it on the server. So there are some slight differences between how Chrome handles local storage versus how HTML5 handles local storage. And I will show you right here. So the big difference is that HTML5 is synchronous. It has a synchronous API for local storage, which means that you can just say, myVal equals localStorage key. So that's just getting the thing right off a disk. It blocks in-line. And so you're going to be blocking on whatever thread you're calling that on. And Chrome is asynchronous. So it's going to go out to disk and then return in a callback the thing. And you can just follow along here. I highlighted in blue the value that I'm trying to set. And then orange here is the value that's coming back from disk. So in the HTML5 example at the top, that's just one line that we have to call. But again, it blocks there. And then the example below is a little bit more verbose, but you can see we start out with null set to that. And then we define a callback function that's going to take a single parameter and assign it to the value that we want set. Oh, boy. Ha. There we go. And then we do this chrome.storage.local.get. And then the first parameter is the key, which is the same key that we have up above. And then whatever gets returned is going to get passed to a function. And it's going to be callback and then data, key. So you get an object back. And you have to pull the value out of the object. It's really not too tough. It's just slightly different because it's asynchronous. And the big deal here is that there may be some third-party libraries, especially if you're in a larger project, that make assumptions about having the synchronous API for local storage. So there was one library that we ran into that was a pretty integral part of KeeChat that just made assumptions all over the place about local storage being synchronous. And we made assumptions-- we kind of used their API in a synchronous way because those were synchronous calls. So it made sense to use it in a synchronous way. So what happened was when we did this change to Chrome, we had to go through and rewrite quite a bit of that third-party library to make it function asynchronously. That was one of the more painful things. It wasn't a huge deal. And it wasn't technically difficult. It was just annoying because you have this dependency. And it's not working for you. So that stuff you might run into. But-- DARREL SUMI: So pretty much, you always want to check and see what kind of assumptions any third-party banks on the access to the APIs, as well as the thing like we talked about earlier-- the Content Security Policy. A lot of these libraries are going to make assumptions, a lot of them more towards the way web has been done up till now and not the Angular way or the Chrome App way. JOHN FEIG: And another great way to persist data locally is with IndexedDB, which is an HTML5 API. And it can actually be a much better fit for certain types of data. So if you have larger data sets, anything that's not a string, really, you probably want to use IndexedDB. It's a NoSQL database on a client. The API's a little-- it takes a little getting used to. It's a little verbose. I actually wrote a wrapper to help with doing things with Angular. So it's an AngularJS IndexedDB wrapper so you can just inject it. And then you just do much simpler calls. But here's an example of IndexedDB Put. So you have a datastore. You have a database. The storeName is kind of like a table. So it's like-- I mean, it's a datastore for NoSQL. And we're just going to put some data, which is an object, so it's going to be key value pairs in that object. And so you have a reference to the database already. You have to have opened the database. And there's a whole process for that. And then you get a transaction. With the storeName, it has to be in read-write mode if you're going to be persisting things. I'm going to say objectStore, dot put, and then your object. And then, hopefully, you'll go through this on, success callback that has your event. And now what I'm doing here is I'm emitting an event through Angular, through the Angular scope, that this data was put, and then on what database, and what storeName. And then I have a similar event in the error case. DARREL SUMI: And this whole wrapper is put as a Angular service, right? JOHN FEIG: Mm-hm. Yep. So it's a singleton. You can call it when you set up your app. I tried to make it as simple as possible. And we actually used the same library in KeeChat. It cut way down on the amount of code that I had to write over trying to do things with local storage. BRAD GREEN: And you'll be able to share that link with us? DARREL SUMI: Yes. JOHN FEIG: Yep, yep. It's at the end. BRAD GREEN: Great. JOHN FEIG: So I actually also have a wrapper for the local storage to choose between Chrome versus the HTML5 thing. It tests to see if chrome.storage exists. And if it does, it uses that. And you can also set it up to use the sync, chrome.storage.sync versus local, which will sync any of those strings across multiple instances of Chrome. So if you're logged into Chrome on your laptop at home and your laptop at work, you can actually have that stuff synced up, which is nice. So back to the slide. Angular has-- it's just an aside, but Angular has these really nice event emitters. In KeeChat, we used a third-party event emitter library because we had-- like I mentioned at the top, there's some third-party libraries that make assumptions about what's available. One of those assumptions was that event emitters would be available. In my podcast demo, I didn't need that. I didn't really use much third-party stuff. So I could use the Angular event emitters, which are really nice because this is done on the scope, on Angular's scope. So Angular knows about these things. You don't have to run scope.apply to get the digest cycle going. So when you send a controller an event, it's going to show up. So it's a really simple API. You listen for-- you register for events with scope.on. You say, like, i_am_event is the string that's going to identify this. And then the second parameter is a function with two parameters, event and data. The event I never use at all. But data is important. And so then you can emit it with rootScope.emit. And it's rootScope because you're usually going to be doing this from a service. Services can't get scope injected. And they probably wouldn't be on the same scope anyway. So rootScope is what you use. DARREL SUMI: The syntax-- the whole notion of this is fairly common, standard event emitter type of paradigm that people are probably used to for any other system. It's just that these events get passed through the scope, so you do have to understand how it goes up and down the scope. JOHN FEIG: Right. Yeah. There are a couple options here. So it's depending on where you're firing events from and where you want to get them to, you might need emit or you might need broadcast. And they take the same parameters. But broadcast basically sends things down to children. Emit goes up the stack. So you have to know which one you need. But yeah, event emitters are really useful. And they really help clean up code. And you don't have to have as many things explicitly calling one another. DARREL SUMI: It just really helps keep your code structured in a really nice, clean way and modularized that way. JOHN FEIG: Right Yeah. It really cuts down on interdependency stuff. And then I have just one other quick example of IndexedDB Get. So very similar to Put, this is just getting some item out of the database. This time, we're going to just use a key, which can be a string, whatever the type for your key is. And so we have a similar thing where we get the transaction out of the database with the storeName. You don't need to be in read-write mode. You don't need to pass the mode in. The default is just read. And then the onsuccess case again, we're emitting something with Angular's rootScope emitter. We say, we've gotten this item. And then we're going to pass an array of results. So this is nice. With IndexedDB, I like to always pass back the database name, the storeName, and then the data so that in my controller-- whatever is getting this data, if it's a service controller or whatever, I can first check to see-- OK, getitem is pretty generic, right? And since this is a service library that I wrote, I'm not going to have dozens of different names for these things. If I have common names, and I pass back identifiers in the data, it just helps so that I can register the listener with that common name and then just inspect things that come back on those event listeners when I get them. So after you do your local storage, convert things to IndexedDB, it's still not going to work. You probably have some dynamic content that you need to handle. DARREL SUMI: And this goes back to the notion of the CSP that you can't bring in external content and just think it's going to run all fine and dandy. JOHN FEIG: Right. So actually, this would probably be a good time to explain one thing here. So this is my little podcast app that I did. And this is actually the Chrome App version. So you can see, I launched it there. And so this middle section here you can see is all pulled in with a web request. So this side-- the Add Podcast and List a Podcast side-- that's local. And then I have this section up top. This-- you've got a playlist here on the left. That's local. But then the content with text that pulls in information about either the show or all these episodes, that's all being pulled in with a web request. And you need XHR to do that. And it's a slightly different call that I had to make in the Chrome environment versus with just the Angular web environment. And so here's what you can use to get, say, an image. And this is an example of getting an image out of imgur. So I am going to set it up with just a regular HTTP. This is Angular's HTTP thing that can be injected into your app. As I said, the method's Get-- here's the URL. And it's going to be a blob response so that I can actually set it as the source of an image. In a success case, you get all this stuff back. Again, I'm going to ignore everything except for the data that's coming back. I keep scrolling. And there. So I'm going to ignore everything except the data. I have in my HTML this image tag with an ID that I specified, onepod_img. So I'm going to grab a reference to it. And then I can set the source with this window.webkitURL.createObjectURL and then the data. And that's going to allow me to just take that data that's the result of this web request, which is-- actually, it works the same way as the XHR stuff. It's just a slightly different API. And then set that into the image. And there's an error case where it failed. And hopefully, it won't fail for you. And you also have to make sure that imgur is in your permissions. So this is not something that-- it's not my server, so I have to put it in the permissions thing. Once I do that, it should be OK. DARREL SUMI: Right. And you'll notice that there's other things that might need to go into your permissions. So any of the Chrome Apps require you to specify either, like what he said, which domains you might be getting external content from that you put in your approved list. But also things like your storage here. You have to tell it that you're going to use storage. And even for your sockets, if you're going to use the Chrome Socket API, you need to specify that, hey, I'm going to allow TCP connections. JOHN FEIG: And then we're going to keep iterating on this. So one thing that was interesting was-- here's my controller to get individual episodes of a podcast. And this is the method here, makeRequest, that's going to actually get the data for each episode-- or for each podcast, actually, and all the episodes within it. So I do the test up top to see. If it's chrome.storage, then I'm in the Chrome environment. There's probably a better way to do that. But that's the thing that I used. And now we get this request URL, which is a really long thing. What's interesting here is I'm actually using a Google service. So with the Content Security Policy, again, and since this is a podcast app, so I needed to go through a single place that's going to return the proper headers. So very basically, with an RSS feed app like mine, you want to be able to subscribe to whoever. And whoever may not have a server that's going to return you the headers that allow you to make requests, like cross-site requests to it. So what Google's done is to set up a service that allows you to get these RSS feeds from a single location. So I can say, ajax.googleapis.com. I'm giving that one permission. It returns me back the proper header so that the requests are valid. And everybody's happy. The one big difference between the HTML5 version and the Chrome version is in the HTML5 version, I can actually use this .jsonp thing, which, basically, this returns as a callback. I don't really need this. There's probably a way that I could get rid of this completely in and do them both the same way. But it's a little nicer. This is how I was doing things before, where it was-- you see a callback equals JSON_CALLBACK and the URL here. And that works with this API. And then for Chrome, if you do that, it'll throw red at you and say, you're not allowed to specify callbacks and responses. So instead, you just do a regular HTTP get. And then in the success, you can still parse the data the same way. So that was the only difference here, really. And then, obviously, the other thing is you've got to be able to load images. And as I showed you before, it's the same deal. So yeah. And then you just iterate until you don't have any more red. There probably are going to be other issues that you'll run into outside of this stuff. DARREL SUMI: So just to recap. You have to really understand where the Content Security Policy's coming from. That's going to be the major source for most of the errors. But you have to look into each of the third-party libraries that you use. A lot of them will make assumptions of what environment they're working in, whether that be how they access sockets, how they access local storage, what they're used to being brought in. So if they use a AMD or Require style of being packaged up, you got to understand how that's all wrapped around those libraries. JOHN FEIG: Yeah. So I can show-- so here's the app in Chrome. Oh, I closed it out. Never mind. Yeah. So here's the app in Chrome. This stuff works. It remembers where you left off because I saved that information in local storage. So I have this method, refreshInterval. So every-- I think it's three seconds, until it's done playing, I send a message to say, update the progress with the current time. DARREL SUMI: And this gets saved into the local storage. JOHN FEIG: It gets saved into the local storage, whereas this data is saved in IndexedDB. I originally wrote it with local storage. And I switched it over to IndexedDB and cut down like-- I just deleted a third of my code. It was really nice. DARREL SUMI: And just to reiterate, the whole point of that is to make sure that the transition from offline mode to online is smooth. So if you do lose connection, it does remember where you left off. And that's really important for the whole user experience point of view. JOHN FEIG: Yeah. And there's definitely some other stuff that I'd like to get to in this app, like basically downloading the audio ahead of time. So that if you have no internet connection, you can still play your stuff. That I did not get to. But it's still a pretty functional tool. It's nicer than some of the other web tools that I've used, which is why I built it. And then we've got-- just throw up some links. We'll make the presentation available to everybody so that they can look at the stuff on their own. We have our app that we're modeling this talk on. This was actually-- I think, basically, all we really needed to do to get KeeChat up and running. The content security stuff was more of a minor issue for us. And then we didn't really have anything outside of this that turned out to be much a problem. It was a pretty straightforward conversion. It took me very little time to get up and running-- really, just a couple days. And this is a pretty decent-size project. So I was very surprised at how quickly we were able to do the conversion. DARREL SUMI: Yeah, definitely. If you come from the Angular setup, it's really easy. JOHN FEIG: And now, we've got Darrel's socket demo linked, which has both the server side and the client side in the same project. DARREL SUMI: Yes. Yep, yep. JOHN FEIG: And then I have two versions of the source for my app. So I created a couple branches so that you can look at before I started the Chrome App conversion and after it was finished. And these specific branches will not have any more commits, so they'll be exactly what I presented to you today. And then I wrote a couple little libraries that work nice with Angular and some of these APIs, like the IndexedDB and localStorage wrapper. And I have to get documentation up for those, but they work. BRAD GREEN: Good, good. Guys, thanks so much. This was a really nice introduction for Chrome Apps and the differences between an HTML5 app and a Chrome App. I think it'll get people started really quickly. And for everyone else, thanks for joining us today. We will see you on the web. We'll share all of these links to the presentation. We love to take questions on Google+ and Twitter. And we'll see you at the next GDL. JOHN FEIG: Thanks for having us. DARREL SUMI: Yeah, thanks for having us. BRAD GREEN: Thanks, guys.
A2 初級 美國腔 使用AngularJS構建Chrome應用 (Building Chrome Apps with AngularJS) 127 8 Joe Huang 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字