Tonights mash-up was taking the gplus-quickstart-dart and wiring it up for server side support. Similar to the gplus-quickstart-java, the client will use the gplus login button to do the OAuth2WebServer flow and send the code over to the server. The server can then verify and make calls on behalf of the client since an ‘offline’ token was requested. This demo just features the server side and what was used to put it together. Yulian Kuncheff has been the primary developer behind fukiya which is an express like framework for dart. The thing I liked most about fukiya was how simple and easy it was to setup URL handlers.
First off, setting up some dependencies.
123456
dependencies:
google_plus_v1_api: any
browser: any
fukiya: '>=0.0.11'
html5lib: ">=0.4.1 <0.4.2"
logging: ">=0.4.3+5"
A quick outline of what URLs fukiya handles. Dead simple to setup!
The index handler is special cause we needed to inject a state token into the page and HTTP session. The state token is then verified on the /connect post. The one-time token helps avoid any Confused_deputy_problems.
12345678910111213141516171819202122
voidgetIndexHandler(FukiyaContextcontext){// Create a state token. context.request.session["state_token"]=_createStateToken();// Readin the index file and add state token into the meta element. varfile=newFile(INDEX_HTML);file.exists().then((boolexists){if(exists){file.readAsString().then((StringindexDocument){Documentdoc=newDocument.html(indexDocument);ElementmetaState=newElement.html('<meta name="state_token" content="${context.request.session["state_token"]}">');doc.head.children.add(metaState);context.response.writeBytes(doc.outerHtml.codeUnits);context.response.done.catchError((e)=>serverLogger.fine("File Response error: ${e}"));context.response.close();},onError:(error)=>serverLogger.fine("error = $error"));}else{context.response.statusCode=404;context.response.close();}});}
On the /connect post we will expect a gplus id to be passed to the query parameters and some token data posted. We can then verify the state token and use the token data for accessing the Google APIs.
voidpostConnectDataHandler(FukiyaContextcontext){serverLogger.fine("postConnectDataHandler");StringtokenData=context.request.session.containsKey("access_token")?context.request.session["access_token"]:null;// TODO: handle missing tokenStringstateToken=context.request.session.containsKey("state_token")?context.request.session["state_token"]:null;StringqueryStateToken=context.request.queryParameters.containsKey("state_token")?context.request.queryParameters["state_token"]:null;// Check if the token already exists for this session. if(tokenData!=null){context.send("Current user is already connected.");return;}// Check if any of the needed token values are null or mismatched.if(stateToken==null||queryStateToken==null||stateToken!=queryStateToken){context.response.statusCode=401;context.send("Invalid state parameter.");return;}// Normally the state would be a one-time use token, however in our// simple case, we want a user to be able to connect and disconnect// without reloading the page. Thus, for demonstration, we don't// implement this best practice.context.request.session.remove("state_token");StringgPlusId=context.request.queryParameters["gplus_id"];StringBuffersb=newStringBuffer();// Read data from request.context.request.transform(newStringDecoder()).listen((data)=>sb.write(data),onDone:(){serverLogger.fine("context.request.listen.onDone = ${sb.toString()}");MaprequestData=JSON.parse(sb.toString());Mapfields={"grant_type":"authorization_code","code":requestData["code"],// http://www.riskcompletefailure.com/2013/03/postmessage-oauth-20.html"redirect_uri":"postmessage","client_id":CLIENT_ID,"client_secret":CLIENT_SECRET};http.Client_httpClient=newhttp.Client();_httpClient.post(TOKEN_ENDPOINT,fields:fields).then((http.Responseresponse){// At this point we have the token and refresh token.varcredentials=JSON.parse(response.body);_httpClient.close();varverifyTokenUrl='${TOKENINFO_URL}?access_token=${credentials["access_token"]}';newhttp.Client()..get(verifyTokenUrl).then((http.Responseresponse){serverLogger.fine("response = ${response.body}");varverifyResponse=JSON.parse(response.body);StringuserId=verifyResponse.containsKey("user_id")?verifyResponse["user_id"]:null;StringaccessToken=credentials.containsKey("access_token")?credentials["access_token"]:null;if(userId!=null&&userId==gPlusId&&accessToken!=null){context.request.session["access_token"]=accessToken;context.send("POST OK");}else{context.response.statusCode=401;context.send("POST FAILED ${userId} != ${gPlusId}");}});});});}
Now the HTTP session has the full ability to make calls on behalf of the user. The /people method will be called from the client to retrieve the list of visible friends of that user.
The final responsibility we can bestow upon the server is allowing the client to disconnect by revoking OAuth access.
1234567891011121314151617181920
voidpostDisconnectHandler(FukiyaContextcontext){StringtokenData=context.request.session.containsKey("access_token")?context.request.session["access_token"]:null;if(tokenData==null){context.response.statusCode=401;context.send("Current user not connected.");return;}finalStringrevokeTokenUrl="${TOKEN_REVOKE_ENDPOINT}?token=${tokenData}";context.request.session.remove("access_token");newhttp.Client()..get(revokeTokenUrl).then((http.Responseresponse){context.request.session["state_token"]=_createStateToken();Mapdata={"state_token":context.request.session["state_token"],"message":"Successfully disconnected."};context.send(JSON.stringify(data));});}
Thats about it, Happy Dart Hacking! Special thanks to Gerwin Sturm for putting together the original example for client side. Full source code can be found at gplus-quickstart-dart in the server folder. Please replace your own keys cause mine will be removed at some point.
Google has made the realtime api available for developers. Realtime api provides operational transformation on strings, lists, maps and custom objects. The application data gets stored on Google Drive and is available from any supported browser. This is going to be the tooling of the future for collaborative applications.
I took some time to see what it would take for implementing a sample realtime application in dart. Also wanted to make sure my sample could run on mobile chrome.
Since realtime api is new, dart bindings don’t really exist. Lucky for us we have js-interop library. The js-interop library provides communications to existing javascript code from dart. I consider this mostly a quick hack to get started with the realtime api until a more native interface exists.
The sample realtime_touch_canvas demonstrates a multi touch canvas surface that updates in realtime with all clients that have the application open.
Most of the heavy lifting is done by rtclient.dart. I ported the code from the javascript version. Its enough code to get started right away but a more structured solution should be done. The main class is RealTimeLoader used for realtime loading.
12345
rtl=newRealTimeLoader(clientId:'CLIENTID.apps.googleusercontent.com',apiKey:'KEY');rtl.start().then((boolisComplete){/* RealTimeLoader has authenticated the application and is ready to load a file */loadRealTimeFile(fileId,model.onFileLoaded,model.initializeModel);});
model.onFileLoaded and model.initializeModel handle the creating of model data and loading of model data.
In the realtime_touch_canvas, model data was a simple list of json strings. The ticky part here is you need to remember that your working with the realtime api within the javascript vm. So an array needs to be allocated from js-interop.
After the model is created we then get called to load the file. Loading the file for our purposes is binding the collaborative objects. Some tricky things to note here is we are retaining the javascript objects so we can access them after exit of the callback. Also the callbacks have to be wrapped within js-interopjs.Callback.many proxy object. The callbacks _linesOnAddValuesChangedEvent and _linesOnRemovedValuesChangedEvent are fired off when the collaborative list object has items added or removed.
When the callback is called the data would be in the javascript virtual machine so we can parse it and store in our native dart code. This is more of a convenience then a must do, that way we can expose plan old dart objects to our other parts of the dart application.
Now when we want to store a line in the application we simply convert it to json and push it into the collaborative list. The little tick here is to make sure we are scoped when accessing the _lines object since it lives in the javascript virtual machine.
Tonights hacking was with stream and heroku. Stream is a Dart web server supporting request routing, filtering, template technology, file-based static resources and MVC design pattern. I just planned on serving static content from heroku using full dart based web server.
Creating a new project called stream-todomvc. Going to use the todomvc from the web-ui project as our content for the stream server. First thing that should be done is adding the dependencies to the pubspec.yaml file.
stream intro documentation goes over some basic configurations and settings. I’m just going to use them for now to get something running right away. The key to note when serving code from the web/ folder in dart projects is having the stream server code in web/webapp/. That way stream can find all your resources with little configuration. With very little dart code we can have static web server going.
Next we build all the static data for our webapp to function. This will include calling build.dart and dart2js. The second step of calling dart2js helps with clients that do not have the dartvm built in.
shell
12345678910111213
adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ pub install
Resolving dependencies...
Dependencies installed!
adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ dart build.dart
Total time spent on web/index.html -- 839 ms
Total time -- 863 ms
adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ dart2js -oweb/out/index.html_bootstrap.dart.js web/out/index.html_bootstrap.dart
Using snapshot /Users/adam/Documents/DartEditor/dart/dart-sdk/lib/_internal/compiler/implementation/dart2js.dart.snapshot
adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ git add -a -m "ready for deploy"adam@Adams-MacBook-Air:~/dart/stream_todomvc
$ git push -v --set-upstream heroku master:master
Pushing to git@heroku.com:stream-todomvc.git
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 283 bytes, done.
Total 3 (delta 2), reused 0 (delta 0)-----> Fetching custom git buildpack... done-----> Dart app detected
-----> Installing Dart VM, build: latest
-----> Copy Dart binaries to app root
-----> Install packages
*** Found pubspec.yaml in .
Resolving dependencies...
Dependencies installed!
Fixed web symlink
-----> Discovering process types
Procfile declares types -> web
-----> Compiled slug size: 8.9MB
-----> Launching... done, v7
http://stream-todomvc.herokuapp.com deployed to Heroku
To git@heroku.com:stream-todomvc.git
042f1f4..b35984b master -> master
updating local tracking ref 'refs/remotes/heroku/master'
Deploying to heroku in this style is just a good starting point. web-ui and dart in general is still working on a deployment story. The URL for the stream-todomvc will contain out in its location, not very desirable. In the future a buildtool will aid the deployment story for dart.
We plan to keep the client libraries up to date with uploader.dart script. Still somewhat a manual process, future automation could happen when/if we have the ability to get notified about google api changes. For now we will push updates when appropriate. This will ensure that we can push the latest versions of the apis to pub and still have previous revisions available. Some of the more intricate parts of this script include auto version incrementing pubspec files and syncing to github, then pushing to pub.
Would you want to contribute to this project? Please feel free to ping us Adam Singer/Gerwin Sturm on g+, we’re definitely looking to refactor some parts and test others. Our main focus for this release was to get something out the door that is pleasantly usable.
Many hours of testing and development was done to have a simple and easy way to use the google client apis in dart! We hope you enjoy and look forward to seeing what you build.
To get started with client api examples check out dart_api_client_examples. The github hosted source code can be found at dart-google-apis
drone.io has really proven to be useful for dart projects. One common pattern I find my self doing with projects these days is running the dart_analyzer before running unit tests. Had a discussion with Kevin Moore about having a dart_analyzer task included in hop, part of the bot.dart framework. We both agreed it would be nice to automate that task and not have shell scripts running the show. Thus createDartAnalyzerTask was born. createDartAnalyzerTask allows you to add dart scripts that are libraries or main entry points to be analyzed by dart_analyzer, this allows for a first step of safety, so that code you have passes the static checker. It does not mean your code is perfect but can help you find warnings and errors. A great combination for this is pairing it up with drone.io, that way when a new sdk comes out drone can let you know automatically if it passes static checker.
libraryhop_runner;import'dart:async';import'dart:io';import'package:bot/bot.dart';import'package:bot/hop.dart';import'package:bot/hop_tasks.dart';voidmain(){//// Analyzer//addTask('analyze_lib',createDartAnalyzerTask(['lib/stats.dart']));//// Hop away!//runHopCore();}
Add a bin/hop script to your project, bin/hop script is not required but does help manage some flags you might be interested in having.
Now we have a warning, but since dart is a dynamic langauge we should not treat that as an error (at some point we might provide the option of making warnings fail drone.io).
If we introduce a real error, the task runner should yell at us.
The exit code will let drone.io know that we did not exit cleanly. I love drone.io for this reason, it’s simple to get setup right away with little fuss.
A complete run on drone.io might look something as follows
Can we make the webViewLink easier to read and share?
At the time I started this investigation the discovery_api_dart_client_generator did not have OAuth2 support for installed applications. Knowing that pub does use OAuth2 in a similar fashion, I decided to peek inside of pub to see what it is doing.
After reviewing the pubsource code, it’s interested to see how they handle getting tokens from the Google OAuth2 Authorization Server back to the client application. pub first checks if the client applications has a ~/.pub-cache/credentials.json file stored, if the credentials.json is not found or invalid pub will generate an authorization url. The authorization url is copied and pasted by the user into a web browser and asks the user if they will allow access to some scopes. When the user accepts access the redirect url with token the Google OAuth2 Authorization Server redirects to a listening http server on localhost. This server was started by the pub application, now pub has the token and stores it for later use.
12:22:46-adam@Adams-MacBook-Air:~/dart/stats.dart
$ pub publish
Publishing "stats" 0.0.4:
|-- .gitignore
|-- AUTHORS
|-- LICENSE
|-- README.md
|-- asset
| |-- stats_dart_fps.png
| '-- stats_dart_ms.png
|-- example
| |-- basic
| | |-- statsdart.dart
| | '-- statsdart.html
| '-- theming
| |-- theming.dart
| '-- theming.html
|-- lib
| |-- src
| | '-- stats.dart
| '-- stats.dart
|-- pubspec.yaml
'-- test
|-- run.sh
|-- tests_browser.dart
'-- tests_browser.html
Looks great! Are you ready to upload your package (y/n)? y
Pub needs your authorization to upload packages on your behalf.
In a web browser, go to https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&response_type=code&client_id=818368855108-8grd2eg9tj9f38os6f1urbcvsq399u8n.apps.googleusercontent.com&redirect_uri=http%3A%2F%2Flocalhost%3A62462&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email
Then click "Allow access".
Waiting for your authorization...
Authorization received, processing...
Successfully authorized.
Package "stats" already has version "0.0.4".
After some initial slicing and dicing I was able to take what was needed from pub and merge it into dart-google-oauth2-library that discovery_api_dart_client_generator depends on for OAuth2 support. pub was structured such that all files are dart libraries, so ripping out parts was easy, I found the most issues with http package. The client requestsContentType could only be set as application/x-www-form-urlencoded when making requests, this did not seem to play nicely with Google APIs. Took many hours of debugging to figure out why POST method would fail but GET method would not. Stumbled on some Google mailing lists that mentioned some services do work with ContentTypeapplication/x-www-form-urlencoded and some only with application/json. So I created a separate client that does the POST, PATCH, PUT, DELETE methods required for the Google Client APIs.
The markdown.dart processor was for the most part a total freebie, Bob Nystrom had already created a markdown processor which eventually got rolled into Dartdoc. All that was needed here is picking out the code from the dart repo, updating libraries and import, minor code clean up and hosting it on github.
One of my all time favorite Google APIs is the url-shortener API. Even without OAuth2 you can use this service, now that we have patched up dart-google-oauth2-library and discovery_api_dart_client_generator shortening the long webViewLink will be easy. A site note, goo.gl links work great as for landing slides when giving a presentation, easy and short enough for audience to type into mobile phone, tablet or laptop to pull down your slides and follow along.
Now we have all the parts needed to create a simple application that reads in some markdown, processes it to html, creates a website folder on drive, uploads the final html, then generates a shortened url.
We are still working on the best way to publish the Dart Google Client APIs, so please don’t depend on the links below for too long, they will be out dated soon. This was just for testing.
Starting off with our pubspec.yaml file we add the dependencies needed for the application.
123456789
name:drive_publish_markdowndescription:Publishing markdown content to a public drive sitedependencies:urlshortener_v1_api:git:https://github.com/financeCoding/urlshortener_v1_api_client.gitdrive_v2_api:git:https://github.com/financeCoding/drive_v2_api_client.gitmarkdown:git:https://github.com/financeCoding/markdown.dart.git
Getting a identifier and secret is simple, just goto your Google APIs Console and pull out Client ID and Client secret for a Client ID for installed applications that was previously created. If you don’t have one already they are easy to create.
Goto API access in a project.
Click Create another client ID.
Choose Installed application and Installed type Other. Your Done!
Now with identifier and secret the setup for a dart application to use Drive and Urlshortener is easy, create a new OAuth2Console and pass them along to the constructor of Drive and Urlshortener. Note that the flag makeAuthRequests has been set on both objects, that allows us to make authroized calls on behalf of the user. You could create the Google Client API objects without a OAuth2 token and leave the makeAuthRequests as false, the objects would then only be allowed to access non-authenticated resources on the Google servers.
1234567891011121314151617181920212223
voidmain(){/** * Create new or load existing oauth2 token. */auth=newOAuth2Console(identifier:identifier,secret:secret,scopes:scopes);/** * Create a new [drivelib.Drive] object with authenticated requests. */drive=newdrivelib.Drive(auth);drive.makeAuthRequests=true;/** * Create a new [urllib.Urlshortener] object with authenticated requests. */urlshort=newurllib.Urlshortener(auth);urlshort.makeAuthRequests=true;/** * Create a new 'public_folder' and insert markdown as html */createPublicFolder("public_folder").then(processMarkdown);}
I have to admit, updating permissions took me awhile to figure out. I first tried to update by calling drive.files.update with a modified drivelib.File. That was not the proper way to change permissions on drive. The correct way is to call the drive.permissions.* methods, lesson learned after some Googling and searching on stackoverflowdrive-sdk. Setting the Permissions and mimeType are the most important parts to note here, thats what change the folder into a public website hosted on drive viewable to anyone.
voidinsertPermissions(drivelib.Filefile,Completercompleter){/** * Create new [drivelib.Permission] for insertion to the * drive permissions list. This will mark the folder publicly * readable by anyone. */varpermissions=newdrivelib.Permission.fromJson({"value":"","type":"anyone","role":"reader"});drive.permissions.insert(permissions,file.id).then((drivelib.Permissionpermission)=>completer.complete(file));}Future<drivelib.File>createPublicFolder(StringfolderName){varcompleter=newCompleter();/** * Create the [drivelib.File] with a web folder app mime type. */drivelib.Filefile=newdrivelib.File.fromJson({'title':folderName,'mimeType':"application/vnd.google-apps.folder"});/** * Insert the [drivelib.File] to google drive. */drive.files.insert(file).then((drivelib.Filenewfile)=>insertPermissions(newfile,completer));returncompleter.future;}
Now we get to the meat and potatoes of our application. At this point we have a public web folder that can host our content. We do the follow steps
processMarkdown(drivelib.Filefolder){/** * Read in both markdown and html template */varmarkdownFile=newFile('markdown.md');vartemplateFile=newFile('template.html');varmarkdownStr=markdownFile.readAsStringSync();vartemplateStr=templateFile.readAsStringSync();/** * Convert markdown to html. */varpage=markdown.markdownToHtml(markdownStr);/** * Replace $page with converted markdown. */templateStr=templateStr.replaceFirst('\$page',page);/** * Create a new [drivelib.File] to hold the html content. */drivelib.Filefile=newdrivelib.File.fromJson({'title':'index.html','mimeType':"text/html"});/** * Create a new [drivelib.ParentReference] to link the html file to. */drivelib.ParentReferencenewParent=newdrivelib.ParentReference.fromJson({'id':folder.id});/** * Encode the content to Base64 for inserting into drive. */varcontent=CryptoUtils.bytesToBase64(templateStr.charCodes);/** * 1) Insert the new file with title index.html and type text/html * 2) Insert the new parent of the file (i.e. place the file in the folder) * 3) Get the folders web view link * 4) Shorten the web view link with UrlShortener */drive.files.insert(file,content:content).then((drivelib.FileinsertedFile){drive.parents.insert(newParent,insertedFile.id).then((drivelib.ParentReferenceparentReference){drive.files.get(folder.id).then((folder){print("Web View Link: ${folder.webViewLink}");varurl=newurllib.Url.fromJson({'longUrl':folder.webViewLink});urlshort.url.insert(url).then((url){print("Short Url ${url.id}");});});});});}
The code for this sample can be found on githubdrive_publish_markdown. Executing the sample follows the flow explained above
tl;dr A common question that comes up is connecting all parts of dart together. Here I will show a simple example of connecting up web-ui and objectory.
web-ui is a WebComponents framework provided by the dart-lang team. objectory is a data persistence layer for MongoDB that provides typed, checked environment for models, saving and queries. With these two components and a very simple web server we could have a nice full stack solution in dart.
Initially Vadim Tsushko did some work to wire up TodoMVC from the web-ui examples with indexdb. Using the same code and updated version of TodoMVC I was able to connect it to mongodb using the web socket connection provided with objectory.
It would of been nice if this solution could possibly be deployed onto heroku, ended up not possible since heroku does not support native web socket connections with their internal routing. socket.io does work on heroku but uses long polling instead of true web sockets.
Another note, since dart is in a transitional phase right now for implementing streams, some of this code depends packages that have not yet been pushed to pub.dartlang.org
So lets get started with the basics, getting up and running with objectory. Install mongo if its not already and fire up a mongo database.
mongo install
1234
brew install mongo
cd /tmp/
mkdir db
mongod -dbpath .
The version of dart being used is the latest trunk build 17072
12
$ cat ~/dart/dart-sdk/version
0.3.0.1_r17072
clone and test objectory as a sanity check
objectory
1234567
$ cd /Users/adam/dart/
$ git clone https://github.com/vadimtsushko/objectory.git
$ cd objectory
$ pub install
$ cd bin
$ dart objectory_server.dart
listing on http://127.0.0.1:8080
Open up another terminal and run the example/blog_console.dart application.
1234567891011121314151617181920212223
$ cd ~/dart/objectory/example
$ dart blog_console.dart
===================================================================================>> Adding Authors===================================================================================>> Authors ordered by age ascending
[Jorge Luis Borges]:[jorge@borges.com]:[123][William Shakespeare]:[william@shakespeare.com]:[587]===================================================================================>> Adding Users===================================================================================>> >> Users ordered by login ascending
[jdoe]:[John Doe]:[john@doe.com][lsmith]:[Lucy Smith]:[lucy@smith.com]===================================================================================>> Adding articles===================================================================================>> Printing articles
Jorge Luis Borges:Caminando por Buenos Aires:Las callecitas de Buenos Aires tienen ese no se que...
2013-01-07 16:21:26.437:Lucy Smith: Well, you may do better...
2013-01-15 16:01:26.445:John Doe: I love this article!
William Shakespeare:I must have seen thy face before:Thine eyes call me in a new way
2013-01-16 11:28:06.453:John Doe: great article!
Now if we want to see the blog stored in mongo we need to remove the line in blog_console.dart that drops the database collection and rerun.
Opening up mongo and peeking in side we see the following entries have been stored.
1234567891011121314151617
$ mongo
MongoDB shell version: 2.2.2
connecting to: test> show dbs
local(empty)objectory_blog 0.203125GB
> use objectory_blog
switched to db objectory_blog
> show collections
Article
Author
User
system.indexes
> db.Article.find(){"_id" : ObjectId("50f751f09925f54002000008"), "title" : "Caminando por Buenos Aires", "body" : "Las callecitas de Buenos Aires tienen ese no se que...", "author" : DBPointer("Author", ObjectId("50f751f09925f54002000001")), "comments" : [{"date" : ISODate("2013-01-08T00:24:21.201Z"), "body" : "Well, you may do better...", "user" : DBPointer("User", ObjectId("50f751f09925f54002000005"))}, {"date" : ISODate("2013-01-16T00:04:21.206Z"), "body" : "I love this article!", "user" : DBPointer("User", ObjectId("50f751f09925f54002000004"))}]}{"_id" : ObjectId("50f751f09925f54002000009"), "title" : "I must have seen thy face before", "body" : "Thine eyes call me in a new way", "author" : DBPointer("Author", ObjectId("50f751f09925f54002000000")), "comments" : [{"date" : ISODate("2013-01-16T19:31:01.212Z"), "body" : "great article!", "user" : DBPointer("User", ObjectId("50f751f09925f54002000004"))}]}>
Now that we have done a sanity check we can start to move forward with TodoMVC side of things.
I’ve provided a web-ui branch, the branch fixes up some of the pubspec inconsistencies between the projects on pub.dartlang.org. In the future this would not be needed.
objectory provides a very nice browser based web socket connector to the objectory server named ObjectoryWebsocketBrowserImpl. This allows us to register our models. The TodoMVC application model has a Todo object we’d like to persist.
By extending PersistentObject and adding the getProperty and setProperty methods to our getters and setters we have easily transformed this object to be persisted by objectory.
To bind this up to objectory we new up a ObjectoryWebsocketBrowserImpl, when the application model is created, register the Todo class.
model.dart
1234567891011121314151617
import'package:objectory/objectory_browser.dart';ObjectoryQueryBuilderget$Todo=>newObjectoryQueryBuilder('Todo');constDefaultUri='127.0.0.1:8080';AppModel_app;AppModelgetapp{if(_app==null){_app=newAppModel();objectory=newObjectoryWebsocketBrowserImpl(DefaultUri,()=>objectory.registerClass('Todo',()=>newTodo('')),false);// set to true to drop modelsobjectory.initDomainModel().then((_){objectory.find($Todo).then((todos){app.resetTodos(todos);});});}return_app;}
The final step is when a todo is added we also save it in objectory by calling objectory.save().
main.dart
1234567891011
import'package:objectory/objectory.dart';[...]voidaddTodo(Evente){e.preventDefault();// don't submit the formvarinput=query('#new-todo');if(input.value=='')return;vartodo=newTodo(input.value);app.todos.add(todo);objectory.save(todo);input.value='';}
In the custom branch provided, I renamed the standard build.dart to x_build.dart in hope to save my poor laptop from needlessly building. On a faster system this is not needed. Now we can launch the x_build.dart to generate the output build from the web components sample.
12
$ dart x_build.dart
Total time spent on example/todomvc/main.html -- 276 ms
From the dart editor we can now launch the output folder main.html.
This is a nice start, we can launch a web-ui application from dartium and connect up mongo. The issues now… how do we get the kind of application live and not launched on localhost?
Create a http server
Add objectory to it
Compile todomvc application to javascript
Why are the steps above needed? First the objectory ObjectoryServerImpl used in objectory_server.dart hides HttpServer which doesn’t really work for us if we need to serve up static content. Good thing that the class is relativity simple to modify. Compiling todomvc to javascript is an additional step also, but helps us greatly the server logic, only a few files will need to be matched. When a complete and updated web server framework comes to town for dart this would also not be needed.
Modifying the DefaultUri for ObjectoryWebsocketBrowserImpl so it looks at the host’s location will help for deploying it on a server. The web socket location can then be resolved dynamically.
$ cd ~/dart/
$ git clone https://github.com/financeCoding/dart-full-stack-example.git
Moving onto dart_full_stack_example, this project will contain the compiled js and html code along with a server. The server will handle delivering the static content also provide the interfaces for the web socket connection.
Looking at the server.dart source code will give you a fuller example, all that is needed now is to run dart server.dart and you’ll have server running.
This is a more complicated process then it has to be, at some point it will get easier when a true deployment and configuration story happen.
Todays random walk of dartness has lead me to generating and executing snapshots.
What is a snapshot in terms of Dart? Serialized binary heaps. It has been said that snapshots can help apps startup 10X faster. dart2js is a working example of this in action, when you execute the dart2js compiler it is actually running from a snapshot file.
How can I currently generate them? (Might not be this way in the future) As of now you need to build from source so the gen_snapshot binary is built. gen_snapshot is the tool built from gen_snapshot.cc.
Running gen_snapshot --help we find the flags needed to generate a snapshot script.
1234567891011121314151617181920212223242526
~/dart_bleeding/dart/xcodebuild/ReleaseIA32
$ ./gen_snapshot --help
No snapshot output file specified.
Usage:
gen_snapshot [<vm-flags>][<options>]\{--script_snapshot=<out-file> | --snapshot=<out-file>}\[<dart-script-file>] Writes a snapshot of <dart-script-file> to <out-file>. If no
<dart-script-file> is passed, a generic snapshot of all the corelibs is
created. One of the following is required:
--script_snapshot=<file> Generates a script snapshot.
--snapshot=<file> Generates a complete snapshot. Uses the url
mapping specified on the command line to load
the libraries.
Supported options:
--package_root=<path>
Where to find packages, that is, "package:..." imports.
--url_mapping=<mapping>
Uses the URL mapping(s) specified on the command line to load the
libraries. For use only with --snapshot=.
The dart vm provides a flag that allows the vm to load the dart script from a snapshot.
12
--use_script_snapshot=<file_name>
executes Dart script present in the specified snapshot file
Combining all this together and using the benchmark_harness we can test out creating and running a dart application from a snapshot.
Decided to move the loading of client.js into its own helper class for now. When dart has real support for Google client apis none of this will be needed. As of now this serves as good practice and examples of javascript/dart interop.
/** * Sample google api client loader. */classGoogleApiClientLoader{staticconstString_CLIENT_ID='299615367852.apps.googleusercontent.com';staticconstString_SCOPE='https://www.googleapis.com/auth/drive';staticconstString_handleClientLoadName="handleClientLoad";staticvoid_loadScript(){/** * Create and load script element. */ScriptElementscript=newScriptElement();script.src="http://apis.google.com/js/client.js?onload=$_handleClientLoadName";script.type="text/javascript";document.body.children.add(script);}staticvoid_createScopedCallbacks(varcompleter){js.scoped((){/** * handleAuthResult is called from javascript when * the function to call once the login process is complete. */js.context.handleAuthResult=newjs.Callback.many((js.ProxyauthResult){MapdartAuthResult=JSON.parse(js.context.JSON.stringify(authResult));completer.complete(dartAuthResult);});/** * This javascript method is called when the client.js script * is loaded. */js.context.handleClientLoad=newjs.Callback.many((){js.context.window.setTimeout(js.context.checkAuth,1);});/** * Authorization check if the client allows this * application to access its google drive. */js.context.checkAuth=newjs.Callback.many((){js.context.gapi.auth.authorize(js.map({'client_id':_CLIENT_ID,'scope':_SCOPE,'immediate':true}),js.context.handleAuthResult);});});}/** * Load the google client api, future returns * map results. */staticFuture<Map>load(){varcompleter=newCompleter();_createScopedCallbacks(completer);_loadScript();returncompleter.future;}}
The calling of load() returns a future with our authentication results. Eases the process of having the google drive scope available to the client application.
A simple dart Drive class created gives access to the list call from the javascript apis. The interesting point for me to learn is knowing that client.js has to load the drive api. In the load() future we call js.context.gapi.client.load which will take the api and version to be loaded for the client application.
/** * Sample google drive class. */classDrive{js.Proxy_drive;boolget_isLoaded=>_drive!=null;/** * Load the gapi.client.drive api. */Future<bool>load(){varcompleter=newCompleter();js.scoped((){js.context.gapi.client.load('drive','v2',newjs.Callback.once((){_drive=js.context.gapi.client.drive;js.retain(_drive);completer.complete(true);}));});returncompleter.future;}/** * Check if gapi.client.drive is loaded, if not * load before executing. */void_loadederExecute(Functionfunction){if(_isLoaded){function();}else{load().then((s){if(s==true){function();}else{throw"loadedExecute failed";}});}}/** * List files with gapi.drive.files.list() */Future<Map>list(){varcompleter=newCompleter();_loadederExecute((){js.scoped((){varrequest=_drive.files.list();request.execute(newjs.Callback.once((js.ProxyjsonResp,varrawResp){Mapm=JSON.parse(js.context.JSON.stringify(jsonResp));completer.complete(m);}));});});returncompleter.future;}}
With the following sugar classes it was easy to just list files from my google drive account.