 Alright, so before we dive into anything, I feel like I need to give you guys a little bit of a warning. For those who aren't really paying attention, the title of this talk includes the word enterprise. So I've got to warn you that the contents of this conversation are likely not very sexy. There will not be foosball tables. However, side effects could include significant revenue streams, alright? So fair warning. So here's a quick roadmap of what we're going to talk about today. I'm going to cover a little bit of backstory. Why am I up here talking about enterprises? I'm going to go over some points about things that are unique to building for a mobile enterprise environment. So I'm going to cover a bit on modularity. I won't rehash it to death because I think at least four other speakers have talked about it. But then we're going to go into some interesting stuff on caching, offline usage, and finish up with some security notes. So let's get started with a little bit of backstory. So why are we talking about enterprise? Well, to understand an enterprise environment, to talk about these points, we need to understand what enterprise IT looks like. And I think it's easiest to compare it to perhaps what we're more used to, many of us. Which is your typical backbone application stack. And this might be like a startup or a small business. And so we start with some kind of database, data store, Postgres, MySQL, whatever you want to put in that box. And then we have a Rails API on top of it. And so our API is transforming the data, feeding it as JSON down to our client, which is a backbone. This is the core structure. You can add some spice on top. You could have a background job processor or something like that. But the general idea is the same. It's a very linear flow. Now let's take a look at what a typical enterprise stack looks like. So the first thing I'm going to do is I'm going to cross off two words here. Typical and stack. So at Convay where I work, we deal with a lot of enterprise customers. And in all of our combined experience of everybody on the team, we've yet to encounter a typical enterprise stack. A lot of the software that enterprises use is similar or the same. You know, a lot of them use Salesforce, a lot of them use Oracle. But everybody's setup tends to be unique. The second point I'm going to make here is that it's never a stack. It's more like a blob or a mess or something. But let's take a look at what kinds of software enterprises might typically use. So let's say you've got Salesforce, and it's holding your CRM data, your sales data, funneling, forecasting, that kind of thing. It's a retailer manufacturing enterprise. So they're using SAP for an ERP system. They've got SharePoint for internal document storage and communication. Splunk for networking and ops. WebSphere for e-commerce. Drupal for their content management system for an internal portal. Active Directory for user authentication and management. Mobile iron for MDM or MAN. And VMware for virtualizing their private infrastructure. This can be a mess. So that's our first point here, is that enterprise IT is large and complex. And if you notice there's a lot of separate components, and these all tie back to different areas of the business. So Salesforce is the sales department. Drupal might be an internal process. You might have accounting software, HR software. And so what this means is that you get this interconnected system where changes in one component require buying from everybody. So this means that enterprises are large and complex, that's the first point. But they also tend to be slow. Slow to change, slow to adapt. Because that you need buying from all of these different people. But they're also slow for a very good reason. They're slow because they take a long term view of things. So your typical startup thinks in like weeks. A small business might plan a few months ahead. Enterprises make decisions in decade time frames. When they make a choice about an infrastructure component, they're thinking in the long term. And that long term view also means they have a keen eye towards maintenance. So when you build a component for an enterprise ecosystem, you're not just factoring in the cost for the build. But also, how are we going to support this for the next 10 years after you are long gone on your next job? Or your agency has left for its next client? They take this long term view of things. And as a result of that, they love off the shelf software. Everything I've just listed here is basically off the shelf software. It all needs a little bit of tweaking to fit into your environment. But the idea is that it's a product. They're not buying a custom application that you built for their one purpose. Because when you're dealing with a retail or manufacturing enterprise, their core competency is not technology. They don't like to be on the cutting edge. They want it to do the job and be cheap to support. So that gives us a sense of what enterprises look like. But how does mobile web fit into all of this? Well, mobile is kind of an interesting area to be talking about when it comes to enterprise IT right now. All of these products up here solve known problems for an IT department. So if you want to talk about storing sales data, you've got Salesforce. But the challenge with mobile is that there is no Lego brick that you can snap into your architecture right now. There is no standard solution for mobile. And so what you get is, you get this empty gap. And normally, IT departments are happy to just wait until there is a Lego brick that fits the spot. But there's a challenge here that's unique to mobile. An enterprise's customers and its employees have smartphones right now and are using them. They can't wait. If you're talking about cloud services, they could take a step back and say, you know what, we'll get into that whole cloud thing sometime later. But mobile, they can't put that off because people are using mobile devices right now. So they need an answer, but there is no pre-built solution for them. And I think this gives us, backbone developers in particular, a great opportunity. Because backbone can fill that gap, I think better than most frameworks can. It's always interesting to me to hear enterprises talk about mobile projects. The one word I keep here coming up in all of our conversations is that they're labeled innovation projects. This is my favorite corporate speak for we have no idea what we're doing and we're making it up as we go. But that's our opportunity. We can be that answer. And I think the reason backbone is great for this is first off, there's a variety of technical reasons why backbone is great in mobile. You know, it's a smaller footprint, it's efficient, flexible. But from an IT perspective, it offers the closest thing that they can get to that snap in solution. It's arguably the most popular framework out there. And it's rich ecosystem of plugins can become that Lego brick in the composite. So what you can do is you can get close to an off the shelf solution. And IT departments find that very valuable. So that's a bit of our backstory. Now that we understand what does enterprise IT look like and why are we talking about mobile web and backbone within that context. So let's move on to the next section of our story here. We're going to talk a bit about modularity. So we've already discussed different strategies from other speakers about how to approach modular code. But I wanted to start off with talking a bit about backbone itself. So this conversation usually goes, you know, how do you build plugins and that's important to understand. But I think it's really interesting to look at the backbone source itself. So here is the definition of a backbone view. I don't expect you guys to be able to read the code at this font size. But this gives you a sense of the shape of what's happening. This is like roughly 130 lines of code. This is the entire backbone view definition. That's everything. If you delete these 130 lines and then one spot at the bottom of the backbone library, you've removed views from backbone. You've ripped out a quarter of the functionality of the library. And nothing's wrong. Nothing breaks. The library won't throw exceptions. Nothing's missing. So modularity is built into backbone's philosophy because everything is very well isolated, very strict, and narrow scope of responsibility. Means that you can rip out a quarter of the library and nothing goes wrong. Unless of course you're using that quarter of the library. I'm not advocating that you rip out portions of the library willy-nilly but just to illustrate a point. So a lot of my examples, I'm going to couch my conversation about how this all works within the context of Convey. So Convey is a back end as a service company. We provide scalable back ends for mobile and web apps. And we have a JavaScript library that's designed to work with backbone. And because of our intersection of having a focus in the mobile space, having this backbone library, and working with enterprise customers, that library has really taught us a lot about how enterprises like to work. So I'll give you an example of how you use Convey's library to get started. So up top might be your standard backbone, you know, you're defining an account model. And then down below here, the only thing that you have to change to use Convey is just to have the Convey prefix right there. Looks very simple on the surface. Underneath the hood though, there's a lot going on to do that. One of the challenges that we had when we built this library is we wanted to be able to be used with angular, knockout, ember, and backbone. All these major frameworks, and each one of them has a different way of doing things. And it's not just like a slightly different take on a model. You know, angular versus backbone is like a fundamentally different paradigm shift. How do you write one JavaScript library that integrates with both? So this was a pretty big challenge that we faced recently. And so our approach was to take a mix-in approach. Basically our JavaScript library is just a series of mix-ins and freestanding functions. And all of this gets basically injected into whatever framework through the use of a shim. So we have a core JavaScript library and then we have our backbone shim, which we combine into a single build. And so when you download the Convey backbone library, you're downloading the core plus the backbone shim. Underneath the hood, our Convey backbone model is built using this kind of approach. We take the backbone model and extend it with our model mix-in. But the interesting part is that this is almost like inception. It just goes levels and levels deep here. So the model mix-in itself is actually a mix-in composed of multiple other mix-ins. This starts to get confusing to talk about. But in this case, we have the sync mix-in along with several freestanding functions. The sync mix-in overrides the backbone sync function to get our network activity. That function in itself, or that method in itself, is composed of other mix-ins. And so we get this nuts and bolts approach where we break out the individual pieces of functionality that we need and compose it into whatever structure that the framework requires. In this case, we're composing a backbone model. It could have been something else in a different framework. So I'd like to go through a bit, you know, how does this work in the real world if you're building your own application? Because like we talked about, enterprises don't like custom code. They don't like things done one-off. Now, of course, any of you who have worked in an enterprise know that there's a lot of custom code and one-off stuff that happens. I'm talking in the ideal. This is what they say they would like. This is how you talk to an enterprise. So let's take an easy example here. We're talking about a blog application, so the standard example. And in this case, I've got my blog post model. And I happen to be using the pretty decent backbone association's plugin for related data. And so you can see I've got a blog post comment here. And I'm setting up all that relations. And I decide, you know what? Let's get fancy. We want to do almost Rails-style resource routing. I want to be able to just call a method on a model and have it navigate to that models, like the URL that would represent that model. That might not be the cleanest separation of concerns, but it's worth that if you stay straight at the point. So I've collapsed all that relational definition into this comment here. And I now have this go-to function. All right, this is pretty sweet. I can just take a blog post and say, you know, go-to. And it will navigate the application to view that blog post. But of course, you know, we're good programmers. We want to dry this up a bit. We want to make it so that we've separated our concerns. So I'll extract that URL out into a separate function. So that way I can use that URL elsewhere if I want to. All right, so I think this looks good. But actually, I want to add this to that blog post comment model. Maybe I want to look at one comment at a time. I'm not very good at UX, so I'll do something like that. So I want to extract all this out into a base model. Something that everybody can inherit from and use. So I'll go ahead and do that. I'll extract it out. Now I'm not defining the blog post model. I'm defining a base model. I'm sure many of us have done something like this. And then all of my other models will extend from that base model. But of course, now I want to add an author model. And I run into a problem. I don't want this one to be relational. But if we go back, my base model is relational. And so now we've hit the limits of a single inheritance model. Because the prototype chain only has one parent prototype, we can't compose functionality. We end up with this rigid chain. So obviously, the alternative here would be to create some kind of mix-in. So we'll go back and we'll extract that resource routing functionality out into a mix-in. So those two functions are now just freestanding functions on this object. My blog post model defined up there. And now I can just extend it. The nice part about this is I can take this mix-in as is and apply it to a collection, a fundamentally different concept in Backbone, and it will work just as well. So this is where you get that composability of applications that lets you reuse code across projects and get closer to that ideal of less custom code more off the shelf. So our next step is going to be talking about caching. And again, I'm going to frame this within the idea of our JavaScript library. Not that kind of caching. Nothing? Not even like a giggle? Come on, guys. I searched everywhere for that. So we're going to talk about caching. And this is something that our JavaScript library does out of the box. And so we had to figure out a strategy to do this, but do it in a way that it could work in anybody's application. And so what we came up with is this concept of caching strategies. So there are several different strategies that you can pick from. The most basic being network-only, which is basically there is no caching. It always goes to the network. Another strategy is cache-only. So if you want to retrieve data from a cache, and then only if it's not in the cache do you go to the network and get the fresh data. That's one option. But the last option and probably the most interesting is hit the cache first and then refresh with network data. Now, the tricky part here is that that means that your success callback for your fetch operation will get called twice, once with the cache data, once with the network data. This can be tricky because if your success callback is not idempotent, if it has side effects like it can't be run twice, then you're going to have problems. So you have to keep these kinds of concerns in mind when you're building your application. So let's take a look at how we might go about implementing some caching. The first component that we need for a caching model is some kind of storage. We need to store all this data locally in the browser, in the client, so that we can read it from a cache. You have a few options. You could hold it in memory. That's obviously not a very good option. If you reload the page, then, oh, that's gone. So it's a very short-lived cache. You could use local storage or session storage or something like that. Unfortunately, those have fairly strict size limits. I think it's five megs in most browsers. So it's not going to take you very far if you're dealing with an application that has a lot of data. So the best approach for a robust cache is going to be using either WebSQL or indexed DB. It's really hard to say. WebSQL is the older standard. It's actually abandoned by the W3C, so I wouldn't bet on it going forward. And thankfully, there are some pretty decent polyfills that basically take WebSQL under the hood and give you that indexed DB API. So I'm going to show you all examples of indexed DB if you're building, for our concerns, for instance, mobile Safari doesn't support indexed DB. It's a major stumbling block if you're dealing with a mobile environment. So we need to make sure that we have that polyfill in place. So the first step in using indexed DB is we have to get access to the database. Now, the indexed DB API is entirely asynchronous. You're never going to make a call and get a return value out of the database. Instead, you're going to make a request to get something and then pass in the callbacks and the handlers. So in this instance, I'm creating that DB variable, a nice global variable that we'll use later because we all love globals. And then I'm going to open up a request to get that DB. And so in this case, I'm just calling it cache DB, call whatever you like. And then when we're successful, we'll assign that result value to our DB. So now we have a handle for the actual indexed DB. So now let's take a look at the cacheable model. So in this particular case, I'm going to use an example of books. And if you notice, I've got that name property here, which is missing a comma, actually. So the name property, we need that in order to specify where in indexed DB we're going to store this data. So indexed DB is not an SQL based storage mechanism. So you're not going to be talking about tables and joins and that kind of thing. It's a key value based storage, although it stores complex objects. So we need a bucket to put all of our objects of one type into. So this loosely translates to your tables on your SQL database on the server. So in this instance, we're going to store books. All right, so we have the name books. And then in our fetch function, we're going to override backbone's default fetch. And we're going to say, read from the cache. So what does that look like? Well, first let's just start off with an option. You don't always have to read from the cache if you want to, you can. And then the next step is to open up a transaction. So when you're dealing with indexed DB, it's a transactional database. You can't interact with it outside of a transaction. And the idea here is, let's say you have two tabs open on the same application and you're doing operations on both at the same time. The challenge is if you don't have something like transactions, your write operations are going to stop all over each other. But with transactions and permissions, the browser is able to line up write operations in series so that there's no conflicts. The next step is going to be to get the object store. So basically we're just saying, get the books object store. And then we're going to query it. So notice we're using the object store variable. We're going to say get. And we're going to pass in our own ID. I'm not going to get into creating object stores or setting up your indexed DB schema. There's some great articles on MDN to walk through the nitty gritty details of that. This is more conceptual. So we're going to get this record out of the cache if it exists. And that's going to be our query. But again, this is async. So we need to pass in the callback function. And then in that callback function, we'll set the result of that query and call success. Now depending on how you want your application to work, you might want to do a null check to see if it wasn't found or something like that. Depends on how your application is set up. But we'll call that success method. But if you notice, right after we attach that callback, we're calling the JavaScript equivalent of super, call the original fetch function. So again, that success callback will happen twice. Once with the cache data, once with the network data. So that gives us a rough outline of caching and how that works. But we need to dive in a bit because we didn't talk about how that data gets into the cache in the first place. And so to do that, we're going to jump to offline. So there's a pretty common use case that we've seen at Convey. So for instance, an enterprise has a sales team and they are out in the field, on the road, talking to clients and they've got, I've had their mobile phones or something like that. And they want to be able to show their clients sales collateral, which is great. You don't need the Wi-Fi, you can use caching for that. We'll store all the collateral information on the device so you can just whip it open and show to the client. But the next request is, well, obviously I want to be able to save data. I want to be able to input their contact information or something like that. And this is one of the biggest challenges I've seen for backbone developers and web developers in general when they move to mobile is dealing with this partially connected environment. We're so used to desktop apps which are always on. You always have the internet. You never have to worry about it. But when you're on a mobile device, it's one of the biggest concerns and more often than not, it gets ignored. So caching and especially offline usage, offline writing and saving are crucial to a good user experience. And so for us, again, this is a common use case when you talk about sales teams for instance. But it's a little bit counterintuitive. How do you deal with writing something when you don't have access to the internet to save it to the database? So it means that we need to store all this information that we're trying to save locally, track it, and then once the network becomes available, we'll synchronize with the server. And again, this is the kind of problem that we face when we built our JavaScript libraries is how do you do this in a robust way? So the first step here, let's, we're still working on that cashable model from earlier. We're going to override the save function. Now in reality, a robust solution to this would require overriding the destroy function, basically anything that can modify the model. We're just going to look at the save function because the principle is very similar, but you got to cover all of those spectrums. So the first step here is going to be to set the data locally. You know, that's what Backbone.save does and it's internals and we need to do that as well. And then we're going to check if we're online. Now this is sort of the naive check to see do we have a network connection? And I'll give a dollar to anyone who can tell me why the L is capitalized in online. That's not a typo, that's the actual spec. I don't know why it's capitalized, but anyway. So if we're not online, we need to write to local storage. If we are, save it to the server. So what does it look like to write to that local storage? Well, we're going to start with a transaction just like we did with the cash, but we're going to ask for additional permissions. So we're asking for read write. And this is what tells the browser to line up our transaction as an in series operation rather than a parallel operation because we're doing writes. So, obviously you don't want to ask for permission if you don't need it because otherwise you're going to be slowing your transactions down. We're also going to ask for this object store and then we're going to insert. So we're going to take that stored, we'll put on it. Put is just like you'd expect, inserts it if it's not there, updates if it is there. And then we're going to bind our success handler for that query to our options.success directly. So that as soon as the query is done, you get success. But there's a problem here. How do we know what needs to be synchronized with the server once we get network? So we've successfully stored the data locally. It's not going to go anywhere. We've got a hold on it. But now that the network comes back online, we don't know what needs to be synchronized. What should we send out to the server? So what we need to do is actually track this separately. So we have the data that needs to be sent, but now we need to track what actions need to be performed. So at first it's tempting to like throw a flag on it saying like dirty or something like that needs to be saved. But the challenge comes when you deal with deleted records. So if you do a delete operation on a model when you don't have the network connection, you need to record the delete somehow but obviously you just deleted it so you can't add a flag to it. So we need a separate area to store our actions that we've performed. So what we'll do is we'll create a second object store. Just call it to save in this case. And so we'll insert basically an intersection or a lookup for this particular record in which object store. So then we can use this later in our synchronizing function to extract which objects need to be saved to the server. But there's another problem here. How do we know when this is finished? Obviously we don't want to say it's a success until the tracking record is in place but we've already found success to insert that on success. So we're gonna use one of my favorite and I think most underappreciated underscore functions after which basically says takeOptions.success and only call it after this function's success has been called twice. So that way we can bind both callbacks to success and it will only get triggered once both have finished. So now let's take a look at what that synchronized function looks like. So we have our save. So we're tracking all these records that are brand new which ones we need to save to the server. And let's say we get a network connection. Now there's a few ways you can do this. You can check for a network event. Most browsers fire a network event of a certain type when the network becomes available or you could do it every time a model saves. You could just say if the network's available and synchronize everybody. It all depends on your particular application. But how do we do that actual synchronization? So this is a basic idea of what that sync function might look like. So first we're gonna open up our, we're gonna open up our to save object store in a transaction and we're gonna open a cursor. So for those of you that are familiar with SQL data stores, this might be a little bit unfamiliar territory, but cursors allow you to iterate over objects in the database. So we're gonna open up the to save object store, open a cursor on it because we wanna just walk through everything that's there and then say once that cursor's available we're gonna loop through it. And so what happens is we get the cursor from the event. We check to see if it exists. If it doesn't that means that we've reached the end of the object store. There's nothing left. We're done iterating over everything. So if we're done, we're done. If not, save the particular value and then continue on to the next, which will actually re-trigger this whole function. So I've left save, we'll come back to save in a second, but the idea here is again, we're gonna iterate through we do this for to delete to update every type of tracking record that we have. We loop through them and perform whatever operation needs to happen on each of them. But what does that save function look like? Because you can get into some hot water around this. So the first thing we do, remember, what we looked up was a tracking record, not the record itself. So all we have is a name and an ID, like which object store are we putting this in and what's the ID of that particular record. So the first thing that we do is we get the actual object store that we wanna use and we retrieve that particular record out of it. And then inside that on success handler, we're gonna fetch the record from the server. I'm just leaving this a generic fetch method that can be implemented however you like. Why are we going back to the server? Well, this is a to save. But we're going back to the server to make sure that there isn't a conflict. So one of the trickier things to deal with when you deal with offline saving is I can lose network connection, make modifications to a few different records. I get a network connection. I attempt to save, but Joe over here had already modified the records in the meantime. So I don't have his changes in my versions. So how do we deal with that kind of problem? So this is again one of the problems we face building the convey library. And again, we picked a strategies based solution. So there's several strategies that you could take. The first would be a client always wins, which means that I don't care what's on the server. Whoever wrote last wins. They get to blow away anything that's there. You can do a server always wins, which means that if there is a conflict, if someone did change something, then the client loses, they have to give up and they can retry or refetch or something like that. Or with our library, we have an option for a custom resolution, which basically does what this function does here. So we fetch the record from the server and we say once we have the server copy, compare the last modified timestamps for each record. And if the last modified on the server was more recent than my last modified, that means that something happened in the meantime. So then we're gonna go into this resolve conflict function. And that could be any one of the strategies I just mentioned. So it could be a client wins, server wins, or it could be some kind of custom function based on your own application and your knowledge of the problem domain. So that gives you a rough overview of offline saving. Now there's a lot more to that topic. Obviously there is, we can go into things about like how to delete records, but that gives you the general idea. The last note that I wanted to touch on here is security. So anybody who's worked with an enterprise IT department knows that security is like the first word on their lips. They can't wait to like poke holes in things about security. And so there's a few different things that we've encountered, questions that we've had, particularly when we talked to our customers about using convey. So one is, it's a client side application. How can it be secure? All of your application logics out there, all of your API keys, how do you secure that? Cause anybody can open up the web inspector and boom, all of your API keys are exposed. And for a service like convey, where your API key gets you access to the backend itself, that's a major security concern. And so we've come up with, I think what's kind of an interesting approach to this and we basically, we issue multiple API keys for each backend. So if you're talking to your server, if you're talking to your convey backend, you get multiple API keys. One is the master secret. That's something that you would never put in your application. That's the admin password. It does anything you want. You can blow away data. It doesn't respect permissions. But then we offer the app secret. And the app secret is a client embeddable API key. And the only thing that allows you to do within convey is create a new user. That's the only operation you can perform. And then every subsequent request to convey has to be within the context of a user. You have to send in user credentials. And so what this does is it creates a graduated level of permissions. So the public level of permissions that you expose to everybody by embedding your app secret key is only the ability to create a new user, which you can't really do a lot of damage with that. But then once you're within a user context, then you have that server side validation, that authentication, where we're able to compare you to a particular set of permissions and say, okay, now you have to play within our sandbox. So one concept to approach this kind of client side security where you have API keys is that graduated level of API keys and permissions. An alternative would be something like OAuth 2. For those of you who work within enterprises, you probably know that you're lucky if you get to use OAuth 2 or something like that. And the other thing to keep in mind when we talk about security is that most enterprises have existing security infrastructure. So like on that slide earlier, we had Active Directory or they might have an LDAP server. And so a lot of them are not going to want to, oh, let's just duplicate all of our user records into whatever data storage system that you're using. They're gonna want your applications to interface with their existing identity management. And when it comes to something like LDAP, that's a proprietary protocol. You can't do that from a browser, from a client environment. So it's gonna involve server side work. So these are things to keep in mind when you deal with enterprise environments because a lot of times they have these different concerns. So that's what I have for today. Thank you all for listening to me and thanks to these guys at Convay for helping me sort of figure out my thoughts for today. So let's have a go. Questions? Oh, look. Hi, yeah, I was wondering if you could talk about why you override the individual model, modification and fetch operations instead of just the core sync function? For which piece of functionality? I'm sorry, for the offline support? Sure. So there's a few reasons why you might want to do that. I think the first is to push some of that functionality up a layer. It gives you the option to turn it on and off for specific components. So if you override the sync functionality, so first off, if you override the core backbone sync function, that probably wouldn't be the best idea because then you can't opt out of any of that functionality for another model or another collection. You certainly could override the sync function on that particular model, because backbone looks to that model first. Well, it's a prototype chain actually. That would certainly be another option as well. Sometimes you might want to have functionality that is specific to fetch or save versus some of the other methods. And sometimes it's easier to put that in fetch rather than doing all these if then checks inside sync to say, if it's a read operation, do this. If it's a write operation, do this. So it's a trade-off either way. I go here. Less of a question, more of a comment. Sure. And disclaimer, I work for Automatic, the company that owns Superium. All right. You should check out Superium for the data-syncing stuff because it handles all that for you transparently. Nice. Yeah, I'll look into it. Anybody else? Oh. Mike, it's fine. Good afternoon. The question I had to ask was about when you were doing the offline comment navigator offline in order to do whether to sync the save to server or did offline. So would you want to save the data twice, like save it on the server and save it locally? So that might be available in offline mode? You could, yeah. That was just me trying to keep the code simple on the screen. But that's definitely true. So you'd want to save it locally, especially if you're going to be doing reads out of a cache. Where the data gets into the cache in the first place. Like I said, there's very few lines that I can use on the screen. So I want to keep it simple. What would be your recommended library for polyfilling in Dex David? Oh, boy, I don't know. We use one in the convey library. I don't remember the name of it off the top of my head. But if you see me afterwards, I can look it up for you real quick. Sorry, I just don't remember the name. And another question. Yeah. Mobile devices and storage limits for local storage and index DB? Is it consistent across devices? Can you just? So local storage is significantly limited compared to index DB. I think in most cases, it's 5 megs. There might be some variance in the browsers. Index DB, again, there's some variance in the browsers. Firefox, I think, is actually unlimited storage. And then Chrome is something like, they have this algorithm that takes into account other apps that are using index DB. So I think it's half of the available memory in the whole system. And then minus what other apps are using. But index DB gives you virtually no constraints compared to local storage. You have moved the conflict resolver in the client side. That one is doing it. So why can't you move it to the server side so that we can get more performance over it? That's a good question. In a lot of cases, that would be ideal. But the thing to keep in mind is that if you're dealing with an enterprise, most likely, you're not going to be dealing with a green field database where you just set up something on the back end and you're just pulling data out of that thing alone. More often than not, enterprises are going to want data integration apps. So for instance, the sales app that I mentioned earlier where you're going on talking to clients, all that data for the sales app is not going to exist in some rails back end inside an enterprise. It's going to be in Salesforce in 47 other different places. And the APIs that those enterprises have set up to expose all that data, most likely will not have the built-in functionality to do conflict resolution. If you do, you're lucky that's awesome. That's certainly more performance probably. But in most cases, they won't have that. And so that's a situation where you would have to do it on the client side or you'd have to get in and modify their existing structure, which is getting pretty hairy. So anything else on the front here, I think? So for the offline stuff, it looked like you were doing serial saves for all the offline transactions that need to happen. So if there's 100 items that need to be synchronized, are you doing 100 XHRs or one giant payload? Yeah, that's a good question. So just to make sure I understand you correctly. So you're saying once you get that network connection back and you have a whole bunch of data that's saved locally, how does that synchronize? Is it a batch request as one or is it multiple? Correct. That depends on the backend system again. So in some cases, you might have a backend system that supports batch saving where you can send in an array of objects and it saves it all for you. In other cases, you might be dealing with a system that doesn't allow batch saving, in which case you're going to have to fire up. It won't be serial because you'll have multiple XHRs at once. But they will be an individual request per item. That's more a function of the backend system you're going to be working with. And again, like I said, usually you're not going to get to decide that on your own, like build it yourself. So that's a question for the backend system as usual. Does that answer your question? What else? That is it. All right. Thanks guys.